From 33ca586cb120bffb4ce9f22be6224a0ea4fff577 Mon Sep 17 00:00:00 2001 From: Oryxel Date: Thu, 17 Jul 2025 02:13:28 +0700 Subject: [PATCH 01/11] Initial work on entity pushing. --- .../geysermc/geyser/entity/type/Entity.java | 10 +++++ .../geyser/entity/type/LivingEntity.java | 28 ++++++++++++ .../animal/horse/AbstractHorseEntity.java | 5 +++ .../type/living/monster/WardenEntity.java | 5 +++ .../entity/type/player/PlayerEntity.java | 18 ++++++++ .../type/player/SessionPlayerEntity.java | 3 +- .../geyser/scoreboard/Scoreboard.java | 6 ++- .../org/geysermc/geyser/scoreboard/Team.java | 11 ++++- .../player/input/BedrockMovePlayer.java | 43 +++++++++++++++++++ .../JavaSetPlayerTeamTranslator.java | 3 +- .../org/geysermc/geyser/util/MathUtils.java | 17 ++++++++ 11 files changed, 144 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index b03dea3ed56..5c85e07a2bb 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -412,6 +412,16 @@ public void setFlags(ByteEntityMetadata entityMetadata) { setInvisible((xd & 0x20) == 0x20); } + /** + * Ensure that collision is possible if this entity is a player. Should be overridden for players as their team + * might change the status of this. + * @param session the Bedrock client session + * @return if this entity can collide with the session's player + */ + public boolean isPushable(GeyserSession session) { + return false; + } + /** * Set a boolean - whether the entity is invisible or visible * diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 92048eb250e..ac53c397a6c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -46,9 +46,14 @@ import org.geysermc.geyser.entity.vehicle.HappyGhastVehicleComponent; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.level.block.property.Properties; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.level.block.type.TrapDoorBlock; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.tags.BlockTag; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.EntityUtils; @@ -278,6 +283,29 @@ public void setParticles(ObjectEntityMetadata> entityMetadata) { } } + public boolean isOnClimbableBlock() { + Vector3i blockPos = this.position.toInt(); + BlockState state = session.getGeyser().getWorldManager().blockAt(session, blockPos); + if (session.getTagCache().is(BlockTag.CLIMBABLE, state.block())) { + return true; + } + + if (state.block() instanceof TrapDoorBlock) { + if (!state.getValue(Properties.OPEN)) { + return false; + } else { + BlockState belowState = session.getGeyser().getWorldManager().blockAt(session, blockPos.down()); + return belowState.is(Blocks.LADDER) && belowState.getValue(Properties.HORIZONTAL_FACING) == state.getValue(Properties.HORIZONTAL_FACING); + } + } + return false; + } + + @Override + public boolean isPushable(GeyserSession session) { + return this.getFlag(EntityFlag.HAS_GRAVITY) && this.isAlive() && !this.isOnClimbableBlock(); + } + protected boolean hasShield(boolean offhand) { ItemMapping shieldMapping = session.getItemMappings().getStoredItems().shield(); if (offhand) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java index 8b0e77c738d..0b6c73cc815 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java @@ -296,4 +296,9 @@ protected boolean canUseSlot(EquipmentSlot slot) { return isAlive() && !isBaby() && getFlag(EntityFlag.TAMED); } } + + @Override + public boolean isPushable(GeyserSession session) { + return this.getPassengers().isEmpty(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java index 2341b8c321c..cbc3c05bdf0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java @@ -71,6 +71,11 @@ public void setAngerLevel(IntEntityMetadata entityMetadata) { dirtyMetadata.put(EntityDataTypes.HEARTBEAT_INTERVAL_TICKS, heartBeatDelay); } + @Override + public boolean isPushable(GeyserSession session) { + return super.isPushable(session) && !getFlag(EntityFlag.DIGGING) && !getFlag(EntityFlag.EMERGING); + } + @Override public void tick() { if (++tickCount % heartBeatDelay == 0 && !silent) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 7dbbf0b5289..c2845b402b9 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -52,6 +52,7 @@ import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; @@ -295,6 +296,23 @@ public void setPosition(Vector3f position) { return bedPosition; } + @Override + public boolean isPushable(GeyserSession session) { + boolean canCollide = super.isPushable(session); + if (this instanceof SessionPlayerEntity) { // This is the session player, don't check for team collision rule. + return canCollide; + } + + Team team = session.getWorldCache().getScoreboard().getTeamFor(username); + if (team == null) return true; + return switch (team.collisionRule()) { + case NEVER -> false; + case PUSH_OWN_TEAM -> canCollide && team.hasEntity(session.getPlayerEntity().getUsername()); + case PUSH_OTHER_TEAMS -> canCollide && !team.hasEntity(session.getPlayerEntity().getUsername()); + default -> canCollide; + }; + } + public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) { // Extra hearts - is not metadata but an attribute on Bedrock UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index dcd3a148529..cf7058b5189 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -476,11 +476,12 @@ public float getJumpVelocity() { return velocity + 0.1F * session.getEffectCache().getJumpPower(); } + @Override public boolean isOnClimbableBlock() { if (session.getGameMode() == GameMode.SPECTATOR) { return false; } - Vector3i pos = getPosition().down(EntityDefinitions.PLAYER.offset()).toInt(); + Vector3i pos = this.position().toInt(); BlockState state = session.getGeyser().getWorldManager().blockAt(session, pos); if (session.getTagCache().is(BlockTag.CLIMBABLE, state.block())) { return true; diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index 10d8aa52536..ab3915af260 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -41,6 +41,7 @@ import org.geysermc.geyser.scoreboard.display.slot.PlayerlistDisplaySlot; import org.geysermc.geyser.scoreboard.display.slot.SidebarDisplaySlot; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; @@ -173,7 +174,8 @@ public void registerNewTeam( Component prefix, Component suffix, NameTagVisibility visibility, - TeamColor color + TeamColor color, + CollisionRule collisionRule ) { Team team = teams.get(teamName); if (team != null) { @@ -183,7 +185,7 @@ public void registerNewTeam( return; } - team = new Team(this, teamName, players, name, prefix, suffix, visibility, color); + team = new Team(this, teamName, players, name, prefix, suffix, visibility, color, collisionRule); teams.put(teamName, team); // Update command parameters - is safe to send even if the command enum doesn't exist on the client (as of 1.19.51) diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index 50752353996..356a75b1330 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; @@ -48,6 +49,7 @@ public final class Team { private final Set managedEntities; @NonNull private NameTagVisibility nameTagVisibility = NameTagVisibility.ALWAYS; private TeamColor color; + private CollisionRule collisionRule; private String name; private String prefix; @@ -62,7 +64,8 @@ public Team( Component prefix, Component suffix, NameTagVisibility visibility, - TeamColor color + TeamColor color, + CollisionRule collisionRule ) { this.scoreboard = scoreboard; this.id = id; @@ -70,6 +73,8 @@ public Team( this.managedEntities = new ObjectOpenHashSet<>(); this.lastUpdate = LAST_UPDATE_DEFAULT; + this.collisionRule = collisionRule; + // doesn't call entity update updateProperties(name, prefix, suffix, visibility, color); // calls entity update @@ -316,6 +321,10 @@ public TeamColor color() { return color; } + public CollisionRule collisionRule() { + return collisionRule; + } + public long lastUpdate() { return lastUpdate; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index ed0738bf1f9..e520f919954 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -25,15 +25,22 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player.input; +import org.cloudburstmc.math.GenericMath; import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionResult; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.util.MathUtils; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; @@ -219,6 +226,42 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { if (entity.getRightParrot() != null) { entity.getRightParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); } + + // If player is inside a vehicle or player doesn't have gravity or the player itself CAN'T be push then don't do any kind of pushing. + if (entity.getFlag(EntityFlag.HAS_GRAVITY) && entity.isPushable(session) && entity.getVehicle() == null) { + // TODO: Is looping through every entities a good idea? + for (Entity entity1 : session.getEntityCache().getEntities().values()) { + final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, entity1.getBoundingBoxWidth(), entity1.getBoundingBoxHeight(), entity1.getBoundingBoxWidth()); + entityBoundingBox.translate(entity1 instanceof PlayerEntity playerEntity ? playerEntity.position().toDouble() : entity1.getPosition().toDouble()); + + // If this entity can't collide with the player or isn't near the player or is the player itself, continue. + if (entity1 == entity || !entityBoundingBox.checkIntersection(session.getCollisionManager().getPlayerBoundingBox()) || !entity1.isPushable(session)) { + continue; + } + + float d = entity.getPosition().getX() - entity1.getPosition().getX(); + float e = entity.getPosition().getZ() - entity1.getPosition().getZ(); + float f = MathUtils.absMax(d, e); + + Vector3f vector3f = Vector3f.from(d, 0, e); + if (f >= 0.01f) { + f = (float) GenericMath.sqrt(f); + float g = Math.min(1 / f, 1); + + vector3f = vector3f.div(f); + vector3f = vector3f.mul(g); + vector3f = vector3f.mul(0.05F); + + // The push motion should be relative to the current motion, we don't want player to fly by sending y vel 0. + entity.setMotion(entity.getMotion().add(vector3f)); + + SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); + motionPacket.setRuntimeEntityId(entity.getGeyserId()); + motionPacket.setMotion(entity.getMotion()); + session.sendUpstreamPacket(motionPacket); + } + } + } } private static boolean isInvalidNumber(float val) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java index 3a1ee63739d..c5347715ae9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java @@ -63,7 +63,8 @@ public void translate(GeyserSession session, ClientboundSetPlayerTeamPacket pack packet.getPrefix(), packet.getSuffix(), packet.getNameTagVisibility(), - packet.getColor() + packet.getColor(), + packet.getCollisionRule() ); } else { Team team = scoreboard.getTeam(packet.getTeamName()); diff --git a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java index 08bed56f49f..6f7c175de7c 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java @@ -28,6 +28,23 @@ public class MathUtils { public static final double SQRT_OF_TWO = Math.sqrt(2); + /** + * Find the absolute value of 2 number and find what the larger number is. + * + * @param d The first value + * @param e The second value + * @return The absolute value of the number that is larger when it's absolute. + */ + public static float absMax(float d, float e) { + if (d < 0.0) { + d = -d; + } + if (e < 0.0) { + e = -e; + } + return Math.max(d, e); + } + /** * Wrap the given float degrees to be between -180.0 and 180.0. * From e9ebf4144e238d7a321938cd4f36b85004acb015 Mon Sep 17 00:00:00 2001 From: Oryxel Date: Thu, 17 Jul 2025 02:19:26 +0700 Subject: [PATCH 02/11] nothing to see here. --- .../bedrock/entity/player/input/BedrockMovePlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index e520f919954..07fe3abd83e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -227,8 +227,8 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { entity.getRightParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); } - // If player is inside a vehicle or player doesn't have gravity or the player itself CAN'T be push then don't do any kind of pushing. - if (entity.getFlag(EntityFlag.HAS_GRAVITY) && entity.isPushable(session) && entity.getVehicle() == null) { + // If player is inside a vehicle or player or the player itself CAN'T be push then don't do any kind of pushing. + if (entity.getVehicle() == null && entity.isPushable(session)) { // TODO: Is looping through every entities a good idea? for (Entity entity1 : session.getEntityCache().getEntities().values()) { final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, entity1.getBoundingBoxWidth(), entity1.getBoundingBoxHeight(), entity1.getBoundingBoxWidth()); From 634a98bba60aaaf8e08f6af47808f9f5b83c2517 Mon Sep 17 00:00:00 2001 From: oryxel Date: Thu, 17 Jul 2025 02:37:08 +0700 Subject: [PATCH 03/11] Change some comments. Co-authored-by: chris --- core/src/main/java/org/geysermc/geyser/entity/type/Entity.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 5c85e07a2bb..2d87e95be8c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -413,8 +413,7 @@ public void setFlags(ByteEntityMetadata entityMetadata) { } /** - * Ensure that collision is possible if this entity is a player. Should be overridden for players as their team - * might change the status of this. + * Tests whether an entity can be pushed by a player. For player entities, the team they're in determines if they can be pushed. * @param session the Bedrock client session * @return if this entity can collide with the session's player */ From 344b60b157cafe1a0a1206a2d7dbb19c6b497dd4 Mon Sep 17 00:00:00 2001 From: Oryxel Date: Thu, 17 Jul 2025 02:52:29 +0700 Subject: [PATCH 04/11] Requested changes. --- .../type/player/GeyserPlayerEntity.java | 8 ----- .../geyser/entity/type/BoatEntity.java | 5 +++ .../geysermc/geyser/entity/type/Entity.java | 9 ++++++ .../geyser/entity/type/ItemEntity.java | 6 ++++ .../geyser/entity/type/LivingEntity.java | 2 +- .../geyser/entity/type/MinecartEntity.java | 5 +++ .../geyser/entity/type/TNTEntity.java | 5 +++ .../geyser/entity/type/TextDisplayEntity.java | 5 +++ .../type/player/SessionPlayerEntity.java | 16 +--------- .../player/input/BedrockMovePlayer.java | 31 +++++++++++-------- 10 files changed, 55 insertions(+), 37 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java index d31def99670..da2e286090b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java @@ -25,15 +25,7 @@ package org.geysermc.geyser.api.entity.type.player; -import org.cloudburstmc.math.vector.Vector3f; import org.geysermc.geyser.api.entity.type.GeyserEntity; public interface GeyserPlayerEntity extends GeyserEntity { - - /** - * Gets the position of the player, as it is known to the Java server. - * - * @return the player's position - */ - Vector3f position(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index 0ba1c12771c..cf544d480c8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -136,6 +136,11 @@ public void updateRotation(float yaw, float pitch, boolean isOnGround) { moveRelative(0, 0, 0, yaw + 90, 0, 0, isOnGround); } + @Override + public Vector3f position() { + return this.position.down(definition.offset()); + } + public void setPaddlingLeft(BooleanEntityMetadata entityMetadata) { isPaddlingLeft = entityMetadata.getPrimitiveValue(); if (!isPaddlingLeft) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 2d87e95be8c..54eba7b0891 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -412,6 +412,15 @@ public void setFlags(ByteEntityMetadata entityMetadata) { setInvisible((xd & 0x20) == 0x20); } + /** + * Gets the position of the entity without it being offset. + * + * @return the position of the entity without the offset value + */ + public Vector3f position() { + return this.position; + } + /** * Tests whether an entity can be pushed by a player. For player entities, the team they're in determines if they can be pushed. * @param session the Bedrock client session diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index 49eb9ddc494..68666d723e1 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -124,6 +124,12 @@ protected void moveAbsoluteImmediate(Vector3f position, float yaw, float pitch, .thenApply(BlockStateValues::getWaterLevel); } + @Override + public Vector3f position() { + float offset = definition.offset(); + return this.position.down(waterLevel.join() == 0 ? -offset : offset); + } + @Override protected float getGravity() { if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index ac53c397a6c..7d40aed1348 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -284,7 +284,7 @@ public void setParticles(ObjectEntityMetadata> entityMetadata) { } public boolean isOnClimbableBlock() { - Vector3i blockPos = this.position.toInt(); + Vector3i blockPos = this.position().toInt(); BlockState state = session.getGeyser().getWorldManager().blockAt(session, blockPos); if (session.getTagCache().is(BlockTag.CLIMBABLE, state.block())) { return true; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java index 1f69d34e79e..acf4ea18c20 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java @@ -210,6 +210,11 @@ public Vector3f getBedrockRotation() { return Vector3f.from(0, getYaw(), 0); } + @Override + public Vector3f position() { + return this.position.down(definition.offset()); + } + @Override protected InteractiveTag testInteraction(Hand hand) { if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java index 47d97b8f7ed..ccb1dd07cf2 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TNTEntity.java @@ -52,6 +52,11 @@ public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYa super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported); } + @Override + public Vector3f position() { + return this.position.down(definition.offset()); + } + public void setFuseLength(IntEntityMetadata entityMetadata) { currentTick = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.IGNITED, true); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java index 80e5f3d18be..e36e06c663d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java @@ -59,6 +59,11 @@ public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYa super.moveAbsolute(position.add(Vector3f.from(0, definition.offset(), 0)), yaw, pitch, headYaw, isOnGround, teleported); } + @Override + public Vector3f position() { + return this.position.down(definition.offset()); + } + @Override protected void initializeMetadata() { super.initializeMetadata(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index cf7058b5189..8b2fa812d33 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -481,21 +481,7 @@ public boolean isOnClimbableBlock() { if (session.getGameMode() == GameMode.SPECTATOR) { return false; } - Vector3i pos = this.position().toInt(); - BlockState state = session.getGeyser().getWorldManager().blockAt(session, pos); - if (session.getTagCache().is(BlockTag.CLIMBABLE, state.block())) { - return true; - } - - if (state.block() instanceof TrapDoorBlock) { - if (!state.getValue(Properties.OPEN)) { - return false; - } else { - BlockState belowState = session.getGeyser().getWorldManager().blockAt(session, pos.down()); - return belowState.is(Blocks.LADDER) && belowState.getValue(Properties.HORIZONTAL_FACING) == state.getValue(Properties.HORIZONTAL_FACING); - } - } - return false; + return super.isOnClimbableBlock(); } public boolean canStartGliding() { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 07fe3abd83e..418599c76a5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -227,33 +227,38 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { entity.getRightParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); } - // If player is inside a vehicle or player or the player itself CAN'T be push then don't do any kind of pushing. + // If player is inside a vehicle or the player itself CAN'T be push then don't push them. if (entity.getVehicle() == null && entity.isPushable(session)) { // TODO: Is looping through every entities a good idea? - for (Entity entity1 : session.getEntityCache().getEntities().values()) { - final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, entity1.getBoundingBoxWidth(), entity1.getBoundingBoxHeight(), entity1.getBoundingBoxWidth()); - entityBoundingBox.translate(entity1 instanceof PlayerEntity playerEntity ? playerEntity.position().toDouble() : entity1.getPosition().toDouble()); + for (Entity other : session.getEntityCache().getEntities().values()) { + if (other == entity) { + return; + } + + final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, other.getBoundingBoxWidth(), other.getBoundingBoxHeight(), other.getBoundingBoxWidth()); + entityBoundingBox.translate(other.position().toDouble()); // If this entity can't collide with the player or isn't near the player or is the player itself, continue. - if (entity1 == entity || !entityBoundingBox.checkIntersection(session.getCollisionManager().getPlayerBoundingBox()) || !entity1.isPushable(session)) { + if (!entityBoundingBox.checkIntersection(session.getCollisionManager().getPlayerBoundingBox()) || !other.isPushable(session)) { continue; } - float d = entity.getPosition().getX() - entity1.getPosition().getX(); - float e = entity.getPosition().getZ() - entity1.getPosition().getZ(); - float f = MathUtils.absMax(d, e); + // According to Entity#push line 1563 (Mojmap). + float xDistance = entity.getPosition().getX() - other.getPosition().getX(); + float zDistance = entity.getPosition().getZ() - other.getPosition().getZ(); + float f = MathUtils.absMax(xDistance, zDistance); - Vector3f vector3f = Vector3f.from(d, 0, e); + Vector3f pushVelocity = Vector3f.from(xDistance, 0, zDistance); if (f >= 0.01f) { f = (float) GenericMath.sqrt(f); float g = Math.min(1 / f, 1); - vector3f = vector3f.div(f); - vector3f = vector3f.mul(g); - vector3f = vector3f.mul(0.05F); + pushVelocity = pushVelocity.div(f); + pushVelocity = pushVelocity.mul(g); + pushVelocity = pushVelocity.mul(0.05F); // The push motion should be relative to the current motion, we don't want player to fly by sending y vel 0. - entity.setMotion(entity.getMotion().add(vector3f)); + entity.setMotion(entity.getMotion().add(pushVelocity)); SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); motionPacket.setRuntimeEntityId(entity.getGeyserId()); From d45c90a03e79ac58062f77ebba32458e2357b285 Mon Sep 17 00:00:00 2001 From: oryxel Date: Thu, 17 Jul 2025 17:21:42 +0700 Subject: [PATCH 05/11] Changes some comments. Co-authored-by: chris --- .../protocol/bedrock/entity/player/input/BedrockMovePlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 418599c76a5..b3f16232cee 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -227,7 +227,7 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { entity.getRightParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false); } - // If player is inside a vehicle or the player itself CAN'T be push then don't push them. + // If the player is riding a vehicle or if the player itself cannot be pushed, then don't push. if (entity.getVehicle() == null && entity.isPushable(session)) { // TODO: Is looping through every entities a good idea? for (Entity other : session.getEntityCache().getEntities().values()) { From 02cb947ca8e8c8317fa8a3f550c48133645e8ebf Mon Sep 17 00:00:00 2001 From: Oryxel Date: Thu, 17 Jul 2025 17:44:33 +0700 Subject: [PATCH 06/11] Requested changes. --- .../type/player/GeyserPlayerEntity.java | 8 +++++ .../player/input/BedrockMovePlayer.java | 31 ++++++++++--------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java index da2e286090b..d31def99670 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java @@ -25,7 +25,15 @@ package org.geysermc.geyser.api.entity.type.player; +import org.cloudburstmc.math.vector.Vector3f; import org.geysermc.geyser.api.entity.type.GeyserEntity; public interface GeyserPlayerEntity extends GeyserEntity { + + /** + * Gets the position of the player, as it is known to the Java server. + * + * @return the player's position + */ + Vector3f position(); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index b3f16232cee..fe8a0b7c1d6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -229,10 +229,10 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { // If the player is riding a vehicle or if the player itself cannot be pushed, then don't push. if (entity.getVehicle() == null && entity.isPushable(session)) { - // TODO: Is looping through every entities a good idea? + boolean affectedByPushMotion = false; for (Entity other : session.getEntityCache().getEntities().values()) { if (other == entity) { - return; + continue; } final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, other.getBoundingBoxWidth(), other.getBoundingBoxHeight(), other.getBoundingBoxWidth()); @@ -243,29 +243,32 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { continue; } - // According to Entity#push line 1563 (Mojmap). + // 1.21.7 entity pushing logic. float xDistance = entity.getPosition().getX() - other.getPosition().getX(); float zDistance = entity.getPosition().getZ() - other.getPosition().getZ(); - float f = MathUtils.absMax(xDistance, zDistance); + float largestDistance = MathUtils.absMax(xDistance, zDistance); Vector3f pushVelocity = Vector3f.from(xDistance, 0, zDistance); - if (f >= 0.01f) { - f = (float) GenericMath.sqrt(f); - float g = Math.min(1 / f, 1); + if (largestDistance >= 0.01F) { + largestDistance = (float) GenericMath.sqrt(largestDistance); + pushVelocity = pushVelocity.div(largestDistance); + float d3 = Math.min(1 / largestDistance, 1); - pushVelocity = pushVelocity.div(f); - pushVelocity = pushVelocity.mul(g); + pushVelocity = pushVelocity.mul(d3); pushVelocity = pushVelocity.mul(0.05F); // The push motion should be relative to the current motion, we don't want player to fly by sending y vel 0. entity.setMotion(entity.getMotion().add(pushVelocity)); - - SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); - motionPacket.setRuntimeEntityId(entity.getGeyserId()); - motionPacket.setMotion(entity.getMotion()); - session.sendUpstreamPacket(motionPacket); + affectedByPushMotion = true; } } + + if (affectedByPushMotion) { + SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); + motionPacket.setRuntimeEntityId(entity.getGeyserId()); + motionPacket.setMotion(entity.getMotion()); + session.sendUpstreamPacket(motionPacket); + } } } From 50d6e026abb0bb49aa4870fdfc783bb03f57f876 Mon Sep 17 00:00:00 2001 From: Oryxel Date: Thu, 17 Jul 2025 18:23:10 +0700 Subject: [PATCH 07/11] Send tick id alongside with velocity. --- .../protocol/bedrock/entity/player/input/BedrockMovePlayer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index fe8a0b7c1d6..3e3f6f93002 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -267,6 +267,8 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); motionPacket.setRuntimeEntityId(entity.getGeyserId()); motionPacket.setMotion(entity.getMotion()); + // This is really important, or else player going to have weird motion without this tick id. + motionPacket.setTick(packet.getTick()); session.sendUpstreamPacket(motionPacket); } } From cb9446fd23115b0312985160c24dfcf407744f4a Mon Sep 17 00:00:00 2001 From: oryxel Date: Sat, 9 Aug 2025 08:27:02 +0700 Subject: [PATCH 08/11] Update core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java Co-authored-by: chris --- .../protocol/bedrock/entity/player/input/BedrockMovePlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 3e3f6f93002..ef40592024a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -231,7 +231,7 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { if (entity.getVehicle() == null && entity.isPushable(session)) { boolean affectedByPushMotion = false; for (Entity other : session.getEntityCache().getEntities().values()) { - if (other == entity) { + if (other == entity) { continue; } From ce7aab87777bb4d36adac8c8b8e6efcea99f6772 Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Sat, 9 Aug 2025 08:30:03 +0700 Subject: [PATCH 09/11] Requested changes. --- .../entity/player/input/BedrockMovePlayer.java | 6 ++---- .../org/geysermc/geyser/util/MathUtils.java | 17 ----------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 3e3f6f93002..144e9184f74 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -246,15 +246,13 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { // 1.21.7 entity pushing logic. float xDistance = entity.getPosition().getX() - other.getPosition().getX(); float zDistance = entity.getPosition().getZ() - other.getPosition().getZ(); - float largestDistance = MathUtils.absMax(xDistance, zDistance); + float largestDistance = Math.max(Math.abs(xDistance), Math.abs(zDistance)); Vector3f pushVelocity = Vector3f.from(xDistance, 0, zDistance); if (largestDistance >= 0.01F) { largestDistance = (float) GenericMath.sqrt(largestDistance); pushVelocity = pushVelocity.div(largestDistance); - float d3 = Math.min(1 / largestDistance, 1); - - pushVelocity = pushVelocity.mul(d3); + pushVelocity = pushVelocity.mul(Math.min(1 / largestDistance, 1)); pushVelocity = pushVelocity.mul(0.05F); // The push motion should be relative to the current motion, we don't want player to fly by sending y vel 0. diff --git a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java index 6f7c175de7c..08bed56f49f 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java @@ -28,23 +28,6 @@ public class MathUtils { public static final double SQRT_OF_TWO = Math.sqrt(2); - /** - * Find the absolute value of 2 number and find what the larger number is. - * - * @param d The first value - * @param e The second value - * @return The absolute value of the number that is larger when it's absolute. - */ - public static float absMax(float d, float e) { - if (d < 0.0) { - d = -d; - } - if (e < 0.0) { - e = -e; - } - return Math.max(d, e); - } - /** * Wrap the given float degrees to be between -180.0 and 180.0. * From 88c9066a277632b90a36ad5fe5d4b64f7807be54 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 18 Aug 2025 16:17:29 +0300 Subject: [PATCH 10/11] early return --- .../bedrock/entity/player/input/BedrockMovePlayer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 18766ace012..f46070b962f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -29,18 +29,15 @@ import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; -import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; -import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionResult; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.util.MathUtils; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket; @@ -235,11 +232,15 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { continue; } + if (!other.isPushable(session)) { + continue; + } + final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, other.getBoundingBoxWidth(), other.getBoundingBoxHeight(), other.getBoundingBoxWidth()); entityBoundingBox.translate(other.position().toDouble()); // If this entity can't collide with the player or isn't near the player or is the player itself, continue. - if (!entityBoundingBox.checkIntersection(session.getCollisionManager().getPlayerBoundingBox()) || !other.isPushable(session)) { + if (!entityBoundingBox.checkIntersection(session.getCollisionManager().getPlayerBoundingBox())) { continue; } From d6c8360a1455b1b4c77827ad5ecf93202bdb82ee Mon Sep 17 00:00:00 2001 From: oryxel1 Date: Mon, 18 Aug 2025 20:49:43 +0700 Subject: [PATCH 11/11] Don't let player be push by armor stand. --- .../geysermc/geyser/entity/type/living/ArmorStandEntity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index afbdcca31f7..835ddb0b7c5 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -109,6 +109,11 @@ public void despawnEntity() { super.despawnEntity(); } + @Override + public boolean isPushable(GeyserSession session) { + return false; + } + @Override public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { moveAbsolute(position.add(relX, relY, relZ), yaw, pitch, headYaw, onGround, false);