Design inventory data model
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user