diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataTypes.java b/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataTypes.java index ca280e1f1..d312bbda7 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataTypes.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserEntityDataTypes.java @@ -1,5 +1,7 @@ package org.geysermc.geyser.api.entity.data; +import org.geysermc.geyser.api.entity.data.types.Hitbox; + /** * Contains commonly used {@link GeyserEntityDataType} constants for built-in entity * metadata fields. @@ -48,6 +50,12 @@ public final class GeyserEntityDataTypes { public static final GeyserEntityDataType SCALE = GeyserEntityDataType.of(Float.class, "scale"); + /** + * Represents custom hitboxes for entities + */ + public static final GeyserListEntityDataType HITBOXES = + GeyserListEntityDataType.of(Hitbox.class, "hitboxes"); + private GeyserEntityDataTypes() { // no-op } diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserListEntityDataType.java b/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserListEntityDataType.java new file mode 100644 index 000000000..655a87760 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/entity/data/GeyserListEntityDataType.java @@ -0,0 +1,48 @@ +/* + * 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.api.entity.data; + +import org.geysermc.geyser.api.GeyserApi; + +import java.util.List; + +/** + * Represents a list of objects for an entity data types + * For example, there can be multiple hitboxes on an entity + * + * @param + */ +public interface GeyserListEntityDataType extends GeyserEntityDataType> { + + Class listTypeClass(); + + /** + * API usage only, use the types defined in {@link GeyserEntityDataTypes} + */ + static GeyserListEntityDataType of(Class typeClass, String name) { + return GeyserApi.api().provider(GeyserListEntityDataType.class, List.class, typeClass, name); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/data/types/Hitbox.java b/api/src/main/java/org/geysermc/geyser/api/entity/data/types/Hitbox.java new file mode 100644 index 000000000..46d284512 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/entity/data/types/Hitbox.java @@ -0,0 +1,71 @@ +/* + * 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.api.entity.data.types; + +import org.cloudburstmc.math.vector.Vector3f; +import org.geysermc.geyser.api.GeyserApi; + +/** + * Represents an entity hitbox. + */ +public interface Hitbox { + + /** + * The min "corner" of the hitbox + * @return the vector of the corner + */ + Vector3f min(); + + /** + * The max "corner" of the hitbox + * @return the vector of the corner + */ + Vector3f max(); + + /** + * The pivot of the hitbox + * @return + */ + Vector3f pivot(); + + static Builder builder() { + return GeyserApi.api().provider(Builder.class); + } + + /** + * The builder for the hitbox + */ + interface Builder { + + Builder min(Vector3f min); + + Builder max(Vector3f max); + + Builder origin(Vector3f pivot); + + Hitbox build(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 0dcc81034..2079e0938 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -113,7 +113,9 @@ public class Entity implements GeyserEntity { * The entity position as it is known to the Java server */ @Accessors(fluent = true) - protected Vector3f position; + private Vector3f position; + @Setter(AccessLevel.NONE) + private Vector3f bedrockPosition; protected Vector3f motion; /** @@ -708,8 +710,13 @@ public class Entity implements GeyserEntity { return this.valid; } + public void position(Vector3f position) { + this.position = position; + this.bedrockPosition = position.up(offset); + } + public Vector3f bedrockPosition() { - return position.up(offset); + return bedrockPosition; } /** @@ -860,10 +867,10 @@ public class Entity implements GeyserEntity { } } - public void offset(float offset) { + public void offset(float offset, boolean teleport) { this.offset = offset; // TODO queue? - if (isValid()) { + if (isValid() && teleport) { this.moveRelative(0, 0, 0, 0, 0, isOnGround()); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java index 9155ab948..1cec69613 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -64,7 +64,7 @@ public class FireballEntity extends ThrowableEntity { newPosition = tickMovement(newPosition); } super.moveAbsoluteImmediate(newPosition, yaw, pitch, headYaw, isOnGround, teleported); - this.position = javaPosition; + position(javaPosition); this.motion = lastMotion; } @@ -73,6 +73,6 @@ public class FireballEntity extends ThrowableEntity { if (removedInVoid()) { return; } - moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false); + moveAbsoluteImmediate(tickMovement(position()), getYaw(), getPitch(), getHeadYaw(), false, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java index 06ecfaa17..a86b1231b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -113,7 +113,7 @@ public class FishingHookEntity extends ThrowableEntity { if (!collided) { super.moveAbsoluteImmediate(javaPosition, yaw, pitch, headYaw, isOnGround, teleported); } else { - super.moveAbsoluteImmediate(this.position, yaw, pitch, headYaw, true, true); + super.moveAbsoluteImmediate(this.position(), yaw, pitch, headYaw, true, true); } } @@ -144,7 +144,7 @@ public class FishingHookEntity extends ThrowableEntity { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); + moveAbsoluteImmediate(position().add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag); @@ -162,7 +162,7 @@ public class FishingHookEntity extends ThrowableEntity { * @return true if this entity is currently in air. */ protected boolean isInAir() { - int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + int block = session.getGeyser().getWorldManager().getBlockAt(session, position().toInt()); return block == Block.JAVA_AIR_ID; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java index 80d6e6eb7..09359fb09 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java @@ -119,7 +119,7 @@ public class InteractionEntity extends Entity { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { - moveAbsolute(position.add(relX, relY, relZ), yaw, pitch, headYaw, isOnGround, false); + moveAbsolute(position().add(relX, relY, relZ), yaw, pitch, headYaw, isOnGround, false); } @Override @@ -141,7 +141,7 @@ public class InteractionEntity extends Entity { setBoundingBoxHeight(Math.min(height.getPrimitiveValue(), 64f)); if (secondEntity != null) { - secondEntity.moveAbsolute(position.up(getBoundingBoxHeight()), yaw, pitch, onGround, true); + secondEntity.moveAbsolute(position().up(getBoundingBoxHeight()), yaw, pitch, onGround, true); } } @@ -159,7 +159,7 @@ public class InteractionEntity extends Entity { } if (this.secondEntity == null) { - secondEntity = new ArmorStandEntity(EntitySpawnContext.inherited(session, VanillaEntities.ARMOR_STAND, this, position.up(getBoundingBoxHeight()))); + secondEntity = new ArmorStandEntity(EntitySpawnContext.inherited(session, VanillaEntities.ARMOR_STAND, this, position().up(getBoundingBoxHeight()))); } secondEntity.getDirtyMetadata().put(EntityDataTypes.NAME, nametag); secondEntity.getDirtyMetadata().put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, isNameTagVisible ? (byte) 1 : (byte) 0); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index 5311c4703..30a4400e1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -79,7 +79,7 @@ public class ItemEntity extends ThrowableEntity { if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { float gravity = getGravity(); motion = motion.down(gravity); - moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); + moveAbsoluteImmediate(position().add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); motion = motion.mul(drag, 0.98f, drag); } @@ -118,7 +118,7 @@ public class ItemEntity extends ThrowableEntity { this.offset = Math.abs(offset); } super.moveAbsoluteImmediate(javaPosition, 0, 0, 0, isOnGround, teleported); - this.position = javaPosition; + position(javaPosition); waterLevel = session.getGeyser().getWorldManager().getBlockAtAsync(session, javaPosition.getFloorX(), javaPosition.getFloorY(), javaPosition.getFloorZ()) .thenApply(BlockStateValues::getWaterLevel); @@ -137,7 +137,7 @@ public class ItemEntity extends ThrowableEntity { @Override protected float getDrag() { if (isOnGround()) { - Vector3i groundBlockPos = position.toInt().down(); + Vector3i groundBlockPos = position().toInt().down(); BlockState blockState = session.getGeyser().getWorldManager().blockAt(session, groundBlockPos); return BlockStateValues.getSlipperiness(blockState) * 0.98f; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index 3093af41d..2bdd65b0d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -80,7 +80,7 @@ public class ItemFrameEntity extends HangingEntity { super(context); blockDefinition = buildBlockDefinition(Direction.SOUTH); // Default to SOUTH direction, like on Java - entity metadata should correct this when necessary - bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + bedrockPosition = position().floor().toInt(); session.getItemFrameCache().put(bedrockPosition, this); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java index ef8210717..66f212f16 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java @@ -35,7 +35,7 @@ public class LeashKnotEntity extends Entity { super(context); // Position is incorrect by default // TODO offset - position(position.add(0.5f, 0.25f, 0.5f)); + position(position().add(0.5f, 0.25f, 0.5f)); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index e5f7709db..3b1611061 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -194,7 +194,7 @@ public class MinecartEntity extends Entity implements Tickable { } private void updateCompletedStep() { - lastCompletedStep = new MinecartStep(position.toDouble(), motion.toDouble(), yaw, pitch, 0.0F); + lastCompletedStep = new MinecartStep(position().toDouble(), motion.toDouble(), yaw, pitch, 0.0F); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java index f3e9e5fd7..15c3bf7fc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/PaintingEntity.java @@ -92,7 +92,7 @@ public class PaintingEntity extends HangingEntity { valid = true; - session.getGeyser().getLogger().debug("Spawned painting on " + position); + session.getGeyser().getLogger().debug("Spawned painting on " + position()); } @Override @@ -101,7 +101,7 @@ public class PaintingEntity extends HangingEntity { } private Vector3f fixOffset(PaintingType paintingName) { - Vector3f position = super.position; + Vector3f position = position(); // ViaVersion already adds the offset for us on older versions, // so no need to do it then otherwise it will be spaced if (session.isEmulatePost1_18Logic()) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java index 1f36736d7..899d80cf7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java @@ -90,7 +90,7 @@ public class TextDisplayEntity extends DisplayBaseEntity { // If the line count changed, update the position to account for the new offset if (previousLineCount != lineCount) { - moveAbsolute(position, yaw, pitch, headYaw, onGround, false); + moveAbsolute(position(), yaw, pitch, headYaw, onGround, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index 28358ca44..2b31a6d31 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -43,7 +43,7 @@ public class ThrowableEntity extends Entity implements Tickable { public ThrowableEntity(EntitySpawnContext context) { super(context); - this.lastJavaPosition = position; + this.lastJavaPosition = position(); } /** @@ -55,7 +55,7 @@ public class ThrowableEntity extends Entity implements Tickable { if (removedInVoid()) { return; } - moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); + moveAbsoluteImmediate(position().add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); float gravity = getGravity(); motion = motion.mul(drag).down(gravity); @@ -75,15 +75,15 @@ public class ThrowableEntity extends Entity implements Tickable { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING); } - if (this.position.getX() != javaPosition.getX()) { + if (this.position().getX() != javaPosition.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); moveEntityDeltaPacket.setX(javaPosition.getX()); } - if (this.position.getY() != javaPosition.getY()) { + if (this.position().getY() != javaPosition.getY()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); moveEntityDeltaPacket.setY(javaPosition.getY() + offset); } - if (this.position.getZ() != javaPosition.getZ()) { + if (this.position().getZ() != javaPosition.getZ()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); moveEntityDeltaPacket.setZ(javaPosition.getZ()); } @@ -155,7 +155,7 @@ public class ThrowableEntity extends Entity implements Tickable { * @return true if this entity is currently in water. */ protected boolean isInWater() { - int block = session.getGeyser().getWorldManager().getBlockAt(session, position.toInt()); + int block = session.getGeyser().getWorldManager().getBlockAt(session, position().toInt()); return BlockStateValues.getWaterLevel(block) != -1; } @@ -173,7 +173,7 @@ public class ThrowableEntity extends Entity implements Tickable { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { moveAbsoluteImmediate(lastJavaPosition.add(relX, relY, relZ), yaw, pitch, headYaw, isOnGround, false); - lastJavaPosition = position; + lastJavaPosition = position(); } @Override @@ -188,7 +188,7 @@ public class ThrowableEntity extends Entity implements Tickable { * @return true if the entity was removed */ public boolean removedInVoid() { - if (position.getY() < session.getDimensionType().minY() - 64) { + if (position().getY() < session.getDimensionType().minY() - 64) { session.getEntityCache().removeEntity(this); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 9857eebd5..c8d598d40 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -92,11 +92,9 @@ public class ArmorStandEntity extends LivingEntity { @Override public void spawnEntity() { - Vector3f javaPosition = position; // Apply the offset if we're the second entity - position = position.up(getYOffset()); + offset(getYOffset(), false); super.spawnEntity(); - position = javaPosition; } @Override @@ -109,7 +107,7 @@ public class ArmorStandEntity extends LivingEntity { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { - moveAbsolute(position.add(relX, relY, relZ), yaw, pitch, headYaw, onGround, false); + moveAbsolute(position().add(relX, relY, relZ), yaw, pitch, headYaw, onGround, false); } @Override @@ -118,9 +116,8 @@ public class ArmorStandEntity extends LivingEntity { secondEntity.moveAbsolute(javaPosition, yaw, pitch, headYaw, isOnGround, teleported); } // Fake the height to be above where it is so the nametag appears in the right location - float yOffset = getYOffset(); - super.moveAbsolute(yOffset != 0 ? javaPosition.up(yOffset) : javaPosition, yaw, yaw, yaw, isOnGround, teleported); - this.position = javaPosition; + offset(getYOffset(), false); + super.moveAbsolute(javaPosition, yaw, yaw, yaw, isOnGround, teleported); } @Override @@ -240,7 +237,7 @@ public class ArmorStandEntity extends LivingEntity { super.updateBedrockMetadata(); if (positionUpdateRequired) { positionUpdateRequired = false; - moveAbsolute(position, yaw, pitch, headYaw, onGround, true); + moveAbsolute(position(), yaw, pitch, headYaw, onGround, true); } } @@ -341,8 +338,7 @@ public class ArmorStandEntity extends LivingEntity { if (secondEntity == null) { // Create the second entity. It doesn't need to worry about the items, but it does need to worry about // the metadata as it will hold the name tag. - // TODO - secondEntity = new ArmorStandEntity(EntitySpawnContext.inherited(session, VanillaEntities.ARMOR_STAND, this, position)); + secondEntity = new ArmorStandEntity(EntitySpawnContext.inherited(session, VanillaEntities.ARMOR_STAND, this, position())); secondEntity.primaryEntity = false; } // Copy metadata diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java index 2f55233c9..8ce1ce2dc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java @@ -128,7 +128,7 @@ public class SquidEntity extends AgeableWaterEntity implements Tickable { if (getFlag(EntityFlag.RIDING)) { inWater = CompletableFuture.completedFuture(false); } else { - inWater = session.getGeyser().getWorldManager().getBlockAtAsync(session, position.toInt()) + inWater = session.getGeyser().getWorldManager().getBlockAtAsync(session, position().toInt()) .thenApply(block -> BlockStateValues.getWaterLevel(block) != -1); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java index f43717f4c..62db94658 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java @@ -52,7 +52,7 @@ public class OcelotEntity extends AnimalEntity { @NonNull @Override protected InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) { - if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().position().distanceSquared(position) < 9f) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().position().distanceSquared(position()) < 9f) { // Attempt to feed return InteractiveTag.FEED; } else { @@ -63,7 +63,7 @@ public class OcelotEntity extends AnimalEntity { @NonNull @Override protected InteractionResult mobInteract(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) { - if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().position().distanceSquared(position) < 9f) { + if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().position().distanceSquared(position()) < 9f) { // Attempt to feed return InteractionResult.SUCCESS; } else { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java index 7cb4749b6..9feb71309 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java @@ -102,7 +102,7 @@ public class SnifferEntity extends AnimalEntity implements Tickable { // The java client renders digging particles on its own, but bedrock does not if (digTicks > 0 && --digTicks < DIG_START && digTicks % 5 == 0) { Vector3f rot = Vector3f.createDirectionDeg(0, -getYaw()).mul(2.25f); - Vector3f pos = position.add(rot).up(0.2f).floor(); // Handle non-full blocks + Vector3f pos = position().add(rot).up(0.2f).floor(); // Handle non-full blocks int blockId = session.getBlockMappings().getBedrockBlockId(session.getGeyser().getWorldManager().getBlockAt(session, pos.toInt().down())); LevelEventPacket levelEventPacket = new LevelEventPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java index e61e9fa35..e9f6a43c8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java @@ -149,12 +149,12 @@ public class VillagerEntity extends AbstractMerchantEntity { setPitch(pitch); setHeadYaw(headYaw); setOnGround(isOnGround); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + position(Vector3f.from(position().getX() + relX, position().getY() + relY, position().getZ() + relZ)); MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); moveEntityPacket.setRotation(Vector3f.from(0, 0, bedRotation)); - moveEntityPacket.setPosition(Vector3f.from(position.getX() + xOffset, position.getY() + offset, position.getZ() + zOffset)); + moveEntityPacket.setPosition(position().add(xOffset, offset, zOffset)); moveEntityPacket.setOnGround(isOnGround); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index b2a4b36db..62d44575e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -134,7 +134,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { for (int i = 0; i < segmentHistory.length; i++) { segmentHistory[i] = new Segment(); segmentHistory[i].yaw = getHeadYaw(); - segmentHistory[i].y = position.getY(); + segmentHistory[i].y = position().getY(); } } @@ -206,7 +206,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { } // Send updated positions for (EnderDragonPartEntity part : allParts) { - part.moveAbsolute(part.position().add(position), 0, 0, 0, false, false); + part.moveAbsolute(part.position().add(position()), 0, 0, 0, false, false); } } @@ -277,7 +277,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { float xOffset = 8f * (random.nextFloat() - 0.5f); float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f; float zOffset = 8f * (random.nextFloat() - 0.5f); - Vector3f particlePos = position.add(xOffset, yOffset, zOffset); + Vector3f particlePos = position().add(xOffset, yOffset, zOffset); LevelEventPacket particlePacket = new LevelEventPacket(); particlePacket.setType(ParticleType.EXPLODE); particlePacket.setPosition(particlePos); @@ -311,7 +311,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { private void pushSegment() { latestSegment = (latestSegment + 1) % segmentHistory.length; segmentHistory[latestSegment].yaw = getHeadYaw(); - segmentHistory[latestSegment].y = position.getY(); + segmentHistory[latestSegment].y = position().getY(); } /** diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java index e16c5f4a4..4da65c898 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java @@ -41,7 +41,6 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.geysermc.geyser.entity.spawn.EntitySpawnContext; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.skin.SkinManager; @@ -162,7 +161,7 @@ public class AvatarEntity extends LivingEntity { setYaw(yaw); setPitch(pitch); setHeadYaw(headYaw); - this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + position(Vector3f.from(position().getX() + relX, position().getY() + relY, position().getZ() + relZ)); setOnGround(isOnGround); @@ -175,9 +174,9 @@ public class AvatarEntity extends LivingEntity { // If the player is moved while sleeping, we have to adjust their y, so it appears // correctly on Bedrock. This fixes GSit's lay. if (getFlag(EntityFlag.SLEEPING)) { - if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { + if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position().toInt()) > 4)) { // Force the player movement by using a teleport - movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - offset + 0.2f, position.getZ())); + movePlayerPacket.setPosition(Vector3f.from(position().getX(), position().getY() - offset + 0.2f, position().getZ())); movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); } } @@ -190,7 +189,7 @@ public class AvatarEntity extends LivingEntity { } @Override - public Entity position(Vector3f position) { + public void position(Vector3f position) { if (this.bedPosition != null) { // As of Bedrock 1.21.22 and Fabric 1.21.1 // Messes with Bedrock if we send this to the client itself, though. @@ -198,7 +197,6 @@ public class AvatarEntity extends LivingEntity { } else { super.position(position); } - return this; } @Override @@ -316,7 +314,7 @@ public class AvatarEntity extends LivingEntity { if (pose == Pose.SWIMMING) { // This is just for, so we know if player is swimming or crawling. - if (session.getGeyser().getWorldManager().blockAt(session, position.toInt()).is(Blocks.WATER)) { + if (session.getGeyser().getWorldManager().blockAt(session, position().toInt()).is(Blocks.WATER)) { setFlag(EntityFlag.SWIMMING, true); } else { setFlag(EntityFlag.CRAWLING, true); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 2ec0c0a4a..95c798d1c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -161,7 +161,7 @@ public class PlayerEntity extends AvatarEntity implements GeyserPlayerEntity { return; } // The parrot is a separate entity in Bedrock, but part of the player entity in Java - EntitySpawnContext context = EntitySpawnContext.inherited(session, VanillaEntities.PARROT, this, position); + EntitySpawnContext context = EntitySpawnContext.inherited(session, VanillaEntities.PARROT, this, position()); if (context.callParrotEvent(this, variant.getAsInt(), !isLeft)) { GeyserImpl.getInstance().getLogger().debug(session, "Cancelled parrot spawn as definition is null!"); return; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 44f552dab..949dd3240 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -163,11 +163,11 @@ public class SessionPlayerEntity extends PlayerEntity { @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); - session.getCollisionManager().updatePlayerBoundingBox(this.position); + session.getCollisionManager().updatePlayerBoundingBox(position()); } @Override - public Entity position(Vector3f position) { + public void position(Vector3f position) { if (valid) { // Don't update during session init session.getCollisionManager().updatePlayerBoundingBox(position); @@ -175,8 +175,7 @@ public class SessionPlayerEntity extends PlayerEntity { session.setNoClip(false); } } - this.position = position; - return this; + super.position(position); } /** @@ -212,10 +211,10 @@ public class SessionPlayerEntity extends PlayerEntity { * Set the player's position from a position sent in a Bedrock packet */ public void setPositionFromBedrock(Vector3f position) { - this.position = position.down(offset); + position(position.down(offset)); // Player is "above" the void so they're not supposed to no clip. - if (session.isNoClip() && this.position.getY() >= session.getBedrockDimension().minY() - 5) { + if (session.isNoClip() && position().getY() >= session.getBedrockDimension().minY() - 5) { session.setNoClip(false); } } @@ -497,7 +496,7 @@ public class SessionPlayerEntity extends PlayerEntity { if (session.getGameMode() == GameMode.SPECTATOR) { return false; } - BlockState state = session.getGeyser().getWorldManager().blockAt(session, position.toInt()); + BlockState state = session.getGeyser().getWorldManager().blockAt(session, position().toInt()); if (state.block().is(session, BlockTag.CLIMBABLE)) { return true; } @@ -506,7 +505,7 @@ public class SessionPlayerEntity extends PlayerEntity { if (!state.getValue(Properties.OPEN)) { return false; } else { - BlockState belowState = session.getGeyser().getWorldManager().blockAt(session, position.toInt().down()); + BlockState belowState = session.getGeyser().getWorldManager().blockAt(session, position().toInt().down()); return belowState.is(Blocks.LADDER) && belowState.getValue(Properties.HORIZONTAL_FACING) == state.getValue(Properties.HORIZONTAL_FACING); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoatVehicleComponent.java b/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoatVehicleComponent.java index 5bee4196e..8bbb9e36f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoatVehicleComponent.java +++ b/core/src/main/java/org/geysermc/geyser/entity/vehicle/BoatVehicleComponent.java @@ -138,28 +138,28 @@ public class BoatVehicleComponent extends VehicleComponent { @Override protected void moveVehicle(Vector3d javaPos, Vector3f lastRotation) { - Vector3f bedrockPos = javaPos.toFloat(); + Vector3f oldPosition = vehicle.position(); + vehicle.position(javaPos.toFloat()); MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); - moveEntityDeltaPacket.setRuntimeEntityId(vehicle.getGeyserId()); + moveEntityDeltaPacket.setRuntimeEntityId(vehicle.geyserId()); if (vehicle.isOnGround()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); } - if (vehicle.getPosition().getX() != bedrockPos.getX()) { + if (vehicle.position().getX() != oldPosition.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); - moveEntityDeltaPacket.setX(bedrockPos.getX()); + moveEntityDeltaPacket.setX(vehicle.bedrockPosition().getX()); } - if (vehicle.getPosition().getY() != bedrockPos.getY()) { + if (vehicle.position().getY() != oldPosition.getY()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); - moveEntityDeltaPacket.setY(bedrockPos.getY() + vehicle.getDefinition().offset()); + moveEntityDeltaPacket.setY(vehicle.bedrockPosition().getY()); } - if (vehicle.getPosition().getZ() != bedrockPos.getZ()) { + if (vehicle.position().getZ() != oldPosition.getZ()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); - moveEntityDeltaPacket.setZ(bedrockPos.getZ()); + moveEntityDeltaPacket.setZ(vehicle.bedrockPosition().getZ()); } - vehicle.setPosition(bedrockPos); if (vehicle.getPitch() != lastRotation.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); @@ -175,7 +175,7 @@ public class BoatVehicleComponent extends VehicleComponent { } if (!moveEntityDeltaPacket.getFlags().isEmpty()) { - vehicle.getSession().sendUpstreamPacket(moveEntityDeltaPacket); + vehicle.getSession().sendUpstreamPacketImmediately(moveEntityDeltaPacket); } ServerboundMoveVehiclePacket moveVehiclePacket = new ServerboundMoveVehiclePacket(javaPos, vehicle.getYaw() - 90, vehicle.getPitch(), vehicle.isOnGround()); 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 7f1edfe83..02c93b5b6 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 @@ -738,7 +738,8 @@ public class VehicleComponent { * @param lastRotation the previous rotation of the vehicle (pitch, yaw, headYaw) */ protected void moveVehicle(Vector3d javaPos, Vector3f lastRotation) { - Vector3f bedrockPos = javaPos.toFloat(); + Vector3f oldPosition = vehicle.position(); + vehicle.position(javaPos.toFloat()); MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); moveEntityDeltaPacket.setRuntimeEntityId(vehicle.geyserId()); @@ -747,19 +748,18 @@ public class VehicleComponent { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); } - if (vehicle.position().getX() != bedrockPos.getX()) { + if (vehicle.position().getX() != oldPosition.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); - moveEntityDeltaPacket.setX(bedrockPos.getX()); + moveEntityDeltaPacket.setX(vehicle.bedrockPosition().getX()); } - if (vehicle.position().getY() != bedrockPos.getY()) { + if (vehicle.position().getY() != oldPosition.getY()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); - moveEntityDeltaPacket.setY(bedrockPos.getY()); + moveEntityDeltaPacket.setY(vehicle.bedrockPosition().getY()); } - if (vehicle.position().getZ() != bedrockPos.getZ()) { + if (vehicle.position().getZ() != oldPosition.getZ()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); - moveEntityDeltaPacket.setZ(bedrockPos.getZ()); + moveEntityDeltaPacket.setZ(vehicle.bedrockPosition().getZ()); } - vehicle.position(bedrockPos); if (vehicle.getPitch() != lastRotation.getX()) { moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); diff --git a/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserEntityDataImpl.java b/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserEntityDataImpl.java index 5fec98b0a..a7c4ec075 100644 --- a/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserEntityDataImpl.java +++ b/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserEntityDataImpl.java @@ -50,7 +50,7 @@ public class GeyserEntityDataImpl implements GeyserEntityDataType { TYPES.put("scale", new GeyserEntityDataImpl<>(Float.class, "scale", EntityDataTypes.SCALE)); // "custom" - TYPES.put("vertical_offset", new GeyserEntityDataImpl<>(Float.class, "offset", Entity::offset, Entity::getOffset)); + TYPES.put("vertical_offset", new GeyserEntityDataImpl<>(Float.class, "offset", (entity, value) -> entity.offset(value, true), Entity::getOffset)); } public static GeyserEntityDataImpl lookup(Class clazz, String name) { diff --git a/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserListEntityDataImpl.java b/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserListEntityDataImpl.java new file mode 100644 index 000000000..bb11baf64 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/entity/GeyserListEntityDataImpl.java @@ -0,0 +1,82 @@ +/* + * 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.impl.entity; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.geysermc.geyser.api.entity.data.GeyserListEntityDataType; +import org.geysermc.geyser.api.entity.data.types.Hitbox; +import org.geysermc.geyser.entity.type.Entity; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class GeyserListEntityDataImpl extends GeyserEntityDataImpl> implements GeyserListEntityDataType { + + public static Map> TYPES; + static { + TYPES = new Object2ObjectOpenHashMap<>(); + TYPES.put("hitboxes", new GeyserListEntityDataImpl<>(Hitbox.class, "hitboxes", + (entity, hitboxes) -> entity.getDirtyMetadata().put(EntityDataTypes.HITBOX, HitboxImpl.toNbtMap(hitboxes)), + (entity -> HitboxImpl.fromMetaData((NbtMap) entity.getMetadata().get(EntityDataTypes.HITBOX))))); + } + + private final Class listTypeClass; + + public GeyserListEntityDataImpl(Class typeClass, String name, BiConsumer> consumer, Function> getter) { + //noinspection unchecked - we do not talk about it + super((Class>) (Class) List.class, name, consumer, getter); + this.listTypeClass = typeClass; + } + + @Override + public Class listTypeClass() { + return listTypeClass; + } + + public static GeyserListEntityDataImpl lookup(Class clazz, Class listTypeClass, String name) { + Objects.requireNonNull(clazz); + Objects.requireNonNull(listTypeClass); + Objects.requireNonNull(name); + + if (clazz != List.class) { + throw new IllegalStateException("Cannot look up list entity data for " + clazz + " and " + listTypeClass + " for " + name); + } + + var type = TYPES.get(name); + if (type == null) { + throw new IllegalArgumentException("Unknown entity data type: " + name); + } + if (type.listTypeClass() == listTypeClass) { + return TYPES.get(name); + } + throw new IllegalArgumentException("Unknown entity data type: " + name); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/impl/entity/HitboxImpl.java b/core/src/main/java/org/geysermc/geyser/impl/entity/HitboxImpl.java new file mode 100644 index 000000000..0e7fd7b8f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/entity/HitboxImpl.java @@ -0,0 +1,116 @@ +/* + * 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.impl.entity; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtType; +import org.geysermc.geyser.api.entity.data.types.Hitbox; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public record HitboxImpl( + Vector3f min, + Vector3f max, + Vector3f pivot +) implements Hitbox { + + public static List fromMetaData(@Nullable NbtMap metaDataMap) { + if (metaDataMap == null) { + return List.of(); + } + + List boxes = new ArrayList<>(); + List hitboxes = metaDataMap.getList("Hitxboxes", NbtType.COMPOUND); + for (NbtMap hitbox : hitboxes) { + boxes.add(new HitboxImpl( + Vector3f.from(hitbox.getFloat("MinX"), hitbox.getFloat("MinY"), hitbox.getFloat("MinZ")), + Vector3f.from(hitbox.getFloat("MaxX"), hitbox.getFloat("MaxY"), hitbox.getFloat("MaxZ")), + Vector3f.from(hitbox.getFloat("PivotX"),hitbox.getFloat("PivotY"),hitbox.getFloat("PivotZ")) + )); + } + return boxes; + } + + public NbtMap toNbtMap() { + return NbtMap.builder() + .putFloat("MinX", min.getX()) + .putFloat("MinY", min.getY()) + .putFloat("MinZ", min.getZ()) + .putFloat("MaxX", max.getX()) + .putFloat("MaxY", max.getY()) + .putFloat("MaxZ", max.getZ()) + .putFloat("PivotX", pivot.getX()) + .putFloat("PivotY", pivot.getY()) + .putFloat("PivotZ", pivot.getZ()) + .build(); + } + + public static NbtMap toNbtMap(List hitboxes) { + List list = new ArrayList<>(); + for (Hitbox hitbox : hitboxes) { + if (hitbox instanceof HitboxImpl impl) { + list.add(impl.toNbtMap()); + } else { + throw new IllegalArgumentException("Unknown hitbox class implementation: " + hitbox.getClass().getSimpleName()); + } + } + return NbtMap.builder().putList("Hitboxes", NbtType.COMPOUND, list).build(); + } + + public static class Builder implements Hitbox.Builder { + Vector3f min, max, pivot; + + @Override + public Hitbox.Builder min(Vector3f min) { + Objects.requireNonNull(min, "min"); + this.min = min; + return this; + } + + @Override + public Hitbox.Builder max(Vector3f max) { + Objects.requireNonNull(max, "max"); + this.max = max; + return this; + } + + @Override + public Hitbox.Builder origin(Vector3f pivot) { + Objects.requireNonNull(pivot, "pivot"); + this.pivot = pivot; + return this; + } + + @Override + public Hitbox build() { + return new HitboxImpl(min == null ? Vector3f.ZERO : min, max == null ? Vector3f.ZERO : max, pivot == null ? Vector3f.ZERO : pivot); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 67d4b873b..0d29e9339 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -35,6 +35,8 @@ import org.geysermc.geyser.api.block.custom.component.MaterialInstance; import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.entity.data.GeyserEntityDataType; +import org.geysermc.geyser.api.entity.data.GeyserListEntityDataType; +import org.geysermc.geyser.api.entity.data.types.Hitbox; import org.geysermc.geyser.api.entity.definition.GeyserEntityDefinition; import org.geysermc.geyser.api.entity.definition.JavaEntityType; import org.geysermc.geyser.api.event.EventRegistrar; @@ -56,6 +58,8 @@ import org.geysermc.geyser.impl.IdentifierImpl; import org.geysermc.geyser.impl.camera.GeyserCameraFade; import org.geysermc.geyser.impl.camera.GeyserCameraPosition; import org.geysermc.geyser.impl.entity.GeyserEntityDataImpl; +import org.geysermc.geyser.impl.entity.GeyserListEntityDataImpl; +import org.geysermc.geyser.impl.entity.HitboxImpl; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; @@ -119,6 +123,9 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov providers.put(GeyserEntityDefinition.class, args -> BedrockEntityDefinition.getOrCreate((Identifier) args[0])); providers.put(JavaEntityType.class, args -> GeyserEntityType.ofVanilla((Identifier) args[0])); providers.put(GeyserEntityDataType.class, args -> GeyserEntityDataImpl.lookup((Class) args[0], (String) args[1])); + providers.put(GeyserListEntityDataType.class, args -> GeyserListEntityDataImpl.lookup((Class) args[0], (Class) args[1], (String) args[2])); + + providers.put(Hitbox.Builder.class, args -> new HitboxImpl.Builder()); return providers; } 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 784c030f5..aec57eb22 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -89,7 +89,6 @@ import java.util.function.Consumer; public final class EntityUtils { private static final AtomicInteger RUNTIME_ID_ALLOCATOR = new AtomicInteger(100000); - public static final float PLAYER_ENTITY_OFFSET = 1.62F; /** * A constant array of the two hands that a player can interact with an entity.