1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2026-01-06 15:41:50 +00:00

Work on exposing custom entity definitions in the API

This commit is contained in:
Eclipse
2025-10-27 10:18:50 +00:00
committed by onebeastchris
parent d843745ded
commit 7fe8992ceb
11 changed files with 467 additions and 62 deletions

View File

@@ -0,0 +1,81 @@
/*
* 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;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.common.returnsreceiver.qual.This;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
import org.geysermc.geyser.api.predicate.PredicateStrategy;
import org.geysermc.geyser.api.predicate.context.entity.EntitySpawnContext;
import org.geysermc.geyser.api.util.GenericBuilder;
import java.util.List;
public interface CustomEntityDefinition {
// TODO Identifier
String bedrockIdentifier();
float width();
float height();
float offset();
List<MinecraftPredicate<? super EntitySpawnContext>> predicates();
PredicateStrategy predicateStrategy();
static Builder builder(@NonNull String bedrockIdentifier, @NonNull JavaEntityType vanillaType) {
return GeyserApi.api().provider(Builder.class, bedrockIdentifier, vanillaType);
}
interface Builder extends GenericBuilder<CustomEntityDefinition> {
@This
Builder width(@Positive float width);
@This
Builder height(@Positive float height);
@This
Builder heightAndWidth(@Positive float value);
@This
Builder offset(@Positive float offset);
@This
Builder predicate(@NonNull MinecraftPredicate<? super EntitySpawnContext> predicate);
@This
Builder predicateStrategy(@NonNull PredicateStrategy strategy);
@Override
CustomEntityDefinition build();
}
}

View File

@@ -38,6 +38,8 @@ public interface JavaEntityType {
boolean isUnregistered();
boolean vanilla();
default boolean is(Identifier javaIdentifier) {
return javaIdentifier().equals(javaIdentifier);
}

View File

@@ -55,15 +55,13 @@ import java.util.function.BiConsumer;
@ToString(callSuper = true)
public abstract class EntityDefinition<T extends Entity> extends EntityDefinitionBase<T> {
private final EntityFactory<T> factory;
private final GeyserEntityType entityType;
private final String bedrockIdentifier;
private final GeyserEntityProperties registeredProperties;
public EntityDefinition(EntityFactory<T> factory, GeyserEntityType entityType, String bedrockIdentifier,
public EntityDefinition(EntityFactory<T> factory, String bedrockIdentifier,
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
super(width, height, offset, translators);
this.factory = factory;
this.entityType = entityType;
this.bedrockIdentifier = bedrockIdentifier;
this.registeredProperties = registeredProperties;
}
@@ -72,8 +70,6 @@ public abstract class EntityDefinition<T extends Entity> extends EntityDefinitio
@Accessors(fluent = true, chain = true)
public static abstract class Builder<T extends Entity> extends EntityDefinitionBase.Builder<T> {
protected final EntityFactory<T> factory;
@Setter(AccessLevel.NONE)
protected GeyserEntityType type;
protected String bedrockIdentifier;
@Setter(AccessLevel.NONE)
protected GeyserEntityProperties.Builder propertiesBuilder;
@@ -91,15 +87,6 @@ public abstract class EntityDefinition<T extends Entity> extends EntityDefinitio
this.offset = offset;
}
/**
* Resets the bedrock identifier as well
*/
public Builder<T> type(GeyserEntityType type) {
this.type = type;
this.bedrockIdentifier = null;
return this;
}
@Override
public Builder<T> width(float width) {
return (Builder<T>) super.width(width);
@@ -137,13 +124,5 @@ public abstract class EntityDefinition<T extends Entity> extends EntityDefinitio
propertiesBuilder.add(propertyType);
return this;
}
protected void validateTypeAndIdentifier() {
if (type == null) {
throw new IllegalStateException("Missing entity type!");
} else if (bedrockIdentifier == null) {
bedrockIdentifier = type.javaIdentifier().toString();
}
}
}
}

View File

