Cuberite
A lightweight, fast and extensible game server for Minecraft
FireSimulator.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
4 #include "FireSimulator.h"
5 #include "../BlockInfo.h"
6 #include "../World.h"
7 #include "../Defines.h"
8 #include "../Chunk.h"
9 #include "../Root.h"
10 #include "../Bindings/PluginManager.h"
11 
12 
13 
14 
15 
16 // Easy switch for turning on debugging logging:
17 #if 0
18  #define FIRE_FLOG FLOGD
19 #else
20  #define FIRE_FLOG(...)
21 #endif
22 
23 
24 
25 
26 
27 #define MAX_CHANCE_REPLACE_FUEL 100000
28 #define MAX_CHANCE_FLAMMABILITY 100000
29 // The base chance that in a tick, rain will extinguish a fire block.
30 #define CHANCE_BASE_RAIN_EXTINGUISH 0.2
31 // The additional chance, multiplied by the meta of the fire block, that rain
32 // will extinguish a fire block in a tick.
33 #define CHANCE_AGE_M_RAIN_EXTINGUISH 0.03
34 
35 
36 
37 
38 
39 static constexpr Vector3i gCrossCoords[] =
40 {
41  { 1, 0, 0},
42  {-1, 0, 0},
43  { 0, 0, 1},
44  { 0, 0, -1},
45 } ;
46 
47 
48 
49 
50 
51 static constexpr Vector3i gNeighborCoords[] =
52 {
53  { 1, 0, 0},
54  {-1, 0, 0},
55  { 0, 1, 0},
56  { 0, -1, 0},
57  { 0, 0, 1},
58  { 0, 0, -1},
59 };
60 
61 
62 
63 
64 
66 // cFireSimulator:
67 
69  cSimulator(a_World)
70 {
71  // Read params from the ini file:
72  m_BurnStepTimeFuel = static_cast<unsigned>(a_IniFile.GetValueSetI("FireSimulator", "BurnStepTimeFuel", 500));
73  m_BurnStepTimeNonfuel = static_cast<unsigned>(a_IniFile.GetValueSetI("FireSimulator", "BurnStepTimeNonfuel", 100));
74  m_Flammability = a_IniFile.GetValueSetI("FireSimulator", "Flammability", 50);
75  m_ReplaceFuelChance = a_IniFile.GetValueSetI("FireSimulator", "ReplaceFuelChance", 50000);
76 }
77 
78 
79 
80 
81 
82 void cFireSimulator::SimulateChunk(std::chrono::milliseconds a_Dt, int a_ChunkX, int a_ChunkZ, cChunk * a_Chunk)
83 {
84  cCoordWithIntList & Data = a_Chunk->GetFireSimulatorData();
85 
86  int NumMSecs = static_cast<int>(a_Dt.count());
87  for (cCoordWithIntList::iterator itr = Data.begin(); itr != Data.end();)
88  {
89  Vector3i relPos(itr->x, itr->y, itr->z);
90  auto blockType = a_Chunk->GetBlock(relPos);
91 
92  if (!IsAllowedBlock(blockType))
93  {
94  // The block is no longer eligible (not a fire block anymore; a player probably placed a block over the fire)
95  FIRE_FLOG("FS: Removing block {0}", absPos);
96  itr = Data.erase(itr);
97  continue;
98  }
99 
100  auto BurnsForever = ((relPos.y > 0) && DoesBurnForever(a_Chunk->GetBlock(relPos.addedY(-1))));
101  auto BlockMeta = a_Chunk->GetMeta(relPos);
102 
103  auto Raining = std::any_of(std::begin(gCrossCoords), std::end(gCrossCoords), [a_Chunk, relPos](Vector3i cc)
104  {
105  auto Adjusted = relPos + cc;
106  const auto Chunk = a_Chunk->GetRelNeighborChunkAdjustCoords(Adjusted);
107  if ((Chunk != nullptr) && Chunk->IsValid())
108  {
109  return Chunk->IsWeatherWetAt(Adjusted);
110  }
111  return false;
112  });
113 
114  // Randomly burn out the fire if it is raining:
115  if (!BurnsForever && Raining && GetRandomProvider().RandBool(CHANCE_BASE_RAIN_EXTINGUISH + (BlockMeta * CHANCE_AGE_M_RAIN_EXTINGUISH)))
116  {
117  a_Chunk->SetBlock(relPos, E_BLOCK_AIR, 0);
118  itr = Data.erase(itr);
119  continue;
120  }
121 
122  // Try to spread the fire:
123  TrySpreadFire(a_Chunk, relPos);
124 
125  itr->Data -= NumMSecs;
126  if (itr->Data >= 0)
127  {
128  // Not yet, wait for it longer
129  ++itr;
130  continue;
131  }
132 
133  // FIRE_FLOG("FS: Fire at {0} is stepping", absPos);
134 
135  // TODO: Add some randomness into this
136  const auto BurnStep = GetBurnStepTime(a_Chunk, relPos);
137  if (BurnStep == 0)
138  {
139  // Fire has no fuel or ground block, extinguish flame
140  a_Chunk->SetBlock(relPos, E_BLOCK_AIR, 0);
141  itr = Data.erase(itr);
142  continue;
143  }
144 
145  // Has the fire burnt out?
146  if (BlockMeta == 0x0f)
147  {
148  // The fire burnt out completely
149  FIRE_FLOG("FS: Fire at {0} burnt out, removing the fire block", absPos);
150  a_Chunk->SetBlock(relPos, E_BLOCK_AIR, 0);
151  RemoveFuelNeighbors(a_Chunk, relPos);
152  itr = Data.erase(itr);
153  continue;
154  }
155 
156  // Burn out the fire one step by increasing the meta:
157  if (!BurnsForever)
158  {
159  a_Chunk->SetMeta(relPos, BlockMeta + 1);
160  }
161 
162  itr->Data = BurnStep;
163  ++itr;
164  } // for itr - Data[]
165 }
166 
167 
168 
169 
170 
172 {
173  return (a_BlockType == E_BLOCK_FIRE);
174 }
175 
176 
177 
178 
179 
181 {
182  switch (a_BlockType)
183  {
184  case E_BLOCK_PLANKS:
186  case E_BLOCK_WOODEN_SLAB:
191  case E_BLOCK_LEAVES:
192  case E_BLOCK_NEW_LEAVES:
193  case E_BLOCK_LOG:
194  case E_BLOCK_NEW_LOG:
195  case E_BLOCK_WOOL:
196  case E_BLOCK_BOOKCASE:
197  case E_BLOCK_FENCE:
199  case E_BLOCK_BIRCH_FENCE:
209  case E_BLOCK_TNT:
210  case E_BLOCK_VINES:
211  case E_BLOCK_HAY_BALE:
212  case E_BLOCK_TALL_GRASS:
213  case E_BLOCK_BIG_FLOWER:
214  case E_BLOCK_DANDELION:
215  case E_BLOCK_FLOWER:
216  case E_BLOCK_CARPET:
217  {
218  return true;
219  }
220  }
221  return false;
222 }
223 
224 
225 
226 
227 
229 {
230  return (a_BlockType == E_BLOCK_NETHERRACK);
231 }
232 
233 
234 
235 
236 
237 void cFireSimulator::AddBlock(cChunk & a_Chunk, Vector3i a_Position, BLOCKTYPE a_Block)
238 {
239  if (!IsAllowedBlock(a_Block))
240  {
241  return;
242  }
243 
244  // Check for duplicates:
245  cFireSimulatorChunkData & ChunkData = a_Chunk.GetFireSimulatorData();
246  for (cCoordWithIntList::iterator itr = ChunkData.begin(), end = ChunkData.end(); itr != end; ++itr)
247  {
248  const Vector3i ItrPos{itr->x, itr->y, itr->z};
249  if (ItrPos == a_Position)
250  {
251  // Block already present, check if burn step should decrease
252  // This means if fuel is removed, then the fire burns out sooner
253  const auto NewBurnStep = GetBurnStepTime(&a_Chunk, a_Position);
254  if (itr->Data > NewBurnStep)
255  {
256  FIRE_FLOG("FS: Block lost its fuel at {0}", a_Block);
257  itr->Data = NewBurnStep;
258  }
259 
260  return;
261  }
262  } // for itr - ChunkData[]
263 
264  FIRE_FLOG("FS: Adding block {0}", a_Block);
265  ChunkData.emplace_back(a_Position.x, a_Position.y, a_Position.z, 100);
266 }
267 
268 
269 
270 
271 
273 {
274  bool IsBlockBelowSolid = false;
275  if (a_RelPos.y > 0)
276  {
277  BLOCKTYPE BlockBelow = a_Chunk->GetBlock(a_RelPos.addedY(-1));
278  if (DoesBurnForever(BlockBelow))
279  {
280  // Is burning atop of netherrack, burn forever (re-check in 10 sec)
281  return 10000;
282  }
283  if (IsFuel(BlockBelow))
284  {
285  return static_cast<int>(m_BurnStepTimeFuel);
286  }
287  IsBlockBelowSolid = cBlockInfo::IsSolid(BlockBelow);
288  }
289 
290  for (const auto & cross: gCrossCoords)
291  {
293  NIBBLETYPE BlockMeta;
294  if (a_Chunk->UnboundedRelGetBlock(a_RelPos + cross, BlockType, BlockMeta))
295  {
296  if (IsFuel(BlockType))
297  {
298  return static_cast<int>(m_BurnStepTimeFuel);
299  }
300  }
301  } // for i - gCrossCoords[]
302 
303  if (!IsBlockBelowSolid)
304  {
305  // Checked through everything, nothing was flammable
306  // If block below isn't solid, we can't have fire, it would be a non-fueled fire
307  return 0;
308  }
309  return static_cast<int>(m_BurnStepTimeNonfuel);
310 }
311 
312 
313 
314 
315 
317 {
318  /*
319  if (GetRandomProvider().RandBool(0.99))
320  {
321  // Make the chance to spread 100x smaller
322  return;
323  }
324  */
325 
326  for (int x = -1; x <= 1; x++)
327  {
328  for (int z = -1; z <= 1; z++)
329  {
330  for (int y = 1; y <= 2; y++) // flames spread up one more block than around
331  {
332  // No need to check the coords for equality with the parent block,
333  // it cannot catch fire anyway (because it's not an air block)
334 
335  if (!GetRandomProvider().RandBool(m_Flammability * (1.0 / MAX_CHANCE_FLAMMABILITY)))
336  {
337  continue;
338  }
339 
340  // Start the fire in the neighbor a_RelPos + {x, y, z}
341  auto dstRelPos = a_RelPos + Vector3i{x, y, z};
342  if (CanStartFireInBlock(a_Chunk, dstRelPos))
343  {
344  auto dstAbsPos = a_Chunk->RelativeToAbsolute(dstRelPos);
345  if (cRoot::Get()->GetPluginManager()->CallHookBlockSpread(m_World, dstAbsPos, ssFireSpread))
346  {
347  return;
348  }
349 
350  FIRE_FLOG("FS: Starting new fire at {0}.", dstAbsPos);
351  a_Chunk->UnboundedRelSetBlock(dstRelPos, E_BLOCK_FIRE, 0);
352  }
353  } // for y
354  } // for z
355  } // for x
356 }
357 
358 
359 
360 
361 
363 {
364  for (auto & coord : gNeighborCoords)
365  {
366  auto relPos = a_RelPos + coord;
367 
368  if (!cChunkDef::IsValidHeight(relPos))
369  {
370  continue;
371  }
372 
373  const auto neighbor = a_Chunk->GetRelNeighborChunkAdjustCoords(relPos);
374 
375  if ((neighbor == nullptr) || !neighbor->IsValid())
376  {
377  continue;
378  }
379 
380  BLOCKTYPE BlockType = neighbor->GetBlock(relPos);
381 
382  if (!IsFuel(BlockType))
383  {
384  continue;
385  }
386 
387  auto absPos = neighbor->RelativeToAbsolute(relPos);
388  if (BlockType == E_BLOCK_TNT)
389  {
390  neighbor->SetBlock(relPos, E_BLOCK_AIR, 0);
391  m_World.SpawnPrimedTNT(Vector3d(absPos) + Vector3d(0.5, 0.5, 0.5)); // 80 ticks to boom
392  return;
393  }
394 
395  bool ShouldReplaceFuel = (GetRandomProvider().RandBool(m_ReplaceFuelChance * (1.0 / MAX_CHANCE_REPLACE_FUEL)));
396  if (ShouldReplaceFuel && !cRoot::Get()->GetPluginManager()->CallHookBlockSpread(m_World, absPos, ssFireSpread))
397  {
398  neighbor->SetBlock(relPos, E_BLOCK_FIRE, 0);
399  }
400  else
401  {
402  neighbor->SetBlock(relPos, E_BLOCK_AIR, 0);
403  }
404  } // for i - Coords[]
405 }
406 
407 
408 
409 
410 
412 {
414  NIBBLETYPE BlockMeta;
415  if (!a_NearChunk->UnboundedRelGetBlock(a_RelPos, BlockType, BlockMeta))
416  {
417  // The chunk is not accessible
418  return false;
419  }
420 
421  if (BlockType != E_BLOCK_AIR)
422  {
423  // Only an air block can be replaced by a fire block
424  return false;
425  }
426 
427  for (const auto & neighbor: gNeighborCoords)
428  {
429  if (!a_NearChunk->UnboundedRelGetBlock(a_RelPos + neighbor, BlockType, BlockMeta))
430  {
431  // Neighbor inaccessible, skip it while evaluating
432  continue;
433  }
434  if (IsFuel(BlockType))
435  {
436  return true;
437  }
438  } // for i - Coords[]
439  return false;
440 }
@ E_BLOCK_NEW_LEAVES
Definition: BlockType.h:180
@ E_BLOCK_OAK_WOOD_STAIRS
Definition: BlockType.h:63
@ E_BLOCK_DARK_OAK_FENCE
Definition: BlockType.h:210
@ E_BLOCK_FLOWER
Definition: BlockType.h:48
@ E_BLOCK_DOUBLE_WOODEN_SLAB
Definition: BlockType.h:140
@ E_BLOCK_BIRCH_FENCE_GATE
Definition: BlockType.h:203
@ E_BLOCK_ACACIA_FENCE_GATE
Definition: BlockType.h:206
@ E_BLOCK_AIR
Definition: BlockType.h:10
@ E_BLOCK_WOOL
Definition: BlockType.h:45
@ E_BLOCK_LEAVES
Definition: BlockType.h:28
@ E_BLOCK_OAK_FENCE_GATE
Definition: BlockType.h:122
@ E_BLOCK_CARPET
Definition: BlockType.h:190
@ E_BLOCK_BIRCH_WOOD_STAIRS
Definition: BlockType.h:150
@ E_BLOCK_FIRE
Definition: BlockType.h:61
@ E_BLOCK_TNT
Definition: BlockType.h:56
@ E_BLOCK_JUNGLE_WOOD_STAIRS
Definition: BlockType.h:151
@ E_BLOCK_FENCE
Definition: BlockType.h:100
@ E_BLOCK_NEW_LOG
Definition: BlockType.h:181
@ E_BLOCK_DANDELION
Definition: BlockType.h:47
@ E_BLOCK_BOOKCASE
Definition: BlockType.h:57
@ E_BLOCK_PLANKS
Definition: BlockType.h:15
@ E_BLOCK_SPRUCE_FENCE_GATE
Definition: BlockType.h:202
@ E_BLOCK_HAY_BALE
Definition: BlockType.h:189
@ E_BLOCK_SPRUCE_WOOD_STAIRS
Definition: BlockType.h:149
@ E_BLOCK_DARK_OAK_FENCE_GATE
Definition: BlockType.h:205
@ E_BLOCK_JUNGLE_FENCE_GATE
Definition: BlockType.h:204
@ E_BLOCK_BIG_FLOWER
Definition: BlockType.h:194
@ E_BLOCK_VINES
Definition: BlockType.h:121
@ E_BLOCK_SPRUCE_FENCE
Definition: BlockType.h:207
@ E_BLOCK_WOODEN_SLAB
Definition: BlockType.h:141
@ E_BLOCK_NETHERRACK
Definition: BlockType.h:102
@ E_BLOCK_LOG
Definition: BlockType.h:27
@ E_BLOCK_ACACIA_FENCE
Definition: BlockType.h:211
@ E_BLOCK_BIRCH_FENCE
Definition: BlockType.h:208
@ E_BLOCK_TALL_GRASS
Definition: BlockType.h:41
@ E_BLOCK_JUNGLE_FENCE
Definition: BlockType.h:209
unsigned char NIBBLETYPE
The datatype used by nibbledata (meta, light, skylight)
Definition: ChunkDef.h:44
std::list< cCoordWithInt > cCoordWithIntList
Definition: ChunkDef.h:502
unsigned char BLOCKTYPE
The datatype used by blockdata.
Definition: ChunkDef.h:41
@ ssFireSpread
Definition: Defines.h:340
MTRand & GetRandomProvider()
Returns the current thread's random number source.
Definition: FastRandom.cpp:12
BlockType
Definition: BlockTypes.h:4
static constexpr Vector3i gCrossCoords[]
static constexpr Vector3i gNeighborCoords[]
#define MAX_CHANCE_FLAMMABILITY
#define CHANCE_AGE_M_RAIN_EXTINGUISH
#define CHANCE_BASE_RAIN_EXTINGUISH
#define FIRE_FLOG(...)
#define MAX_CHANCE_REPLACE_FUEL
cCoordWithIntList cFireSimulatorChunkData
Stores individual fire blocks in the chunk; the int data is used as the time [msec] the fire takes to...
Definition: FireSimulator.h:69
Vector3< double > Vector3d
Definition: Vector3.h:485
bool CallHookBlockSpread(cWorld &a_World, Vector3i a_BlockPos, eSpreadSource a_Source)
static bool IsSolid(BLOCKTYPE Block)
Is this block solid (player cannot walk through)?
Definition: BlockInfo.cpp:892
Definition: Chunk.h:36
cFireSimulatorChunkData & GetFireSimulatorData(void)
Definition: Chunk.h:412
NIBBLETYPE GetMeta(int a_RelX, int a_RelY, int a_RelZ) const
Definition: Chunk.h:279
BLOCKTYPE GetBlock(int a_RelX, int a_RelY, int a_RelZ) const
Definition: Chunk.h:146
Vector3i RelativeToAbsolute(Vector3i a_RelBlockPosition) const
Converts the coord relative to this chunk into an absolute coord.
Definition: Chunk.h:450
cChunk * GetRelNeighborChunkAdjustCoords(Vector3i &a_RelPos) const
Returns the chunk into which the relatively-specified block belongs.
Definition: Chunk.cpp:1885
void SetBlock(Vector3i a_RelBlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
Definition: Chunk.cpp:1263
bool UnboundedRelSetBlock(Vector3i a_RelPos, 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 ...
Definition: Chunk.cpp:1135
bool UnboundedRelGetBlock(Vector3i a_RelCoords, BLOCKTYPE &a_BlockType, NIBBLETYPE &a_BlockMeta) const
Same as GetBlock(), but relative coords needn't be in this chunk (uses m_Neighbor-s or m_ChunkMap in ...
Definition: Chunk.cpp:1008
void SetMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_Meta)
Definition: Chunk.h:286
static bool IsValidHeight(Vector3i a_BlockPosition)
Validates a height-coordinate.
Definition: ChunkDef.h:185
bool RandBool(double a_TrueProbability=0.5)
Return a random bool with the given probability of being true.
Definition: FastRandom.h:158
int GetValueSetI(const AString &keyname, const AString &valuename, const int defValue=0) override
Definition: IniFile.cpp:559
static cRoot * Get()
Definition: Root.h:52
cPluginManager * GetPluginManager(void)
Definition: Root.h:111
unsigned m_BurnStepTimeNonfuel
Time (in msec) that a fire block takes to burn without a fuel block into the next step.
Definition: FireSimulator.h:38
void RemoveFuelNeighbors(cChunk *a_Chunk, Vector3i a_RelPos)
Removes all burnable blocks neighboring the specified block.
int m_ReplaceFuelChance
Chance [0..100000] of a fuel burning out being replaced by a new fire block instead of an air block.
Definition: FireSimulator.h:44
static bool IsFuel(BLOCKTYPE a_BlockType)
bool CanStartFireInBlock(cChunk *a_NearChunk, Vector3i a_RelPos)
Returns true if a fire can be started in the specified block, that is, it is an air block and has fue...
static bool IsAllowedBlock(BLOCKTYPE a_BlockType)
int m_Flammability
Chance [0..100000] of an adjacent fuel to catch fire on each tick.
Definition: FireSimulator.h:41
virtual void AddBlock(cChunk &a_Chunk, Vector3i a_Position, BLOCKTYPE a_Block) override
Called to simulate a new block.
void TrySpreadFire(cChunk *a_Chunk, Vector3i a_RelPos)
Tries to spread fire to a neighborhood of the specified block.
virtual void SimulateChunk(std::chrono::milliseconds a_Dt, int a_ChunkX, int a_ChunkZ, cChunk *a_Chunk) override
unsigned m_BurnStepTimeFuel
Time (in msec) that a fire block takes to burn with a fuel block into the next step.
Definition: FireSimulator.h:35
cFireSimulator(cWorld &a_World, cIniFile &a_IniFile)
int GetBurnStepTime(cChunk *a_Chunk, Vector3i a_RelPos)
Returns the time [msec] after which the specified fire block is stepped again; based on surrounding f...
static bool DoesBurnForever(BLOCKTYPE a_BlockType)
Base class for all block-based physics simulators (such as fluid, fire, falling blocks etc....
Definition: Simulator.h:22
cWorld & m_World
Definition: Simulator.h:71
Vector3< T > addedY(T a_AddY) const
Returns a copy of this vector moved by the specified amount on the y axis.
Definition: Vector3.h:314
T x
Definition: Vector3.h:17
T y
Definition: Vector3.h:17
T z
Definition: Vector3.h:17
Definition: World.h:53
UInt32 SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTimeInSec=80, double a_InitialVelocityCoeff=1, bool a_ShouldPlayFuseSound=true)
Definition: World.h:519