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
master
madmaxoft@gmail.com 2013-03-01 19:35:29 +00:00
parent d636875fc0
commit 011e11af2c
9 changed files with 408 additions and 143 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,7 +146,39 @@ 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);
}
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);
}
@ -78,97 +187,173 @@ bool cFireSimulator::IsAllowedBlock(BLOCKTYPE a_BlockType)
void cFireSimulator::AddBlock(int a_BlockX, int a_BlockY, int a_BlockZ, cChunk * a_Chunk)
{
// TODO: This can be optimized
BLOCKTYPE BlockType = m_World.GetBlock(a_BlockX, a_BlockY, a_BlockZ);
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:
for (BlockList::iterator itr = m_Blocks->begin(); itr != m_Blocks->end(); ++itr )
cFireSimulatorChunkData & ChunkData = a_Chunk->GetFireSimulatorData();
for (cCoordWithIntList::iterator itr = ChunkData.begin(), end = ChunkData.end(); itr != end; ++itr)
{
Vector3i Pos = *itr;
if ((Pos.x == a_BlockX) && (Pos.y == a_BlockY) && (Pos.z == a_BlockZ))
if ((itr->x == RelX) && (itr->y == a_BlockY) && (itr->z == RelZ))
{
// Already present, skip adding
return;
}
}
} // for itr - ChunkData[]
m_Blocks->push_back(Vector3i(a_BlockX, a_BlockY, a_BlockZ));
FLOG("FS: Adding block {%d, %d, %d}", a_BlockX, a_BlockY, a_BlockZ);
ChunkData.push_back(cCoordWithInt(RelX, a_BlockY, RelZ, 100));
}
bool cFireSimulator::IsForeverBurnable( BLOCKTYPE a_BlockType )
int cFireSimulator::GetBurnStepTime(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
{
return a_BlockType == E_BLOCK_NETHERRACK;
}
bool cFireSimulator::IsBurnable( BLOCKTYPE a_BlockType )
{
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;
}
bool cFireSimulator::FiresForever( BLOCKTYPE a_BlockType )
{
return a_BlockType != E_BLOCK_FIRE;
}
bool cFireSimulator::BurnBlockAround(int a_X, int a_Y, int a_Z)
{
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);
}
bool cFireSimulator::BurnBlock(int a_X, int a_Y, int a_Z)
{
BLOCKTYPE BlockID = m_World.GetBlock(a_X, a_Y, a_Z);
if (IsBurnable(BlockID))
if (a_RelY > 0)
{
m_World.SetBlock(a_X, a_Y, a_Z, E_BLOCK_FIRE, 0);
return true;
}
if (IsForeverBurnable(BlockID))
{
BLOCKTYPE BlockAbove = m_World.GetBlock(a_X, a_Y + 1, a_Z);
if (BlockAbove == E_BLOCK_AIR)
BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ);
if (IsForever(BlockBelow))
{
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;
// 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;
}
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;
}

View File

@ -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;

View File

@ -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));
}

View File

@ -2,6 +2,7 @@
#pragma once
#include "../Vector3i.h"
#include "../../iniFile/iniFile.h"

View File

@ -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:

View File

@ -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;