summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-03-01 20:35:29 +0100
committermadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-03-01 20:35:29 +0100
commit011e11af2caa9da43e92cb7c5806502645270f9d (patch)
tree3e53007e45c049e2eb15987a60c4b0ff3c72b629
parentPrevious commit was missing the Entity.h file (diff)
downloadcuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar.gz
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar.bz2
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar.lz
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar.xz
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.tar.zst
cuberite-011e11af2caa9da43e92cb7c5806502645270f9d.zip
-rw-r--r--source/Chunk.cpp29
-rw-r--r--source/Chunk.h39
-rw-r--r--source/ChunkDef.h28
-rw-r--r--source/Simulator/FireSimulator.cpp327
-rw-r--r--source/Simulator/FireSimulator.h68
-rw-r--r--source/Simulator/Simulator.cpp8
-rw-r--r--source/Simulator/Simulator.h1
-rw-r--r--source/World.cpp4
-rw-r--r--source/World.h3
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 <typename X> class cCoordWithData
+{
+public:
+ int x;
+ int y;
+ int z;
+ X Data;
+
+ cCoordWithData<typename X>(int a_X, int a_Y, int a_Z) :
+ x(a_X), y(a_Y), z(a_Z)
+ {
+ }
+
+ cCoordWithData<typename X>(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<X> > cCoordWithDataList<X>;
+typedef cCoordWithData<int> cCoordWithInt;
+typedef std::list<cCoordWithInt> 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 <Vector3i> 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;