From 7ae69ea1fff56209a65fc15de8a52b9e4166798b Mon Sep 17 00:00:00 2001 From: Connor Date: Mon, 16 Feb 2026 11:27:01 +0900 Subject: [PATCH] inventory tests --- include/Components/Inventory.hpp | 14 ++++ include/Components/Resource.hpp | 29 ++++---- tests/Components/test_Resource.cpp | 112 +++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 15 deletions(-) diff --git a/include/Components/Inventory.hpp b/include/Components/Inventory.hpp index e298097..dafc13b 100644 --- a/include/Components/Inventory.hpp +++ b/include/Components/Inventory.hpp @@ -19,6 +19,7 @@ struct InventoryT public: InventoryT() = default; InventoryT(size_t itemAmount) { Slots = { static_cast(itemAmount), InventoryMeta{} }; } + InventoryT(size_t itemAmount, IntegralType maxAmount) { Slots = { static_cast(itemAmount), InventoryMeta{ maxAmount } }; } public: IntegralType GetItemsAmount(uint16_t id) const { Assert(id); return Slots[id]; } @@ -41,6 +42,8 @@ public: Slots[i] += other.Slots[i]; } + bool IsFull(uint16_t id) const { Assert(id); return Slots[id] >= Slots.GetMetaData()->MaxSize; } + void Clear() { for (IntegralType i{}; i < Slots.GetSize(); ++i) @@ -124,6 +127,9 @@ struct InventoryAreaOfEffect }; static_assert(sizeof(InventoryAreaOfEffect) == 1); +struct InventoryOwner +{}; + struct ItemProcessor { ItemProcessor() = default; @@ -208,4 +214,12 @@ inline void Flecs_Inventory(flecs::world& world) world.component() .member("ProcessedTicks"); + + world.component(); +} + +inline void Inventory_Helper(const flecs::entity& entity, const WorldConfig& config, uint32_t maxPerSlot) +{ + entity.set(Inventory{config.GetItems().size(), maxPerSlot}); + entity.add(); } \ No newline at end of file diff --git a/include/Components/Resource.hpp b/include/Components/Resource.hpp index 0bcbda7..5d3e427 100644 --- a/include/Components/Resource.hpp +++ b/include/Components/Resource.hpp @@ -48,20 +48,18 @@ inline void Flecs_Resource(flecs::world& world) world.component() .add(); - // harvesting resource to world inventory - world.system() + // harvesting resource to inventory + world.system() .kind(flecs::OnUpdate) .without() - .each([](ResourceInfo info, ResourceTick tick, WorldInventory& worldInventory) { - worldInventory.AddItems(info.ResourceID, tick.Finished()); - }); - - // harvesting resource to local inventory - world.system() - .kind(flecs::OnUpdate) - .without() - .each([](ResourceInfo info, ResourceTick tick, Inventory& worldInventory) { - worldInventory.AddItems(info.ResourceID, tick.Finished()); + .each([](ResourceInfo info, ResourceTick tick, WorldInventory& worldInventory, Inventory* optionalInventory) { + if (tick.Finished()) + { + bool pushToLocalInventory = optionalInventory && !optionalInventory->IsFull(info.ResourceID); + + if (pushToLocalInventory) optionalInventory->AddItems(info.ResourceID, 1); + else worldInventory.AddItems(info.ResourceID, 1); + } }); // decrease health if ResourceHealth component @@ -73,14 +71,15 @@ inline void Flecs_Resource(flecs::world& world) }); // checking if we have to renew the resource - world.system() + world.system() .kind(flecs::OnUpdate) + .with() .without() - .each([](flecs::entity entity, ResourceHealth health, RenewingTick& tick) { + .each([](flecs::entity entity, ResourceHealth health) { if (health.Health == 0) { entity.remove(); entity.add(); - tick.AccumulatedTick = 0; + entity.ensure().AccumulatedTick = 0; } }); diff --git a/tests/Components/test_Resource.cpp b/tests/Components/test_Resource.cpp index 1b0544a..461ac06 100644 --- a/tests/Components/test_Resource.cpp +++ b/tests/Components/test_Resource.cpp @@ -132,3 +132,115 @@ TEST_SUITE("Resource - Health & Renewing") { CHECK(inv.GetItemsAmount(woodID) == 2); } } + +TEST_SUITE("Inventory Entity") { + TEST_CASE("resource harvests into local inventory when present") { + WorldConfig config{}; + uint16_t stoneID = config.RegisterItem("Stone"); + + WorldInstance world{ config }; + + auto entity = world.GetEcsWorld().entity(); + Resource_Ore_Helper(entity, stoneID, 1); + Inventory_Helper(entity, config, 10); + + world.ProcessFrame(); + + auto localInv = entity.get(); + CHECK(localInv.GetItemsAmount(stoneID) == 1); + + auto& worldInv = world.GetEcsWorld().ensure(); + CHECK(worldInv.GetItemsAmount(stoneID) == 0); + } + + TEST_CASE("overflow goes to world inventory when local is full") { + WorldConfig config{}; + uint16_t stoneID = config.RegisterItem("Stone"); + + WorldInstance world{ config }; + + auto entity = world.GetEcsWorld().entity(); + Resource_Ore_Helper(entity, stoneID, 1); + Inventory_Helper(entity, config, 2); + + // fill local inventory (max 2) + world.ProcessFrame(); + world.ProcessFrame(); + + auto localInv = entity.get(); + CHECK(localInv.GetItemsAmount(stoneID) == 2); + + auto& worldInv = world.GetEcsWorld().ensure(); + CHECK(worldInv.GetItemsAmount(stoneID) == 0); + + // next item should overflow to world inventory + world.ProcessFrame(); + + localInv = entity.get(); + CHECK(localInv.GetItemsAmount(stoneID) == 2); + CHECK(worldInv.GetItemsAmount(stoneID) == 1); + } + + TEST_CASE("inventory tracks multiple item types independently") { + WorldConfig config{}; + uint16_t stoneID = config.RegisterItem("Stone"); + uint16_t ironID = config.RegisterItem("Iron"); + + WorldInstance world{ config }; + + auto stoneEntity = world.GetEcsWorld().entity(); + Resource_Ore_Helper(stoneEntity, stoneID, 1); + Inventory_Helper(stoneEntity, config, 5); + + auto ironEntity = world.GetEcsWorld().entity(); + Resource_Ore_Helper(ironEntity, ironID, 1); + Inventory_Helper(ironEntity, config, 5); + + world.ProcessFrame(); + + auto stoneInv = stoneEntity.get(); + CHECK(stoneInv.GetItemsAmount(stoneID) == 1); + CHECK(stoneInv.GetItemsAmount(ironID) == 0); + + auto ironInv = ironEntity.get(); + CHECK(ironInv.GetItemsAmount(ironID) == 1); + CHECK(ironInv.GetItemsAmount(stoneID) == 0); + } + + TEST_CASE("two producers share a separate inventory entity") { + WorldConfig config{}; + uint16_t stoneID = config.RegisterItem("Stone"); + uint16_t ironID = config.RegisterItem("Iron"); + + WorldInstance world{ config }; + + // inventory entity (chest/barrel) + auto chest = world.GetEcsWorld().entity(); + Inventory_Helper(chest, config, 10); + + // copy the shared inventory to both producers + auto chestInv = chest.get(); + + auto stoneProducer = world.GetEcsWorld().entity(); + Resource_Ore_Helper(stoneProducer, stoneID, 1); + stoneProducer.set(chestInv); + + auto ironProducer = world.GetEcsWorld().entity(); + Resource_Ore_Helper(ironProducer, ironID, 1); + ironProducer.set(chestInv); + + world.ProcessFrame(); + world.ProcessFrame(); + world.ProcessFrame(); + + // all items should be in the shared inventory + auto resultInv = chest.get(); + CHECK(resultInv.GetItemsAmount(stoneID) == 3); + CHECK(resultInv.GetItemsAmount(ironID) == 3); + + // world inventory should be empty + auto& worldInv = world.GetEcsWorld().ensure(); + CHECK(worldInv.GetItemsAmount(stoneID) == 0); + CHECK(worldInv.GetItemsAmount(ironID) == 0); + } +}