From ac9fee463c09455802122f05f08062a7bf3e87c0 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 17 May 2026 13:56:28 -0700 Subject: [PATCH] Add inventory persistence restore hook --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 5 +- Docs/InventoryDataModel.md | 4 +- Docs/TechnicalDesignDocument.md | 6 ++ Scripts/verify_inventory_persistence.py | 67 +++++++++++++++++++ .../AgrarianInventoryComponent.cpp | 11 +++ .../AgrarianGame/AgrarianInventoryComponent.h | 3 + .../AgrarianPersistenceSubsystem.cpp | 2 +- 7 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 Scripts/verify_inventory_persistence.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 4b1966a..563e267 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -519,7 +519,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe from the full developer HUD and shows occupied slots, total carried weight, and visible item stacks from the replicated inventory component. - [x] Add replication for inventory changes. -- [ ] Add persistence for inventory. +- [x] Add persistence for inventory. Player save data stores + `FAgrarianSavedPlayer::Inventory`, capture writes the inventory component's + stack array, restore now goes through `RestoreSavedItems`, and inventory UI + listeners are notified after load. - [x] Add debug item spawn command. ## 0.1.F Gathering And Resources diff --git a/Docs/InventoryDataModel.md b/Docs/InventoryDataModel.md index 4c1cbd0..f99f4f0 100644 --- a/Docs/InventoryDataModel.md +++ b/Docs/InventoryDataModel.md @@ -100,7 +100,9 @@ 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. +- Restore the stack array through + `UAgrarianInventoryComponent::RestoreSavedItems` during load so HUD/UI + listeners receive `OnInventoryChanged`. - Recompute derived values such as total weight after load from stack data. - Avoid saving UI-only selection state as part of the inventory model. diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index e530280..378432b 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -144,6 +144,12 @@ array, shows occupied slots, total carried weight, and a short visible stack list, and leaves mutation actions on the existing server-authoritative commands and RPCs until a full UMG inventory screen is introduced. +Inventory persistence saves `UAgrarianInventoryComponent::Items` into +`FAgrarianSavedPlayer::Inventory` and restores through +`UAgrarianInventoryComponent::RestoreSavedItems`. Restore broadcasts +`OnInventoryChanged`, which keeps the MVP HUD panel and future UI listeners in +sync after load while preserving total weight as a derived value. + ## Time And Environment The MVP gameplay calendar target is: diff --git a/Scripts/verify_inventory_persistence.py b/Scripts/verify_inventory_persistence.py new file mode 100644 index 0000000..49895ec --- /dev/null +++ b/Scripts/verify_inventory_persistence.py @@ -0,0 +1,67 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "AgrarianSaveGame.h": ROOT / "Source" / "AgrarianGame" / "AgrarianSaveGame.h", + "AgrarianInventoryComponent.h": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.h", + "AgrarianInventoryComponent.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianInventoryComponent.cpp", + "AgrarianPersistenceSubsystem.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianPersistenceSubsystem.cpp", + "InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md", + "TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "AgrarianSaveGame.h": [ + "TArray Inventory", + ], + "AgrarianInventoryComponent.h": [ + "RestoreSavedItems", + "const TArray& SavedItems", + ], + "AgrarianInventoryComponent.cpp": [ + "UAgrarianInventoryComponent::RestoreSavedItems", + "Items = SavedItems", + "BroadcastInventoryChanged()", + ], + "AgrarianPersistenceSubsystem.cpp": [ + "SavedPlayer.Inventory = InventoryComponent->Items", + "InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory)", + ], + "InventoryDataModel.md": [ + "`UAgrarianInventoryComponent::RestoreSavedItems`", + "`OnInventoryChanged`", + "Recompute derived values such as total weight", + ], + "TechnicalDesignDocument.md": [ + "Inventory persistence saves", + "`FAgrarianSavedPlayer::Inventory`", + "`UAgrarianInventoryComponent::RestoreSavedItems`", + "`OnInventoryChanged`", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add persistence for inventory.", + "`FAgrarianSavedPlayer::Inventory`", + "`RestoreSavedItems`", + "listeners are notified after load", + ], +} + + +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 persistence verification failed: " + "; ".join(missing)) + + print("Agrarian inventory persistence verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianInventoryComponent.cpp b/Source/AgrarianGame/AgrarianInventoryComponent.cpp index 64fc31e..f5853e7 100644 --- a/Source/AgrarianGame/AgrarianInventoryComponent.cpp +++ b/Source/AgrarianGame/AgrarianInventoryComponent.cpp @@ -136,6 +136,17 @@ bool UAgrarianInventoryComponent::SplitStackByIndex(int32 StackIndex, int32 Spli return true; } +void UAgrarianInventoryComponent::RestoreSavedItems(const TArray& SavedItems) +{ + if (!GetOwner() || !GetOwner()->HasAuthority()) + { + return; + } + + Items = SavedItems; + BroadcastInventoryChanged(); +} + void UAgrarianInventoryComponent::ServerAddItem_Implementation(const FAgrarianItemStack& Stack) { AddItem(Stack); diff --git a/Source/AgrarianGame/AgrarianInventoryComponent.h b/Source/AgrarianGame/AgrarianInventoryComponent.h index c9aa860..587687d 100644 --- a/Source/AgrarianGame/AgrarianInventoryComponent.h +++ b/Source/AgrarianGame/AgrarianInventoryComponent.h @@ -49,6 +49,9 @@ public: UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory") bool SplitStackByIndex(int32 StackIndex, int32 SplitQuantity); + UFUNCTION(BlueprintCallable, Category = "Agrarian|Inventory") + void RestoreSavedItems(const TArray& SavedItems); + UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Inventory") void ServerAddItem(const FAgrarianItemStack& Stack); diff --git a/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp b/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp index 7786fff..d076d0a 100644 --- a/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp +++ b/Source/AgrarianGame/AgrarianPersistenceSubsystem.cpp @@ -231,7 +231,7 @@ int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* Sav if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent()) { - InventoryComponent->Items = SavedPlayer->Inventory; + InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory); } RestoredCount++;