summaryrefslogtreecommitdiffstats
path: root/src/Simulator
diff options
context:
space:
mode:
Diffstat (limited to 'src/Simulator')
-rw-r--r--src/Simulator/DelayedFluidSimulator.cpp158
-rw-r--r--src/Simulator/DelayedFluidSimulator.h82
-rw-r--r--src/Simulator/FireSimulator.cpp374
-rw-r--r--src/Simulator/FireSimulator.h75
-rw-r--r--src/Simulator/FloodyFluidSimulator.cpp330
-rw-r--r--src/Simulator/FloodyFluidSimulator.h53
-rw-r--r--src/Simulator/FluidSimulator.cpp212
-rw-r--r--src/Simulator/FluidSimulator.h75
-rw-r--r--src/Simulator/NoopFluidSimulator.h36
-rw-r--r--src/Simulator/RedstoneSimulator.cpp1073
-rw-r--r--src/Simulator/RedstoneSimulator.h199
-rw-r--r--src/Simulator/SandSimulator.cpp309
-rw-r--r--src/Simulator/SandSimulator.h63
-rw-r--r--src/Simulator/Simulator.cpp51
-rw-r--r--src/Simulator/Simulator.h46
-rw-r--r--src/Simulator/SimulatorManager.cpp80
-rw-r--r--src/Simulator/SimulatorManager.h52
-rw-r--r--src/Simulator/VaporizeFluidSimulator.cpp53
-rw-r--r--src/Simulator/VaporizeFluidSimulator.h34
19 files changed, 3355 insertions, 0 deletions
diff --git a/src/Simulator/DelayedFluidSimulator.cpp b/src/Simulator/DelayedFluidSimulator.cpp
new file mode 100644
index 000000000..a4645ca09
--- /dev/null
+++ b/src/Simulator/DelayedFluidSimulator.cpp
@@ -0,0 +1,158 @@
+
+// DelayedFluidSimulator.cpp
+
+// Interfaces to the cDelayedFluidSimulator class representing a fluid simulator that has a configurable delay
+// before simulating a block. Each tick it takes a consecutive delay "slot" and simulates only blocks in that slot.
+
+#include "Globals.h"
+
+#include "DelayedFluidSimulator.h"
+#include "../World.h"
+#include "../Chunk.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cDelayedFluidSimulatorChunkData::cSlot
+
+bool cDelayedFluidSimulatorChunkData::cSlot::Add(int a_RelX, int a_RelY, int a_RelZ)
+{
+ ASSERT(a_RelZ >= 0);
+ ASSERT(a_RelZ < ARRAYCOUNT(m_Blocks));
+
+ cCoordWithIntVector & Blocks = m_Blocks[a_RelZ];
+ int Index = cChunkDef::MakeIndexNoCheck(a_RelX, a_RelY, a_RelZ);
+ for (cCoordWithIntVector::const_iterator itr = Blocks.begin(), end = Blocks.end(); itr != end; ++itr)
+ {
+ if (itr->Data == Index)
+ {
+ // Already present
+ return false;
+ }
+ } // for itr - Blocks[]
+ Blocks.push_back(cCoordWithInt(a_RelX, a_RelY, a_RelZ, Index));
+ return true;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cDelayedFluidSimulatorChunkData:
+
+cDelayedFluidSimulatorChunkData::cDelayedFluidSimulatorChunkData(int a_TickDelay) :
+ m_Slots(new cSlot[a_TickDelay])
+{
+}
+
+
+
+
+
+cDelayedFluidSimulatorChunkData::~cDelayedFluidSimulatorChunkData()
+{
+ delete[] m_Slots;
+ m_Slots = NULL;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cDelayedFluidSimulator:
+
+cDelayedFluidSimulator::cDelayedFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid, int a_TickDelay) :
+ super(a_World, a_Fluid, a_StationaryFluid),
+ m_TickDelay(a_TickDelay),
+ m_AddSlotNum(a_TickDelay - 1),
+ m_SimSlotNum(0),
+ m_TotalBlocks(0)
+{
+}
+
+
+
+
+
+void cDelayedFluidSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
+ {
+ // Not inside the world (may happen when rclk with a full bucket - the client sends Y = -1)
+ return;
+ }
+
+ if ((a_Chunk == NULL) || !a_Chunk->IsValid())
+ {
+ return;
+ }
+
+ int RelX = a_BlockX - a_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_BlockZ - a_Chunk->GetPosZ() * cChunkDef::Width;
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(RelX, a_BlockY, RelZ);
+ if (BlockType != m_FluidBlock)
+ {
+ return;
+ }
+
+ void * ChunkDataRaw = (m_FluidBlock == E_BLOCK_WATER) ? a_Chunk->GetWaterSimulatorData() : a_Chunk->GetLavaSimulatorData();
+ cDelayedFluidSimulatorChunkData * ChunkData = (cDelayedFluidSimulatorChunkData *)ChunkDataRaw;
+ cDelayedFluidSimulatorChunkData::cSlot & Slot = ChunkData->m_Slots[m_AddSlotNum];
+
+ // Add, if not already present:
+ if (!Slot.Add(RelX, a_BlockY, RelZ))
+ {
+ return;
+ }
+
+ ++m_TotalBlocks;
+}
+
+
+
+
+
+void cDelayedFluidSimulator::Simulate(float a_Dt)
+{
+ m_AddSlotNum = m_SimSlotNum;
+ m_SimSlotNum += 1;
+ if (m_SimSlotNum >= m_TickDelay)
+ {
+ m_SimSlotNum = 0;
+ }
+}
+
+
+
+
+
+void cDelayedFluidSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
+{
+ void * ChunkDataRaw = (m_FluidBlock == E_BLOCK_WATER) ? a_Chunk->GetWaterSimulatorData() : a_Chunk->GetLavaSimulatorData();
+ cDelayedFluidSimulatorChunkData * ChunkData = (cDelayedFluidSimulatorChunkData *)ChunkDataRaw;
+ cDelayedFluidSimulatorChunkData::cSlot & Slot = ChunkData->m_Slots[m_SimSlotNum];
+
+ // Simulate all the blocks in the scheduled slot:
+ for (int i = 0; i < ARRAYCOUNT(Slot.m_Blocks); i++)
+ {
+ cCoordWithIntVector & Blocks = Slot.m_Blocks[i];
+ if (Blocks.empty())
+ {
+ continue;
+ }
+ for (cCoordWithIntVector::iterator itr = Blocks.begin(), end = Blocks.end(); itr != end; ++itr)
+ {
+ SimulateBlock(a_Chunk, itr->x, itr->y, itr->z);
+ }
+ m_TotalBlocks -= Blocks.size();
+ Blocks.clear();
+ }
+}
+
+
+
+
diff --git a/src/Simulator/DelayedFluidSimulator.h b/src/Simulator/DelayedFluidSimulator.h
new file mode 100644
index 000000000..c81500741
--- /dev/null
+++ b/src/Simulator/DelayedFluidSimulator.h
@@ -0,0 +1,82 @@
+
+// DelayedFluidSimulator.h
+
+// Interfaces to the cDelayedFluidSimulator class representing a fluid simulator that has a configurable delay
+// before simulating a block. Each tick it takes a consecutive delay "slot" and simulates only blocks in that slot.
+
+
+
+
+#pragma once
+
+#include "FluidSimulator.h"
+
+
+
+
+
+class cDelayedFluidSimulatorChunkData :
+ public cFluidSimulatorData
+{
+public:
+ class cSlot
+ {
+ public:
+ /// Returns true if the specified block is stored
+ bool HasBlock(int a_RelX, int a_RelY, int a_RelZ);
+
+ /// Adds the specified block unless already present; returns true if added, false if the block was already present
+ bool Add(int a_RelX, int a_RelY, int a_RelZ);
+
+ /** Array of block containers, each item stores blocks for one Z coord
+ Int param is the block index (for faster duplicate comparison in Add())
+ */
+ cCoordWithIntVector m_Blocks[16];
+ } ;
+
+ cDelayedFluidSimulatorChunkData(int a_TickDelay);
+ virtual ~cDelayedFluidSimulatorChunkData();
+
+ /// Slots, one for each delay tick, each containing the blocks to simulate
+ cSlot * m_Slots;
+} ;
+
+
+
+
+
+class cDelayedFluidSimulator :
+ public cFluidSimulator
+{
+ typedef cFluidSimulator super;
+
+public:
+ cDelayedFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid, int a_TickDelay);
+
+ // cSimulator overrides:
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override;
+ virtual void Simulate(float a_Dt) override;
+ virtual void SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) override;
+ virtual cFluidSimulatorData * CreateChunkData(void) override { return new cDelayedFluidSimulatorChunkData(m_TickDelay); }
+
+protected:
+
+ int m_TickDelay; // Count of the m_Slots array in each ChunkData
+ int m_AddSlotNum; // Index into m_Slots[] where to add new blocks in each ChunkData
+ int m_SimSlotNum; // Index into m_Slots[] where to simulate blocks in each ChunkData
+
+ int m_TotalBlocks; // Statistics only: the total number of blocks currently queued
+
+ /*
+ Slots:
+ | 0 | 1 | ... | m_AddSlotNum | m_SimSlotNum | ... | m_TickDelay - 1 |
+ adding blocks here ^ | ^ simulating here
+ */
+
+ /// Called from SimulateChunk() to simulate each block in one slot of blocks. Descendants override this method to provide custom simulation.
+ virtual void SimulateBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) = 0;
+} ;
+
+
+
+
diff --git a/src/Simulator/FireSimulator.cpp b/src/Simulator/FireSimulator.cpp
new file mode 100644
index 000000000..ac3fb9695
--- /dev/null
+++ b/src/Simulator/FireSimulator.cpp
@@ -0,0 +1,374 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "FireSimulator.h"
+#include "../World.h"
+#include "../BlockID.h"
+#include "../Defines.h"
+#include "../Chunk.h"
+
+
+
+
+
+// Easy switch for turning on debugging logging:
+#if 0
+ #define FLOG LOGD
+#else
+ #define FLOG(...)
+#endif
+
+
+
+
+
+#define MAX_CHANCE_REPLACE_FUEL 100000
+#define MAX_CHANCE_FLAMMABILITY 100000
+
+
+
+
+
+static const struct
+{
+ int x, y, z;
+} gCrossCoords[] =
+{
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+} ;
+
+
+
+
+
+static const struct
+{
+ int x, y, z;
+} gNeighborCoords[] =
+{
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 1, 0},
+ { 0, -1, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+} ;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFireSimulator:
+
+cFireSimulator::cFireSimulator(cWorld & a_World, cIniFile & a_IniFile) :
+ cSimulator(a_World)
+{
+ // Read params from the ini file:
+ m_BurnStepTimeFuel = a_IniFile.GetValueSetI("FireSimulator", "BurnStepTimeFuel", 500);
+ m_BurnStepTimeNonfuel = a_IniFile.GetValueSetI("FireSimulator", "BurnStepTimeNonfuel", 100);
+ m_Flammability = a_IniFile.GetValueSetI("FireSimulator", "Flammability", 50);
+ m_ReplaceFuelChance = a_IniFile.GetValueSetI("FireSimulator", "ReplaceFuelChance", 50000);
+}
+
+
+
+
+
+cFireSimulator::~cFireSimulator()
+{
+}
+
+
+
+
+
+void cFireSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
+{
+ cCoordWithIntList & Data = a_Chunk->GetFireSimulatorData();
+
+ int NumMSecs = (int)a_Dt;
+ for (cCoordWithIntList::iterator itr = Data.begin(); itr != Data.end();)
+ {
+ int idx = cChunkDef::MakeIndexNoCheck(itr->x, itr->y, itr->z);
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(idx);
+
+ if (!IsAllowedBlock(BlockType))
+ {
+ // The block is no longer eligible (not a fire block anymore; a player probably placed a block over the fire)
+ FLOG("FS: Removing block {%d, %d, %d}",
+ itr->x + a_ChunkX * cChunkDef::Width, itr->y, itr->z + a_ChunkZ * cChunkDef::Width
+ );
+ itr = Data.erase(itr);
+ continue;
+ }
+
+ // Try to spread the fire:
+ TrySpreadFire(a_Chunk, itr->x, itr->y, itr->z);
+
+ itr->Data -= NumMSecs;
+ if (itr->Data >= 0)
+ {
+ // Not yet, wait for it longer
+ ++itr;
+ continue;
+ }
+
+ // Burn out the fire one step by increasing the meta:
+ /*
+ FLOG("FS: Fire at {%d, %d, %d} is stepping",
+ itr->x + a_ChunkX * cChunkDef::Width, itr->y, itr->z + a_ChunkZ * cChunkDef::Width
+ );
+ */
+ NIBBLETYPE BlockMeta = a_Chunk->GetMeta(idx);
+ if (BlockMeta == 0x0f)
+ {
+ // The fire burnt out completely
+ FLOG("FS: Fire at {%d, %d, %d} burnt out, removing the fire block",
+ itr->x + a_ChunkX * cChunkDef::Width, itr->y, itr->z + a_ChunkZ * cChunkDef::Width
+ );
+ a_Chunk->SetBlock(itr->x, itr->y, itr->z, E_BLOCK_AIR, 0);
+ RemoveFuelNeighbors(a_Chunk, itr->x, itr->y, itr->z);
+ itr = Data.erase(itr);
+ continue;
+ }
+ a_Chunk->SetMeta(idx, BlockMeta + 1);
+ itr->Data = GetBurnStepTime(a_Chunk, itr->x, itr->y, itr->z); // TODO: Add some randomness into this
+ } // for itr - Data[]
+}
+
+
+
+
+
+bool cFireSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
+{
+ return (a_BlockType == E_BLOCK_FIRE);
+}
+
+
+
+
+
+bool cFireSimulator::IsFuel(BLOCKTYPE a_BlockType)
+{
+ switch (a_BlockType)
+ {
+ case E_BLOCK_PLANKS:
+ case E_BLOCK_LEAVES:
+ case E_BLOCK_LOG:
+ case E_BLOCK_WOOL:
+ case E_BLOCK_BOOKCASE:
+ case E_BLOCK_FENCE:
+ case E_BLOCK_TNT:
+ case E_BLOCK_VINES:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cFireSimulator::IsForever(BLOCKTYPE a_BlockType)
+{
+ return (a_BlockType == E_BLOCK_NETHERRACK);
+}
+
+
+
+
+
+void cFireSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ if ((a_Chunk == NULL) || !a_Chunk->IsValid())
+ {
+ return;
+ }
+
+ int RelX = a_BlockX - a_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_BlockZ - a_Chunk->GetPosZ() * cChunkDef::Width;
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(RelX, a_BlockY, RelZ);
+ if (!IsAllowedBlock(BlockType))
+ {
+ return;
+ }
+
+ // Check for duplicates:
+ cFireSimulatorChunkData & ChunkData = a_Chunk->GetFireSimulatorData();
+ for (cCoordWithIntList::iterator itr = ChunkData.begin(), end = ChunkData.end(); itr != end; ++itr)
+ {
+ if ((itr->x == RelX) && (itr->y == a_BlockY) && (itr->z == RelZ))
+ {
+ // Already present, skip adding
+ return;
+ }
+ } // for itr - ChunkData[]
+
+ FLOG("FS: Adding block {%d, %d, %d}", a_BlockX, a_BlockY, a_BlockZ);
+ ChunkData.push_back(cCoordWithInt(RelX, a_BlockY, RelZ, 100));
+}
+
+
+
+
+
+int cFireSimulator::GetBurnStepTime(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ bool IsBlockBelowSolid = false;
+ if (a_RelY > 0)
+ {
+ BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ);
+ if (IsForever(BlockBelow))
+ {
+ // Is burning atop of netherrack, burn forever (re-check in 10 sec)
+ return 10000;
+ }
+ if (IsFuel(BlockBelow))
+ {
+ return m_BurnStepTimeFuel;
+ }
+ IsBlockBelowSolid = g_BlockIsSolid[BlockBelow];
+ }
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ if (a_Chunk->UnboundedRelGetBlock(a_RelX + gCrossCoords[i].x, a_RelY, a_RelZ + gCrossCoords[i].z, BlockType, BlockMeta))
+ {
+ if (IsFuel(BlockType))
+ {
+ return m_BurnStepTimeFuel;
+ }
+ }
+ } // for i - gCrossCoords[]
+
+ if (!IsBlockBelowSolid && (a_RelY >= 0))
+ {
+ // Checked through everything, nothing was flammable
+ // If block below isn't solid, we can't have fire, it would be a non-fueled fire
+ // SetBlock just to make sure fire doesn't spawn
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_AIR, 0);
+ return 0;
+ }
+ return m_BurnStepTimeNonfuel;
+}
+
+
+
+
+
+void cFireSimulator::TrySpreadFire(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ /*
+ if (m_World.GetTickRandomNumber(10000) > 100)
+ {
+ // Make the chance to spread 100x smaller
+ return;
+ }
+ */
+
+ for (int x = a_RelX - 1; x <= a_RelX + 1; x++)
+ {
+ for (int z = a_RelZ - 1; z <= a_RelZ + 1; z++)
+ {
+ for (int y = a_RelY - 1; y <= a_RelY + 2; y++) // flames spread up one more block than around
+ {
+ // No need to check the coords for equality with the parent block,
+ // it cannot catch fire anyway (because it's not an air block)
+
+ if (m_World.GetTickRandomNumber(MAX_CHANCE_FLAMMABILITY) > m_Flammability)
+ {
+ continue;
+ }
+
+ // Start the fire in the neighbor {x, y, z}
+ /*
+ FLOG("FS: Trying to start fire at {%d, %d, %d}.",
+ x + a_Chunk->GetPosX() * cChunkDef::Width, y, z + a_Chunk->GetPosZ() * cChunkDef::Width
+ );
+ */
+ if (CanStartFireInBlock(a_Chunk, x, y, z))
+ {
+ FLOG("FS: Starting new fire at {%d, %d, %d}.",
+ x + a_Chunk->GetPosX() * cChunkDef::Width, y, z + a_Chunk->GetPosZ() * cChunkDef::Width
+ );
+ a_Chunk->UnboundedRelSetBlock(x, y, z, E_BLOCK_FIRE, 0);
+ }
+ } // for y
+ } // for z
+ } // for x
+}
+
+
+
+
+
+void cFireSimulator::RemoveFuelNeighbors(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ for (int i = 0; i < ARRAYCOUNT(gNeighborCoords); i++)
+ {
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ if (!a_Chunk->UnboundedRelGetBlock(a_RelX + gNeighborCoords[i].x, a_RelY + gNeighborCoords[i].y, a_RelZ + gNeighborCoords[i].z, BlockType, BlockMeta))
+ {
+ // Neighbor not accessible, ignore it
+ continue;
+ }
+ if (!IsFuel(BlockType))
+ {
+ continue;
+ }
+ bool ShouldReplaceFuel = (m_World.GetTickRandomNumber(MAX_CHANCE_REPLACE_FUEL) < m_ReplaceFuelChance);
+ a_Chunk->UnboundedRelSetBlock(
+ a_RelX + gNeighborCoords[i].x, a_RelY + gNeighborCoords[i].y, a_RelZ + gNeighborCoords[i].z,
+ ShouldReplaceFuel ? E_BLOCK_FIRE : E_BLOCK_AIR, 0
+ );
+ } // for i - Coords[]
+}
+
+
+
+
+
+bool cFireSimulator::CanStartFireInBlock(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ if (!a_NearChunk->UnboundedRelGetBlock(a_RelX, a_RelY, a_RelZ, BlockType, BlockMeta))
+ {
+ // The chunk is not accessible
+ return false;
+ }
+
+ if (BlockType != E_BLOCK_AIR)
+ {
+ // Only an air block can be replaced by a fire block
+ return false;
+ }
+
+ for (int i = 0; i < ARRAYCOUNT(gNeighborCoords); i++)
+ {
+ if (!a_NearChunk->UnboundedRelGetBlock(a_RelX + gNeighborCoords[i].x, a_RelY + gNeighborCoords[i].y, a_RelZ + gNeighborCoords[i].z, BlockType, BlockMeta))
+ {
+ // Neighbor inaccessible, skip it while evaluating
+ continue;
+ }
+ if (IsFuel(BlockType))
+ {
+ return true;
+ }
+ } // for i - Coords[]
+ return false;
+}
+
+
+
+
diff --git a/src/Simulator/FireSimulator.h b/src/Simulator/FireSimulator.h
new file mode 100644
index 000000000..0d8a548ef
--- /dev/null
+++ b/src/Simulator/FireSimulator.h
@@ -0,0 +1,75 @@
+
+#pragma once
+
+#include "Simulator.h"
+#include "../BlockEntities/BlockEntity.h"
+
+
+
+
+
+/** The fire simulator takes care of the fire blocks.
+It periodically increases their meta ("steps") until they "burn out"; it also supports the forever burning netherrack.
+Each individual fire block gets stored in per-chunk data; that list is then used for fast retrieval.
+The data value associated with each coord is used as the number of msec that the fire takes until
+it progresses to the next step (blockmeta++). This value is updated if a neighbor is changed.
+The simulator reads its parameters from the ini file given to the constructor.
+*/
+class cFireSimulator :
+ public cSimulator
+{
+public:
+ cFireSimulator(cWorld & a_World, cIniFile & a_IniFile);
+ ~cFireSimulator();
+
+ virtual void Simulate(float a_Dt) override {} // not used
+ virtual void SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) override;
+
+ virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) override;
+
+ bool IsFuel (BLOCKTYPE a_BlockType);
+ bool IsForever(BLOCKTYPE a_BlockType);
+
+protected:
+ /// Time (in msec) that a fire block takes to burn with a fuel block into the next step
+ unsigned m_BurnStepTimeFuel;
+
+ /// Time (in msec) that a fire block takes to burn without a fuel block into the next step
+ unsigned m_BurnStepTimeNonfuel;
+
+ /// Chance [0..100000] of an adjacent fuel to catch fire on each tick
+ int m_Flammability;
+
+ /// Chance [0..100000] of a fuel burning out being replaced by a new fire block instead of an air block
+ int m_ReplaceFuelChance;
+
+
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override;
+
+ /// Returns the time [msec] after which the specified fire block is stepped again; based on surrounding fuels
+ int GetBurnStepTime(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ);
+
+ /// Tries to spread fire to a neighborhood of the specified block
+ void TrySpreadFire(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ);
+
+ /// Removes all burnable blocks neighboring the specified block
+ void RemoveFuelNeighbors(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ);
+
+ /** Returns true if a fire can be started in the specified block,
+ that is, it is an air block and has fuel next to it.
+ Note that a_NearChunk may be a chunk neighbor to the block specified!
+ The coords are relative to a_NearChunk but not necessarily in it.
+ */
+ bool CanStartFireInBlock(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ);
+} ;
+
+
+
+
+
+/// Stores individual fire blocks in the chunk; the int data is used as the time [msec] the fire takes to step to another stage (blockmeta++)
+typedef cCoordWithIntList cFireSimulatorChunkData;
+
+
+
+
diff --git a/src/Simulator/FloodyFluidSimulator.cpp b/src/Simulator/FloodyFluidSimulator.cpp
new file mode 100644
index 000000000..d204a1f8b
--- /dev/null
+++ b/src/Simulator/FloodyFluidSimulator.cpp
@@ -0,0 +1,330 @@
+
+// FloodyFluidSimulator.cpp
+
+// Interfaces to the cFloodyFluidSimulator that represents a fluid simulator that tries to flood everything :)
+// http://forum.mc-server.org/showthread.php?tid=565
+
+#include "Globals.h"
+
+#include "FloodyFluidSimulator.h"
+#include "../World.h"
+#include "../Chunk.h"
+#include "../BlockArea.h"
+#include "../Blocks/BlockHandler.h"
+
+
+
+
+
+// Enable or disable detailed logging
+#if 0
+ #define FLOG LOGD
+#else
+ #define FLOG(...)
+#endif
+
+
+
+
+
+cFloodyFluidSimulator::cFloodyFluidSimulator(
+ cWorld & a_World,
+ BLOCKTYPE a_Fluid,
+ BLOCKTYPE a_StationaryFluid,
+ NIBBLETYPE a_Falloff,
+ int a_TickDelay,
+ int a_NumNeighborsForSource
+) :
+ super(a_World, a_Fluid, a_StationaryFluid, a_TickDelay),
+ m_Falloff(a_Falloff),
+ m_NumNeighborsForSource(a_NumNeighborsForSource)
+{
+}
+
+
+
+
+
+void cFloodyFluidSimulator::SimulateBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ FLOG("Simulating block {%d, %d, %d}: block %d, meta %d",
+ a_Chunk->GetPosX() * cChunkDef::Width + a_RelX, a_RelY, a_Chunk->GetPosZ() * cChunkDef::Width + a_RelZ,
+ a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ),
+ a_Chunk->GetMeta(a_RelX, a_RelY, a_RelZ)
+ );
+
+ NIBBLETYPE MyMeta = a_Chunk->GetMeta(a_RelX, a_RelY, a_RelZ);
+ if (!IsAnyFluidBlock(a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ)))
+ {
+ // Can happen - if a block is scheduled for simulating and gets replaced in the meantime.
+ FLOG(" BadBlockType exit");
+ return;
+ }
+
+ if (MyMeta != 0)
+ {
+ // Source blocks aren't checked for tributaries, others are.
+ if (CheckTributaries(a_Chunk, a_RelX, a_RelY, a_RelZ, MyMeta))
+ {
+ // Has no tributary, has been decreased (in CheckTributaries()),
+ // no more processing needed (neighbors have been scheduled by the decrease)
+ FLOG(" CheckTributaries exit");
+ return;
+ }
+ }
+
+ // New meta for the spreading to neighbors:
+ // If this is a source block or was falling, the new meta is just the falloff
+ // Otherwise it is the current meta plus falloff (may be larger than max height, will be checked later)
+ NIBBLETYPE NewMeta = ((MyMeta == 0) || ((MyMeta & 0x08) != 0)) ? m_Falloff : (MyMeta + m_Falloff);
+ bool SpreadFurther = true;
+ if (a_RelY > 0)
+ {
+ BLOCKTYPE Below = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ);
+ if (IsPassableForFluid(Below) || IsBlockLava(Below) || IsBlockWater(Below))
+ {
+ // Spread only down, possibly washing away what's there or turning lava to stone / cobble / obsidian:
+ SpreadToNeighbor(a_Chunk, a_RelX, a_RelY - 1, a_RelZ, 8);
+ SpreadFurther = false;
+ }
+ // If source creation is on, check for it here:
+ else if (
+ (m_NumNeighborsForSource > 0) && // Source creation is on
+ (MyMeta == m_Falloff) && // Only exactly one block away from a source (fast bail-out)
+ !IsPassableForFluid(Below) && // Only exactly 1 block deep
+ CheckNeighborsForSource(a_Chunk, a_RelX, a_RelY, a_RelZ) // Did we create a source?
+ )
+ {
+ // We created a source, no more spreading is to be done now
+ // Also has been re-scheduled for ticking in the next wave, so no marking is needed
+ return;
+ }
+ }
+
+ if (SpreadFurther && (NewMeta < 8))
+ {
+ // Spread to the neighbors:
+ SpreadToNeighbor(a_Chunk, a_RelX - 1, a_RelY, a_RelZ, NewMeta);
+ SpreadToNeighbor(a_Chunk, a_RelX + 1, a_RelY, a_RelZ, NewMeta);
+ SpreadToNeighbor(a_Chunk, a_RelX, a_RelY, a_RelZ - 1, NewMeta);
+ SpreadToNeighbor(a_Chunk, a_RelX, a_RelY, a_RelZ + 1, NewMeta);
+ }
+
+ // Mark as processed:
+ a_Chunk->FastSetBlock(a_RelX, a_RelY, a_RelZ, m_StationaryFluidBlock, MyMeta);
+}
+
+
+
+
+
+bool cFloodyFluidSimulator::CheckTributaries(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_MyMeta)
+{
+ // If we have a section above, check if there's fluid above this block that would feed it:
+ if (a_RelY < cChunkDef::Height - 1)
+ {
+ if (IsAnyFluidBlock(a_Chunk->GetBlock(a_RelX, a_RelY + 1, a_RelZ)))
+ {
+ // This block is fed from above, no more processing needed
+ FLOG(" Fed from above");
+ return false;
+ }
+ }
+
+ // Not fed from above, check if there's a feed from the side (but not if it's a downward-flowing block):
+ if (a_MyMeta != 8)
+ {
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ static const Vector3i Coords[] =
+ {
+ Vector3i( 1, 0, 0),
+ Vector3i(-1, 0, 0),
+ Vector3i( 0, 0, 1),
+ Vector3i( 0, 0, -1),
+ } ;
+ for (int i = 0; i < ARRAYCOUNT(Coords); i++)
+ {
+ if (!a_Chunk->UnboundedRelGetBlock(a_RelX + Coords[i].x, a_RelY, a_RelZ + Coords[i].z, BlockType, BlockMeta))
+ {
+ continue;
+ }
+ if (IsAllowedBlock(BlockType) && IsHigherMeta(BlockMeta, a_MyMeta))
+ {
+ // This block is fed, no more processing needed
+ FLOG(" Fed from {%d, %d, %d}, type %d, meta %d",
+ a_Chunk->GetPosX() * cChunkDef::Width + a_RelX + Coords[i].x,
+ a_RelY,
+ a_Chunk->GetPosZ() * cChunkDef::Width + a_RelZ + Coords[i].z,
+ BlockType, BlockMeta
+ );
+ return false;
+ }
+ } // for i - Coords[]
+ } // if not fed from above
+
+ // Block is not fed, decrease by m_Falloff levels:
+ if (a_MyMeta >= 8)
+ {
+ FLOG(" Not fed and downwards, turning into non-downwards meta %d", m_Falloff);
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, m_StationaryFluidBlock, m_Falloff);
+ }
+ else
+ {
+ a_MyMeta += m_Falloff;
+ if (a_MyMeta < 8)
+ {
+ FLOG(" Not fed, decreasing from %d to %d", a_MyMeta - m_Falloff, a_MyMeta);
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, m_StationaryFluidBlock, a_MyMeta);
+ }
+ else
+ {
+ FLOG(" Not fed, meta %d, erasing altogether", a_MyMeta);
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_AIR, 0);
+ }
+ }
+ return true;
+}
+
+
+
+
+
+void cFloodyFluidSimulator::SpreadToNeighbor(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
+{
+ ASSERT(a_NewMeta <= 8); // Invalid meta values
+ ASSERT(a_NewMeta > 0); // Source blocks aren't spread
+
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ if (!a_NearChunk->UnboundedRelGetBlock(a_RelX, a_RelY, a_RelZ, BlockType, BlockMeta))
+ {
+ // Chunk not available
+ return;
+ }
+
+ if (IsAllowedBlock(BlockType))
+ {
+ if ((BlockMeta == a_NewMeta) || IsHigherMeta(BlockMeta, a_NewMeta))
+ {
+ // Don't spread there, there's already a higher or same level there
+ return;
+ }
+ }
+
+ // Check water - lava interaction:
+ if (m_FluidBlock == E_BLOCK_LAVA)
+ {
+ if (IsBlockWater(BlockType))
+ {
+ // Lava flowing into water, change to stone / cobblestone based on direction:
+ BLOCKTYPE NewBlock = (a_NewMeta == 8) ? E_BLOCK_STONE : E_BLOCK_COBBLESTONE;
+ FLOG(" Lava flowing into water, turning water at rel {%d, %d, %d} into stone",
+ a_RelX, a_RelY, a_RelZ,
+ ItemTypeToString(NewBlock).c_str()
+ );
+ a_NearChunk->UnboundedRelSetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0);
+ m_World.BroadcastSoundEffect("random.fizz", a_RelX * 8, a_RelY * 8, a_RelZ * 8, 0.5f, 1.5f);
+ return;
+ }
+ }
+ else if (m_FluidBlock == E_BLOCK_WATER)
+ {
+ if (IsBlockLava(BlockType))
+ {
+ // Water flowing into lava, change to cobblestone / obsidian based on dest block:
+ BLOCKTYPE NewBlock = (BlockMeta == 0) ? E_BLOCK_OBSIDIAN : E_BLOCK_COBBLESTONE;
+ FLOG(" Water flowing into lava, turning lava at rel {%d, %d, %d} into %s",
+ a_RelX, a_RelY, a_RelZ, ItemTypeToString(NewBlock).c_str()
+ );
+ a_NearChunk->UnboundedRelSetBlock(a_RelX, a_RelY, a_RelZ, NewBlock, 0);
+ m_World.BroadcastSoundEffect("random.fizz", a_RelX * 8, a_RelY * 8, a_RelZ * 8, 0.5f, 1.5f);
+ return;
+ }
+ }
+ else
+ {
+ ASSERT(!"Unknown fluid!");
+ }
+
+ if (!IsPassableForFluid(BlockType))
+ {
+ // Can't spread there
+ return;
+ }
+
+ // Wash away the block there, if possible:
+ if (CanWashAway(BlockType))
+ {
+ cBlockHandler * Handler = BlockHandler(BlockType);
+ if (Handler->DoesDropOnUnsuitable())
+ {
+ Handler->DropBlock(
+ &m_World, NULL,
+ a_NearChunk->GetPosX() * cChunkDef::Width + a_RelX,
+ a_RelY,
+ a_NearChunk->GetPosZ() * cChunkDef::Width + a_RelZ
+ );
+ }
+ } // if (CanWashAway)
+
+ // Spread:
+ FLOG(" Spreading to {%d, %d, %d} with meta %d",
+ a_NearChunk->GetPosX() * cChunkDef::Width + a_RelX,
+ a_RelY,
+ a_NearChunk->GetPosZ() * cChunkDef::Width + a_RelZ,
+ a_NewMeta
+ );
+ a_NearChunk->UnboundedRelSetBlock(a_RelX, a_RelY, a_RelZ, m_FluidBlock, a_NewMeta);
+}
+
+
+
+
+
+bool cFloodyFluidSimulator::CheckNeighborsForSource(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ FLOG(" Checking neighbors for source creation");
+
+ static const Vector3i NeighborCoords[] =
+ {
+ Vector3i(-1, 0, 0),
+ Vector3i( 1, 0, 0),
+ Vector3i( 0, 0, -1),
+ Vector3i( 0, 0, 1),
+ } ;
+
+ int NumNeeded = m_NumNeighborsForSource;
+ for (int i = 0; i < ARRAYCOUNT(NeighborCoords); i++)
+ {
+ int x = a_RelX + NeighborCoords[i].x;
+ int y = a_RelY + NeighborCoords[i].y;
+ int z = a_RelZ + NeighborCoords[i].z;
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ if (!a_Chunk->UnboundedRelGetBlock(x, y, z, BlockType, BlockMeta))
+ {
+ // Neighbor not available, skip it
+ continue;
+ }
+ // FLOG(" Neighbor at {%d, %d, %d}: %s", x, y, z, ItemToFullString(cItem(BlockType, 1, BlockMeta)).c_str());
+ if ((BlockMeta == 0) && IsAnyFluidBlock(BlockType))
+ {
+ NumNeeded--;
+ // FLOG(" Found a neighbor source at {%d, %d, %d}, NumNeeded := %d", x, y, z, NumNeeded);
+ if (NumNeeded == 0)
+ {
+ // Found enough, turn into a source and bail out
+ // FLOG(" Found enough neighbor sources, turning into a source");
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, m_FluidBlock, 0);
+ return true;
+ }
+ }
+ }
+ // FLOG(" Not enough neighbors for turning into a source, NumNeeded = %d", NumNeeded);
+ return false;
+}
+
+
+
+
diff --git a/src/Simulator/FloodyFluidSimulator.h b/src/Simulator/FloodyFluidSimulator.h
new file mode 100644
index 000000000..c4af2e246
--- /dev/null
+++ b/src/Simulator/FloodyFluidSimulator.h
@@ -0,0 +1,53 @@
+
+// FloodyFluidSimulator.h
+
+// Interfaces to the cFloodyFluidSimulator that represents a fluid simulator that tries to flood everything :)
+// http://forum.mc-server.org/showthread.php?tid=565
+
+
+
+
+
+#pragma once
+
+#include "DelayedFluidSimulator.h"
+
+
+
+
+
+// fwd:
+class cBlockArea;
+
+
+
+
+
+class cFloodyFluidSimulator :
+ public cDelayedFluidSimulator
+{
+ typedef cDelayedFluidSimulator super;
+
+public:
+ cFloodyFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid, NIBBLETYPE a_Falloff, int a_TickDelay, int a_NumNeighborsForSource);
+
+protected:
+ NIBBLETYPE m_Falloff;
+ int m_NumNeighborsForSource;
+
+ // cDelayedFluidSimulator overrides:
+ virtual void SimulateBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) override;
+
+ /// Checks tributaries, if not fed, decreases the block's level and returns true
+ bool CheckTributaries(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_MyMeta);
+
+ /// Spreads into the specified block, if the blocktype there allows. a_Area is for checking.
+ void SpreadToNeighbor(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta);
+
+ /// Checks if there are enough neighbors to create a source at the coords specified; turns into source and returns true if so
+ bool CheckNeighborsForSource(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ);
+} ;
+
+
+
+
diff --git a/src/Simulator/FluidSimulator.cpp b/src/Simulator/FluidSimulator.cpp
new file mode 100644
index 000000000..dac666484
--- /dev/null
+++ b/src/Simulator/FluidSimulator.cpp
@@ -0,0 +1,212 @@
+
+#include "Globals.h"
+
+#include "FluidSimulator.h"
+#include "../World.h"
+
+
+
+
+
+cFluidSimulator::cFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid) :
+ super(a_World),
+ m_FluidBlock(a_Fluid),
+ m_StationaryFluidBlock(a_StationaryFluid)
+{
+}
+
+
+
+
+
+bool cFluidSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
+{
+ return ((a_BlockType == m_FluidBlock) || (a_BlockType == m_StationaryFluidBlock));
+}
+
+
+
+
+
+bool cFluidSimulator::CanWashAway(BLOCKTYPE a_BlockType)
+{
+ switch (a_BlockType)
+ {
+ case E_BLOCK_BROWN_MUSHROOM:
+ case E_BLOCK_CACTUS:
+ case E_BLOCK_COBWEB:
+ case E_BLOCK_CROPS:
+ case E_BLOCK_DEAD_BUSH:
+ case E_BLOCK_RAIL:
+ case E_BLOCK_REDSTONE_TORCH_OFF:
+ case E_BLOCK_REDSTONE_TORCH_ON:
+ case E_BLOCK_REDSTONE_WIRE:
+ case E_BLOCK_RED_MUSHROOM:
+ case E_BLOCK_RED_ROSE:
+ case E_BLOCK_SNOW:
+ case E_BLOCK_SUGARCANE:
+ case E_BLOCK_TALL_GRASS:
+ case E_BLOCK_TORCH:
+ case E_BLOCK_YELLOW_FLOWER:
+ {
+ return true;
+ }
+ default:
+ {
+ return false;
+ }
+ }
+}
+
+
+
+
+
+bool cFluidSimulator::IsSolidBlock(BLOCKTYPE a_BlockType)
+{
+ return !IsPassableForFluid(a_BlockType);
+}
+
+
+
+
+
+bool cFluidSimulator::IsPassableForFluid(BLOCKTYPE a_BlockType)
+{
+ return (
+ (a_BlockType == E_BLOCK_AIR) ||
+ (a_BlockType == E_BLOCK_FIRE) ||
+ IsAllowedBlock(a_BlockType) ||
+ CanWashAway(a_BlockType)
+ );
+}
+
+
+
+
+
+bool cFluidSimulator::IsHigherMeta(NIBBLETYPE a_Meta1, NIBBLETYPE a_Meta2)
+{
+ if (a_Meta1 == 0)
+ {
+ // Source block is higher than anything, even itself.
+ return true;
+ }
+ if ((a_Meta1 & 0x08) != 0)
+ {
+ // Falling fluid is higher than anything, including self
+ return true;
+ }
+
+ if (a_Meta2 == 0)
+ {
+ // Second block is a source and first block isn't
+ return false;
+ }
+ if ((a_Meta2 & 0x08) != 0)
+ {
+ // Second block is falling and the first one is neither a source nor falling
+ return false;
+ }
+
+ // All special cases have been handled, now it's just a raw comparison:
+ return (a_Meta1 < a_Meta2);
+}
+
+
+
+
+
+// TODO Not working very well yet :s
+Direction cFluidSimulator::GetFlowingDirection(int a_X, int a_Y, int a_Z, bool a_Over)
+{
+ if ((a_Y < 0) || (a_Y >= cChunkDef::Height))
+ {
+ return NONE;
+ }
+ BLOCKTYPE BlockID = m_World.GetBlock(a_X, a_Y, a_Z);
+ if (!IsAllowedBlock(BlockID)) // No Fluid -> No Flowing direction :D
+ {
+ return NONE;
+ }
+
+ /*
+ Disabled because of causing problems and being useless atm
+ char BlockBelow = m_World.GetBlock(a_X, a_Y - 1, a_Z); //If there is nothing or fluid below it -> dominating flow is down :D
+ if (BlockBelow == E_BLOCK_AIR || IsAllowedBlock(BlockBelow))
+ return Y_MINUS;
+ */
+
+ NIBBLETYPE LowestPoint = m_World.GetBlockMeta(a_X, a_Y, a_Z); //Current Block Meta so only lower points will be counted
+ int X = 0, Y = 0, Z = 0; //Lowest Pos will be stored here
+
+ if (IsAllowedBlock(m_World.GetBlock(a_X, a_Y + 1, a_Z)) && a_Over) //check for upper block to flow because this also affects the flowing direction
+ {
+ return GetFlowingDirection(a_X, a_Y + 1, a_Z, false);
+ }
+
+ std::vector< Vector3i * > Points;
+
+ Points.reserve(4); //Already allocate 4 places :D
+
+ //add blocks around the checking pos
+ Points.push_back(new Vector3i(a_X - 1, a_Y, a_Z));
+ Points.push_back(new Vector3i(a_X + 1, a_Y, a_Z));
+ Points.push_back(new Vector3i(a_X, a_Y, a_Z + 1));
+ Points.push_back(new Vector3i(a_X, a_Y, a_Z - 1));
+
+ for (std::vector<Vector3i *>::iterator it = Points.begin(); it < Points.end(); it++)
+ {
+ Vector3i *Pos = (*it);
+ char BlockID = m_World.GetBlock(Pos->x, Pos->y, Pos->z);
+ if(IsAllowedBlock(BlockID))
+ {
+ char Meta = m_World.GetBlockMeta(Pos->x, Pos->y, Pos->z);
+
+ if(Meta > LowestPoint)
+ {
+ LowestPoint = Meta;
+ X = Pos->x;
+ Y = Pos->y;
+ Z = Pos->z;
+ }
+ }else if(BlockID == E_BLOCK_AIR)
+ {
+ LowestPoint = 9; //This always dominates
+ X = Pos->x;
+ Y = Pos->y;
+ Z = Pos->z;
+
+ }
+ delete Pos;
+ }
+
+ if (LowestPoint == m_World.GetBlockMeta(a_X, a_Y, a_Z))
+ return NONE;
+
+ if (a_X - X > 0)
+ {
+ return X_MINUS;
+ }
+
+ if (a_X - X < 0)
+ {
+ return X_PLUS;
+ }
+
+ if (a_Z - Z > 0)
+ {
+ return Z_MINUS;
+ }
+
+ if (a_Z - Z < 0)
+ {
+ return Z_PLUS;
+ }
+
+ return NONE;
+}
+
+
+
+
diff --git a/src/Simulator/FluidSimulator.h b/src/Simulator/FluidSimulator.h
new file mode 100644
index 000000000..672b740a2
--- /dev/null
+++ b/src/Simulator/FluidSimulator.h
@@ -0,0 +1,75 @@
+
+#pragma once
+
+#include "Simulator.h"
+
+
+
+
+
+enum Direction
+{
+ X_PLUS,
+ X_MINUS,
+ Y_PLUS,
+ Y_MINUS,
+ Z_PLUS,
+ Z_MINUS,
+ NONE
+};
+
+
+
+
+
+/** This is a base class for all fluid simulator data classes.
+Needed so that cChunk can properly delete instances of fluid simulator data, no matter what simulator it's using
+*/
+class cFluidSimulatorData
+{
+public:
+ virtual ~cFluidSimulatorData() {}
+} ;
+
+
+
+
+
+class cFluidSimulator :
+ public cSimulator
+{
+ typedef cSimulator super;
+
+public:
+ cFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid);
+
+ // cSimulator overrides:
+ virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) override;
+
+ /// Gets the flowing direction. If a_Over is true also the block over the current block affects the direction (standard)
+ virtual Direction GetFlowingDirection(int a_X, int a_Y, int a_Z, bool a_Over = true);
+
+ /// Creates a ChunkData object for the simulator to use. The simulator returns the correct object type.
+ virtual cFluidSimulatorData * CreateChunkData(void) { return NULL; }
+
+ bool IsFluidBlock (BLOCKTYPE a_BlockType) const { return (a_BlockType == m_FluidBlock); }
+ bool IsStationaryFluidBlock(BLOCKTYPE a_BlockType) const { return (a_BlockType == m_StationaryFluidBlock); }
+ bool IsAnyFluidBlock (BLOCKTYPE a_BlockType) const { return ((a_BlockType == m_FluidBlock) || (a_BlockType == m_StationaryFluidBlock)); }
+
+ static bool CanWashAway(BLOCKTYPE a_BlockType);
+
+ bool IsSolidBlock (BLOCKTYPE a_BlockType);
+ bool IsPassableForFluid(BLOCKTYPE a_BlockType);
+
+ /// Returns true if a_Meta1 is a higher fluid than a_Meta2. Takes source blocks into account.
+ bool IsHigherMeta(NIBBLETYPE a_Meta1, NIBBLETYPE a_Meta2);
+
+protected:
+ BLOCKTYPE m_FluidBlock; // The fluid block type that needs simulating
+ BLOCKTYPE m_StationaryFluidBlock; // The fluid block type that indicates no simulation is needed
+} ;
+
+
+
+
+
diff --git a/src/Simulator/NoopFluidSimulator.h b/src/Simulator/NoopFluidSimulator.h
new file mode 100644
index 000000000..8f894433f
--- /dev/null
+++ b/src/Simulator/NoopFluidSimulator.h
@@ -0,0 +1,36 @@
+
+// NoopFluidSimulator.h
+
+// Declares the cNoopFluidSimulator class representing a fluid simulator that performs nothing, it ignores all blocks
+
+
+
+
+
+#pragma once
+
+#include "FluidSimulator.h"
+
+
+
+
+
+class cNoopFluidSimulator :
+ public cFluidSimulator
+{
+ typedef cFluidSimulator super;
+
+public:
+ cNoopFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid) :
+ super(a_World, a_Fluid, a_StationaryFluid)
+ {
+ }
+
+ // cSimulator overrides:
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override {}
+ virtual void Simulate(float a_Dt) override {}
+} ;
+
+
+
+
diff --git a/src/Simulator/RedstoneSimulator.cpp b/src/Simulator/RedstoneSimulator.cpp
new file mode 100644
index 000000000..906961490
--- /dev/null
+++ b/src/Simulator/RedstoneSimulator.cpp
@@ -0,0 +1,1073 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "RedstoneSimulator.h"
+#include "../BlockEntities/DropSpenserEntity.h"
+#include "../Entities/TNTEntity.h"
+#include "../Blocks/BlockTorch.h"
+#include "../Blocks/BlockDoor.h"
+#include "../Piston.h"
+
+
+
+
+
+cRedstoneSimulator::cRedstoneSimulator(cWorld & a_World)
+ : super(a_World)
+{
+}
+
+
+
+
+
+cRedstoneSimulator::~cRedstoneSimulator()
+{
+}
+
+
+
+
+
+void cRedstoneSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ if ((a_Chunk == NULL) || !a_Chunk->IsValid())
+ {
+ return;
+ }
+ else if ((a_BlockY < 0) || (a_BlockY > cChunkDef::Height))
+ {
+ return;
+ }
+
+ int RelX = a_BlockX - a_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_BlockZ - a_Chunk->GetPosZ() * cChunkDef::Width;
+
+ if (!IsAllowedBlock(a_Chunk->GetBlock(RelX, a_BlockY, RelZ)))
+ {
+ return;
+ }
+
+ // Check for duplicates:
+ cRedstoneSimulatorChunkData & ChunkData = a_Chunk->GetRedstoneSimulatorData();
+ for (cRedstoneSimulatorChunkData::iterator itr = ChunkData.begin(); itr != ChunkData.end(); ++itr)
+ {
+ if ((itr->x == RelX) && (itr->y == a_BlockY) && (itr->z == RelZ))
+ {
+ return;
+ }
+ }
+
+ ChunkData.push_back(cCoordWithInt(RelX, a_BlockY, RelZ));
+}
+
+
+
+
+
+void cRedstoneSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
+{
+ cRedstoneSimulatorChunkData & ChunkData = a_Chunk->GetRedstoneSimulatorData();
+ if (ChunkData.empty())
+ {
+ return;
+ }
+
+ int BaseX = a_Chunk->GetPosX() * cChunkDef::Width;
+ int BaseZ = a_Chunk->GetPosZ() * cChunkDef::Width;
+
+ for (cRedstoneSimulatorChunkData::iterator dataitr = ChunkData.begin(), end = ChunkData.end(); dataitr != end;)
+ {
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(dataitr->x, dataitr->y, dataitr->z);
+ if (!IsAllowedBlock(BlockType))
+ {
+ dataitr = ChunkData.erase(dataitr);
+ continue;
+ }
+
+ // Check to see if PoweredBlocks have invalid items (source is air or an unpowered source)
+ for (PoweredBlocksList::iterator itr = m_PoweredBlocks.begin(); itr != m_PoweredBlocks.end();)
+ {
+ sPoweredBlocks & Change = *itr;
+ BLOCKTYPE SourceBlockType = m_World.GetBlock(Change.a_SourcePos);
+
+ if (SourceBlockType != Change.a_SourceBlock)
+ {
+ itr = m_PoweredBlocks.erase(itr);
+ }
+ else if (
+ // Changeable sources
+ ((SourceBlockType == E_BLOCK_REDSTONE_WIRE) && (m_World.GetBlockMeta(Change.a_SourcePos) == 0)) ||
+ ((SourceBlockType == E_BLOCK_LEVER) && !IsLeverOn(m_World.GetBlockMeta(Change.a_SourcePos))) ||
+ ((SourceBlockType == E_BLOCK_DETECTOR_RAIL) && (m_World.GetBlockMeta(Change.a_SourcePos) & 0x08) == 0x08) ||
+ (((SourceBlockType == E_BLOCK_STONE_BUTTON) || (SourceBlockType == E_BLOCK_WOODEN_BUTTON)) && (!IsButtonOn(m_World.GetBlockMeta(Change.a_SourcePos))))
+ )
+ {
+ itr = m_PoweredBlocks.erase(itr);
+ }
+ else
+ {
+ itr++;
+ }
+ }
+
+ // Check to see if LinkedPoweredBlocks have invalid items: source, block powered through, or power destination block has changed
+ for (LinkedBlocksList::iterator itr = m_LinkedPoweredBlocks.begin(); itr != m_LinkedPoweredBlocks.end();)
+ {
+ sLinkedPoweredBlocks & Change = *itr;
+ BLOCKTYPE SourceBlockType = m_World.GetBlock(Change.a_SourcePos);
+ BLOCKTYPE MiddleBlockType = m_World.GetBlock(Change.a_MiddlePos);
+
+ if (SourceBlockType != Change.a_SourceBlock)
+ {
+ itr = m_LinkedPoweredBlocks.erase(itr);
+ }
+ else if (MiddleBlockType != Change.a_MiddleBlock)
+ {
+ itr = m_LinkedPoweredBlocks.erase(itr);
+ }
+ else if (
+ // Things that can send power through a block but which depends on meta
+ ((SourceBlockType == E_BLOCK_REDSTONE_WIRE) && (m_World.GetBlockMeta(Change.a_SourcePos) == 0)) ||
+ ((SourceBlockType == E_BLOCK_LEVER) && !IsLeverOn(m_World.GetBlockMeta(Change.a_SourcePos))) ||
+ (((SourceBlockType == E_BLOCK_STONE_BUTTON) || (SourceBlockType == E_BLOCK_WOODEN_BUTTON)) && (!IsButtonOn(m_World.GetBlockMeta(Change.a_SourcePos))))
+ )
+ {
+ itr = m_LinkedPoweredBlocks.erase(itr);
+ }
+ else
+ {
+ itr++;
+ }
+ }
+
+ // PoweredBlock list was fine, now to the actual handling
+ int a_X = BaseX + dataitr->x;
+ int a_Z = BaseZ + dataitr->z;
+ switch (BlockType)
+ {
+ case E_BLOCK_BLOCK_OF_REDSTONE: HandleRedstoneBlock(a_X, dataitr->y, a_Z); break;
+ case E_BLOCK_LEVER: HandleRedstoneLever(a_X, dataitr->y, a_Z); break;
+ case E_BLOCK_TNT: HandleTNT(a_X, dataitr->y, a_Z); break;
+ case E_BLOCK_REDSTONE_WIRE: HandleRedstoneWire(a_X, dataitr->y, a_Z); break;
+
+ case E_BLOCK_REDSTONE_TORCH_OFF:
+ case E_BLOCK_REDSTONE_TORCH_ON:
+ {
+ HandleRedstoneTorch(a_X, dataitr->y, a_Z, BlockType);
+ break;
+ }
+ case E_BLOCK_STONE_BUTTON:
+ case E_BLOCK_WOODEN_BUTTON:
+ {
+ HandleRedstoneButton(a_X, dataitr->y, a_Z, BlockType);
+ break;
+ }
+ case E_BLOCK_REDSTONE_REPEATER_OFF:
+ case E_BLOCK_REDSTONE_REPEATER_ON:
+ {
+ HandleRedstoneRepeater(a_X, dataitr->y, a_Z, BlockType);
+ break;
+ }
+ case E_BLOCK_PISTON:
+ case E_BLOCK_STICKY_PISTON:
+ {
+ HandlePiston(a_X, dataitr->y, a_Z);
+ break;
+ }
+ case E_BLOCK_REDSTONE_LAMP_OFF:
+ case E_BLOCK_REDSTONE_LAMP_ON:
+ {
+ HandleRedstoneLamp(a_X, dataitr->y, a_Z, BlockType);
+ break;
+ }
+ case E_BLOCK_DISPENSER:
+ case E_BLOCK_DROPPER:
+ {
+ HandleDropSpenser(a_X, dataitr->y, a_Z);
+ break;
+ }
+ case E_BLOCK_WOODEN_DOOR:
+ case E_BLOCK_IRON_DOOR:
+ {
+ HandleDoor(a_X, dataitr->y, a_Z);
+ break;
+ }
+ case E_BLOCK_ACTIVATOR_RAIL:
+ case E_BLOCK_DETECTOR_RAIL:
+ case E_BLOCK_POWERED_RAIL:
+ {
+ HandleRail(a_X, dataitr->y, a_Z, BlockType);
+ break;
+ }
+ }
+
+ ++dataitr;
+ }
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneTorch(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState)
+{
+ static const struct // Define which directions the torch can power
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 0, 1, 0},
+ } ;
+
+ if (a_MyState == E_BLOCK_REDSTONE_TORCH_ON)
+ {
+ // Check if the block the torch is on is powered
+ int X = a_BlockX; int Y = a_BlockY; int Z = a_BlockZ;
+ AddFaceDirection(X, Y, Z, cBlockTorchHandler::MetaDataToDirection(m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ)), true); // Inverse true to get the block torch is on
+
+ if (AreCoordsPowered(X, Y, Z))
+ {
+ // There was a match, torch goes off
+ // FastSetBlock so the server doesn't fail an assert -_-
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_TORCH_OFF, m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ));
+ return;
+ }
+
+ // Torch still on, make all 4(X, Z) + 1(Y) sides powered
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ BLOCKTYPE Type = m_World.GetBlock(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z);
+ if (i < ARRAYCOUNT(gCrossCoords) - 1) // Sides of torch, not top (top is last)
+ {
+ if (
+ ((IsMechanism(Type)) || (Type == E_BLOCK_REDSTONE_WIRE)) && // Is it a mechanism or wire? Not block/other torch etc.
+ (!Vector3i(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z).Equals(Vector3i(X, Y, Z))) // CAN'T power block is that it is on
+ )
+ {
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_TORCH_ON);
+ }
+ }
+ else
+ {
+ // Top side, power whatever is there, including blocks
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_TORCH_ON);
+ }
+ }
+
+ if (m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) != 0x5) // Is torch standing on ground? If not (i.e. on wall), power block beneath
+ {
+ BLOCKTYPE Type = m_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ);
+
+ if ((IsMechanism(Type)) || (Type == E_BLOCK_REDSTONE_WIRE)) // Still can't make a normal block powered though!
+ {
+ SetBlockPowered(a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_TORCH_ON);
+ }
+ }
+ }
+ else
+ {
+ // Check if the block the torch is on is powered
+ int X = a_BlockX; int Y = a_BlockY; int Z = a_BlockZ;
+ AddFaceDirection(X, Y, Z, cBlockTorchHandler::MetaDataToDirection(m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ)), true); // Inverse true to get the block torch is on
+
+ // See if off state torch can be turned on again
+ if (AreCoordsPowered(X, Y, Z))
+ {
+ return; // Something matches, torch still powered
+ }
+
+ // Block torch on not powered, can be turned on again!
+ // FastSetBlock so the server doesn't fail an assert -_-
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_TORCH_ON, m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ));
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneBlock(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ static const struct // Define which directions the redstone block can power
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 0, 0, 0}, // Oh, anomalous redstone. Only block that powers itself
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 0, 1, 0},
+ { 0,-1, 0},
+ } ;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ // Power everything
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_BLOCK_OF_REDSTONE);
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneLever(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ if (IsLeverOn(m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ)))
+ {
+ static const struct // Define which directions the redstone lever can power (all sides)
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 0, 1, 0},
+ { 0,-1, 0},
+ } ;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ // Power everything
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_LEVER);
+ }
+
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XM, E_BLOCK_LEVER);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XP, E_BLOCK_LEVER);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_YM, E_BLOCK_LEVER);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_YP, E_BLOCK_LEVER);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZM, E_BLOCK_LEVER);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZP, E_BLOCK_LEVER);
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneButton(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType)
+{
+ if (IsButtonOn(m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ)))
+ {
+ static const struct // Define which directions the redstone button can power (all sides)
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 0, 1, 0},
+ { 0,-1, 0},
+ } ;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ // Power everything
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, a_BlockType);
+ }
+
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XM, a_BlockType);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XP, a_BlockType);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_YM, a_BlockType);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_YP, a_BlockType);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZM, a_BlockType);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZP, a_BlockType);
+ }
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneWire(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ static const struct // Define which directions the wire can receive power from
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 1, 1, 0}, // From here to end, check for wire placed on sides of blocks
+ {-1, 1, 0},
+ { 0, 1, 1},
+ { 0, 1, -1},
+ { 1,-1, 0},
+ {-1,-1, 0},
+ { 0,-1, 1},
+ { 0,-1, -1},
+ } ;
+
+ // Check to see if directly beside a power source
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ m_World.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, 15); // Maximum power
+ }
+ else
+ {
+ NIBBLETYPE MyMeta = m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
+ int TimesMetaSmaller = 0, TimesFoundAWire = 0;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++) // Loop through all directions to transfer or receive power
+ {
+ BLOCKTYPE SurroundType;
+ NIBBLETYPE SurroundMeta;
+ m_World.GetBlockTypeMeta(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, SurroundType, SurroundMeta);
+
+ if (SurroundType == E_BLOCK_REDSTONE_WIRE)
+ {
+ TimesFoundAWire++;
+
+ if (SurroundMeta > 1) // Wires of power 1 or 0 cannot transfer power TO ME, don't bother checking
+ {
+ if (SurroundMeta > MyMeta) // Does surrounding wire have a higher power level than self?
+ {
+ m_World.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, SurroundMeta - 1);
+ }
+ }
+
+ if (SurroundMeta < MyMeta) // Go through all surroundings to see if self power is larger than everyone else's
+ {
+ TimesMetaSmaller++;
+ }
+ }
+ }
+
+ if (TimesMetaSmaller == TimesFoundAWire)
+ {
+ // All surrounding metas were smaller - self must have been a wire that was
+ // transferring power to other wires around.
+ // However, self not directly powered anymore, so source must have been removed,
+ // therefore, self must be set to meta zero
+ m_World.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, 0);
+ }
+ }
+
+ if (m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) != 0) // A powered wire
+ {
+ //SetBlockPowered(a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE); // No matter what, block underneath gets powered
+
+ switch (GetWireDirection(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ case REDSTONE_NONE:
+ {
+ static const struct // Define which directions the redstone wire can power
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0}, // Power block in front
+ { 2, 0, 0}, // Power block in front of that (strongly power)
+ {-1, 0, 0},
+ {-2, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, 2},
+ { 0, 0, -1},
+ { 0, 0, -2},
+ { 0, 1, 0},
+ { 0, 2, 0},
+ { 0,-1, 0},
+ { 0,-2, 0},
+ } ;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ // Power if block is solid, CURRENTLY all mechanisms are solid
+ if (g_BlockIsSolid[m_World.GetBlock(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z)])
+ {
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE);
+ }
+ }
+ break;
+ }
+ case REDSTONE_X_POS:
+ {
+ if (m_World.GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ) != E_BLOCK_REDSTONE_WIRE)
+ {
+ SetBlockPowered(a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE);
+ }
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XP, E_BLOCK_REDSTONE_WIRE);
+ break;
+ }
+ case REDSTONE_X_NEG:
+ {
+ if (m_World.GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ) != E_BLOCK_REDSTONE_WIRE)
+ {
+ SetBlockPowered(a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE);
+ }
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XM, E_BLOCK_REDSTONE_WIRE);
+ break;
+ }
+ case REDSTONE_Z_POS:
+ {
+ if (m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1) != E_BLOCK_REDSTONE_WIRE)
+ {
+ SetBlockPowered(a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE);
+ }
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZP, E_BLOCK_REDSTONE_WIRE);
+ break;
+ }
+ case REDSTONE_Z_NEG:
+ {
+ if (m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1) != E_BLOCK_REDSTONE_WIRE)
+ {
+ SetBlockPowered(a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_WIRE);
+ }
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZM, E_BLOCK_REDSTONE_WIRE);
+ break;
+ }
+ }
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneRepeater(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState)
+{
+ NIBBLETYPE a_Meta = m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
+ if (a_MyState == E_BLOCK_REDSTONE_REPEATER_OFF)
+ {
+ if (IsRepeaterPowered(a_BlockX, a_BlockY, a_BlockZ, a_Meta & 0x3))
+ {
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_ON, a_Meta);
+ switch (a_Meta & 0x3) // We only want the direction (bottom) bits
+ {
+ case 0x0:
+ {
+ SetBlockPowered(a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_ON);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZM, E_BLOCK_REDSTONE_REPEATER_ON);
+ break;
+ }
+ case 0x1:
+ {
+ SetBlockPowered(a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_ON);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XP, E_BLOCK_REDSTONE_REPEATER_ON);
+ break;
+ }
+ case 0x2:
+ {
+ SetBlockPowered(a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_ON);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_ZP, E_BLOCK_REDSTONE_REPEATER_ON);
+ break;
+ }
+ case 0x3:
+ {
+ SetBlockPowered(a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_ON);
+ SetDirectionLinkedPowered(a_BlockX, a_BlockY, a_BlockZ, BLOCK_FACE_XM, E_BLOCK_REDSTONE_REPEATER_ON);
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (!IsRepeaterPowered(a_BlockX, a_BlockY, a_BlockZ, a_Meta & 0x3))
+ {
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_REPEATER_OFF, a_Meta);
+ }
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandlePiston(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ cPiston Piston(&m_World);
+ Piston.ExtendPiston(a_BlockX, a_BlockY, a_BlockZ);
+ }
+ else
+ {
+ cPiston Piston(&m_World);
+ Piston.RetractPiston(a_BlockX, a_BlockY, a_BlockZ);
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleDropSpenser(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ class cSetPowerToDropSpenser :
+ public cDropSpenserCallback
+ {
+ bool m_IsPowered;
+ public:
+ cSetPowerToDropSpenser(bool a_IsPowered) : m_IsPowered(a_IsPowered) {}
+
+ virtual bool Item(cDropSpenserEntity * a_DropSpenser) override
+ {
+ a_DropSpenser->SetRedstonePower(m_IsPowered);
+ return false;
+ }
+ } DrSpSP (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ));
+
+ m_World.DoWithDropSpenserAt(a_BlockX, a_BlockY, a_BlockZ, DrSpSP);
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRedstoneLamp(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState)
+{
+ if (a_MyState == E_BLOCK_REDSTONE_LAMP_OFF)
+ {
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_LAMP_ON, 0);
+ }
+ }
+ else
+ {
+ if (!AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_REDSTONE_LAMP_OFF, 0);
+ }
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleTNT(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ m_World.BroadcastSoundEffect("random.fuse", a_BlockX * 8, a_BlockY * 8, a_BlockZ * 8, 0.5f, 0.6f);
+ m_World.SpawnPrimedTNT(a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5, 4); // 4 seconds to boom
+ m_World.FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_AIR, 0);
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleDoor(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ if ((m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) & 0x08) == 0x08)
+ {
+ // Block position is located at top half of door
+ // Is Y - 1 both within world boundaries, a door block, and the bottom half of a door?
+ // The bottom half stores the open/closed information
+ if (
+ (a_BlockY - 1 >= 0) &&
+ ((m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_WOODEN_DOOR) || (m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ) == E_BLOCK_IRON_DOOR)) &&
+ (m_World.GetBlockMeta(a_BlockX, a_BlockY - 1, a_BlockZ & 0x08) == 0)
+ )
+ {
+ if ((m_World.GetBlockMeta(a_BlockX, a_BlockY - 1, a_BlockZ) & 0x04) == 0) // Closed door?
+ {
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) // Powered? If so, toggle open
+ {
+ cBlockDoorHandler::ChangeDoor(&m_World, a_BlockX, a_BlockY, a_BlockZ);
+ }
+ }
+ else // Opened door
+ {
+ if (!AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) // Unpowered? Close if so
+ {
+ cBlockDoorHandler::ChangeDoor(&m_World, a_BlockX, a_BlockY, a_BlockZ);
+ }
+ }
+ }
+ }
+ else
+ {
+ if ((m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) & 0x04) == 0) // Closed door?
+ {
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) // Powered? If so, toggle open
+ {
+ cBlockDoorHandler::ChangeDoor(&m_World, a_BlockX, a_BlockY, a_BlockZ);
+ }
+ }
+ else // Opened door
+ {
+ if (!AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) // Unpowered? Close if so
+ {
+ cBlockDoorHandler::ChangeDoor(&m_World, a_BlockX, a_BlockY, a_BlockZ);
+ }
+ }
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::HandleRail(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyType)
+{
+ switch (a_MyType)
+ {
+ case E_BLOCK_DETECTOR_RAIL:
+ {
+ if ((m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) & 0x08) == 0x08)
+ {
+ static const struct // Define which directions the rail can power (all sides)
+ {
+ int x, y, z;
+ } gCrossCoords[] =
+ {
+ { 1, 0, 0},
+ {-1, 0, 0},
+ { 0, 0, 1},
+ { 0, 0, -1},
+ { 0, 1, 0},
+ { 0,-1, 0},
+ } ;
+
+ for (int i = 0; i < ARRAYCOUNT(gCrossCoords); i++)
+ {
+ // Power everything
+ SetBlockPowered(a_BlockX + gCrossCoords[i].x, a_BlockY + gCrossCoords[i].y, a_BlockZ + gCrossCoords[i].z, a_BlockX, a_BlockY, a_BlockZ, a_MyType);
+ }
+ }
+ break;
+ }
+ case E_BLOCK_ACTIVATOR_RAIL:
+ case E_BLOCK_POWERED_RAIL:
+ {
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ))
+ {
+ m_World.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) | 0x08);
+ }
+ else
+ {
+ m_World.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, m_World.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ) & 0x07);
+ }
+ break;
+ }
+ }
+}
+
+
+
+
+
+bool cRedstoneSimulator::AreCoordsPowered(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ for (PoweredBlocksList::iterator itr = m_PoweredBlocks.begin(); itr != m_PoweredBlocks.end(); ++itr) // Check powered list
+ {
+ sPoweredBlocks & Change = *itr;
+
+ if (Change.a_BlockPos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ)))
+ {
+ return true;
+ }
+ }
+
+ for (LinkedBlocksList::iterator itr = m_LinkedPoweredBlocks.begin(); itr != m_LinkedPoweredBlocks.end(); ++itr) // Check linked powered list
+ {
+ sLinkedPoweredBlocks & Change = *itr;
+
+ if (Change.a_BlockPos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ)))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cRedstoneSimulator::IsRepeaterPowered(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_Meta)
+{
+ // Check through powered blocks list
+ for (PoweredBlocksList::iterator itr = m_PoweredBlocks.begin(); itr != m_PoweredBlocks.end(); ++itr)
+ {
+ sPoweredBlocks & Change = *itr;
+
+ switch (a_Meta)
+ {
+ case 0x0:
+ {
+ // Flip the coords to check the back of the repeater
+ if (Change.a_SourcePos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ + 1))) { return true; }
+ break;
+ }
+ case 0x1:
+ {
+ if (Change.a_SourcePos.Equals(Vector3i(a_BlockX - 1, a_BlockY, a_BlockZ))) { return true; }
+ break;
+ }
+ case 0x2:
+ {
+ if (Change.a_SourcePos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ - 1))) { return true; }
+ break;
+ }
+ case 0x3:
+ {
+ if (Change.a_SourcePos.Equals(Vector3i(a_BlockX + 1, a_BlockY, a_BlockZ))) { return true; }
+ break;
+ }
+ }
+ }
+
+ // Check linked powered list, 'middle' blocks
+ for (LinkedBlocksList::iterator itr = m_LinkedPoweredBlocks.begin(); itr != m_LinkedPoweredBlocks.end(); ++itr)
+ {
+ sLinkedPoweredBlocks & Change = *itr;
+
+ switch (a_Meta)
+ {
+ case 0x0:
+ {
+ if (Change.a_MiddlePos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ + 1))) { return true; }
+ break;
+ }
+ case 0x1:
+ {
+ if (Change.a_MiddlePos.Equals(Vector3i(a_BlockX - 1, a_BlockY, a_BlockZ))) { return true; }
+ break;
+ }
+ case 0x2:
+ {
+ if (Change.a_MiddlePos.Equals(Vector3i(a_BlockX, a_BlockY, a_BlockZ - 1))) { return true; }
+ break;
+ }
+ case 0x3:
+ {
+ if (Change.a_MiddlePos.Equals(Vector3i(a_BlockX + 1, a_BlockY, a_BlockZ))) { return true; }
+ break;
+ }
+ }
+ }
+ return false; // Couldn't find power source behind repeater
+}
+
+
+
+
+
+void cRedstoneSimulator::SetDirectionLinkedPowered(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Direction, BLOCKTYPE a_SourceType)
+{
+ switch (a_Direction)
+ {
+ case BLOCK_FACE_XM:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX - 2, a_BlockY, a_BlockZ, a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY + 1, a_BlockZ, a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY - 1, a_BlockZ, a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY, a_BlockZ + 1, a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY, a_BlockZ - 1, a_BlockX - 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ case BLOCK_FACE_XP:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX + 2, a_BlockY, a_BlockZ, a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY + 1, a_BlockZ, a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY - 1, a_BlockZ, a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY, a_BlockZ + 1, a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY, a_BlockZ - 1, a_BlockX + 1, a_BlockY, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ case BLOCK_FACE_YM:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX, a_BlockY - 1, a_BlockZ);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX, a_BlockY - 2, a_BlockZ, a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY - 1, a_BlockZ + 1, a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY - 1, a_BlockZ - 1, a_BlockX, a_BlockY - 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ case BLOCK_FACE_YP:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX, a_BlockY + 1, a_BlockZ);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX, a_BlockY + 2, a_BlockZ, a_BlockX, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY + 1, a_BlockZ + 1, a_BlockX, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY + 1, a_BlockZ - 1, a_BlockX, a_BlockY + 1, a_BlockZ, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ case BLOCK_FACE_ZM:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX, a_BlockY, a_BlockZ - 2, a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY + 1, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY - 1, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ - 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ case BLOCK_FACE_ZP:
+ {
+ BLOCKTYPE MiddleBlock = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1);
+ if (!g_BlockIsSolid[MiddleBlock]) { return; }
+
+ SetBlockLinkedPowered(a_BlockX, a_BlockY, a_BlockZ + 2, a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX + 1, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX - 1, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY + 1, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ SetBlockLinkedPowered(a_BlockX, a_BlockY - 1, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ + 1, a_BlockX, a_BlockY, a_BlockZ, a_SourceType, MiddleBlock);
+ break;
+ }
+ default:
+ {
+ ASSERT(!"Unhandled face direction when attempting to set blocks as linked powered!");
+ break;
+ }
+ }
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::SetBlockPowered(int a_BlockX, int a_BlockY, int a_BlockZ, int a_SourceX, int a_SourceY, int a_SourceZ, BLOCKTYPE a_SourceBlock)
+{
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) { return; } // Check for duplicates
+
+ sPoweredBlocks RC;
+ RC.a_BlockPos = Vector3i(a_BlockX, a_BlockY, a_BlockZ);
+ RC.a_SourcePos = Vector3i(a_SourceX, a_SourceY, a_SourceZ);
+ RC.a_SourceBlock = a_SourceBlock;
+ m_PoweredBlocks.push_back(RC);
+ return;
+}
+
+
+
+
+
+void cRedstoneSimulator::SetBlockLinkedPowered(int a_BlockX, int a_BlockY, int a_BlockZ,
+ int a_MiddleX, int a_MiddleY, int a_MiddleZ,
+ int a_SourceX, int a_SourceY, int a_SourceZ,
+ BLOCKTYPE a_SourceBlock, BLOCKTYPE a_MiddleBlock
+ )
+{
+ if (AreCoordsPowered(a_BlockX, a_BlockY, a_BlockZ)) { return; } // Check for duplicates
+
+ sLinkedPoweredBlocks RC;
+ RC.a_BlockPos = Vector3i(a_BlockX, a_BlockY, a_BlockZ);
+ RC.a_MiddlePos = Vector3i(a_MiddleX, a_MiddleY, a_MiddleZ);
+ RC.a_SourcePos = Vector3i(a_SourceX, a_SourceY, a_SourceZ);
+ RC.a_SourceBlock = a_SourceBlock;
+ RC.a_MiddleBlock = a_MiddleBlock;
+ m_LinkedPoweredBlocks.push_back(RC);
+ return;
+}
+
+
+
+
+
+cRedstoneSimulator::eRedstoneDirection cRedstoneSimulator::GetWireDirection(int a_BlockX, int a_BlockY, int a_BlockZ)
+{
+ int Dir = REDSTONE_NONE;
+
+ BLOCKTYPE NegX = m_World.GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ);
+ if (IsPotentialSource(NegX))
+ {
+ Dir |= (REDSTONE_X_POS);
+ }
+
+ BLOCKTYPE PosX = m_World.GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ);
+ if (IsPotentialSource(PosX))
+ {
+ Dir |= (REDSTONE_X_NEG);
+ }
+
+ BLOCKTYPE NegZ = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1);
+ if (IsPotentialSource(NegZ))
+ {
+ if ((Dir & REDSTONE_X_POS) && !(Dir & REDSTONE_X_NEG)) // corner
+ {
+ Dir ^= REDSTONE_X_POS;
+ Dir |= REDSTONE_X_NEG;
+ }
+ if ((Dir & REDSTONE_X_NEG) && !(Dir & REDSTONE_X_POS)) // corner
+ {
+ Dir ^= REDSTONE_X_NEG;
+ Dir |= REDSTONE_X_POS;
+ }
+ Dir |= REDSTONE_Z_POS;
+ }
+
+ BLOCKTYPE PosZ = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1);
+ if (IsPotentialSource(PosZ))
+ {
+ if ((Dir & REDSTONE_X_POS) && !(Dir & REDSTONE_X_NEG)) // corner
+ {
+ Dir ^= REDSTONE_X_POS;
+ Dir |= REDSTONE_X_NEG;
+ }
+ if ((Dir & REDSTONE_X_NEG) && !(Dir & REDSTONE_X_POS)) // corner
+ {
+ Dir ^= REDSTONE_X_NEG;
+ Dir |= REDSTONE_X_POS;
+ }
+ Dir |= REDSTONE_Z_NEG;
+ }
+ return (eRedstoneDirection)Dir;
+}
+
+
+
+
+
+bool cRedstoneSimulator::IsLeverOn(NIBBLETYPE a_BlockMeta)
+{
+ // Extract the ON bit from metadata and return if true if it is set:
+ return ((a_BlockMeta & 0x8) == 0x8);
+}
+
+
+
+
+
+bool cRedstoneSimulator::IsButtonOn(NIBBLETYPE a_BlockMeta)
+{
+ return IsLeverOn(a_BlockMeta);
+}
+
+
+
+
diff --git a/src/Simulator/RedstoneSimulator.h b/src/Simulator/RedstoneSimulator.h
new file mode 100644
index 000000000..d68c6daeb
--- /dev/null
+++ b/src/Simulator/RedstoneSimulator.h
@@ -0,0 +1,199 @@
+
+#pragma once
+
+#include "Simulator.h"
+
+/// Per-chunk data for the simulator, specified individual chunks to simulate; 'Data' is not used
+typedef cCoordWithIntList cRedstoneSimulatorChunkData;
+
+
+
+
+
+class cRedstoneSimulator :
+ public cSimulator
+{
+ typedef cSimulator super;
+public:
+
+ cRedstoneSimulator(cWorld & a_World);
+ ~cRedstoneSimulator();
+
+ virtual void Simulate(float a_Dt) override {}; // Not used in this simulator
+ virtual void SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) override;
+ virtual bool IsAllowedBlock( BLOCKTYPE a_BlockType ) override { return IsRedstone(a_BlockType); }
+
+ enum eRedstoneDirection
+ {
+ REDSTONE_NONE = 0,
+ REDSTONE_X_POS = 0x1,
+ REDSTONE_X_NEG = 0x2,
+ REDSTONE_Z_POS = 0x4,
+ REDSTONE_Z_NEG = 0x8,
+ };
+ eRedstoneDirection GetWireDirection(int a_BlockX, int a_BlockY, int a_BlockZ);
+ eRedstoneDirection GetWireDirection(const Vector3i & a_Pos) { return GetWireDirection(a_Pos.x, a_Pos.y, a_Pos.z); }
+
+private:
+
+ struct sPoweredBlocks // Define structure of the directly powered blocks list
+ {
+ Vector3i a_BlockPos; // Position of powered block
+ Vector3i a_SourcePos; // Position of source powering the block at a_BlockPos
+ BLOCKTYPE a_SourceBlock; // The source block type (for pistons pushing away sources and replacing with non source etc.)
+ };
+
+ struct sLinkedPoweredBlocks // Define structure of the indirectly powered blocks list (i.e. repeaters powering through a block to the block at the other side)
+ {
+ Vector3i a_BlockPos;
+ Vector3i a_MiddlePos;
+ Vector3i a_SourcePos;
+ BLOCKTYPE a_SourceBlock;
+ BLOCKTYPE a_MiddleBlock;
+ };
+
+ typedef std::vector <sPoweredBlocks> PoweredBlocksList;
+ typedef std::vector <sLinkedPoweredBlocks> LinkedBlocksList;
+
+ PoweredBlocksList m_PoweredBlocks;
+ LinkedBlocksList m_LinkedPoweredBlocks;
+
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override;
+
+ // We want a_MyState for devices needing a full FastSetBlock (as opposed to meta) because with our simulation model, we cannot keep setting the block if it is already set correctly
+ // In addition to being non-performant, it would stop the player from actually breaking said device
+
+ /* ====== SOURCES ====== */
+ ///<summary>Handles the redstone torch</summary>
+ void HandleRedstoneTorch(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState);
+ ///<summary>Handles the redstone block</summary>
+ void HandleRedstoneBlock(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles levers</summary>
+ void HandleRedstoneLever(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles buttons</summary>
+ void HandleRedstoneButton(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType);
+ /* ==================== */
+
+ /* ====== CARRIERS ====== */
+ ///<summary>Handles redstone wire</summary>
+ void HandleRedstoneWire(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles repeaters</summary>
+ void HandleRedstoneRepeater(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState);
+ /* ====================== */
+
+ /* ====== DEVICES ====== */
+ ///<summary>Handles pistons</summary>
+ void HandlePiston(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles dispensers and droppers</summary>
+ void HandleDropSpenser(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles TNT (exploding)</summary>
+ void HandleTNT(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles redstone lamps</summary>
+ void HandleRedstoneLamp(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyState);
+ ///<summary>Handles doords</summary>
+ void HandleDoor(int a_BlockX, int a_BlockY, int a_BlockZ);
+ ///<summary>Handles activator, detector, and powered rails</summary>
+ void HandleRail(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_MyType);
+ /* ===================== */
+
+ /* ====== Helper functions ====== */
+ void SetBlockPowered(int a_BlockX, int a_BlockY, int a_BlockZ, int a_SourceX, int a_SourceY, int a_SourceZ, BLOCKTYPE a_SourceBlock);
+ void SetBlockLinkedPowered(int a_BlockX, int a_BlockY, int a_BlockZ, int a_MiddleX, int a_MiddleY, int a_MiddleZ, int a_SourceX, int a_SourceY, int a_SourceZ, BLOCKTYPE a_SourceBlock, BLOCKTYPE a_MiddeBlock);
+ void SetDirectionLinkedPowered(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Direction, BLOCKTYPE a_SourceType);
+
+ bool AreCoordsPowered(int a_BlockX, int a_BlockY, int a_BlockZ);
+ bool IsRepeaterPowered(int a_BlockX, int a_BlockY, int a_BlockZ, NIBBLETYPE a_Meta);
+
+ bool IsLeverOn(NIBBLETYPE a_BlockMeta);
+ bool IsButtonOn(NIBBLETYPE a_BlockMeta);
+ /* ============================== */
+
+ inline static bool IsMechanism(BLOCKTYPE Block)
+ {
+ switch (Block)
+ {
+ case E_BLOCK_PISTON:
+ case E_BLOCK_STICKY_PISTON:
+ case E_BLOCK_DISPENSER:
+ case E_BLOCK_DROPPER:
+ case E_BLOCK_TNT:
+ case E_BLOCK_REDSTONE_LAMP_OFF:
+ case E_BLOCK_REDSTONE_LAMP_ON:
+ case E_BLOCK_WOODEN_DOOR:
+ case E_BLOCK_IRON_DOOR:
+ case E_BLOCK_REDSTONE_REPEATER_OFF:
+ case E_BLOCK_REDSTONE_REPEATER_ON:
+ case E_BLOCK_POWERED_RAIL:
+ {
+ return true;
+ }
+ default: return false;
+ }
+ }
+
+ inline static bool IsPotentialSource(BLOCKTYPE Block)
+ {
+ switch (Block)
+ {
+ case E_BLOCK_WOODEN_BUTTON:
+ case E_BLOCK_STONE_BUTTON:
+ case E_BLOCK_REDSTONE_WIRE:
+ case E_BLOCK_REDSTONE_TORCH_OFF:
+ case E_BLOCK_REDSTONE_TORCH_ON:
+ case E_BLOCK_LEVER:
+ case E_BLOCK_REDSTONE_REPEATER_ON:
+ case E_BLOCK_REDSTONE_REPEATER_OFF:
+ case E_BLOCK_BLOCK_OF_REDSTONE:
+ case E_BLOCK_ACTIVE_COMPARATOR:
+ case E_BLOCK_INACTIVE_COMPARATOR:
+ {
+ return true;
+ }
+ default: return false;
+ }
+ }
+
+ inline static bool IsRedstone(BLOCKTYPE Block)
+ {
+ switch (Block)
+ {
+ // All redstone devices, please alpha sort
+ case E_BLOCK_ACTIVATOR_RAIL:
+ case E_BLOCK_ACTIVE_COMPARATOR:
+ case E_BLOCK_BLOCK_OF_REDSTONE:
+ case E_BLOCK_DETECTOR_RAIL:
+ case E_BLOCK_DISPENSER:
+ case E_BLOCK_DAYLIGHT_SENSOR:
+ case E_BLOCK_DROPPER:
+ case E_BLOCK_FENCE_GATE:
+ case E_BLOCK_HEAVY_WEIGHTED_PRESSURE_PLATE:
+ case E_BLOCK_HOPPER:
+ case E_BLOCK_INACTIVE_COMPARATOR:
+ case E_BLOCK_IRON_DOOR:
+ case E_BLOCK_LEVER:
+ case E_BLOCK_LIGHT_WEIGHTED_PRESSURE_PLATE:
+ case E_BLOCK_NOTE_BLOCK:
+ case E_BLOCK_REDSTONE_LAMP_OFF:
+ case E_BLOCK_REDSTONE_LAMP_ON:
+ case E_BLOCK_REDSTONE_REPEATER_OFF:
+ case E_BLOCK_REDSTONE_REPEATER_ON:
+ case E_BLOCK_REDSTONE_TORCH_OFF:
+ case E_BLOCK_REDSTONE_TORCH_ON:
+ case E_BLOCK_REDSTONE_WIRE:
+ case E_BLOCK_STICKY_PISTON:
+ case E_BLOCK_STONE_BUTTON:
+ case E_BLOCK_STONE_PRESSURE_PLATE:
+ case E_BLOCK_TNT:
+ case E_BLOCK_TRAPDOOR:
+ case E_BLOCK_TRIPWIRE_HOOK:
+ case E_BLOCK_WOODEN_BUTTON:
+ case E_BLOCK_WOODEN_DOOR:
+ case E_BLOCK_WOODEN_PRESSURE_PLATE:
+ case E_BLOCK_PISTON:
+ {
+ return true;
+ }
+ default: return false;
+ }
+ }
+}; \ No newline at end of file
diff --git a/src/Simulator/SandSimulator.cpp b/src/Simulator/SandSimulator.cpp
new file mode 100644
index 000000000..87fb83357
--- /dev/null
+++ b/src/Simulator/SandSimulator.cpp
@@ -0,0 +1,309 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "SandSimulator.h"
+#include "../World.h"
+#include "../BlockID.h"
+#include "../Defines.h"
+#include "../Entities/FallingBlock.h"
+#include "../Chunk.h"
+
+
+
+
+
+cSandSimulator::cSandSimulator(cWorld & a_World, cIniFile & a_IniFile) :
+ cSimulator(a_World),
+ m_TotalBlocks(0)
+{
+ m_IsInstantFall = a_IniFile.GetValueSetB("Physics", "SandInstantFall", false);
+}
+
+
+
+
+
+void cSandSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
+{
+ cSandSimulatorChunkData & ChunkData = a_Chunk->GetSandSimulatorData();
+ if (ChunkData.empty())
+ {
+ return;
+ }
+
+ int BaseX = a_Chunk->GetPosX() * cChunkDef::Width;
+ int BaseZ = a_Chunk->GetPosZ() * cChunkDef::Width;
+ for (cSandSimulatorChunkData::const_iterator itr = ChunkData.begin(), end = ChunkData.end(); itr != end; ++itr)
+ {
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(itr->x, itr->y, itr->z);
+ if (!IsAllowedBlock(BlockType) || (itr->y <= 0))
+ {
+ continue;
+ }
+
+ BLOCKTYPE BlockBelow = (itr->y > 0) ? a_Chunk->GetBlock(itr->x, itr->y - 1, itr->z) : E_BLOCK_AIR;
+ if (CanStartFallingThrough(BlockBelow))
+ {
+ if (m_IsInstantFall)
+ {
+ DoInstantFall(a_Chunk, itr->x, itr->y, itr->z);
+ continue;
+ }
+ Vector3i Pos;
+ Pos.x = itr->x + BaseX;
+ Pos.y = itr->y;
+ Pos.z = itr->z + BaseZ;
+ /*
+ LOGD(
+ "Creating a falling block at {%d, %d, %d} of type %s, block below: %s",
+ Pos.x, Pos.y, Pos.z, ItemTypeToString(BlockType).c_str(), ItemTypeToString(BlockBelow).c_str()
+ );
+ */
+ cFallingBlock * FallingBlock = new cFallingBlock(Pos, BlockType, a_Chunk->GetMeta(itr->x, itr->y, itr->z));
+ FallingBlock->Initialize(&m_World);
+ a_Chunk->SetBlock(itr->x, itr->y, itr->z, E_BLOCK_AIR, 0);
+ }
+ }
+ m_TotalBlocks -= ChunkData.size();
+ ChunkData.clear();
+}
+
+
+
+
+
+bool cSandSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
+{
+ switch (a_BlockType)
+ {
+ case E_BLOCK_SAND:
+ case E_BLOCK_GRAVEL:
+ case E_BLOCK_ANVIL:
+ case E_BLOCK_DRAGON_EGG:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+void cSandSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ if ((a_Chunk == NULL) || !a_Chunk->IsValid())
+ {
+ return;
+ }
+ int RelX = a_BlockX - a_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_BlockZ - a_Chunk->GetPosZ() * cChunkDef::Width;
+ if (!IsAllowedBlock(a_Chunk->GetBlock(RelX, a_BlockY, RelZ)))
+ {
+ return;
+ }
+
+ // Check for duplicates:
+ cSandSimulatorChunkData & ChunkData = a_Chunk->GetSandSimulatorData();
+ for (cSandSimulatorChunkData::iterator itr = ChunkData.begin(); itr != ChunkData.end(); ++itr)
+ {
+ if ((itr->x == RelX) && (itr->y == a_BlockY) && (itr->z == RelZ))
+ {
+ return;
+ }
+ }
+
+ m_TotalBlocks += 1;
+ ChunkData.push_back(cCoordWithInt(RelX, a_BlockY, RelZ));
+}
+
+
+
+
+
+bool cSandSimulator::CanStartFallingThrough(BLOCKTYPE a_BlockType)
+{
+ // Please keep the list alpha-sorted
+ switch (a_BlockType)
+ {
+ case E_BLOCK_AIR:
+ case E_BLOCK_FIRE:
+ case E_BLOCK_LAVA:
+ case E_BLOCK_SNOW:
+ case E_BLOCK_STATIONARY_LAVA:
+ case E_BLOCK_STATIONARY_WATER:
+ case E_BLOCK_WATER:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cSandSimulator::CanContinueFallThrough(BLOCKTYPE a_BlockType)
+{
+ // Please keep the list alpha-sorted
+ switch (a_BlockType)
+ {
+ case E_BLOCK_AIR:
+ case E_BLOCK_BROWN_MUSHROOM:
+ case E_BLOCK_COBWEB:
+ case E_BLOCK_CROPS:
+ case E_BLOCK_DEAD_BUSH:
+ case E_BLOCK_DETECTOR_RAIL:
+ case E_BLOCK_FIRE:
+ case E_BLOCK_FLOWER_POT:
+ case E_BLOCK_LAVA:
+ case E_BLOCK_LEVER:
+ case E_BLOCK_MINECART_TRACKS:
+ case E_BLOCK_MELON_STEM:
+ case E_BLOCK_POWERED_RAIL:
+ case E_BLOCK_PUMPKIN_STEM:
+ case E_BLOCK_REDSTONE_REPEATER_OFF:
+ case E_BLOCK_REDSTONE_REPEATER_ON:
+ case E_BLOCK_REDSTONE_TORCH_OFF:
+ case E_BLOCK_REDSTONE_TORCH_ON:
+ case E_BLOCK_REDSTONE_WIRE:
+ case E_BLOCK_RED_MUSHROOM:
+ case E_BLOCK_RED_ROSE:
+ case E_BLOCK_SIGN_POST:
+ case E_BLOCK_SNOW:
+ case E_BLOCK_STATIONARY_LAVA:
+ case E_BLOCK_STATIONARY_WATER:
+ case E_BLOCK_STONE_BUTTON:
+ case E_BLOCK_STONE_PRESSURE_PLATE:
+ case E_BLOCK_TALL_GRASS:
+ case E_BLOCK_TORCH:
+ case E_BLOCK_TRAPDOOR:
+ case E_BLOCK_TRIPWIRE:
+ case E_BLOCK_TRIPWIRE_HOOK:
+ case E_BLOCK_WALLSIGN:
+ case E_BLOCK_WATER:
+ case E_BLOCK_WOODEN_BUTTON:
+ case E_BLOCK_WOODEN_PRESSURE_PLATE:
+ case E_BLOCK_YELLOW_FLOWER:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cSandSimulator::IsReplacedOnRematerialization(BLOCKTYPE a_BlockType)
+{
+ // Please keep the list alpha-sorted
+ switch (a_BlockType)
+ {
+ case E_BLOCK_AIR:
+ case E_BLOCK_DEAD_BUSH:
+ case E_BLOCK_FIRE:
+ case E_BLOCK_LAVA:
+ case E_BLOCK_SNOW:
+ case E_BLOCK_STATIONARY_LAVA:
+ case E_BLOCK_STATIONARY_WATER:
+ case E_BLOCK_TALL_GRASS:
+ case E_BLOCK_WATER:
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+
+
+bool cSandSimulator::DoesBreakFallingThrough(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
+{
+ switch (a_BlockType)
+ {
+ case E_BLOCK_STONE_SLAB:
+ case E_BLOCK_WOODEN_SLAB:
+ {
+ return ((a_BlockMeta & 0x08) == 0); // Only a bottom-slab breaks the block
+ }
+ }
+ return false;
+}
+
+
+
+
+
+void cSandSimulator::FinishFalling(
+ cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ,
+ BLOCKTYPE a_FallingBlockType, NIBBLETYPE a_FallingBlockMeta
+)
+{
+ ASSERT(a_BlockY < cChunkDef::Height);
+
+ BLOCKTYPE CurrentBlockType = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
+ if ((a_FallingBlockType == E_BLOCK_ANVIL) || IsReplacedOnRematerialization(CurrentBlockType))
+ {
+ // Rematerialize the material here:
+ a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, a_FallingBlockType, a_FallingBlockMeta);
+ return;
+ }
+
+ // Create a pickup instead:
+ cItems Pickups;
+ Pickups.Add((ENUM_ITEM_ID)a_FallingBlockType, 1, a_FallingBlockMeta);
+ a_World->SpawnItemPickups(Pickups, (double)a_BlockX + 0.5, (double)a_BlockY + 0.5, (double)a_BlockZ + 0.5);
+}
+
+
+
+
+
+void cSandSimulator::DoInstantFall(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
+{
+ // Remove the original block:
+ BLOCKTYPE FallingBlockType;
+ NIBBLETYPE FallingBlockMeta;
+ a_Chunk->GetBlockTypeMeta(a_RelX, a_RelY, a_RelZ, FallingBlockType, FallingBlockMeta);
+ a_Chunk->SetBlock(a_RelX, a_RelY, a_RelZ, E_BLOCK_AIR, 0);
+
+ // Search for a place to put it:
+ for (int y = a_RelY - 1; y >= 0; y--)
+ {
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ a_Chunk->GetBlockTypeMeta(a_RelX, y, a_RelZ, BlockType, BlockMeta);
+ int BlockY;
+ if (DoesBreakFallingThrough(BlockType, BlockMeta))
+ {
+ BlockY = y;
+ }
+ else if (!CanContinueFallThrough(BlockType))
+ {
+ BlockY = y + 1;
+ }
+ else
+ {
+ // Can fall further down
+ continue;
+ }
+
+ // Finish the fall at the found bottom:
+ int BlockX = a_RelX + a_Chunk->GetPosX() * cChunkDef::Width;
+ int BlockZ = a_RelZ + a_Chunk->GetPosZ() * cChunkDef::Width;
+ FinishFalling(&m_World, BlockX, BlockY, BlockZ, FallingBlockType, FallingBlockMeta);
+ return;
+ }
+
+ // The block just "fell off the world" without leaving a trace
+}
+
+
+
+
diff --git a/src/Simulator/SandSimulator.h b/src/Simulator/SandSimulator.h
new file mode 100644
index 000000000..6e9ea15ac
--- /dev/null
+++ b/src/Simulator/SandSimulator.h
@@ -0,0 +1,63 @@
+
+#pragma once
+
+#include "Simulator.h"
+
+
+
+
+
+/// Despite the class name, this simulator takes care of all blocks that fall when suspended in the air.
+class cSandSimulator :
+ public cSimulator
+{
+public:
+ cSandSimulator(cWorld & a_World, cIniFile & a_IniFile);
+
+ // cSimulator overrides:
+ virtual void Simulate(float a_Dt) override {} // Unused in this simulator
+ virtual void SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) override;
+ virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) override;
+
+ /// Returns true if a falling-able block can start falling through the specified block type
+ static bool CanStartFallingThrough(BLOCKTYPE a_BlockType);
+
+ /// Returns true if an already-falling block can pass through the specified block type (e. g. torch)
+ static bool CanContinueFallThrough(BLOCKTYPE a_BlockType);
+
+ /// Returns true if the falling block rematerializing will replace the specified block type (e. g. tall grass)
+ static bool IsReplacedOnRematerialization(BLOCKTYPE a_BlockType);
+
+ /// Returns true if the specified block breaks falling blocks while they fall through it (e. g. halfslabs)
+ static bool DoesBreakFallingThrough(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
+
+ /** Called when a block finishes falling at the specified coords, either by insta-fall,
+ or through cFallingBlock entity.
+ It either rematerializes the block (a_FallingBlockType) at the specified coords, or creates a pickup,
+ based on the block currently present in the world at the dest specified coords
+ */
+ static void FinishFalling(
+ cWorld * a_World, int a_BlockX, int a_BlockY, int a_BlockZ,
+ BLOCKTYPE a_FallingBlockType, NIBBLETYPE a_FallingBlockMeta
+ );
+
+protected:
+ bool m_IsInstantFall; // If set to true, blocks don't fall using cFallingBlock entity, but instantly instead
+
+ int m_TotalBlocks; // Total number of blocks currently in the queue for simulating
+
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override;
+
+ /// Performs the instant fall of the block - removes it from top, Finishes it at the bottom
+ void DoInstantFall(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ);
+};
+
+
+
+
+/// Per-chunk data for the simulator, specified individual chunks to simulate; Data is not used
+typedef cCoordWithIntList cSandSimulatorChunkData;
+
+
+
+
diff --git a/src/Simulator/Simulator.cpp b/src/Simulator/Simulator.cpp
new file mode 100644
index 000000000..06fd0f858
--- /dev/null
+++ b/src/Simulator/Simulator.cpp
@@ -0,0 +1,51 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Simulator.h"
+#include "../World.h"
+#include "../Vector3i.h"
+#include "../BlockID.h"
+#include "../Defines.h"
+#include "../Chunk.h"
+
+
+
+
+
+cSimulator::cSimulator(cWorld & a_World)
+ : m_World(a_World)
+{
+}
+
+
+
+
+
+cSimulator::~cSimulator()
+{
+}
+
+
+
+
+
+void cSimulator::WakeUp(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ AddBlock(a_BlockX, a_BlockY, a_BlockZ, a_Chunk);
+ AddBlock(a_BlockX - 1, a_BlockY, a_BlockZ, a_Chunk->GetNeighborChunk(a_BlockX - 1, a_BlockZ));
+ AddBlock(a_BlockX + 1, a_BlockY, a_BlockZ, a_Chunk->GetNeighborChunk(a_BlockX + 1, a_BlockZ));
+ AddBlock(a_BlockX, a_BlockY, a_BlockZ - 1, a_Chunk->GetNeighborChunk(a_BlockX, a_BlockZ - 1));
+ AddBlock(a_BlockX, a_BlockY, a_BlockZ + 1, a_Chunk->GetNeighborChunk(a_BlockX, a_BlockZ + 1));
+ if (a_BlockY > 0)
+ {
+ AddBlock(a_BlockX, a_BlockY - 1, a_BlockZ, a_Chunk);
+ }
+ if (a_BlockY < cChunkDef::Height - 1)
+ {
+ AddBlock(a_BlockX, a_BlockY + 1, a_BlockZ, a_Chunk);
+ }
+}
+
+
+
+
diff --git a/src/Simulator/Simulator.h b/src/Simulator/Simulator.h
new file mode 100644
index 000000000..e1d88f1c5
--- /dev/null
+++ b/src/Simulator/Simulator.h
@@ -0,0 +1,46 @@
+
+#pragma once
+
+#include "../Vector3i.h"
+#include "../../iniFile/iniFile.h"
+
+
+
+
+
+class cWorld;
+class cChunk;
+
+
+
+
+
+class cSimulator
+{
+public:
+ cSimulator(cWorld & a_World);
+ virtual ~cSimulator();
+
+ /// Called in each tick, a_Dt is the time passed since the last tick, in msec
+ virtual void Simulate(float a_Dt) = 0;
+
+ /// Called in each tick for each chunk, a_Dt is the time passed since the last tick, in msec; direct access to chunk data available
+ virtual void SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) {};
+
+ /// Called when a block changes
+ virtual void WakeUp(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk);
+
+ virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) = 0;
+
+protected:
+ friend class cChunk; // Calls AddBlock() in its WakeUpSimulators() function, to speed things up
+
+ /// Called to simulate a new block
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) = 0;
+
+ cWorld & m_World;
+} ;
+
+
+
+
diff --git a/src/Simulator/SimulatorManager.cpp b/src/Simulator/SimulatorManager.cpp
new file mode 100644
index 000000000..2bc483cbd
--- /dev/null
+++ b/src/Simulator/SimulatorManager.cpp
@@ -0,0 +1,80 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "SimulatorManager.h"
+#include "../World.h"
+
+
+
+
+
+cSimulatorManager::cSimulatorManager(cWorld & a_World) :
+ m_World(a_World),
+ m_Ticks(0)
+{
+}
+
+
+
+
+
+cSimulatorManager::~cSimulatorManager()
+{
+}
+
+
+
+
+
+void cSimulatorManager::Simulate(float a_Dt)
+{
+ m_Ticks++;
+ for (cSimulators::iterator itr = m_Simulators.begin(); itr != m_Simulators.end(); ++itr )
+ {
+ if ((m_Ticks % itr->second) == 0)
+ {
+ itr->first->Simulate(a_Dt);
+ }
+ }
+}
+
+
+
+
+
+void cSimulatorManager::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
+{
+ // m_Ticks has already been increased in Simulate()
+ for (cSimulators::iterator itr = m_Simulators.begin(); itr != m_Simulators.end(); ++itr )
+ {
+ if ((m_Ticks % itr->second) == 0)
+ {
+ itr->first->SimulateChunk(a_Dt, a_ChunkX, a_ChunkZ, a_Chunk);
+ }
+ }
+}
+
+
+
+
+
+void cSimulatorManager::WakeUp(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ for (cSimulators::iterator itr = m_Simulators.begin(); itr != m_Simulators.end(); ++itr )
+ {
+ itr->first->WakeUp(a_BlockX, a_BlockY, a_BlockZ, a_Chunk);
+ }
+}
+
+
+
+
+
+void cSimulatorManager::RegisterSimulator(cSimulator * a_Simulator, int a_Rate)
+{
+ m_Simulators.push_back(std::make_pair(a_Simulator, a_Rate));
+}
+
+
+
+
diff --git a/src/Simulator/SimulatorManager.h b/src/Simulator/SimulatorManager.h
new file mode 100644
index 000000000..31a709316
--- /dev/null
+++ b/src/Simulator/SimulatorManager.h
@@ -0,0 +1,52 @@
+
+// cSimulatorManager.h
+
+
+
+
+#pragma once
+
+
+
+
+#include "Simulator.h"
+
+
+
+
+
+// fwd: Chunk.h
+class cChunk;
+
+// fwd: World.h
+class cWorld;
+
+
+
+
+
+class cSimulatorManager
+{
+public:
+ cSimulatorManager(cWorld & a_World);
+ ~cSimulatorManager();
+
+ void Simulate(float a_Dt);
+
+ void SimulateChunk(float a_DT, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk);
+
+ void WakeUp(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk);
+
+ void RegisterSimulator(cSimulator * a_Simulator, int a_Rate); // Takes ownership of the simulator object!
+
+protected:
+ typedef std::vector <std::pair<cSimulator *, int> > cSimulators;
+
+ cWorld & m_World;
+ cSimulators m_Simulators;
+ long long m_Ticks;
+};
+
+
+
+
diff --git a/src/Simulator/VaporizeFluidSimulator.cpp b/src/Simulator/VaporizeFluidSimulator.cpp
new file mode 100644
index 000000000..4206c64d1
--- /dev/null
+++ b/src/Simulator/VaporizeFluidSimulator.cpp
@@ -0,0 +1,53 @@
+
+// VaporizeFluidSimulator.cpp
+
+// Implements the cVaporizeFluidSimulator class representing a fluid simulator that replaces all fluid blocks with air
+
+#include "Globals.h"
+#include "VaporizeFluidSimulator.h"
+#include "../Chunk.h"
+
+
+
+
+
+cVaporizeFluidSimulator::cVaporizeFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid) :
+ super(a_World, a_Fluid, a_StationaryFluid)
+{
+}
+
+
+
+
+
+void cVaporizeFluidSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
+{
+ if (a_Chunk == NULL)
+ {
+ return;
+ }
+ int RelX = a_BlockX - a_Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = a_BlockZ - a_Chunk->GetPosZ() * cChunkDef::Width;
+ BLOCKTYPE BlockType = a_Chunk->GetBlock(RelX, a_BlockY, RelZ);
+ if (
+ (BlockType == m_FluidBlock) ||
+ (BlockType == m_StationaryFluidBlock)
+ )
+ {
+ a_Chunk->SetBlock(RelX, a_BlockY, RelZ, E_BLOCK_AIR, 0);
+ a_Chunk->BroadcastSoundEffect("random.fizz", a_BlockX * 8, a_BlockY * 8, a_BlockZ * 8, 1.0f, 0.6f);
+ }
+}
+
+
+
+
+
+void cVaporizeFluidSimulator::Simulate(float a_Dt)
+{
+ // Nothing needed
+}
+
+
+
+
diff --git a/src/Simulator/VaporizeFluidSimulator.h b/src/Simulator/VaporizeFluidSimulator.h
new file mode 100644
index 000000000..c8eb7802b
--- /dev/null
+++ b/src/Simulator/VaporizeFluidSimulator.h
@@ -0,0 +1,34 @@
+
+// VaporizeFluidSimulator.h
+
+// Declares the cVaporizeFluidSimulator class representing a fluid simulator that replaces all fluid blocks with air
+// Useful for water simulation in the Nether
+
+
+
+
+
+#pragma once
+
+#include "FluidSimulator.h"
+
+
+
+
+
+class cVaporizeFluidSimulator :
+ public cFluidSimulator
+{
+ typedef cFluidSimulator super;
+
+public:
+ cVaporizeFluidSimulator(cWorld & a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid);
+
+ // cSimulator overrides:
+ virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override;
+ virtual void Simulate(float a_Dt) override;
+} ;
+
+
+
+