From 7b927169866ebf1661f345479a35bd28dc2e4d66 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Fri, 12 Dec 2025 00:08:12 +0700 Subject: [PATCH] Implement proper nautilus vehicle movement. --- .../geyser/entity/EntityDefinitions.java | 2 +- .../geyser/entity/type/LivingEntity.java | 5 ++ .../nautilus/AbstractNautilusEntity.java | 41 +++++---- .../vehicle/NautilusVehicleComponent.java | 83 ++++++++++++------- .../entity/vehicle/VehicleComponent.java | 12 ++- .../BedrockPlayerAuthInputTranslator.java | 4 +- .../org/geysermc/geyser/util/MathUtils.java | 11 +++ 7 files changed, 112 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 217dac8ad..d84b9d1a7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -1234,7 +1234,7 @@ public final class EntityDefinitions { { EntityDefinition abstractNautilusBase = EntityDefinition.inherited(null, tameableEntityBase) // No factory, is abstract .width(0.95f).height(0.875f) - .addTranslator(null) + .addTranslator(MetadataTypes.BOOLEAN, AbstractNautilusEntity::setBoost) .build(); NAUTILUS = EntityDefinition.inherited(NautilusEntity::new, abstractNautilusBase) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index d59cd0842..1d0c22800 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -536,6 +536,11 @@ public class LivingEntity extends Entity { clientVehicle.getVehicleComponent().setWaterMovementEfficiency(AttributeUtils.calculateValue(javaAttribute)); } } + case MOVEMENT_EFFICIENCY -> { + if (this instanceof ClientVehicle clientVehicle) { + clientVehicle.getVehicleComponent().setMovementEfficiency(AttributeUtils.calculateValue(javaAttribute)); + } + } } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/nautilus/AbstractNautilusEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/nautilus/AbstractNautilusEntity.java index 317228189..9857476b7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/nautilus/AbstractNautilusEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/nautilus/AbstractNautilusEntity.java @@ -37,16 +37,19 @@ import org.geysermc.geyser.entity.type.living.animal.tameable.TameableEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.entity.vehicle.NautilusVehicleComponent; import org.geysermc.geyser.entity.vehicle.VehicleComponent; +import org.geysermc.geyser.input.InputLocksFlag; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.enchantment.EnchantmentComponent; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.level.block.Fluid; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.session.cache.tags.Tag; import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.ItemUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; @@ -112,8 +115,27 @@ public abstract class AbstractNautilusEntity extends TameableEntity implements C return super.testMobInteraction(hand, itemInHand); } - public void setBoost(IntEntityMetadata entityMetadata) { - vehicleComponent.startBoost(entityMetadata.getPrimitiveValue()); + @Override + protected void updateSaddled(boolean saddled) { + setFlag(EntityFlag.CAN_DASH, saddled); + super.updateSaddled(saddled); + + if (this.passengers.contains(session.getPlayerEntity())) { + // We want to allow player to press jump again if pressing jump doesn't dismount the entity. + this.session.setLockInput(InputLocksFlag.JUMP, this.doesJumpDismount()); + this.session.updateInputLocks(); + } + } + + @Override + public boolean doesJumpDismount() { + return !this.getFlag(EntityFlag.SADDLED); + } + + public void setBoost(BooleanEntityMetadata entityMetadata) { + if (entityMetadata.getPrimitiveValue()) { + vehicleComponent.setDashCooldown(40); + } } @Override @@ -137,25 +159,16 @@ public abstract class AbstractNautilusEntity extends TameableEntity implements C } } - if (session.getInputCache().wasJumping()) { - y += 0.5f; - } - - return Vector3f.from(x, y, z).mul(3.9f * vehicleComponent.getMoveSpeed()); + return Vector3f.from(x, y, z); } @Override public float getVehicleSpeed() { - return 0.0f; // Unused + return vehicleComponent.isInWater() ? 0.0325F * vehicleComponent.getMoveSpeed() : 0.02F * vehicleComponent.getMoveSpeed(); } @Override public boolean isClientControlled() { - return false; - } - - @Override - public boolean canClimb() { - return false; + return !this.passengers.isEmpty() && this.passengers.get(0) == session.getPlayerEntity(); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/NautilusVehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/NautilusVehicleComponent.java index 7d32f116d..6fe282f7e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/vehicle/NautilusVehicleComponent.java +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/NautilusVehicleComponent.java @@ -25,49 +25,76 @@ package org.geysermc.geyser.entity.vehicle; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.type.living.animal.nautilus.AbstractNautilusEntity; -import org.geysermc.geyser.level.block.Fluid; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.util.MathUtils; -public class NautilusVehicleComponent extends BoostableVehicleComponent { +public class NautilusVehicleComponent extends VehicleComponent { + private int dashCooldown; + public NautilusVehicleComponent(AbstractNautilusEntity vehicle, float stepHeight, float defSpeed) { super(vehicle, stepHeight); this.moveSpeed = defSpeed; } + @Override + public boolean isPushedByFluid() { + return false; + } + + @Override + public void tickVehicle() { + vehicle.setFlag(EntityFlag.CAN_DASH, vehicle.getFlag(EntityFlag.SADDLED)); + vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, this.dashCooldown > 0); + vehicle.updateBedrockMetadata(); + + super.tickVehicle(); + if (this.dashCooldown > 0) { + this.dashCooldown--; + } + } + + @Override + protected Vector3f getInputVector(VehicleComponent.VehicleContext ctx, float speed, Vector3f input) { + Vector3f inputVelocity = super.getInputVector(ctx, speed, input); + + SessionPlayerEntity player = vehicle.getSession().getPlayerEntity(); + float jumpStrength = player.getVehicleJumpStrength(); + player.setVehicleJumpStrength(0); + + if (this.dashCooldown <= 0 && jumpStrength > 0) { + final Vector3f viewVector = MathUtils.calculateViewVector(vehicle.getPitch(), vehicle.getYaw()); + + float movementMultiplier = getVelocityMultiplier(ctx); + float strength = (float) (movementMultiplier + movementEfficiency * (1 - movementMultiplier)); + float actualJumpStrength = (jumpStrength >= 90) ? 1.0F : (0.4F + 0.4F * jumpStrength / 90.0F); + + inputVelocity = inputVelocity.add(viewVector.mul(((this.isInWater() ? 1.2F : 0.5F) * actualJumpStrength) * getMoveSpeed() * strength)); + setDashCooldown(40); + } + + return inputVelocity; + } + @Override protected void updateRotation() { - float yaw = vehicle.getYaw() + MathUtils.wrapDegrees(getRiddenRotation().getX() - vehicle.getYaw()) * 0.08f; + float yaw = vehicle.getYaw() + MathUtils.wrapDegrees(getRiddenRotation().getX() - vehicle.getYaw()) * 0.5F; vehicle.setYaw(yaw); vehicle.setHeadYaw(yaw); } - /** - * Called every session tick while the player is mounted on the vehicle. - */ - public void tickVehicle() { - if (!vehicle.isClientControlled()) { - return; - } - - VehicleContext ctx = new VehicleContext(); - ctx.loadSurroundingBlocks(); - - // LivingEntity#travel - Fluid fluid = checkForFluid(ctx); - float drag = switch (fluid) { - case WATER -> 0.9f; // AbstractNautilus#travelInWater - case LAVA -> 0.5f; // LivingEntity#travelInLava - case EMPTY -> 1f; // TODO No drag it seems? Should probably check the block below, e.g. soul sand - }; - - travel(ctx, getRiddenSpeed(fluid)); - vehicle.setMotion(vehicle.getMotion().mul(drag)); + @Override + protected void waterMovement(VehicleComponent.VehicleContext ctx) { + travel(ctx, vehicle.getVehicleSpeed()); + this.vehicle.setMotion(this.vehicle.getMotion().mul(0.9f)); } - // AbstractNautilus#getRiddenSpeed - private float getRiddenSpeed(Fluid fluid) { - return fluid == Fluid.WATER ? 0.0325F * this.getMoveSpeed() : - 0.02F * this.getMoveSpeed(); + public void setDashCooldown(int cooldown) { + this.dashCooldown = this.dashCooldown == 0 ? cooldown : this.dashCooldown; + + vehicle.setFlag(EntityFlag.HAS_DASH_COOLDOWN, this.dashCooldown > 0); + vehicle.updateBedrockMetadata(); } } 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 33ce59797..e83039837 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 @@ -74,7 +74,7 @@ public class VehicleComponent { protected float moveSpeed; protected double gravity; @Getter @Setter - protected double waterMovementEfficiency; + protected double waterMovementEfficiency, movementEfficiency; protected int effectLevitation; protected boolean effectSlowFalling; protected boolean effectWeaving; @@ -85,6 +85,7 @@ public class VehicleComponent { this.moveSpeed = (float) AttributeType.Builtin.MOVEMENT_SPEED.getDef(); this.gravity = AttributeType.Builtin.GRAVITY.getDef(); this.waterMovementEfficiency = AttributeType.Builtin.WATER_MOVEMENT_EFFICIENCY.getDef(); + this.movementEfficiency = AttributeType.Builtin.WATER_MOVEMENT_EFFICIENCY.getDef(); double width = vehicle.getBoundingBoxWidth(); double height = vehicle.getBoundingBoxHeight(); @@ -163,6 +164,8 @@ public class VehicleComponent { // } + @Getter + private boolean inWater; /** * Called every session tick while the player is mounted on the vehicle. */ @@ -175,6 +178,7 @@ public class VehicleComponent { ctx.loadSurroundingBlocks(); ObjectDoublePair fluidHeight = updateFluidMovement(ctx); + inWater = fluidHeight.left() == Fluid.WATER; switch (fluidHeight.left()) { case WATER -> waterMovement(ctx); case LAVA -> { @@ -188,6 +192,10 @@ public class VehicleComponent { } } + public boolean isPushedByFluid() { + return true; + } + /** * Update the rotation of the vehicle. Should be called once per tick, and before getInputVector. */ @@ -330,7 +338,7 @@ public class VehicleComponent { fluidBlocks++; } - if (!totalVelocity.equals(Vector3d.ZERO)) { + if (!totalVelocity.equals(Vector3d.ZERO) && isPushedByFluid()) { Vector3f motion = vehicle.getMotion(); totalVelocity = javaNormalize(totalVelocity.mul(1.0 / fluidBlocks)); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index b2ccd4bf8..2248e3be2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -39,6 +39,8 @@ import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; 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.living.animal.nautilus.AbstractNautilusEntity; +import org.geysermc.geyser.entity.type.living.animal.nautilus.NautilusEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.physics.BoundingBox; @@ -249,7 +251,7 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator