diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index 0162d498e..04044fcb4 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -30,7 +30,11 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.ParticleType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket; +import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; +import org.cloudburstmc.protocol.bedrock.packet.SpawnParticleEffectPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.living.MobEntity; @@ -260,7 +264,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable { // so we need to manually spawn particles for (int i = 0; i < 8; i++) { SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket(); - spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); + spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session)); spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f)); spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire"); spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty()); diff --git a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java index dd0f4215e..7462844fc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java @@ -25,15 +25,17 @@ package org.geysermc.geyser.level; +import net.kyori.adventure.key.Key; import org.cloudburstmc.nbt.NbtMap; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; +import org.geysermc.geyser.util.DimensionUtils; /** * Represents the information we store from the current Java dimension * @param piglinSafe Whether piglins and hoglins are safe from conversion in this dimension. * This controls if they have the shaking effect applied in the dimension. */ -public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale) { +public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale, int bedrockId, boolean isNetherLike) { public static JavaDimension read(RegistryEntryContext entry) { NbtMap dimension = entry.data(); @@ -46,6 +48,22 @@ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double world // Load world coordinate scale for the world border double coordinateScale = dimension.getDouble("coordinate_scale"); - return new JavaDimension(minY, maxY, piglinSafe, coordinateScale); + boolean isNetherLike; + // Cache the Bedrock version of this dimension, and base it off the ID - THE ID CAN CHANGE!!! + // https://github.com/GeyserMC/Geyser/issues/4837 + int bedrockId; + Key id = entry.id(); + if ("minecraft".equals(id.namespace())) { + String identifier = id.asString(); + bedrockId = DimensionUtils.javaToBedrock(identifier); + isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(identifier); + } else { + // Effects should give is a clue on how this (custom) dimension is supposed to look like + String effects = dimension.getString("effects"); + bedrockId = DimensionUtils.javaToBedrock(effects); + isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects); + } + + return new JavaDimension(minY, maxY, piglinSafe, coordinateScale, bedrockId, isNetherLike); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 25dd21662..3d47956b9 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -312,8 +312,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * The dimension of the player. * As all entities are in the same world, this can be safely applied to all other entities. */ - @Setter - private int dimension = DimensionUtils.OVERWORLD; @MonotonicNonNull @Setter private JavaDimension dimensionType = null; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 6988d6cc8..1e885403b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.java; import net.kyori.adventure.key.Key; import org.geysermc.erosion.Constants; +import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerSpawnInfo; import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; @@ -65,12 +66,15 @@ public class JavaLoginTranslator extends PacketTranslator { SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); stringPacket.setIdentifier(particleMapping.identifier()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java index 1591b4952..52a08ab29 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java @@ -46,7 +46,7 @@ public class JavaMapItemDataTranslator extends PacketTranslator entityEffects = session.getEffectCache().getEntityEffects(); + for (Effect effect : entityEffects) { + MobEffectPacket mobEffectPacket = new MobEffectPacket(); + mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE); + mobEffectPacket.setRuntimeEntityId(player.getGeyserId()); + mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect)); + session.sendUpstreamPacket(mobEffectPacket); + } + // Effects are re-sent from server + entityEffects.clear(); + + finalizeDimensionSwitch(session, player); + + // If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension, + // we check if the player is entering the nether and apply the nether fog to fake the fact that the client + // thinks they are in the end dimension. + if (isCustomBedrockNetherId()) { + if (javaDimension.isNetherLike()) { + session.camera().sendFog(BEDROCK_FOG_HELL); + } else if (previousDimension.isNetherLike()) { + session.camera().removeFog(BEDROCK_FOG_HELL); + } + } + } + + /** + * Switch dimensions without clearing internal logic. + */ + public static void fastSwitchDimension(GeyserSession session, int bedrockDimension) { + changeDimension(session, bedrockDimension); + finalizeDimensionSwitch(session, session.getPlayerEntity()); + } + + private static void changeDimension(GeyserSession session, int bedrockDimension) { if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) { // The server-sided view distance wasn't a thing until Minecraft Java 1.14 // So ViaVersion compensates by sending a "view distance" of 64 @@ -77,7 +116,7 @@ public class DimensionUtils { // To solve this, we cap at 32 unless we know that the render distance actually exceeds 32 // Also, as of 1.19: PS4 crashes with a ChunkRadiusUpdatedPacket too large session.getGeyser().getLogger().debug("Applying dimension switching workaround for Bedrock render distance of " - + session.getServerRenderDistance()); + + session.getServerRenderDistance()); ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); chunkRadiusUpdatedPacket.setRadius(32); session.sendUpstreamPacket(chunkRadiusUpdatedPacket); @@ -92,24 +131,14 @@ public class DimensionUtils { changeDimensionPacket.setPosition(pos); session.sendUpstreamPacket(changeDimensionPacket); - session.setDimension(javaDimension); - setBedrockDimension(session, javaDimension); + setBedrockDimension(session, bedrockDimension); - player.setPosition(pos); + session.getPlayerEntity().setPosition(pos); session.setSpawned(false); session.setLastChunkPosition(null); + } - Set entityEffects = session.getEffectCache().getEntityEffects(); - for (Effect effect : entityEffects) { - MobEffectPacket mobEffectPacket = new MobEffectPacket(); - mobEffectPacket.setEvent(MobEffectPacket.Event.REMOVE); - mobEffectPacket.setRuntimeEntityId(player.getGeyserId()); - mobEffectPacket.setEffectId(EntityUtils.toBedrockEffectId(effect)); - session.sendUpstreamPacket(mobEffectPacket); - } - // Effects are re-sent from server - entityEffects.clear(); - + private static void finalizeDimensionSwitch(GeyserSession session, Entity player) { //let java server handle portal travel sound StopSoundPacket stopSoundPacket = new StopSoundPacket(); stopSoundPacket.setStoppingAllSound(true); @@ -130,23 +159,12 @@ public class DimensionUtils { // TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent. // The client wants sections sent to it before it can successfully respawn. ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true); - - // If the bedrock nether height workaround is enabled, meaning the client is told it's in the end dimension, - // we check if the player is entering the nether and apply the nether fog to fake the fact that the client - // thinks they are in the end dimension. - if (isCustomBedrockNetherId()) { - if (NETHER == javaDimension) { - session.camera().sendFog(BEDROCK_FOG_HELL); - } else if (NETHER == previousDimension) { - session.camera().removeFog(BEDROCK_FOG_HELL); - } - } } - public static void setBedrockDimension(GeyserSession session, int javaDimension) { - session.getChunkCache().setBedrockDimension(switch (javaDimension) { - case DimensionUtils.THE_END -> BedrockDimension.THE_END; - case DimensionUtils.NETHER -> DimensionUtils.isCustomBedrockNetherId() ? BedrockDimension.THE_END : BedrockDimension.THE_NETHER; + public static void setBedrockDimension(GeyserSession session, int bedrockDimension) { + session.getChunkCache().setBedrockDimension(switch (bedrockDimension) { + case BEDROCK_END_ID -> BedrockDimension.THE_END; + case BEDROCK_DEFAULT_NETHER_ID -> BedrockDimension.THE_NETHER; // JavaDimension *should* be set to BEDROCK_END_ID if the Nether workaround is enabled. default -> BedrockDimension.OVERWORLD; }); } @@ -155,26 +173,12 @@ public class DimensionUtils { if (dimension == BedrockDimension.THE_NETHER) { return BEDROCK_NETHER_ID; } else if (dimension == BedrockDimension.THE_END) { - return 2; + return BEDROCK_END_ID; } else { - return 0; + return BEDROCK_OVERWORLD_ID; } } - /** - * Map the Java edition dimension IDs to Bedrock edition - * - * @param javaDimension Dimension ID to convert - * @return Converted Bedrock edition dimension ID - */ - public static int javaToBedrock(int javaDimension) { - return switch (javaDimension) { - case NETHER -> BEDROCK_NETHER_ID; - case THE_END -> 2; - default -> 0; - }; - } - /** * Map the Java edition dimension IDs to Bedrock edition * @@ -183,12 +187,23 @@ public class DimensionUtils { */ public static int javaToBedrock(String javaDimension) { return switch (javaDimension) { - case "minecraft:the_nether" -> BEDROCK_NETHER_ID; + case NETHER_IDENTIFIER -> BEDROCK_NETHER_ID; case "minecraft:the_end" -> 2; default -> 0; }; } + /** + * Gets the Bedrock dimension ID, with a safety check if a packet is created before the player is logged/spawned in. + */ + public static int javaToBedrock(GeyserSession session) { + JavaDimension dimension = session.getDimensionType(); + if (dimension == null) { + return BEDROCK_OVERWORLD_ID; + } + return dimension.bedrockId(); + } + /** * The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension. * This workaround sets the Nether as the End dimension to ignore this limit. @@ -197,28 +212,28 @@ public class DimensionUtils { */ public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) { // Change dimension ID to the End to allow for building above Bedrock - BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1; + BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? BEDROCK_END_ID : BEDROCK_DEFAULT_NETHER_ID; } /** * Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional * dimension switch. * - * @param currentDimension the current dimension of the player - * @param newDimension the new dimension that the player will be transferred to - * @return the fake dimension to transfer to + * @param currentBedrockDimension the current dimension of the player + * @param newBedrockDimension the new dimension that the player will be transferred to + * @return the Bedrock fake dimension to transfer to */ - public static int getTemporaryDimension(int currentDimension, int newDimension) { + public static int getTemporaryDimension(int currentBedrockDimension, int newBedrockDimension) { if (isCustomBedrockNetherId()) { // Prevents rare instances of Bedrock locking up - return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER; + return newBedrockDimension == BEDROCK_END_ID ? BEDROCK_OVERWORLD_ID : BEDROCK_END_ID; } // Check current Bedrock dimension and not just the Java dimension. // Fixes rare instances like https://github.com/GeyserMC/Geyser/issues/3161 - return javaToBedrock(currentDimension) == 0 ? NETHER : OVERWORLD; + return currentBedrockDimension == BEDROCK_OVERWORLD_ID ? BEDROCK_DEFAULT_NETHER_ID : BEDROCK_OVERWORLD_ID; } public static boolean isCustomBedrockNetherId() { - return BEDROCK_NETHER_ID == 2; + return BEDROCK_NETHER_ID == BEDROCK_END_ID; } }