1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Happy ghast movement

This commit is contained in:
chris
2025-06-25 19:44:01 +02:00
committed by GitHub
18 changed files with 427 additions and 84 deletions

View File

@@ -66,7 +66,7 @@ public class DisplayBaseEntity extends Entity {
this.setRiderSeatPosition(this.baseTranslation); this.setRiderSeatPosition(this.baseTranslation);
this.moveRelative(this.baseTranslation.getX(), this.baseTranslation.getY(), this.baseTranslation.getZ(), yaw, pitch, headYaw, false); this.moveRelative(this.baseTranslation.getX(), this.baseTranslation.getY(), this.baseTranslation.getZ(), yaw, pitch, headYaw, false);
} else { } else {
EntityUtils.updateMountOffset(this, this.vehicle, true, true, false); EntityUtils.updateMountOffset(this, this.vehicle, true, true, 0, 1);
this.updateBedrockMetadata(); this.updateBedrockMetadata();
} }
} }

View File

@@ -621,7 +621,7 @@ public class Entity implements GeyserEntity {
Entity passenger = passengers.get(i); Entity passenger = passengers.get(i);
if (passenger != null) { if (passenger != null) {
boolean rider = i == 0; boolean rider = i == 0;
EntityUtils.updateMountOffset(passenger, this, rider, true, passengers.size() > 1); EntityUtils.updateMountOffset(passenger, this, rider, true, i, passengers.size());
passenger.updateBedrockMetadata(); passenger.updateBedrockMetadata();
} }
} }
@@ -633,7 +633,7 @@ public class Entity implements GeyserEntity {
protected void updateMountOffset() { protected void updateMountOffset() {
if (vehicle != null) { if (vehicle != null) {
boolean rider = vehicle.getPassengers().get(0) == this; boolean rider = vehicle.getPassengers().get(0) == this;
EntityUtils.updateMountOffset(this, vehicle, rider, true, vehicle.getPassengers().size() > 1); EntityUtils.updateMountOffset(this, vehicle, rider, true, vehicle.getPassengers().indexOf(this), vehicle.getPassengers().size());
updateBedrockMetadata(); updateBedrockMetadata();
} }
} }

View File

@@ -41,7 +41,9 @@ import org.cloudburstmc.protocol.bedrock.packet.MobEquipmentPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; 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.ClientVehicle;
import org.geysermc.geyser.entity.vehicle.HappyGhastVehicleComponent;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
@@ -49,6 +51,7 @@ import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.MathUtils; import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
@@ -510,7 +513,13 @@ public class LivingEntity extends Entity {
} }
} }
case ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE)); 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 ghast && ghast.getVehicleComponent() instanceof HappyGhastVehicleComponent component) {
component.setFlyingSpeed(attributeData.getValue());
}
}
case FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE)); case FOLLOW_RANGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.FOLLOW_RANGE));
case KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE)); case KNOCKBACK_RESISTANCE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.KNOCKBACK_RESISTANCE));
case JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH)); case JUMP_STRENGTH -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.HORSE_JUMP_STRENGTH));
@@ -519,10 +528,41 @@ public class LivingEntity extends Entity {
setAttributeScale((float) AttributeUtils.calculateValue(javaAttribute)); setAttributeScale((float) AttributeUtils.calculateValue(javaAttribute));
updateBedrockMetadata(); updateBedrockMetadata();
} }
case WATER_MOVEMENT_EFFICIENCY -> {
if (this instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().setWaterMovementEfficiency(AttributeUtils.calculateValue(javaAttribute));
}
}
} }
} }
} }
protected boolean hasBodyArmor() {
return this.hasValidEquippableItemForSlot(EquipmentSlot.BODY);
}
private boolean hasValidEquippableItemForSlot(EquipmentSlot slot) {
// MojMap LivingEntity#hasItemInSlot
GeyserItemStack itemInSlot = equipment.get(slot);
if (itemInSlot != null) {
// MojMap LivingEntity#isEquippableInSlot
Equippable equippable = itemInSlot.getComponent(DataComponentTypes.EQUIPPABLE);
if (equippable != null) {
return slot == equippable.slot() &&
canUseSlot(slot) &&
EntityUtils.equipmentUsableByEntity(session, equippable, this.definition.entityType());
} else {
return slot == EquipmentSlot.MAIN_HAND && canUseSlot(EquipmentSlot.MAIN_HAND);
}
}
return false;
}
protected boolean canUseSlot(EquipmentSlot slot) {
return true;
}
/** /**
* Calculates the complete attribute value to send to Bedrock. Will be overriden if attributes need to be cached. * Calculates the complete attribute value to send to Bedrock. Will be overriden if attributes need to be cached.
*/ */

View File

