Piston support

This commit is contained in:
AJ Ferguson 2024-06-14 21:19:37 -04:00
parent e1aebca67c
commit 1a6c70feed
4 changed files with 139 additions and 57 deletions

View file

@ -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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
}
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
}
}
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
// 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<T extends LivingEntity & ClientVehicle> {
}
// 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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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.
* <p>
* 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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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<T extends LivingEntity & ClientVehicle> {
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) {

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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);