diff --git a/core/src/Circuit.cpp b/core/src/Circuit.cpp new file mode 100644 index 0000000..f8a12cc --- /dev/null +++ b/core/src/Circuit.cpp @@ -0,0 +1,307 @@ +#include "vf/Circuit.h" + +#include + +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(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& 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& wireQueue) { + injectOmniExcept(pos, level, Vec3i{0, 0, 0}, wireQueue); +} + +void RedstoneCircuit::injectOmniExcept(const Vec3i& pos, int level, const Vec3i& excludeOffset, + std::vector& 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(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(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& 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(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(level); + if (world.blockAt(target.x, target.y, target.z) == Block::RedstoneWire) + wireQueue.push_back(target); +} + +void RedstoneCircuit::propagateWire(std::vector& 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(next); + wireQueue.push_back(nb); + } + } +} + +void RedstoneCircuit::resolvePowerField() { + previousPower = power; + std::fill(power.begin(), power.end(), 0); + std::vector 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