diff options
Diffstat (limited to 'Minecraft.World/PistonBaseTile.cpp')
| -rw-r--r-- | Minecraft.World/PistonBaseTile.cpp | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/Minecraft.World/PistonBaseTile.cpp b/Minecraft.World/PistonBaseTile.cpp new file mode 100644 index 00000000..cbad5cec --- /dev/null +++ b/Minecraft.World/PistonBaseTile.cpp @@ -0,0 +1,628 @@ +#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" + +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 +bool PistonBaseTile::ignoreUpdate() +{ + return (TlsGetValue(tlsIdx) != NULL); +} + +void PistonBaseTile::ignoreUpdate(bool set) +{ + TlsSetValue(tlsIdx,(LPVOID)(set?1:0)); +} + +PistonBaseTile::PistonBaseTile(int id, bool isSticky) : Tile(id, Material::piston, isSolidRender() ) +{ + // 4J - added initialiser + ignoreUpdate(false); + + this->isSticky = isSticky; + setSoundType(SOUND_STONE); + setDestroyTime(0.5f); + + iconInside = NULL; + iconBack = NULL; + iconPlatform = NULL; +} + +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 = (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::pistonStickyBase->iconPlatform; + if (name.compare(INSIDE_TEX) == 0) return Tile::pistonBase->iconInside; + + return NULL; +} + +//@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<Mob> by) +{ + int targetData = getNewFacing(level, x, y, z, dynamic_pointer_cast<Player>(by) ); + level->setData(x, y, z, targetData); + if (!level->isClientSide && !ignoreUpdate()) + { + checkIfExtend(level, x, y, z); + } +} + +void PistonBaseTile::neighborChanged(Level *level, int x, int y, int z, int type) +{ + if (!level->isClientSide && !ignoreUpdate()) + { + checkIfExtend(level, x, y, z); + } +} + +void PistonBaseTile::onPlace(Level *level, int x, int y, int z) +{ + if (!level->isClientSide && level->getTileEntity(x, y, z) == NULL && !ignoreUpdate()) + { + 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->setDataNoUpdate(x, y, z, facing | EXTENDED_BIT); + level->tileEvent(x, y, z, id, TRIGGER_EXTEND, facing); + } + } + else if (!extend && isExtended(data)) + { + //level->setDataNoUpdate(x, y, z, facing); + 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->getSignal(x, y - 1, z, Facing::DOWN)) return true; + if (facing != Facing::UP && level->getSignal(x, y + 1, z, Facing::UP)) return true; + if (facing != Facing::NORTH && level->getSignal(x, y, z - 1, Facing::NORTH)) return true; + if (facing != Facing::SOUTH && level->getSignal(x, y, z + 1, Facing::SOUTH)) return true; + if (facing != Facing::EAST && level->getSignal(x + 1, y, z, Facing::EAST)) return true; + if (facing != Facing::WEST && level->getSignal(x - 1, y, z, Facing::WEST)) return true; + + // check signals above + if (level->getSignal(x, y, z, 0)) return true; + if (level->getSignal(x, y + 2, z, 1)) return true; + if (level->getSignal(x, y + 1, z - 1, 2)) return true; + if (level->getSignal(x, y + 1, z + 1, 3)) return true; + if (level->getSignal(x - 1, y + 1, z, 4)) return true; + if (level->getSignal(x + 1, y + 1, z, 5)) return true; + + return false; +} + +void PistonBaseTile::triggerEvent(Level *level, int x, int y, int z, int param1, int facing) +{ + ignoreUpdate(true); + + if (param1 == TRIGGER_EXTEND) + { + level->setDataNoUpdate(x, y, z, facing | EXTENDED_BIT); + } + else + { + level->setDataNoUpdate(x, y, z, facing); + } + + if (param1 == TRIGGER_EXTEND) + { + 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, 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 + { + level->setDataNoUpdate(x, y, z, facing); + } + PIXEndNamedEvent(); + } + else if (param1 == TRIGGER_CONTRACT) + { + 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 != NULL && dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity) != NULL) + { + dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity)->finalTick(); + } + + stopSharingIfServer(level, x, y, z); // 4J added + level->setTileAndDataNoUpdate(x, y, z, Tile::pistonMovingPiece_Id, facing); + 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::pistonMovingPiece_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 != NULL && dynamic_pointer_cast<PistonPieceEntity>(tileEntity) != NULL ) + { + 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"); + if (!pistonPiece && block > 0 && (isPushable(block, level, twoX, twoY, twoZ, false)) + && (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_NORMAL || block == Tile::pistonBase_Id || block == Tile::pistonStickyBase_Id)) + { + stopSharingIfServer(level, twoX, twoY, twoZ); // 4J added + + x += Facing::STEP_X[facing]; + y += Facing::STEP_Y[facing]; + z += Facing::STEP_Z[facing]; + + level->setTileAndDataNoUpdate(x, y, z, Tile::pistonMovingPiece_Id, blockData); + level->setTileEntity(x, y, z, PistonMovingPiece::newMovingPieceEntity(block, blockData, facing, false, false)); + + ignoreUpdate(false); + level->setTile(twoX, twoY, twoZ, 0); + ignoreUpdate(true); + } + else if (!pistonPiece) + { + stopSharingIfServer(level, x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); // 4J added + ignoreUpdate(false); + level->setTile(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing], 0); + ignoreUpdate(true); + } + PIXEndNamedEvent(); + } + else + { + stopSharingIfServer(level, x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]); // 4J added + ignoreUpdate(false); + level->setTile(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing], 0); + ignoreUpdate(true); + } + + level->playSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_TILE_PISTON_IN, 0.5f, level->random->nextFloat() * 0.15f + 0.6f); + } + + ignoreUpdate(false); +} + +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<Player> player) +{ + if (Mth::abs((float) player->x - x) < 2 && Mth::abs((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) +{ + // special case for obsidian + if (block == Tile::obsidian_Id) + { + return false; + } + + if (block == Tile::pistonBase_Id || block == Tile::pistonStickyBase_Id) + { + // special case for piston bases + if (isExtended(level->getData(cx, cy, cz))) + { + return false; + } + } + else + { + if (Tile::tiles[block]->getDestroySpeed(level, cx, cy, cz) == Tile::INDESTRUCTIBLE_DESTROY_TIME) + { + return false; + } + + if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_BLOCK) + { + return false; + } + + if (!allowDestroyable && Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY) + { + return false; + } + } + + if( Tile::tiles[block]->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->setTile(cx, cy, cz, 0); + 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]; + } + + 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->setTileAndDataNoUpdate(cx, cy, cz, Tile::pistonMovingPiece_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), false); + level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(Tile::pistonExtensionPiece_Id, facing | (isSticky ? PistonExtensionTile::STICKY_BIT : 0), facing, true, false)); + } + else + { + level->setTileAndDataNoUpdate(cx, cy, cz, Tile::pistonMovingPiece_Id, data, false); + level->setTileEntity(cx, cy, cz, PistonMovingPiece::newMovingPieceEntity(block, data, facing, true, false)); + } + + cx = nx; + cy = ny; + cz = nz; + } + + return true; + +} |
