Horses are still weird, but boats are mostly working

This commit is contained in:
Camotoy 2024-10-26 01:27:55 -04:00
parent 2025a2dc3a
commit 774d3670c5
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
10 changed files with 138 additions and 76 deletions

View file

@ -701,9 +701,4 @@ public class Entity implements GeyserEntity {
packet.setData(data);
session.sendUpstreamPacket(packet);
}
@SuppressWarnings("unchecked")
public <I extends Entity> @Nullable I as(Class<I> entityClass) {
return entityClass.isInstance(this) ? (I) this : null;
}
}

View file

@ -758,7 +758,6 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos.getX(), javaPos.getY(), javaPos.getZ(), rotation.getX(), rotation.getY());
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
vehicle.getSession().setLastVehicleMoveTimestamp(System.currentTimeMillis());
}
protected double getGravity() {

View file

@ -524,12 +524,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private boolean placedBucket;
/**
* Used to send a ServerboundMoveVehiclePacket for every PlayerInputPacket after idling on a boat/horse for more than 100ms
*/
@Setter
private long lastVehicleMoveTimestamp = System.currentTimeMillis();
/**
* Counts how many ticks have occurred since an arm animation started.
* -1 means there is no active arm swing; -2 means an arm swing will start in a tick.

View file

@ -25,6 +25,8 @@
package org.geysermc.geyser.session.cache;
import lombok.Getter;
import lombok.Setter;
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.geysermc.geyser.session.GeyserSession;
@ -37,6 +39,10 @@ public final class InputCache {
private ServerboundPlayerInputPacket inputPacket = new ServerboundPlayerInputPacket(false, false, false, false, false, false, false);
private boolean lastHorizontalCollision;
private int ticksSinceLastMovePacket;
@Getter @Setter
private int jumpingTicks;
@Getter @Setter
private float jumpScale;
public InputCache(GeyserSession session) {
this.session = session;
@ -61,6 +67,10 @@ public final class InputCache {
}
}
public boolean wasJumping() {
return this.inputPacket.isJump();
}
public void markPositionPacketSent() {
this.ticksSinceLastMovePacket = 0;
}

View file

@ -48,7 +48,7 @@ public class TeleportCache {
/**
* How many move packets the teleport can be unconfirmed for before it gets resent to the client
*/
private static final int RESEND_THRESHOLD = 5;
private static final int RESEND_THRESHOLD = 20; // Make it one full second with auth input
private final double x, y, z;
private final float pitch, yaw;

View file

@ -42,17 +42,15 @@ public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEn
@Override
public void translate(GeyserSession session, MoveEntityAbsolutePacket packet) {
session.setLastVehicleMoveTimestamp(System.currentTimeMillis());
Entity ridingEntity = session.getPlayerEntity().getVehicle();
if (ridingEntity != null && session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), false)) {
Vector3f position = Vector3f.from(ridingEntity.getPosition().getX(), packet.getPosition().getY(),
ridingEntity.getPosition().getZ());
if (ridingEntity instanceof BoatEntity) {
// Undo the changes usually applied to the boat
ridingEntity.as(BoatEntity.class)
.moveAbsoluteWithoutAdjustments(position, ridingEntity.getYaw(),
ridingEntity.isOnGround(), true);
// ridingEntity.as(BoatEntity.class)
// .moveAbsoluteWithoutAdjustments(position, ridingEntity.getYaw(),
// ridingEntity.isOnGround(), true);
} else {
// This doesn't work if teleported is false
ridingEntity.moveAbsolute(position,

View file

@ -44,47 +44,6 @@ public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPa
@Override
public void translate(GeyserSession session, PlayerInputPacket packet) {
// ServerboundPlayerInputPacket playerInputPacket = new ServerboundPlayerInputPacket(
// packet.getInputMotion().getX(), packet.getInputMotion().getY(), packet.isJumping(), packet.isSneaking()
// );
//
// session.sendDownstreamGamePacket(playerInputPacket);
session.getPlayerEntity().setVehicleInput(packet.getInputMotion());
// Bedrock only sends movement vehicle packets while moving
// This allows horses to take damage while standing on magma
Entity vehicle = session.getPlayerEntity().getVehicle();
boolean sendMovement = false;
if (vehicle instanceof AbstractHorseEntity && !(vehicle instanceof LlamaEntity)) {
sendMovement = vehicle.isOnGround();
} else if (vehicle instanceof BoatEntity) {
if (vehicle.getPassengers().size() == 1) {
// The player is the only rider
sendMovement = true;
} else {
// Check if the player is the front rider
if (session.getPlayerEntity().isRidingInFront()) {
sendMovement = true;
}
}
}
if (sendMovement) {
long timeSinceVehicleMove = System.currentTimeMillis() - session.getLastVehicleMoveTimestamp();
if (timeSinceVehicleMove >= 100) {
Vector3f vehiclePosition = vehicle.getPosition();
if (vehicle instanceof BoatEntity && !vehicle.isOnGround()) {
// Remove some Y position to prevents boats flying up
vehiclePosition = vehiclePosition.down(vehicle.getDefinition().offset());
}
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(
vehiclePosition.getX(), vehiclePosition.getY(), vehiclePosition.getZ(),
vehicle.getYaw() - 90, vehicle.getPitch()
);
session.sendDownstreamGamePacket(moveVehiclePacket);
}
}
}
}

View file

@ -31,7 +31,6 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.level.physics.CollisionResult;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
@ -48,17 +47,17 @@ public final class BedrockMovePlayerTranslator {
SessionPlayerEntity entity = session.getPlayerEntity();
if (!session.isSpawned()) return;
// Ignore movement packets until Bedrock's position matches the teleported position
if (session.getUnconfirmedTeleport() != null) {
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
return;
}
boolean actualPositionChanged = !entity.getPosition().equals(packet.getPosition());
if (actualPositionChanged) {
// Send book update before the player moves
session.getBookEditCache().checkForSend();
// Ignore movement packets until Bedrock's position matches the teleported position
if (session.getUnconfirmedTeleport() != null) {
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
return;
}
}
if (entity.getBedPosition() != null) {
@ -72,9 +71,11 @@ public final class BedrockMovePlayerTranslator {
float pitch = packet.getRotation().getX();
float headYaw = packet.getRotation().getY();
// shouldSendPositionReminder also increments a tick counter, so make sure it's always called.
boolean positionChanged = session.getInputCache().shouldSendPositionReminder() || actualPositionChanged;
boolean rotationChanged = entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw;
boolean hasVehicle = entity.getVehicle() != null;
// shouldSendPositionReminder also increments a tick counter, so make sure it's always called unless the player is on a vehicle.
boolean positionChanged = !hasVehicle && session.getInputCache().shouldSendPositionReminder() || actualPositionChanged;
boolean rotationChanged = hasVehicle || (entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw);
if (session.getLookBackScheduledFuture() != null) {
// Resend the rotation if it was changed by Geyser
@ -99,12 +100,6 @@ public final class BedrockMovePlayerTranslator {
session.sendDownstreamGamePacket(playerRotationPacket);
} else if (positionChanged) {
// World border collision will be handled by client vehicle
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
return;
}
if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT));
if (result != null) { // A null return value cancels the packet

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
@ -36,19 +37,25 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.BoatEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.protocol.bedrock.BedrockInventoryTransactionTranslator;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundMoveVehiclePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
@ -57,13 +64,17 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.Serv
import java.util.Set;
@Translator(packet = PlayerAuthInputPacket.class)
public class BedrockPlayerAuthInputTranslator extends PacketTranslator<PlayerAuthInputPacket> {
public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<PlayerAuthInputPacket> {
@Override
public void translate(GeyserSession session, PlayerAuthInputPacket packet) {
SessionPlayerEntity entity = session.getPlayerEntity();
boolean wasJumping = session.getInputCache().wasJumping();
session.getInputCache().processInputs(packet);
processVehicleInput(session, packet, wasJumping);
BedrockMovePlayerTranslator.translate(session, packet);
Set<PlayerAuthInputData> inputData = packet.getInputData();
@ -202,4 +213,94 @@ public class BedrockPlayerAuthInputTranslator extends PacketTranslator<PlayerAut
session.getGeyser().getLogger().error("Unhandled item use transaction type!");
}
}
private static void processVehicleInput(GeyserSession session, PlayerAuthInputPacket packet, boolean wasJumping) {
Entity vehicle = session.getPlayerEntity().getVehicle();
if (vehicle == null) {
return;
}
if (vehicle instanceof ClientVehicle) {
session.getPlayerEntity().setVehicleInput(packet.getAnalogMoveVector());
}
boolean sendMovement = false;
if (vehicle instanceof AbstractHorseEntity && !(vehicle instanceof LlamaEntity)) {
sendMovement = vehicle.isOnGround();
} else if (vehicle instanceof BoatEntity) {
if (vehicle.getPassengers().size() == 1) {
// The player is the only rider
sendMovement = true;
} else {
// Check if the player is the front rider
if (session.getPlayerEntity().isRidingInFront()) {
sendMovement = true;
}
}
}
if (vehicle instanceof AbstractHorseEntity) {
// Behavior verified as of Java Edition 1.21.3
int currentJumpingTicks = session.getInputCache().getJumpingTicks();
if (currentJumpingTicks < 0) {
session.getInputCache().setJumpingTicks(++currentJumpingTicks);
if (currentJumpingTicks == 0) {
session.getPlayerEntity().setVehicleJumpStrength(0);
}
}
boolean holdingJump = packet.getInputData().contains(PlayerAuthInputData.JUMPING);
if (wasJumping && !holdingJump) {
// Jump released
// Yes, I'm fairly certain that entity ID is correct.
session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(session.getPlayerEntity().getEntityId(),
PlayerState.START_HORSE_JUMP, MathUtils.floor(session.getInputCache().getJumpScale() * 100f)));
session.getInputCache().setJumpingTicks(-10);
} else if (!wasJumping && holdingJump) {
session.getInputCache().setJumpingTicks(0);
session.getInputCache().setJumpScale(0);
} else if (holdingJump) {
session.getInputCache().setJumpingTicks(++currentJumpingTicks);
if (currentJumpingTicks < 10) {
session.getInputCache().setJumpScale(session.getInputCache().getJumpScale() * 0.1F);
} else {
session.getInputCache().setJumpScale(0.8f + 2.0f / (currentJumpingTicks - 9) * 0.1f);
}
}
} else {
session.getInputCache().setJumpScale(0);
}
if (sendMovement) {
Vector3f vehiclePosition = packet.getPosition();
Vector2f vehicleRotation = packet.getVehicleRotation();
if (vehicleRotation == null) {
return; // If the client just got in or out of a vehicle for example.
}
if (session.getWorldBorder().isPassingIntoBorderBoundaries(vehiclePosition, false)) {
Vector3f position = vehicle.getPosition();
if (vehicle instanceof BoatEntity boat) {
// Undo the changes usually applied to the boat
boat.moveAbsoluteWithoutAdjustments(position, vehicle.getYaw(), vehicle.isOnGround(), true);
} else {
// This doesn't work if teleported is false
vehicle.moveAbsolute(position,
vehicle.getYaw(), vehicle.getPitch(), vehicle.getHeadYaw(),
vehicle.isOnGround(), true);
}
return;
}
if (vehicle instanceof BoatEntity && !vehicle.isOnGround()) {
// Remove some Y position to prevents boats flying up
vehiclePosition = vehiclePosition.down(vehicle.getDefinition().offset());
}
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(
vehiclePosition.getX(), vehiclePosition.getY(), vehiclePosition.getZ(),
vehicleRotation.getY() - 90, vehiclePosition.getX() // TODO I wonder if this is related to the horse spinning bugs...
);
session.sendDownstreamGamePacket(moveVehiclePacket);
}
}
}

View file

@ -105,6 +105,17 @@ public class MathUtils {
return floatNumber > truncated ? truncated + 1 : truncated;
}
/**
* Round the given float to the previous whole number
*
* @param floatNumber Float to round
* @return Rounded number
*/
public static int floor(float floatNumber) {
int truncated = (int) floatNumber;
return floatNumber < truncated ? truncated - 1 : truncated;
}
/**
* If number is greater than the max, set it to max, and if number is lower than low, set it to low.
*