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
This commit is contained in:
parent
d636875fc0
commit
011e11af2c
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Vector3i.h"
|
||||
#include "../../iniFile/iniFile.h"
|
||||
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user