From 712a8548c0cc1c219412d9c211aeacc99b4a6b88 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 17 May 2026 13:01:00 -0700 Subject: [PATCH] Add item use command --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 5 +- Docs/TechnicalDesignDocument.md | 9 +++ Scripts/verify_item_use.py | 76 +++++++++++++++++++ .../AgrarianGamePlayerController.cpp | 73 ++++++++++++++++++ .../AgrarianGamePlayerController.h | 6 ++ .../AgrarianSurvivalComponent.cpp | 10 +++ .../AgrarianGame/AgrarianSurvivalComponent.h | 3 + 7 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_item_use.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index c2ea9e0..e82aa32 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -501,7 +501,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe `SplitStackByIndex`/`AgrarianSplitStack StackIndex SplitQuantity` support that validates source slot, quantity, and free slot capacity, preserves stack metadata, creates a separate stack slot, and avoids immediate re-merge. -- [ ] Add item use. +- [x] Add item use. Added server-authoritative + `AgrarianUseItem ItemId Quantity` support for MVP consumables, with food + hunger recovery, raw meat hunger recovery plus sickness risk, bandage injury + treatment, and restore-on-unsupported-item behavior. - [ ] Add equipment slots if needed. - [ ] Add weight or carry capacity placeholder. - [ ] Add inventory UI. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index b27d12f..c8a0090 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -114,6 +114,15 @@ creates a separate inventory slot. It intentionally does not re-merge the split stack through `AddItem`, because the UI needs an actual separate stack to support later drag/drop and partial drop flows. +MVP item use is available through `AgrarianUseItem ItemId Quantity`. The command +routes to the server, extracts the requested stack quantity, applies a +whitelisted item effect, and restores the stack if the item is not usable yet. +For the first survival loop, `food` restores hunger, `meat` restores more hunger +but adds sickness risk because it is raw, and `bandage` reduces injury severity +with a small health bump. This gives UI item-use work a concrete authority path +while leaving tools, structures, and future complex consumables blocked until +they have explicit gameplay rules. + ## Time And Environment The MVP gameplay calendar target is: diff --git a/Scripts/verify_item_use.py b/Scripts/verify_item_use.py new file mode 100644 index 0000000..203e3c2 --- /dev/null +++ b/Scripts/verify_item_use.py @@ -0,0 +1,76 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "AgrarianSurvivalComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.h", + "AgrarianSurvivalComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianSurvivalComponent.cpp", + "AgrarianGamePlayerController.h": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.h", + "AgrarianGamePlayerController.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianGamePlayerController.cpp", + "TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md", + "InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "AgrarianSurvivalComponent.h": [ + "void ReduceInjury(float Amount);", + ], + "AgrarianSurvivalComponent.cpp": [ + "void UAgrarianSurvivalComponent::ReduceInjury", + "Survival.InjurySeverity -= FMath::Max(0.0f, Amount);", + "BroadcastSurvivalChanged();", + ], + "AgrarianGamePlayerController.h": [ + "void AgrarianUseItem(FName ItemId, int32 Quantity);", + "void ServerAgrarianUseItem(FName ItemId, int32 Quantity);", + ], + "AgrarianGamePlayerController.cpp": [ + "ApplyAgrarianItemUseEffect", + "ItemId == TEXT(\"food\")", + "SurvivalComponent->AddFood(15.0f * Quantity);", + "ItemId == TEXT(\"meat\")", + "SurvivalComponent->AddSickness(3.0f * Quantity);", + "ItemId == TEXT(\"bandage\")", + "SurvivalComponent->ReduceInjury(18.0f * Quantity);", + "void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity)", + "Usage: AgrarianUseItem ", + "void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation", + "InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack)", + "InventoryComponent->AddItem(UsedStack);", + "cannot be used yet; item restored", + ], + "TechnicalDesignDocument.md": [ + "`AgrarianUseItem ItemId Quantity`", + "`food` restores hunger", + "`meat` restores more hunger", + "adds sickness risk", + "`bandage` reduces injury severity", + "restores the stack if the item is not usable yet", + ], + "InventoryDataModel.md": [ + "Item use:", + "server validates the item type and applies item-specific effects.", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add item use.", + ], +} + + +def main(): + missing = [] + for label, path in FILES.items(): + text = path.read_text(encoding="utf-8") + for snippet in EXPECTED[label]: + if snippet not in text: + missing.append(f"{label}: {snippet}") + + if missing: + raise RuntimeError("Item use verification failed: " + "; ".join(missing)) + + print("Agrarian item use verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.cpp b/Source/AgrarianGame/AgrarianGamePlayerController.cpp index c25ec59..f6deda0 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.cpp +++ b/Source/AgrarianGame/AgrarianGamePlayerController.cpp @@ -20,6 +20,39 @@ namespace { const FVector GroundZeroDeveloperTravelHomeLocation(-22000.0f, -3500.0f, 1148.0f); + + bool ApplyAgrarianItemUseEffect(const FName ItemId, const int32 Quantity, UAgrarianSurvivalComponent* SurvivalComponent, FString& OutEffectSummary) + { + if (!SurvivalComponent || Quantity <= 0) + { + return false; + } + + if (ItemId == TEXT("food")) + { + SurvivalComponent->AddFood(15.0f * Quantity); + OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger"), 15.0f * Quantity); + return true; + } + + if (ItemId == TEXT("meat")) + { + SurvivalComponent->AddFood(22.0f * Quantity); + SurvivalComponent->AddSickness(3.0f * Quantity); + OutEffectSummary = FString::Printf(TEXT("restored %.0f hunger but added raw-meat sickness risk"), 22.0f * Quantity); + return true; + } + + if (ItemId == TEXT("bandage")) + { + SurvivalComponent->ReduceInjury(18.0f * Quantity); + SurvivalComponent->RestoreHealth(4.0f * Quantity); + OutEffectSummary = FString::Printf(TEXT("treated %.0f injury severity"), 18.0f * Quantity); + return true; + } + + return false; + } } void AAgrarianGamePlayerController::BeginPlay() @@ -150,6 +183,17 @@ void AAgrarianGamePlayerController::AgrarianSplitStack(int32 StackIndex, int32 S ServerAgrarianSplitStack(StackIndex, SplitQuantity); } +void AAgrarianGamePlayerController::AgrarianUseItem(FName ItemId, int32 Quantity) +{ + if (ItemId == NAME_None || Quantity <= 0) + { + ClientMessage(TEXT("Usage: AgrarianUseItem ")); + return; + } + + ServerAgrarianUseItem(ItemId, Quantity); +} + void AAgrarianGamePlayerController::AgrarianTravel(float X, float Y, float Z) { ServerAgrarianTravel(FVector(X, Y, Z)); @@ -299,6 +343,35 @@ void AAgrarianGamePlayerController::ServerAgrarianSplitStack_Implementation(int3 } } +void AAgrarianGamePlayerController::ServerAgrarianUseItem_Implementation(FName ItemId, int32 Quantity) +{ + AAgrarianGameCharacter* AgrarianCharacter = GetPawn(); + UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter ? AgrarianCharacter->GetInventoryComponent() : nullptr; + UAgrarianSurvivalComponent* SurvivalComponent = AgrarianCharacter ? AgrarianCharacter->GetSurvivalComponent() : nullptr; + if (!InventoryComponent || !SurvivalComponent) + { + ClientMessage(TEXT("No Agrarian inventory or survival component found.")); + return; + } + + FAgrarianItemStack UsedStack; + if (!InventoryComponent->ExtractItem(ItemId, Quantity, UsedStack)) + { + ClientMessage(FString::Printf(TEXT("Could not use %d x %s."), Quantity, *ItemId.ToString())); + return; + } + + FString EffectSummary; + if (!ApplyAgrarianItemUseEffect(ItemId, UsedStack.Quantity, SurvivalComponent, EffectSummary)) + { + InventoryComponent->AddItem(UsedStack); + ClientMessage(FString::Printf(TEXT("%s cannot be used yet; item restored."), *ItemId.ToString())); + return; + } + + ClientMessage(FString::Printf(TEXT("Used %d x %s: %s."), UsedStack.Quantity, *ItemId.ToString(), *EffectSummary)); +} + void AAgrarianGamePlayerController::ServerAgrarianTravel_Implementation(FVector Destination) { APawn* ControlledPawn = GetPawn(); diff --git a/Source/AgrarianGame/AgrarianGamePlayerController.h b/Source/AgrarianGame/AgrarianGamePlayerController.h index ee01864..6ab3e2d 100644 --- a/Source/AgrarianGame/AgrarianGamePlayerController.h +++ b/Source/AgrarianGame/AgrarianGamePlayerController.h @@ -72,6 +72,9 @@ public: UFUNCTION(Exec) void AgrarianSplitStack(int32 StackIndex, int32 SplitQuantity); + UFUNCTION(Exec) + void AgrarianUseItem(FName ItemId, int32 Quantity); + UFUNCTION(Exec) void AgrarianTravel(float X, float Y, float Z); @@ -97,6 +100,9 @@ protected: UFUNCTION(Server, Reliable) void ServerAgrarianSplitStack(int32 StackIndex, int32 SplitQuantity); + UFUNCTION(Server, Reliable) + void ServerAgrarianUseItem(FName ItemId, int32 Quantity); + UFUNCTION(Server, Reliable) void ServerAgrarianTravel(FVector Destination); }; diff --git a/Source/AgrarianGame/AgrarianSurvivalComponent.cpp b/Source/AgrarianGame/AgrarianSurvivalComponent.cpp index c9fcae1..c958f63 100644 --- a/Source/AgrarianGame/AgrarianSurvivalComponent.cpp +++ b/Source/AgrarianGame/AgrarianSurvivalComponent.cpp @@ -179,6 +179,16 @@ void UAgrarianSurvivalComponent::AddInjury(float Severity) } } +void UAgrarianSurvivalComponent::ReduceInjury(float Amount) +{ + if (GetOwner() && GetOwner()->HasAuthority()) + { + Survival.InjurySeverity -= FMath::Max(0.0f, Amount); + ClampSurvival(); + BroadcastSurvivalChanged(); + } +} + void UAgrarianSurvivalComponent::AddSickness(float Severity) { if (GetOwner() && GetOwner()->HasAuthority()) diff --git a/Source/AgrarianGame/AgrarianSurvivalComponent.h b/Source/AgrarianGame/AgrarianSurvivalComponent.h index 390d25f..865cb0a 100644 --- a/Source/AgrarianGame/AgrarianSurvivalComponent.h +++ b/Source/AgrarianGame/AgrarianSurvivalComponent.h @@ -93,6 +93,9 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") void AddInjury(float Severity); + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") + void ReduceInjury(float Amount); + UFUNCTION(BlueprintCallable, Category = "Agrarian|Survival") void AddSickness(float Severity);