From 09eed7c4c4264c04eec3f061c5215eccfe35b4d0 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 17 May 2026 13:43:21 -0700 Subject: [PATCH] Add MVP inventory HUD panel --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 4 +- Docs/InventoryDataModel.md | 2 + Docs/TechnicalDesignDocument.md | 6 +++ Scripts/verify_inventory_ui.py | 66 +++++++++++++++++++++++ Source/AgrarianGame/AgrarianDebugHUD.cpp | 68 ++++++++++++++++++++++++ Source/AgrarianGame/AgrarianDebugHUD.h | 10 ++++ 6 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 Scripts/verify_inventory_ui.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index dc223f6..4b1966a 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -515,7 +515,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe thresholds, strength-scaled movement penalties, and a debug HUD carried weight readout. Later volume, backpack, awkward-object, and hard overload rules should extend this hook. -- [ ] Add inventory UI. +- [x] Add inventory UI. Added a compact MVP HUD inventory panel that is separate + 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 debug item spawn command. diff --git a/Docs/InventoryDataModel.md b/Docs/InventoryDataModel.md index e8f90ad..4c1cbd0 100644 --- a/Docs/InventoryDataModel.md +++ b/Docs/InventoryDataModel.md @@ -119,6 +119,8 @@ The 0.1.E inventory work should implement these operations on top of the model: - 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. + The MVP HUD now includes a compact inventory panel that shows occupied slots, + total carried weight, and the first visible item stacks. ## Carry Capacity Placeholder diff --git a/Docs/TechnicalDesignDocument.md b/Docs/TechnicalDesignDocument.md index 66fe152..e530280 100644 --- a/Docs/TechnicalDesignDocument.md +++ b/Docs/TechnicalDesignDocument.md @@ -138,6 +138,12 @@ HUD. Future backpacks, containers, awkward-object rules, and hard overload limits should extend this total-weight path rather than creating a second carry model. +The MVP inventory UI is a compact `AAgrarianDebugHUD` inventory panel, enabled +separately from the full developer HUD. It reads the replicated inventory stack +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. + ## Time And Environment The MVP gameplay calendar target is: diff --git a/Scripts/verify_inventory_ui.py b/Scripts/verify_inventory_ui.py new file mode 100644 index 0000000..c196092 --- /dev/null +++ b/Scripts/verify_inventory_ui.py @@ -0,0 +1,66 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +FILES = { + "AgrarianDebugHUD.h": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.h", + "AgrarianDebugHUD.cpp": ROOT / "Source" / "AgrarianGame" / "AgrarianDebugHUD.cpp", + "InventoryDataModel.md": ROOT / "Docs" / "InventoryDataModel.md", + "TechnicalDesignDocument.md": ROOT / "Docs" / "TechnicalDesignDocument.md", + "AGRARIAN_DEVELOPMENT_ROADMAP.md": ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md", +} + +EXPECTED = { + "AgrarianDebugHUD.h": [ + "bShowInventoryHUD", + "InventoryTextScale", + "MaxInventoryPanelRows", + "DrawInventoryPanel", + ], + "AgrarianDebugHUD.cpp": [ + "DrawInventoryPanel(AgrarianCharacter)", + "AAgrarianDebugHUD::DrawInventoryPanel", + "InventoryComponent->Items.Num()", + "InventoryComponent->MaxSlots", + "InventoryComponent->GetTotalWeight()", + "MaxInventoryPanelRows", + "Stack.UnitWeight * Stack.Quantity", + ], + "InventoryDataModel.md": [ + "compact inventory panel", + "occupied slots", + "total carried weight", + "visible item stacks", + ], + "TechnicalDesignDocument.md": [ + "The MVP inventory UI is a compact `AAgrarianDebugHUD` inventory panel", + "enabled", + "separately from the full developer HUD", + "shows occupied slots", + "total carried weight", + "server-authoritative commands", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Add inventory UI.", + "compact MVP HUD inventory panel", + "replicated inventory component", + ], +} + + +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 UI verification failed: " + "; ".join(missing)) + + print("Agrarian inventory UI verification complete.") + + +if __name__ == "__main__": + main() diff --git a/Source/AgrarianGame/AgrarianDebugHUD.cpp b/Source/AgrarianGame/AgrarianDebugHUD.cpp index 5bbc90f..3debf7c 100644 --- a/Source/AgrarianGame/AgrarianDebugHUD.cpp +++ b/Source/AgrarianGame/AgrarianDebugHUD.cpp @@ -24,6 +24,7 @@ void AAgrarianDebugHUD::DrawHUD() DrawInteractionPrompt(AgrarianCharacter); DrawCriticalStats(AgrarianCharacter->GetSurvivalComponent()); + DrawInventoryPanel(AgrarianCharacter); if (bShowDebugHUD) { @@ -109,6 +110,73 @@ void AAgrarianDebugHUD::DrawCriticalStats(const UAgrarianSurvivalComponent* Surv DrawScaledLine(FString::Printf(TEXT("Sickness %3.0f"), Survival.SicknessSeverity), X, Y, CriticalStatsTextScale, StatusColor(Survival.SicknessSeverity, true)); } +void AAgrarianDebugHUD::DrawInventoryPanel(const AAgrarianGameCharacter* AgrarianCharacter) +{ + if (!bShowInventoryHUD || !AgrarianCharacter || !Canvas) + { + return; + } + + const UAgrarianInventoryComponent* InventoryComponent = AgrarianCharacter->GetInventoryComponent(); + if (!InventoryComponent) + { + return; + } + + const float Scale = FMath::Max(0.25f, InventoryTextScale); + const float PanelWidth = 360.0f * Scale; + const float X = FMath::Max(32.0f, Canvas->ClipX - PanelWidth - 32.0f); + float Y = 32.0f; + + const int32 VisibleRows = InventoryComponent->Items.IsEmpty() + ? 1 + : FMath::Min(InventoryComponent->Items.Num(), FMath::Max(1, MaxInventoryPanelRows)); + const float LineHeight = 18.0f * Scale; + const float PanelHeight = (56.0f * Scale) + (VisibleRows * LineHeight); + + DrawRect(FLinearColor(0.02f, 0.025f, 0.02f, 0.72f), X - (12.0f * Scale), Y - (10.0f * Scale), PanelWidth, PanelHeight); + DrawText( + FString::Printf( + TEXT("INVENTORY %d/%d slots %.1f wt"), + InventoryComponent->Items.Num(), + InventoryComponent->MaxSlots, + InventoryComponent->GetTotalWeight()), + FColor(160, 220, 140), + X, + Y, + nullptr, + Scale, + false); + Y += 24.0f * Scale; + + if (InventoryComponent->Items.IsEmpty()) + { + DrawText(TEXT("Empty"), FColor::Silver, X, Y, nullptr, Scale, false); + return; + } + + for (int32 Index = 0; Index < VisibleRows; ++Index) + { + const FAgrarianItemStack& Stack = InventoryComponent->Items[Index]; + const FText DisplayName = Stack.DisplayName.IsEmpty() ? FText::FromName(Stack.ItemId) : Stack.DisplayName; + FString ItemName = DisplayName.ToString(); + if (ItemName.Len() > 24) + { + ItemName = ItemName.Left(21) + TEXT("..."); + } + + DrawText( + FString::Printf(TEXT("%02d %-24s x%-3d %5.1f"), Index + 1, *ItemName, Stack.Quantity, Stack.UnitWeight * Stack.Quantity), + FColor(225, 235, 220), + X, + Y, + nullptr, + Scale, + false); + Y += LineHeight; + } +} + void AAgrarianDebugHUD::DrawPlayerStatus(const AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y) { if (!AgrarianCharacter) diff --git a/Source/AgrarianGame/AgrarianDebugHUD.h b/Source/AgrarianGame/AgrarianDebugHUD.h index 30c1a01..5f112c9 100644 --- a/Source/AgrarianGame/AgrarianDebugHUD.h +++ b/Source/AgrarianGame/AgrarianDebugHUD.h @@ -23,12 +23,21 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD") bool bShowCriticalStatsHUD = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD") + bool bShowInventoryHUD = true; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25")) float TextScale = 1.0f; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25")) float CriticalStatsTextScale = 1.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "0.25")) + float InventoryTextScale = 0.9f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD", meta = (ClampMin = "1", ClampMax = "12")) + int32 MaxInventoryPanelRows = 6; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|HUD") bool bShowInteractionPrompt = true; @@ -38,6 +47,7 @@ public: protected: void DrawInteractionPrompt(const class AAgrarianGameCharacter* AgrarianCharacter); void DrawCriticalStats(const UAgrarianSurvivalComponent* SurvivalComponent); + void DrawInventoryPanel(const class AAgrarianGameCharacter* AgrarianCharacter); void DrawPlayerStatus(const class AAgrarianGameCharacter* AgrarianCharacter, float X, float& Y); void DrawSurvival(const UAgrarianSurvivalComponent* SurvivalComponent, float X, float& Y); void DrawInventory(const UAgrarianInventoryComponent* InventoryComponent, float X, float& Y);