@@ -27,36 +27,60 @@ package org.geysermc.geyser.entity.type.living.animal;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.TrigMath;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition; 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.inventory.GeyserItemStack;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag; import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public class HappyGhastEntity extends AnimalEntity { public class HappyGhastEntity extends AnimalEntity implements ClientVehicle {
public static final float[] X_OFFSETS = {0.0F, -1.7F, 0.0F, 1.7F};
public static final float[] Z_OFFSETS = {1.7F, 0.0F, -1.7F, 0.0F};
private final HappyGhastVehicleComponent vehicleComponent = new HappyGhastVehicleComponent(this, 0.0f);
private boolean staysStill;
public HappyGhastEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { 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); 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.CAN_FLY, true);
setFlag(EntityFlag.TAMED, true);
setFlag(EntityFlag.CAN_WALK, 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.WASD_AIR_CONTROLLED, true);
setFlag(EntityFlag.DOES_SERVER_AUTH_ONLY_DISMOUNT, true); setFlag(EntityFlag.DOES_SERVER_AUTH_ONLY_DISMOUNT, true);
// TODO: verify which flags are necessary propertyManager.add("minecraft:can_move", true);
setAirSupply(100);
} }
@Override @Override
@@ -71,6 +95,7 @@ public class HappyGhastEntity extends AnimalEntity {
} }
public void setStaysStill(BooleanEntityMetadata entityMetadata) { public void setStaysStill(BooleanEntityMetadata entityMetadata) {
staysStill = entityMetadata.getPrimitiveValue();
propertyManager.add("minecraft:can_move", !entityMetadata.getPrimitiveValue()); propertyManager.add("minecraft:can_move", !entityMetadata.getPrimitiveValue());
updateBedrockEntityProperties(); updateBedrockEntityProperties();
} }
@@ -124,4 +149,65 @@ public class HappyGhastEntity extends AnimalEntity {
} }
} }
} }
@Override
public VehicleComponent<?> getVehicleComponent() {
return vehicleComponent;
}
@Override
public Vector3f getRiddenInput(Vector2f input) {
float x = input.getX();
float y = 0.0f;
float z = 0.0f;
if (input.getY() != 0.0f) {
float pitch = session.getPlayerEntity().getPitch();
z = TrigMath.cos(pitch * TrigMath.DEG_TO_RAD);
y = -TrigMath.sin(pitch * TrigMath.DEG_TO_RAD);
if (input.getY() < 0.0f) {
z *= -0.5f;
y *= -0.5f;
}
}
if (session.getInputCache().wasJumping()) {
y += 0.5f;
}
return Vector3f.from(x, y, z).mul(3.9f * vehicleComponent.getFlyingSpeed());
}
@Override
public float getVehicleSpeed() {
return 1; // TODO this doesnt seem right?
}
@Override
public boolean isClientControlled() {
if (!hasBodyArmor() || getFlag(EntityFlag.NO_AI) || staysStill) {
return false;
}
return getFirstPassenger() instanceof SessionPlayerEntity;
}
private Entity getFirstPassenger() {
return passengers.isEmpty() ? null : passengers.get(0);
}
@Override
protected void updateAttribute(Attribute javaAttribute, List<AttributeData> newAttributes) {
super.updateAttribute(javaAttribute, newAttributes);
if (javaAttribute.getType() instanceof AttributeType.Builtin type) {
if (type == AttributeType.Builtin.CAMERA_DISTANCE) {
vehicleComponent.setCameraDistance((float) AttributeUtils.calculateValue(javaAttribute));
}
}
}
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
return slot != EquipmentSlot.BODY ? super.canUseSlot(slot) : this.isAlive() && !this.isBaby();
}
} }

View File

@@ -47,6 +47,7 @@ import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.InteractiveTag;
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.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@@ -169,8 +170,8 @@ public class StriderEntity extends AnimalEntity implements Tickable, ClientVehic
} }
@Override @Override
public Vector2f getAdjustedInput(Vector2f input) { public Vector3f getRiddenInput(Vector2f input) {
return Vector2f.UNIT_Y; return Vector3f.UNIT_Z;
} }
@Override @Override
@@ -195,4 +196,9 @@ public class StriderEntity extends AnimalEntity implements Tickable, ClientVehic
public boolean canWalkOnLava() { public boolean canWalkOnLava() {
return true; return true;
} }
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
return slot != EquipmentSlot.SADDLE ? super.canUseSlot(slot) : this.isAlive() && !this.isBaby();
}
} }

View File

@@ -48,6 +48,7 @@ import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@@ -128,8 +129,8 @@ public class PigEntity extends TemperatureVariantAnimal implements Tickable, Cli
} }
@Override @Override
public Vector2f getAdjustedInput(Vector2f input) { public Vector3f getRiddenInput(Vector2f input) {
return Vector2f.UNIT_Y; return Vector3f.UNIT_Z;
} }
@Override @Override
@@ -154,4 +155,9 @@ public class PigEntity extends TemperatureVariantAnimal implements Tickable, Cli
public JavaRegistryKey<BuiltInVariant> variantRegistry() { public JavaRegistryKey<BuiltInVariant> variantRegistry() {
return JavaRegistries.PIG_VARIANT; return JavaRegistries.PIG_VARIANT;
} }
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
return slot != EquipmentSlot.SADDLE ? super.canUseSlot(slot) : this.isAlive() && !this.isBaby();
}
} }

