feat(core): add generic item container storage
This commit is contained in:
1 parent
2c15155c2d
commit
3ea0180527
2 files changed
+230
No files matched your search
@@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#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<int>(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<ItemSlot> 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<Vec3i, Container, Vec3iHash> containers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#include "vf/Container.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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<float>(stack.count) / static_cast<float>(maxStackSize(stack.item));
|
||||||
|
}
|
||||||
|
if (filled == 0) return 0;
|
||||||
|
fill /= static_cast<float>(slots.size());
|
||||||
|
int signal = static_cast<int>(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
|
||||||
Reference in new issue
Block a user