the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2#include "PistonBaseTile.h"
3#include "PistonMovingPiece.h"
4#include "PistonPieceEntity.h"
5#include "PistonExtensionTile.h"
6#include "Facing.h"
7#include "net.minecraft.world.level.h"
8#include "..\Minecraft.Client\Minecraft.h"
9#include "..\Minecraft.Client\MultiPlayerLevel.h"
10#include "net.minecraft.world.h"
11#include "LevelChunk.h"
12#include "Dimension.h"
13
14const wstring PistonBaseTile::EDGE_TEX = L"piston_side";
15const wstring PistonBaseTile::PLATFORM_TEX = L"piston_top";
16const wstring PistonBaseTile::PLATFORM_STICKY_TEX = L"piston_top_sticky";
17const wstring PistonBaseTile::BACK_TEX = L"piston_bottom";
18const wstring PistonBaseTile::INSIDE_TEX = L"piston_inner_top";
19
20const float PistonBaseTile::PLATFORM_THICKNESS = 4.0f;
21
22DWORD PistonBaseTile::tlsIdx = TlsAlloc();
23
24// 4J - NOTE - this ignoreUpdate stuff has been removed from the java version, but I'm not currently sure how the java version does without it... there must be
25// some other mechanism that we don't have that stops the event from one piston being processed, from causing neighbours to have extra events created for them.
26// For us, that means that if we create a piston next to another one, then one of them gets two events to createPush, the second of which fails, leaving the
27// piston in a bad (simultaneously extended & not extended) state.
28// 4J - ignoreUpdate is a static in java, implementing as TLS here to make thread safe
29bool PistonBaseTile::ignoreUpdate()
30{
31 return (TlsGetValue(tlsIdx) != NULL);
32}
33
34void PistonBaseTile::ignoreUpdate(bool set)
35{
36 TlsSetValue(tlsIdx,(LPVOID)(set?1:0));
37}
38
39PistonBaseTile::PistonBaseTile(int id, bool isSticky) : Tile(id, Material::piston, isSolidRender() )
40{
41 // 4J - added initialiser
42 ignoreUpdate(false);
43
44 this->isSticky = isSticky;
45 setSoundType(SOUND_STONE);
46 setDestroyTime(0.5f);
47
48 iconInside = NULL;
49 iconBack = NULL;
50 iconPlatform = NULL;
51}
52
53Icon *PistonBaseTile::getPlatformTexture()
54{
55 return iconPlatform;
56}
57
58void PistonBaseTile::updateShape(float x0, float y0, float z0, float x1, float y1, float z1)
59{
60 setShape(x0, y0, z0, x1, y1, z1);
61}
62
63Icon *PistonBaseTile::getTexture(int face, int data)
64{
65 int facing = getFacing(data);
66
67 if (facing > 5)
68 {
69 return iconPlatform;
70 }
71
72 if (face == facing)
73 {
74 // sorry about this mess...
75 // when the piston is extended, either normally
76 // or because a piston arm animation, the top
77 // texture is the furnace bottom
78 ThreadStorage *tls = (ThreadStorage *)TlsGetValue(Tile::tlsIdxShape);
79 if (isExtended(data) || tls->xx0 > 0 || tls->yy0 > 0 || tls->zz0 > 0 || tls->xx1 < 1 || tls->yy1 < 1 || tls->zz1 < 1)
80 {
81 return iconInside;
82 }
83 return iconPlatform;
84 }
85 if (face == Facing::OPPOSITE_FACING[facing])
86 {
87 return iconBack;
88 }
89
90 return icon;
91}
92
93Icon *PistonBaseTile::getTexture(const wstring &name)
94{
95 if (name.compare(EDGE_TEX) == 0) return Tile::pistonBase->icon;
96 if (name.compare(PLATFORM_TEX) == 0) return Tile::pistonBase->iconPlatform;
97 if (name.compare(PLATFORM_STICKY_TEX) == 0) return Tile::pistonStickyBase->iconPlatform;
98 if (name.compare(INSIDE_TEX) == 0) return Tile::pistonBase->iconInside;
99
100 return NULL;
101}
102
103//@Override
104void PistonBaseTile::registerIcons(IconRegister *iconRegister)
105{
106 icon = iconRegister->registerIcon(EDGE_TEX);
107 iconPlatform = iconRegister->registerIcon(isSticky ? PLATFORM_STICKY_TEX : PLATFORM_TEX);
108 iconInside = iconRegister->registerIcon(INSIDE_TEX);
109 iconBack = iconRegister->registerIcon(BACK_TEX);
110}
111
112int PistonBaseTile::getRenderShape()
113{
114 return SHAPE_PISTON_BASE;
115}
116
117bool PistonBaseTile::isSolidRender(bool isServerLevel)
118{
119 return false;
120}
121
122bool PistonBaseTile::use(Level *level, int x, int y, int z, shared_ptr<Player> player, int clickedFace, float clickX, float clickY, float clickZ, bool soundOnly/*=false*/) // 4J added soundOnly param
123{
124 return false;
125}
126
127void PistonBaseTile::setPlacedBy(Level *level, int x, int y, int z, shared_ptr<LivingEntity> by, shared_ptr<ItemInstance> itemInstance)
128{
129 int targetData = getNewFacing(level, x, y, z, dynamic_pointer_cast<Player>(by) );
130 level->setData(x, y, z, targetData, Tile::UPDATE_CLIENTS);
131 if (!level->isClientSide && !ignoreUpdate())
132 {
133 checkIfExtend(level, x, y, z);
134 }
135}
136
137void PistonBaseTile::neighborChanged(Level *level, int x, int y, int z, int type)
138{
139 if (!level->isClientSide && !ignoreUpdate())
140 {
141 checkIfExtend(level, x, y, z);
142 }
143}
144
145void PistonBaseTile::onPlace(Level *level, int x, int y, int z)
146{
147 if (!level->isClientSide && level->getTileEntity(x, y, z) == NULL && !ignoreUpdate())
148 {
149 checkIfExtend(level, x, y, z);
150 }
151}
152
153void PistonBaseTile::checkIfExtend(Level *level, int x, int y, int z)
154{
155 int data = level->getData(x, y, z);
156 int facing = getFacing(data);
157
158 if (facing == UNDEFINED_FACING)
159 {
160 return;
161 }
162 bool extend = getNeighborSignal(level, x, y, z, facing);
163
164 if (extend && !isExtended(data))
165 {
166 if (canPush(level, x, y, z, facing))
167 {
168 level->tileEvent(x, y, z, id, TRIGGER_EXTEND, facing);
169 }
170 }
171 else if (!extend && isExtended(data))
172 {
173 level->setData(x, y, z, facing, UPDATE_CLIENTS);
174 level->tileEvent(x, y, z, id, TRIGGER_CONTRACT, facing);
175 }
176}
177
178/**
179* This method checks neighbor signals for this block and the block above,
180* and directly beneath. However, it avoids checking blocks that would be
181* pushed by this block.
182*
183* @param level
184* @param x
185* @param y
186* @param z
187* @return
188*/
189bool PistonBaseTile::getNeighborSignal(Level *level, int x, int y, int z, int facing)
190{
191 // check adjacent neighbors, but not in push direction
192 if (facing != Facing::DOWN && level->hasSignal(x, y - 1, z, Facing::DOWN)) return true;
193 if (facing != Facing::UP && level->hasSignal(x, y + 1, z, Facing::UP)) return true;
194 if (facing != Facing::NORTH && level->hasSignal(x, y, z - 1, Facing::NORTH)) return true;
195 if (facing != Facing::SOUTH && level->hasSignal(x, y, z + 1, Facing::SOUTH)) return true;
196 if (facing != Facing::EAST && level->hasSignal(x + 1, y, z, Facing::EAST)) return true;
197 if (facing != Facing::WEST && level->hasSignal(x - 1, y, z, Facing::WEST)) return true;
198
199 // check signals above
200 if (level->hasSignal(x, y, z, 0)) return true;
201 if (level->hasSignal(x, y + 2, z, 1)) return true;
202 if (level->hasSignal(x, y + 1, z - 1, 2)) return true;
203 if (level->hasSignal(x, y + 1, z + 1, 3)) return true;
204 if (level->hasSignal(x - 1, y + 1, z, 4)) return true;
205 if (level->hasSignal(x + 1, y + 1, z, 5)) return true;
206
207 return false;
208}
209
210bool PistonBaseTile::triggerEvent(Level *level, int x, int y, int z, int param1, int facing)
211{
212 ignoreUpdate(true);
213
214 if (!level->isClientSide)
215 {
216 bool extend = getNeighborSignal(level, x, y, z, facing);
217
218 if (extend && param1 == TRIGGER_CONTRACT)
219 {
220 level->setData(x, y, z, facing | EXTENDED_BIT, UPDATE_CLIENTS);
221 return false;
222 }
223 else if (!extend && param1 == TRIGGER_EXTEND)
224 {
225 return false;
226 }
227 }
228
229 if (param1 == TRIGGER_EXTEND)
230 {
231 PIXBeginNamedEvent(0,"Create push\n");
232 if (createPush(level, x, y, z, facing))
233 {
234 // 4J - it is (currently) critical that this setData sends data to the client, so have added a bool to the method so that it sends data even if the data was already set to the same value
235 // as before, which was actually its behaviour until a change in 1.0.1 meant that setData only conditionally sent updates to listeners. If the data update Isn't sent, then what
236 // can happen is:
237 // (1) the host sends the tile event to the client
238 // (2) the client gets the tile event, and sets the tile/data value locally.
239 // (3) just before setting the tile/data locally, the client will put the old value in the vector of things to be restored should an update not be received back from the host
240 // (4) we don't get any update of the tile from the host, and so the old value gets restored on the client
241 // (5) the piston base ends up being restored to its retracted state whilst the piston arm is extended
242 // We really need to spend some time investigating a better way for pistons to work as it all seems a bit scary how the host/client interact, but forcing this to send should at least
243 // restore the behaviour of the pistons to something closer to what they were before the 1.0.1 update. By sending this data update, then (4) in the list above doesn't happen
244 // because the client does actually receive an update for this tile from the host after the event has been processed on the cient.
245 level->setData(x, y, z, facing | EXTENDED_BIT, Tile::UPDATE_CLIENTS, true);
246 level->playSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_TILE_PISTON_OUT, 0.5f, level->random->nextFloat() * 0.25f + 0.6f);
247 }
248 else
249 {
250 return false;
251 }
252 PIXEndNamedEvent();
253 }
254 else if (param1 == TRIGGER_CONTRACT)
255 {
256 PIXBeginNamedEvent(0,"Contract phase A\n");
257 shared_ptr<TileEntity> prevTileEntity = level->getTileEntity(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
258 if (prevTileEntity != NULL && dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity) != NULL)
259 {
260 dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity)->finalTick();
261 }
262
263 stopSharingIfServer(level, x, y, z); // 4J added
264 level->setTileAndData(x, y, z, Tile::pistonMovingPiece_Id, facing, Tile::UPDATE_ALL);
265 level->setTileEntity(x, y, z, PistonMovingPiece::newMovingPieceEntity(id, facing, facing, false, true));
266
267 PIXEndNamedEvent();
268
269 // sticky movement
270 if (isSticky)
271 {
272 PIXBeginNamedEvent(0,"Contract sticky phase A\n");
273 int twoX = x + Facing::STEP_X[facing] * 2;
274 int twoY = y + Facing::STEP_Y[facing] * 2;
275 int twoZ = z + Facing::STEP_Z[facing] * 2;
276 int block = level->getTile(twoX, twoY, twoZ);
277 int blockData = level->getData(twoX, twoY, twoZ);
278 bool pistonPiece = false;
279
280 PIXEndNamedEvent();
281
282 if (block == Tile::pistonMovingPiece_Id)
283 {
284 PIXBeginNamedEvent(0,"Contract sticky phase B\n");
285 // the block two steps away is a moving piston block piece, so replace it with the real data,
286 // since it's probably this piston which is changing too fast
287 shared_ptr<TileEntity> tileEntity = level->getTileEntity(twoX, twoY, twoZ);
288 if (tileEntity != NULL && dynamic_pointer_cast<PistonPieceEntity>(tileEntity) != NULL )
289 {
290 shared_ptr<PistonPieceEntity> ppe = dynamic_pointer_cast<PistonPieceEntity>(tileEntity);
291
292 if (ppe->getFacing() == facing && ppe->isExtending())
293 {
294 // force the tile to air before pushing
295 ppe->finalTick();
296 block = ppe->getId();
297 blockData = ppe->getData();
298 pistonPiece = true;
299 }
300 }
301 PIXEndNamedEvent();
302 }
303
304 PIXBeginNamedEvent(0,"Contract sticky phase C\n");
305 if (!pistonPiece && block > 0 && (isPushable(block, level, twoX, twoY, twoZ, false))
306 && (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_NORMAL || block == Tile::pistonBase_Id || block == Tile::pistonStickyBase_Id))
307 {
308 stopSharingIfServer(level, twoX, twoY, twoZ); // 4J added
309
310 x += Facing::STEP_X[facing];
311 y += Facing::STEP_Y[facing];
312 z += Facing::STEP_Z[facing];
313
314 level->setTileAndData(x, y, z, Tile::pistonMovingPiece_Id, blockData, Tile::UPDATE_ALL);
315 level->setTileEntity(x, y, z, PistonMovingPiece::newMovingPieceEntity(block, blockData, facing, false, false));
316
317 ignoreUpdate(false);
318 level->removeTile(twoX, twoY, twoZ);
319 ignoreUpdate(true);
320 }
321 else if (!pistonPiece)
322 {
323 stopSharingIfServer(level, x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); // 4J added
324 ignoreUpdate(false);
325 level->removeTile(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
326 ignoreUpdate(true);
327 }
328 PIXEndNamedEvent();
329 }
330 else
331 {
332 stopSharingIfServer(level, x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); // 4J added
333 ignoreUpdate(false);
334 level->removeTile(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
335 ignoreUpdate(true);
336 }
337
338 level->playSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_TILE_PISTON_IN, 0.5f, level->random->nextFloat() * 0.15f + 0.6f);
339 }
340
341 ignoreUpdate(false);
342
343 return true;
344}
345
346void PistonBaseTile::updateShape(LevelSource *level, int x, int y, int z, int forceData, shared_ptr<TileEntity> forceEntity) // 4J added forceData, forceEntity param
347{
348 int data = (forceData == -1 ) ? level->getData(x, y, z) : forceData;
349
350 if (isExtended(data))
351 {
352 const float thickness = PLATFORM_THICKNESS / 16.0f;
353 switch (getFacing(data))
354 {
355 case Facing::DOWN:
356 setShape(0, thickness, 0, 1, 1, 1);
357 break;
358 case Facing::UP:
359 setShape(0, 0, 0, 1, 1 - thickness, 1);
360 break;
361 case Facing::NORTH:
362 setShape(0, 0, thickness, 1, 1, 1);
363 break;
364 case Facing::SOUTH:
365 setShape(0, 0, 0, 1, 1, 1 - thickness);
366 break;
367 case Facing::WEST:
368 setShape(thickness, 0, 0, 1, 1, 1);
369 break;
370 case Facing::EAST:
371 setShape(0, 0, 0, 1 - thickness, 1, 1);
372 break;
373 }
374 }
375 else
376 {
377 setShape(0, 0, 0, 1, 1, 1);
378 }
379}
380
381void PistonBaseTile::updateDefaultShape()
382{
383 setShape(0, 0, 0, 1, 1, 1);
384}
385
386void PistonBaseTile::addAABBs(Level *level, int x, int y, int z, AABB *box, AABBList *boxes, shared_ptr<Entity> source)
387{
388 setShape(0, 0, 0, 1, 1, 1);
389 Tile::addAABBs(level, x, y, z, box, boxes, source);
390}
391
392AABB *PistonBaseTile::getAABB(Level *level, int x, int y, int z)
393{
394 updateShape(level, x, y, z);
395 return Tile::getAABB(level, x, y, z);
396}
397
398bool PistonBaseTile::isCubeShaped()
399{
400 return false;
401}
402
403int PistonBaseTile::getFacing(int data)
404{
405 return data & 0x7;
406}
407
408bool PistonBaseTile::isExtended(int data)
409{
410 return (data & EXTENDED_BIT) != 0;
411}
412
413int PistonBaseTile::getNewFacing(Level *level, int x, int y, int z, shared_ptr<LivingEntity> player)
414{
415 if (Mth::abs((float) player->x - x) < 2 && Mth::abs((float) player->z - z) < 2)
416 {
417 // If the player is above the block, the slot is on the top
418 double py = player->y + 1.82 - player->heightOffset;
419 if (py - y > 2)
420 {
421 return Facing::UP;
422 }
423 // If the player is below the block, the slot is on the bottom
424 if (y - py > 0)
425 {
426 return Facing::DOWN;
427 }
428 }
429 // The slot is on the side
430 int i = Mth::floor(player->yRot * 4.0f / 360.0f + 0.5) & 0x3;
431 if (i == 0) return Facing::NORTH;
432 if (i == 1) return Facing::EAST;
433 if (i == 2) return Facing::SOUTH;
434 if (i == 3) return Facing::WEST;
435 return 0;
436}
437
438bool PistonBaseTile::isPushable(int block, Level *level, int cx, int cy, int cz, bool allowDestroyable)
439{
440 // special case for obsidian
441 if (block == Tile::obsidian_Id)
442 {
443 return false;
444 }
445
446 if (block == Tile::pistonBase_Id || block == Tile::pistonStickyBase_Id)
447 {
448 // special case for piston bases
449 if (isExtended(level->getData(cx, cy, cz)))
450 {
451 return false;
452 }
453 }
454 else
455 {
456 if (Tile::tiles[block]->getDestroySpeed(level, cx, cy, cz) == Tile::INDESTRUCTIBLE_DESTROY_TIME)
457 {
458 return false;
459 }
460
461 if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_BLOCK)
462 {
463 return false;
464 }
465
466 if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY)
467 {
468 if(!allowDestroyable)
469 {
470 return false;
471 }
472 return true;
473 }
474 }
475
476 if( Tile::tiles[block]->isEntityTile() ) // 4J - java uses instanceof EntityTile here
477 {
478 // may not push tile entities
479 return false;
480 }
481
482 return true;
483}
484
485bool PistonBaseTile::canPush(Level *level, int sx, int sy, int sz, int facing)
486{
487 int cx = sx + Facing::STEP_X[facing];
488 int cy = sy + Facing::STEP_Y[facing];
489 int cz = sz + Facing::STEP_Z[facing];
490
491 for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++)
492 {
493
494 if (cy <= 0 || cy >= (Level::maxBuildHeight - 1))
495 {
496 // out of bounds
497 return false;
498 }
499
500 // 4J - added to also check for out of bounds in x/z for our finite world
501 int minXZ = - (level->dimension->getXZSize() * 16 ) / 2;
502 int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1;
503 if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) )
504 {
505 return false;
506 }
507 int block = level->getTile(cx, cy, cz);
508 if (block == 0)
509 {
510 break;
511 }
512
513 if (!isPushable(block, level, cx, cy, cz, true))
514 {
515 return false;
516 }
517
518 if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY)
519 {
520 break;
521 }
522
523 if (i == MAX_PUSH_DEPTH)
524 {
525 // we've reached the maximum push depth
526 // without finding air or a breakable block
527 return false;
528 }
529
530 cx += Facing::STEP_X[facing];
531 cy += Facing::STEP_Y[facing];
532 cz += Facing::STEP_Z[facing];
533 }
534
535 return true;
536
537}
538
539void PistonBaseTile::stopSharingIfServer(Level *level, int x, int y, int z)
540{
541 if( !level->isClientSide )
542 {
543 MultiPlayerLevel *clientLevel = Minecraft::GetInstance()->getLevel(level->dimension->id);
544 if( clientLevel )
545 {
546 LevelChunk *lc = clientLevel->getChunkAt( x, z );
547 lc->stopSharingTilesAndData();
548 }
549 }
550}
551
552bool PistonBaseTile::createPush(Level *level, int sx, int sy, int sz, int facing)
553{
554 int cx = sx + Facing::STEP_X[facing];
555 int cy = sy + Facing::STEP_Y[facing];
556 int cz = sz + Facing::STEP_Z[facing];
557
558 for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++)
559 {
560 if (cy <= 0 || cy >= (Level::maxBuildHeight - 1))
561 {
562 // out of bounds
563 return false;
564 }
565
566 // 4J - added to also check for out of bounds in x/z for our finite world
567 int minXZ = - (level->dimension->getXZSize() * 16 ) / 2;
568 int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1;
569 if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) )
570 {
571 return false;
572 }
573
574 int block = level->getTile(cx, cy, cz);
575 if (block == 0)
576 {
577 break;
578 }
579
580 if (!isPushable(block, level, cx, cy, cz, true))
581 {
582 return false;
583 }
584
585 if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY)
586 {
587 // this block is destroyed when pushed
588 Tile::tiles[block]->spawnResources(level, cx, cy, cz, level->getData(cx, cy, cz), 0);
589 // setting the tile to air is actually superflous, but helps vs multiplayer problems
590 stopSharingIfServer(level, cx, cy, cz); // 4J added
591 level->removeTile(cx, cy, cz);
592 break;
593 }
594
595 if (i == MAX_PUSH_DEPTH)
596 {
597 // we've reached the maximum push depth without finding air or a breakable block
598 return false;
599 }
600
601 cx += Facing::STEP_X[facing];
602 cy += Facing::STEP_Y[facing];
603 cz += Facing::STEP_Z[facing];
604 }
605
606 int ex = cx;
607 int ey = cy;
608 int ez = cz;
609 int count = 0;
610 int tiles[MAX_PUSH_DEPTH + 1];
611
612 while (cx != sx || cy != sy || cz != sz)
613 {
614
615 int nx = cx - Facing::STEP_X[facing];
616 int ny = cy - Facing::STEP_Y[facing];
617 int nz = cz - Facing::STEP_Z[facing];
618
619 int block = level->getTile(nx, ny, nz);
620 int data = level->getData(nx, ny, nz);
621
622 stopSharingIfServer(level, cx, cy, cz); // 4J added
623
624 if (block == id && nx == sx && ny == sy && nz == sz)
625 {
626 level->setTileAndData(cx, cy, cz, Tile::pistonMovingPiece_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), Tile::UPDATE_NONE);
627 level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(Tile::pistonExtensionPiece_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), facing, true, false));
628 }
629 else
630 {
631 level->setTileAndData(cx, cy, cz, Tile::pistonMovingPiece_Id, data, Tile::UPDATE_NONE);
632 level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(block, data, facing, true, false));
633 }
634 tiles[count++] = block;
635
636 cx = nx;
637 cy = ny;
638 cz = nz;
639 }
640
641 cx = ex;
642 cy = ey;
643 cz = ez;
644 count = 0;
645
646 while (cx != sx || cy != sy || cz != sz)
647 {
648 int nx = cx - Facing::STEP_X[facing];
649 int ny = cy - Facing::STEP_Y[facing];
650 int nz = cz - Facing::STEP_Z[facing];
651
652 level->updateNeighborsAt(nx, ny, nz, tiles[count++]);
653
654 cx = nx;
655 cy = ny;
656 cz = nz;
657 }
658
659 return true;
660
661}