View File

@@ -45,6 +45,7 @@ import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag; import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag; import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
@@ -286,4 +287,13 @@ public class AbstractHorseEntity extends AnimalEntity {
return InteractionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
} }
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
if (slot != EquipmentSlot.SADDLE) {
return super.canUseSlot(slot);
} else {
return isAlive() && !isBaby() && getFlag(EntityFlag.TAMED);
}
}
} }

View File

@@ -155,8 +155,9 @@ public class CamelEntity extends AbstractHorseEntity implements ClientVehicle {
} }
@Override @Override
public Vector2f getAdjustedInput(Vector2f input) { public Vector3f getRiddenInput(Vector2f input) {
return input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f); input = input.mul(0.5f, input.getY() < 0 ? 0.25f : 1.0f);
return Vector3f.from(input.getX(), 0.0, input.getY());
} }
@Override @Override

View File

@@ -29,6 +29,7 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID; import java.util.UUID;
@@ -44,4 +45,9 @@ public class HorseEntity extends AbstractHorseEntity {
dirtyMetadata.put(EntityDataTypes.VARIANT, value & 255); dirtyMetadata.put(EntityDataTypes.VARIANT, value & 255);
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, (value >> 8) % 5); dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, (value >> 8) % 5);
} }
}
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
return true;
}
}

View File

@@ -35,6 +35,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.session.cache.tags.ItemTag;
import org.geysermc.geyser.session.cache.tags.Tag; import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.util.MathUtils; import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import java.util.UUID; import java.util.UUID;
@@ -61,4 +62,9 @@ public class LlamaEntity extends ChestedHorseEntity {
protected @Nullable Tag<Item> getFoodTag() { protected @Nullable Tag<Item> getFoodTag() {
return ItemTag.LLAMA_FOOD; return ItemTag.LLAMA_FOOD;
} }
@Override
protected boolean canUseSlot(EquipmentSlot slot) {
return true;
}
} }

View File