@@ -61,7 +61,9 @@ public class EntityDefinitionBase<T extends Entity> {
return new Builder<>(clazz);
}
public static <T extends Entity> Builder<T> baseInherited(EntityDefinitionBase<? super T> parent) {
// Unused param so Java knows what entity we're talking about
@SuppressWarnings("unused")
public static <T extends Entity> Builder<T> baseInherited(Class<T> clazz, EntityDefinitionBase<? super T> parent) {
return new Builder<>(parent.width(), parent.height(), parent.offset(), new ObjectArrayList<>(parent.translators()));
}
@@ -97,7 +99,8 @@ public class EntityDefinitionBase<T extends Entity> {
}
// Unused param so Java knows what entity we're talking about
protected Builder(@SuppressWarnings("unused") Class<T> clazz) {
@SuppressWarnings("unused")
protected Builder(Class<T> clazz) {
this();
}

View File

@@ -0,0 +1,165 @@
/*
* 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;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.type.BoatEntity;
import org.geysermc.geyser.entity.type.ChestBoatEntity;
import org.geysermc.geyser.entity.type.DisplayBaseEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.FireballEntity;
import org.geysermc.geyser.entity.type.HangingEntity;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.ThrowableItemEntity;
import org.geysermc.geyser.entity.type.living.AbstractFishEntity;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.entity.type.living.MobEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.TameableEntity;
import org.geysermc.geyser.entity.type.living.monster.BasePiglinEntity;
import org.geysermc.geyser.entity.type.living.monster.raid.RaidParticipantEntity;
import org.geysermc.geyser.entity.type.living.monster.raid.SpellcasterIllagerEntity;
import org.geysermc.geyser.entity.type.player.AvatarEntity;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataTypes;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
public final class EntityDefinitionBases {
public static final EntityDefinitionBase<Entity> ENTITY;
public static final EntityDefinitionBase<DisplayBaseEntity> DISPLAY;
public static final EntityDefinitionBase<FireballEntity> FIREBALL;
public static final EntityDefinitionBase<ThrowableItemEntity> THROWABLE;
public static final EntityDefinitionBase<HangingEntity> HANGING;
public static final EntityDefinitionBase<BoatEntity> BOAT;
public static final EntityDefinitionBase<ChestBoatEntity> CHEST_BOAT;
public static final EntityDefinitionBase<LivingEntity> LIVING_ENTITY;
public static final EntityDefinitionBase<AvatarEntity> AVATAR;
public static final EntityDefinitionBase<MobEntity> MOB;
public static final EntityDefinitionBase<AbstractFishEntity> FISH;
public static final EntityDefinitionBase<BasePiglinEntity> PIGLIN;
public static final EntityDefinitionBase<RaidParticipantEntity> RAID_PARTICIPANT;
public static final EntityDefinitionBase<SpellcasterIllagerEntity> SPELLCASTER;
public static final EntityDefinitionBase<AgeableEntity> AGEABLE;
public static final EntityDefinitionBase<AbstractHorseEntity> HORSE;
public static final EntityDefinitionBase<TameableEntity> TAMABLE;
static {
ENTITY = EntityDefinition.baseBuilder(Entity.class)
.addTranslator(MetadataTypes.BYTE, Entity::setFlags)
.addTranslator(MetadataTypes.INT, Entity::setAir) // Air/bubbles
.addTranslator(MetadataTypes.OPTIONAL_COMPONENT, Entity::setDisplayName)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setDisplayNameVisible)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setSilent)
.addTranslator(MetadataTypes.BOOLEAN, Entity::setGravity)
.addTranslator(MetadataTypes.POSE, (entity, entityMetadata) -> entity.setPose(entityMetadata.getValue()))
.addTranslator(MetadataTypes.INT, Entity::setFreezing)
.build();
DISPLAY = EntityDefinitionBase.baseInherited(DisplayBaseEntity.class, ENTITY)
.addTranslator(null) // Interpolation delay
.addTranslator(null) // Transformation interpolation duration
.addTranslator(null) // Position/Rotation interpolation duration
.addTranslator(MetadataTypes.VECTOR3, DisplayBaseEntity::setTranslation) // Translation
.addTranslator(null) // Scale
.addTranslator(null) // Left rotation
.addTranslator(null) // Right rotation
.addTranslator(null) // Billboard render constraints
.addTranslator(null) // Brightness override
.addTranslator(null) // View range
.addTranslator(null) // Shadow radius
.addTranslator(null) // Shadow strength
.addTranslator(null) // Width
.addTranslator(null) // Height
.addTranslator(null) // Glow color override
.build();
FIREBALL = EntityDefinitionBase.baseInherited(FireballEntity.class, ENTITY)
.addTranslator(null) // Item
.build();
THROWABLE = EntityDefinitionBase.baseInherited(ThrowableItemEntity.class, ENTITY)
.addTranslator(MetadataTypes.ITEM_STACK, ThrowableItemEntity::setItem)
.build();
HANGING = EntityDefinitionBase.baseInherited(HangingEntity.class, ENTITY)
.addTranslator(MetadataTypes.DIRECTION, HangingEntity::setDirectionMetadata)
.build();
BOAT = EntityDefinitionBase.baseInherited(BoatEntity.class, ENTITY)
.height(0.6f).width(1.6f)
.offset(0.35f)
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, entityMetadata.getValue())) // Time since last hit
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Rocking direction
.addTranslator(MetadataTypes.FLOAT, (boatEntity, entityMetadata) ->
// 'Health' in Bedrock, damage taken in Java - it makes motion in Bedrock
boatEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, 40 - ((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue())))
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingLeft)
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingRight)
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.BOAT_BUBBLE_TIME, entityMetadata.getValue())) // May not actually do anything
.build();
CHEST_BOAT = EntityDefinitionBase.baseInherited(ChestBoatEntity.class, BOAT)
.build();
LIVING_ENTITY = EntityDefinitionBase.baseInherited(LivingEntity.class, ENTITY)
.addTranslator(MetadataTypes.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataTypes.FLOAT, LivingEntity::setHealth)
.addTranslator(MetadataTypes.PARTICLES, LivingEntity::setParticles)
.addTranslator(MetadataTypes.BOOLEAN,
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
.addTranslator(null) // Arrow count
.addTranslator(null) // Stinger count
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, LivingEntity::setBedPosition)
.build();
AVATAR = EntityDefinitionBase.baseInherited(AvatarEntity.class, LIVING_ENTITY)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(null) // Player main hand
.addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility)
.build();
MOB = EntityDefinitionBase.baseInherited(MobEntity.class, LIVING_ENTITY)
.addTranslator(MetadataTypes.BYTE, MobEntity::setMobFlags)
.build();
FISH = EntityDefinitionBase.baseInherited(AbstractFishEntity.class, MOB)
.addTranslator(null) // From bucket
.build();
PIGLIN = EntityDefinitionBase.baseInherited(BasePiglinEntity.class, MOB)
.addTranslator(MetadataTypes.BOOLEAN, BasePiglinEntity::setImmuneToZombification)
.build();
RAID_PARTICIPANT = EntityDefinitionBase.baseInherited(RaidParticipantEntity.class, MOB)
.addTranslator(null) // Celebrating //TODO
.build();
SPELLCASTER = EntityDefinitionBase.baseInherited(SpellcasterIllagerEntity.class, RAID_PARTICIPANT)
.addTranslator(MetadataTypes.BYTE, SpellcasterIllagerEntity::setSpellType)
.build();
AGEABLE = EntityDefinitionBase.baseInherited(AgeableEntity.class, MOB)
.addTranslator(MetadataTypes.BOOLEAN, AgeableEntity::setBaby)
.build();
HORSE = EntityDefinitionBase.baseInherited(AbstractHorseEntity.class, AGEABLE)
.addTranslator(MetadataTypes.BYTE, AbstractHorseEntity::setHorseFlags)
.build();
TAMABLE = EntityDefinitionBase.baseInherited(TameableEntity.class, AGEABLE)
.addTranslator(MetadataTypes.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataTypes.OPTIONAL_LIVING_ENTITY_REFERENCE, TameableEntity::setOwner)
.build();
}
private EntityDefinitionBases() {
}
}

View File

@@ -337,7 +337,7 @@ public final class EntityDefinitions {
public static final VanillaEntityDefinition<WitherSkullEntity> WITHER_SKULL_DANGEROUS;
static {
EntityDefinition<Entity> entityBase = VanillaEntityDefinition.builder(Entity::new)
EntityDefinitionBase<Entity> entityBase = EntityDefinition.baseBuilder(Entity.class)
.addTranslator(MetadataTypes.BYTE, Entity::setFlags)
.addTranslator(MetadataTypes.INT, Entity::setAir) // Air/bubbles
.addTranslator(MetadataTypes.OPTIONAL_COMPONENT, Entity::setDisplayName)
@@ -432,7 +432,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.INT, TNTEntity::setFuseLength)
.build();
VanillaEntityDefinition<DisplayBaseEntity> displayBase = VanillaEntityDefinition.inherited(DisplayBaseEntity::new, entityBase)
EntityDefinitionBase<DisplayBaseEntity> displayBase = EntityDefinitionBase.baseInherited(DisplayBaseEntity.class, entityBase)
.addTranslator(null) // Interpolation delay
.addTranslator(null) // Transformation interpolation duration
.addTranslator(null) // Position/Rotation interpolation duration
@@ -469,7 +469,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.BOOLEAN, InteractionEntity::setResponse)
.build();
VanillaEntityDefinition<FireballEntity> fireballBase = VanillaEntityDefinition.inherited(FireballEntity::new, entityBase)
EntityDefinitionBase<FireballEntity> fireballBase = EntityDefinitionBase.baseInherited(FireballEntity.class, entityBase)
.addTranslator(null) // Item
.build();
FIREBALL = VanillaEntityDefinition.inherited(FireballEntity::new, fireballBase)
@@ -481,7 +481,7 @@ public final class EntityDefinitions {
.heightAndWidth(0.3125f)
.build();
VanillaEntityDefinition<ThrowableItemEntity> throwableItemBase = VanillaEntityDefinition.inherited(ThrowableItemEntity::new, entityBase)
EntityDefinitionBase<ThrowableItemEntity> throwableItemBase = EntityDefinitionBase.baseInherited(ThrowableItemEntity.class, entityBase)
.addTranslator(MetadataTypes.ITEM_STACK, ThrowableItemEntity::setItem)
.build();
EGG = VanillaEntityDefinition.inherited(ThrowableEggEntity::new, throwableItemBase)
@@ -525,7 +525,7 @@ public final class EntityDefinitions {
.heightAndWidth(0.3125f)
.build();
VanillaEntityDefinition<AbstractArrowEntity> abstractArrowBase = VanillaEntityDefinition.inherited(AbstractArrowEntity::new, entityBase)
EntityDefinitionBase<AbstractArrowEntity> abstractArrowBase = EntityDefinitionBase.baseInherited(AbstractArrowEntity.class, entityBase)
.addTranslator(MetadataTypes.BYTE, AbstractArrowEntity::setArrowFlags)
.addTranslator(null) // "Piercing level"
.addTranslator(null) // If the arrow is in the ground
@@ -535,7 +535,7 @@ public final class EntityDefinitions {
.heightAndWidth(0.25f)
.addTranslator(MetadataTypes.INT, ArrowEntity::setPotionEffectColor)
.build();
SPECTRAL_ARROW = VanillaEntityDefinition.inherited(abstractArrowBase.factory(), abstractArrowBase)
SPECTRAL_ARROW = VanillaEntityDefinition.inherited(AbstractArrowEntity::new, abstractArrowBase)
.type(BuiltinEntityType.SPECTRAL_ARROW)
.heightAndWidth(0.25f)
.bedrockIdentifier("minecraft:arrow")
@@ -547,7 +547,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.BOOLEAN, (tridentEntity, entityMetadata) -> tridentEntity.setFlag(EntityFlag.ENCHANTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
VanillaEntityDefinition<HangingEntity> hangingEntityBase = VanillaEntityDefinition.<HangingEntity>inherited(null, entityBase)
EntityDefinitionBase<HangingEntity> hangingEntityBase = EntityDefinitionBase.baseInherited(HangingEntity.class, entityBase)
.addTranslator(MetadataTypes.DIRECTION, HangingEntity::setDirectionMetadata)
.build();
@@ -613,7 +613,7 @@ public final class EntityDefinitions {
// Boats
{
VanillaEntityDefinition<BoatEntity> boatBase = VanillaEntityDefinition.<BoatEntity>inherited(null, entityBase)
EntityDefinitionBase<BoatEntity> boatBase = EntityDefinitionBase.baseInherited(BoatEntity.class, entityBase)
.height(0.6f).width(1.6f)
.offset(0.35f)
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, entityMetadata.getValue())) // Time since last hit
@@ -637,7 +637,7 @@ public final class EntityDefinitions {
SPRUCE_BOAT = buildBoat(boatBase, BuiltinEntityType.SPRUCE_BOAT, BoatEntity.BoatVariant.SPRUCE);
PALE_OAK_BOAT = buildBoat(boatBase, BuiltinEntityType.PALE_OAK_BOAT, BoatEntity.BoatVariant.PALE_OAK);
VanillaEntityDefinition<ChestBoatEntity> chestBoatBase = VanillaEntityDefinition.<ChestBoatEntity>inherited(null, boatBase)
EntityDefinitionBase<ChestBoatEntity> chestBoatBase = EntityDefinitionBase.baseInherited(ChestBoatEntity.class, boatBase)
.build();
ACACIA_CHEST_BOAT = buildChestBoat(chestBoatBase, BuiltinEntityType.ACACIA_CHEST_BOAT, BoatEntity.BoatVariant.ACACIA);
@@ -652,7 +652,7 @@ public final class EntityDefinitions {
PALE_OAK_CHEST_BOAT = buildChestBoat(chestBoatBase, BuiltinEntityType.PALE_OAK_CHEST_BOAT, BoatEntity.BoatVariant.PALE_OAK);
}
VanillaEntityDefinition<LivingEntity> livingEntityBase = VanillaEntityDefinition.inherited(LivingEntity::new, entityBase)
EntityDefinitionBase<LivingEntity> livingEntityBase = EntityDefinitionBase.baseInherited(LivingEntity.class, entityBase)
.addTranslator(MetadataTypes.BYTE, LivingEntity::setLivingEntityFlags)
.addTranslator(MetadataTypes.FLOAT, LivingEntity::setHealth)
.addTranslator(MetadataTypes.PARTICLES, LivingEntity::setParticles)
@@ -675,7 +675,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.ROTATIONS, ArmorStandEntity::setRightLegRotation)
.build();
VanillaEntityDefinition<AvatarEntity> avatarEntityBase = VanillaEntityDefinition.<AvatarEntity>inherited(null, livingEntityBase)
EntityDefinitionBase<AvatarEntity> avatarEntityBase = EntityDefinitionBase.baseInherited(AvatarEntity.class, livingEntityBase)
.height(1.8f).width(0.6f)
.offset(1.62f)
.addTranslator(null) // Player main hand
@@ -697,7 +697,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.OPTIONAL_UNSIGNED_INT, PlayerEntity::setRightParrot)
.build();
VanillaEntityDefinition<MobEntity> mobEntityBase = VanillaEntityDefinition.inherited(MobEntity::new, livingEntityBase)
EntityDefinitionBase<MobEntity> mobEntityBase = EntityDefinitionBase.baseInherited(MobEntity.class, livingEntityBase)
.addTranslator(MetadataTypes.BYTE, MobEntity::setMobFlags)
.build();
@@ -907,10 +907,10 @@ public final class EntityDefinitions {
.type(BuiltinEntityType.MAGMA_CUBE)
.build();
VanillaEntityDefinition<AbstractFishEntity> abstractFishEntityBase = VanillaEntityDefinition.inherited(AbstractFishEntity::new, mobEntityBase)
EntityDefinitionBase<AbstractFishEntity> abstractFishEntityBase = EntityDefinitionBase.baseInherited(AbstractFishEntity.class, mobEntityBase)
.addTranslator(null) // From bucket
.build();
COD = VanillaEntityDefinition.inherited(abstractFishEntityBase.factory(), abstractFishEntityBase)
COD = VanillaEntityDefinition.inherited(AbstractFishEntity::new, abstractFishEntityBase)
.type(BuiltinEntityType.COD)
.height(0.25f).width(0.5f)
.build();
@@ -919,7 +919,7 @@ public final class EntityDefinitions {
.heightAndWidth(0.7f)
.addTranslator(MetadataTypes.INT, PufferFishEntity::setPufferfishSize)
.build();
SALMON = VanillaEntityDefinition.inherited(abstractFishEntityBase.factory(), abstractFishEntityBase)
SALMON = VanillaEntityDefinition.inherited(AbstractFishEntity::new, abstractFishEntityBase)
.type(BuiltinEntityType.SALMON)
.height(0.5f).width(0.7f)
.addTranslator(null) // Scale/variant - TODO
@@ -935,7 +935,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.INT, TropicalFishEntity::setFishVariant)
.build();
VanillaEntityDefinition<BasePiglinEntity> abstractPiglinEntityBase = VanillaEntityDefinition.inherited(BasePiglinEntity::new, mobEntityBase)
EntityDefinitionBase<BasePiglinEntity> abstractPiglinEntityBase = EntityDefinitionBase.baseInherited(BasePiglinEntity.class, mobEntityBase)
.addTranslator(MetadataTypes.BOOLEAN, BasePiglinEntity::setImmuneToZombification)
.build();
PIGLIN = VanillaEntityDefinition.inherited(PiglinEntity::new, abstractPiglinEntityBase)
@@ -945,23 +945,23 @@ public final class EntityDefinitions {
.addTranslator(MetadataTypes.BOOLEAN, PiglinEntity::setChargingCrossbow)
.addTranslator(MetadataTypes.BOOLEAN, PiglinEntity::setDancing)
.build();
PIGLIN_BRUTE = VanillaEntityDefinition.inherited(abstractPiglinEntityBase.factory(), abstractPiglinEntityBase)
PIGLIN_BRUTE = VanillaEntityDefinition.inherited(BasePiglinEntity::new, abstractPiglinEntityBase)
.type(BuiltinEntityType.PIGLIN_BRUTE)
.height(1.95f).width(0.6f)
.build();
VanillaEntityDefinition<RaidParticipantEntity> raidParticipantEntityBase = VanillaEntityDefinition.inherited(RaidParticipantEntity::new, mobEntityBase)
EntityDefinitionBase<RaidParticipantEntity> raidParticipantEntityBase = EntityDefinitionBase.baseInherited(RaidParticipantEntity.class, mobEntityBase)
.addTranslator(null) // Celebrating //TODO
.build();
VanillaEntityDefinition<SpellcasterIllagerEntity> spellcasterEntityBase = VanillaEntityDefinition.inherited(SpellcasterIllagerEntity::new, raidParticipantEntityBase)
EntityDefinitionBase<SpellcasterIllagerEntity> spellcasterEntityBase = EntityDefinitionBase.baseInherited(SpellcasterIllagerEntity.class, raidParticipantEntityBase)
.addTranslator(MetadataTypes.BYTE, SpellcasterIllagerEntity::setSpellType)
.build();
EVOKER = VanillaEntityDefinition.inherited(spellcasterEntityBase.factory(), spellcasterEntityBase)
EVOKER = VanillaEntityDefinition.inherited(SpellcasterIllagerEntity::new, spellcasterEntityBase)
.type(BuiltinEntityType.EVOKER)
.height(1.95f).width(0.6f)
.bedrockIdentifier("minecraft:evocation_illager")
.build();
ILLUSIONER = VanillaEntityDefinition.inherited(spellcasterEntityBase.factory(), spellcasterEntityBase)
ILLUSIONER = VanillaEntityDefinition.inherited(SpellcasterIllagerEntity::new, spellcasterEntityBase)
.type(BuiltinEntityType.ILLUSIONER)
.height(1.95f).width(0.6f)
.bedrockIdentifier("minecraft:evocation_illager")
@@ -981,7 +981,7 @@ public final class EntityDefinitions {
.height(1.8f).width(0.6f)
.offset(1.62f)
.build();
WITCH = VanillaEntityDefinition.inherited(raidParticipantEntityBase.factory(), raidParticipantEntityBase)
WITCH = VanillaEntityDefinition.inherited(RaidParticipantEntity::new, raidParticipantEntityBase)
.type(BuiltinEntityType.WITCH)
.height(1.8f).width(0.6f)
.offset(1.62f)
@@ -989,7 +989,7 @@ public final class EntityDefinitions {
.build();
}
VanillaEntityDefinition<AgeableEntity> ageableEntityBase = VanillaEntityDefinition.inherited(AgeableEntity::new, mobEntityBase)
EntityDefinitionBase<AgeableEntity> ageableEntityBase = EntityDefinitionBase.baseInherited(AgeableEntity.class, mobEntityBase)
.addTranslator(MetadataTypes.BOOLEAN, AgeableEntity::setBaby)
.build();
@@ -1164,7 +1164,7 @@ public final class EntityDefinitions {
// Horses
{
VanillaEntityDefinition<AbstractHorseEntity> abstractHorseEntityBase = VanillaEntityDefinition.inherited(AbstractHorseEntity::new, ageableEntityBase)
EntityDefinitionBase<AbstractHorseEntity> abstractHorseEntityBase = EntityDefinitionBase.baseInherited(AbstractHorseEntity.class, ageableEntityBase)
.addTranslator(MetadataTypes.BYTE, AbstractHorseEntity::setHorseFlags)
.build();
CAMEL = VanillaEntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
@@ -1186,14 +1186,14 @@ public final class EntityDefinitions {
.type(BuiltinEntityType.ZOMBIE_HORSE)
.height(1.6f).width(1.3965f)
.build();
VanillaEntityDefinition<ChestedHorseEntity> chestedHorseEntityBase = VanillaEntityDefinition.inherited(ChestedHorseEntity::new, abstractHorseEntityBase)
EntityDefinitionBase<ChestedHorseEntity> chestedHorseEntityBase = EntityDefinitionBase.baseInherited(ChestedHorseEntity.class, abstractHorseEntityBase)
.addTranslator(MetadataTypes.BOOLEAN, (horseEntity, entityMetadata) -> horseEntity.setFlag(EntityFlag.CHESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
DONKEY = VanillaEntityDefinition.inherited(chestedHorseEntityBase.factory(), chestedHorseEntityBase)
DONKEY = VanillaEntityDefinition.inherited(ChestedHorseEntity::new, chestedHorseEntityBase)
.type(BuiltinEntityType.DONKEY)
.height(1.6f).width(1.3965f)
.build();
MULE = VanillaEntityDefinition.inherited(chestedHorseEntityBase.factory(), chestedHorseEntityBase)
MULE = VanillaEntityDefinition.inherited(ChestedHorseEntity::new, chestedHorseEntityBase)
.type(BuiltinEntityType.MULE)
.height(1.6f).width(1.3965f)
.build();
@@ -1209,7 +1209,7 @@ public final class EntityDefinitions {
.build();
}
VanillaEntityDefinition<TameableEntity> tameableEntityBase = VanillaEntityDefinition.<TameableEntity>inherited(null, ageableEntityBase) // No factory, is abstract
EntityDefinitionBase<TameableEntity> tameableEntityBase = EntityDefinitionBase.baseInherited(TameableEntity.class, ageableEntityBase)
.addTranslator(MetadataTypes.BYTE, TameableEntity::setTameableFlags)
.addTranslator(MetadataTypes.OPTIONAL_LIVING_ENTITY_REFERENCE, TameableEntity::setOwner)
.build();
@@ -1246,7 +1246,7 @@ public final class EntityDefinitions {
Registries.JAVA_ENTITY_IDENTIFIERS.get().put("minecraft:marker", null); // We don't need an entity definition for this as it is never sent over the network
}
private static VanillaEntityDefinition<BoatEntity> buildBoat(VanillaEntityDefinition<BoatEntity> base, BuiltinEntityType BuiltinEntityType, BoatEntity.BoatVariant variant) {
private static VanillaEntityDefinition<BoatEntity> buildBoat(EntityDefinitionBase<BoatEntity> base, BuiltinEntityType BuiltinEntityType, BoatEntity.BoatVariant variant) {
return VanillaEntityDefinition.inherited((session, javaId, bedrockId, uuid, definition, position, motion, yaw, pitch, headYaw) ->
new BoatEntity(session, javaId, bedrockId, uuid, definition, position, motion, yaw, variant), base)
.type(BuiltinEntityType)
@@ -1254,7 +1254,7 @@ public final class EntityDefinitions {
.build();
}
private static VanillaEntityDefinition<ChestBoatEntity> buildChestBoat(VanillaEntityDefinition<ChestBoatEntity> base, BuiltinEntityType BuiltinEntityType, BoatEntity.BoatVariant variant) {
private static VanillaEntityDefinition<ChestBoatEntity> buildChestBoat(EntityDefinitionBase<ChestBoatEntity> base, BuiltinEntityType BuiltinEntityType, BoatEntity.BoatVariant variant) {
return VanillaEntityDefinition.inherited((session, javaId, bedrockId, uuid, definition, position, motion, yaw, pitch, headYaw) ->
new ChestBoatEntity(session, javaId, bedrockId, uuid, definition, position, motion, yaw, variant), base)
.type(BuiltinEntityType)

View File

@@ -0,0 +1,152 @@
/*
* 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;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.entity.CustomEntityDefinition;
import org.geysermc.geyser.api.entity.JavaEntityType;
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
import org.geysermc.geyser.api.predicate.PredicateStrategy;
import org.geysermc.geyser.api.predicate.context.entity.EntitySpawnContext;
import org.geysermc.geyser.entity.factory.EntityFactory;
import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
import org.geysermc.geyser.entity.properties.type.PropertyType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.translator.entity.EntityMetadataTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
@Getter
@Accessors(fluent = true)
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class GeyserCustomEntityDefinition<T extends Entity> extends EntityDefinition<T> implements CustomEntityDefinition {
private final List<MinecraftPredicate<? super EntitySpawnContext>> predicates;
private final PredicateStrategy predicateStrategy;
public GeyserCustomEntityDefinition(EntityFactory<T> factory, String bedrockIdentifier, List<MinecraftPredicate<? super EntitySpawnContext>> predicates, PredicateStrategy predicateStrategy,
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
super(factory, bedrockIdentifier, width, height, offset, registeredProperties, translators);
this.predicates = predicates;
this.predicateStrategy = predicateStrategy;
}
public static Builder<?> inherited(String bedrockIdentifier, JavaEntityType vanillaType) {
if (!vanillaType.vanilla()) {
throw new IllegalArgumentException("vanillaType must be a vanilla entity type, was: " + vanillaType);
}
VanillaEntityDefinition<?> parent = Registries.ENTITY_DEFINITIONS.get(vanillaType);
if (parent == null) {
throw new IllegalArgumentException("No vanilla entity definition registered for vanilla entity type " + vanillaType);
}
// TODO fix the rawtypes/unchecked
return new Builder<>(bedrockIdentifier, parent.factory(), parent.width(), parent.height(), parent.offset(), new ObjectArrayList(parent.translators()));
}
public static class Builder<T extends Entity> extends EntityDefinition.Builder<T> implements CustomEntityDefinition.Builder {
protected List<MinecraftPredicate<? super EntitySpawnContext>> predicates;
protected PredicateStrategy predicateStrategy = PredicateStrategy.AND;
protected Builder(EntityFactory<T> factory, String bedrockIdentifier) {
super(factory);
this.bedrockIdentifier = Objects.requireNonNull(bedrockIdentifier, "bedrockIdentifier must not be null");
}
protected Builder(String bedrockIdentifier, EntityFactory<T> factory, float width, float height, float offset, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
super(factory, width, height, offset, translators);
this.bedrockIdentifier = Objects.requireNonNull(bedrockIdentifier, "bedrockIdentifier must not be null");
}
@Override
public EntityDefinition.Builder<T> bedrockIdentifier(String bedrockIdentifier) {
throw new UnsupportedOperationException("bedrockIdentifier should be immutable");
}
@Override
public Builder<T> width(@Positive float width) {
return (Builder<T>) super.width(width);
}
@Override
public Builder<T> height(@Positive float height) {
return (Builder<T>) super.height(height);
}
@Override
public Builder<T> heightAndWidth(@Positive float value) {
return (Builder<T>) super.heightAndWidth(value);
}
@Override
public Builder<T> offset(@Positive float offset) {
return (Builder<T>) super.offset(offset);
}
public Builder<T> predicate(@NonNull MinecraftPredicate<? super EntitySpawnContext> predicate) {
predicates.add(Objects.requireNonNull(predicate, "predicate must not be null"));
return this;
}
public Builder<T> predicateStrategy(@NonNull PredicateStrategy strategy) {
predicateStrategy = Objects.requireNonNull(strategy, "strategy must not be null");
return this;
}
@Override
public Builder<T> property(PropertyType<?, ?> propertyType) {
return (Builder<T>) super.property(propertyType);
}
@Override
public <U, EM extends EntityMetadata<U, ? extends MetadataType<U>>> Builder<T> addTranslator(MetadataType<U> type, BiConsumer<T, EM> translateFunction) {
return (Builder<T>) super.addTranslator(type, translateFunction);
}
@Override
public Builder<T> addTranslator(EntityMetadataTranslator<T, ?, ?> translator) {
return (Builder<T>) super.addTranslator(translator);
}
@Override
public GeyserCustomEntityDefinition<T> build() {
if (predicates.isEmpty()) {
throw new IllegalStateException("predicates must not be empty!");
}
return new GeyserCustomEntityDefinition<>(factory, bedrockIdentifier, predicates, predicateStrategy, width, height, offset, propertiesBuilder.build(), translators);
}
}
}

View File

@@ -72,6 +72,11 @@ public record GeyserEntityType(Identifier javaIdentifier, int javaId) implements
return javaIdentifier.equals(UNREGISTERED);
}
@Override
public boolean vanilla() {
return VANILLA.containsValue(this);
}
public boolean is(EntityType type) {
return javaId == type.id();
}

View File

@@ -48,21 +48,24 @@ import java.util.function.BiConsumer;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class VanillaEntityDefinition<T extends Entity> extends EntityDefinition<T> {
private final GeyserEntityType entityType;
public VanillaEntityDefinition(EntityFactory<T> factory, GeyserEntityType entityType, String bedrockIdentifier,
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
super(factory, entityType, bedrockIdentifier, width, height, offset, registeredProperties, translators);
super(factory, bedrockIdentifier, width, height, offset, registeredProperties, translators);
this.entityType = entityType;
}
public static <T extends Entity> Builder<T> builder(EntityFactory<T> factory) {
return new Builder<>(factory);
}
public static <T extends Entity> Builder<T> inherited(EntityFactory<T> factory, EntityDefinition<? super T> parent) {
public static <T extends Entity> Builder<T> inherited(EntityFactory<T> factory, EntityDefinitionBase<? super T> parent) {
return new Builder<>(factory, parent.width(), parent.height(), parent.offset(), new ObjectArrayList<>(parent.translators()));
}
public static class Builder<T extends Entity> extends EntityDefinition.Builder<T> {
protected GeyserEntityType type;
protected Builder(EntityFactory<T> factory) {
super(factory);
@@ -72,8 +75,13 @@ public class VanillaEntityDefinition<T extends Entity> extends EntityDefinition<
super(factory, width, height, offset, translators);
}
/**
* Resets the bedrock identifier as well
*/
public Builder<T> type(BuiltinEntityType type) {
return (Builder<T>) super.type(GeyserEntityType.ofVanilla(type));
this.type = GeyserEntityType.ofVanilla(type);
this.bedrockIdentifier = null;
return this;
}
@Override
@@ -121,6 +129,14 @@ public class VanillaEntityDefinition<T extends Entity> extends EntityDefinition<
return build(true);
}
private void validateTypeAndIdentifier() {
if (type == null) {
throw new IllegalStateException("Missing entity type!");
} else if (bedrockIdentifier == null) {
bedrockIdentifier = type.javaIdentifier().toString();
}
}
/**
* @param register whether to register this entity in the Registries for entity types. Generally this should be
* set to false if we're not expecting this entity to spawn from the network.

View File

@@ -38,7 +38,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.entity.JavaEntityType;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.VanillaEntityDefinition;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.pack.ResourcePackHolder;
@@ -65,14 +65,12 @@ import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
import org.geysermc.geyser.translator.sound.SoundTranslator;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent;
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -126,7 +124,8 @@ public final class Registries {
* A map containing all entity types and their respective Geyser definitions
*/
// Is a Reference2ObjectMap since GeyserEntityType, the implementation of JavaEntityType, only ever keeps one instance per registered entity type
public static final SimpleMappedRegistry<JavaEntityType, EntityDefinition<?>> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(Reference2ObjectOpenHashMap::new));
// TODO rename to VANILLA_ENTITY_DEFINITIONS
public static final SimpleMappedRegistry<JavaEntityType, VanillaEntityDefinition<?>> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(Reference2ObjectOpenHashMap::new));
/**
* A registry holding a list of all the known entity properties to be sent to the client after start game.
@@ -136,7 +135,7 @@ public final class Registries {
/**
* A map containing all Java entity identifiers and their respective Geyser definitions
*/
public static final SimpleMappedRegistry<String, EntityDefinition<?>> JAVA_ENTITY_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
public static final SimpleMappedRegistry<String, VanillaEntityDefinition<?>> JAVA_ENTITY_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
/**
* A registry containing all the Java packet translators.

View File

@@ -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.CustomEntityDefinition;
import org.geysermc.geyser.api.entity.JavaEntityType;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.extension.Extension;
@@ -46,6 +47,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.GeyserCustomEntityDefinition;
import org.geysermc.geyser.entity.GeyserEntityType;
import org.geysermc.geyser.event.GeyserEventRegistrar;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
@@ -113,6 +115,7 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
// entities
providers.put(JavaEntityType.class, args -> args.length == 1 ? GeyserEntityType.ofVanilla((Identifier) args[0]) : GeyserEntityType.createCustom((Identifier) args[0], (int) args[1]));
providers.put(CustomEntityDefinition.class, args -> GeyserCustomEntityDefinition.inherited((String) args[0], (JavaEntityType) args[1]));
return providers;
}