Cuberite
A lightweight, fast and extensible game server for Minecraft
BlockPiston.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h"
3 #include "BlockPiston.h"
4 #include "../BlockInfo.h"
5 #include "../Item.h"
6 #include "../World.h"
7 #include "../Entities/Player.h"
8 #include "../BlockInServerPluginInterface.h"
9 #include "ChunkInterface.h"
10 
11 
12 
13 
14 
15 #define PISTON_MAX_PUSH_DISTANCE 12
16 
17 
18 
19 
20 
22 {
23  switch (a_PistonMeta & 0x07)
24  {
25  case 0: return Vector3i( 0, -1, 0);
26  case 1: return Vector3i( 0, 1, 0);
27  case 2: return Vector3i( 0, 0, -1);
28  case 3: return Vector3i( 0, 0, 1);
29  case 4: return Vector3i(-1, 0, 0);
30  case 5: return Vector3i( 1, 0, 0);
31  default:
32  {
33  LOGWARNING("%s: invalid direction %d, ignoring", __FUNCTION__, a_PistonMeta & 0x07);
34  ASSERT(!"Invalid direction");
35  return Vector3i();
36  }
37  }
38 }
39 
40 
41 
42 
43 
45 {
46  {
47  // Broadcast block action first. Will do nothing if piston cannot in fact push
48 
49  BLOCKTYPE pistonBlock;
50  NIBBLETYPE pistonMeta;
51  a_World.GetBlockTypeMeta(a_BlockPos, pistonBlock, pistonMeta);
52  a_World.BroadcastBlockAction(a_BlockPos, PistonExtendAction, pistonMeta, pistonBlock);
53  }
54 
55  // Client expects the server to "play" the animation before setting the final blocks
56  // However, we don't confuse animation with the underlying state of the world, so emulate by delaying 1 tick
57  // (Probably why vanilla has so many dupe glitches with sand and pistons lolol)
58 
59  a_World.ScheduleTask(1_tick, [a_BlockPos](cWorld & World)
60  {
61  BLOCKTYPE pistonBlock;
62  NIBBLETYPE pistonMeta;
63  World.GetBlockTypeMeta(a_BlockPos, pistonBlock, pistonMeta);
64 
65  if ((pistonBlock != E_BLOCK_PISTON) && !IsSticky(pistonBlock))
66  {
67  // Ensure we operate on a piston to avoid spurious behaviour
68  // Note that the scheduled task may result in the block type of a_BlockPos changing
69  return;
70  }
71 
72  if (IsExtended(pistonMeta))
73  {
74  // Already extended, bail out
75  return;
76  }
77 
78  Vector3i pushDir = MetadataToOffset(pistonMeta);
79  Vector3iSet blocksPushed;
80  if (!CanPushBlock(a_BlockPos + pushDir, World, true, blocksPushed, pushDir))
81  {
82  // Can't push anything, bail out
83  return;
84  }
85  PushBlocks(blocksPushed, World, pushDir);
86 
87  // Set the extension and the piston base correctly
88  Vector3i extensionPos = a_BlockPos + pushDir;
89  World.SetBlock(a_BlockPos, pistonBlock, pistonMeta | 0x8);
90  World.SetBlock(extensionPos, E_BLOCK_PISTON_EXTENSION, pistonMeta | (IsSticky(pistonBlock) ? 8 : 0));
91 
92  // Play sound effect only if extended successfully
93  World.BroadcastSoundEffect("block.piston.extend", a_BlockPos, 0.5f, 0.7f);
94  }
95  );
96 }
97 
98 
99 
100 
101 
103 {
104  {
105  BLOCKTYPE pistonBlock;
106  NIBBLETYPE pistonMeta;
107  a_World.GetBlockTypeMeta(a_BlockPos, pistonBlock, pistonMeta);
108  a_World.BroadcastBlockAction(a_BlockPos, PistonRetractAction, pistonMeta, pistonBlock);
109  }
110 
111  a_World.ScheduleTask(1_tick, [a_BlockPos](cWorld & World)
112  {
113  BLOCKTYPE pistonBlock;
114  NIBBLETYPE pistonMeta;
115  World.GetBlockTypeMeta(a_BlockPos, pistonBlock, pistonMeta);
116 
117  if ((pistonBlock != E_BLOCK_PISTON) && !IsSticky(pistonBlock))
118  {
119  // Ensure we operate on a piston to avoid spurious behaviour
120  // Note that the scheduled task may result in the block type of a_BlockPos changing
121  return;
122  }
123 
124  if (!IsExtended(pistonMeta))
125  {
126  // Already retracted, bail out
127  return;
128  }
129 
130  Vector3i pushDir = MetadataToOffset(pistonMeta);
131 
132  // Check the extension:
133  Vector3i extensionPos = a_BlockPos + pushDir;
134  if (World.GetBlock(extensionPos) != E_BLOCK_PISTON_EXTENSION)
135  {
136  LOGD("%s: Piston without an extension - still extending, or just in an invalid state?", __FUNCTION__);
137  return;
138  }
139 
140  // Remove extension, update base state:
141  World.SetBlock(extensionPos, E_BLOCK_AIR, 0);
142  World.SetBlock(a_BlockPos, pistonBlock, pistonMeta & ~(8));
143 
144  // (Retraction is always successful, but play in the task for consistency)
145  World.BroadcastSoundEffect("block.piston.contract", a_BlockPos, 0.5f, 0.7f);
146 
147  if (!IsSticky(pistonBlock))
148  {
149  // No need for block pulling, bail out
150  return;
151  }
152 
153  // Get the block to pull
154  Vector3i AdjustedPosition = a_BlockPos + pushDir * 2;
155  // Try to "push" the pulling block in the opposite direction
156  pushDir *= -1;
157 
158  Vector3iSet pushedBlocks;
159  if (!CanPushBlock(AdjustedPosition, World, false, pushedBlocks, pushDir))
160  {
161  // Not pushable, bail out
162  return;
163  }
164 
165  PushBlocks(pushedBlocks, World, pushDir);
166  }
167  );
168 }
169 
170 
171 
172 
173 
175  const Vector3iSet & a_BlocksToPush,
176  cWorld & a_World, const Vector3i & a_PushDir
177 )
178 {
179  // Sort blocks to move the blocks first, which are farthest away from the piston
180  // This prevents the overwriting of existing blocks
181  std::vector<Vector3i> sortedBlocks(a_BlocksToPush.begin(), a_BlocksToPush.end());
182  std::sort(sortedBlocks.begin(), sortedBlocks.end(), [a_PushDir](const Vector3i & a, const Vector3i & b)
183  {
184  return (a.Dot(a_PushDir) > b.Dot(a_PushDir));
185  });
186 
187  // Move every block
188  BLOCKTYPE moveBlock;
189  NIBBLETYPE moveMeta;
190  for (auto & moveBlockPos : sortedBlocks)
191  {
192  a_World.GetBlockTypeMeta(moveBlockPos, moveBlock, moveMeta);
193 
194  if (cBlockInfo::IsPistonBreakable(moveBlock))
195  {
196  // Block is breakable, drop it:
197  a_World.DropBlockAsPickups(moveBlockPos, nullptr, nullptr);
198  }
199  else
200  {
201  // Not breakable, just move it
202  a_World.SetBlock(moveBlockPos, E_BLOCK_AIR, 0);
203  moveBlockPos += a_PushDir;
204  a_World.SetBlock(moveBlockPos, moveBlock, moveMeta);
205  }
206  }
207 }
208 
209 
210 
211 
212 
214  const Vector3i & a_BlockPos, cWorld & a_World, bool a_RequirePushable,
215  Vector3iSet & a_BlocksPushed, const Vector3i & a_PushDir
216 )
217 {
218  if (!cChunkDef::IsValidHeight(a_BlockPos))
219  {
220  // Can't push a void block.
221  return false;
222  }
223 
224  const static std::array<Vector3i, 6> pushingDirs =
225  {
226  {
227  Vector3i(-1, 0, 0), Vector3i(1, 0, 0),
228  Vector3i( 0, -1, 0), Vector3i(0, 1, 0),
229  Vector3i( 0, 0, -1), Vector3i(0, 0, 1)
230  }
231  };
232 
233  BLOCKTYPE currBlock;
234  NIBBLETYPE currMeta;
235  a_World.GetBlockTypeMeta(a_BlockPos, currBlock, currMeta);
236 
237  if (currBlock == E_BLOCK_AIR)
238  {
239  // Air can be pushed
240  return true;
241  }
242 
243  if (!a_RequirePushable && cBlockInfo::IsPistonBreakable(currBlock))
244  {
245  // Block should not be broken, when it's not in the pushing direction
246  return true;
247  }
248 
249  if (!CanPush(currBlock, currMeta))
250  {
251  // When it's not required to push this block, don't fail
252  return !a_RequirePushable;
253  }
254 
255  if (a_BlocksPushed.size() >= PISTON_MAX_PUSH_DISTANCE)
256  {
257  // Do not allow to push too much blocks
258  return false;
259  }
260 
261  if (!a_BlocksPushed.insert(a_BlockPos).second || cBlockInfo::IsPistonBreakable(currBlock))
262  {
263  return true; // Element exist already
264  }
265 
266  if (currBlock == E_BLOCK_SLIME_BLOCK)
267  {
268  // Try to push the other directions
269  for (const auto & testDir : pushingDirs)
270  {
271  if (!CanPushBlock(a_BlockPos + testDir, a_World, false, a_BlocksPushed, a_PushDir))
272  {
273  // When it's not possible for a direction, then fail
274  return false;
275  }
276  }
277  }
278 
279  // Try to push the block in front of this block
280  return CanPushBlock(a_BlockPos + a_PushDir, a_World, true, a_BlocksPushed, a_PushDir);
281 }
282 
283 
284 
285 
286 
288  cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface,
289  Vector3i a_BlockPos,
290  BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta,
291  const cEntity * a_Digger
292 ) const
293 {
294  UNUSED(a_Digger);
295  if (!IsExtended(a_OldBlockMeta))
296  {
297  return;
298  }
299 
300  const auto Extension = a_BlockPos + MetadataToOffset(a_OldBlockMeta);
301  if (
302  cChunkDef::IsValidHeight(Extension) &&
303  (a_ChunkInterface.GetBlock(Extension) == E_BLOCK_PISTON_EXTENSION)
304  )
305  {
306  // If the piston is extended, destroy the extension as well:
307  a_ChunkInterface.SetBlock(Extension, E_BLOCK_AIR, 0);
308  }
309 }
310 
311 
312 
313 
314 
316 // cBlockPistonHeadHandler:
317 
319  cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface,
320  Vector3i a_BlockPos,
321  BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta,
322  const cEntity * a_Digger
323 ) const
324 {
325  UNUSED(a_Digger);
326  const auto Base = a_BlockPos - cBlockPistonHandler::MetadataToOffset(a_OldBlockMeta);
327  if (!cChunkDef::IsValidHeight(Base))
328  {
329  return;
330  }
331 
332  const auto Block = a_ChunkInterface.GetBlock(Base);
334  {
335  // Remove the base of the piston:
336  a_ChunkInterface.SetBlock(Base, E_BLOCK_AIR, 0);
337  }
338 }
339 
340 
341 
342 
343 
344 cItems cBlockPistonHeadHandler::ConvertToPickups(const NIBBLETYPE a_BlockMeta, const cItem * const a_Tool) const
345 {
346  // Give a normal\sticky piston base, not piston extension
347  // With 1.7, the item forms of these technical blocks have been removed, so giving someone this will crash their client...
348  return { cItem(((a_BlockMeta & 0x8) == 0x8) ? E_BLOCK_STICKY_PISTON : E_BLOCK_PISTON) };
349 }
#define PISTON_MAX_PUSH_DISTANCE
Definition: BlockPiston.cpp:15
@ E_BLOCK_STICKY_PISTON
Definition: BlockType.h:39
@ E_BLOCK_SLIME_BLOCK
Definition: BlockType.h:184
@ E_BLOCK_AIR
Definition: BlockType.h:10
@ E_BLOCK_PISTON_EXTENSION
Definition: BlockType.h:44
@ E_BLOCK_PISTON
Definition: BlockType.h:43
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 ASSERT(x)
Definition: Globals.h:276
#define UNUSED
Definition: Globals.h:72
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
#define LOGD
Definition: LoggerSimple.h:83
Vector3< int > Vector3i
Definition: Vector3.h:487
Utilities to allow casting a cWorld to one of its interfaces without including World....
Definition: OpaqueWorld.h:13
static bool IsPistonBreakable(BLOCKTYPE Block)
Can a piston break this block?
Definition: BlockInfo.cpp:750
virtual void OnBroken(cChunkInterface &a_ChunkInterface, cWorldInterface &a_WorldInterface, Vector3i a_BlockPos, BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta, const cEntity *a_Digger) const override
static const Byte PistonExtendAction
Piston extension block action.
Definition: BlockPiston.h:59
static Vector3i MetadataToOffset(NIBBLETYPE a_PistonMeta)
Converts piston block's metadata into a unit vector representing the direction in which the piston wi...
Definition: BlockPiston.cpp:21
static bool IsSticky(BLOCKTYPE a_BlockType)
Returns true if the piston (specified by blocktype) is a sticky piston.
Definition: BlockPiston.h:65
static void RetractPiston(Vector3i a_BlockPos, cWorld &a_World)
std::unordered_set< Vector3i, VectorHasher< int > > Vector3iSet
Definition: BlockPiston.h:56
static void ExtendPiston(Vector3i a_BlockPos, cWorld &a_World)
Definition: BlockPiston.cpp:44
static void PushBlocks(const Vector3iSet &a_BlocksToPush, cWorld &a_World, const Vector3i &a_PushDir)
Moves a list of blocks in a specific direction.
static bool CanPushBlock(const Vector3i &a_BlockPos, cWorld &a_World, bool a_RequirePushable, Vector3iSet &a_BlocksPushed, const Vector3i &a_PushDir)
Tries to push a block and increases the pushed blocks variable.
static const Byte PistonRetractAction
Piston retraction block action.
Definition: BlockPiston.h:62
static bool CanPush(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
Returns true if the specified block can be pushed by a piston (and left intact)
Definition: BlockPiston.h:68
static bool IsExtended(NIBBLETYPE a_PistonMeta)
Returns true if the piston (with the specified meta) is extended.
Definition: BlockPiston.h:52
virtual void OnBroken(cChunkInterface &a_ChunkInterface, cWorldInterface &a_WorldInterface, Vector3i a_BlockPos, BLOCKTYPE a_OldBlockType, NIBBLETYPE a_OldBlockMeta, const cEntity *a_Digger) const override
Called after a block gets broken (replaced with air), by natural means.
virtual cItems ConvertToPickups(NIBBLETYPE a_BlockMeta, const cItem *a_Tool) const override
Returns the pickups that would result if the block was mined by a_Digger using a_Tool.
void SetBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
Sets the block at the specified coords to the specified value.
BLOCKTYPE GetBlock(Vector3i a_Pos)
static bool IsValidHeight(Vector3i a_BlockPosition)
Validates a height-coordinate.
Definition: ChunkDef.h:185
Definition: Entity.h:76
Definition: Item.h:37
This class bridges a vector of cItem for safe access via Lua.
Definition: Item.h:215
Definition: World.h:53
void ScheduleTask(cTickTime a_DelayTicks, std::function< void(cWorld &)> a_Task)
Queues a lambda task onto the tick thread, with the specified delay.
Definition: World.cpp:2744
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
bool GetBlockTypeMeta(Vector3i a_BlockPos, BLOCKTYPE &a_BlockType, NIBBLETYPE &a_BlockMeta) const
Retrieves the block type and meta at the specified coords.
Definition: World.cpp:1779
virtual void BroadcastBlockAction(Vector3i a_BlockPos, Byte a_Byte1, Byte a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle *a_Exclude=nullptr) override
void SetBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
Sets the block at the specified coords to the specified value.
Definition: World.cpp:1743