-
-
Notifications
You must be signed in to change notification settings - Fork 806
Feature: Custom Entity API #6010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…nition, create GeyserEntityType, target custom entities MCPL
…can now be modified in the ServerSpawnEntityEvent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a Custom Entity API for Geyser, refactoring entity creation to use a context-based approach instead of individual constructor parameters. The changes enable support for custom entities while modernizing the entity type system.
Key Changes:
- Introduced
EntitySpawnContextas a unified way to pass entity creation parameters - Renamed
EntityDefinitiontoEntityTypeDefinitionfor clarity - Changed
EntityTypetoBuiltinEntityTypeto distinguish vanilla entities from custom ones - Added new registries for custom entities and Bedrock entity definitions
- Refactored 100+ entity class constructors to use the new context pattern
Reviewed changes
Copilot reviewed 192 out of 193 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle/libs.versions.toml | Updated mcprotocollib version to feature branch for custom entities |
| Test files | Updated mock entity creation to use EntitySpawnContext |
| StatisticsUtils.java | Changed entity name translation to use GeyserEntityType |
| EntityUtils.java | Refactored entity type comparisons from switch to if-else with .is() method |
| Translator classes | Updated to use BuiltinEntityType and new entity creation patterns |
| Session/cache classes | Updated entity instantiation with EntitySpawnContext |
| Registry classes | Added new registries for custom entity support |
| Entity hierarchy | All entity constructors refactored to accept EntitySpawnContext |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
...a/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java
Outdated
Show resolved
Hide resolved
...in/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/entity/NonVanillaEntityTypeDefinition.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 222 out of 223 changed files in this pull request and generated 11 comments.
Comments suppressed due to low confidence (1)
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java:610
- getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/GeyserWaypoint.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 243 out of 244 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 244 out of 245 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tities-api # Conflicts: # core/src/main/java/org/geysermc/geyser/entity/type/Entity.java # core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/MannequinEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java # core/src/main/java/org/geysermc/geyser/skin/SkinManager.java # core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveMobEffectTranslator.java # core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 248 out of 249 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java:625
- getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| List<Hitbox> boxes = new ArrayList<>(); | ||
| List<NbtMap> hitboxes = metaDataMap.getList("Hitxboxes", NbtType.COMPOUND); |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in NBT key name: "Hitxboxes" should be "Hitboxes" (note the extra 'x')
…tities-api # Conflicts: # core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java # core/src/main/java/org/geysermc/geyser/entity/spawn/EntitySpawnContext.java # core/src/main/java/org/geysermc/geyser/entity/type/AbstractArrowEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/Entity.java # core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/InteractionEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/LeashKnotEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/WitherSkullEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/AbstractFishEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/AgeableEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/MagmaCubeEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/SquidEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/AbstractMerchantEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/raid/SpellcasterIllagerEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java # core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java # core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java # core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java # core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java # core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java # core/src/main/java/org/geysermc/geyser/translator/inventory/MerchantInventoryTranslator.java # core/src/main/java/org/geysermc/geyser/translator/level/block/entity/VaultBlockEntityTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaAddEntityTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoUpdateTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaGameEventTranslator.java # core/src/test/java/org/geysermc/geyser/scoreboard/network/util/GeyserMockContextScoreboard.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 133 out of 135 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
core/src/main/java/org/geysermc/geyser/entity/type/Entity.java:105
- This method overrides GeyserEntity.uuid; it is advisable to add an Override annotation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return NbtMap.builder().putList("Hitboxes", NbtType.COMPOUND, list).build(); | ||
| } | ||
|
|
||
| public static class Builder implements Hitbox.Builder { |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Builder has the same name as its supertype org.geysermc.geyser.api.entity.data.types.Hitbox$Builder.
…tities-api # Conflicts: # core/src/main/java/org/geysermc/geyser/entity/VanillaEntities.java # core/src/main/java/org/geysermc/geyser/entity/type/DisplayBaseEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/ShulkerEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java # core/src/main/java/org/geysermc/geyser/util/DimensionUtils.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 133 out of 134 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Override | ||
| public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { | ||
| public void moveAbsolute(Vector3f javaPosition, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) { | ||
| // It's vanilla behaviour to lerp if the position is within 64 blocks, however we also check if the position is close enough to the player | ||
| // position to see if it can actually affect anything to save network. | ||
| if (shouldLerp() && position.distanceSquared(this.position) < 4096 && position.distanceSquared(session.getPlayerEntity().position()) < 4096) { | ||
| if (shouldLerp() && javaPosition.distanceSquared(javaPosition) < 4096 && javaPosition.distanceSquared(session.getPlayerEntity().position()) < 4096) { | ||
| this.dirtyPitch = this.dirtyYaw = this.dirtyHeadYaw = true; | ||
|
|
||
| setYaw(yaw); | ||
| setPitch(pitch); | ||
| setHeadYaw(headYaw); | ||
| setOnGround(isOnGround); | ||
|
|
||
| this.lerpPosition = position; | ||
| this.lerpPosition = javaPosition; | ||
| this.lerpSteps = 3; | ||
| } else { | ||
| super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported); | ||
| super.moveAbsolute(javaPosition, yaw, pitch, headYaw, isOnGround, teleported); | ||
| } |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In moveAbsolute, the distance check for deciding whether to lerp is incorrect: javaPosition.distanceSquared(javaPosition) always returns 0, so the first half of the condition is effectively always true. This both defeats the intended 64-block range guard and can lead to unnecessary interpolation behavior; the comparison should be against the entity’s current position (e.g., javaPosition.distanceSquared(position())).
Introducing: Custom Entities!
tl;dr: with this PR, you can summon custom bedrock entities that replace the vanilla mapping
Additions
CustomEntityDefinition/GeyserEntityDefinition: Representations of custom, and vanilla Bedrock entities. Unlike custom blocks/items, bedrock entities have fewer properties that are defined in advance; it's just the identifier, and the Bedrock Entity Properties. Setting entity properties was introduced in a previous PR; and works the same way.JavaEntityType: Represents a vanilla Java entity type, with the width / height / type identifier, as well as the default Bedrock entity associated with it. Similarly,CustomJavaEntityTyperepresents a non-vanilla Java entity - however, that part of the API still needs some more work and should be regarded as unstable.GeyserEntityDataType/GeyserEntityDataTypes: These are representations of various Bedrock entity metadata types, such as scale, width, height, variant, or color. Further,vertical_offsethas been added as a "custom" data type to allow setting a vertical entity offset.The
GeyserEntityclass has seen major additions! You can now query the entities' associated Bedrock entity definition, Java position, the Geyser id, UUID, or update / query the aforementioned data types.You can now look up
GeyserEntityinstances using the entity UUID or Geyser entity ID, additionally to the Java entity id.New Events
GeyserDefineEntitiesEvent: Allows registering custom Bedrock entities and querying existing entities.SessionSpawnEntityEvent: Base entity spawn event extended by the server events. With it, you can set a pre-spawn consumer, and switch the Bedrock entity definition, or cancel the entity spawn outright.ServerAttachParrotsEvent: Called every time a parrot is spawned on the player entityServerSpawnEntityEvent: Called for every non-player entity that is spawned by the Java server. Within this event, you can query the Java entity type, uuid, and entity id (and also have access to the methods provided by theSessionSpawnEntityEvent!Here's some example code of the API in action:
https://gist.github.com/onebeastchris/1521ab585669792a79a9558d9d069834
Internal changes:
EntitySpawnContextthat is passed in entity constructors - instead of many arguments. This should make it easier to add new entities, call events, or add more arguments in the futureEntityclass - no more hacks in TntEntity and the like!TO-DO's:
EXPERIMENTAL downloads: