Implement mesh for crop and some fixes

This commit is contained in:
2026-09-18 14:43:06 +02:00
parent d217397a80
commit b0908e2e65
14 changed files with 178 additions and 54 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -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;
/*-------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------*/

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();
} }

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;