diff options
Diffstat (limited to 'src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h')
-rw-r--r-- | src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h | 355 |
1 files changed, 238 insertions, 117 deletions
diff --git a/src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h b/src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h index 5bf4afcd3..2772441bd 100644 --- a/src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h +++ b/src/Simulator/IncrementalRedstoneSimulator/RedstoneWireHandler.h @@ -2,6 +2,7 @@ #pragma once #include "RedstoneHandler.h" +#include "../../Registries/Blocks.h" @@ -9,198 +10,318 @@ class cRedstoneWireHandler final : public cRedstoneHandler { -public: + /** A unified representation of wire direction. */ + enum class TemporaryDirection + { + Up, + Side + }; - inline static bool IsDirectlyConnectingMechanism(BLOCKTYPE a_Block, NIBBLETYPE a_BlockMeta, const Vector3i a_Offset) + /** Adjusts a given wire block so that the direction represented by Offset has state Direction. */ + inline static void SetDirectionState(const Vector3i Offset, short & Block, TemporaryDirection Direction) { - switch (a_Block) + Block = DoWithDirectionState(Offset, Block, [Direction](auto, auto & Front, auto) { - case E_BLOCK_REDSTONE_REPEATER_ON: - case E_BLOCK_REDSTONE_REPEATER_OFF: + using FrontState = std::remove_reference_t<decltype(Front)>; + switch (Direction) { - a_BlockMeta &= E_META_REDSTONE_REPEATER_FACING_MASK; - if ((a_BlockMeta == E_META_REDSTONE_REPEATER_FACING_XP) || (a_BlockMeta == E_META_REDSTONE_REPEATER_FACING_XM)) + case TemporaryDirection::Up: { - // Wire connects to repeater if repeater is aligned along X - // and wire is in front or behind it (#4639) - return a_Offset.x != 0; + Front = FrontState::Up; + return; + } + case TemporaryDirection::Side: + { + Front = FrontState::Side; + return; } - - return a_Offset.z != 0; } - case E_BLOCK_ACTIVE_COMPARATOR: - case E_BLOCK_INACTIVE_COMPARATOR: - case E_BLOCK_REDSTONE_TORCH_OFF: - case E_BLOCK_REDSTONE_TORCH_ON: - case E_BLOCK_REDSTONE_WIRE: return true; - default: return false; - } + }); } + /** Invokes Callback with the wire's left, front, and right direction state corresponding to Offset. + Returns a new block constructed from the directions that the callback may have modified. */ template <class OffsetCallback> - static bool ForTerracingConnectionOffsets(cChunk & a_Chunk, const Vector3i a_Position, OffsetCallback Callback) + inline static short DoWithDirectionState(const Vector3i Offset, short Block, OffsetCallback Callback) + { + auto North = Block::RedstoneWire::North(Block); + auto South = Block::RedstoneWire::South(Block); + auto West = Block::RedstoneWire::West(Block); + auto East = Block::RedstoneWire::East(Block); + + if (Offset.x == -1) + { + Callback(South, West, North); + } + else if (Offset.x == 1) + { + Callback(North, East, South); + } + + if (Offset.z == -1) + { + Callback(West, North, East); + } + else if (Offset.z == 1) + { + Callback(East, South, West); + } + + return Block::RedstoneWire::RedstoneWire(East, North, 0, South, West); + } + +public: + + /** Temporary. Discovers a wire's connection state, including terracing, storing the block inside redstone chunk data. + TODO: once the server supports block states this should go in the block handler, with data saved in the world. */ + void SetWireState(const cChunk & Chunk, const Vector3i Position) const { - const auto YPTerraceBlock = a_Chunk.GetBlock(a_Position + OffsetYP); + auto Block = Block::RedstoneWire::RedstoneWire(); + const auto YPTerraceBlock = Chunk.GetBlock(Position + OffsetYP); const bool IsYPTerracingBlocked = cBlockInfo::IsSolid(YPTerraceBlock) && !cBlockInfo::IsTransparent(YPTerraceBlock); - for (const auto Adjacent : RelativeLaterals) + // Loop through laterals, discovering terracing connections: + for (const auto Offset : RelativeLaterals) { - // All laterals are counted as terracing, duh - if (Callback(Adjacent)) + auto Adjacent = Position + Offset; + auto NeighbourChunk = Chunk.GetRelNeighborChunkAdjustCoords(Adjacent); + + if ((NeighbourChunk == nullptr) || !NeighbourChunk->IsValid()) { - return true; + continue; } - if ( - BLOCKTYPE YPBlock; + BLOCKTYPE LateralBlock; + NIBBLETYPE LateralMeta; + NeighbourChunk->GetBlockTypeMeta(Adjacent, LateralBlock, LateralMeta); - // A block above us blocks all YP terracing, so the check is static in the loop - !IsYPTerracingBlocked && - a_Chunk.UnboundedRelGetBlockType(a_Position + Adjacent + OffsetYP, YPBlock) && - (YPBlock == E_BLOCK_REDSTONE_WIRE) - ) + if (IsDirectlyConnectingMechanism(LateralBlock, LateralMeta, Offset)) { - if (Callback(Adjacent + OffsetYP)) + // Any direct connections on a lateral means the wire has side connection in that direction: + SetDirectionState(Offset, Block, TemporaryDirection::Side); + + // Temporary: this case will eventually be handled when wires are placed, with the state saved as blocks + // When a neighbour wire was loaded into its chunk, its neighbour chunks may not have loaded yet + // This function is called during chunk load (through AddBlock). Attempt to tell it its new state: + if ((NeighbourChunk != &Chunk) && (LateralBlock == E_BLOCK_REDSTONE_WIRE)) { - return true; + auto & NeighbourBlock = DataForChunk(*NeighbourChunk).WireStates.find(Adjacent)->second; + SetDirectionState(-Offset, NeighbourBlock, TemporaryDirection::Side); } + + continue; } if ( - BLOCKTYPE YMTerraceBlock, YMDiagonalBlock; - - // IsYMTerracingBlocked (i.e. check block above lower terracing position, a.k.a. just the plain adjacent) - a_Chunk.UnboundedRelGetBlockType(a_Position + Adjacent, YMTerraceBlock) && - (!cBlockInfo::IsSolid(YMTerraceBlock) || cBlockInfo::IsTransparent(YMTerraceBlock)) && - - a_Chunk.UnboundedRelGetBlockType(a_Position + Adjacent + OffsetYM, YMDiagonalBlock) && - (YMDiagonalBlock == E_BLOCK_REDSTONE_WIRE) + !IsYPTerracingBlocked && // A block above us blocks all YP terracing, so the check is static in the loop + (Adjacent.y < (cChunkDef::Height - 1)) && + (NeighbourChunk->GetBlock(Adjacent + OffsetYP) == E_BLOCK_REDSTONE_WIRE) // Only terrace YP with another wire ) { - if (Callback(Adjacent + OffsetYM)) + SetDirectionState(Offset, Block, TemporaryDirection::Up); + + if (NeighbourChunk != &Chunk) { - return true; + auto & NeighbourBlock = DataForChunk(*NeighbourChunk).WireStates.find(Adjacent + OffsetYP)->second; + SetDirectionState(-Offset, NeighbourBlock, TemporaryDirection::Side); } + + continue; } - } - return false; - } + if ( + // IsYMTerracingBlocked (i.e. check block above lower terracing position, a.k.a. just the plain adjacent) + (!cBlockInfo::IsSolid(LateralBlock) || cBlockInfo::IsTransparent(LateralBlock)) && + (NeighbourChunk->GetBlock(Adjacent + OffsetYM) == E_BLOCK_REDSTONE_WIRE) // Only terrace YM with another wire + ) + { + SetDirectionState(Offset, Block, TemporaryDirection::Side); - virtual unsigned char GetPowerDeliveredToPosition(cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, Vector3i a_QueryPosition, BLOCKTYPE a_QueryBlockType) const override - { - if (a_QueryPosition == (a_Position + OffsetYP)) - { - // Wires do not power things above them - return 0; + if (NeighbourChunk != &Chunk) + { + auto & NeighbourBlock = DataForChunk(*NeighbourChunk).WireStates.find(Adjacent + OffsetYM)->second; + SetDirectionState(-Offset, NeighbourBlock, TemporaryDirection::Up); + } + } } - if (a_QueryBlockType == E_BLOCK_REDSTONE_WIRE) + auto & States = DataForChunk(Chunk).WireStates; + const auto FindResult = States.find(Position); + if (FindResult != States.end()) { - // For mechanisms, wire of power one will still power them - // But for wire-to-wire connections, power level decreases by 1 - return (a_Meta != 0) ? --a_Meta : a_Meta; - } + if (Block != FindResult->second) + { + FindResult->second = Block; - // Wires always deliver power to the block underneath, and any directly connecting mechanisms - if ( - NIBBLETYPE QueryMeta; + // TODO: when state is stored as the block, the block handler updating via SetBlock will do this automatically + // When a wire changes connection state, it needs to update its neighbours: + Chunk.GetWorld()->WakeUpSimulators(cChunkDef::RelativeToAbsolute(Position, Chunk.GetPos())); + } - (a_QueryPosition == (a_Position + OffsetYM)) || - (a_Chunk.UnboundedRelGetBlockMeta(a_QueryPosition, QueryMeta) && IsDirectlyConnectingMechanism(a_QueryBlockType, QueryMeta, a_QueryPosition - a_Position)) - ) - { - return a_Meta; + return; } - /* - Okay, we do not directly connect to the wire. - If there are no DC mechanisms at all, the wire powers all laterals. Great, we fall out the loop. - If there is one DC mechanism, the wire "goes straight" along the axis of the wire and mechanism. - The only possible way for us to be powered is for us to be on the opposite end, with the wire pointing towards us. - If there is more than one DC, no non-DCs are powered. - */ + DataForChunk(Chunk).WireStates[Position] = Block; + } - Vector3i PotentialOffset; - bool FoundOneBorderingMechanism = false; +private: - if ( - ForTerracingConnectionOffsets(a_Chunk, a_Position, [&a_Chunk, a_Position, &FoundOneBorderingMechanism, &PotentialOffset](const Vector3i Offset) + inline static bool IsDirectlyConnectingMechanism(BLOCKTYPE a_Block, NIBBLETYPE a_BlockMeta, const Vector3i a_Offset) + { + switch (a_Block) + { + case E_BLOCK_REDSTONE_REPEATER_ON: + case E_BLOCK_REDSTONE_REPEATER_OFF: { - BLOCKTYPE Block; - NIBBLETYPE Meta; - - if ( - !a_Chunk.UnboundedRelGetBlock(Offset + a_Position, Block, Meta) || - !IsDirectlyConnectingMechanism(Block, Meta, Offset) - ) + a_BlockMeta &= E_META_REDSTONE_REPEATER_FACING_MASK; + if ((a_BlockMeta == E_META_REDSTONE_REPEATER_FACING_XP) || (a_BlockMeta == E_META_REDSTONE_REPEATER_FACING_XM)) { - return false; + // Wire connects to repeater if repeater is aligned along X + // and wire is in front or behind it (#4639) + return a_Offset.x != 0; } - if (FoundOneBorderingMechanism) - { - // Case 3 - return true; - } + return a_Offset.z != 0; + } + case E_BLOCK_ACTIVE_COMPARATOR: + case E_BLOCK_INACTIVE_COMPARATOR: + case E_BLOCK_BLOCK_OF_REDSTONE: + case E_BLOCK_REDSTONE_TORCH_OFF: + case E_BLOCK_REDSTONE_TORCH_ON: + case E_BLOCK_REDSTONE_WIRE: return true; + default: return false; + } + } - // Potential case 2 - FoundOneBorderingMechanism = true; - PotentialOffset = { -Offset.x, 0, -Offset.z }; + virtual unsigned char GetPowerDeliveredToPosition(const cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, Vector3i a_QueryPosition, BLOCKTYPE a_QueryBlockType, bool IsLinked) const override + { + // Starts off as the wire's meta value, modified appropriately and returned + auto Power = a_Chunk.GetMeta(a_Position); + const auto QueryOffset = a_QueryPosition - a_Position; - return false; - }) + if ( + (QueryOffset == OffsetYP) || // Wires do not power things above them + (IsLinked && (a_QueryBlockType == E_BLOCK_REDSTONE_WIRE)) // Nor do they link power other wires ) { - // Case 3 return 0; } - if (FoundOneBorderingMechanism && (a_QueryPosition != (a_Position + PotentialOffset))) + if (QueryOffset == OffsetYM) { - // Case 2 fail - return 0; + // Wires always deliver power to the block underneath + return Power; } - // Case 1 - // Case 2 success + const auto & Data = DataForChunk(a_Chunk); + const auto Block = Data.WireStates.find(a_Position)->second; + + DoWithDirectionState(QueryOffset, Block, [a_QueryBlockType, &Power](const auto Left, const auto Front, const auto Right) + { + using LeftState = std::remove_reference_t<decltype(Left)>; + using FrontState = std::remove_reference_t<decltype(Front)>; + using RightState = std::remove_reference_t<decltype(Right)>; + + // Wires always deliver power to any directly connecting mechanisms: + if (Front != FrontState::None) + { + if ((a_QueryBlockType == E_BLOCK_REDSTONE_WIRE) && (Power != 0)) + { + // For mechanisms, wire of power one will still power them + // But for wire-to-wire connections, power level decreases by 1: + Power--; + } + + return; + } + + /* + Okay, we do not directly connect to the wire. + 1. If there are no DC mechanisms at all, the wire powers all laterals. Great, left and right are both None. + 2. If there is one DC mechanism, the wire "goes straight" along the axis of the wire and mechanism. + The only possible way for us to be powered is for us to be on the opposite end, with the wire pointing towards us. + Check that left and right are both None. + 3. If there is more than one DC, no non-DCs are powered. Left, right, cannot both be None. + */ + if ((Left == LeftState::None) && (Right == RightState::None)) + { + // Case 1 + // Case 2 + return; + } - return a_Meta; + // Case 3 + Power = 0; + }); + + return Power; } virtual void Update(cChunk & a_Chunk, cChunk & CurrentlyTicking, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, PoweringData a_PoweringData) const override { // LOGD("Evaluating dusty the wire (%d %d %d) %i", a_Position.x, a_Position.y, a_Position.z, a_PoweringData.PowerLevel); - if (a_Meta != a_PoweringData.PowerLevel) + if (a_Meta == a_PoweringData.PowerLevel) { - a_Chunk.SetMeta(a_Position, a_PoweringData.PowerLevel); + return; + } - // Notify block below us to update: - UpdateAdjustedRelative(a_Chunk, CurrentlyTicking, a_Position + OffsetYM); + a_Chunk.SetMeta(a_Position, a_PoweringData.PowerLevel); - // Notify all terracing positions: - ForTerracingConnectionOffsets(a_Chunk, a_Position, [&a_Chunk, &CurrentlyTicking, a_Position](const Vector3i Offset) + // Notify all positions, sans YP, to update: + for (const auto Offset : RelativeAdjacents) + { + if (Offset == OffsetYP) { - UpdateAdjustedRelative(a_Chunk, CurrentlyTicking, a_Position + Offset); - return false; - }); + continue; + } + + UpdateAdjustedRelative(a_Chunk, CurrentlyTicking, a_Position, Offset); } } - virtual void ForValidSourcePositions(cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, SourceCallback Callback) const override + virtual void ForValidSourcePositions(const cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta, SourceCallback Callback) const override { - UNUSED(a_Chunk); UNUSED(a_BlockType); UNUSED(a_Meta); Callback(a_Position + OffsetYP); Callback(a_Position + OffsetYM); - ForTerracingConnectionOffsets(a_Chunk, a_Position, [&Callback, a_Position](const Vector3i Offset) + const auto & Data = DataForChunk(a_Chunk); + const auto Block = Data.WireStates.find(a_Position)->second; + + // Figure out, based on our pre-computed block, where we connect to: + for (const auto Offset : RelativeLaterals) { - Callback(a_Position + Offset); - return false; - }); + const auto Relative = a_Position + Offset; + Callback(Relative); + + DoWithDirectionState(Offset, Block, [&a_Chunk, &Callback, Relative](auto, const auto Front, auto) + { + using FrontState = std::remove_reference_t<decltype(Front)>; + + if (Front == FrontState::Up) + { + Callback(Relative + OffsetYP); + } + else if (Front == FrontState::Side) + { + // Alas, no way to distinguish side lateral and side diagonal + // Have to do a manual check to only accept power from YM diagonal if there's a wire there + + const auto YMDiagonalPosition = Relative + OffsetYM; + if ( + BLOCKTYPE Block; + cChunkDef::IsValidHeight(YMDiagonalPosition.y) && + a_Chunk.UnboundedRelGetBlockType(YMDiagonalPosition, Block) && + (Block == E_BLOCK_REDSTONE_WIRE) + ) + { + Callback(YMDiagonalPosition); + } + } + }); + } } }; |