From 1a6c70feeddf3b833879d186633d0aedd7ea149f Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 14 Jun 2024 21:19:37 -0400 Subject: [PATCH] Piston support --- .../entity/vehicle/VehicleComponent.java | 94 ++++++++++++------- .../level/physics/CollisionManager.java | 3 +- .../geyser/session/cache/PistonCache.java | 26 ++++- .../level/block/entity/PistonBlockEntity.java | 73 +++++++++----- 4 files changed, 139 insertions(+), 57 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java index b49301faa..d3839e47f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java @@ -35,7 +35,6 @@ import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.geysermc.erosion.util.BlockPositionIterator; -import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.Blocks; @@ -81,8 +80,8 @@ public class VehicleComponent { this.moveSpeed = (float) AttributeType.Builtin.GENERIC_MOVEMENT_SPEED.getDef(); this.gravity = AttributeType.Builtin.GENERIC_GRAVITY.getDef(); - double width = Double.parseDouble(Float.toString(vehicle.getBoundingBoxWidth())); - double height = Double.parseDouble(Float.toString(vehicle.getBoundingBoxHeight())); + double width = vehicle.getBoundingBoxWidth(); + double height = vehicle.getBoundingBoxHeight(); this.boundingBox = new BoundingBox( vehicle.getPosition().getX(), vehicle.getPosition().getY() + height / 2, @@ -92,15 +91,13 @@ public class VehicleComponent { } public void setWidth(float width) { - double doubleWidth = Double.parseDouble(Float.toString(width)); - boundingBox.setSizeX(doubleWidth); - boundingBox.setSizeZ(doubleWidth); + boundingBox.setSizeX(width); + boundingBox.setSizeZ(width); } public void setHeight(float height) { - double doubleHeight = Double.parseDouble(Float.toString(height)); - boundingBox.translate(0, (doubleHeight - boundingBox.getSizeY()) / 2, 0); - boundingBox.setSizeY(doubleHeight); + boundingBox.translate(0, (height - boundingBox.getSizeY()) / 2, 0); + boundingBox.setSizeY(height); } public void moveAbsolute(double x, double y, double z) { @@ -113,6 +110,14 @@ public class VehicleComponent { boundingBox.translate(x, y, z); } + public void moveRelative(Vector3d vec) { + boundingBox.translate(vec); + } + + public BoundingBox getBoundingBox() { + return this.boundingBox; + } + public void setEffect(Effect effect, int effectAmplifier) { switch (effect) { case LEVITATION -> effectLevitation = effectAmplifier + 1; @@ -129,6 +134,12 @@ public class VehicleComponent { } } + public Vector3d correctMovement(Vector3d movement) { + return vehicle.getSession().getCollisionManager().correctMovement( + movement, boundingBox, vehicle.isOnGround(), this.stepHeight, true, vehicle.canWalkOnLava() + ); + } + public void setMoveSpeed(float moveSpeed) { this.moveSpeed = moveSpeed; } @@ -382,7 +393,7 @@ public class VehicleComponent { protected void landMovement(VehicleContext ctx) { double gravity = getGravity(); - float slipperiness = BlockStateValues.getSlipperiness(ctx.velocityAffectingBlock()); + float slipperiness = BlockStateValues.getSlipperiness(getVelocityBlock(ctx)); float drag = vehicle.isOnGround() ? 0.91f * slipperiness : 0.91f; float speed = vehicle.getVehicleSpeed() * (vehicle.isOnGround() ? BASE_SLIPPERINESS_CUBED / (slipperiness * slipperiness * slipperiness) : 0.1f); @@ -556,13 +567,12 @@ public class VehicleComponent { // Non-zero values indicate a collision on that axis Vector3d moveDiff = motion.toDouble().sub(correctedMovement); - boolean onGround = moveDiff.getY() != 0 && motion.getY() < 0; + vehicle.setOnGround(moveDiff.getY() != 0 && motion.getY() < 0); boolean horizontalCollision = moveDiff.getX() != 0 || moveDiff.getZ() != 0; boolean bounced = false; - if (onGround) { - Vector3i landingPos = ctx.centerPos().sub(0, 0.2f, 0).toInt(); - Block landingBlock = ctx.getBlock(landingPos).block(); + if (vehicle.isOnGround()) { + Block landingBlock = getLandingBlock(ctx).block(); if (landingBlock == Blocks.SLIME_BLOCK) { motion = Vector3f.from(motion.getX(), -motion.getY(), motion.getZ()); @@ -592,7 +602,7 @@ public class VehicleComponent { } // Send the new position to the bedrock client and java server - moveVehicle(ctx.centerPos(), onGround); + moveVehicle(ctx.centerPos()); vehicle.setMotion(motion); applyBlockCollisionEffects(ctx); @@ -651,17 +661,16 @@ public class VehicleComponent { return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f); } - protected void moveVehicle(Vector3d javaPos, boolean isOnGround) { + protected void moveVehicle(Vector3d javaPos) { Vector3f bedrockPos = javaPos.toFloat(); Vector2f rotation = getVehicleRotation(); MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId()); - if (isOnGround) { + if (vehicle.isOnGround()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); } - vehicle.setOnGround(isOnGround); if (vehicle.getPosition().getX() != bedrockPos.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); @@ -714,7 +723,16 @@ public class VehicleComponent { return this.gravity; } - protected @Nullable Vector3i getSupportingBlockPos(VehicleContext ctx) { + /** + * Finds the position of the main block supporting the vehicle. + * Used when determining slipperiness, speed, etc. + *

+ * Should use {@link VehicleContext#supportingBlockPos()}, instead of calling this directly. + * + * @param ctx context + * @return position of the main block supporting this entity + */ + private @Nullable Vector3i getSupportingBlockPos(VehicleContext ctx) { Vector3i result = null; if (vehicle.isOnGround()) { @@ -752,13 +770,25 @@ public class VehicleComponent { return result; } - protected Vector3i getVelocityAffectingPos(VehicleContext ctx) { - Vector3i blockPos = getSupportingBlockPos(ctx); - if (blockPos != null) { - return Vector3i.from(blockPos.getX(), Math.floor(ctx.centerPos().getY() - 0.500001f), blockPos.getZ()); + protected BlockState getBlockUnder(VehicleContext ctx, float dist) { + Vector3i supportingBlockPos = ctx.supportingBlockPos(); + + Vector3i blockPos; + if (supportingBlockPos != null) { + blockPos = Vector3i.from(supportingBlockPos.getX(), Math.floor(ctx.centerPos().getY() - dist), supportingBlockPos.getZ()); } else { - return ctx.centerPos().sub(0, 0.500001f, 0).toInt(); + blockPos = ctx.centerPos().sub(0, dist, 0).toInt(); } + + return ctx.getBlock(blockPos); + } + + protected BlockState getLandingBlock(VehicleContext ctx) { + return getBlockUnder(ctx, 0.2f); + } + + protected BlockState getVelocityBlock(VehicleContext ctx) { + return getBlockUnder(ctx, 0.500001f); } protected float getVelocityMultiplier(VehicleContext ctx) { @@ -771,7 +801,7 @@ public class VehicleComponent { return 0.4f; } - block = ctx.velocityAffectingBlock().block(); + block = getVelocityBlock(ctx).block(); if (block == Blocks.SOUL_SAND || block == Blocks.HONEY_BLOCK) { return 0.4f; } @@ -785,7 +815,7 @@ public class VehicleComponent { return 0.5f; } - block = ctx.velocityAffectingBlock().block(); + block = getVelocityBlock(ctx).block(); if (block == Blocks.HONEY_BLOCK) { return 0.5f; } @@ -796,7 +826,7 @@ public class VehicleComponent { protected class VehicleContext { private Vector3d centerPos; private BlockState centerBlock; - private BlockState velocityAffectingBlock; + private Vector3i supportingBlockPos; private BlockPositionIterator blockIter; private int[] blocks; @@ -811,7 +841,7 @@ public class VehicleComponent { this.centerPos = boundingBox.getBottomCenter(); this.centerBlock = getBlock(this.centerPos.toInt()); - this.velocityAffectingBlock = null; + this.supportingBlockPos = null; } protected Vector3d centerPos() { @@ -822,12 +852,12 @@ public class VehicleComponent { return this.centerBlock; } - protected BlockState velocityAffectingBlock() { - if (this.velocityAffectingBlock == null) { - this.velocityAffectingBlock = getBlock(getVelocityAffectingPos(this)); + protected Vector3i supportingBlockPos() { + if (this.supportingBlockPos == null) { + this.supportingBlockPos = getSupportingBlockPos(this); } - return this.velocityAffectingBlock; + return this.supportingBlockPos; } protected int getBlockId(int x, int y, int z) { diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index a4fdb2e7c..ec2ddd269 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -177,7 +177,8 @@ public class CollisionManager { // Send corrected position to Bedrock if they differ by too much to prevent de-syncs if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) { PlayerEntity playerEntity = session.getPlayerEntity(); - if (pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { + // Client will dismount if on a vehicle + if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), newOnGround, true); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java index d0a5bc094..dee4aa7cf 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/PistonCache.java @@ -33,7 +33,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.session.GeyserSession; @@ -119,6 +121,12 @@ public class PistonCache { private void sendPlayerMovement() { if (!playerDisplacement.equals(Vector3d.ZERO) && playerMotion.equals(Vector3f.ZERO)) { SessionPlayerEntity playerEntity = session.getPlayerEntity(); + + Entity vehicle = playerEntity.getVehicle(); + if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + return; + } + boolean isOnGround = playerDisplacement.getY() > 0 || playerEntity.isOnGround(); Vector3d position = session.getCollisionManager().getPlayerBoundingBox().getBottomCenter(); playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), isOnGround, true); @@ -128,6 +136,13 @@ public class PistonCache { private void sendPlayerMotion() { if (!playerMotion.equals(Vector3f.ZERO)) { SessionPlayerEntity playerEntity = session.getPlayerEntity(); + + Entity vehicle = playerEntity.getVehicle(); + if (vehicle instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + vehicle.setMotion(playerMotion); + return; + } + playerEntity.setMotion(playerMotion); SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket(); @@ -149,10 +164,15 @@ public class PistonCache { totalDisplacement = totalDisplacement.max(-0.51d, -0.51d, -0.51d).min(0.51d, 0.51d, 0.51d); Vector3d delta = totalDisplacement.sub(playerDisplacement); - // Check if the piston is pushing a player into collision - delta = session.getCollisionManager().correctPlayerMovement(delta, true, false); - session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ()); + // Check if the piston is pushing a player into collision + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + delta = clientVehicle.getVehicleComponent().correctMovement(delta); + clientVehicle.getVehicleComponent().moveRelative(delta); + } else { + delta = session.getCollisionManager().correctPlayerMovement(delta, true, false); + session.getCollisionManager().getPlayerBoundingBox().translate(delta.getX(), delta.getY(), delta.getZ()); + } playerDisplacement = totalDisplacement; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java index 350ce8c3e..2cde98553 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/PistonBlockEntity.java @@ -38,6 +38,7 @@ import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.property.Properties; @@ -348,18 +349,31 @@ public class PistonBlockEntity { blockMovement = 1f - lastProgress; } - BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox(); + boolean onGround; + BoundingBox playerBoundingBox; + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + onGround = session.getPlayerEntity().getVehicle().isOnGround(); + playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox(); + } else { + onGround = session.getPlayerEntity().isOnGround(); + playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox(); + } + // Shrink the collision in the other axes slightly, to avoid false positives when pressed up against the side of blocks Vector3d shrink = Vector3i.ONE.sub(direction.abs()).toDouble().mul(CollisionManager.COLLISION_TOLERANCE * 2); - playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() - shrink.getX()); - playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() - shrink.getY()); - playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() - shrink.getZ()); + double sizeX = playerBoundingBox.getSizeX(); + double sizeY = playerBoundingBox.getSizeY(); + double sizeZ = playerBoundingBox.getSizeZ(); + + playerBoundingBox.setSizeX(sizeX - shrink.getX()); + playerBoundingBox.setSizeY(sizeY - shrink.getY()); + playerBoundingBox.setSizeZ(sizeZ - shrink.getZ()); // Resolve collision with the piston head BlockState pistonHeadId = Blocks.PISTON_HEAD.defaultBlockState() .withValue(Properties.SHORT, false) .withValue(Properties.FACING, orientation); - pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox); + pushPlayerBlock(pistonHeadId, getPistonHeadPos().toDouble(), blockMovement, playerBoundingBox, onGround); // Resolve collision with any attached moving blocks, but skip slime blocks // This prevents players from being launched by slime blocks covered by other blocks @@ -367,7 +381,7 @@ public class PistonBlockEntity { BlockState state = entry.getValue(); if (!state.is(Blocks.SLIME_BLOCK)) { Vector3d blockPos = entry.getKey().toDouble(); - pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox); + pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround); } } // Resolve collision with slime blocks @@ -375,14 +389,14 @@ public class PistonBlockEntity { BlockState state = entry.getValue(); if (state.is(Blocks.SLIME_BLOCK)) { Vector3d blockPos = entry.getKey().toDouble(); - pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox); + pushPlayerBlock(state, blockPos, blockMovement, playerBoundingBox, onGround); } } // Undo shrink - playerBoundingBox.setSizeX(playerBoundingBox.getSizeX() + shrink.getX()); - playerBoundingBox.setSizeY(playerBoundingBox.getSizeY() + shrink.getY()); - playerBoundingBox.setSizeZ(playerBoundingBox.getSizeZ() + shrink.getZ()); + playerBoundingBox.setSizeX(sizeX); + playerBoundingBox.setSizeY(sizeY); + playerBoundingBox.setSizeZ(sizeZ); } /** @@ -392,20 +406,22 @@ public class PistonBlockEntity { * @param playerBoundingBox The player's bounding box * @return True if the player attached, otherwise false */ - private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox) { + private boolean isPlayerAttached(Vector3d blockPos, BoundingBox playerBoundingBox, boolean onGround) { if (orientation.isVertical()) { return false; } - return session.getPlayerEntity().isOnGround() && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox); + return onGround && HONEY_BOUNDING_BOX.checkIntersection(blockPos, playerBoundingBox); } /** * Launches a player if the player is on the pushing side of the slime block * * @param blockPos The position of the slime block - * @param playerPos The player's position + * @param playerBoundingBox The player's bounding box */ - private void applySlimeBlockMotion(Vector3d blockPos, Vector3d playerPos) { + private void applySlimeBlockMotion(Vector3d blockPos, BoundingBox playerBoundingBox) { + Vector3d playerPos = Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ()); + Direction movementDirection = orientation; // Invert direction when pulling if (action == PistonValueType.PULLING) { @@ -471,7 +487,7 @@ public class PistonBlockEntity { return maxIntersection; } - private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox) { + private void pushPlayerBlock(BlockState state, Vector3d startingPos, double blockMovement, BoundingBox playerBoundingBox, boolean onGround) { PistonCache pistonCache = session.getPistonCache(); Vector3d movement = getMovement().toDouble(); // Check if the player collides with the movingBlock block entity @@ -481,12 +497,12 @@ public class PistonBlockEntity { if (state.is(Blocks.SLIME_BLOCK)) { pistonCache.setPlayerSlimeCollision(true); - applySlimeBlockMotion(finalBlockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + applySlimeBlockMotion(finalBlockPos, playerBoundingBox); } } Vector3d blockPos = startingPos.add(movement.mul(blockMovement)); - if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox)) { + if (state.is(Blocks.HONEY_BLOCK) && isPlayerAttached(blockPos, playerBoundingBox, onGround)) { pistonCache.setPlayerCollided(true); pistonCache.setPlayerAttachedToHoney(true); @@ -509,7 +525,7 @@ public class PistonBlockEntity { if (state.is(Blocks.SLIME_BLOCK)) { pistonCache.setPlayerSlimeCollision(true); - applySlimeBlockMotion(blockPos, Vector3d.from(playerBoundingBox.getMiddleX(), playerBoundingBox.getMiddleY(), playerBoundingBox.getMiddleZ())); + applySlimeBlockMotion(blockPos, playerBoundingBox); } } } @@ -585,7 +601,14 @@ public class PistonBlockEntity { movingBlockMap.put(getPistonHeadPos(), this); Vector3i movement = getMovement(); - BoundingBox playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone(); + + BoundingBox playerBoundingBox; + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox().clone(); + } else { + playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone(); + } + if (orientation == Direction.UP) { // Extend the bounding box down, to catch collisions when the player is falling down playerBoundingBox.extend(0, -256, 0); @@ -629,17 +652,25 @@ public class PistonBlockEntity { return; } placedFinalBlocks = true; + + BoundingBox playerBoundingBox; + if (session.getPlayerEntity().getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled()) { + playerBoundingBox = clientVehicle.getVehicleComponent().getBoundingBox().clone(); + } else { + playerBoundingBox = session.getCollisionManager().getPlayerBoundingBox().clone(); + } + Vector3i movement = getMovement(); attachedBlocks.forEach((blockPos, state) -> { blockPos = blockPos.add(movement); // Don't place blocks that collide with the player - if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), playerBoundingBox)) { ChunkUtils.updateBlock(session, state, blockPos); } }); if (action == PistonValueType.PUSHING) { Vector3i pistonHeadPos = getPistonHeadPos().add(movement); - if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) { + if (!SOLID_BOUNDING_BOX.checkIntersection(pistonHeadPos.toDouble(), playerBoundingBox)) { ChunkUtils.updateBlock(session, Blocks.PISTON_HEAD.defaultBlockState() .withValue(Properties.SHORT, false) .withValue(Properties.FACING, orientation), pistonHeadPos);