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