Implement mesh for crop and some fixes
This commit is contained in:
BIN
Content/GameData/Items/Seeds/I_Lavender_Seed.uasset
LFS
Normal file
BIN
Content/GameData/Items/Seeds/I_Lavender_Seed.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/VFX/NI_CropReady.uasset
LFS
BIN
Content/VFX/NI_CropReady.uasset
LFS
Binary file not shown.
@@ -141,28 +141,28 @@ public:
|
|||||||
/*--------------------------------------------------------------------------------------------------------------------------*/
|
/*--------------------------------------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/** What base will the seed spawn when planting initiates */
|
/** What base will the seed spawn when planting initiates */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
BaseType TypeOfBase;
|
BaseType TypeOfBase;
|
||||||
|
|
||||||
/** Does this ingredient have a seed? Ignored in potions */
|
/** Does this ingredient have a seed? Ignored in potions */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
bool IsSeed = true;
|
bool IsSeed = true;
|
||||||
|
|
||||||
/** What is the base cost of the seed for this ingredient, ignored in potions */
|
/** What is the base cost of the seed for this ingredient, ignored in potions */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Inventory, Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Inventory, Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
int32 SeedCost;
|
int32 SeedCost;
|
||||||
|
|
||||||
/** Grow time for a seed in game days, if the seed is a tree or wine this is the growth for them */
|
/** Grow time for a seed in game days, if the seed is a tree or wine this is the growth for them */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, DisplayName = "Grow Time", Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Inventory, Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, DisplayName = "Grow Time", Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Inventory, Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
int32 GrowTimeSelf;
|
int32 GrowTimeSelf;
|
||||||
|
|
||||||
/** Stages of a plant, set a mesh for certain days */
|
/** Stages of a plant, set a mesh for certain days */
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
TArray<FSeedStage> SeedStages;
|
TArray<FSeedStage> SeedStages;
|
||||||
|
|
||||||
/** What is the preferred temperature in alchemy that the ingredient releases it's
|
/** What is the preferred temperature in alchemy that the ingredient releases it's
|
||||||
properties in the most effective way*/
|
properties in the most effective way*/
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Plant", EditConditionHides))
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Seed", AssetRegistrySearchable, meta = (AssetBundles = "Book", EditCondition="ItemCategory==Category::Seed", EditConditionHides))
|
||||||
int32 PreferredTemperature;
|
int32 PreferredTemperature;
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------------------------------------------------------*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "Engine/DataAsset.h"
|
#include "Engine/DataAsset.h"
|
||||||
#include "PaperSprite.h"
|
#include "PaperSprite.h"
|
||||||
#include "Engine/DataTable.h"
|
#include "Engine/DataTable.h"
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
|
||||||
#include "ItemContainer.generated.h"
|
#include "ItemContainer.generated.h"
|
||||||
|
|
||||||
@@ -106,6 +107,12 @@ struct FSeedStage {
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||||
TSoftObjectPtr<UPaperSprite> Sprite;
|
TSoftObjectPtr<UPaperSprite> Sprite;
|
||||||
|
|
||||||
|
/** Mesh this stage is shown with. A stage that has one is placed as a mesh actor on the seed bed and never shows
|
||||||
|
the sprite of the stage, a stage without one keeps showing its sprite. The mesh stands on the slot, the offset
|
||||||
|
of the stage moves it and SpriteScale sizes it. */
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||||
|
TSoftObjectPtr<UStaticMesh> StageMesh;
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||||
float SpriteScale = 1.f;
|
float SpriteScale = 1.f;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
#include "ProjectEleri/Settings/EleriGameSettings.h"
|
#include "ProjectEleri/Settings/EleriGameSettings.h"
|
||||||
#include "ProjectEleri/System/MainBlueprintFunctionLibrary.h"
|
#include "ProjectEleri/System/MainBlueprintFunctionLibrary.h"
|
||||||
|
|
||||||
|
UItemDatabase* UItemDatabase::Get(const UObject* WorldContext)
|
||||||
|
{
|
||||||
|
return UGameplayStatics::GetGameInstance(WorldContext)->GetSubsystem<UItemDatabase>();
|
||||||
|
}
|
||||||
|
|
||||||
UWorld* UItemDatabase::GetWorld() const {
|
UWorld* UItemDatabase::GetWorld() const {
|
||||||
if (const UGameInstance* GameInstance = GetGameInstance()) {
|
if (const UGameInstance* GameInstance = GetGameInstance()) {
|
||||||
return GameInstance->GetWorld();
|
return GameInstance->GetWorld();
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ class PROJECTELERI_API UItemDatabase : public UGameInstanceSubsystem
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
static UItemDatabase* Get(const UObject* WorldContext);
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly)
|
UPROPERTY(EditDefaultsOnly)
|
||||||
UItemContainer* ItemDatabase;
|
UItemContainer* ItemDatabase;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "Components/StaticMeshComponent.h"
|
#include "Components/StaticMeshComponent.h"
|
||||||
#include "DataAssets/ItemDataAsset.h"
|
#include "DataAssets/ItemDataAsset.h"
|
||||||
#include "Engine/StaticMesh.h"
|
#include "Engine/StaticMesh.h"
|
||||||
|
#include "Engine/StaticMeshActor.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "ItemContainer.h"
|
#include "ItemContainer.h"
|
||||||
#include "NiagaraComponent.h"
|
#include "NiagaraComponent.h"
|
||||||
@@ -24,14 +25,18 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
/** Destroys the components of a slot and clears the pointers so nothing stale is left in the map */
|
/** Destroys the components and the plant mesh actor of a slot and clears the pointers so nothing stale is left */
|
||||||
void DestroySlotComponents(FSeedBedSeedSlot& Slot)
|
void DestroySlotComponents(FSeedBedSeedSlot& Slot)
|
||||||
{
|
{
|
||||||
if (Slot.SeedBedDecal) Slot.SeedBedDecal->DestroyComponent();
|
if (Slot.SeedBedDecal) Slot.SeedBedDecal->DestroyComponent();
|
||||||
if (Slot.PlantSprite) Slot.PlantSprite->DestroyComponent();
|
if (Slot.PlantSprite) Slot.PlantSprite->DestroyComponent();
|
||||||
|
|
||||||
|
// The plant mesh is an actor of its own, it does not go away with the components of the slot
|
||||||
|
if (IsValid(Slot.PlantMeshActor)) Slot.PlantMeshActor->Destroy();
|
||||||
|
|
||||||
Slot.SeedBedDecal = nullptr;
|
Slot.SeedBedDecal = nullptr;
|
||||||
Slot.PlantSprite = nullptr;
|
Slot.PlantSprite = nullptr;
|
||||||
|
Slot.PlantMeshActor = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Slots are created at runtime as well, so the components are created with stable, deterministic names */
|
/** Slots are created at runtime as well, so the components are created with stable, deterministic names */
|
||||||
@@ -218,6 +223,17 @@ void ASeedBedActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
GameState->CacheSeedData(SeedBedIndex, PrepareSeedSaveData());
|
GameState->CacheSeedData(SeedBedIndex, PrepareSeedSaveData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The plant meshes are actors of their own, they are taken down together with the bed. This runs for destroyed,
|
||||||
|
// for streamed out and for shut down beds alike, so no plant mesh is ever left behind in the world
|
||||||
|
for (TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||||
|
{
|
||||||
|
if (IsValid(SlotPair.Value.PlantMeshActor))
|
||||||
|
{
|
||||||
|
SlotPair.Value.PlantMeshActor->Destroy();
|
||||||
|
SlotPair.Value.PlantMeshActor = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Super::EndPlay(EndPlayReason);
|
Super::EndPlay(EndPlayReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,6 +686,9 @@ void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlo
|
|||||||
// A plant that died or was harvested keeps its mesh, the slot is only emptied when it is cleared for real
|
// A plant that died or was harvested keeps its mesh, the slot is only emptied when it is cleared for real
|
||||||
Slot.PlantSprite->SetVisibility(false);
|
Slot.PlantSprite->SetVisibility(false);
|
||||||
Slot.PlantSprite->DecalComponent->SetVisibility(false);
|
Slot.PlantSprite->DecalComponent->SetVisibility(false);
|
||||||
|
|
||||||
|
// A slot without a living plant has no stage at all, so the mesh actor of the slot is hidden as well
|
||||||
|
ApplySlotPlantMesh(SlotCoordinate, Slot, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,29 +700,42 @@ void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlo
|
|||||||
{
|
{
|
||||||
Slot.PlantSprite->SetVisibility(false);
|
Slot.PlantSprite->SetVisibility(false);
|
||||||
Slot.PlantSprite->DecalComponent->SetVisibility(false);
|
Slot.PlantSprite->DecalComponent->SetVisibility(false);
|
||||||
|
|
||||||
|
ApplySlotPlantMesh(SlotCoordinate, Slot, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UItemDataAsset* SeedData = Slot.SlotData.ItemDataAsset;
|
const UItemDataAsset* SeedData = Slot.SlotData.ItemDataAsset;
|
||||||
|
const FSeedStage* Stage = nullptr;
|
||||||
if (IsValid(SeedData) && SeedData->SeedStages.IsValidIndex(StageIndex))
|
if (IsValid(SeedData) && SeedData->SeedStages.IsValidIndex(StageIndex))
|
||||||
{
|
{
|
||||||
PlantScale *= FVector(SeedData->SeedStages[StageIndex].SpriteScale);
|
Stage = &SeedData->SeedStages[StageIndex];
|
||||||
PlantOffset += SeedData->SeedStages[StageIndex].Offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Stage)
|
||||||
|
{
|
||||||
|
PlantScale *= FVector(Stage->SpriteScale);
|
||||||
|
PlantOffset += Stage->Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A stage that carries a mesh is placed as a mesh actor of its own, its sprite stays the fallback of mesh stages
|
||||||
|
// whose mesh is still streaming in, so a stage with a mesh never flashes the sprite of another stage
|
||||||
|
const bool bStageHasMesh = Stage != nullptr && !Stage->StageMesh.IsNull();
|
||||||
|
ApplySlotPlantMesh(SlotCoordinate, Slot, Stage);
|
||||||
|
|
||||||
Slot.PlantSprite->SetRelativeScale3D(PlantScale);
|
Slot.PlantSprite->SetRelativeScale3D(PlantScale);
|
||||||
Slot.PlantSprite->SetRelativeLocation(PlantOffset);
|
Slot.PlantSprite->SetRelativeLocation(PlantOffset);
|
||||||
Slot.PlantSprite->UpdateRelativeLocation(FVector(PlantOffset.X, PlantOffset.Y, 0.f));
|
Slot.PlantSprite->UpdateRelativeLocation(FVector(PlantOffset.X, PlantOffset.Y, 0.f));
|
||||||
|
|
||||||
if (SeedData->SeedStages[StageIndex].Sprite.IsValid())
|
if (Stage->Sprite.IsValid())
|
||||||
{
|
{
|
||||||
Slot.PlantSprite->SetSprite(SeedData->SeedStages[StageIndex].Sprite.Get());
|
Slot.PlantSprite->SetSprite(Stage->Sprite.Get());
|
||||||
Slot.PlantSprite->SetVisibility(true);
|
Slot.PlantSprite->SetVisibility(!bStageHasMesh);
|
||||||
Slot.PlantSprite->DecalComponent->SetVisibility(true);
|
Slot.PlantSprite->DecalComponent->SetVisibility(!bStageHasMesh);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SeedData->SeedStages[StageIndex].Sprite.LoadAsync(FLoadSoftObjectPathAsyncDelegate::CreateWeakLambda(this, [this, SlotCoordinate](const FSoftObjectPath& Path, UObject* SpriteObj)
|
Stage->Sprite.LoadAsync(FLoadSoftObjectPathAsyncDelegate::CreateWeakLambda(this, [this, SlotCoordinate](const FSoftObjectPath& Path, UObject* SpriteObj)
|
||||||
{
|
{
|
||||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||||
if (!Slot || !IsValid(Slot->PlantSprite)) return;
|
if (!Slot || !IsValid(Slot->PlantSprite)) return;
|
||||||
@@ -715,6 +747,23 @@ void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlo
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The mesh of a stage that carries one is streamed in like the sprite and re-resolves the visuals of the slot,
|
||||||
|
// so it is placed, scaled and shown the moment it is there. A stage without a mesh keeps the sprite alone
|
||||||
|
if (Stage && !Stage->StageMesh.IsValid())
|
||||||
|
{
|
||||||
|
Stage->StageMesh.LoadAsync(FLoadSoftObjectPathAsyncDelegate::CreateWeakLambda(this, [this, SlotCoordinate](const FSoftObjectPath& Path, UObject* MeshObj)
|
||||||
|
{
|
||||||
|
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||||
|
if (!Slot) return;
|
||||||
|
|
||||||
|
UStaticMesh* StageMesh = Cast<UStaticMesh>(MeshObj);
|
||||||
|
if (!IsValid(StageMesh)) return;
|
||||||
|
|
||||||
|
// The slot may have moved on to another stage while the mesh streamed in, so the visuals are re-resolved
|
||||||
|
ApplySlotVisuals(SlotCoordinate, *Slot);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (Slot.SlotData.bBloomed && !Slot.NiagaraComponent->IsActive())
|
if (Slot.SlotData.bBloomed && !Slot.NiagaraComponent->IsActive())
|
||||||
{
|
{
|
||||||
Slot.NiagaraComponent->SetAsset(nullptr);
|
Slot.NiagaraComponent->SetAsset(nullptr);
|
||||||
@@ -743,6 +792,69 @@ void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void ASeedBedActor::ApplySlotPlantMesh(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& Slot, const FSeedStage* Stage)
|
||||||
|
{
|
||||||
|
UStaticMesh* StageMesh = Stage ? Stage->StageMesh.Get() : nullptr;
|
||||||
|
|
||||||
|
// A slot without a stage, or with a stage whose mesh has not streamed in yet, shows nothing but its sprite
|
||||||
|
if (!IsValid(StageMesh))
|
||||||
|
{
|
||||||
|
if (IsValid(Slot.PlantMeshActor))
|
||||||
|
{
|
||||||
|
Slot.PlantMeshActor->SetActorHiddenInGame(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRotator PlantRotation = GetActorRotation();
|
||||||
|
const FVector PlantLocation = GetActorTransform().TransformPosition(GetSlotRelativeLocation(SlotCoordinate) + Stage->Offset);
|
||||||
|
|
||||||
|
if (!IsValid(Slot.PlantMeshActor))
|
||||||
|
{
|
||||||
|
FActorSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.Owner = this;
|
||||||
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||||
|
|
||||||
|
// The mesh is spawned once for the slot and stays with it, every later stage only swaps the mesh it shows
|
||||||
|
Slot.PlantMeshActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), FTransform(PlantRotation, PlantLocation), SpawnParams);
|
||||||
|
if (!IsValid(Slot.PlantMeshActor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A plant appears and disappears while the game runs, so the actor can never keep the static mobility it spawns with
|
||||||
|
Slot.PlantMeshActor->SetMobility(EComponentMobility::Movable);
|
||||||
|
Slot.PlantMeshActor->AttachToActor(this, FAttachmentTransformRules::KeepWorldTransform);
|
||||||
|
|
||||||
|
if (UStaticMeshComponent* MeshComponent = Slot.PlantMeshActor->GetStaticMeshComponent())
|
||||||
|
{
|
||||||
|
// The plant is decoration only, it blocks nothing, moves no navigation and catches none of the bed decals
|
||||||
|
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
MeshComponent->SetCanEverAffectNavigation(false);
|
||||||
|
MeshComponent->SetReceivesDecals(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UStaticMeshComponent* MeshComponent = Slot.PlantMeshActor->GetStaticMeshComponent();
|
||||||
|
if (!MeshComponent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshComponent->SetStaticMesh(StageMesh);
|
||||||
|
|
||||||
|
// The mesh stands on the slot itself, the offset of the stage moves it and the sprite scale of the stage sizes it
|
||||||
|
Slot.PlantMeshActor->SetActorLocationAndRotation(PlantLocation, PlantRotation);
|
||||||
|
Slot.PlantMeshActor->SetActorScale3D(FVector(Stage->SpriteScale));
|
||||||
|
Slot.PlantMeshActor->SetActorHiddenInGame(false);
|
||||||
|
}
|
||||||
void ASeedBedActor::RefreshSlotVisual(const FIntVector2& SlotCoordinate)
|
void ASeedBedActor::RefreshSlotVisual(const FIntVector2& SlotCoordinate)
|
||||||
{
|
{
|
||||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ class UMaterialInstanceDynamic;
|
|||||||
class UMaterialInterface;
|
class UMaterialInterface;
|
||||||
class UStaticMesh;
|
class UStaticMesh;
|
||||||
class UStaticMeshComponent;
|
class UStaticMeshComponent;
|
||||||
|
class AStaticMeshActor;
|
||||||
class UItemDataAsset;
|
class UItemDataAsset;
|
||||||
|
struct FSeedStage;
|
||||||
|
|
||||||
/** In game time units the seed bed works with, the seed data itself stays authored in days */
|
/** In game time units the seed bed works with, the seed data itself stays authored in days */
|
||||||
constexpr int32 SeedBedHoursPerDay = 24;
|
constexpr int32 SeedBedHoursPerDay = 24;
|
||||||
@@ -85,6 +87,11 @@ struct FSeedBedSeedSlot
|
|||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
UEleriPaperSpriteComponent* PlantSprite = nullptr;
|
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)
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||||
UNiagaraComponent* NiagaraComponent = nullptr;
|
UNiagaraComponent* NiagaraComponent = nullptr;
|
||||||
@@ -284,6 +291,10 @@ protected:
|
|||||||
/** Re-applies material, plant mesh and highlight decal of a slot */
|
/** Re-applies material, plant mesh and highlight decal of a slot */
|
||||||
void ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& 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 */
|
/** Resolves which growth stage mesh of the planted seed has to be shown */
|
||||||
int32 ResolveGrowthStageIndex(const FSeedBedSeedSlot& Slot) const;
|
int32 ResolveGrowthStageIndex(const FSeedBedSeedSlot& Slot) const;
|
||||||
|
|
||||||
|
|||||||
@@ -1184,14 +1184,13 @@ void AEleriPlayerController::OnHarvestSeed(const FInputActionValue &Value)
|
|||||||
|
|
||||||
// PLANTING
|
// PLANTING
|
||||||
|
|
||||||
void AEleriPlayerController::SetSeedToPlant(UItemDataAsset *SeedData, bool bHighQuality)
|
void AEleriPlayerController::SetSeedToPlant(const FItemStack& Stack)
|
||||||
{
|
{
|
||||||
// Anything that is not a seed that fits into a seed bed is rejected, the UI cannot leave us in a bad state
|
// Anything that is not a seed that fits into a seed bed is rejected, the UI cannot leave us in a bad state
|
||||||
SeedToPlant = ASeedBedActor::IsPlantableSeedData(SeedData) ? SeedData : nullptr;
|
SeedToPlant = Stack;
|
||||||
bSeedToPlantHighQuality = IsValid(SeedToPlant) && bHighQuality;
|
|
||||||
|
|
||||||
// Selecting a seed is what starts the planting, the beds in range light up for it right away
|
// Selecting a seed is what starts the planting, the beds in range light up for it right away
|
||||||
if (IsValid(SeedToPlant))
|
if (SeedToPlant.ItemId.IsValid())
|
||||||
{
|
{
|
||||||
SetPlantingMode(true);
|
SetPlantingMode(true);
|
||||||
}
|
}
|
||||||
@@ -1199,12 +1198,7 @@ void AEleriPlayerController::SetSeedToPlant(UItemDataAsset *SeedData, bool bHigh
|
|||||||
|
|
||||||
void AEleriPlayerController::ClearSeedToPlant()
|
void AEleriPlayerController::ClearSeedToPlant()
|
||||||
{
|
{
|
||||||
if (!IsValid(SeedToPlant) && !bSeedToPlantHighQuality)
|
SeedToPlant = FItemStack();
|
||||||
return;
|
|
||||||
|
|
||||||
SeedToPlant = nullptr;
|
|
||||||
bSeedToPlantHighQuality = false;
|
|
||||||
|
|
||||||
// Nothing is being planted anymore, so the beds stop being highlighted
|
// Nothing is being planted anymore, so the beds stop being highlighted
|
||||||
if (IsPlantingModeActive())
|
if (IsPlantingModeActive())
|
||||||
{
|
{
|
||||||
@@ -1231,7 +1225,8 @@ void AEleriPlayerController::TryPlantSelectedSeed()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// A seed that is not plantable anymore, for example when the inventory was emptied, drops out of planting
|
// A seed that is not plantable anymore, for example when the inventory was emptied, drops out of planting
|
||||||
if (!ASeedBedActor::IsPlantableSeedData(SeedToPlant))
|
UItemDataAsset* Item = UItemDatabase::Get(this)->GetDataAssetFromId(SeedToPlant.ItemId);
|
||||||
|
if (!ASeedBedActor::IsPlantableSeedData(Item))
|
||||||
{
|
{
|
||||||
ClearSeedToPlant();
|
ClearSeedToPlant();
|
||||||
return;
|
return;
|
||||||
@@ -1240,9 +1235,7 @@ void AEleriPlayerController::TryPlantSelectedSeed()
|
|||||||
ASeedBedActor *SeedBed = PlayerCharacter->GetClosestSeedBedActor();
|
ASeedBedActor *SeedBed = PlayerCharacter->GetClosestSeedBedActor();
|
||||||
if (!IsValid(SeedBed))
|
if (!IsValid(SeedBed))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const FVector PlayerLocation = PlayerCharacter->GetActorLocation();
|
|
||||||
|
|
||||||
FIntVector2 SlotCoordinate = SeedBed->GetHighlightedSlotIndex();
|
FIntVector2 SlotCoordinate = SeedBed->GetHighlightedSlotIndex();
|
||||||
if (SlotCoordinate == FIntVector2::NoneValue)
|
if (SlotCoordinate == FIntVector2::NoneValue)
|
||||||
return;
|
return;
|
||||||
@@ -1252,16 +1245,15 @@ void AEleriPlayerController::TryPlantSelectedSeed()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// The seed is paid for before it is planted, so a slot can never hold a seed the inventory never had
|
// The seed is paid for before it is planted, so a slot can never hold a seed the inventory never had
|
||||||
const FPrimaryAssetId SeedAssetId = SeedToPlant->GetPrimaryAssetId();
|
if (Inventory->HasItem(SeedToPlant.ItemId, SeedToPlant.HQ) <= 0)
|
||||||
if (Inventory->HasItem(SeedAssetId, true) <= 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!SeedBed->PlantSeedInSlot(SlotCoordinate, SeedToPlant, bSeedToPlantHighQuality))
|
if (!SeedBed->PlantSeedInSlot(SlotCoordinate, Item, SeedToPlant.HQ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Inventory->RemoveItemFromInventory(SeedAssetId, 1, true);
|
Inventory->RemoveItemFromInventory(SeedToPlant.ItemId, 1, SeedToPlant.HQ);
|
||||||
|
|
||||||
if (Inventory->HasItem(SeedAssetId, true) == INDEX_NONE)
|
if (Inventory->HasItem(SeedToPlant.ItemId, SeedToPlant.HQ) == INDEX_NONE)
|
||||||
{
|
{
|
||||||
ClearSeedToPlant();
|
ClearSeedToPlant();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ void AMyCharacter::Tick(float DeltaTime)
|
|||||||
{
|
{
|
||||||
Super::Tick(DeltaTime);
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
if (bIsPlanting)
|
if (GetIsPlanting())
|
||||||
{
|
{
|
||||||
// The reach is measured from the closest free slot and not from the center of the bed: a bed is as big as
|
// The reach is measured from the closest free slot and not from the center of the bed: a bed is as big as
|
||||||
// its grid, so its outer slots sit up to 1273 units away from that center and would never be in reach
|
// its grid, so its outer slots sit up to 1273 units away from that center and would never be in reach
|
||||||
@@ -354,9 +354,9 @@ void AMyCharacter::InitiateLoad()
|
|||||||
|
|
||||||
void AMyCharacter::SetPlanting(bool bActive)
|
void AMyCharacter::SetPlanting(bool bActive)
|
||||||
{
|
{
|
||||||
bIsPlanting = bActive;
|
CurrentlyDoing = bActive? ECurrentlyDoing::PLANTING : ECurrentlyDoing::NOTHING;
|
||||||
|
|
||||||
if (bIsPlanting)
|
if (GetIsPlanting())
|
||||||
{
|
{
|
||||||
// Planting takes over the interact input, so keep the plugin's interaction UI out of the way.
|
// Planting takes over the interact input, so keep the plugin's interaction UI out of the way.
|
||||||
// The plugin knows nothing about planting - it just holds/releases a generic UI block.
|
// The plugin knows nothing about planting - it just holds/releases a generic UI block.
|
||||||
|
|||||||
@@ -386,17 +386,14 @@ public:
|
|||||||
// PLANTING
|
// PLANTING
|
||||||
/** Seed the player selected in the inventory, it is what the planting action plants */
|
/** Seed the player selected in the inventory, it is what the planting action plants */
|
||||||
UFUNCTION(BlueprintCallable)
|
UFUNCTION(BlueprintCallable)
|
||||||
void SetSeedToPlant(UItemDataAsset* SeedData, bool bHighQuality = false);
|
void SetSeedToPlant(const FItemStack& Stack);
|
||||||
|
|
||||||
/** Forgets the selected seed, the seed beds stop being highlighted for it */
|
/** Forgets the selected seed, the seed beds stop being highlighted for it */
|
||||||
UFUNCTION(BlueprintCallable)
|
UFUNCTION(BlueprintCallable)
|
||||||
void ClearSeedToPlant();
|
void ClearSeedToPlant();
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||||
UItemDataAsset* GetSeedToPlant() const { return SeedToPlant; }
|
const FItemStack& GetSeedToPlant() const { return SeedToPlant; }
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
|
||||||
bool IsSeedToPlantHighQuality() const { return bSeedToPlantHighQuality; }
|
|
||||||
|
|
||||||
/** Plants the selected seed into the closest free slot of the closest seed bed, removes it from the inventory */
|
/** Plants the selected seed into the closest free slot of the closest seed bed, removes it from the inventory */
|
||||||
UFUNCTION(BlueprintCallable)
|
UFUNCTION(BlueprintCallable)
|
||||||
@@ -411,10 +408,7 @@ public:
|
|||||||
void SetPlantingMode(bool bActive);
|
void SetPlantingMode(bool bActive);
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Planting")
|
UPROPERTY(BlueprintReadOnly, Category = "Planting")
|
||||||
UItemDataAsset* SeedToPlant;
|
FItemStack SeedToPlant;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category = "Planting")
|
|
||||||
bool bSeedToPlantHighQuality = false;
|
|
||||||
|
|
||||||
void OnPlantSeedAction(const FInputActionValue& Value);
|
void OnPlantSeedAction(const FInputActionValue& Value);
|
||||||
|
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ protected:
|
|||||||
|
|
||||||
//PLANTING STUFF START
|
//PLANTING STUFF START
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
bool bIsPlanting = false;
|
|
||||||
UPROPERTY()
|
|
||||||
TArray<ASeedBedActor*> SeedBedActors;
|
TArray<ASeedBedActor*> SeedBedActors;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
ASeedBedActor* LastClosestSeedBedActor = nullptr;
|
ASeedBedActor* LastClosestSeedBedActor = nullptr;
|
||||||
@@ -243,7 +241,7 @@ public:
|
|||||||
ASeedBedActor* GetClosestSeedBedActor() { return LastClosestSeedBedActor; }
|
ASeedBedActor* GetClosestSeedBedActor() { return LastClosestSeedBedActor; }
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||||
inline bool GetIsPlanting() { return bIsPlanting; }
|
inline bool GetIsPlanting() const { return CurrentlyDoing == ECurrentlyDoing::PLANTING; }
|
||||||
|
|
||||||
void BeginWatering();
|
void BeginWatering();
|
||||||
void EndWatering() const;
|
void EndWatering() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user