From 011e11af2caa9da43e92cb7c5806502645270f9d Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Fri, 1 Mar 2013 19:35:29 +0000 Subject: New fire simulator, fully rewritten to the new scheme of things, directly accessing chunk data. http://forum.mc-server.org/showthread.php?tid=617&pid=6626#pid6626 git-svn-id: http://mc-server.googlecode.com/svn/trunk@1233 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/Chunk.cpp | 29 ++-- source/Chunk.h | 39 +++-- source/ChunkDef.h | 28 ++++ source/Simulator/FireSimulator.cpp | 327 +++++++++++++++++++++++++++++-------- source/Simulator/FireSimulator.h | 68 ++++++-- source/Simulator/Simulator.cpp | 8 +- source/Simulator/Simulator.h | 1 + source/World.cpp | 4 +- source/World.h | 3 + 9 files changed, 386 insertions(+), 121 deletions(-) diff --git a/source/Chunk.cpp b/source/Chunk.cpp index 763555083..7c8d8744e 100644 --- a/source/Chunk.cpp +++ b/source/Chunk.cpp @@ -434,6 +434,9 @@ void cChunk::Tick(float a_Dt, MTRand & a_TickRandom) CheckBlocks(); + // Tick simulators: + m_World->GetSimulatorManager()->SimulateChunk(a_Dt, m_PosX, m_PosZ, this); + TickBlocks(a_TickRandom); // Tick block entities (furnaces) @@ -1208,7 +1211,7 @@ void cChunk::QueueTickBlockNeighbors(int a_RelX, int a_RelY, int a_RelZ) } ; for (int i = 0; i < ARRAYCOUNT(Coords); i++) { - cChunk * ch = GetRelNeighborChunk(a_RelX + Coords[i].x, a_RelY, a_RelZ + Coords[i].z); + cChunk * ch = GetRelNeighborChunk(a_RelX + Coords[i].x, a_RelZ + Coords[i].z); if (ch != NULL) { ch->QueueTickBlock(a_RelX + Coords[i].x, a_RelY + Coords[i].y, a_RelZ + Coords[i].z); @@ -1310,7 +1313,7 @@ void cChunk::SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, cClientHandle * a_C -void cChunk::AddBlockEntity( cBlockEntity* a_BlockEntity ) +void cChunk::AddBlockEntity(cBlockEntity * a_BlockEntity) { cCSLock Lock(m_CSBlockLists); m_BlockEntities.push_back( a_BlockEntity ); @@ -1320,14 +1323,14 @@ void cChunk::AddBlockEntity( cBlockEntity* a_BlockEntity ) -cBlockEntity * cChunk::GetBlockEntity(int a_X, int a_Y, int a_Z) +cBlockEntity * cChunk::GetBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ) { for (cBlockEntityList::iterator itr = m_BlockEntities.begin(); itr != m_BlockEntities.end(); ++itr) { if ( - ((*itr)->GetPosX() == a_X) && - ((*itr)->GetPosY() == a_Y) && - ((*itr)->GetPosZ() == a_Z) + ((*itr)->GetPosX() == a_BlockX) && + ((*itr)->GetPosY() == a_BlockY) && + ((*itr)->GetPosZ() == a_BlockZ) ) { return *itr; @@ -1803,26 +1806,26 @@ void cChunk::GetBlockInfo(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_Bloc -cChunk * cChunk::GetNeighborChunk(int a_BlockX, int a_BlockY, int a_BlockZ) +cChunk * cChunk::GetNeighborChunk(int a_BlockX, int a_BlockZ) { // Convert coords to relative, then call the relative version: a_BlockX -= m_PosX * cChunkDef::Width; a_BlockZ -= m_PosZ * cChunkDef::Width; - return GetRelNeighborChunk(a_BlockX, a_BlockY, a_BlockZ); + return GetRelNeighborChunk(a_BlockX, a_BlockZ); } -cChunk * cChunk::GetRelNeighborChunk(int a_RelX, int a_RelY, int a_RelZ) +cChunk * cChunk::GetRelNeighborChunk(int a_RelX, int a_RelZ) { bool ReturnThis = true; if (a_RelX < 0) { if (m_NeighborXM != NULL) { - cChunk * Candidate = m_NeighborXM->GetRelNeighborChunk(a_RelX + cChunkDef::Width, a_RelY, a_RelZ); + cChunk * Candidate = m_NeighborXM->GetRelNeighborChunk(a_RelX + cChunkDef::Width, a_RelZ); if (Candidate != NULL) { return Candidate; @@ -1835,7 +1838,7 @@ cChunk * cChunk::GetRelNeighborChunk(int a_RelX, int a_RelY, int a_RelZ) { if (m_NeighborXP != NULL) { - cChunk * Candidate = m_NeighborXP->GetRelNeighborChunk(a_RelX - cChunkDef::Width, a_RelY, a_RelZ); + cChunk * Candidate = m_NeighborXP->GetRelNeighborChunk(a_RelX - cChunkDef::Width, a_RelZ); if (Candidate != NULL) { return Candidate; @@ -1849,7 +1852,7 @@ cChunk * cChunk::GetRelNeighborChunk(int a_RelX, int a_RelY, int a_RelZ) { if (m_NeighborZM != NULL) { - return m_NeighborZM->GetRelNeighborChunk(a_RelX, a_RelY, a_RelZ + cChunkDef::Width); + return m_NeighborZM->GetRelNeighborChunk(a_RelX, a_RelZ + cChunkDef::Width); // For requests crossing both X and Z, the X-first way has been already tried } return NULL; @@ -1858,7 +1861,7 @@ cChunk * cChunk::GetRelNeighborChunk(int a_RelX, int a_RelY, int a_RelZ) { if (m_NeighborZP != NULL) { - return m_NeighborZP->GetRelNeighborChunk(a_RelX, a_RelY, a_RelZ - cChunkDef::Width); + return m_NeighborZP->GetRelNeighborChunk(a_RelX, a_RelZ - cChunkDef::Width); // For requests crossing both X and Z, the X-first way has been already tried } return NULL; diff --git a/source/Chunk.h b/source/Chunk.h index 324929cac..826e8bb6d 100644 --- a/source/Chunk.h +++ b/source/Chunk.h @@ -4,6 +4,8 @@ #include "Entity.h" #include "ChunkDef.h" +#include "Simulator/FireSimulator.h" + @@ -147,13 +149,13 @@ public: /** Returns the chunk into which the specified block belongs, by walking the neighbors. Will return self if appropriate. Returns NULL if not reachable through neighbors. */ - cChunk * GetNeighborChunk(int a_BlockX, int a_BlockY, int a_BlockZ); + cChunk * GetNeighborChunk(int a_BlockX, int a_BlockZ); /** Returns the chunk into which the relatively-specified block belongs, by walking the neighbors. Will return self if appropriate. Returns NULL if not reachable through neighbors. */ - cChunk * GetRelNeighborChunk(int a_RelX, int a_RelY, int a_RelZ); + cChunk * GetRelNeighborChunk(int a_RelX, int a_RelZ); EMCSBiome GetBiomeAt(int a_RelX, int a_RelZ) const {return cChunkDef::GetBiome(m_BiomeMap, a_RelX, a_RelZ); } @@ -250,9 +252,22 @@ public: inline NIBBLETYPE GetMeta(int a_RelX, int a_RelY, int a_RelZ) {return cChunkDef::GetNibble(m_BlockMeta, a_RelX, a_RelY, a_RelZ); } inline NIBBLETYPE GetMeta(int a_BlockIdx) {return cChunkDef::GetNibble(m_BlockMeta, a_BlockIdx); } inline void SetMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Meta) { cChunkDef::SetNibble(m_BlockMeta, a_RelX, a_RelY, a_RelZ, a_Meta); } + inline void SetMeta(int a_BlockIdx, NIBBLETYPE a_Meta) { cChunkDef::SetNibble(m_BlockMeta, a_BlockIdx, a_Meta); } inline NIBBLETYPE GetBlockLight(int a_RelX, int a_RelY, int a_RelZ) {return cChunkDef::GetNibble(m_BlockLight, a_RelX, a_RelY, a_RelZ); } inline NIBBLETYPE GetSkyLight (int a_RelX, int a_RelY, int a_RelZ) {return cChunkDef::GetNibble(m_BlockSkyLight, a_RelX, a_RelY, a_RelZ); } + + /// Same as GetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() + bool UnboundedRelGetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta); + + /// Same as SetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() + bool UnboundedRelSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + + /// Same as FastSetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() + bool UnboundedRelFastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + + // Simulator data: + cFireSimulatorChunkData & GetFireSimulatorData(void) { return m_FireSimulatorData; } private: @@ -296,11 +311,14 @@ private: cChunk * m_NeighborXP; // Neighbor at [X + 1, Z] cChunk * m_NeighborZM; // Neighbor at [X, Z - 1] cChunk * m_NeighborZP; // Neighbor at [X, Z + 1] + + cFireSimulatorChunkData m_FireSimulatorData; + - void RemoveBlockEntity( cBlockEntity* a_BlockEntity ); - void AddBlockEntity( cBlockEntity* a_BlockEntity ); - cBlockEntity * GetBlockEntity( int a_X, int a_Y, int a_Z ); - cBlockEntity * GetBlockEntity( const Vector3i & a_BlockPos ) { return GetBlockEntity( a_BlockPos.x, a_BlockPos.y, a_BlockPos.z ); } + void RemoveBlockEntity(cBlockEntity * a_BlockEntity); + void AddBlockEntity (cBlockEntity * a_BlockEntity); + cBlockEntity * GetBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ); + cBlockEntity * GetBlockEntity(const Vector3i & a_BlockPos) { return GetBlockEntity(a_BlockPos.x, a_BlockPos.y, a_BlockPos.z); } void SpreadLightOfBlock(NIBBLETYPE * a_LightBuffer, int a_X, int a_Y, int a_Z, char a_Falloff); @@ -331,15 +349,6 @@ private: /// Checks if a leaves block at the specified coords has a log up to 4 blocks away connected by other leaves blocks (false if no log) bool HasNearLog(cBlockArea & a_Area, int a_BlockX, int a_BlockY, int a_BlockZ); - - /// Same as GetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() - bool UnboundedRelGetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta); - - /// Same as SetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() - bool UnboundedRelSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); - - /// Same as FastSetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in such a case); returns true on success; only usable in Tick() - bool UnboundedRelFastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); }; typedef cChunk * cChunkPtr; diff --git a/source/ChunkDef.h b/source/ChunkDef.h index 9e5f190b8..b0d053b5e 100644 --- a/source/ChunkDef.h +++ b/source/ChunkDef.h @@ -516,3 +516,31 @@ public: + +/// Generic template that can store any kind of data together with a triplet of 3 coords: +template class cCoordWithData +{ +public: + int x; + int y; + int z; + X Data; + + cCoordWithData(int a_X, int a_Y, int a_Z) : + x(a_X), y(a_Y), z(a_Z) + { + } + + cCoordWithData(int a_X, int a_Y, int a_Z, const X & a_Data) : + x(a_X), y(a_Y), z(a_Z), Data(a_Data) + { + } +} ; + +// Illegal in C++03: typedef std::list< cCoordWithData > cCoordWithDataList; +typedef cCoordWithData cCoordWithInt; +typedef std::list cCoordWithIntList; + + + + diff --git a/source/Simulator/FireSimulator.cpp b/source/Simulator/FireSimulator.cpp index 277ac0700..c9ade90c9 100644 --- a/source/Simulator/FireSimulator.cpp +++ b/source/Simulator/FireSimulator.cpp @@ -5,17 +5,73 @@ #include "../World.h" #include "../BlockID.h" #include "../Defines.h" +#include "../Chunk.h" -cFireSimulator::cFireSimulator(cWorld & a_World) - : cSimulator(a_World) - , m_Blocks(new BlockList) - , m_Buffer(new BlockList) - , m_BurningBlocks(new BlockList) +// 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); } @@ -24,43 +80,64 @@ cFireSimulator::cFireSimulator(cWorld & a_World) cFireSimulator::~cFireSimulator() { - delete m_Buffer; - delete m_Blocks; - delete m_BurningBlocks; } -void cFireSimulator::Simulate(float a_Dt) +void cFireSimulator::SimulateChunk(float a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk) { - m_Buffer->clear(); - std::swap(m_Blocks, m_Buffer); + cCoordWithIntList & Data = a_Chunk->GetFireSimulatorData(); - for (BlockList::iterator itr = m_Buffer->begin(); itr != m_Buffer->end(); ++itr) + int NumMSecs = (int)a_Dt; + for (cCoordWithIntList::iterator itr = Data.begin(); itr != Data.end();) { - Vector3i Pos = *itr; + int idx = cChunkDef::MakeIndexNoCheck(itr->x, itr->y, itr->z); + BLOCKTYPE BlockType = a_Chunk->GetBlock(idx); - BLOCKTYPE BlockID = m_World.GetBlock(Pos.x, Pos.y, Pos.z); - - if (!IsAllowedBlock(BlockID)) // Check wheather the block is still burning + 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; } - if (BurnBlockAround(Pos.x, Pos.y, Pos.z)) //Burn single block and if there was one -> next time again + // Try to spread the fire: + TrySpreadFire(a_Chunk, itr->x, itr->y, itr->z); + + itr->Data -= NumMSecs; + if (itr->Data >= 0) { - m_Blocks->push_back(Pos); + // Not yet, wait for it longer + ++itr; + continue; } - else + + // 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) { - if (!IsForeverBurnable(m_World.GetBlock(Pos.x, Pos.y - 1, Pos.z)) && !FiresForever(BlockID)) - { - m_World.SetBlock(Pos.x, Pos.y, Pos.z, E_BLOCK_AIR, 0); - } + // 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; } - } // for itr - m_Buffer[] + 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[] } @@ -69,106 +146,214 @@ void cFireSimulator::Simulate(float a_Dt) bool cFireSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType) { - return (a_BlockType == E_BLOCK_FIRE) || IsBlockLava(a_BlockType); + return (a_BlockType == E_BLOCK_FIRE); } -void cFireSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) +bool cFireSimulator::IsFuel(BLOCKTYPE a_BlockType) { - // TODO: This can be optimized - BLOCKTYPE BlockType = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ); - if (!IsAllowedBlock(BlockType)) - { - return; - } - - // Check for duplicates: - for (BlockList::iterator itr = m_Blocks->begin(); itr != m_Blocks->end(); ++itr ) + switch (a_BlockType) { - Vector3i Pos = *itr; - if ((Pos.x == a_BlockX) && (Pos.y == a_BlockY) && (Pos.z == a_BlockZ)) + 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; + return true; } } + return false; +} + + - m_Blocks->push_back(Vector3i(a_BlockX, a_BlockY, a_BlockZ)); + + +bool cFireSimulator::IsForever(BLOCKTYPE a_BlockType) +{ + return (a_BlockType == E_BLOCK_NETHERRACK); } -bool cFireSimulator::IsForeverBurnable( BLOCKTYPE a_BlockType ) +void cFireSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) { - return a_BlockType == E_BLOCK_NETHERRACK; + 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)); } -bool cFireSimulator::IsBurnable( BLOCKTYPE a_BlockType ) +int cFireSimulator::GetBurnStepTime(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) { - return a_BlockType == E_BLOCK_PLANKS - || a_BlockType == E_BLOCK_LEAVES - || a_BlockType == E_BLOCK_LOG - || a_BlockType == E_BLOCK_WOOL - || a_BlockType == E_BLOCK_BOOKCASE - || a_BlockType == E_BLOCK_FENCE - || a_BlockType == E_BLOCK_TNT - || a_BlockType == E_BLOCK_VINES; + 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; + } + } + if ((a_RelY < cChunkDef::Height - 1) && IsFuel(a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ))) + { + return m_BurnStepTimeFuel; + } + + 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[] + return m_BurnStepTimeNonfuel; } -bool cFireSimulator::FiresForever( BLOCKTYPE a_BlockType ) +void cFireSimulator::TrySpreadFire(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) { - return a_BlockType != E_BLOCK_FIRE; + /* + 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 } -bool cFireSimulator::BurnBlockAround(int a_X, int a_Y, int a_Z) +void cFireSimulator::RemoveFuelNeighbors(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ) { - return BurnBlock(a_X + 1, a_Y, a_Z) - || BurnBlock(a_X - 1, a_Y, a_Z) - || BurnBlock(a_X, a_Y + 1, a_Z) - || BurnBlock(a_X, a_Y - 1, a_Z) - || BurnBlock(a_X, a_Y, a_Z + 1) - || BurnBlock(a_X, a_Y, a_Z - 1); + 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::BurnBlock(int a_X, int a_Y, int a_Z) +bool cFireSimulator::CanStartFireInBlock(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ) { - BLOCKTYPE BlockID = m_World.GetBlock(a_X, a_Y, a_Z); - if (IsBurnable(BlockID)) + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + if (!a_NearChunk->UnboundedRelGetBlock(a_RelX, a_RelY, a_RelZ, BlockType, BlockMeta)) { - m_World.SetBlock(a_X, a_Y, a_Z, E_BLOCK_FIRE, 0); - return true; + // 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; } - if (IsForeverBurnable(BlockID)) + + for (int i = 0; i < ARRAYCOUNT(gNeighborCoords); i++) { - BLOCKTYPE BlockAbove = m_World.GetBlock(a_X, a_Y + 1, a_Z); - if (BlockAbove == E_BLOCK_AIR) + 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)) { - m_World.SetBlock(a_X, a_Y + 1, a_Z, E_BLOCK_FIRE, 0); //Doesn´t notify the simulator so it won´t go off return true; } - return false; - } - + } // for i - Coords[] return false; } diff --git a/source/Simulator/FireSimulator.h b/source/Simulator/FireSimulator.h index 2a510d276..877075e73 100644 --- a/source/Simulator/FireSimulator.h +++ b/source/Simulator/FireSimulator.h @@ -8,31 +8,67 @@ -class cFireSimulator : public cSimulator +/** 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); + cFireSimulator(cWorld & a_World, cIniFile & a_IniFile); ~cFireSimulator(); - virtual void Simulate( float a_Dt ) override; + 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; + virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) override; - virtual bool IsBurnable( BLOCKTYPE a_BlockType ); - virtual bool IsForeverBurnable( BLOCKTYPE a_BlockType ); - virtual bool FiresForever( BLOCKTYPE a_BlockType ); + 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 + unsigned m_Flammability; + + /// Chance [0..100000] of a fuel burning out being replaced by a new fire block instead of an air block + unsigned m_ReplaceFuelChance; + + virtual void AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk) override; - virtual bool BurnBlockAround(int a_X, int a_Y, int a_Z); - virtual bool BurnBlock(int a_X, int a_Y, int a_Z); - - typedef std::list BlockList; - BlockList *m_Blocks; - BlockList *m_Buffer; - - BlockList *m_BurningBlocks; -}; + + /// 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/source/Simulator/Simulator.cpp b/source/Simulator/Simulator.cpp index 07ed31b52..78e12324a 100644 --- a/source/Simulator/Simulator.cpp +++ b/source/Simulator/Simulator.cpp @@ -34,10 +34,10 @@ void cSimulator::WakeUp(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chu AddBlock(a_BlockX, a_BlockY, a_BlockZ, a_Chunk); AddBlock(a_BlockX, a_BlockY - 1, a_BlockZ, a_Chunk); AddBlock(a_BlockX, a_BlockY + 1, a_BlockZ, a_Chunk); - AddBlock(a_BlockX - 1, a_BlockY, a_BlockZ, a_Chunk->GetNeighborChunk(a_BlockX - 1, a_BlockY, a_BlockZ)); - AddBlock(a_BlockX + 1, a_BlockY, a_BlockZ, a_Chunk->GetNeighborChunk(a_BlockX + 1, a_BlockY, a_BlockZ)); - AddBlock(a_BlockX, a_BlockY, a_BlockZ - 1, a_Chunk->GetNeighborChunk(a_BlockX, a_BlockY, a_BlockZ - 1)); - AddBlock(a_BlockX, a_BlockY, a_BlockZ + 1, a_Chunk->GetNeighborChunk(a_BlockX, a_BlockY, a_BlockZ + 1)); + 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)); } diff --git a/source/Simulator/Simulator.h b/source/Simulator/Simulator.h index 1f31bc2e8..63cc0b17f 100644 --- a/source/Simulator/Simulator.h +++ b/source/Simulator/Simulator.h @@ -2,6 +2,7 @@ #pragma once #include "../Vector3i.h" +#include "../../iniFile/iniFile.h" diff --git a/source/World.cpp b/source/World.cpp index 3097e5029..84874258f 100644 --- a/source/World.cpp +++ b/source/World.cpp @@ -254,12 +254,12 @@ cWorld::cWorld(const AString & a_WorldName) : m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER); m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA); m_SandSimulator = new cSandSimulator(*this); - m_FireSimulator = new cFireSimulator(*this); + m_FireSimulator = new cFireSimulator(*this, IniFile); m_RedstoneSimulator = new cRedstoneSimulator(*this); // Water and Lava simulators get registered in InitializeFluidSimulator() m_SimulatorManager->RegisterSimulator(m_SandSimulator, 1); - m_SimulatorManager->RegisterSimulator(m_FireSimulator, 10); + m_SimulatorManager->RegisterSimulator(m_FireSimulator, 1); m_SimulatorManager->RegisterSimulator(m_RedstoneSimulator, 1); // Save any changes that the defaults may have done to the ini file: diff --git a/source/World.h b/source/World.h index 6fc3fa088..2965ae1c3 100644 --- a/source/World.h +++ b/source/World.h @@ -451,6 +451,9 @@ public: /// Spawns a mob of the specified entity type. Returns the mob's EntityID if recognized and spawned, <0 otherwise int SpawnMob(double a_PosX, double a_PosY, double a_PosZ, int a_EntityType); // tolua_export + /// Returns a random number from the m_TickRand in range [0 .. a_Range]. To be used only in the tick thread! + unsigned GetTickRandomNumber(unsigned a_Range) { return m_TickRand.randInt(a_Range); } + private: friend class cRoot; -- cgit v1.2.3