From 0b8678e2f2004be7e03f49bd5f38f1f0371e9630 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Fri, 24 Oct 2025 10:10:49 +0000 Subject: [PATCH] Allow creating custom entity types from API --- .../geyser/api/entity/JavaEntityType.java | 11 ++++- .../geyser/entity/GeyserEntityType.java | 42 ++++++++++++++----- .../loader/ProviderRegistryLoader.java | 5 +++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/JavaEntityType.java b/api/src/main/java/org/geysermc/geyser/api/entity/JavaEntityType.java index 70b4d5b85..50a06539b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/JavaEntityType.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/JavaEntityType.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.api.entity; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.util.Identifier; public interface JavaEntityType { @@ -35,7 +38,11 @@ public interface JavaEntityType { boolean isUnregistered(); - default boolean is(Identifier identifier) { - return javaIdentifier().equals(identifier); + default boolean is(Identifier javaIdentifier) { + return javaIdentifier().equals(javaIdentifier); + } + + static JavaEntityType create(@NonNull Identifier javaIdentifier, @NonNegative int javaId) { + return GeyserApi.api().provider(JavaEntityType.class, javaIdentifier, javaId); } } 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 2209c5687..1871d7f37 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java @@ -27,7 +27,12 @@ package org.geysermc.geyser.entity; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.kyori.adventure.key.Key; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.Constants; import org.geysermc.geyser.api.entity.JavaEntityType; import org.geysermc.geyser.api.util.Identifier; @@ -38,10 +43,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; import java.util.EnumMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; public record GeyserEntityType(Identifier javaIdentifier, int javaId) implements JavaEntityType { private static final Map VANILLA = new EnumMap<>(BuiltinEntityType.class); private static final Int2ObjectMap CUSTOM = new Int2ObjectOpenHashMap<>(); + private static final Object2ObjectMap CUSTOM_BY_IDENTIFIER = new Object2ObjectOpenHashMap<>(); private GeyserEntityType(BuiltinEntityType builtin) { this(Identifier.of(builtin.name().toLowerCase(Locale.ROOT)), builtin.id()); @@ -73,23 +80,36 @@ public record GeyserEntityType(Identifier javaIdentifier, int javaId) implements return type == null ? new GeyserEntityType(javaId) : type; } - // TODO improve this + @Nullable public static GeyserEntityType of(Key javaKey) { - Identifier identifier = MinecraftKey.keyToIdentifier(javaKey); - for (GeyserEntityType builtin : VANILLA.values()) { - if (builtin.javaIdentifier.equals(identifier)) { - return builtin; + if (javaKey.namespace().equals(Key.MINECRAFT_NAMESPACE)) { + try { + return ofVanilla(BuiltinEntityType.valueOf(javaKey.value().toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException exception) { + return null; } } - for (GeyserEntityType custom : CUSTOM.values()) { - if (custom.javaIdentifier.equals(identifier)) { - return custom; - } - } - return null; + return CUSTOM_BY_IDENTIFIER.get(MinecraftKey.keyToIdentifier(javaKey)); } public static GeyserEntityType of(EntityType type) { return type instanceof BuiltinEntityType builtin ? ofVanilla(builtin) : of(type.id()); } + + public static GeyserEntityType createCustom(@NonNull Identifier javaIdentifier, @NonNegative int javaId) { + Objects.requireNonNull(javaIdentifier, "javaIdentifier may not be null"); + if (javaIdentifier.vanilla()) { + throw new IllegalArgumentException("Cannot register custom entity type in vanilla namespace!" + javaIdentifier); + } else if (javaId < 0) { + throw new IllegalArgumentException("Invalid ID (may not be below zero): " + javaId); + } else if (javaId < BuiltinEntityType.VALUES.length) { + throw new IllegalArgumentException("Invalid ID (conflicts with vanilla entity type): " + javaId); + } else if (CUSTOM.containsKey(javaId) || CUSTOM_BY_IDENTIFIER.containsKey(javaIdentifier)) { + throw new IllegalArgumentException("Custom entity type with identifier " + javaIdentifier + " and ID " + javaId + " conflicts with existing custom entity type"); + } + GeyserEntityType type = new GeyserEntityType(javaIdentifier, javaId); + CUSTOM.put(javaId, type); + CUSTOM_BY_IDENTIFIER.put(javaIdentifier, type); + return type; + } } 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 48e08cc9c..3d2426b56 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 @@ -34,6 +34,7 @@ import org.geysermc.geyser.api.block.custom.component.GeometryComponent; 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.JavaEntityType; import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.item.custom.CustomItemData; @@ -45,6 +46,7 @@ import org.geysermc.geyser.api.pack.option.PriorityOption; import org.geysermc.geyser.api.pack.option.SubpackOption; import org.geysermc.geyser.api.pack.option.UrlFallbackOption; import org.geysermc.geyser.api.util.Identifier; +import org.geysermc.geyser.entity.GeyserEntityType; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.impl.IdentifierImpl; @@ -109,6 +111,9 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder()); + // entities + providers.put(JavaEntityType.class, args -> GeyserEntityType.createCustom((Identifier) args[0], (int) args[1])); + return providers; } }