Design inventory data model

This commit is contained in:
2026-05-16 22:20:30 -07:00
parent e4364554de
commit c511ae904a
4 changed files with 231 additions and 1 deletions
+5 -1
View File
@@ -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.
+135
View File
@@ -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<FAgrarianItemStack>`.
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.
+7
View File
@@ -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:
+84
View File
@@ -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<FAgrarianItemStack> 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()