feat(core): add hopper item transfer between containers

This commit is contained in:
dualfroz committed 2026-08-25 10:37:13 +02:00
1 parent a6bae52f26
commit 9f0cbc3410
2 files changed
+93

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "Container.h"
#include "World.h"
#include "Block.h"
#include "Math.h"
namespace vf {
constexpr int hopperSlots = 5;
constexpr int hopperCooldownTicks = 8;
struct HopperState {
Face facing = Face::NegY;
int cooldown = 0;
};
// One deterministic hopper step at `pos`. Pulls a single item from the
// container directly above and pushes a single item into the container the
// hopper faces, then arms the cooldown when anything moved. The hopper owns a
// five slot inventory registered in `store` at its own cell. No-op while the
// cooldown is running or when the world block is no longer a hopper.
bool tickHopper(const World& world, const Vec3i& pos, HopperState& state, ContainerStore& store);
// True when the hopper has an item ready to push and a target that can take it,
// or a source above with an item it can pull. Used to gate cooldown re-arming.
bool hopperCanTransfer(const Vec3i& pos, const HopperState& state, ContainerStore& store);
} // namespace vf
+64
View File
@@ -0,0 +1,64 @@
#include "vf/Hopper.h"
namespace vf {
namespace {
Vec3i above(const Vec3i& pos) { return pos + Vec3i{0, 1, 0}; }
bool pushOne(Container& source, ContainerStore& store, const Vec3i& target) {
Container* dest = store.at(target);
if (!dest) return false;
return source.transferTo(*dest, 1) > 0;
}
bool pullOne(ContainerStore& store, const Vec3i& source, Container& sink) {
Container* src = store.at(source);
if (!src) return false;
return src->transferTo(sink, 1) > 0;
}
} // namespace
bool hopperCanTransfer(const Vec3i& pos, const HopperState& state, ContainerStore& store) {
Container* self = store.at(pos);
if (!self) return false;
Vec3i target = pos + faceDelta(state.facing);
if (!self->isEmpty()) {
Container* dest = store.at(target);
if (dest && !dest->isFull()) {
Container probe = *self;
Container destCopy = *dest;
if (probe.transferTo(destCopy, 1) > 0) return true;
}
}
Container* src = store.at(above(pos));
if (src && !src->isEmpty() && !self->isFull()) {
Container srcCopy = *src;
Container selfCopy = *self;
if (srcCopy.transferTo(selfCopy, 1) > 0) return true;
}
return false;
}
bool tickHopper(const World& world, const Vec3i& pos, HopperState& state, ContainerStore& store) {
if (world.blockAt(pos.x, pos.y, pos.z) != Block::Hopper) return false;
Container& self = store.ensure(pos, hopperSlots);
if (state.cooldown > 0) {
--state.cooldown;
return false;
}
bool moved = false;
Vec3i target = pos + faceDelta(state.facing);
moved |= pushOne(self, store, target);
moved |= pullOne(store, above(pos), self);
if (moved) state.cooldown = hopperCooldownTicks;
return moved;
}
} // namespace vf