Skip to content

Commit c1a8564

Browse files
Work on exposing custom entity definitions in the API
1 parent ed617a9 commit c1a8564

File tree

11 files changed

+467
-62
lines changed

11 files changed

+467
-62
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2025 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.api.entity;
27+
28+
import org.checkerframework.checker.index.qual.Positive;
29+
import org.checkerframework.checker.nullness.qual.NonNull;
30+
import org.checkerframework.common.returnsreceiver.qual.This;
31+
import org.geysermc.geyser.api.GeyserApi;
32+
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
33+
import org.geysermc.geyser.api.predicate.PredicateStrategy;
34+
import org.geysermc.geyser.api.predicate.context.entity.EntitySpawnContext;
35+
import org.geysermc.geyser.api.util.GenericBuilder;
36+
37+
import java.util.List;
38+
39+
public interface CustomEntityDefinition {
40+
41+
// TODO Identifier
42+
String bedrockIdentifier();
43+
44+
float width();
45+
46+
float height();
47+
48+
float offset();
49+
50+
List<MinecraftPredicate<? super EntitySpawnContext>> predicates();
51+
52+
PredicateStrategy predicateStrategy();
53+
54+
static Builder builder(@NonNull String bedrockIdentifier, @NonNull JavaEntityType vanillaType) {
55+
return GeyserApi.api().provider(Builder.class, bedrockIdentifier, vanillaType);
56+
}
57+
58+
interface Builder extends GenericBuilder<CustomEntityDefinition> {
59+
60+
@This
61+
Builder width(@Positive float width);
62+
63+
@This
64+
Builder height(@Positive float height);
65+
66+
@This
67+
Builder heightAndWidth(@Positive float value);
68+
69+
@This
70+
Builder offset(@Positive float offset);
71+
72+
@This
73+
Builder predicate(@NonNull MinecraftPredicate<? super EntitySpawnContext> predicate);
74+
75+
@This
76+
Builder predicateStrategy(@NonNull PredicateStrategy strategy);
77+
78+
@Override
79+
CustomEntityDefinition build();
80+
}
81+
}

