Cuberite
A lightweight, fast and extensible game server for Minecraft
FloodyFluidSimulator.cpp
Go to the documentation of this file.
1 
2 // FloodyFluidSimulator.cpp
3 
4 // Interfaces to the cFloodyFluidSimulator that represents a fluid simulator that tries to flood everything :)
5 // https://forum.cuberite.org/thread-565.html
6 
7 #include "Globals.h"
8 
9 #include "FloodyFluidSimulator.h"
10 #include "../World.h"
11 #include "../Chunk.h"
12 #include "../BlockArea.h"
13 #include "../Blocks/BlockHandler.h"
14 #include "../BlockInServerPluginInterface.h"
15 #include "../Blocks/ChunkInterface.h"
16 
17 
18 
19 
20 
21 // Enable or disable detailed logging
22 #if 0
23  #define FLUID_FLOG FLOGD
24 #else
25  #define FLUID_FLOG(...)
26 #endif
27 
28 
29 
30 
31 
33  cWorld & a_World,
34  BLOCKTYPE a_Fluid,
35  BLOCKTYPE a_StationaryFluid,
36  NIBBLETYPE a_Falloff,
37  int a_TickDelay,
38  int a_NumNeighborsForSource
39 ) :
40  super(a_World, a_Fluid, a_StationaryFluid, a_TickDelay),
41  m_Falloff(a_Falloff),
42  m_NumNeighborsForSource(a_NumNeighborsForSource)
43 {
44 }
45 
46 
47 
48 
49 
50 void cFloodyFluidSimulator::SimulateBlock(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
51 {
52  FLUID_FLOG("Simulating block {0}: block {1}, meta {2}",
53  a_Chunk->PositionToWorldPosition(a_RelX, a_RelY, a_RelZ),
54  a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ),
55  a_Chunk->GetMeta(a_RelX, a_RelY, a_RelZ)
56  );
57 
58  BLOCKTYPE MyBlock; NIBBLETYPE MyMeta;
59  a_Chunk->GetBlockTypeMeta(a_RelX, a_RelY, a_RelZ, MyBlock, MyMeta);
60 
61  if (!IsAnyFluidBlock(MyBlock))
62  {
63  // Can happen - if a block is scheduled for simulating and gets replaced in the meantime.
64  FLUID_FLOG(" BadBlockType exit");
65  return;
66  }
67 
68  // When in contact with water, lava should harden
69  if (HardenBlock(a_Chunk, {a_RelX, a_RelY, a_RelZ}, MyBlock, MyMeta))
70  {
71  // Block was changed, bail out
72  return;
73  }
74 
75  if (MyMeta != 0)
76  {
77  // Source blocks aren't checked for tributaries, others are.
78  if (CheckTributaries(a_Chunk, a_RelX, a_RelY, a_RelZ, MyMeta))
79  {
80  // Has no tributary, has been decreased (in CheckTributaries()),
81  // no more processing needed (neighbors have been scheduled by the decrease)
82  FLUID_FLOG(" CheckTributaries exit");
83  return;
84  }
85  }
86 
87  // New meta for the spreading to neighbors:
88  // If this is a source block or was falling, the new meta is just the falloff
89  // Otherwise it is the current meta plus falloff (may be larger than max height, will be checked later)
90  NIBBLETYPE NewMeta = ((MyMeta == 0) || ((MyMeta & 0x08) != 0)) ? m_Falloff : (MyMeta + m_Falloff);
91  if (a_RelY > 0)
92  {
93  bool SpreadFurther = true;
94  BLOCKTYPE Below = a_Chunk->GetBlock(a_RelX, a_RelY - 1, a_RelZ);
95  if (IsPassableForFluid(Below) || IsBlockLava(Below) || IsBlockWater(Below))
96  {
97  // Spread only down, possibly washing away what's there or turning lava to stone / cobble / obsidian:
98  SpreadToNeighbor(a_Chunk, a_RelX, a_RelY - 1, a_RelZ, 8);
99 
100  // Source blocks spread both downwards and sideways
101  if (MyMeta != 0)
102  {
103  SpreadFurther = false;
104  }
105  }
106  // Spread to the neighbors:
107  if (SpreadFurther && (NewMeta < 8))
108  {
109  SpreadXZ(a_Chunk, a_RelX, a_RelY, a_RelZ, NewMeta);
110  }
111 
112  // If source creation is on, check for it here:
113  if (
114  (m_NumNeighborsForSource > 0) && // Source creation is on
115  (MyMeta == m_Falloff) && // Only exactly one block away from a source (fast bail-out)
116  (
117  !IsPassableForFluid(Below) || // Only exactly 1 block deep
118  (Below == m_StationaryFluidBlock) // Or a source block underneath
119  ) &&
120  CheckNeighborsForSource(a_Chunk, a_RelX, a_RelY, a_RelZ) // Did we create a source?
121  )
122  {
123  // We created a source, no more spreading is to be done now
124  // Also has been re-scheduled for ticking in the next wave, so no marking is needed
125  return;
126  }
127  }
128 
129  // Mark as processed:
130  a_Chunk->FastSetBlock(a_RelX, a_RelY, a_RelZ, m_StationaryFluidBlock, MyMeta);
131 }
132 
133 
134 
135 
136 
137 void cFloodyFluidSimulator::SpreadXZ(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
138 {
139  SpreadToNeighbor(a_Chunk, a_RelX - 1, a_RelY, a_RelZ, a_NewMeta);
140  SpreadToNeighbor(a_Chunk, a_RelX + 1, a_RelY, a_RelZ, a_NewMeta);
141  SpreadToNeighbor(a_Chunk, a_RelX, a_RelY, a_RelZ - 1, a_NewMeta);
142  SpreadToNeighbor(a_Chunk, a_RelX, a_RelY, a_RelZ + 1, a_NewMeta);
143 }
144 
145 
146 
147 
148 
149 bool cFloodyFluidSimulator::CheckTributaries(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_MyMeta)
150 {
151  // If we have a section above, check if there's fluid above this block that would feed it:
152  if (a_RelY < cChunkDef::Height - 1)
153  {
154  if (IsAnyFluidBlock(a_Chunk->GetBlock(a_RelX, a_RelY + 1, a_RelZ)))
155  {
156  // This block is fed from above, no more processing needed
157  FLUID_FLOG(" Fed from above");
158  return false;
159  }
160  }
161 
162  // Not fed from above, check if there's a feed from the side (but not if it's a downward-flowing block):
163  if (a_MyMeta != 8)
164  {
165  BLOCKTYPE BlockType;
166  NIBBLETYPE BlockMeta;
167  static const Vector3i Coords[] =
168  {
169  Vector3i( 1, 0, 0),
170  Vector3i(-1, 0, 0),
171  Vector3i( 0, 0, 1),
172  Vector3i( 0, 0, -1),
173  } ;
174  for (size_t i = 0; i < ARRAYCOUNT(Coords); i++)
175  {
176  if (!a_Chunk->UnboundedRelGetBlock(a_RelX + Coords[i].x, a_RelY, a_RelZ + Coords[i].z, BlockType, BlockMeta))
177  {
178  continue;
179  }
180  if (IsAllowedBlock(BlockType) && IsHigherMeta(BlockMeta, a_MyMeta))
181  {
182  // This block is fed, no more processing needed
183  FLUID_FLOG(" Fed from {0}, type {1}, meta {2}",
184  a_Chunk->PositionToWorldPosition(a_RelX+ Coords[i].x, a_RelY, a_RelZ + Coords[i].z),
185  BlockType, BlockMeta
186  );
187  return false;
188  }
189  } // for i - Coords[]
190  } // if not fed from above
191 
192  // Block is not fed, decrease by m_Falloff levels:
193  if (a_MyMeta >= 8)
194  {
195  FLUID_FLOG(" Not fed and downwards, turning into non-downwards meta {0}", m_Falloff);
196  a_Chunk->SetBlock({a_RelX, a_RelY, a_RelZ}, m_StationaryFluidBlock, m_Falloff);
197  }
198  else
199  {
200  a_MyMeta += m_Falloff;
201  if (a_MyMeta < 8)
202  {
203  FLUID_FLOG(" Not fed, decreasing from {0} to {1}", a_MyMeta - m_Falloff, a_MyMeta);
204  a_Chunk->SetBlock({a_RelX, a_RelY, a_RelZ}, m_StationaryFluidBlock, a_MyMeta);
205  }
206  else
207  {
208  FLUID_FLOG(" Not fed, meta {0}, erasing altogether", a_MyMeta);
209  a_Chunk->SetBlock({a_RelX, a_RelY, a_RelZ}, E_BLOCK_AIR, 0);
210  }
211  }
212  return true;
213 }
214 
215 
216 
217 
218 
219 void cFloodyFluidSimulator::SpreadToNeighbor(cChunk * a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
220 {
221  ASSERT(a_NewMeta <= 8); // Invalid meta values
222  ASSERT(a_NewMeta > 0); // Source blocks aren't spread
223 
224  Vector3i relPos(a_RelX, a_RelY, a_RelZ);
225  a_NearChunk = a_NearChunk->GetRelNeighborChunkAdjustCoords(relPos);
226  if ((a_NearChunk == nullptr) || (!a_NearChunk->IsValid()))
227  {
228  // Chunk not available
229  return;
230  }
231 
232  const auto absPos = a_NearChunk->RelativeToAbsolute(relPos);
233  BLOCKTYPE BlockType;
234  NIBBLETYPE BlockMeta;
235  a_NearChunk->GetBlockTypeMeta(relPos, BlockType, BlockMeta);
236 
237  if (IsAllowedBlock(BlockType))
238  {
239  if ((BlockMeta == a_NewMeta) || IsHigherMeta(BlockMeta, a_NewMeta))
240  {
241  // Don't spread there, there's already a higher or same level there
242  return;
243  }
244  }
245 
246  // Check water - lava interaction:
247  if (m_FluidBlock == E_BLOCK_LAVA)
248  {
249  if (IsBlockWater(BlockType))
250  {
251  // Lava flowing into water, change to stone / cobblestone based on direction:
252  BLOCKTYPE NewBlock = (a_NewMeta == 8) ? E_BLOCK_STONE : E_BLOCK_COBBLESTONE;
253  FLUID_FLOG(" Lava flowing into water, turning water at rel {0} into {1}",
254  relPos, ItemTypeToString(NewBlock)
255  );
256  a_NearChunk->SetBlock(relPos, NewBlock, 0);
257 
259  "block.lava.extinguish",
260  absPos,
261  0.5f,
262  1.5f
263  );
264  return;
265  }
266  }
267  else if (m_FluidBlock == E_BLOCK_WATER)
268  {
269  if (IsBlockLava(BlockType))
270  {
271  // Water flowing into lava, change to cobblestone / obsidian based on dest block:
272  BLOCKTYPE NewBlock = (BlockMeta == 0) ? E_BLOCK_OBSIDIAN : E_BLOCK_COBBLESTONE;
273  FLUID_FLOG(" Water flowing into lava, turning lava at rel {0} into {1}",
274  relPos, ItemTypeToString(NewBlock)
275  );
276  a_NearChunk->SetBlock(relPos, NewBlock, 0);
277 
279  "block.lava.extinguish",
280  absPos,
281  0.5f,
282  1.5f
283  );
284  return;
285  }
286  }
287  else
288  {
289  ASSERT(!"Unknown fluid!");
290  }
291 
292  if (!IsPassableForFluid(BlockType))
293  {
294  // Can't spread there
295  return;
296  }
297 
298  // Wash away the block there, if possible:
299  if (CanWashAway(BlockType))
300  {
301  cBlockHandler * Handler = BlockHandler(BlockType);
302  if (Handler->DoesDropOnUnsuitable())
303  {
304  m_World.DropBlockAsPickups(absPos, nullptr, nullptr);
305  }
306  } // if (CanWashAway)
307 
308  // Spread:
309  FLUID_FLOG(" Spreading to {0} with meta {1}", absPos, a_NewMeta);
310  a_NearChunk->SetBlock(relPos, m_FluidBlock, a_NewMeta);
311  m_World.GetSimulatorManager()->WakeUp(absPos, a_NearChunk);
312 
313  HardenBlock(a_NearChunk, relPos, m_FluidBlock, a_NewMeta);
314 }
315 
316 
317 
318 
319 
320 bool cFloodyFluidSimulator::CheckNeighborsForSource(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
321 {
322  FLUID_FLOG(" Checking neighbors for source creation");
323 
324  static const Vector3i NeighborCoords[] =
325  {
326  Vector3i(-1, 0, 0),
327  Vector3i( 1, 0, 0),
328  Vector3i( 0, 0, -1),
329  Vector3i( 0, 0, 1),
330  } ;
331 
332  int NumNeeded = m_NumNeighborsForSource;
333  for (size_t i = 0; i < ARRAYCOUNT(NeighborCoords); i++)
334  {
335  int x = a_RelX + NeighborCoords[i].x;
336  int y = a_RelY + NeighborCoords[i].y;
337  int z = a_RelZ + NeighborCoords[i].z;
338  BLOCKTYPE BlockType;
339  NIBBLETYPE BlockMeta;
340  if (!a_Chunk->UnboundedRelGetBlock(x, y, z, BlockType, BlockMeta))
341  {
342  // Neighbor not available, skip it
343  continue;
344  }
345  // FLUID_FLOG(" Neighbor at {0}: {1}", Vector3i{x, y, z}, ItemToFullString(cItem(BlockType, 1, BlockMeta)));
346  if ((BlockMeta == 0) && IsAnyFluidBlock(BlockType))
347  {
348  NumNeeded--;
349  // FLUID_FLOG(" Found a neighbor source at {0}, NumNeeded := {1}", Vector3i{x, y, z}, NumNeeded);
350  if (NumNeeded == 0)
351  {
352  // Found enough, turn into a source and bail out
353  // FLUID_FLOG(" Found enough neighbor sources, turning into a source");
354  a_Chunk->SetBlock({a_RelX, a_RelY, a_RelZ}, m_FluidBlock, 0);
355  return true;
356  }
357  }
358  }
359  // FLUID_FLOG(" Not enough neighbors for turning into a source, NumNeeded = {0}", NumNeeded);
360  return false;
361 }
362 
363 
364 
365 
366 
367 bool cFloodyFluidSimulator::HardenBlock(cChunk * a_Chunk, Vector3i a_RelPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta)
368 {
369  ASSERT(cChunkDef::IsValidRelPos(a_RelPos));
370 
371  // Only lava blocks can harden
372  if (!IsBlockLava(a_BlockType))
373  {
374  return false;
375  }
376 
377  bool ShouldHarden = false;
378 
379  BLOCKTYPE BlockType;
380  NIBBLETYPE BlockMeta;
381  static const Vector3i neighborOffsets[] =
382  {
383  Vector3i( 1, 0, 0),
384  Vector3i(-1, 0, 0),
385  Vector3i( 0, 0, 1),
386  Vector3i( 0, 0, -1),
387  };
388  for (const auto & ofs: neighborOffsets)
389  {
390  if (!a_Chunk->UnboundedRelGetBlock(a_RelPos + ofs, BlockType, BlockMeta))
391  {
392  continue;
393  }
394  if (IsBlockWater(BlockType))
395  {
396  ShouldHarden = true;
397  }
398  } // for i - Coords[]
399 
400  if (ShouldHarden)
401  {
402  if (a_Meta == 0)
403  {
404  // Source lava block
405  a_Chunk->SetBlock(a_RelPos, E_BLOCK_OBSIDIAN, 0);
406  return true;
407  }
408  // Ignore last lava level
409  else if (a_Meta <= 4)
410  {
411  a_Chunk->SetBlock(a_RelPos, E_BLOCK_COBBLESTONE, 0);
412  return true;
413  }
414  }
415 
416  return false;
417 }
418 
419 
420 
bool IsValid(void) const
Returns true iff the chunk block data is valid (loaded / generated)
Definition: Chunk.h:72
virtual bool IsAllowedBlock(BLOCKTYPE a_BlockType) override
Returns true if the specified block type is "interesting" for this simulator.
T x
Definition: Vector3.h:17
bool IsBlockWater(BLOCKTYPE a_BlockType)
Definition: Defines.h:436
void WakeUp(Vector3i a_Block, cChunk *a_Chunk)
cChunk * GetRelNeighborChunkAdjustCoords(Vector3i &a_RelPos) const
Returns the chunk into which the relatively-specified block belongs.
Definition: Chunk.cpp:2358
BLOCKTYPE m_FluidBlock
void GetBlockTypeMeta(Vector3i a_RelPos, BLOCKTYPE &a_BlockType, NIBBLETYPE &a_BlockMeta) const
Definition: Chunk.cpp:2230
unsigned char BLOCKTYPE
The datatype used by blockdata.
Definition: ChunkDef.h:42
static bool IsValidRelPos(Vector3i a_RelPos)
Validates a chunk relative coordinate.
Definition: ChunkDef.h:224
bool IsPassableForFluid(BLOCKTYPE a_BlockType)
BLOCKTYPE m_StationaryFluidBlock
Vector3i RelativeToAbsolute(Vector3i a_RelBlockPosition)
Converts the coord relative to this chunk into an absolute coord.
Definition: Chunk.h:569
bool HardenBlock(cChunk *a_Chunk, Vector3i a_RelPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta)
Checks if the specified block should harden (Water / Lava interaction) and if so, converts it to a su...
bool IsHigherMeta(NIBBLETYPE a_Meta1, NIBBLETYPE a_Meta2)
Returns true if a_Meta1 is a higher fluid than a_Meta2.
void SetBlock(Vector3i a_RelBlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
Definition: Chunk.cpp:1313
Vector3i PositionToWorldPosition(Vector3i a_RelPos)
Definition: Chunk.h:358
cSimulatorManager * GetSimulatorManager(void)
Definition: World.h:713
unsigned char NIBBLETYPE
The datatype used by nibbledata (meta, light, skylight)
Definition: ChunkDef.h:45
bool DropBlockAsPickups(Vector3i a_BlockPos, const cEntity *a_Digger=nullptr, const cItem *a_Tool=nullptr)
Digs the specified block, and spawns the appropriate pickups for it.
Definition: World.cpp:2200
Definition: Chunk.h:49
cWorld & m_World
Definition: Simulator.h:60
T y
Definition: Vector3.h:17
bool CheckTributaries(cChunk *a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_MyMeta)
Checks tributaries, if not fed, decreases the block&#39;s level and returns true.
T z
Definition: Vector3.h:17
static const int Height
Definition: ChunkDef.h:135
virtual void BroadcastSoundEffect(const AString &a_SoundName, Vector3d a_Position, float a_Volume, float a_Pitch, const cClientHandle *a_Exclude=nullptr) override
static bool CanWashAway(BLOCKTYPE a_BlockType)
Definition: World.h:65
NIBBLETYPE GetMeta(int a_RelX, int a_RelY, int a_RelZ) const
Definition: Chunk.h:380
#define ASSERT(x)
Definition: Globals.h:335
cFloodyFluidSimulator(cWorld &a_World, BLOCKTYPE a_Fluid, BLOCKTYPE a_StationaryFluid, NIBBLETYPE a_Falloff, int a_TickDelay, int a_NumNeighborsForSource)
bool CheckNeighborsForSource(cChunk *a_Chunk, int a_RelX, int a_RelY, int a_RelZ)
Checks if there are enough neighbors to create a source at the coords specified; turns into source an...
bool IsAnyFluidBlock(BLOCKTYPE a_BlockType) const
cBlockHandler * BlockHandler(BLOCKTYPE a_BlockType)
Definition: BlockInfo.h:159
virtual void SpreadXZ(cChunk *a_Chunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
Spread fluid to XZ neighbors.
bool UnboundedRelGetBlock(Vector3i a_RelCoords, BLOCKTYPE &a_BlockType, NIBBLETYPE &a_BlockMeta) const
Same as GetBlock(), but relative coords needn&#39;t be in this chunk (uses m_Neighbor-s or m_ChunkMap in ...
Definition: Chunk.cpp:997
BLOCKTYPE GetBlock(int a_RelX, int a_RelY, int a_RelZ) const
Definition: Chunk.h:177
virtual bool DoesDropOnUnsuitable(void)
Returns if this block drops if it gets destroyed by an unsuitable situation.
void SpreadToNeighbor(cChunk *a_NearChunk, int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_NewMeta)
Spreads into the specified block, if the blocktype there allows.
virtual void SimulateBlock(cChunk *a_Chunk, int a_RelX, int a_RelY, int a_RelZ) override
Called from SimulateChunk() to simulate each block in one slot of blocks.
Vector3< int > Vector3i
Definition: Vector3.h:447
void FastSetBlock(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, BLOCKTYPE a_BlockMeta, bool a_SendToClients=true)
Definition: Chunk.cpp:1402
#define ARRAYCOUNT(X)
Evaluates to the number of elements in an array (compile-time!)
Definition: Globals.h:290
AString ItemTypeToString(short a_ItemType)
Translates itemtype into a string.
Definition: BlockID.cpp:270
#define FLUID_FLOG(...)
bool IsBlockLava(BLOCKTYPE a_BlockType)
Definition: Defines.h:475