Files
neoLegacy/Minecraft.World/PistonBaseTile.cpp
2026-06-25 17:13:47 -04:00

803 lines
24 KiB
C++

#include "stdafx.h"
#include "PistonBaseTile.h"
#include "PistonMovingPiece.h"
#include "PistonPieceEntity.h"
#include "PistonExtensionTile.h"
#include "Facing.h"
#include "net.minecraft.world.level.h"
#include "../Minecraft.Client/Minecraft.h"
#include "../Minecraft.Client/MultiPlayerLevel.h"
#include "net.minecraft.world.h"
#include "LevelChunk.h"
#include "Dimension.h"
#include <algorithm>
#include <deque>
#include <climits>
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "../Minecraft.Server/FourKitBridge.h"
#endif
const wstring PistonBaseTile::EDGE_TEX = L"piston_side";
const wstring PistonBaseTile::PLATFORM_TEX = L"piston_top";
const wstring PistonBaseTile::PLATFORM_STICKY_TEX = L"piston_top_sticky";
const wstring PistonBaseTile::BACK_TEX = L"piston_bottom";
const wstring PistonBaseTile::INSIDE_TEX = L"piston_inner_top";
const float PistonBaseTile::PLATFORM_THICKNESS = 4.0f;
DWORD PistonBaseTile::tlsIdx = TlsAlloc();
// 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
// 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.
// 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
// piston in a bad (simultaneously extended & not extended) state.
// 4J - ignoreUpdate is a static in java, implementing as TLS here to make thread safe
//I removed the code for ignoreUpdate so the above comment no longer applies ^.^
PistonBaseTile::PistonBaseTile(int id, bool isSticky) : Tile(id, Material::piston, isSolidRender() )
{
this->isSticky = isSticky;
setSoundType(SOUND_STONE);
setDestroyTime(0.5f);
iconInside = nullptr;
iconBack = nullptr;
iconPlatform = nullptr;
}
void PistonBaseTile::createBlockStateDefinition()
{
if (!m_blockStateDefinition)
m_blockStateDefinition = new BlockStateDefinition(this);
}
int PistonBaseTile::defaultBlockState()
{
return 0;
}
int PistonBaseTile::convertBlockStateToLegacyData(BlockState *state)
{
return state ? (state->value & 0xF) : 0;
}
Tile::BlockState PistonBaseTile::getBlockState(int data)
{
return Tile::BlockState(data & 0xF);
}
Tile::BlockState PistonBaseTile::getBlockState(LevelSource *level, int x, int y, int z)
{
return Tile::BlockState(level->getData(x, y, z) & 0xF);
}
Icon *PistonBaseTile::getPlatformTexture()
{
return iconPlatform;
}
void PistonBaseTile::updateShape(float x0, float y0, float z0, float x1, float y1, float z1)
{
setShape(x0, y0, z0, x1, y1, z1);
}
Icon *PistonBaseTile::getTexture(int face, int data)
{
int facing = getFacing(data);
if (facing > 5)
{
return iconPlatform;
}
if (face == facing)
{
// sorry about this mess...
// when the piston is extended, either normally
// or because a piston arm animation, the top
// texture is the furnace bottom
ThreadStorage *tls = static_cast<ThreadStorage *>(TlsGetValue(Tile::tlsIdxShape));
if (isExtended(data) || tls->xx0 > 0 || tls->yy0 > 0 || tls->zz0 > 0 || tls->xx1 < 1 || tls->yy1 < 1 || tls->zz1 < 1)
{
return iconInside;
}
return iconPlatform;
}
if (face == Facing::OPPOSITE_FACING[facing])
{
return iconBack;
}
return icon;
}
Icon *PistonBaseTile::getTexture(const wstring &name)
{
if (name.compare(EDGE_TEX) == 0) return Tile::pistonBase->icon;
if (name.compare(PLATFORM_TEX) == 0) return Tile::pistonBase->iconPlatform;
if (name.compare(PLATFORM_STICKY_TEX) == 0) return Tile::sticky_piston->iconPlatform;
if (name.compare(INSIDE_TEX) == 0) return Tile::pistonBase->iconInside;
return nullptr;
}
//@Override
void PistonBaseTile::registerIcons(IconRegister *iconRegister)
{
icon = iconRegister->registerIcon(EDGE_TEX);
iconPlatform = iconRegister->registerIcon(isSticky ? PLATFORM_STICKY_TEX : PLATFORM_TEX);
iconInside = iconRegister->registerIcon(INSIDE_TEX);
iconBack = iconRegister->registerIcon(BACK_TEX);
}
int PistonBaseTile::getRenderShape()
{
return SHAPE_PISTON_BASE;
}
bool PistonBaseTile::isSolidRender(bool isServerLevel)
{
return false;
}
bool 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
{
return false;
}
void PistonBaseTile::setPlacedBy(Level *level, int x, int y, int z, shared_ptr<LivingEntity> by, shared_ptr<ItemInstance> itemInstance)
{
int targetData = getNewFacing(level, x, y, z, dynamic_pointer_cast<Player>(by) );
level->setData(x, y, z, targetData, Tile::UPDATE_CLIENTS);
if (!level->isClientSide)
{
checkIfExtend(level, x, y, z);
}
}
void PistonBaseTile::neighborChanged(Level *level, int x, int y, int z, int type)
{
if (!level->isClientSide)
{
checkIfExtend(level, x, y, z);
}
}
void PistonBaseTile::onPlace(Level *level, int x, int y, int z)
{
if (!level->isClientSide && level->getTileEntity(x, y, z) == nullptr)
{
checkIfExtend(level, x, y, z);
}
}
void PistonBaseTile::checkIfExtend(Level *level, int x, int y, int z)
{
int data = level->getData(x, y, z);
int facing = getFacing(data);
if (facing == UNDEFINED_FACING)
{
return;
}
bool extend = getNeighborSignal(level, x, y, z, facing);
if (extend && !isExtended(data))
{
if (canPush(level, x, y, z, facing))
{
level->tileEvent(x, y, z, id, TRIGGER_EXTEND, facing);
}
}
else if (!extend && isExtended(data))
{
// level->setData(x, y, z, facing, UPDATE_CLIENTS);
level->tileEvent(x, y, z, id, TRIGGER_CONTRACT, facing);
}
}
/**
* This method checks neighbor signals for this block and the block above,
* and directly beneath. However, it avoids checking blocks that would be
* pushed by this block.
*
* @param level
* @param x
* @param y
* @param z
* @return
*/
bool PistonBaseTile::getNeighborSignal(Level *level, int x, int y, int z, int facing)
{
// check adjacent neighbors, but not in push direction
if (facing != Facing::DOWN && level->hasSignal(x, y - 1, z, Facing::DOWN)) return true;
if (facing != Facing::UP && level->hasSignal(x, y + 1, z, Facing::UP)) return true;
if (facing != Facing::NORTH && level->hasSignal(x, y, z - 1, Facing::NORTH)) return true;
if (facing != Facing::SOUTH && level->hasSignal(x, y, z + 1, Facing::SOUTH)) return true;
if (facing != Facing::EAST && level->hasSignal(x + 1, y, z, Facing::EAST)) return true;
if (facing != Facing::WEST && level->hasSignal(x - 1, y, z, Facing::WEST)) return true;
// check signals above
if (level->hasSignal(x, y, z, 0)) return true;
if (level->hasSignal(x, y + 2, z, 1)) return true;
if (level->hasSignal(x, y + 1, z - 1, 2)) return true;
if (level->hasSignal(x, y + 1, z + 1, 3)) return true;
if (level->hasSignal(x - 1, y + 1, z, 4)) return true;
if (level->hasSignal(x + 1, y + 1, z, 5)) return true;
return false;
}
bool PistonBaseTile::triggerEvent(Level *level, int x, int y, int z, int param1, int facing)
{
if (!level->isClientSide)
{
bool extend = getNeighborSignal(level, x, y, z, facing);
if (extend && param1 == TRIGGER_CONTRACT)
{
level->setData(x, y, z, facing | EXTENDED_BIT, UPDATE_CLIENTS);
return false;
}
else if (!extend && param1 == TRIGGER_EXTEND)
{
return false;
}
}
if (param1 == TRIGGER_EXTEND)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int pushLength = 0;
int cx = x + Facing::STEP_X[facing];
int cy = y + Facing::STEP_Y[facing];
int cz = z + Facing::STEP_Z[facing];
for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++)
{
int block = level->getTile(cx, cy, cz);
if (block == 0) break;
if (!isPushable(block, level, cx, cy, cz, true)) break;
if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY) { pushLength++; break; }
pushLength++;
if (i == MAX_PUSH_DEPTH) break;
cx += Facing::STEP_X[facing];
cy += Facing::STEP_Y[facing];
cz += Facing::STEP_Z[facing];
}
if (FourKitBridge::FirePistonExtend(level->dimension->id, x, y, z, facing, pushLength))
{
return false;
}
}
#endif
PIXBeginNamedEvent(0,"Create push\n");
if (createPush(level, x, y, z, facing))
{
// 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
// 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
// can happen is:
// (1) the host sends the tile event to the client
// (2) the client gets the tile event, and sets the tile/data value locally.
// (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
// (4) we don't get any update of the tile from the host, and so the old value gets restored on the client
// (5) the piston base ends up being restored to its retracted state whilst the piston arm is extended
// 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
// 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
// because the client does actually receive an update for this tile from the host after the event has been processed on the cient.
level->setData(x, y, z, facing | EXTENDED_BIT, Tile::UPDATE_CLIENTS, true);
level->playSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_TILE_PISTON_OUT, 0.5f, level->random->nextFloat() * 0.25f + 0.6f);
}
else
{
return false;
}
PIXEndNamedEvent();
}
else if (param1 == TRIGGER_CONTRACT)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FirePistonRetract(level->dimension->id, x, y, z, facing))
{
level->setData(x, y, z, facing | EXTENDED_BIT, UPDATE_CLIENTS);
return false;
}
#endif
PIXBeginNamedEvent(0,"Contract phase A\n");
shared_ptr<TileEntity> prevTileEntity = level->getTileEntity(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
if (prevTileEntity != nullptr && dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity) != nullptr)
{
dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity)->finalTick();
}
stopSharingIfServer(level, x, y, z); // 4J added
level->setTileAndData(x, y, z, Tile::piston_extension_Id, facing, Tile::UPDATE_ALL);
level->setTileEntity(x, y, z, PistonMovingPiece::newMovingPieceEntity(id, facing, facing, false, true));
PIXEndNamedEvent();
// sticky movement
if (isSticky)
{
PIXBeginNamedEvent(0,"Contract sticky phase A\n");
int twoX = x + Facing::STEP_X[facing] * 2;
int twoY = y + Facing::STEP_Y[facing] * 2;
int twoZ = z + Facing::STEP_Z[facing] * 2;
int block = level->getTile(twoX, twoY, twoZ);
int blockData = level->getData(twoX, twoY, twoZ);
bool pistonPiece = false;
PIXEndNamedEvent();
if (block == Tile::piston_extension_Id)
{
PIXBeginNamedEvent(0,"Contract sticky phase B\n");
// the block two steps away is a moving piston block piece, so replace it with the real data,
// since it's probably this piston which is changing too fast
shared_ptr<TileEntity> tileEntity = level->getTileEntity(twoX, twoY, twoZ);
if (tileEntity != nullptr && dynamic_pointer_cast<PistonPieceEntity>(tileEntity) != nullptr )
{
shared_ptr<PistonPieceEntity> ppe = dynamic_pointer_cast<PistonPieceEntity>(tileEntity);
if (ppe->getFacing() == facing && ppe->isExtending())
{
// force the tile to air before pushing
ppe->finalTick();
block = ppe->getId();
blockData = ppe->getData();
pistonPiece = true;
}
}
PIXEndNamedEvent();
}
PIXBeginNamedEvent(0,"Contract sticky phase C\n");
{
int pullDir = Facing::OPPOSITE_FACING[facing];
int armX = x + Facing::STEP_X[facing];
int armY = y + Facing::STEP_Y[facing];
int armZ = z + Facing::STEP_Z[facing];
bool canPull = block > 0
&& isPushable(block, level, twoX, twoY, twoZ, false)
&& (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_NORMAL
|| Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_SLIME
|| block == Tile::piston_Id
|| block == Tile::sticky_piston_Id);
if (canPull)
{
std::vector<BlockPos3> toMove, toDestroy;
bool ok = collectStructure(level, pullDir,
twoX, twoY, twoZ,
x, y, z,
armX, armY, armZ,
false,
toMove, toDestroy);
if (ok && !toMove.empty())
{
applyStructureMove(level, x, y, z, facing, pullDir, isSticky,
toMove, toDestroy, false);
}
else if (block > 0 && Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_SLIME)
{
std::vector<BlockPos3> fallbackMove;
std::vector<BlockPos3> fallbackDestroy;
bool fallbackOk = collectStructure(level, pullDir,
twoX, twoY, twoZ,
x, y, z,
armX, armY, armZ,
true,
fallbackMove, fallbackDestroy);
if (fallbackOk && !fallbackMove.empty())
{
applyStructureMove(level, x, y, z, facing, pullDir, isSticky,
fallbackMove, fallbackDestroy, false);
}
else
{
stopSharingIfServer(level, armX, armY, armZ);
level->removeTile(armX, armY, armZ);
}
}
else
{
stopSharingIfServer(level, armX, armY, armZ);
level->removeTile(armX, armY, armZ);
}
}
else
{
stopSharingIfServer(level, armX, armY, armZ);
level->removeTile(armX, armY, armZ);
}
}
PIXEndNamedEvent();
}
else
{
stopSharingIfServer(level, x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); // 4J added
level->removeTile(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
}
level->playSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_TILE_PISTON_IN, 0.5f, level->random->nextFloat() * 0.15f + 0.6f);
}
return true;
}
void PistonBaseTile::updateShape(LevelSource *level, int x, int y, int z, int forceData, shared_ptr<TileEntity> forceEntity) // 4J added forceData, forceEntity param
{
int data = (forceData == -1 ) ? level->getData(x, y, z) : forceData;
if (isExtended(data))
{
const float thickness = PLATFORM_THICKNESS / 16.0f;
switch (getFacing(data))
{
case Facing::DOWN:
setShape(0, thickness, 0, 1, 1, 1);
break;
case Facing::UP:
setShape(0, 0, 0, 1, 1 - thickness, 1);
break;
case Facing::NORTH:
setShape(0, 0, thickness, 1, 1, 1);
break;
case Facing::SOUTH:
setShape(0, 0, 0, 1, 1, 1 - thickness);
break;
case Facing::WEST:
setShape(thickness, 0, 0, 1, 1, 1);
break;
case Facing::EAST:
setShape(0, 0, 0, 1 - thickness, 1, 1);
break;
}
}
else
{
setShape(0, 0, 0, 1, 1, 1);
}
}
void PistonBaseTile::updateDefaultShape()
{
setShape(0, 0, 0, 1, 1, 1);
}
void PistonBaseTile::addAABBs(Level *level, int x, int y, int z, AABB *box, AABBList *boxes, shared_ptr<Entity> source)
{
setShape(0, 0, 0, 1, 1, 1);
Tile::addAABBs(level, x, y, z, box, boxes, source);
}
AABB *PistonBaseTile::getAABB(Level *level, int x, int y, int z)
{
updateShape(level, x, y, z);
return Tile::getAABB(level, x, y, z);
}
bool PistonBaseTile::isCubeShaped()
{
return false;
}
int PistonBaseTile::getFacing(int data)
{
return data & 0x7;
}
bool PistonBaseTile::isExtended(int data)
{
return (data & EXTENDED_BIT) != 0;
}
int PistonBaseTile::getNewFacing(Level *level, int x, int y, int z, shared_ptr<LivingEntity> player)
{
if (Mth::abs(static_cast<float>(player->x) - x) < 2 && Mth::abs(static_cast<float>(player->z) - z) < 2)
{
// If the player is above the block, the slot is on the top
double py = player->y + 1.82 - player->heightOffset;
if (py - y > 2)
{
return Facing::UP;
}
// If the player is below the block, the slot is on the bottom
if (y - py > 0)
{
return Facing::DOWN;
}
}
// The slot is on the side
int i = Mth::floor(player->yRot * 4.0f / 360.0f + 0.5) & 0x3;
if (i == 0) return Facing::NORTH;
if (i == 1) return Facing::EAST;
if (i == 2) return Facing::SOUTH;
if (i == 3) return Facing::WEST;
return 0;
}
bool PistonBaseTile::isPushable(int block, Level *level, int cx, int cy, int cz, bool allowDestroyable)
{
Tile *tile = Tile::tiles[block];
if (tile == nullptr) return false; // tu31 tutorial world fix
// special case for obsidian
if (block == Tile::obsidian_Id)
{
return false;
}
if (block == Tile::piston_Id || block == Tile::sticky_piston_Id)
{
// special case for piston bases
if (isExtended(level->getData(cx, cy, cz)))
{
return false;
}
}
else
{
if (tile->getDestroySpeed(level, cx, cy, cz) == Tile::INDESTRUCTIBLE_DESTROY_TIME)
{
return false;
}
if (tile->getPistonPushReaction() == Material::PUSH_BLOCK)
{
return false;
}
if (tile->getPistonPushReaction() == Material::PUSH_DESTROY)
{
if(!allowDestroyable)
{
return false;
}
return true;
}
}
if( tile->isEntityTile() ) // 4J - java uses instanceof EntityTile here
{
// may not push tile entities
return false;
}
return true;
}
bool PistonBaseTile::canPush(Level *level, int sx, int sy, int sz, int facing)
{
std::vector<BlockPos3> toMove, toDestroy;
return collectStructure(level, facing,
sx + Facing::STEP_X[facing], sy + Facing::STEP_Y[facing], sz + Facing::STEP_Z[facing],
sx, sy, sz,
INT_MIN, 0, 0,
true,
toMove, toDestroy);
}
bool PistonBaseTile::collectStructure(Level *level, int moveDir,
int startX, int startY, int startZ,
int skipX, int skipY, int skipZ,
int skip2X, int skip2Y, int skip2Z,
bool allowDestroy,
std::vector<BlockPos3> &toMove,
std::vector<BlockPos3> &toDestroy)
{
std::deque<BlockPos3> queue;
std::vector<BlockPos3> visited;
auto hasVisited = [&](int x, int y, int z) -> bool {
for (auto &v : visited) if (v.x == x && v.y == y && v.z == z) return true;
return false;
};
auto isSkipped = [&](int x, int y, int z) -> bool {
if (x == skipX && y == skipY && z == skipZ) return true;
if (x == skip2X && y == skip2Y && z == skip2Z) return true;
return false;
};
queue.push_back({startX, startY, startZ});
while (!queue.empty())
{
BlockPos3 pos = queue.front();
queue.pop_front();
if (hasVisited(pos.x, pos.y, pos.z)) continue;
if (isSkipped(pos.x, pos.y, pos.z)) continue;
visited.push_back(pos);
if (pos.y <= 0 || pos.y >= (Level::maxBuildHeight - 1)) return false;
int minXZ = -(level->dimension->getXZSize() * 16) / 2;
int maxXZ = (level->dimension->getXZSize() * 16) / 2 - 1;
if (pos.x <= minXZ || pos.x >= maxXZ || pos.z <= minXZ || pos.z >= maxXZ) return false;
int block = level->getTile(pos.x, pos.y, pos.z);
if (block == 0) continue;
if (!isPushable(block, level, pos.x, pos.y, pos.z, allowDestroy)) return false;
int reaction = Tile::tiles[block]->getPistonPushReaction();
if (reaction == Material::PUSH_DESTROY)
{
toDestroy.push_back(pos);
continue;
}
if ((int)toMove.size() >= MAX_PUSH_DEPTH) return false;
toMove.push_back(pos);
queue.push_back({pos.x + Facing::STEP_X[moveDir],
pos.y + Facing::STEP_Y[moveDir],
pos.z + Facing::STEP_Z[moveDir]});
if (reaction == Material::PUSH_SLIME)
{
for (int d = 0; d < 6; d++)
{
if (d == moveDir) continue;
int nx = pos.x + Facing::STEP_X[d];
int ny = pos.y + Facing::STEP_Y[d];
int nz = pos.z + Facing::STEP_Z[d];
int neighborBlock = level->getTile(nx, ny, nz);
if (neighborBlock == 0) continue;
if (!isPushable(neighborBlock, level, nx, ny, nz, false)) continue;
queue.push_back({nx, ny, nz});
}
}
}
return true;
}
void PistonBaseTile::applyStructureMove(Level *level,
int pistonX, int pistonY, int pistonZ,
int facing, int moveDir, bool sticky,
std::vector<BlockPos3> &toMove,
std::vector<BlockPos3> &toDestroy,
bool isExtension)
{
for (auto &dpos : toDestroy)
{
int block = level->getTile(dpos.x, dpos.y, dpos.z);
if (block > 0)
{
Tile::tiles[block]->spawnResources(level, dpos.x, dpos.y, dpos.z, level->getData(dpos.x, dpos.y, dpos.z), 0);
stopSharingIfServer(level, dpos.x, dpos.y, dpos.z);
level->removeTile(dpos.x, dpos.y, dpos.z);
}
}
int n = (int)toMove.size();
int armX = pistonX + Facing::STEP_X[facing];
int armY = pistonY + Facing::STEP_Y[facing];
int armZ = pistonZ + Facing::STEP_Z[facing];
if (n == 0)
{
if (isExtension)
{
int armData = facing | (sticky ? PistonExtensionTile::STICKY_BIT : 0);
stopSharingIfServer(level, armX, armY, armZ);
level->setTileAndData(armX, armY, armZ, Tile::piston_extension_Id, armData, Tile::UPDATE_NONE);
level->setTileEntity(armX, armY, armZ,
PistonMovingPiece::newMovingPieceEntity(Tile::piston_head_Id, armData, facing, true, false));
}
return;
}
std::vector<int> savedBlocks(n), savedDatas(n);
for (int i = 0; i < n; i++)
{
savedBlocks[i] = level->getTile(toMove[i].x, toMove[i].y, toMove[i].z);
savedDatas[i] = level->getData(toMove[i].x, toMove[i].y, toMove[i].z);
}
std::sort(toMove.begin(), toMove.end(),
[&](const BlockPos3 &a, const BlockPos3 &b) {
int da = a.x * Facing::STEP_X[moveDir] + a.y * Facing::STEP_Y[moveDir] + a.z * Facing::STEP_Z[moveDir];
int db = b.x * Facing::STEP_X[moveDir] + b.y * Facing::STEP_Y[moveDir] + b.z * Facing::STEP_Z[moveDir];
return da > db;
});
for (int i = 0; i < n; i++)
{
savedBlocks[i] = level->getTile(toMove[i].x, toMove[i].y, toMove[i].z);
savedDatas[i] = level->getData(toMove[i].x, toMove[i].y, toMove[i].z);
}
for (int i = 0; i < n; i++)
{
auto &pos = toMove[i];
int destX = pos.x + Facing::STEP_X[moveDir];
int destY = pos.y + Facing::STEP_Y[moveDir];
int destZ = pos.z + Facing::STEP_Z[moveDir];
stopSharingIfServer(level, destX, destY, destZ);
level->setTileAndData(destX, destY, destZ, Tile::piston_extension_Id, savedDatas[i], Tile::UPDATE_NONE);
level->setTileEntity(destX, destY, destZ,
PistonMovingPiece::newMovingPieceEntity(savedBlocks[i], savedDatas[i], facing, isExtension, false));
}
if (isExtension)
{
int armData = facing | (sticky ? PistonExtensionTile::STICKY_BIT : 0);
stopSharingIfServer(level, armX, armY, armZ);
level->setTileAndData(armX, armY, armZ, Tile::piston_extension_Id, armData, Tile::UPDATE_NONE);
level->setTileEntity(armX, armY, armZ,
PistonMovingPiece::newMovingPieceEntity(Tile::piston_head_Id, armData, facing, true, false));
}
for (int i = 0; i < n; i++)
{
auto &pos = toMove[i];
if (isExtension && pos.x == armX && pos.y == armY && pos.z == armZ)
continue;
int srcX = pos.x - Facing::STEP_X[moveDir];
int srcY = pos.y - Facing::STEP_Y[moveDir];
int srcZ = pos.z - Facing::STEP_Z[moveDir];
bool receivesBlock = false;
for (auto &src : toMove)
{
if (src.x == srcX && src.y == srcY && src.z == srcZ)
{
receivesBlock = true;
break;
}
}
if (!receivesBlock)
{
stopSharingIfServer(level, pos.x, pos.y, pos.z);
level->setTileAndData(pos.x, pos.y, pos.z, 0, 0, Tile::UPDATE_CLIENTS);
}
}
for (int i = 0; i < n; i++)
{
level->updateNeighborsAt(toMove[i].x, toMove[i].y, toMove[i].z, savedBlocks[i]);
}
}
void PistonBaseTile::stopSharingIfServer(Level *level, int x, int y, int z)
{
if( !level->isClientSide )
{
MultiPlayerLevel *clientLevel = Minecraft::GetInstance()->getLevel(level->dimension->id);
if( clientLevel )
{
LevelChunk *lc = clientLevel->getChunkAt( x, z );
lc->stopSharingTilesAndData();
}
}
}
bool PistonBaseTile::createPush(Level *level, int sx, int sy, int sz, int facing)
{
std::vector<BlockPos3> toMove, toDestroy;
if (!collectStructure(level, facing,
sx + Facing::STEP_X[facing], sy + Facing::STEP_Y[facing], sz + Facing::STEP_Z[facing],
sx, sy, sz,
INT_MIN, 0, 0,
true,
toMove, toDestroy))
{
return false;
}
applyStructureMove(level, sx, sy, sz, facing, facing, isSticky,
toMove, toDestroy, true);
return true;
}