feat(core): implement redstone-style circuit signal propagation

This commit is contained in:
dualfroz committed 2026-08-24 22:31:51 +02:00
1 parent 29a229d975
commit cff1e80fe8
1 file changed
+307
+307
View File
@@ -0,0 +1,307 @@
#include "vf/Circuit.h"
#include <algorithm>
namespace vf {
namespace {
const Vec3i neighbourOffsets[6] = {
{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1},
};
Vec3i faceOffset(Face f) {
switch (f) {
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};
}
Face oppositeFace(Face f) {
switch (f) {
case Face::PosX: return Face::NegX;
case Face::NegX: return Face::PosX;
case Face::PosY: return Face::NegY;
case Face::NegY: return Face::PosY;
case Face::PosZ: return Face::NegZ;
case Face::NegZ: return Face::PosZ;
}
return Face::PosX;
}
void sideFaces(Face facing, Face& sideA, Face& sideB) {
switch (facing) {
case Face::PosX:
case Face::NegX:
sideA = Face::PosZ;
sideB = Face::NegZ;
return;
case Face::PosZ:
case Face::NegZ:
sideA = Face::PosX;
sideB = Face::NegX;
return;
case Face::PosY:
case Face::NegY:
sideA = Face::PosX;
sideB = Face::NegX;
return;
}
sideA = Face::PosX;
sideB = Face::NegX;
}
int clampDelay(int delayTicks) {
if (delayTicks < 1) return 1;
if (delayTicks > 4) return 4;
return delayTicks;
}
} // namespace
RedstoneCircuit::RedstoneCircuit(World& world, const Vec3i& origin, const Vec3i& dims)
: world(world), origin(origin), dims(dims) {
std::size_t cells = static_cast<std::size_t>(dims.x) * dims.y * dims.z;
power.assign(cells, 0);
previousPower.assign(cells, 0);
}
bool RedstoneCircuit::inBox(int lx, int ly, int lz) const {
return lx >= 0 && lx < dims.x && ly >= 0 && ly < dims.y && lz >= 0 && lz < dims.z;
}
bool RedstoneCircuit::toLocal(int gx, int gy, int gz, int& lx, int& ly, int& lz) const {
lx = gx - origin.x;
ly = gy - origin.y;
lz = gz - origin.z;
return inBox(lx, ly, lz);
}
int RedstoneCircuit::fieldAt(const std::vector<uint8_t>& field, int gx, int gy, int gz) const {
int lx, ly, lz;
if (!toLocal(gx, gy, gz, lx, ly, lz)) return 0;
return field[index(lx, ly, lz)];
}
void RedstoneCircuit::setLever(const Vec3i& pos, bool on) { leverStates[pos] = on; }
bool RedstoneCircuit::leverOn(const Vec3i& pos) const {
auto it = leverStates.find(pos);
return it != leverStates.end() && it->second;
}
void RedstoneCircuit::pressButton(const Vec3i& pos, int durationTicks) {
buttonTimers[pos] = durationTicks;
}
bool RedstoneCircuit::buttonActive(const Vec3i& pos) const {
auto it = buttonTimers.find(pos);
return it != buttonTimers.end() && it->second > 0;
}
void RedstoneCircuit::setRepeater(const Vec3i& pos, Face facing, int delayTicks) {
RepeaterState state;
state.facing = facing;
state.delayTicks = clampDelay(delayTicks);
repeaters[pos] = state;
}
void RedstoneCircuit::setComparator(const Vec3i& pos, Face facing, ComparatorMode mode) {
ComparatorState state;
state.facing = facing;
state.mode = mode;
comparators[pos] = state;
}
bool RedstoneCircuit::repeaterOutputOn(const Vec3i& pos) const {
auto it = repeaters.find(pos);
return it != repeaters.end() && it->second.outputOn;
}
void RedstoneCircuit::advanceComponents() {
for (auto& entry : buttonTimers)
if (entry.second > 0) --entry.second;
for (auto& entry : repeaters) {
RepeaterState& state = entry.second;
Vec3i backPos = entry.first + faceOffset(oppositeFace(state.facing));
bool input = fieldAt(power, backPos.x, backPos.y, backPos.z) > 0;
if (input != state.lastInput) {
state.lastInput = input;
state.pendingOutput = input;
state.countdown = state.delayTicks;
}
if (state.countdown > 0) {
--state.countdown;
if (state.countdown == 0) state.outputOn = state.pendingOutput;
}
}
}
int RedstoneCircuit::comparatorOutput(const Vec3i& pos, const ComparatorState& state) const {
Vec3i backPos = pos + faceOffset(oppositeFace(state.facing));
int backLevel = fieldAt(previousPower, backPos.x, backPos.y, backPos.z);
Face sideA, sideB;
sideFaces(state.facing, sideA, sideB);
Vec3i sideAPos = pos + faceOffset(sideA);
Vec3i sideBPos = pos + faceOffset(sideB);
int sideLevel = std::max(fieldAt(previousPower, sideAPos.x, sideAPos.y, sideAPos.z),
fieldAt(previousPower, sideBPos.x, sideBPos.y, sideBPos.z));
if (state.mode == ComparatorMode::Compare) return backLevel >= sideLevel ? backLevel : 0;
return backLevel > sideLevel ? backLevel - sideLevel : 0;
}
void RedstoneCircuit::injectOmni(const Vec3i& pos, int level, std::vector<Vec3i>& wireQueue) {
injectOmniExcept(pos, level, Vec3i{0, 0, 0}, wireQueue);
}
void RedstoneCircuit::injectOmniExcept(const Vec3i& pos, int level, const Vec3i& excludeOffset,
std::vector<Vec3i>& wireQueue) {
if (level <= 0) return;
int lx, ly, lz;
if (toLocal(pos.x, pos.y, pos.z, lx, ly, lz)) {
int idx = index(lx, ly, lz);
if (level > power[idx]) power[idx] = static_cast<uint8_t>(level);
}
for (const Vec3i& off : neighbourOffsets) {
if (off == excludeOffset) continue;
Vec3i target = pos + off;
int tlx, tly, tlz;
if (!toLocal(target.x, target.y, target.z, tlx, tly, tlz)) continue;
int tidx = index(tlx, tly, tlz);
if (level <= power[tidx]) continue;
power[tidx] = static_cast<uint8_t>(level);
if (world.blockAt(target.x, target.y, target.z) == Block::RedstoneWire)
wireQueue.push_back(target);
}
}
void RedstoneCircuit::injectDirectional(const Vec3i& pos, Face facing, int level,
std::vector<Vec3i>& wireQueue) {
if (level <= 0) return;
int lx, ly, lz;
if (toLocal(pos.x, pos.y, pos.z, lx, ly, lz)) {
int idx = index(lx, ly, lz);
if (level > power[idx]) power[idx] = static_cast<uint8_t>(level);
}
Vec3i target = pos + faceOffset(facing);
int tlx, tly, tlz;
if (!toLocal(target.x, target.y, target.z, tlx, tly, tlz)) return;
int tidx = index(tlx, tly, tlz);
if (level <= power[tidx]) return;
power[tidx] = static_cast<uint8_t>(level);
if (world.blockAt(target.x, target.y, target.z) == Block::RedstoneWire)
wireQueue.push_back(target);
}
void RedstoneCircuit::propagateWire(std::vector<Vec3i>& wireQueue) {
while (!wireQueue.empty()) {
Vec3i cur = wireQueue.back();
wireQueue.pop_back();
int lx = cur.x - origin.x, ly = cur.y - origin.y, lz = cur.z - origin.z;
int curLevel = power[index(lx, ly, lz)];
int next = curLevel > 0 ? curLevel - 1 : 0;
if (next <= 0) continue;
for (const Vec3i& off : neighbourOffsets) {
Vec3i nb = cur + off;
int nlx, nly, nlz;
if (!toLocal(nb.x, nb.y, nb.z, nlx, nly, nlz)) continue;
if (world.blockAt(nb.x, nb.y, nb.z) != Block::RedstoneWire) continue;
int nidx = index(nlx, nly, nlz);
if (next <= power[nidx]) continue;
power[nidx] = static_cast<uint8_t>(next);
wireQueue.push_back(nb);
}
}
}
void RedstoneCircuit::resolvePowerField() {
previousPower = power;
std::fill(power.begin(), power.end(), 0);
std::vector<Vec3i> wireQueue;
for (int lx = 0; lx < dims.x; ++lx) {
for (int ly = 0; ly < dims.y; ++ly) {
for (int lz = 0; lz < dims.z; ++lz) {
Vec3i pos{origin.x + lx, origin.y + ly, origin.z + lz};
Block b = world.blockAt(pos.x, pos.y, pos.z);
switch (b) {
case Block::RedstoneBlock:
injectOmni(pos, redstoneMaxPower, wireQueue);
break;
case Block::Lever:
if (leverOn(pos)) injectOmni(pos, redstoneMaxPower, wireQueue);
break;
case Block::Button:
if (buttonActive(pos)) injectOmni(pos, redstoneMaxPower, wireQueue);
break;
case Block::RedstoneTorch: {
Vec3i mount{pos.x, pos.y - 1, pos.z};
int mountPower = fieldAt(previousPower, mount.x, mount.y, mount.z);
if (mountPower <= 0)
injectOmniExcept(pos, redstoneMaxPower, Vec3i{0, -1, 0}, wireQueue);
break;
}
case Block::Repeater: {
auto it = repeaters.find(pos);
if (it != repeaters.end() && it->second.outputOn)
injectDirectional(pos, it->second.facing, redstoneMaxPower, wireQueue);
break;
}
case Block::Comparator: {
auto it = comparators.find(pos);
if (it != comparators.end()) {
int level = comparatorOutput(pos, it->second);
if (level > 0) injectDirectional(pos, it->second.facing, level, wireQueue);
}
break;
}
default:
break;
}
}
}
}
propagateWire(wireQueue);
}
void RedstoneCircuit::tick() {
++tickCounter;
advanceComponents();
resolvePowerField();
}
int RedstoneCircuit::powerAt(int gx, int gy, int gz) const { return fieldAt(power, gx, gy, gz); }
void RedstoneCircuit::applyLamps() {
for (int lx = 0; lx < dims.x; ++lx) {
for (int ly = 0; ly < dims.y; ++ly) {
for (int lz = 0; lz < dims.z; ++lz) {
int gx = origin.x + lx, gy = origin.y + ly, gz = origin.z + lz;
Block b = world.blockAt(gx, gy, gz);
if (b != Block::RedstoneLamp && b != Block::RedstoneLampLit) continue;
bool lit = false;
for (const Vec3i& off : neighbourOffsets) {
if (fieldAt(power, gx + off.x, gy + off.y, gz + off.z) > 0) {
lit = true;
break;
}
}
Block want = lit ? Block::RedstoneLampLit : Block::RedstoneLamp;
if (b != want) world.setBlock(gx, gy, gz, want);
}
}
}
}
} // namespace vf