diff options
Diffstat (limited to 'source/Simulator/FireSimulator.cpp')
-rw-r--r-- | source/Simulator/FireSimulator.cpp | 327 |
1 files changed, 256 insertions, 71 deletions
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; } |