diff --git a/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java b/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java index 5b7999431..8e2cdf9b6 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java +++ b/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java @@ -30,6 +30,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.entity.GeyserEntityDefinition; import org.geysermc.geyser.api.entity.property.GeyserEntityProperty; import org.geysermc.geyser.api.util.Identifier; @@ -102,7 +103,7 @@ public class BedrockEntityDefinition implements GeyserEntityDefinition { return this; } - public Builder properties(GeyserEntityProperties.Builder propertiesBuilder) { + public Builder properties(GeyserEntityProperties.@Nullable Builder propertiesBuilder) { this.propertiesBuilder = propertiesBuilder; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinitions.java new file mode 100644 index 000000000..3228a68a8 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinitions.java @@ -0,0 +1,33 @@ +/* + * 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; + +public class BedrockEntityDefinitions { + + // TODO re-usable Bedrock entity definitions?????????? + // Or rather... looking up otherwise?? + +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java index caae00c10..2dcc7b183 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java @@ -53,12 +53,6 @@ public record GeyserEntityType(Identifier identifier, int javaId) implements Jav private static final Int2ObjectMap CUSTOM = new Int2ObjectOpenHashMap<>(); private static final Object2ObjectMap CUSTOM_BY_IDENTIFIER = new Object2ObjectOpenHashMap<>(); - public GeyserEntityType { - if (!VANILLA.containsValue(this) && !CUSTOM.containsKey(javaId)) { - throw new IllegalCallerException("Public constructor of GeyserEntityType should not be used; use one of the static factory methods instead"); - } - } - private GeyserEntityType(BuiltinEntityType builtin) { this(IdentifierImpl.of(builtin.name().toLowerCase(Locale.ROOT)), builtin.id()); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java b/core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java index 6140b971d..ef299f493 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java +++ b/core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java @@ -168,6 +168,7 @@ import org.geysermc.geyser.entity.type.living.monster.raid.VindicatorEntity; import org.geysermc.geyser.entity.type.player.AvatarEntity; import org.geysermc.geyser.entity.type.player.MannequinEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.impl.IdentifierImpl; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataTypes; @@ -335,10 +336,6 @@ public final class VanillaEntities { * Is not sent over the network */ public static final VanillaEntityType ENDER_DRAGON_PART; - /** - * Special Bedrock type - */ - public static final VanillaEntityType WITHER_SKULL_DANGEROUS; public static final float PLAYER_ENTITY_OFFSET; @@ -469,7 +466,7 @@ public final class VanillaEntities { INTERACTION = VanillaEntityType.inherited(InteractionEntity::new, entityBase) .type(BuiltinEntityType.INTERACTION) .heightAndWidth(1.0f) // default size until server specifies otherwise - .bedrockIdentifier("minecraft:armor_stand") + .bedrockDefinition(ARMOR_STAND.defaultBedrockDefinition()) .addTranslator(MetadataTypes.FLOAT, InteractionEntity::setWidth) .addTranslator(MetadataTypes.FLOAT, InteractionEntity::setHeight) .addTranslator(MetadataTypes.BOOLEAN, InteractionEntity::setResponse) @@ -613,8 +610,19 @@ public final class VanillaEntities { .heightAndWidth(0.3125f) .addTranslator(MetadataTypes.BOOLEAN, WitherSkullEntity::setDangerous) .build(); - WITHER_SKULL_DANGEROUS = VanillaEntityType.inherited(WITHER_SKULL.factory(), WITHER_SKULL) - .build(false); + + // Bedrock exclusive entity + IdentifierImpl dangerousSkull = IdentifierImpl.of("wither_skull_dangerous"); + BedrockEntityDefinition bedrockDefinition = BedrockEntityDefinition.builder() + .height(WITHER_SKULL.height) + .width(WITHER_SKULL.width) + .offset(WITHER_SKULL.offset) + .identifier(dangerousSkull) + .build(); + Registries.BEDROCK_ENTITY_DEFINITIONS.get().put(dangerousSkull, bedrockDefinition); + +// WITHER_SKULL_DANGEROUS = VanillaEntityType.inherited(WITHER_SKULL.factory(), WITHER_SKULL) +// .build(false); } // Boats @@ -1246,7 +1254,7 @@ public final class VanillaEntities { // As of 1.18 these don't track entity data at all ENDER_DRAGON_PART = VanillaEntityType.builder(null) - .bedrockIdentifier("minecraft:armor_stand") // Emulated + .bedrockDefinition(ARMOR_STAND.defaultBedrockDefinition()) // Emulated .build(false); // Never sent over the network PLAYER_ENTITY_OFFSET = PLAYER.defaultBedrockDefinition().offset(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/VanillaEntityType.java b/core/src/main/java/org/geysermc/geyser/entity/VanillaEntityType.java index 41b8ae45c..c590bb81c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/VanillaEntityType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/VanillaEntityType.java @@ -70,6 +70,7 @@ public class VanillaEntityType extends EntityTypeDefinition public static class Builder extends EntityTypeDefinition.Builder { protected GeyserEntityType type; + protected BedrockEntityDefinition bedrockDefinition; protected Builder(EntityFactory factory) { super(factory); @@ -128,6 +129,11 @@ public class VanillaEntityType extends EntityTypeDefinition return (Builder) super.addTranslator(translator); } + public Builder bedrockDefinition(BedrockEntityDefinition bedrockDefinition) { + this.bedrockDefinition = bedrockDefinition; + return this; + } + @Override public VanillaEntityType build() { return build(true); @@ -136,8 +142,17 @@ public class VanillaEntityType extends EntityTypeDefinition private void validateTypeAndIdentifier() { if (type == null) { throw new IllegalStateException("Missing entity type!"); - } else if (bedrockIdentifier == null) { - bedrockIdentifier = type.identifier().toString(); + } + + if (bedrockDefinition == null) { + bedrockDefinition = BedrockEntityDefinition.builder() + .height(height) + .width(width) + .properties(propertiesBuilder) + .offset(offset) + .identifier(Identifier.of(bedrockIdentifier)) + .build(); + Registries.BEDROCK_ENTITY_DEFINITIONS.get().put(Identifier.of(bedrockIdentifier), bedrockDefinition); } } @@ -148,16 +163,6 @@ public class VanillaEntityType extends EntityTypeDefinition public VanillaEntityType build(boolean register) { validateTypeAndIdentifier(); - BedrockEntityDefinition bedrockDefinition = BedrockEntityDefinition.builder() - .height(height) - .width(width) - .properties(propertiesBuilder) - .offset(offset) - .identifier(Identifier.of(bedrockIdentifier)) - .build(); - // TODO TEST!!! - Registries.BEDROCK_ENTITY_DEFINITIONS.get().put(Identifier.of(bedrockIdentifier), bedrockDefinition); - VanillaEntityType definition = new VanillaEntityType<>(factory, type, bedrockDefinition, translators); if (register && definition.entityType() != null) { Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition); diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java index 5d9563f3a..4ad955fa6 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java +++ b/core/src/main/java/org/geysermc/geyser/entity/properties/GeyserEntityProperties.java @@ -31,7 +31,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.EqualsAndHashCode; import lombok.ToString; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtType; @@ -106,7 +105,7 @@ public class GeyserEntityProperties { } public static class Builder { - private GeyserEntityProperties properties; + private GeyserEntityProperties properties = new GeyserEntityProperties(); private final String identifier; public Builder(String identifier) { @@ -122,7 +121,7 @@ public class GeyserEntityProperties { return this; } - public @Nullable GeyserEntityProperties build() { + public @NonNull GeyserEntityProperties build() { return properties; } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java b/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java index cd1471273..55af16bb2 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/properties/type/PropertyType.java @@ -38,7 +38,8 @@ public interface PropertyType javaDefinition; protected int entityId; @@ -152,7 +152,7 @@ public class Entity implements GeyserEntity { @Setter(AccessLevel.PROTECTED) // For players private boolean flagsDirty = false; - protected final GeyserEntityPropertyManager propertyManager; + protected final @Nullable GeyserEntityPropertyManager propertyManager; public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityTypeDefinition definition, BedrockEntityDefinition bedrockDefinition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { @@ -171,7 +171,7 @@ public class Entity implements GeyserEntity { this.valid = false; // TODO null or empty check - this.propertyManager = bedrockDefinition.registeredProperties() == null ? null : new GeyserEntityPropertyManager(bedrockDefinition.registeredProperties()); + this.propertyManager = bedrockDefinition.registeredProperties().isEmpty() ? null : new GeyserEntityPropertyManager(bedrockDefinition.registeredProperties()); setPosition(position); setAirSupply(getMaxAir()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java index b5ef8cc0c..3a9a08a5d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java @@ -28,7 +28,8 @@ package org.geysermc.geyser.entity.type; import org.cloudburstmc.math.vector.Vector3f; import org.geysermc.geyser.entity.BedrockEntityDefinition; import org.geysermc.geyser.entity.EntityTypeDefinition; -import org.geysermc.geyser.entity.VanillaEntities; +import org.geysermc.geyser.impl.IdentifierImpl; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; @@ -36,6 +37,7 @@ import java.util.UUID; public class WitherSkullEntity extends FireballEntity { private boolean isCharged; + private static final IdentifierImpl DANGEROUS_SKULL = IdentifierImpl.of("wither_skull_dangerous"); public WitherSkullEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityTypeDefinition definition, BedrockEntityDefinition bedrockDefinition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { super(session, entityId, geyserId, uuid, definition, bedrockDefinition, position, motion, yaw, pitch, headYaw); @@ -48,7 +50,7 @@ public class WitherSkullEntity extends FireballEntity { if (newDangerous != isCharged) { isCharged = newDangerous; // Is an entirely new entity in Bedrock but just a metadata type in Java - javaDefinition = isCharged ? VanillaEntities.WITHER_SKULL_DANGEROUS : VanillaEntities.WITHER_SKULL; + definition = isCharged ? Registries.BEDROCK_ENTITY_DEFINITIONS.get(DANGEROUS_SKULL) : javaDefinition.defaultBedrockDefinition(); despawnEntity(); spawnEntity(); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java index 2c806d7be..ea5dfec5f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java @@ -50,7 +50,7 @@ public class JavaSetEquipmentTranslator extends PacketTranslator