@@ -90,13 +90,13 @@ public class CamelVehicleComponent extends VehicleComponent<CamelEntity> {
} }
@Override @Override
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) { protected Vector3f getInputVector(VehicleContext ctx, float speed, Vector3f input) {
if (isStationary()) { if (isStationary()) {
return Vector3f.ZERO; return Vector3f.ZERO;
} }
SessionPlayerEntity player = vehicle.getSession().getPlayerEntity(); SessionPlayerEntity player = vehicle.getSession().getPlayerEntity();
Vector3f inputVelocity = super.getInputVelocity(ctx, speed); Vector3f inputVelocity = super.getInputVector(ctx, speed, input);
float jumpStrength = player.getVehicleJumpStrength(); float jumpStrength = player.getVehicleJumpStrength();
if (jumpStrength > 0) { if (jumpStrength > 0) {
@@ -117,11 +117,11 @@ public class CamelVehicleComponent extends VehicleComponent<CamelEntity> {
} }
@Override @Override
protected Vector2f getVehicleRotation() { protected Vector2f getRiddenRotation() {
if (isStationary()) { if (isStationary()) {
return Vector2f.from(vehicle.getYaw(), vehicle.getPitch()); return Vector2f.from(vehicle.getYaw(), vehicle.getPitch());
} }
return super.getVehicleRotation(); return super.getRiddenRotation();
} }
/** /**

View File

@@ -26,14 +26,18 @@
package org.geysermc.geyser.entity.vehicle; package org.geysermc.geyser.entity.vehicle;
import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
public interface ClientVehicle { public interface ClientVehicle {
VehicleComponent<?> getVehicleComponent(); VehicleComponent<?> getVehicleComponent();
Vector2f getAdjustedInput(Vector2f input); // MojMap LivingEntity#getRiddenInput
Vector3f getRiddenInput(Vector2f input);
// MojMap LivingEntity#getRiddenSpeed
float getVehicleSpeed(); float getVehicleSpeed();
// MojMap Mob#getControllingPassenger
boolean isClientControlled(); boolean isClientControlled();
default boolean canWalkOnLava() { default boolean canWalkOnLava() {

View File

@@ -0,0 +1,128 @@
/*
* 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.Getter;
import lombok.Setter;
import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.Fluid;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType;
@Setter
@Getter
public class HappyGhastVehicleComponent extends VehicleComponent<HappyGhastEntity> {
private float flyingSpeed;
private float cameraDistance;
public HappyGhastVehicleComponent(HappyGhastEntity vehicle, float stepHeight) {
super(vehicle, stepHeight);
// Happy Ghast has different defaults
flyingSpeed = 0.05f;
moveSpeed = 0.05f;
cameraDistance = 8.0f;
}
@Override
protected void updateRotation() {
float addYaw = MathUtils.wrapDegrees(getRiddenRotation().getX() - vehicle.getYaw()) * 0.08f;
vehicle.setYaw(vehicle.getYaw() + addYaw);
}
@Override
public void onMount() {
super.onMount();
SessionPlayerEntity playerEntity = vehicle.getSession().getPlayerEntity();
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, false);
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 181f);
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_THIRD_PERSON_CAMERA_RADIUS, cameraDistance);
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_CAMERA_RELAX_DISTANCE_SMOOTHING, cameraDistance * 0.75f);
playerEntity.getDirtyMetadata().put(EntityDataTypes.CONTROLLING_RIDER_SEAT_INDEX, (byte) 0);
}
@Override
public void onDismount() {
super.onDismount();
SessionPlayerEntity playerEntity = vehicle.getSession().getPlayerEntity();
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_THIRD_PERSON_CAMERA_RADIUS, (float) AttributeType.Builtin.CAMERA_DISTANCE.getDef());
playerEntity.getDirtyMetadata().put(EntityDataTypes.SEAT_CAMERA_RELAX_DISTANCE_SMOOTHING, cameraDistance * 0.75f);
playerEntity.getDirtyMetadata().put(EntityDataTypes.CONTROLLING_RIDER_SEAT_INDEX, (byte) 0);
}
/**
* 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#travelFlying
Fluid fluid = checkForFluid(ctx);
float drag = switch (fluid) {
case WATER -> 0.8f;
case LAVA -> 0.5f;
case EMPTY -> 0.91f;
};
// HappyGhast#travel
travel(ctx, flyingSpeed * 5.0f / 3.0f);
vehicle.setMotion(vehicle.getMotion().mul(drag));
}
private Fluid checkForFluid(VehicleContext ctx) {
Fluid result = Fluid.EMPTY;
BoundingBox box = boundingBox.clone();
box.expand(-0.001);
Vector3d min = box.getMin();
Vector3d max = box.getMax();
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ());
for (iter.reset(); iter.hasNext(); iter.next()) {
BlockState blockState = ctx.getBlock(iter);
if (blockState.is(Blocks.WATER)) {
return Fluid.WATER; // Water takes priority over lava
}
if (blockState.is(Blocks.LAVA)) {
result = Fluid.LAVA;
}
}
return result;
}
}

View File

@@ -26,6 +26,8 @@
package org.geysermc.geyser.entity.vehicle; package org.geysermc.geyser.entity.vehicle;
import it.unimi.dsi.fastutil.objects.ObjectDoublePair; import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.TrigMath; import org.cloudburstmc.math.TrigMath;
import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector2f;
@@ -64,20 +66,27 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
private static final float MIN_VELOCITY = 0.003f; private static final float MIN_VELOCITY = 0.003f;
protected final T vehicle; protected final T vehicle;
@Getter
protected final BoundingBox boundingBox; protected final BoundingBox boundingBox;
protected Vector2f lastSentRotation; // (yaw, pitch)
protected float stepHeight; protected float stepHeight;
@Getter @Setter
protected float moveSpeed; protected float moveSpeed;
protected double gravity; protected double gravity;
@Getter @Setter
protected double waterMovementEfficiency;
protected int effectLevitation; protected int effectLevitation;
protected boolean effectSlowFalling; protected boolean effectSlowFalling;
protected boolean effectWeaving; protected boolean effectWeaving;
public VehicleComponent(T vehicle, float stepHeight) { public VehicleComponent(T vehicle, float stepHeight) {
this.vehicle = vehicle; this.vehicle = vehicle;
this.lastSentRotation = Vector2f.from(vehicle.getYaw(), vehicle.getPitch());
this.stepHeight = stepHeight; this.stepHeight = stepHeight;
this.moveSpeed = (float) AttributeType.Builtin.MOVEMENT_SPEED.getDef(); this.moveSpeed = (float) AttributeType.Builtin.MOVEMENT_SPEED.getDef();
this.gravity = AttributeType.Builtin.GRAVITY.getDef(); this.gravity = AttributeType.Builtin.GRAVITY.getDef();
this.waterMovementEfficiency = AttributeType.Builtin.WATER_MOVEMENT_EFFICIENCY.getDef();
double width = vehicle.getBoundingBoxWidth(); double width = vehicle.getBoundingBoxWidth();
double height = vehicle.getBoundingBoxHeight(); double height = vehicle.getBoundingBoxHeight();
@@ -117,10 +126,6 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
boundingBox.translate(vec); boundingBox.translate(vec);
} }
public BoundingBox getBoundingBox() {
return this.boundingBox;
}
public void setEffect(Effect effect, int effectAmplifier) { public void setEffect(Effect effect, int effectAmplifier) {
switch (effect) { switch (effect) {
case LEVITATION -> effectLevitation = effectAmplifier + 1; case LEVITATION -> effectLevitation = effectAmplifier + 1;
@@ -137,14 +142,6 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
} }
} }
public void setMoveSpeed(float moveSpeed) {
this.moveSpeed = moveSpeed;
}
public float getMoveSpeed() {
return moveSpeed;
}
public void setStepHeight(float stepHeight) { public void setStepHeight(float stepHeight) {
this.stepHeight = MathUtils.clamp(stepHeight, 1.0f, 10.0f); this.stepHeight = MathUtils.clamp(stepHeight, 1.0f, 10.0f);
} }
@@ -193,6 +190,13 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
} }
} }
protected void updateRotation() {
Vector2f rot = getRiddenRotation();
vehicle.setYaw(rot.getX());
vehicle.setHeadYaw(rot.getX());
vehicle.setPitch(rot.getY());
}
/** /**
* Adds velocity of all colliding fluids to the vehicle, and returns the height of the fluid to use for movement. * Adds velocity of all colliding fluids to the vehicle, and returns the height of the fluid to use for movement.
* *
@@ -208,6 +212,7 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ()); BlockPositionIterator iter = BlockPositionIterator.fromMinMax(min.getFloorX(), min.getFloorY(), min.getFloorZ(), max.getFloorX(), max.getFloorY(), max.getFloorZ());
// Mojmap Entity#updateInWaterStateAndDoFluidPushing
double waterHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.WATER, 0.014, min.getY()); double waterHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.WATER, 0.014, min.getY());
double lavaHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.LAVA, vehicle.getSession().getDimensionType().ultrawarm() ? 0.007 : 0.007 / 3, min.getY()); double lavaHeight = getFluidHeightAndApplyMovement(ctx, iter, Fluid.LAVA, vehicle.getSession().getDimensionType().ultrawarm() ? 0.007 : 0.007 / 3, min.getY());
@@ -341,11 +346,12 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
} }
/** /**
* Java edition returns the zero vector if the length of the input vector is less than 0.0001 * Java edition returns the zero vector if the length of the input vector is less than 0.00001f
*/ */
protected Vector3d javaNormalize(Vector3d vec) { protected Vector3d javaNormalize(Vector3d vec) {
double len = vec.length(); double len = vec.length();
return len < 1.0E-4 ? Vector3d.ZERO : Vector3d.from(vec.getX() / len, vec.getY() / len, vec.getZ() / len); // Used to be 1.0E-4
return len < 1.0E-5F ? Vector3d.ZERO : Vector3d.from(vec.getX() / len, vec.getY() / len, vec.getZ() / len);
} }
protected float getWorldFluidHeight(Fluid fluidType, int blockId) { protected float getWorldFluidHeight(Fluid fluidType, int blockId) {
@@ -373,6 +379,7 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
return BlockUtils.getCollision(adjacentBlockId) instanceof SolidCollision; return BlockUtils.getCollision(adjacentBlockId) instanceof SolidCollision;
} }
// Mojmap: LivingEntity#travelInFluid
protected void waterMovement(VehicleContext ctx) { protected void waterMovement(VehicleContext ctx) {
double gravity = getGravity(); double gravity = getGravity();
float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier float drag = vehicle.getFlag(EntityFlag.SPRINTING) ? 0.9f : 0.8f; // 0.8f: getBaseMovementSpeedMultiplier
@@ -380,6 +387,21 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
boolean falling = vehicle.getMotion().getY() <= 0; boolean falling = vehicle.getMotion().getY() <= 0;
// NOT IMPLEMENTED: depth strider and dolphins grace // NOT IMPLEMENTED: depth strider and dolphins grace
// float g = 0.02f;
// float waterMovementEfficiencyMultiplier = (float) waterMovementEfficiency;
// if (!vehicle.isOnGround()) {
// // TODO test
// waterMovementEfficiencyMultiplier *= 0.5f;
// }
//
// if (waterMovementEfficiencyMultiplier > 0.0F) {
// drag += (0.54600006F - drag) * waterMovementEfficiencyMultiplier;
// g += (this.getSpeed() - g) * waterMovementEfficiencyMultiplier;
// }
// if (this.hasEffect(MobEffects.DOLPHINS_GRACE)) {
// drag = 0.96F;
// }
boolean horizontalCollision = travel(ctx, 0.02f); boolean horizontalCollision = travel(ctx, 0.02f);
@@ -570,6 +592,7 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
* *
* @return true if there was a horizontal collision * @return true if there was a horizontal collision
*/ */
// Mojmap: LivingEntity#moveRelative / LivingEntity#move
protected boolean travel(VehicleContext ctx, float speed) { protected boolean travel(VehicleContext ctx, float speed) {
Vector3f motion = vehicle.getMotion(); Vector3f motion = vehicle.getMotion();
@@ -582,9 +605,13 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
Math.abs(motion.getZ()) < MIN_VELOCITY ? 0 : motion.getZ() Math.abs(motion.getZ()) < MIN_VELOCITY ? 0 : motion.getZ()
); );
updateRotation();
Vector2f playerInput = vehicle.getSession().getPlayerEntity().getVehicleInput();
Vector3f riddenInput = vehicle.getRiddenInput(playerInput.mul(0.98f));
// !isImmobile // !isImmobile
if (vehicle.isAlive()) { if (vehicle.isAlive()) {
motion = motion.add(getInputVelocity(ctx, speed)); motion = motion.add(getInputVector(ctx, speed, riddenInput));
} }
Vector3f movementMultiplier = getBlockMovementMultiplier(ctx); Vector3f movementMultiplier = getBlockMovementMultiplier(ctx);
@@ -670,41 +697,30 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
return false; return false;
} }
/** protected Vector3f getInputVector(VehicleContext ctx, float speed, Vector3f input) {
* Translates the player's input into velocity. double lenSquared = input.lengthSquared();
* if (lenSquared < 1.0E-7) {
* @param ctx context return Vector3f.ZERO;
* @param speed multiplier for input }
* @return velocity
*/ if (lenSquared > 1.0f) {
protected Vector3f getInputVelocity(VehicleContext ctx, float speed) { input = input.normalize();
Vector2f input = vehicle.getSession().getPlayerEntity().getVehicleInput(); }
input = input.mul(0.98f);
input = vehicle.getAdjustedInput(input);
input = normalizeInput(input);
input = input.mul(speed); input = input.mul(speed);
// Match player rotation // Match vehicle rotation
float yaw = vehicle.getSession().getPlayerEntity().getYaw(); float yaw = vehicle.getYaw();
float sin = TrigMath.sin(yaw * TrigMath.DEG_TO_RAD); float sin = TrigMath.sin(yaw * TrigMath.DEG_TO_RAD);
float cos = TrigMath.cos(yaw * TrigMath.DEG_TO_RAD); float cos = TrigMath.cos(yaw * TrigMath.DEG_TO_RAD);
return Vector3f.from(input.getX() * cos - input.getY() * sin, 0, input.getY() * cos + input.getX() * sin); return Vector3f.from(input.getX() * cos - input.getZ() * sin, input.getY(), input.getZ() * cos + input.getX() * sin);
}
protected Vector2f normalizeInput(Vector2f input) {
float lenSquared = input.lengthSquared();
if (lenSquared < 1.0E-7) {
return Vector2f.ZERO;
} else if (lenSquared > 1.0) {
return input.normalize();
}
return input;
} }
/** /**
* Gets the rotation to use for the vehicle. This is based on the player's head rotation. * Gets the rotation to use for the vehicle. This is based on the player's head rotation.
*
* @return (yaw, pitch)
*/ */
protected Vector2f getVehicleRotation() { protected Vector2f getRiddenRotation() {
LivingEntity player = vehicle.getSession().getPlayerEntity(); LivingEntity player = vehicle.getSession().getPlayerEntity();
return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f); return Vector2f.from(player.getYaw(), player.getPitch() * 0.5f);
} }
@@ -717,7 +733,6 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
*/ */
protected void moveVehicle(Vector3d javaPos) { protected void moveVehicle(Vector3d javaPos) {
Vector3f bedrockPos = javaPos.toFloat(); Vector3f bedrockPos = javaPos.toFloat();
Vector2f rotation = getVehicleRotation();
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId()); moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId());
@@ -740,27 +755,25 @@ public class VehicleComponent<T extends LivingEntity & ClientVehicle> {
} }
vehicle.setPosition(bedrockPos); vehicle.setPosition(bedrockPos);
if (vehicle.getYaw() != rotation.getX()) { if (vehicle.getYaw() != lastSentRotation.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(rotation.getX()); moveEntityDeltaPacket.setYaw(vehicle.getYaw());
vehicle.setYaw(rotation.getX());
} }
if (vehicle.getPitch() != rotation.getY()) { if (vehicle.getPitch() != lastSentRotation.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(rotation.getY()); moveEntityDeltaPacket.setPitch(vehicle.getPitch());
vehicle.setPitch(rotation.getY());
} }
if (vehicle.getHeadYaw() != rotation.getX()) { // Same as yaw if (vehicle.getHeadYaw() != lastSentRotation.getX()) { // Same as yaw
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(rotation.getX()); moveEntityDeltaPacket.setHeadYaw(vehicle.getYaw());
vehicle.setHeadYaw(rotation.getX());
} }
lastSentRotation = Vector2f.from(vehicle.getYaw(), vehicle.getPitch());
if (!moveEntityDeltaPacket.getFlags().isEmpty()) { if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket); vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket);
} }
ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos, rotation.getX(), rotation.getY(), vehicle.isOnGround()); ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos, vehicle.getYaw(), vehicle.getPitch(), vehicle.isOnGround());
vehicle.getSession().sendDownstreamPacket(moveVehiclePacket); vehicle.getSession().sendDownstreamPacket(moveVehiclePacket);
} }