api/src/main/java/org/geysermc/geyser/api/entity/JavaEntityType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public interface JavaEntityType {
3838

3939
boolean isUnregistered();
4040

41+
boolean vanilla();
42+
4143
default boolean is(Identifier javaIdentifier) {
4244
return javaIdentifier().equals(javaIdentifier);
4345
}

core/src/main/java/org/geysermc/geyser/entity/EntityDefinition.java

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,13 @@
5555
@ToString(callSuper = true)
5656
public abstract class EntityDefinition<T extends Entity> extends EntityDefinitionBase<T> {
5757
private final EntityFactory<T> factory;
58-
private final GeyserEntityType entityType;
5958
private final String bedrockIdentifier;
6059
private final GeyserEntityProperties registeredProperties;
6160

62-
public EntityDefinition(EntityFactory<T> factory, GeyserEntityType entityType, String bedrockIdentifier,
61+
public EntityDefinition(EntityFactory<T> factory, String bedrockIdentifier,
6362
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
6463
super(width, height, offset, translators);
6564
this.factory = factory;
66-
this.entityType = entityType;
6765
this.bedrockIdentifier = bedrockIdentifier;
6866
this.registeredProperties = registeredProperties;
6967
}
@@ -72,8 +70,6 @@ public EntityDefinition(EntityFactory<T> factory, GeyserEntityType entityType, S
7270
@Accessors(fluent = true, chain = true)
7371
public static abstract class Builder<T extends Entity> extends EntityDefinitionBase.Builder<T> {
7472
protected final EntityFactory<T> factory;
75-
@Setter(AccessLevel.NONE)
76-
protected GeyserEntityType type;
7773
protected String bedrockIdentifier;
7874
@Setter(AccessLevel.NONE)
7975
protected GeyserEntityProperties.Builder propertiesBuilder;
@@ -91,15 +87,6 @@ protected Builder(EntityFactory<T> factory, float width, float height, float off
9187
this.offset = offset;
9288
}
9389

94-
/**
95-
* Resets the bedrock identifier as well
96-
*/
97-
public Builder<T> type(GeyserEntityType type) {
98-
this.type = type;
99-
this.bedrockIdentifier = null;
100-
return this;
101-
}
102-
10390
@Override
10491
public Builder<T> width(float width) {
10592
return (Builder<T>) super.width(width);
@@ -137,13 +124,5 @@ public Builder<T> property(PropertyType<?, ?> propertyType) {
137124
propertiesBuilder.add(propertyType);
138125
return this;
139126
}
140-
141-
protected void validateTypeAndIdentifier() {
142-
if (type == null) {
143-
throw new IllegalStateException("Missing entity type!");
144-
} else if (bedrockIdentifier == null) {
145-
bedrockIdentifier = type.javaIdentifier().toString();
146-
}
147-
}
148127
}
149128
}

core/src/main/java/org/geysermc/geyser/entity/EntityDefinitionBase.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ public static <T extends Entity> Builder<T> baseBuilder(Class<T> clazz) {
6161
return new Builder<>(clazz);
6262
}
6363

64-
public static <T extends Entity> Builder<T> baseInherited(EntityDefinitionBase<? super T> parent) {
64+
// Unused param so Java knows what entity we're talking about
65+
@SuppressWarnings("unused")
66+
public static <T extends Entity> Builder<T> baseInherited(Class<T> clazz, EntityDefinitionBase<? super T> parent) {
6567
return new Builder<>(parent.width(), parent.height(), parent.offset(), new ObjectArrayList<>(parent.translators()));
6668
}
6769

@@ -97,7 +99,8 @@ protected Builder() {
9799
}
98100

99101
// Unused param so Java knows what entity we're talking about
100-
protected Builder(@SuppressWarnings("unused") Class<T> clazz) {
102+
@SuppressWarnings("unused")
103+
protected Builder(Class<T> clazz) {
101104
this();
102105
}
103106

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright (c) 2025 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.entity;
27+
28+
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
29+
import org.geysermc.geyser.entity.type.BoatEntity;
30+
import org.geysermc.geyser.entity.type.ChestBoatEntity;
31+
import org.geysermc.geyser.entity.type.DisplayBaseEntity;
32+
import org.geysermc.geyser.entity.type.Entity;
33+
import org.geysermc.geyser.entity.type.FireballEntity;
34+
import org.geysermc.geyser.entity.type.HangingEntity;
35+
import org.geysermc.geyser.entity.type.LivingEntity;
36+
import org.geysermc.geyser.entity.type.ThrowableItemEntity;
37+
import org.geysermc.geyser.entity.type.living.AbstractFishEntity;
38+
import org.geysermc.geyser.entity.type.living.AgeableEntity;
39+
import org.geysermc.geyser.entity.type.living.MobEntity;
40+
import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
41+
import org.geysermc.geyser.entity.type.living.animal.tameable.TameableEntity;
42+
import org.geysermc.geyser.entity.type.living.monster.BasePiglinEntity;
43+
import org.geysermc.geyser.entity.type.living.monster.raid.RaidParticipantEntity;
44+
import org.geysermc.geyser.entity.type.living.monster.raid.SpellcasterIllagerEntity;
45+
import org.geysermc.geyser.entity.type.player.AvatarEntity;
46+
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataTypes;
47+
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
48+
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
49+
50+
public final class EntityDefinitionBases {
51+
public static final EntityDefinitionBase<Entity> ENTITY;
52+
public static final EntityDefinitionBase<DisplayBaseEntity> DISPLAY;
53+
public static final EntityDefinitionBase<FireballEntity> FIREBALL;
54+
public static final EntityDefinitionBase<ThrowableItemEntity> THROWABLE;
55+
public static final EntityDefinitionBase<HangingEntity> HANGING;
56+
public static final EntityDefinitionBase<BoatEntity> BOAT;
57+
public static final EntityDefinitionBase<ChestBoatEntity> CHEST_BOAT;
58+
public static final EntityDefinitionBase<LivingEntity> LIVING_ENTITY;
59+
public static final EntityDefinitionBase<AvatarEntity> AVATAR;
60+
public static final EntityDefinitionBase<MobEntity> MOB;
61+
public static final EntityDefinitionBase<AbstractFishEntity> FISH;
62+
public static final EntityDefinitionBase<BasePiglinEntity> PIGLIN;
63+
public static final EntityDefinitionBase<RaidParticipantEntity> RAID_PARTICIPANT;
64+
public static final EntityDefinitionBase<SpellcasterIllagerEntity> SPELLCASTER;
65+
public static final EntityDefinitionBase<AgeableEntity> AGEABLE;
66+
public static final EntityDefinitionBase<AbstractHorseEntity> HORSE;
67+
public static final EntityDefinitionBase<TameableEntity> TAMABLE;
68+
69+
static {
70+
ENTITY = EntityDefinition.baseBuilder(Entity.class)
71+
.addTranslator(MetadataTypes.BYTE, Entity::setFlags)
72+
.addTranslator(MetadataTypes.INT, Entity::setAir) // Air/bubbles
73+
.addTranslator(MetadataTypes.OPTIONAL_COMPONENT, Entity::setDisplayName)
74+
.addTranslator(MetadataTypes.BOOLEAN, Entity::setDisplayNameVisible)
75+
.addTranslator(MetadataTypes.BOOLEAN, Entity::setSilent)
76+
.addTranslator(MetadataTypes.BOOLEAN, Entity::setGravity)
77+
.addTranslator(MetadataTypes.POSE, (entity, entityMetadata) -> entity.setPose(entityMetadata.getValue()))
78+
.addTranslator(MetadataTypes.INT, Entity::setFreezing)
79+
.build();
80+
DISPLAY = EntityDefinitionBase.baseInherited(DisplayBaseEntity.class, ENTITY)
81+
.addTranslator(null) // Interpolation delay
82+
.addTranslator(null) // Transformation interpolation duration
83+
.addTranslator(null) // Position/Rotation interpolation duration
84+
.addTranslator(MetadataTypes.VECTOR3, DisplayBaseEntity::setTranslation) // Translation
85+
.addTranslator(null) // Scale
86+
.addTranslator(null) // Left rotation
87+
.addTranslator(null) // Right rotation
88+
.addTranslator(null) // Billboard render constraints
89+
.addTranslator(null) // Brightness override
90+
.addTranslator(null) // View range
91+
.addTranslator(null) // Shadow radius
92+
.addTranslator(null) // Shadow strength
93+
.addTranslator(null) // Width
94+
.addTranslator(null) // Height
95+
.addTranslator(null) // Glow color override
96+
.build();
97+
FIREBALL = EntityDefinitionBase.baseInherited(FireballEntity.class, ENTITY)
98+
.addTranslator(null) // Item
99+
.build();
100+
THROWABLE = EntityDefinitionBase.baseInherited(ThrowableItemEntity.class, ENTITY)
101+
.addTranslator(MetadataTypes.ITEM_STACK, ThrowableItemEntity::setItem)
102+
.build();
103+
HANGING = EntityDefinitionBase.baseInherited(HangingEntity.class, ENTITY)
104+
.addTranslator(MetadataTypes.DIRECTION, HangingEntity::setDirectionMetadata)
105+
.build();
106+
BOAT = EntityDefinitionBase.baseInherited(BoatEntity.class, ENTITY)
107+
.height(0.6f).width(1.6f)
108+
.offset(0.35f)
109+
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_TICKS, entityMetadata.getValue())) // Time since last hit
110+
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.HURT_DIRECTION, entityMetadata.getValue())) // Rocking direction
111+
.addTranslator(MetadataTypes.FLOAT, (boatEntity, entityMetadata) ->
112+
// 'Health' in Bedrock, damage taken in Java - it makes motion in Bedrock
113+
boatEntity.getDirtyMetadata().put(EntityDataTypes.STRUCTURAL_INTEGRITY, 40 - ((int) ((FloatEntityMetadata) entityMetadata).getPrimitiveValue())))
114+
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingLeft)
115+
.addTranslator(MetadataTypes.BOOLEAN, BoatEntity::setPaddlingRight)
116+
.addTranslator(MetadataTypes.INT, (boatEntity, entityMetadata) -> boatEntity.getDirtyMetadata().put(EntityDataTypes.BOAT_BUBBLE_TIME, entityMetadata.getValue())) // May not actually do anything
117+
.build();
118+
CHEST_BOAT = EntityDefinitionBase.baseInherited(ChestBoatEntity.class, BOAT)
119+
.build();
120+
LIVING_ENTITY = EntityDefinitionBase.baseInherited(LivingEntity.class, ENTITY)
121+
.addTranslator(MetadataTypes.BYTE, LivingEntity::setLivingEntityFlags)
122+
.addTranslator(MetadataTypes.FLOAT, LivingEntity::setHealth)
123+
.addTranslator(MetadataTypes.PARTICLES, LivingEntity::setParticles)
124+
.addTranslator(MetadataTypes.BOOLEAN,
125+
(livingEntity, entityMetadata) -> livingEntity.getDirtyMetadata().put(EntityDataTypes.EFFECT_AMBIENCE, (byte) (((BooleanEntityMetadata) entityMetadata).getPrimitiveValue() ? 1 : 0)))
126+
.addTranslator(null) // Arrow count
127+
.addTranslator(null) // Stinger count
128+
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, LivingEntity::setBedPosition)
129+
.build();
130+
AVATAR = EntityDefinitionBase.baseInherited(AvatarEntity.class, LIVING_ENTITY)
131+
.height(1.8f).width(0.6f)
132+
.offset(1.62f)
133+
.addTranslator(null) // Player main hand
134+
.addTranslator(MetadataTypes.BYTE, AvatarEntity::setSkinVisibility)
135+
.build();
136+
MOB = EntityDefinitionBase.baseInherited(MobEntity.class, LIVING_ENTITY)
137+
.addTranslator(MetadataTypes.BYTE, MobEntity::setMobFlags)
138+
.build();
139+
FISH = EntityDefinitionBase.baseInherited(AbstractFishEntity.class, MOB)
140+
.addTranslator(null) // From bucket
141+
.build();
142+
PIGLIN = EntityDefinitionBase.baseInherited(BasePiglinEntity.class, MOB)
143+
.addTranslator(MetadataTypes.BOOLEAN, BasePiglinEntity::setImmuneToZombification)
144+
.build();
145+
RAID_PARTICIPANT = EntityDefinitionBase.baseInherited(RaidParticipantEntity.class, MOB)
146+
.addTranslator(null) // Celebrating //TODO
147+
.build();
148+
SPELLCASTER = EntityDefinitionBase.baseInherited(SpellcasterIllagerEntity.class, RAID_PARTICIPANT)
149+
.addTranslator(MetadataTypes.BYTE, SpellcasterIllagerEntity::setSpellType)
150+
.build();
151+
AGEABLE = EntityDefinitionBase.baseInherited(AgeableEntity.class, MOB)
152+
.addTranslator(MetadataTypes.BOOLEAN, AgeableEntity::setBaby)
153+
.build();
154+
HORSE = EntityDefinitionBase.baseInherited(AbstractHorseEntity.class, AGEABLE)
155+
.addTranslator(MetadataTypes.BYTE, AbstractHorseEntity::setHorseFlags)
156+
.build();
157+
TAMABLE = EntityDefinitionBase.baseInherited(TameableEntity.class, AGEABLE)
158+
.addTranslator(MetadataTypes.BYTE, TameableEntity::setTameableFlags)
159+
.addTranslator(MetadataTypes.OPTIONAL_LIVING_ENTITY_REFERENCE, TameableEntity::setOwner)
160+
.build();
161+
}
162+
163+
private EntityDefinitionBases() {
164+
}
165+
}

0 commit comments

Comments
 (0)