diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 62f2090..f7ddd85 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -482,7 +482,11 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe ## 0.1.E Inventory System -- [ ] Design inventory data model. +- [x] Design inventory data model. Added `Docs/InventoryDataModel.md` to lock + the MVP contract for stable item definitions, runtime/save-game item stacks, + server-authoritative replicated inventory components, persistence, carry + weight, and the pickup/drop/split/use/equipment/UI operations that will build + on the existing code. - [x] Create item definition data asset. - [x] Create item stack structure. - [x] Create inventory component. diff --git a/Docs/InventoryDataModel.md b/Docs/InventoryDataModel.md new file mode 100644 index 0000000..47b64d7 --- /dev/null +++ b/Docs/InventoryDataModel.md @@ -0,0 +1,135 @@ +# Agrarian Inventory Data Model + +This document defines the baseline inventory model for the 0.1.E MVP. The +current implementation is intentionally small, but it needs a stable contract so +pickup, drop, splitting, use, equipment, persistence, and UI work all build on +the same assumptions. + +## Goals + +- Keep the server authoritative for every inventory mutation. +- Use stable item IDs so save files, recipes, drops, vendors, and future network + services do not depend on display text or asset names. +- Keep stacks compact enough for replication and save data. +- Support realistic constraints through slot count and weight before adding + more advanced volume, spoilage, durability, ownership, and container rules. +- Keep resource, food, tool, structure, medicine, and currency items inside one + shared model unless a later system proves it needs a separate path. + +## Core Records + +### Item Definition + +`FAgrarianItemDefinition` is the canonical design-time description of an item. +It is authored through `UAgrarianItemDefinitionAsset`. + +Required MVP fields: + +- `ItemId`: stable `FName` used by inventory, crafting, persistence, drops, and + debug commands. +- `DisplayName`: player-facing label. +- `Description`: player-facing description. +- `ItemType`: broad item category. +- `UnitWeight`: per-unit carry weight used by movement and capacity systems. +- `MaxStackSize`: maximum units expected in one stack. + +Future fields should be added to the item definition when they describe all +instances of an item type, such as durability profile, spoilage category, +nutrition profile, tool class, equipment slot, fuel value, material family, or +trade category. + +### Item Stack + +`FAgrarianItemStack` is the runtime and save-game representation of carried +items. + +Required MVP fields: + +- `ItemId`: stable reference to the item definition. +- `DisplayName`: cached player-facing label for early UI/debug display. +- `Quantity`: number of units in the stack. +- `UnitWeight`: cached per-unit weight so movement and debug views can work + without synchronously loading item assets. + +Stacks are valid only when `ItemId != NAME_None` and `Quantity > 0`. + +Future instance-specific fields, such as durability remaining, condition, +temperature, spoilage age, ownership, quality, or custom metadata, should live +on the stack only when individual instances of the same item can differ. If an +item can have per-instance metadata, stack merge rules must require matching +metadata before combining stacks. + +## Inventory Component + +`UAgrarianInventoryComponent` owns a replicated `TArray`. +The player character owns the MVP inventory component. + +Current baseline: + +- `Items`: replicated stack list. +- `MaxSlots`: maximum occupied stack slots. +- `OnInventoryChanged`: event for HUD/UI refresh. +- `HasItem`: quantity check by `ItemId`. +- `GetItemCount`: aggregate quantity across matching stacks. +- `GetTotalWeight`: sum of `Quantity * UnitWeight`. +- `AddItem`: server-only mutation. +- `RemoveItem`: server-only mutation. +- `ServerAddItem` and `ServerRemoveItem`: RPC wrappers for authorized requests. + +The component currently merges stacks by `ItemId`. Later stack splitting and +metadata work must refine this into a `CanMergeStacks` rule that considers +`MaxStackSize` and any per-instance fields. + +## Authority And Replication + +Inventory is server authoritative. + +- Clients may request inventory actions through server RPCs. +- The server validates item IDs, quantities, ownership, distance, and action + rules before mutating inventory. +- The replicated `Items` array is the client read model for the MVP. +- `OnRep_Items` triggers `OnInventoryChanged` for UI and debug refresh. +- Direct client-side inventory edits are not part of the supported model. + +## Persistence + +Save data stores the same `FAgrarianItemStack` array used by the runtime +component. This keeps the MVP save format simple and lets save/load restore the +player's inventory without a translation layer. + +Save/load responsibilities: + +- Save the player inventory from `UAgrarianInventoryComponent::Items`. +- Restore the stack array onto the component during load. +- Recompute derived values such as total weight after load from stack data. +- Avoid saving UI-only selection state as part of the inventory model. + +## MVP Operations + +The 0.1.E inventory work should implement these operations on top of the model: + +- Pickup: world item validates range, authority, stack data, and available + inventory space before adding. +- Drop: server removes a stack quantity and spawns a world item or bundle near + the player. +- Stack splitting: server moves a requested quantity into a new stack when slots + are available. +- Item use: server validates the item type and applies item-specific effects. +- Equipment: only add dedicated slots once an item type needs equipped state. +- Carry capacity: continue using total weight first, with later volume or pack + systems layered on top. +- UI: read the replicated stack list and send requests back through server RPCs. + +## Design Rules + +- `ItemId` is the stable key. Display names can change. +- Inventory mutations happen on the server. +- Stacks should not exceed the item definition's `MaxStackSize` once stack + splitting and pickup are implemented. +- Weight lives on item definitions and is cached onto stacks for runtime and + save simplicity. +- New item categories should extend `EAgrarianItemType` only when gameplay needs + distinct rules. +- Future world-scale storage, containers, vehicles, livestock packs, and markets + should reuse the same item definition and stack records unless they need + location-specific metadata. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 1558089..75897a8 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -84,6 +84,13 @@ Early runtime systems should remain small and explicit: Blueprints can compose and expose these systems, but core replicated behavior should remain in C++ as much as practical. +Inventory data is defined in `Docs/InventoryDataModel.md`. The MVP model uses +stable `ItemId` keys, design-time `FAgrarianItemDefinition` records, runtime +and save-game `FAgrarianItemStack` records, and a server-authoritative +`UAgrarianInventoryComponent` with replicated stack arrays. Pickup, drop, +splitting, item use, equipment, carry capacity, persistence, and UI work should +extend that contract rather than inventing parallel inventory state. + ## Time And Environment The MVP gameplay calendar target is: diff --git a/Scripts/verify_inventory_data_model.py b/Scripts/verify_inventory_data_model.py new file mode 100644 index 0000000..0063bd1 --- /dev/null +++ b/Scripts/verify_inventory_data_model.py @@ -0,0 +1,84 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md", + "AgrarianTypes.h": ROOT / "Source" / "AgrarianGame" / "AgrarianTypes.h", + "AgrarianInventoryComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.h", + "AgrarianInventoryComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.cpp", + "AgrarianItemDefinitionAsset.h": ROOT / "Source" / "AgrarianGame" / "AgrarianItemDefinitionAsset.h", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "InventoryDataModel.md": [ + "server authoritative", + "`FAgrarianItemDefinition`", + "`UAgrarianItemDefinitionAsset`", + "`FAgrarianItemStack`", + "`UAgrarianInventoryComponent`", + "`ItemId` is the stable key", + "Pickup:", + "Drop:", + "Stack splitting:", + "Item use:", + "Equipment:", + "Carry capacity:", + "Persistence", + ], + "AgrarianTypes.h": [ + "struct FAgrarianItemStack", + "FName ItemId = NAME_None;", + "int32 Quantity = 0;", + "float UnitWeight = 0.0f;", + "bool IsValidStack() const", + "enum class EAgrarianItemType", + "struct FAgrarianItemDefinition", + "int32 MaxStackSize = 99;", + ], + "AgrarianInventoryComponent.h": [ + "TArray Items;", + "int32 MaxSlots = 24;", + "bool HasItem(FName ItemId, int32 Quantity) const;", + "int32 GetItemCount(FName ItemId) const;", + "float GetTotalWeight() const;", + "void ServerAddItem(const FAgrarianItemStack& Stack);", + "void ServerRemoveItem(FName ItemId, int32 Quantity);", + "OnRep_Items", + ], + "AgrarianInventoryComponent.cpp": [ + "SetIsReplicatedByDefault(true);", + "DOREPLIFETIME(UAgrarianInventoryComponent, Items);", + "GetOwner()->HasAuthority()", + "Existing.ItemId == Stack.ItemId", + "Items.Num() >= MaxSlots", + "OnInventoryChanged.Broadcast();", + ], + "AgrarianItemDefinitionAsset.h": [ + "class UAgrarianItemDefinitionAsset", + "FAgrarianItemDefinition Definition;", + "FAgrarianItemStack MakeStack(int32 Quantity) const;", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Design inventory data model.", + ], +} + + +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("Inventory data model verification failed: " + "; ".join(missing)) + + print("Agrarian inventory data model verification complete.") + + +if __name__ == "__main__": + main()