From 3ea01805270769b4a492b9d188f08ccf5e7c5540 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 13:53:13 +0200 Subject: [PATCH] feat(core): add generic item container storage --- core/include/vf/Container.h | 84 +++++++++++++++++++++ core/src/Container.cpp | 146 ++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 core/include/vf/Container.h create mode 100644 core/src/Container.cpp diff --git a/core/include/vf/Container.h b/core/include/vf/Container.h new file mode 100644 index 0000000..24d817f --- /dev/null +++ b/core/include/vf/Container.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include "Item.h" +#include "Block.h" +#include "Math.h" + +namespace vf { + +constexpr int redstoneComparatorMax = 15; + +inline Vec3i faceDelta(Face face) { + switch (face) { + case Face::PosX: return {1, 0, 0}; + case Face::NegX: return {-1, 0, 0}; + case Face::PosY: return {0, 1, 0}; + case Face::NegY: return {0, -1, 0}; + case Face::PosZ: return {0, 0, 1}; + case Face::NegZ: return {0, 0, -1}; + } + return {0, 0, 0}; +} + +// Generic multi-slot inventory holding ItemSlot values. Backs chests, hoppers, +// droppers, dispensers and container minecarts. Merging respects each item's +// own max stack (tools do not stack, blocks stack to 64). +class Container { +public: + Container() = default; + explicit Container(int slotCount) : slots(slotCount) {} + + int size() const { return static_cast(slots.size()); } + + const ItemSlot& slot(int index) const { return slots[index]; } + ItemSlot& slot(int index) { return slots[index]; } + + void setSlot(int index, const ItemSlot& stack) { slots[index] = stack; } + void clear() { for (ItemSlot& stack : slots) stack = ItemSlot{}; } + + bool isEmpty() const; + bool isFull() const; + int usedSlots() const; + int totalItems() const; + + // Inserts as much of `incoming` as fits, merging into matching stacks first + // then filling empty slots (each capped at the item's max stack). Reduces + // `incoming.count` by what was taken. Returns the amount inserted. + int insert(ItemSlot& incoming); + int insert(ItemId item, int count, int durability = 0); + + // Pulls up to `amount` of `item` from any slots (first-fit, matching + // durability). Returns the removed stack (empty when nothing matched). + ItemSlot extract(ItemId item, int amount); + ItemSlot extractSlot(int index, int amount); + + // Moves up to `maxItems` items from this container into `dest`, walking + // slots in order. Returns how many items actually moved. + int transferTo(Container& dest, int maxItems); + + // Redstone comparator reading 0..15 from average fill fraction, matching the + // vanilla formula: 0 when empty, 15 when every slot is at its max stack. + int comparatorSignal() const; + +private: + std::vector slots; +}; + +// Position keyed registry of block-entity inventories. Chests, hoppers, +// droppers and dispensers each own a Container addressed by their world cell. +class ContainerStore { +public: + Container& ensure(const Vec3i& pos, int slotCount); + Container* at(const Vec3i& pos); + const Container* at(const Vec3i& pos) const; + bool has(const Vec3i& pos) const { return containers.find(pos) != containers.end(); } + void remove(const Vec3i& pos) { containers.erase(pos); } + std::size_t count() const { return containers.size(); } + +private: + std::unordered_map containers; +}; + +} // namespace vf diff --git a/core/src/Container.cpp b/core/src/Container.cpp new file mode 100644 index 0000000..4c5255e --- /dev/null +++ b/core/src/Container.cpp @@ -0,0 +1,146 @@ +#include "vf/Container.h" + +#include +#include + +namespace vf { + +bool Container::isEmpty() const { + for (const ItemSlot& stack : slots) + if (!stack.empty()) return false; + return true; +} + +bool Container::isFull() const { + if (slots.empty()) return false; + for (const ItemSlot& stack : slots) { + if (stack.empty()) return false; + if (stack.count < maxStackSize(stack.item)) return false; + } + return true; +} + +int Container::usedSlots() const { + int used = 0; + for (const ItemSlot& stack : slots) + if (!stack.empty()) ++used; + return used; +} + +int Container::totalItems() const { + int total = 0; + for (const ItemSlot& stack : slots) + if (!stack.empty()) total += stack.count; + return total; +} + +int Container::insert(ItemSlot& incoming) { + if (incoming.empty()) return 0; + const int cap = maxStackSize(incoming.item); + int inserted = 0; + + for (ItemSlot& stack : slots) { + if (incoming.count <= 0) break; + if (stack.empty()) continue; + if (stack.item != incoming.item || stack.durability != incoming.durability) continue; + int room = cap - stack.count; + if (room <= 0) continue; + int moved = std::min(room, incoming.count); + stack.count += moved; + incoming.count -= moved; + inserted += moved; + } + + for (ItemSlot& stack : slots) { + if (incoming.count <= 0) break; + if (!stack.empty()) continue; + int moved = std::min(cap, incoming.count); + stack = ItemSlot{incoming.item, moved, incoming.durability}; + incoming.count -= moved; + inserted += moved; + } + + if (incoming.count <= 0) incoming = ItemSlot{}; + return inserted; +} + +int Container::insert(ItemId item, int count, int durability) { + ItemSlot incoming{item, count, durability}; + insert(incoming); + return incoming.count; +} + +ItemSlot Container::extract(ItemId item, int amount) { + if (item == ItemId::None || amount <= 0) return ItemSlot{}; + ItemSlot out{}; + for (ItemSlot& stack : slots) { + if (amount <= 0) break; + if (stack.empty() || stack.item != item) continue; + if (out.count > 0 && out.durability != stack.durability) continue; + int take = std::min(amount, stack.count); + if (out.count == 0) { + out.item = item; + out.durability = stack.durability; + } + out.count += take; + stack.count -= take; + amount -= take; + if (stack.count <= 0) stack = ItemSlot{}; + } + return out.count > 0 ? out : ItemSlot{}; +} + +ItemSlot Container::extractSlot(int index, int amount) { + return splitStack(slots[index], amount); +} + +int Container::transferTo(Container& dest, int maxItems) { + if (maxItems <= 0) return 0; + int moved = 0; + for (ItemSlot& stack : slots) { + if (moved >= maxItems) break; + if (stack.empty()) continue; + int want = std::min(maxItems - moved, stack.count); + ItemSlot piece{stack.item, want, stack.durability}; + int accepted = dest.insert(piece); + if (accepted <= 0) continue; + stack.count -= accepted; + if (stack.count <= 0) stack = ItemSlot{}; + moved += accepted; + } + return moved; +} + +int Container::comparatorSignal() const { + if (slots.empty()) return 0; + float fill = 0.0f; + int filled = 0; + for (const ItemSlot& stack : slots) { + if (stack.empty()) continue; + ++filled; + fill += static_cast(stack.count) / static_cast(maxStackSize(stack.item)); + } + if (filled == 0) return 0; + fill /= static_cast(slots.size()); + int signal = static_cast(std::floor(fill * 14.0f)) + 1; + return std::min(signal, redstoneComparatorMax); +} + +Container& ContainerStore::ensure(const Vec3i& pos, int slotCount) { + auto it = containers.find(pos); + if (it != containers.end()) return it->second; + auto inserted = containers.emplace(pos, Container{slotCount}); + return inserted.first->second; +} + +Container* ContainerStore::at(const Vec3i& pos) { + auto it = containers.find(pos); + return it == containers.end() ? nullptr : &it->second; +} + +const Container* ContainerStore::at(const Vec3i& pos) const { + auto it = containers.find(pos); + return it == containers.end() ? nullptr : &it->second; +} + +} // namespace vf