Files
ProjectEleri/Source/ProjectEleri/Items/SeedBedActor.h

350 lines
14 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProjectEleri/System/TimeOfDayStruct.h"
#include "SeedBedActor.generated.h"
class UNiagaraSystem;
class UNiagaraComponent;
class UEleriPaperSpriteComponent;
class UPaperSpriteComponent;
class ATimeOfDayManager;
class UDecalComponent;
class UMaterialInstanceDynamic;
class UMaterialInterface;
class UStaticMesh;
class UStaticMeshComponent;
class AStaticMeshActor;
class UItemDataAsset;
struct FSeedStage;
/** In game time units the seed bed works with, the seed data itself stays authored in days */
constexpr int32 SeedBedHoursPerDay = 24;
constexpr int32 SeedBedMinutesPerHour = 60;
constexpr int32 SeedBedMinutesPerDay = SeedBedHoursPerDay * SeedBedMinutesPerHour;
/** In game growth hours a single watering covers, one watering makes a plant grow for a whole day */
constexpr int32 SeedBedMoistureHoursPerWatering = SeedBedHoursPerDay;
/** Called every time a slot of the seed bed has been planted, cleared or visually refreshed */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSeedBedSlotChanged, FIntVector2, SlotCoordinate, bool, bHasPlant);
USTRUCT(BlueprintType)
struct FSeedBedSeedSlotData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient)
UItemDataAsset* ItemDataAsset;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FPrimaryAssetId ItemAssetID;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 GrowTime = 0;
/** In game hours of growth the plant has collected, the seed data grows for GrowTime days */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 SeedAgeHours = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 WateredTimes = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bBloomed = false;
/** In game growth hours the last watering still covers, one watering covers a whole in game day */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MoistureHoursRemaining = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bDied = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bHighQuality = false;
/** In game time of the last watering, the death check measures the days that passed since then */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FTimeOfDayData LastWateredTime;
};
USTRUCT(BlueprintType)
struct FSeedBedSeedSlot
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FSeedBedSeedSlotData SlotData;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UDecalComponent* SeedBedDecal = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UMaterialInstanceDynamic* SeedBedDynamicMat = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UEleriPaperSpriteComponent* PlantSprite = nullptr;
/** Mesh actor of a slot that reached a stage which brings a mesh, it is created once for such a stage and only
hidden or re-used after that, a stage without a mesh leaves the slot to its sprite */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
AStaticMeshActor* PlantMeshActor = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UNiagaraComponent* NiagaraComponent = nullptr;
};
USTRUCT(BlueprintType)
struct FSeedBedSaveData
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 Index = 0;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TMap<FIntVector2, FSeedBedSeedSlotData> SlotData;
/** In game time the bed last applied growth to its slots, the resume math starts from this stamp */
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FTimeOfDayData LastUpdateTime;
};
UCLASS()
class PROJECTELERI_API ASeedBedActor : public AActor
{
GENERATED_BODY()
public:
ASeedBedActor();
virtual void Tick(float DeltaTime) override;
//------------------------------------------------------------------------------------------------
// DATA
//------------------------------------------------------------------------------------------------
/** Every slot of the seed bed, keyed by its X / Y coordinate inside the grid */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TMap<FIntVector2, FSeedBedSeedSlot> SeedBedSeedSlotsMap;
UPROPERTY(BlueprintAssignable)
FOnSeedBedSlotChanged OnSeedBedSlotChanged;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 SeedBedIndex = 0;
//------------------------------------------------------------------------------------------------
// SEED BED SETUP
//------------------------------------------------------------------------------------------------
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Setup")
TSoftObjectPtr<UMaterialInterface> SeedBedDecalMaterial;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Setup")
FVector SeedBedDecalSize = FVector(40.f, 40.f, 40.f);
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Setup")
FRotator SeedBedDecalRotation = FRotator(-90.f, 0.f, 0.f);
/** Fallback mesh used when the planted seed has no growth stage set up */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Setup")
TSoftObjectPtr<UMaterialInterface> DefaultPlantSpriteMaterial;
/** Dimensions of the grid generated by GenerateSeedBedGrid, X = columns, Y = rows */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
FIntVector2 GridSize = FIntVector2(3, 3);
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
TSoftObjectPtr<UNiagaraSystem> PlantBloomedVfx;
//------------------------------------------------------------------------------------------------
// HIGHLIGHT DECAL
//------------------------------------------------------------------------------------------------
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Highlight")
TSoftObjectPtr<UMaterialInterface> HighlightDecalMaterial;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Highlight")
FVector HighlightDecalSize = FVector(40.f, 40.f, 40.f);
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed Bed|Highlight")
FRotator HighlightDecalRotation = FRotator(-90.f, 0.f, 0.f);
//------------------------------------------------------------------------------------------------
// GRID GENERATION
//------------------------------------------------------------------------------------------------
/** Generates the seed bed grid using the GridSize set up above (editor helper) */
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
void GenerateSeedBedGrid();
/** Generates / regenerates the seed bed grid with an explicit size (editor helper) */
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
void GenerateSeedBedGridSized(int32 Columns, int32 Rows, bool bRebuildExistingSlots);
/** Destroys every slot of the seed bed and clears the slot map (editor helper) */
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
void ClearSeedBedGrid();
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
int32 GetSlotCount() const { return SeedBedSeedSlotsMap.Num(); }
//------------------------------------------------------------------------------------------------
// PLANTING
//------------------------------------------------------------------------------------------------
/** Plants a seed into the given slot, returns false when the slot is unknown or already taken */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
bool PlantSeedInSlot(FIntVector2 SlotCoordinate, UItemDataAsset* SeedData, bool bHighQuality = false);
/** Empties a slot, optionally removing the plant mesh as well */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
void ClearSeedFromSlot(FIntVector2 SlotCoordinate, bool bRemovePlantMesh = true);
/** Waters a single slot: refills the soil and restarts the countdown the death check measures */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
void WaterSeedInSlot(FIntVector2 SlotCoordinate);
/** Waters every planted slot of the bed */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
void WaterAllSeedSlots();
/** Waters every planted slot whose world location lies within the given radius around a world point.
* Returns the number of slots that were watered and caches the new slot data into the game state. */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
int32 WaterSlotsInRadius(const FVector& WorldLocation, float Radius);
/** Collects the coordinates of every planted slot whose world location lies within the given radius.
* Returns the number of collected slots, OutSlotCoordinates is emptied first. */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
int32 GatherSlotsInRadius(const FVector& WorldLocation, float Radius, TArray<FIntVector2>& OutSlotCoordinates) const;
/** Finds the free slot that is closest to the given world location */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
bool FindClosestFreeSlot(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate) const;
/** Same as FindClosestFreeSlot, but also reports how far away the slot is, as a squared distance in XY */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
bool FindClosestFreeSlotWithDistance(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate, float& OutDistanceSquared) const;
/** World location a plant of the given slot is placed at */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
bool GetSlotWorldLocation(FIntVector2 SlotCoordinate, FVector& OutWorldLocation) const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
bool IsSlotFree(FIntVector2 SlotCoordinate) const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
int32 GetFreeSlotCount() const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
bool HasFreeSlot() const { return GetFreeSlotCount() > 0; }
//------------------------------------------------------------------------------------------------
// TIME
//------------------------------------------------------------------------------------------------
/** The in game time the bed last applied growth for, the stamp the resume math continues from */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed|Time")
FTimeOfDayData GetLastUpdateTime() const { return LastUpdateTime; }
/** Applies the given amount of in game hours to every plant of the bed, used by the hour tick and the resume catch up */
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Time")
void AdvanceSeedBedByHours(int32 Hours);
//------------------------------------------------------------------------------------------------
// VISUALS
//------------------------------------------------------------------------------------------------
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed|Highlight")
bool IsHighlightActive() const { return bPlantingHighlightActive; }
/** Re-applies mesh, material and decal of a single slot */
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Visuals")
void RefreshSlotVisual(const FIntVector2& SlotCoordinate);
/** Re-applies mesh, material and decal of every slot of the bed */
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Visuals")
void RefreshAllSlotVisuals();
/** True when the given asset can be planted in a seed bed at all */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
static bool IsPlantableSeedData(const UItemDataAsset* SeedData);
FIntVector2 GetHighlightedSlotIndex() const { return HighlightedSlotIndex; }
void HighlightSlot(FIntVector2 SlotCoordinate);
void StopHighlightingSlot();
FSeedBedSaveData PrepareSeedSaveData();
void PropagateSeedSaveData(const FSeedBedSaveData& SeedSaveData);
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/** Creates (or rebuilds) the components of a single slot and adds it to the slot map */
FSeedBedSeedSlot& CreateSlot(FIntVector2 SlotCoordinate, bool bRebuildIfExisting);
/** Re-applies material, plant mesh and highlight decal of a slot */
void ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& Slot);
/** Places, moves or hides the mesh actor of a slot, a null stage or a stage whose mesh has not streamed in yet
hides it. The actor is created once for the first stage that brings a mesh and re-used by every later stage */
void ApplySlotPlantMesh(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& Slot, const FSeedStage* Stage);
/** Resolves which growth stage mesh of the planted seed has to be shown */
int32 ResolveGrowthStageIndex(const FSeedBedSeedSlot& Slot) const;
/** Name of a mesh / decal component of a slot, names have to stay stable for serialization */
static FName GetSlotComponentName(FIntVector2 SlotCoordinate, const TCHAR* Prefix);
FVector GetSlotRelativeLocation(FIntVector2 SlotCoordinate) const;
/** Subscribes to the time of day actor, the bed ages its plants on every in game hour that passes */
void BindToTimeOfDay();
/** Applies one in game hour of growth, moisture handling and death checks to a single slot */
void ApplyHourPassedToSlot(FSeedBedSeedSlot& Slot, const FTimeOfDayData& Now, const FIntVector2& SlotCoordinate);
/** Catches the plants up on the in game time that passed while the bed was not in the world */
void ApplyOfflineProgression();
/** Stores the current in game time, the manager may already be gone when the bed is torn down */
void CacheCurrentTimeOfDay();
/** Hour tick of the time of day actor */
UFUNCTION()
void HandleHourPassed();
/** Whether the highlight decals of the bed are currently shown */
bool bPlantingHighlightActive = false;
UPROPERTY(Transient)
FIntVector2 HighlightedSlotIndex = FIntVector2::NoneValue;
/** Decal used to highlight this slot when the player is looking for a place to plant a seed */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UDecalComponent* HighlightDecal = nullptr;
/** Days a plant survives without being watered before it dies */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Time")
int32 DaysWithoutWaterToDie = 2;
/** In game time the bed last applied growth for, written on every hour and again when the bed leaves the world */
UPROPERTY(Transient)
FTimeOfDayData LastUpdateTime;
/** Copy of the last time the manager reported, used when the bed is torn down after the manager is gone */
UPROPERTY(Transient)
FTimeOfDayData LastKnownTimeOfDay;
UPROPERTY()
ATimeOfDayManager* TimeOfDayManager = nullptr;
};