350 lines
14 KiB
C++
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;
|
|
|
|
};
|
|
|