diff options
Diffstat (limited to 'src/Simulator')
-rw-r--r-- | src/Simulator/DelayedFluidSimulator.cpp | 158 | ||||
-rw-r--r-- | src/Simulator/DelayedFluidSimulator.h | 82 | ||||
-rw-r--r-- | src/Simulator/FireSimulator.cpp | 374 | ||||
-rw-r--r-- | src/Simulator/FireSimulator.h | 75 | ||||
-rw-r--r-- | src/Simulator/FloodyFluidSimulator.cpp | 330 | ||||
-rw-r--r-- | src/Simulator/FloodyFluidSimulator.h | 53 | ||||
-rw-r--r-- | src/Simulator/FluidSimulator.cpp | 212 | ||||
-rw-r--r-- | src/Simulator/FluidSimulator.h | 75 | ||||
-rw-r--r-- | src/Simulator/NoopFluidSimulator.h | 36 | ||||
-rw-r--r-- | src/Simulator/RedstoneSimulator.cpp | 1073 | ||||
-rw-r--r-- | src/Simulator/RedstoneSimulator.h | 199 | ||||
-rw-r--r-- | src/Simulator/SandSimulator.cpp | 309 | ||||
-rw-r--r-- | src/Simulator/SandSimulator.h | 63 | ||||
-rw-r--r-- | src/Simulator/Simulator.cpp | 51 | ||||
-rw-r--r-- | src/Simulator/Simulator.h | 46 | ||||
-rw-r--r-- | src/Simulator/SimulatorManager.cpp | 80 | ||||
-rw-r--r-- | src/Simulator/SimulatorManager.h | 52 | ||||
-rw-r--r-- | src/Simulator/VaporizeFluidSimulator.cpp | 53 | ||||
-rw-r--r-- | src/Simulator/VaporizeFluidSimulator.h | 34 |
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..5cd0e8657 --- /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; +} ; + + + + |