diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e89260..f638b5555a6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -177,6 +177,10 @@ public void moveRelative(GeyserSession session, double relX, double relY, double moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); + + if (relX != 0 || relY != 0 || relZ != 0) { + checkPlayerCollision(session); + } } public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { @@ -184,6 +188,7 @@ public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, fl } public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + boolean positionChanged = !position.equals(this.position); setPosition(position); setRotation(rotation); setOnGround(isOnGround); @@ -196,10 +201,14 @@ public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rota moveEntityPacket.setTeleported(teleported); session.sendUpstreamPacket(moveEntityPacket); + + if (positionChanged) { + checkPlayerCollision(session); + } } /** - * Teleports an entity to a new location. Used in JavaEntityTeleportTranslator. + * Teleports an entity to a new location. Used in {@link org.geysermc.connector.network.translators.java.entity.JavaEntityTeleportTranslator}. * @param session GeyserSession. * @param position The new position of the entity. * @param yaw The new yaw of the entity. @@ -211,7 +220,7 @@ public void teleport(GeyserSession session, Vector3f position, float yaw, float } /** - * Updates an entity's head position. Used in JavaEntityHeadLookTranslator. + * Updates an entity's head position. Used in {@link org.geysermc.connector.network.translators.java.entity.JavaEntityHeadLookTranslator}. * @param session GeyserSession. * @param headYaw The new head rotation of the entity. */ @@ -220,7 +229,7 @@ public void updateHeadLookRotation(GeyserSession session, float headYaw) { } /** - * Updates an entity's position and rotation. Used in JavaEntityPositionRotationTranslator. + * Updates an entity's position and rotation. Used in {@link org.geysermc.connector.network.translators.java.entity.JavaEntityPositionRotationTranslator}. * @param session GeyserSession * @param moveX The new X offset of the current position. * @param moveY The new Y offset of the current position. @@ -234,7 +243,7 @@ public void updatePositionAndRotation(GeyserSession session, double moveX, doubl } /** - * Updates an entity's rotation. Used in JavaEntityRotationTranslator. + * Updates an entity's rotation. Used in {@link org.geysermc.connector.network.translators.java.entity.JavaEntityRotationTranslator}. * @param session GeyserSession. * @param yaw The new yaw of the entity. * @param pitch The new pitch of the entity. @@ -244,6 +253,58 @@ public void updateRotation(GeyserSession session, float yaw, float pitch, boolea updatePositionAndRotation(session, 0, 0, 0, yaw, pitch, isOnGround); } + /** + * Unlike fishing rod pulling behavior that is calculated serverside on Bedrock and clientside on Java, + * Bedrock player "pushing" is nonexistent on Bedrock. + *
+ * This code checks to see if a Geyser player should be moved, and if so applies motion to the player + * which sends movement packets back. + *
+ * This should be called any time an entity moves position. + * @param session the session of the Bedrock player + */ + protected void checkPlayerCollision(GeyserSession session) { + if (session.getRidingVehicleEntity() == this) return; + if (doesYCoordinateIntersect(session)) { + double distanceX = session.getPlayerEntity().getPosition().getX() - this.position.getX(); + double distanceZ = session.getPlayerEntity().getPosition().getZ() - this.position.getZ(); + double largestDistance = Math.max(Math.abs(distanceX), Math.abs(distanceZ)); + if (largestDistance <= 0.65 && canCollideWithPlayer(session)) { + double largestDistanceSquareRoot = Math.sqrt(largestDistance); + double velocityX = (distanceX / 20) / largestDistanceSquareRoot; + double velocityZ = (distanceZ / 20) / largestDistanceSquareRoot; + + SetEntityMotionPacket packet = new SetEntityMotionPacket(); + packet.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + packet.setMotion(Vector3f.from(velocityX, 0, velocityZ)); + session.sendUpstreamPacket(packet); + } + } + } + + /** + * 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 + */ + protected boolean canCollideWithPlayer(GeyserSession session) { + return true; + } + + /** + * @param session the Bedrock client session + * @return true if the Y level of this entity intersects with player entity + */ + protected boolean doesYCoordinateIntersect(GeyserSession session) { + float playerYMin = session.getPlayerEntity().getPosition().getY() - session.getPlayerEntity().getEntityType().getOffset(); + float playerYMax = playerYMin + session.getPlayerEntity().getEntityType().getHeight(); + float entityYMax = this.position.getY() + this.entityType.getHeight(); + float entityYMin = this.position.getY(); + if (entityYMin > playerYMax) return false; // Entity is higher than the player + return !(playerYMin > entityYMax); // Player is higher than the entity + } + public void updateBedrockAttributes(GeyserSession session) { if (!valid) return; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 390110d1849..ec6d31de939 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -130,6 +130,7 @@ public void sendPlayer(GeyserSession session) { @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + boolean positionChanged = !position.add(0, entityType.getOffset(), 0).equals(this.position); setPosition(position); setRotation(rotation); @@ -153,6 +154,10 @@ public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rota if (rightParrot != null) { rightParrot.moveAbsolute(session, position, rotation, true, teleported); } + + if (session.getPlayerEntity() != this && positionChanged) { + checkPlayerCollision(session); + } } @Override @@ -175,6 +180,10 @@ public void moveRelative(GeyserSession session, double relX, double relY, double if (rightParrot != null) { rightParrot.moveRelative(session, relX, relY, relZ, rotation, true); } + + if (session.getPlayerEntity() != this && (relX != 0 || relY != 0 || relZ != 0)) { + checkPlayerCollision(session); + } } @Override @@ -218,6 +227,32 @@ public void updateRotation(GeyserSession session, float yaw, float pitch, boolea } } + @Override + protected boolean canCollideWithPlayer(GeyserSession session) { + Team team = session.getWorldCache().getScoreboard().getTeamFor(username); + if (team == null) return true; + switch (team.getCollisionRule()) { + case NEVER: + return false; + case PUSH_OWN_TEAM: + return team.getEntities().contains(session.getPlayerEntity().getUsername()); + case PUSH_OTHER_TEAMS: + return !team.getEntities().contains(session.getPlayerEntity().getUsername()); + default: + return true; + } + } + + @Override + protected boolean doesYCoordinateIntersect(GeyserSession session) { + float playerYMin = session.getPlayerEntity().getPosition().getY() - session.getPlayerEntity().getEntityType().getOffset(); + float playerYMax = playerYMin + session.getPlayerEntity().getEntityType().getHeight(); + float thisYMin = this.position.getY() - this.entityType.getOffset(); + float thisYMax = thisYMin + this.entityType.getHeight(); + if (thisYMin > playerYMax) return false; // Entity is higher than the player + return !(playerYMin > thisYMax); // Player is higher than the entity + } + @Override public void setPosition(Vector3f position) { this.position = position.add(0, entityType.getOffset(), 0); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 7ada302c2d2..a9b79112545 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -26,19 +26,18 @@ package org.geysermc.connector.entity.living.merchant; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,12 +94,17 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s @Override public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + // No need to worry about extra processing to compensate for sleeping + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + return; + } int z = 0; int bedId = 0; float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; if (session.getConnector().getConfig().isCacheChunks()) { - Position bedLocation = new Position((int) position.getFloorX(), (int) position.getFloorY(), (int) position.getFloorZ()); + Position bedLocation = new Position(position.getFloorX(), position.getFloorY(), position.getFloorZ()); bedId = session.getConnector().getWorldManager().getBlockAt(session, bedLocation); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); @@ -110,42 +114,41 @@ public void moveRelative(GeyserSession session, double relX, double relY, double MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); - //Sets Villager position and rotation when sleeping - if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - } else { - //String Setup - Pattern r = Pattern.compile("facing=([a-z]+)"); - Matcher m = r.matcher(bedRotationZ); - if (m.find()) { - switch (m.group(0)){ - case "facing=south": - //bed is facing south - z = 180; - bedPositionSubtractorW = -.5f; - break; - case "facing=east": - //bed is facing east - z = 90; - bedPositionSubtractorW = -.5f; - break; - case "facing=west": - //bed is facing west - z = 270; - bedPositionSubtractorW = .5f; - break; - case "facing=north": - //rotation does not change because north is 0 - bedPositionSubtractorN = .5f; - break; - } + // Sets Villager position and rotation when sleeping + // String Setup + Pattern r = Pattern.compile("facing=([a-z]+)"); + Matcher m = r.matcher(bedRotationZ); + if (m.find()) { + switch (m.group(0)) { + case "facing=south": + //bed is facing south + z = 180; + bedPositionSubtractorW = -.5f; + break; + case "facing=east": + //bed is facing east + z = 90; + bedPositionSubtractorW = -.5f; + break; + case "facing=west": + //bed is facing west + z = 270; + bedPositionSubtractorW = .5f; + break; + case "facing=north": + //rotation does not change because north is 0 + bedPositionSubtractorN = .5f; + break; } - moveEntityPacket.setRotation(Vector3f.from(0, 0, z)); - moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN)); } + moveEntityPacket.setRotation(Vector3f.from(0, 0, z)); + moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN)); moveEntityPacket.setOnGround(isOnGround); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); + + if (relX != 0 || relY != 0 || relZ != 0) { + checkPlayerCollision(session); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index 998e5effe9c..5b0d3fcc7ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -60,6 +60,7 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setCollisionRule(packet.getCollisionRule()) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) @@ -75,6 +76,7 @@ public void translate(ServerTeamPacket packet, GeyserSession session) { } team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setCollisionRule(packet.getCollisionRule()) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index a073e2e99c7..e529877a562 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -25,6 +25,7 @@ package org.geysermc.connector.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.CollisionRule; import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -45,6 +46,7 @@ public class Team { private UpdateType updateType = UpdateType.ADD; private String name; + private CollisionRule collisionRule; private NameTagVisibility nameTagVisibility; private String prefix; private TeamColor color;