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 363ce6008..4c5cb9f8a 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 @@ -41,7 +41,9 @@ import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; +import org.geysermc.geyser.entity.vehicle.HappyGhastVehicleComponent; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.registry.type.ItemMapping; @@ -510,7 +512,14 @@ public class LivingEntity extends Entity { } } case ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); - case FLYING_SPEED -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED)); + case FLYING_SPEED -> { + AttributeData attributeData = calculateAttribute(javaAttribute, GeyserAttributeType.FLYING_SPEED); + newAttributes.add(attributeData); + if (this instanceof HappyGhastEntity happyGhast && + happyGhast.getVehicleComponent() instanceof HappyGhastVehicleComponent vehicleComponent) { + vehicleComponent.setFlyingSpeed(attributeData.getValue()); + } + } case FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE)); case KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE)); case JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH)); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HappyGhastEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HappyGhastEntity.java index 38d6f1eb4..44ad0b7e8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HappyGhastEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/HappyGhastEntity.java @@ -27,9 +27,15 @@ package org.geysermc.geyser.entity.type.living.animal; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +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.entity.vehicle.HappyGhastVehicleComponent; +import org.geysermc.geyser.entity.vehicle.VehicleComponent; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.session.GeyserSession; @@ -43,20 +49,30 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import java.util.UUID; -public class HappyGhastEntity extends AnimalEntity { +public class HappyGhastEntity extends AnimalEntity implements ClientVehicle { + + private final HappyGhastVehicleComponent vehicleComponent = new HappyGhastVehicleComponent(this, 0.0f); + private boolean staysStill; + private float speed; + public HappyGhastEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // BDS 1.21.90 setFlag(EntityFlag.CAN_FLY, true); - setFlag(EntityFlag.TAMED, true); setFlag(EntityFlag.CAN_WALK, true); + setFlag(EntityFlag.TAMED, true); + setFlag(EntityFlag.BODY_ROTATION_ALWAYS_FOLLOWS_HEAD, true); + setFlag(EntityFlag.COLLIDABLE, true); setFlag(EntityFlag.WASD_AIR_CONTROLLED, true); setFlag(EntityFlag.DOES_SERVER_AUTH_ONLY_DISMOUNT, true); - // TODO: verify which flags are necessary - - setAirSupply(100); + propertyManager.add("minecraft:can_move", true); } @Override @@ -71,6 +87,7 @@ public class HappyGhastEntity extends AnimalEntity { } public void setStaysStill(BooleanEntityMetadata entityMetadata) { + staysStill = entityMetadata.getPrimitiveValue(); propertyManager.add("minecraft:can_move", !entityMetadata.getPrimitiveValue()); updateBedrockEntityProperties(); } @@ -124,4 +141,38 @@ public class HappyGhastEntity extends AnimalEntity { } } } + + @Override + public VehicleComponent getVehicleComponent() { + return vehicleComponent; + } + + @Override + public Vector2f getAdjustedInput(Vector2f input) { + // not used; calculations look a bit different for the happy ghast + return Vector2f.ZERO; + } + + @Override + public float getVehicleSpeed() { + return speed; // TODO this doesnt seem right? + } + + @Override + public boolean isClientControlled() { + // TODO proper check, just lazy + if (body == null) { + return false; + } + // TODO must have ai check + if (staysStill) { + return false; + } + + return getFirstPassenger() instanceof SessionPlayerEntity; + } + + private Entity getFirstPassenger() { + return passengers.isEmpty() ? null : passengers.get(0); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java index e6aaf1daa..00ea802df 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/ClientVehicle.java @@ -30,10 +30,13 @@ import org.cloudburstmc.math.vector.Vector2f; public interface ClientVehicle { VehicleComponent getVehicleComponent(); + // LivingEntity#getRiddenInput Vector2f getAdjustedInput(Vector2f input); + // MojMap LivingEntity#getRiddenSpeed float getVehicleSpeed(); + // MojMap Mob#getControllingPassenger boolean isClientControlled(); default boolean canWalkOnLava() { diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/HappyGhastVehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/HappyGhastVehicleComponent.java new file mode 100644 index 000000000..92f219d44 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/HappyGhastVehicleComponent.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity.vehicle; + +import lombok.Setter; +import org.cloudburstmc.math.vector.Vector2f; +import org.cloudburstmc.math.vector.Vector3f; +import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity; + +public class HappyGhastVehicleComponent extends VehicleComponent { + + @Setter + private float flyingSpeed; + + public HappyGhastVehicleComponent(HappyGhastEntity vehicle, float stepHeight) { + super(vehicle, stepHeight); + } + + protected Vector3f getInputVelocity(VehicleContext ctx, float speed) { + Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput(); + input = input.mul(0.98f); // ? + + float x = input.getX(); + float y = 0.0f; + float z = 0.0f; + + float playerZ = input.getY(); + if (playerZ != 0.0F) { + float i = Mth.cos(player.getXRot() * (float) (Math.PI / 180.0)); + float j = -Mth.sin(player.getXRot() * (float) (Math.PI / 180.0)); + if (playerZ < 0.0F) { + i *= -0.5F; + j *= -0.5F; + } + + y = j; + z = i; + } + + if (session.isJumping()) { + y += 0.5F; + } + + return Vector3f.from((double) x, (double) y, (double)z).mul(3.9F * flyingSpeed); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 388162a49..c6797ab79 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -39,6 +39,7 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.TextDisplayEntity; import org.geysermc.geyser.entity.type.living.ArmorStandEntity; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; +import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity; import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; @@ -221,6 +222,13 @@ public final class EntityUtils { } } } + case HAPPY_GHAST -> { + // TODO seat index matters here, likely + // 0.0, 5.02001, 1.7 BDS + xOffset = 0; + yOffset = 3.4f; + zOffset = 1.7f; + } } if (mount instanceof ChestBoatEntity) { xOffset = 0.15F; @@ -268,17 +276,30 @@ public final class EntityUtils { } public static void updateRiderRotationLock(Entity passenger, Entity mount, boolean isRiding) { - if (isRiding && mount instanceof BoatEntity) { - // Head rotation is locked while riding in a boat - passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, true); - passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 90f); - passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_HAS_ROTATION, true); - passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, -90f); + if (isRiding) { + if (mount instanceof BoatEntity) { + // Head rotation is locked while riding in a boat + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, true); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 90f); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_HAS_ROTATION, true); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, -90f); + } else if (mount instanceof HappyGhastEntity) { + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, false); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 181f); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_THIRD_PERSON_CAMERA_RADIUS, 8f); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_CAMERA_RELAX_DISTANCE_SMOOTHING, 6f); + + passenger.getDirtyMetadata().put(EntityDataTypes.CONTROLLING_RIDER_SEAT_INDEX, (byte) 0); + } } else { passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, false); passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 0f); passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_HAS_ROTATION, false); passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, 0f); + // TODO what are defaults here??? + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_THIRD_PERSON_CAMERA_RADIUS, 8f); + passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_CAMERA_RELAX_DISTANCE_SMOOTHING, 6f); + passenger.getDirtyMetadata().put(EntityDataTypes.CONTROLLING_RIDER_SEAT_INDEX, (byte) 0); } }