View File

@@ -213,9 +213,6 @@ public class MapHasher<Type> {
} }
public HashCode build() { public HashCode build() {
if (unhashed != null) {
System.out.println(unhashed);
}
return encoder.map(map); return encoder.map(map);
} }
} }

View File

@@ -48,9 +48,12 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.session.dialog.Dialog;
import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@@ -64,6 +67,14 @@ public class JavaRegistries {
Block::javaId, Block::javaIdentifier, key -> Optional.ofNullable(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get(key.asString())).orElse(-1)); Block::javaId, Block::javaIdentifier, key -> Optional.ofNullable(BlockRegistries.JAVA_IDENTIFIER_TO_ID.get(key.asString())).orElse(-1));
public static final JavaRegistryKey<Item> ITEM = createHardcoded("item", Registries.JAVA_ITEMS, public static final JavaRegistryKey<Item> ITEM = createHardcoded("item", Registries.JAVA_ITEMS,
Item::javaId, Item::javaKey, key -> Optional.ofNullable(Registries.JAVA_ITEM_IDENTIFIERS.get(key.asString())).map(Item::javaId).orElse(-1)); Item::javaId, Item::javaKey, key -> Optional.ofNullable(Registries.JAVA_ITEM_IDENTIFIERS.get(key.asString())).map(Item::javaId).orElse(-1));
public static JavaRegistryKey<EntityType> ENTITY_TYPE = createHardcoded("entity_type", Arrays.asList(EntityType.values()), EntityType::ordinal,
type -> MinecraftKey.key(type.name().toLowerCase(Locale.ROOT)), key -> {
try {
return EntityType.valueOf(key.value().toUpperCase(Locale.ROOT)).ordinal();
} catch (IllegalArgumentException exception) {
return -1; // Non-existent entity type
}
});
public static final JavaRegistryKey<ChatType> CHAT_TYPE = create("chat_type"); public static final JavaRegistryKey<ChatType> CHAT_TYPE = create("chat_type");
public static final JavaRegistryKey<JavaDimension> DIMENSION_TYPE = create("dimension_type"); public static final JavaRegistryKey<JavaDimension> DIMENSION_TYPE = create("dimension_type");
@@ -95,6 +106,11 @@ public class JavaRegistries {
private static <T> JavaRegistryKey<T> createHardcoded(String key, ListRegistry<T> registry, RegistryNetworkMapper<T> networkSerializer, private static <T> JavaRegistryKey<T> createHardcoded(String key, ListRegistry<T> registry, RegistryNetworkMapper<T> networkSerializer,
RegistryIdentifierMapper<T> identifierMapper, RegistryIdMapper idMapper) { RegistryIdentifierMapper<T> identifierMapper, RegistryIdMapper idMapper) {
return createHardcoded(key, registry.get(), networkSerializer, identifierMapper, idMapper);
}
private static <T> JavaRegistryKey<T> createHardcoded(String key, List<T> registry, RegistryNetworkMapper<T> networkSerializer,
RegistryIdentifierMapper<T> identifierMapper, RegistryIdMapper idMapper) {
return create(key, new HardcodedLookup<>(registry, networkSerializer, identifierMapper, idMapper)); return create(key, new HardcodedLookup<>(registry, networkSerializer, identifierMapper, idMapper));
} }
@@ -130,7 +146,7 @@ public class JavaRegistries {
int get(Key key); int get(Key key);
} }
private record HardcodedLookup<T>(ListRegistry<T> registry, RegistryNetworkMapper<T> networkMapper, RegistryIdentifierMapper<T> identifierMapper, private record HardcodedLookup<T>(List<T> registry, RegistryNetworkMapper<T> networkMapper, RegistryIdentifierMapper<T> identifierMapper,
RegistryIdMapper idMapper) implements JavaRegistryKey.RegistryLookup<T> { RegistryIdMapper idMapper) implements JavaRegistryKey.RegistryLookup<T> {
@Override @Override

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.entity; package org.geysermc.geyser.translator.protocol.java.entity;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
@@ -50,7 +51,9 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
// Handle new/existing passengers // Handle new/existing passengers
List<Entity> newPassengers = new ArrayList<>(); List<Entity> newPassengers = new ArrayList<>();
for (int passengerId : packet.getPassengerIds()) { int @NonNull [] passengerIds = packet.getPassengerIds();
for (int i = 0; i < passengerIds.length; i++) {
int passengerId = passengerIds[i];
Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId); Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId);
if (passenger == session.getPlayerEntity()) { if (passenger == session.getPlayerEntity()) {
session.getPlayerEntity().setVehicle(entity); session.getPlayerEntity().setVehicle(entity);
@@ -76,13 +79,15 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
passenger.setVehicle(entity); passenger.setVehicle(entity);
EntityUtils.updateRiderRotationLock(passenger, entity, true); EntityUtils.updateRiderRotationLock(passenger, entity, true);
EntityUtils.updateMountOffset(passenger, entity, rider, true, (packet.getPassengerIds().length > 1)); EntityUtils.updateMountOffset(passenger, entity, rider, true, i, packet.getPassengerIds().length);
// Force an update to the passenger metadata // Force an update to the passenger metadata
passenger.updateBedrockMetadata(); passenger.updateBedrockMetadata();
} }
// Handle passengers that were removed // Handle passengers that were removed
for (Entity passenger : entity.getPassengers()) { List<Entity> passengers = entity.getPassengers();
for (int i = 0; i < passengers.size(); i++) {
Entity passenger = passengers.get(i);
if (passenger == null) { if (passenger == null) {
continue; continue;
} }
@@ -93,7 +98,7 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
passenger.setVehicle(null); passenger.setVehicle(null);
EntityUtils.updateRiderRotationLock(passenger, entity, false); EntityUtils.updateRiderRotationLock(passenger, entity, false);
EntityUtils.updateMountOffset(passenger, entity, false, false, (packet.getPassengerIds().length > 1)); EntityUtils.updateMountOffset(passenger, entity, false, false, i, packet.getPassengerIds().length);
// Force an update to the passenger metadata // Force an update to the passenger metadata
passenger.updateBedrockMetadata(); passenger.updateBedrockMetadata();

View File

@@ -39,15 +39,19 @@ import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.TextDisplayEntity; import org.geysermc.geyser.entity.type.TextDisplayEntity;
import org.geysermc.geyser.entity.type.living.ArmorStandEntity; 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.AnimalEntity;
import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity; import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.session.cache.tags.GeyserHolderSet;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
import java.util.Locale; import java.util.Locale;
@@ -166,7 +170,7 @@ public final class EntityUtils {
/** /**
* Adjust an entity's height if they have mounted/dismounted an entity. * Adjust an entity's height if they have mounted/dismounted an entity.
*/ */
public static void updateMountOffset(Entity passenger, Entity mount, boolean rider, boolean riding, boolean moreThanOneEntity) { public static void updateMountOffset(Entity passenger, Entity mount, boolean rider, boolean riding, int index, int passengers) {
passenger.setFlag(EntityFlag.RIDING, riding); passenger.setFlag(EntityFlag.RIDING, riding);
if (riding) { if (riding) {
// Without the Y offset, Bedrock players will find themselves in the floor when mounting // Without the Y offset, Bedrock players will find themselves in the floor when mounting
@@ -179,7 +183,7 @@ public final class EntityUtils {
switch (mount.getDefinition().entityType()) { switch (mount.getDefinition().entityType()) {
case CAMEL -> { case CAMEL -> {
zOffset = 0.5f; zOffset = 0.5f;
if (moreThanOneEntity) { if (passengers > 1) {
if (!rider) { if (!rider) {
zOffset = -0.7f; zOffset = -0.7f;
} }
@@ -221,12 +225,18 @@ public final class EntityUtils {
} }
} }
} }
case HAPPY_GHAST -> {
int seatingIndex = Math.min(index, 4);
xOffset = HappyGhastEntity.X_OFFSETS[seatingIndex];
yOffset = 3.4f;
zOffset = HappyGhastEntity.Z_OFFSETS[seatingIndex];
}
} }
if (mount instanceof ChestBoatEntity) { if (mount instanceof ChestBoatEntity) {
xOffset = 0.15F; xOffset = 0.15F;
} else if (mount instanceof BoatEntity) { } else if (mount instanceof BoatEntity) {
// Without the X offset, more than one entity on a boat is stacked on top of each other // Without the X offset, more than one entity on a boat is stacked on top of each other
if (moreThanOneEntity) { if (passengers > 1) {
xOffset = rider ? 0.2f : -0.6f; xOffset = rider ? 0.2f : -0.6f;
if (passenger instanceof AnimalEntity) { if (passenger instanceof AnimalEntity) {
xOffset += 0.2f; xOffset += 0.2f;
@@ -342,6 +352,15 @@ public final class EntityUtils {
return translatedEntityName("minecraft", typeName, session); return translatedEntityName("minecraft", typeName, session);
} }
public static boolean equipmentUsableByEntity(GeyserSession session, Equippable equippable, EntityType entity) {
if (equippable.allowedEntities() == null) {
return true;
}
GeyserHolderSet<EntityType> holderSet = GeyserHolderSet.fromHolderSet(JavaRegistries.ENTITY_TYPE, equippable.allowedEntities());
return session.getTagCache().is(holderSet, entity);
}
private EntityUtils() { private EntityUtils() {
} }
} }