diff --git a/Minecraft.World/PistonBaseTile.cpp b/Minecraft.World/PistonBaseTile.cpp index 00986b07..c45722de 100644 --- a/Minecraft.World/PistonBaseTile.cpp +++ b/Minecraft.World/PistonBaseTile.cpp @@ -1,858 +1,766 @@ -#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 "net.minecraft.world.phys.h" -#include "../Minecraft.Client/Minecraft.h" -#include "../Minecraft.Client/MultiPlayerLevel.h" -#include "net.minecraft.world.h" -#include "LevelChunk.h" -#include "Dimension.h" -#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() ) -{ - // 4J - added initialiser - 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"); - if (!pistonPiece && 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)) - { - stopSharingIfServer(level, twoX, twoY, twoZ); // 4J added - - x += Facing::STEP_X[facing]; - y += Facing::STEP_Y[facing]; - z += Facing::STEP_Z[facing]; - - level->setTileAndData(x, y, z, Tile::piston_extension_Id, blockData, Tile::UPDATE_ALL); - level->setTileEntity(x, y, z, PistonMovingPiece::newMovingPieceEntity(block, blockData, facing, false, false)); - - level->removeTile(twoX, twoY, twoZ); - - if (block == Tile::slimeBlock->id) - { - struct MoveBlock - { - int x; - int y; - int z; - int block; - int data; - }; - - vector movedBlocks; - movedBlocks.push_back({ twoX, twoY, twoZ, block, blockData }); - - auto alreadyMoved = [&movedBlocks](int x, int y, int z) - { - for (const auto &move : movedBlocks) - { - if (move.x == x && move.y == y && move.z == z) - { - return true; - } - } - return false; - }; - - for (size_t i = 0; i < movedBlocks.size(); i++) - { - if (movedBlocks.size() >= MAX_PUSH_DEPTH) - { - break; - } - - if (movedBlocks[i].block != Tile::slimeBlock->id) - { - continue; - } - - for (int face = 0; face < 6; face++) - { - if (movedBlocks.size() >= MAX_PUSH_DEPTH) - { - break; - } - - int adjX = movedBlocks[i].x + Facing::STEP_X[face]; - int adjY = movedBlocks[i].y + Facing::STEP_Y[face]; - int adjZ = movedBlocks[i].z + Facing::STEP_Z[face]; - - if (alreadyMoved(adjX, adjY, adjZ)) - { - continue; - } - - int adjBlock = level->getTile(adjX, adjY, adjZ); - if (adjBlock == 0) - { - continue; - } - - if (!isPushable(adjBlock, level, adjX, adjY, adjZ, false)) - { - continue; - } - - int adjData = level->getData(adjX, adjY, adjZ); - int destX = adjX - Facing::STEP_X[facing]; - int destY = adjY - Facing::STEP_Y[facing]; - int destZ = adjZ - Facing::STEP_Z[facing]; - - stopSharingIfServer(level, destX, destY, destZ); - level->setTileAndData(destX, destY, destZ, Tile::piston_extension_Id, adjData, Tile::UPDATE_NONE); - level->setTileEntity(destX, destY, destZ, PistonMovingPiece::newMovingPieceEntity(adjBlock, adjData, facing, false, false)); - - level->removeTile(adjX, adjY, adjZ); - level->updateNeighborsAt(adjX, adjY, adjZ, adjBlock); - - movedBlocks.push_back({ adjX, adjY, adjZ, adjBlock, adjData }); - } - } - } - } - else if (!pistonPiece) - { - 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]); - } - 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) -{ - int cx = sx + Facing::STEP_X[facing]; - int cy = sy + Facing::STEP_Y[facing]; - int cz = sz + Facing::STEP_Z[facing]; - - for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++) - { - - if (cy <= 0 || cy >= (Level::maxBuildHeight - 1)) - { - // out of bounds - return false; - } - - // 4J - added to also check for out of bounds in x/z for our finite world - int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; - int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1; - if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) ) - { - return false; - } - int block = level->getTile(cx, cy, cz); - if (block == 0) - { - break; - } - - if (!isPushable(block, level, cx, cy, cz, true)) - { - return false; - } - - if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY) - { - break; - } - - if (i == MAX_PUSH_DEPTH) - { - // we've reached the maximum push depth - // without finding air or a breakable block - return false; - } - - cx += Facing::STEP_X[facing]; - cy += Facing::STEP_Y[facing]; - cz += Facing::STEP_Z[facing]; - } - - return true; - -} - -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) -{ - int cx = sx + Facing::STEP_X[facing]; - int cy = sy + Facing::STEP_Y[facing]; - int cz = sz + Facing::STEP_Z[facing]; - - for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++) - { - if (cy <= 0 || cy >= (Level::maxBuildHeight - 1)) - { - // out of bounds - return false; - } - - // 4J - added to also check for out of bounds in x/z for our finite world - int minXZ = - (level->dimension->getXZSize() * 16 ) / 2; - int maxXZ = (level->dimension->getXZSize() * 16 ) / 2 - 1; - if( ( cx <= minXZ ) || ( cx >= maxXZ ) || ( cz <= minXZ ) || ( cz >= maxXZ ) ) - { - return false; - } - - int block = level->getTile(cx, cy, cz); - if (block == 0) - { - break; - } - - if (!isPushable(block, level, cx, cy, cz, true)) - { - return false; - } - - if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY) - { - // this block is destroyed when pushed - Tile::tiles[block]->spawnResources(level, cx, cy, cz, level->getData(cx, cy, cz), 0); - // setting the tile to air is actually superflous, but helps vs multiplayer problems - stopSharingIfServer(level, cx, cy, cz); // 4J added - level->removeTile(cx, cy, cz); - break; - } - - if (i == MAX_PUSH_DEPTH) - { - // we've reached the maximum push depth without finding air or a breakable block - return false; - } - - cx += Facing::STEP_X[facing]; - cy += Facing::STEP_Y[facing]; - cz += Facing::STEP_Z[facing]; - } - - int ex = cx; - int ey = cy; - int ez = cz; - int count = 0; - int tiles[MAX_PUSH_DEPTH + 1]; - struct MoveBlock - { - int x; - int y; - int z; - int block; - int data; - }; - vector movedBlocks; - auto alreadyMoved = [&movedBlocks](int x, int y, int z) - { - for (const auto &move : movedBlocks) - { - if (move.x == x && move.y == y && move.z == z) - { - return true; - } - } - return false; - }; - - while (cx != sx || cy != sy || cz != sz) - { - - int nx = cx - Facing::STEP_X[facing]; - int ny = cy - Facing::STEP_Y[facing]; - int nz = cz - Facing::STEP_Z[facing]; - - int block = level->getTile(nx, ny, nz); - int data = level->getData(nx, ny, nz); - - stopSharingIfServer(level, cx, cy, cz); // 4J added - - if (block == id && nx == sx && ny == sy && nz == sz) - { - level->setTileAndData(cx, cy, cz, Tile::piston_extension_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), Tile::UPDATE_NONE); - level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(Tile::piston_head_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), facing, true, false)); - } - else - { - level->setTileAndData(cx, cy, cz, Tile::piston_extension_Id, data, Tile::UPDATE_NONE); - level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(block, data, facing, true, false)); - } - if (block == Tile::slimeBlock->id) - { - AABB *bb = AABB::newTemp(nx, ny, nz, nx + 1, ny + 2, nz + 1); - auto entities = level->getEntities(nullptr, bb); - } - movedBlocks.push_back({ nx, ny, nz, block, data }); - tiles[count++] = block; - - cx = nx; - cy = ny; - cz = nz; - } - - for (size_t i = 0; i < movedBlocks.size(); i++) - { - if (movedBlocks[i].block != Tile::slimeBlock->id) - { - continue; - } - - for (int face = 0; face < 6; face++) - { - int nx = movedBlocks[i].x + Facing::STEP_X[face]; - int ny = movedBlocks[i].y + Facing::STEP_Y[face]; - int nz = movedBlocks[i].z + Facing::STEP_Z[face]; - - if (alreadyMoved(nx, ny, nz)) - { - continue; - } - - int block = level->getTile(nx, ny, nz); - if (block == 0) - { - continue; - } - - if (!isPushable(block, level, nx, ny, nz, false)) - { - continue; - } - - int data = level->getData(nx, ny, nz); - int dx = nx + Facing::STEP_X[facing]; - int dy = ny + Facing::STEP_Y[facing]; - int dz = nz + Facing::STEP_Z[facing]; - - stopSharingIfServer(level, dx, dy, dz); - level->setTileAndData(dx, dy, dz, Tile::piston_extension_Id, data, Tile::UPDATE_NONE); - level->setTileEntity(dx, dy, dz, PistonMovingPiece::newMovingPieceEntity(block, data, facing, true, false)); - level->removeTile(nx, ny, nz); - level->updateNeighborsAt(nx, ny, nz, block); - movedBlocks.push_back({ nx, ny, nz, block, data }); - } - } - - cx = ex; - cy = ey; - cz = ez; - count = 0; - - while (cx != sx || cy != sy || cz != sz) - { - int nx = cx - Facing::STEP_X[facing]; - int ny = cy - Facing::STEP_Y[facing]; - int nz = cz - Facing::STEP_Z[facing]; - - level->updateNeighborsAt(nx, ny, nz, tiles[count++]); - - cx = nx; - cy = ny; - cz = nz; - } - - return true; - -} +#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 + { + 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; + queue.push_back({pos.x + Facing::STEP_X[d], + pos.y + Facing::STEP_Y[d], + pos.z + Facing::STEP_Z[d]}); + } + } + } + + 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(); + if (n == 0) 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)); + } + + int armX = pistonX + Facing::STEP_X[facing]; + int armY = pistonY + Facing::STEP_Y[facing]; + int armZ = pistonZ + Facing::STEP_Z[facing]; + + 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; +} diff --git a/Minecraft.World/PistonBaseTile.h b/Minecraft.World/PistonBaseTile.h index ea2e026b..ee535bce 100644 --- a/Minecraft.World/PistonBaseTile.h +++ b/Minecraft.World/PistonBaseTile.h @@ -1,75 +1,93 @@ -#pragma once -#include "Tile.h" - -class PistonBaseTile : public Tile -{ -public: - static const int EXTENDED_BIT = 8; - static const int UNDEFINED_FACING = 7; - - static const float PLATFORM_THICKNESS; - static const int MAX_PUSH_DEPTH = 12; - static const int TRIGGER_EXTEND = 0; - static const int TRIGGER_CONTRACT = 1; - - static const wstring EDGE_TEX; - static const wstring PLATFORM_TEX; - static const wstring PLATFORM_STICKY_TEX; - static const wstring BACK_TEX; - static const wstring INSIDE_TEX; - -private: - bool isSticky; - - Icon *iconInside; - Icon *iconBack; - Icon *iconPlatform; - - static DWORD tlsIdx; - // 4J - was just a static but implemented with TLS for our version - //code removed so the above comment no longer applies - -public: - PistonBaseTile(int id, bool isSticky); - virtual void createBlockStateDefinition() override; - virtual int defaultBlockState() override; - virtual int convertBlockStateToLegacyData(BlockState *state) override; - virtual Tile::BlockState getBlockState(int data); - virtual Tile::BlockState getBlockState(LevelSource *level, int x, int y, int z) override; - - Icon *getPlatformTexture(); - virtual void updateShape(float x0, float y0, float z0, float x1, float y1, float z1); - - virtual Icon *getTexture(int face, int data); - static Icon *getTexture(const wstring &name); - void registerIcons(IconRegister *iconRegister); - - virtual int getRenderShape(); - virtual bool isSolidRender(bool isServerLevel = false); - virtual bool 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 - virtual void setPlacedBy(Level *level, int x, int y, int z, shared_ptr by, shared_ptr itemInstance); - virtual void neighborChanged(Level *level, int x, int y, int z, int type); - virtual void onPlace(Level *level, int x, int y, int z); - -private: - void checkIfExtend(Level *level, int x, int y, int z); - bool getNeighborSignal(Level *level, int x, int y, int z, int facing); - -public: - virtual bool triggerEvent(Level *level, int x, int y, int z, int param1, int facing); - virtual void updateShape(LevelSource *level, int x, int y, int z, int forceData = -1, shared_ptr forceEntity = shared_ptr()); // 4J added forceData, forceEntity param - virtual void updateDefaultShape(); - virtual void addAABBs(Level *level, int x, int y, int z, AABB *box, AABBList *boxes, shared_ptr source); - virtual AABB *getAABB(Level *level, int x, int y, int z); - virtual bool isCubeShaped(); - - static int getFacing(int data); - static bool isExtended(int data); - static int getNewFacing(Level *level, int x, int y, int z, shared_ptr player); -private: - static bool isPushable(int block, Level *level, int cx, int cy, int cz, bool allowDestroyable); - static bool canPush(Level *level, int sx, int sy, int sz, int facing); - static void stopSharingIfServer(Level *level, int x, int y, int z); // 4J added - - bool createPush(Level *level, int sx, int sy, int sz, int facing); -}; +#pragma once +#include "Tile.h" +#include + +struct BlockPos3 +{ + int x, y, z; + bool operator==(const BlockPos3 &o) const { return x == o.x && y == o.y && z == o.z; } +}; + +class PistonBaseTile : public Tile +{ +public: + static const int EXTENDED_BIT = 8; + static const int UNDEFINED_FACING = 7; + + static const float PLATFORM_THICKNESS; + static const int MAX_PUSH_DEPTH = 12; + static const int TRIGGER_EXTEND = 0; + static const int TRIGGER_CONTRACT = 1; + + static const wstring EDGE_TEX; + static const wstring PLATFORM_TEX; + static const wstring PLATFORM_STICKY_TEX; + static const wstring BACK_TEX; + static const wstring INSIDE_TEX; + +private: + bool isSticky; + + Icon *iconInside; + Icon *iconBack; + Icon *iconPlatform; + + static DWORD tlsIdx; + //code removed so the above comment no longer applies + +public: + PistonBaseTile(int id, bool isSticky); + virtual void createBlockStateDefinition() override; + virtual int defaultBlockState() override; + virtual int convertBlockStateToLegacyData(BlockState *state) override; + virtual Tile::BlockState getBlockState(int data); + virtual Tile::BlockState getBlockState(LevelSource *level, int x, int y, int z) override; + + Icon *getPlatformTexture(); + virtual void updateShape(float x0, float y0, float z0, float x1, float y1, float z1); + + virtual Icon *getTexture(int face, int data); + static Icon *getTexture(const wstring &name); + void registerIcons(IconRegister *iconRegister); + + virtual int getRenderShape(); + virtual bool isSolidRender(bool isServerLevel = false); + virtual bool 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 + virtual void setPlacedBy(Level *level, int x, int y, int z, shared_ptr by, shared_ptr itemInstance); + virtual void neighborChanged(Level *level, int x, int y, int z, int type); + virtual void onPlace(Level *level, int x, int y, int z); + +private: + void checkIfExtend(Level *level, int x, int y, int z); + bool getNeighborSignal(Level *level, int x, int y, int z, int facing); + +public: + virtual bool triggerEvent(Level *level, int x, int y, int z, int param1, int facing); + virtual void updateShape(LevelSource *level, int x, int y, int z, int forceData = -1, shared_ptr forceEntity = shared_ptr()); // 4J added forceData, forceEntity param + virtual void updateDefaultShape(); + virtual void addAABBs(Level *level, int x, int y, int z, AABB *box, AABBList *boxes, shared_ptr source); + virtual AABB *getAABB(Level *level, int x, int y, int z); + virtual bool isCubeShaped(); + + static int getFacing(int data); + static bool isExtended(int data); + static int getNewFacing(Level *level, int x, int y, int z, shared_ptr player); +private: + static bool isPushable(int block, Level *level, int cx, int cy, int cz, bool allowDestroyable); + static bool canPush(Level *level, int sx, int sy, int sz, int facing); + static bool 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); + static void applyStructureMove(Level *level, int pistonX, int pistonY, int pistonZ, + int facing, int moveDir, bool isSticky, + std::vector &toMove, + std::vector &toDestroy, + bool isExtension); + static void stopSharingIfServer(Level *level, int x, int y, int z); // 4J added + + bool createPush(Level *level, int sx, int sy, int sz, int facing); +}; diff --git a/Minecraft.World/TallGrass2.cpp b/Minecraft.World/TallGrass2.cpp index ab6e3c2d..f6c41f35 100644 --- a/Minecraft.World/TallGrass2.cpp +++ b/Minecraft.World/TallGrass2.cpp @@ -363,7 +363,13 @@ shared_ptr TallGrass2::getSilkTouchItemInstance(int data) void TallGrass2::playerDestroy(Level* level, shared_ptr player, int x, int y, int z, int data) { - int resolvedVariant = data & ~UPPER_BIT; + int resolvedVariant; + bool isUpper = (data & UPPER_BIT) != 0; + if (isUpper && level->getTile(x, y - 1, z) == id) + resolvedVariant = level->getData(x, y - 1, z) & ~UPPER_BIT; + else + resolvedVariant = data & ~UPPER_BIT; + if (resolvedVariant < 0 || resolvedVariant >= VARIANT_COUNT) resolvedVariant = 0; int resolvedData = data; if (resolvedVariant == SUNFLOWER)