Add item data assets and building placement foundation

This commit is contained in:
2026-05-11 00:54:19 -07:00
parent a8f722d980
commit e81138425b
13 changed files with 389 additions and 8 deletions
+9 -3
View File
@@ -18,6 +18,12 @@
- [x] Character now owns survival, inventory, and crafting components. - [x] Character now owns survival, inventory, and crafting components.
- [x] Character has server-authoritative interaction path. - [x] Character has server-authoritative interaction path.
- [x] Git ignore and Git LFS attribute prep files added. - [x] Git ignore and Git LFS attribute prep files added.
- [x] Item definition data asset class added.
- [x] Recipe data asset class added.
- [x] Crafting component can load recipes from data assets.
- [x] Resource nodes can use item definition assets for harvest yields.
- [x] Building placement component added.
- [x] Character now owns a building placement component.
## Next Unreal Editor Tasks ## Next Unreal Editor Tasks
@@ -36,9 +42,9 @@
## Next C++ Foundation Tasks ## Next C++ Foundation Tasks
- [ ] Add building placement component. - [x] Add building placement component.
- [ ] Add simple crafting recipe defaults or data asset pipeline. - [x] Add simple crafting recipe defaults or data asset pipeline.
- [ ] Add item definition data asset class. - [x] Add item definition data asset class.
- [ ] Add save/load capture for placed actors. - [ ] Add save/load capture for placed actors.
- [ ] Add admin/dev console commands. - [ ] Add admin/dev console commands.
- [ ] Add wildlife base actor. - [ ] Add wildlife base actor.
@@ -0,0 +1,204 @@
// Copyright Pacificao. All Rights Reserved.
#include "AgrarianBuildingPlacementComponent.h"
#include "AgrarianInventoryComponent.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Controller.h"
#include "Engine/World.h"
UAgrarianBuildingPlacementComponent::UAgrarianBuildingPlacementComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
}
void UAgrarianBuildingPlacementComponent::SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost)
{
ActiveBuildClass = BuildClass;
PlacementCost = Cost;
}
bool UAgrarianBuildingPlacementComponent::GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const
{
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn)
{
FailureReason = FText::FromString(TEXT("Only pawns can place buildables."));
return false;
}
FVector ViewLocation;
FRotator ViewRotation;
if (OwnerPawn->GetController())
{
OwnerPawn->GetController()->GetPlayerViewPoint(ViewLocation, ViewRotation);
}
else
{
ViewLocation = OwnerPawn->GetActorLocation();
ViewRotation = OwnerPawn->GetActorRotation();
}
const FVector TraceEnd = ViewLocation + ViewRotation.Vector() * PlacementDistance;
FHitResult Hit;
FCollisionQueryParams Params(SCENE_QUERY_STAT(AgrarianBuildPlacementTrace), false, OwnerPawn);
FVector PlacementLocation = TraceEnd;
if (GetWorld() && GetWorld()->LineTraceSingleByChannel(Hit, ViewLocation, TraceEnd, ECC_Visibility, Params))
{
PlacementLocation = Hit.ImpactPoint + Hit.ImpactNormal * SurfaceOffset;
}
PlacementLocation = SnapLocation(PlacementLocation);
const FRotator PlacementRotation(0.0f, OwnerPawn->GetActorRotation().Yaw, 0.0f);
OutTransform = FTransform(PlacementRotation, PlacementLocation);
return true;
}
bool UAgrarianBuildingPlacementComponent::CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const
{
if (!BuildClass)
{
FailureReason = FText::FromString(TEXT("No buildable is selected."));
return false;
}
const AActor* OwnerActor = GetOwner();
if (!OwnerActor)
{
FailureReason = FText::FromString(TEXT("No owner is available."));
return false;
}
if (FVector::DistSquared(OwnerActor->GetActorLocation(), PlacementTransform.GetLocation()) > FMath::Square(PlacementDistance + 150.0f))
{
FailureReason = FText::FromString(TEXT("Placement is too far away."));
return false;
}
if (!GetWorld())
{
FailureReason = FText::FromString(TEXT("No world is available."));
return false;
}
FCollisionQueryParams Params(SCENE_QUERY_STAT(AgrarianBuildPlacementProbe), false, OwnerActor);
const bool bBlocked = GetWorld()->OverlapBlockingTestByChannel(
PlacementTransform.GetLocation() + FVector(0.0f, 0.0f, PlacementProbeRadius),
PlacementTransform.GetRotation(),
ECC_WorldDynamic,
FCollisionShape::MakeSphere(PlacementProbeRadius),
Params);
if (bBlocked)
{
FailureReason = FText::FromString(TEXT("Placement area is blocked."));
return false;
}
return true;
}
bool UAgrarianBuildingPlacementComponent::PlaceActiveBuildable()
{
FTransform PlacementTransform;
FText FailureReason;
if (!GetPlacementPreview(PlacementTransform, FailureReason))
{
FailPlacement(FailureReason);
return false;
}
if (!GetOwner())
{
return false;
}
if (!GetOwner()->HasAuthority())
{
ServerPlaceBuildable(ActiveBuildClass, PlacementTransform);
return true;
}
ServerPlaceBuildable_Implementation(ActiveBuildClass, PlacementTransform);
return true;
}
void UAgrarianBuildingPlacementComponent::ServerPlaceBuildable_Implementation(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform)
{
FText FailureReason;
if (!CanPlaceAtTransform(BuildClass, PlacementTransform, FailureReason) || !HasPlacementCost(FailureReason))
{
FailPlacement(FailureReason);
return;
}
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
AActor* PlacedActor = GetWorld()->SpawnActor<AActor>(BuildClass, PlacementTransform, SpawnParams);
if (!PlacedActor)
{
FailPlacement(FText::FromString(TEXT("Buildable could not be placed.")));
return;
}
ConsumePlacementCost();
OnBuildPlaced.Broadcast(PlacedActor);
}
bool UAgrarianBuildingPlacementComponent::HasPlacementCost(FText& FailureReason) const
{
const UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
if (!Inventory)
{
FailureReason = FText::FromString(TEXT("No inventory is available."));
return false;
}
for (const FAgrarianItemStack& Cost : PlacementCost)
{
if (!Inventory->HasItem(Cost.ItemId, Cost.Quantity))
{
FailureReason = FText::Format(
FText::FromString(TEXT("Missing build material: {0}")),
Cost.DisplayName.IsEmpty() ? FText::FromName(Cost.ItemId) : Cost.DisplayName);
return false;
}
}
return true;
}
void UAgrarianBuildingPlacementComponent::ConsumePlacementCost()
{
UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
if (!Inventory)
{
return;
}
for (const FAgrarianItemStack& Cost : PlacementCost)
{
Inventory->RemoveItem(Cost.ItemId, Cost.Quantity);
}
}
void UAgrarianBuildingPlacementComponent::FailPlacement(const FText& Reason)
{
OnBuildPlacementFailed.Broadcast(Reason);
}
FVector UAgrarianBuildingPlacementComponent::SnapLocation(const FVector& Location) const
{
if (!bSnapToGrid || GridSize <= 0.0f)
{
return Location;
}
return FVector(
FMath::GridSnap(Location.X, GridSize),
FMath::GridSnap(Location.Y, GridSize),
Location.Z);
}
@@ -0,0 +1,68 @@
// Copyright Pacificao. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "AgrarianTypes.h"
#include "AgrarianBuildingPlacementComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAgrarianBuildPlacedSignature, AActor*, PlacedActor);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAgrarianBuildPlacementFailedSignature, FText, Reason);
UCLASS(ClassGroup = (Agrarian), BlueprintType, Blueprintable, meta = (BlueprintSpawnableComponent))
class UAgrarianBuildingPlacementComponent : public UActorComponent
{
GENERATED_BODY()
public:
UAgrarianBuildingPlacementComponent();
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
FAgrarianBuildPlacedSignature OnBuildPlaced;
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
FAgrarianBuildPlacementFailedSignature OnBuildPlacementFailed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
TSubclassOf<AActor> ActiveBuildClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
TArray<FAgrarianItemStack> PlacementCost;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (ClampMin = "100"))
float PlacementDistance = 600.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (ClampMin = "0"))
float PlacementProbeRadius = 75.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (ClampMin = "0"))
float SurfaceOffset = 2.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
bool bSnapToGrid = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (EditCondition = "bSnapToGrid", ClampMin = "1"))
float GridSize = 50.0f;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
void SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost);
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
bool GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
bool CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
bool PlaceActiveBuildable();
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Building")
void ServerPlaceBuildable(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform);
protected:
bool HasPlacementCost(FText& FailureReason) const;
void ConsumePlacementCost();
void FailPlacement(const FText& Reason);
FVector SnapLocation(const FVector& Location) const;
};
@@ -2,6 +2,7 @@
#include "AgrarianCraftingComponent.h" #include "AgrarianCraftingComponent.h"
#include "AgrarianInventoryComponent.h" #include "AgrarianInventoryComponent.h"
#include "AgrarianRecipeDataAsset.h"
UAgrarianCraftingComponent::UAgrarianCraftingComponent() UAgrarianCraftingComponent::UAgrarianCraftingComponent()
{ {
@@ -111,6 +112,15 @@ bool UAgrarianCraftingComponent::AddKnownRecipe(const FAgrarianRecipe& Recipe)
bool UAgrarianCraftingComponent::FindRecipe(FName RecipeId, FAgrarianRecipe& OutRecipe) const bool UAgrarianCraftingComponent::FindRecipe(FName RecipeId, FAgrarianRecipe& OutRecipe) const
{ {
for (const UAgrarianRecipeDataAsset* RecipeAsset : KnownRecipeAssets)
{
if (RecipeAsset && RecipeAsset->Recipe.RecipeId == RecipeId)
{
OutRecipe = RecipeAsset->Recipe;
return true;
}
}
for (const FAgrarianRecipe& Recipe : KnownRecipes) for (const FAgrarianRecipe& Recipe : KnownRecipes)
{ {
if (Recipe.RecipeId == RecipeId) if (Recipe.RecipeId == RecipeId)
@@ -128,7 +138,7 @@ UAgrarianInventoryComponent* UAgrarianCraftingComponent::GetInventory() const
return GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr; return GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
} }
void UAgrarianCraftingComponent::FailCraft(FName RecipeId, const FText& Reason) const void UAgrarianCraftingComponent::FailCraft(FName RecipeId, const FText& Reason)
{ {
OnCraftFailed.Broadcast(RecipeId, Reason); OnCraftFailed.Broadcast(RecipeId, Reason);
} }
@@ -8,6 +8,7 @@
#include "AgrarianCraftingComponent.generated.h" #include "AgrarianCraftingComponent.generated.h"
class UAgrarianInventoryComponent; class UAgrarianInventoryComponent;
class UAgrarianRecipeDataAsset;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAgrarianCraftCompletedSignature, FName, RecipeId, const FAgrarianItemStack&, Result); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAgrarianCraftCompletedSignature, FName, RecipeId, const FAgrarianItemStack&, Result);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAgrarianCraftFailedSignature, FName, RecipeId, FText, Reason); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAgrarianCraftFailedSignature, FName, RecipeId, FText, Reason);
@@ -29,6 +30,9 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Crafting") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Crafting")
TArray<FAgrarianRecipe> KnownRecipes; TArray<FAgrarianRecipe> KnownRecipes;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Crafting")
TArray<TObjectPtr<UAgrarianRecipeDataAsset>> KnownRecipeAssets;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Crafting") UFUNCTION(BlueprintCallable, Category = "Agrarian|Crafting")
bool CanCraft(FName RecipeId, FText& FailureReason) const; bool CanCraft(FName RecipeId, FText& FailureReason) const;
@@ -46,5 +50,5 @@ public:
protected: protected:
UAgrarianInventoryComponent* GetInventory() const; UAgrarianInventoryComponent* GetInventory() const;
void FailCraft(FName RecipeId, const FText& Reason) const; void FailCraft(FName RecipeId, const FText& Reason);
}; };
@@ -1,6 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved. // Copyright Epic Games, Inc. All Rights Reserved.
#include "AgrarianGameCharacter.h" #include "AgrarianGameCharacter.h"
#include "AgrarianBuildingPlacementComponent.h"
#include "AgrarianCraftingComponent.h" #include "AgrarianCraftingComponent.h"
#include "AgrarianInteractable.h" #include "AgrarianInteractable.h"
#include "AgrarianInventoryComponent.h" #include "AgrarianInventoryComponent.h"
@@ -53,6 +54,7 @@ AAgrarianGameCharacter::AAgrarianGameCharacter()
SurvivalComponent = CreateDefaultSubobject<UAgrarianSurvivalComponent>(TEXT("SurvivalComponent")); SurvivalComponent = CreateDefaultSubobject<UAgrarianSurvivalComponent>(TEXT("SurvivalComponent"));
InventoryComponent = CreateDefaultSubobject<UAgrarianInventoryComponent>(TEXT("InventoryComponent")); InventoryComponent = CreateDefaultSubobject<UAgrarianInventoryComponent>(TEXT("InventoryComponent"));
CraftingComponent = CreateDefaultSubobject<UAgrarianCraftingComponent>(TEXT("CraftingComponent")); CraftingComponent = CreateDefaultSubobject<UAgrarianCraftingComponent>(TEXT("CraftingComponent"));
BuildingPlacementComponent = CreateDefaultSubobject<UAgrarianBuildingPlacementComponent>(TEXT("BuildingPlacementComponent"));
// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
// are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++) // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++)
@@ -10,6 +10,7 @@
class USpringArmComponent; class USpringArmComponent;
class UCameraComponent; class UCameraComponent;
class UInputAction; class UInputAction;
class UAgrarianBuildingPlacementComponent;
class UAgrarianCraftingComponent; class UAgrarianCraftingComponent;
class UAgrarianInventoryComponent; class UAgrarianInventoryComponent;
class UAgrarianSurvivalComponent; class UAgrarianSurvivalComponent;
@@ -46,6 +47,10 @@ class AAgrarianGameCharacter : public ACharacter
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UAgrarianCraftingComponent* CraftingComponent; UAgrarianCraftingComponent* CraftingComponent;
/** Server-authoritative primitive building placement component. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
UAgrarianBuildingPlacementComponent* BuildingPlacementComponent;
protected: protected:
/** Jump Input Action */ /** Jump Input Action */
@@ -135,5 +140,8 @@ public:
/** Returns CraftingComponent subobject **/ /** Returns CraftingComponent subobject **/
FORCEINLINE UAgrarianCraftingComponent* GetCraftingComponent() const { return CraftingComponent; } FORCEINLINE UAgrarianCraftingComponent* GetCraftingComponent() const { return CraftingComponent; }
/** Returns BuildingPlacementComponent subobject **/
FORCEINLINE UAgrarianBuildingPlacementComponent* GetBuildingPlacementComponent() const { return BuildingPlacementComponent; }
}; };
@@ -0,0 +1,13 @@
// Copyright Pacificao. All Rights Reserved.
#include "AgrarianItemDefinitionAsset.h"
FAgrarianItemStack UAgrarianItemDefinitionAsset::MakeStack(int32 Quantity) const
{
FAgrarianItemStack Stack;
Stack.ItemId = Definition.ItemId;
Stack.DisplayName = Definition.DisplayName;
Stack.Quantity = FMath::Clamp(Quantity, 0, Definition.MaxStackSize);
Stack.UnitWeight = Definition.UnitWeight;
return Stack;
}
@@ -0,0 +1,21 @@
// Copyright Pacificao. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/PrimaryDataAsset.h"
#include "AgrarianTypes.h"
#include "AgrarianItemDefinitionAsset.generated.h"
UCLASS(BlueprintType)
class UAgrarianItemDefinitionAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Agrarian|Items")
FAgrarianItemDefinition Definition;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Items")
FAgrarianItemStack MakeStack(int32 Quantity) const;
};
@@ -0,0 +1,8 @@
// Copyright Pacificao. All Rights Reserved.
#include "AgrarianRecipeDataAsset.h"
FName UAgrarianRecipeDataAsset::GetRecipeId() const
{
return Recipe.RecipeId;
}
@@ -0,0 +1,21 @@
// Copyright Pacificao. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/PrimaryDataAsset.h"
#include "AgrarianTypes.h"
#include "AgrarianRecipeDataAsset.generated.h"
UCLASS(BlueprintType)
class UAgrarianRecipeDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Agrarian|Crafting")
FAgrarianRecipe Recipe;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Crafting")
FName GetRecipeId() const;
};
+14 -3
View File
@@ -3,6 +3,7 @@
#include "AgrarianResourceNode.h" #include "AgrarianResourceNode.h"
#include "AgrarianGameCharacter.h" #include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h" #include "AgrarianInventoryComponent.h"
#include "AgrarianItemDefinitionAsset.h"
#include "Components/StaticMeshComponent.h" #include "Components/StaticMeshComponent.h"
#include "Net/UnrealNetwork.h" #include "Net/UnrealNetwork.h"
@@ -45,9 +46,7 @@ void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Inte
if (UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent()) if (UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent())
{ {
FAgrarianItemStack Granted = YieldItem; const FAgrarianItemStack Granted = MakeYieldStack();
Granted.Quantity = QuantityPerHarvest;
if (Inventory->AddItem(Granted)) if (Inventory->AddItem(Granted))
{ {
RemainingHarvests--; RemainingHarvests--;
@@ -61,6 +60,18 @@ void AAgrarianResourceNode::OnRep_RemainingHarvests()
UpdateDepletedState(); UpdateDepletedState();
} }
FAgrarianItemStack AAgrarianResourceNode::MakeYieldStack() const
{
if (YieldItemDefinition)
{
return YieldItemDefinition->MakeStack(QuantityPerHarvest);
}
FAgrarianItemStack Granted = YieldItem;
Granted.Quantity = QuantityPerHarvest;
return Granted;
}
void AAgrarianResourceNode::UpdateDepletedState() void AAgrarianResourceNode::UpdateDepletedState()
{ {
if (Mesh) if (Mesh)
@@ -9,6 +9,7 @@
#include "AgrarianResourceNode.generated.h" #include "AgrarianResourceNode.generated.h"
class UStaticMeshComponent; class UStaticMeshComponent;
class UAgrarianItemDefinitionAsset;
UCLASS(Blueprintable) UCLASS(Blueprintable)
class AAgrarianResourceNode : public AActor, public IAgrarianInteractable class AAgrarianResourceNode : public AActor, public IAgrarianInteractable
@@ -26,6 +27,9 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource")
FAgrarianItemStack YieldItem; FAgrarianItemStack YieldItem;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource")
TObjectPtr<UAgrarianItemDefinitionAsset> YieldItemDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_RemainingHarvests, Category = "Agrarian|Resource", meta = (ClampMin = "0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_RemainingHarvests, Category = "Agrarian|Resource", meta = (ClampMin = "0"))
int32 RemainingHarvests = 5; int32 RemainingHarvests = 5;
@@ -40,5 +44,6 @@ protected:
UFUNCTION() UFUNCTION()
void OnRep_RemainingHarvests(); void OnRep_RemainingHarvests();
FAgrarianItemStack MakeYieldStack() const;
void UpdateDepletedState(); void UpdateDepletedState();
}; };