From 03d856efbfd18689f5add9c5e53604e12df008f6 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 17 May 2026 00:55:16 -0700 Subject: [PATCH] Add item pickup actor --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 5 +- Docs/TechnicalDesignDocument.md | 8 +++ Scripts/verify_item_pickup.py | 65 ++++++++++++++++++ Source/AgrarianGame/AgrarianItemPickup.cpp | 76 ++++++++++++++++++++++ Source/AgrarianGame/AgrarianItemPickup.h | 44 +++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_item_pickup.py create mode 100644 Source/AgrarianGame/AgrarianItemPickup.cpp create mode 100644 Source/AgrarianGame/AgrarianItemPickup.h diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index f7ddd85..10cf9e0 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -490,7 +490,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Create item definition data asset. - [x] Create item stack structure. - [x] Create inventory component. -- [ ] Add item pickup. +- [x] Add item pickup. Added native `AAgrarianItemPickup` interactable world + actor with definition-backed or inline stack data, server-authoritative + inventory add, prompt text, and destroy-on-success behavior so failed pickups + remain available. - [ ] Add item drop. - [ ] Add stack splitting. - [ ] Add item use. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 75897a8..2891068 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -91,6 +91,14 @@ and save-game `FAgrarianItemStack` records, and a server-authoritative splitting, item use, equipment, carry capacity, persistence, and UI work should extend that contract rather than inventing parallel inventory state. +World pickups use `AAgrarianItemPickup`, an interactable replicated actor with a +static mesh and either a definition-backed or inline `FAgrarianItemStack`. +Player interaction already routes to the server through the Agrarian character, +so pickups validate authority, produce a valid stack, add it to the player's +inventory, and only then remove the pickup by destroying the world pickup actor. +If the inventory is full or the stack is invalid, the pickup remains in the +world for another attempt. + ## Time And Environment The MVP gameplay calendar target is: diff --git a/Scripts/verify_item_pickup.py b/Scripts/verify_item_pickup.py new file mode 100644 index 0000000..9f59a79 --- /dev/null +++ b/Scripts/verify_item_pickup.py @@ -0,0 +1,65 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "AgrarianItemPickup.h": ROOT / "Source" / "AgrarianGame" / "AgrarianItemPickup.h", + "AgrarianItemPickup.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianItemPickup.cpp", + "InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md", + "TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "AgrarianItemPickup.h": [ + "class AGRARIANGAME_API AAgrarianItemPickup : public AActor, public IAgrarianInteractable", + "TObjectPtr Mesh;", + "FAgrarianItemStack PickupStack;", + "TObjectPtr ItemDefinition;", + "int32 Quantity = 1;", + "virtual FText GetInteractionText_Implementation", + "virtual bool CanInteract_Implementation", + "virtual void Interact_Implementation", + "FAgrarianItemStack MakePickupStack() const;", + ], + "AgrarianItemPickup.cpp": [ + "bReplicates = true;", + "Mesh->SetCollisionProfileName(TEXT(\"BlockAll\"));", + "ItemDefinition->MakeStack(Quantity)", + "MakePickupStack().IsValidStack()", + "if (!HasAuthority() || !Interactor)", + "Interactor->GetInventoryComponent()", + "Inventory->AddItem(Stack)", + "Destroy();", + ], + "InventoryDataModel.md": [ + "Pickup:", + "world item validates range, authority, stack data, and available", + ], + "TechnicalDesignDocument.md": [ + "`AAgrarianItemPickup`", + "definition-backed or inline `FAgrarianItemStack`", + "destroying the world pickup actor", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add item pickup.", + ], +} + + +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 pickup verification failed: " + "; ".join(missing)) + + print("Agrarian item pickup verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianItemPickup.cpp b/Source/AgrarianGame/AgrarianItemPickup.cpp new file mode 100644 index 0000000..5ca6baf --- /dev/null +++ b/Source/AgrarianGame/AgrarianItemPickup.cpp @@ -0,0 +1,76 @@ +// Copyright Pacificao. All Rights Reserved. + +#include "AgrarianItemPickup.h" +#include "AgrarianGameCharacter.h" +#include "AgrarianInventoryComponent.h" +#include "AgrarianItemDefinitionAsset.h" +#include "Components/StaticMeshComponent.h" + +AAgrarianItemPickup::AAgrarianItemPickup() +{ + bReplicates = true; + + Mesh = CreateDefaultSubobject(TEXT("Mesh")); + RootComponent = Mesh; + Mesh->SetCollisionProfileName(TEXT("BlockAll")); + + PickupStack.ItemId = TEXT("wood"); + PickupStack.DisplayName = FText::FromString(TEXT("Wood")); + PickupStack.Quantity = 1; + PickupStack.UnitWeight = 1.0f; + InteractionVerb = FText::FromString(TEXT("Pick up")); +} + +FText AAgrarianItemPickup::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const +{ + const FAgrarianItemStack Stack = MakePickupStack(); + const FText DisplayName = Stack.DisplayName.IsEmpty() ? FText::FromName(Stack.ItemId) : Stack.DisplayName; + const FString Verb = InteractionVerb.IsEmpty() ? FString(TEXT("Pick up")) : InteractionVerb.ToString(); + + if (Stack.Quantity > 1) + { + return FText::FromString(FString::Printf(TEXT("%s %s x%d"), *Verb, *DisplayName.ToString(), Stack.Quantity)); + } + + return FText::FromString(FString::Printf(TEXT("%s %s"), *Verb, *DisplayName.ToString())); +} + +bool AAgrarianItemPickup::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const +{ + return Interactor != nullptr && MakePickupStack().IsValidStack(); +} + +void AAgrarianItemPickup::Interact_Implementation(AAgrarianGameCharacter* Interactor) +{ + if (!HasAuthority() || !Interactor) + { + return; + } + + UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent(); + if (!Inventory) + { + return; + } + + const FAgrarianItemStack Stack = MakePickupStack(); + if (Inventory->AddItem(Stack)) + { + Destroy(); + } +} + +FAgrarianItemStack AAgrarianItemPickup::MakePickupStack() const +{ + if (ItemDefinition) + { + return ItemDefinition->MakeStack(Quantity); + } + + FAgrarianItemStack Stack = PickupStack; + if (Quantity > 0) + { + Stack.Quantity = Quantity; + } + return Stack; +} diff --git a/Source/AgrarianGame/AgrarianItemPickup.h b/Source/AgrarianGame/AgrarianItemPickup.h new file mode 100644 index 0000000..23cdfd4 --- /dev/null +++ b/Source/AgrarianGame/AgrarianItemPickup.h @@ -0,0 +1,44 @@ +// Copyright Pacificao. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "AgrarianInteractable.h" +#include "AgrarianTypes.h" +#include "AgrarianItemPickup.generated.h" + +class AAgrarianGameCharacter; +class UAgrarianItemDefinitionAsset; +class UStaticMeshComponent; + +UCLASS(Blueprintable) +class AGRARIANGAME_API AAgrarianItemPickup : public AActor, public IAgrarianInteractable +{ + GENERATED_BODY() + +public: + AAgrarianItemPickup(); + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Pickup") + TObjectPtr Mesh; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Pickup") + FAgrarianItemStack PickupStack; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Pickup") + TObjectPtr ItemDefinition; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Pickup", meta = (ClampMin = "1")) + int32 Quantity = 1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Pickup") + FText InteractionVerb; + + virtual FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override; + virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override; + virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override; + + UFUNCTION(BlueprintCallable, Category = "Agrarian|Pickup") + FAgrarianItemStack MakePickupStack() const; +};