#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 #include #include #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(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, 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 by, shared_ptr itemInstance) { int targetData = getNewFacing(level, x, y, z, dynamic_pointer_cast(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 prevTileEntity = level->getTileEntity(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); if (prevTileEntity != nullptr && dynamic_pointer_cast(prevTileEntity) != nullptr) { dynamic_pointer_cast(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 = level->getTileEntity(twoX, twoY, twoZ); if (tileEntity != nullptr && dynamic_pointer_cast(tileEntity) != nullptr ) { shared_ptr ppe = dynamic_pointer_cast(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 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 fallbackMove; std::vector 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 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 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 player) { if (Mth::abs(static_cast(player->x) - x) < 2 && Mth::abs(static_cast(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 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 &toMove, std::vector &toDestroy) { std::deque queue; std::vector 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 &toMove, std::vector &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 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 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; }