feat(core): add dispenser item ejection triggered by circuit signal
This commit is contained in:
1 parent
9f0cbc3410
commit
3639670fae
2 files changed
+138
No files matched your search
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "Container.h"
|
||||||
|
#include "World.h"
|
||||||
|
#include "Circuit.h"
|
||||||
|
#include "Block.h"
|
||||||
|
#include "Math.h"
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
constexpr int dispenserSlots = 9;
|
||||||
|
|
||||||
|
enum class DispenseAction : uint8_t {
|
||||||
|
None,
|
||||||
|
PlacedBlock,
|
||||||
|
InsertedToContainer,
|
||||||
|
EjectedToWorld,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DispenseResult {
|
||||||
|
DispenseAction action = DispenseAction::None;
|
||||||
|
ItemSlot item;
|
||||||
|
Vec3i target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DispenserState {
|
||||||
|
Face facing = Face::PosX;
|
||||||
|
bool lastPowered = false;
|
||||||
|
std::uint32_t rng = 0x9e3779b9u;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fires on a rising redstone edge. A dispenser places block items into the air
|
||||||
|
// cell it faces, or ejects non-block items (into a container in front when one
|
||||||
|
// exists, otherwise into the world). A dropper never places: it always ejects.
|
||||||
|
// The slot to draw from is chosen with the deterministic per-dispenser rng.
|
||||||
|
DispenseResult tickDispenser(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, int powerLevel);
|
||||||
|
DispenseResult tickDropper(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, int powerLevel);
|
||||||
|
|
||||||
|
DispenseResult tickDispenser(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, const RedstoneCircuit& circuit);
|
||||||
|
DispenseResult tickDropper(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, const RedstoneCircuit& circuit);
|
||||||
|
|
||||||
|
// Deterministic non-empty slot pick used by dispensers/droppers. Returns -1 when
|
||||||
|
// the inventory holds nothing. Exposed for testing the selection stream.
|
||||||
|
int chooseNonEmptySlot(const Container& inventory, std::uint32_t& rng);
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#include "vf/Dispenser.h"
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::uint32_t nextRandom(std::uint32_t& state) {
|
||||||
|
state = state * 1664525u + 1013904223u;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult ejectInto(ContainerStore& store, const Vec3i& front, const ItemSlot& one) {
|
||||||
|
Container* dest = store.at(front);
|
||||||
|
if (dest) {
|
||||||
|
ItemSlot copy = one;
|
||||||
|
if (dest->insert(copy) > 0) return {DispenseAction::InsertedToContainer, one, front};
|
||||||
|
}
|
||||||
|
return {DispenseAction::EjectedToWorld, one, front};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool risingEdge(DispenserState& state, int powerLevel) {
|
||||||
|
bool powered = powerLevel > 0;
|
||||||
|
bool rising = powered && !state.lastPowered;
|
||||||
|
state.lastPowered = powered;
|
||||||
|
return rising;
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult fire(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, bool placeBlocks) {
|
||||||
|
Container& inventory = store.ensure(pos, dispenserSlots);
|
||||||
|
int index = chooseNonEmptySlot(inventory, state.rng);
|
||||||
|
if (index < 0) return {};
|
||||||
|
|
||||||
|
ItemSlot one = inventory.extractSlot(index, 1);
|
||||||
|
if (one.empty()) return {};
|
||||||
|
|
||||||
|
Vec3i front = pos + faceDelta(state.facing);
|
||||||
|
if (placeBlocks && isBlockItem(one.item)) {
|
||||||
|
Block placed = blockForItem(one.item);
|
||||||
|
if (placed != Block::Air && world.blockAt(front.x, front.y, front.z) == Block::Air) {
|
||||||
|
world.setBlock(front.x, front.y, front.z, placed);
|
||||||
|
return {DispenseAction::PlacedBlock, one, front};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ejectInto(store, front, one);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int chooseNonEmptySlot(const Container& inventory, std::uint32_t& rng) {
|
||||||
|
int candidates = 0;
|
||||||
|
for (int i = 0; i < inventory.size(); ++i)
|
||||||
|
if (!inventory.slot(i).empty()) ++candidates;
|
||||||
|
if (candidates == 0) return -1;
|
||||||
|
|
||||||
|
int pick = static_cast<int>((nextRandom(rng) >> 16) % static_cast<std::uint32_t>(candidates));
|
||||||
|
for (int i = 0; i < inventory.size(); ++i) {
|
||||||
|
if (inventory.slot(i).empty()) continue;
|
||||||
|
if (pick == 0) return i;
|
||||||
|
--pick;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult tickDispenser(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, int powerLevel) {
|
||||||
|
if (!risingEdge(state, powerLevel)) return {};
|
||||||
|
return fire(world, pos, state, store, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult tickDropper(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, int powerLevel) {
|
||||||
|
if (!risingEdge(state, powerLevel)) return {};
|
||||||
|
return fire(world, pos, state, store, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult tickDispenser(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, const RedstoneCircuit& circuit) {
|
||||||
|
return tickDispenser(world, pos, state, store, circuit.powerAt(pos.x, pos.y, pos.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
DispenseResult tickDropper(World& world, const Vec3i& pos, DispenserState& state,
|
||||||
|
ContainerStore& store, const RedstoneCircuit& circuit) {
|
||||||
|
return tickDropper(world, pos, state, store, circuit.powerAt(pos.x, pos.y, pos.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
Reference in new issue
Block a user