Skip to content

Commit 26c4c52

Browse files
authored
Add support for extended world height (#5002)
1 parent 172cf82 commit 26c4c52

File tree

8 files changed

+141
-73
lines changed

8 files changed

+141
-73
lines changed

core/src/main/java/org/geysermc/geyser/GeyserImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.geysermc.geyser.event.GeyserEventBus;
7777
import org.geysermc.geyser.extension.GeyserExtensionManager;
7878
import org.geysermc.geyser.impl.MinecraftVersionImpl;
79+
import org.geysermc.geyser.level.BedrockDimension;
7980
import org.geysermc.geyser.level.WorldManager;
8081
import org.geysermc.geyser.network.GameProtocol;
8182
import org.geysermc.geyser.network.netty.GeyserServer;
@@ -95,7 +96,6 @@
9596
import org.geysermc.geyser.translator.text.MessageTranslator;
9697
import org.geysermc.geyser.util.AssetUtils;
9798
import org.geysermc.geyser.util.CooldownUtils;
98-
import org.geysermc.geyser.util.DimensionUtils;
9999
import org.geysermc.geyser.util.Metrics;
100100
import org.geysermc.geyser.util.MinecraftAuthLogger;
101101
import org.geysermc.geyser.util.NewsHandler;
@@ -425,7 +425,7 @@ private void startInstance() {
425425
}
426426

427427
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
428-
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
428+
BedrockDimension.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
429429

430430
Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads");
431431
if (bedrockThreadCount == null) {

core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,84 @@
2525

2626
package org.geysermc.geyser.level;
2727

28+
import lombok.ToString;
29+
2830
/**
2931
* A data structure to represent what Bedrock believes are the height requirements for a specific dimension.
3032
* As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash
3133
* the client.
32-
*
33-
* @param minY The minimum height Bedrock Edition will accept.
34-
* @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
35-
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
3634
*/
37-
public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) {
38-
public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true);
39-
public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false);
40-
public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true);
35+
@ToString
36+
public class BedrockDimension {
37+
38+
public static final int OVERWORLD_ID = 0;
39+
public static final int DEFAULT_NETHER_ID = 1;
40+
public static final int END_ID = 2;
41+
42+
// Changes if the above-bedrock Nether building workaround is applied
43+
public static int BEDROCK_NETHER_ID = DEFAULT_NETHER_ID;
44+
45+
public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true, OVERWORLD_ID);
46+
public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false, -1) {
47+
@Override
48+
public int bedrockId() {
49+
return BEDROCK_NETHER_ID;
50+
}
51+
};
52+
public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true, END_ID);
53+
public static final String NETHER_IDENTIFIER = "minecraft:the_nether";
54+
55+
private final int minY;
56+
private final int height;
57+
private final boolean doUpperHeightWarn;
58+
private final int bedrockId;
59+
60+
/**
61+
* @param minY The minimum height Bedrock Edition will accept.
62+
* @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
63+
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
64+
* @param bedrockId the Bedrock dimension ID of this dimension.
65+
*/
66+
public BedrockDimension(int minY, int height, boolean doUpperHeightWarn, int bedrockId) {
67+
this.minY = minY;
68+
this.height = height;
69+
this.doUpperHeightWarn = doUpperHeightWarn;
70+
this.bedrockId = bedrockId;
71+
}
72+
73+
/**
74+
* The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension.
75+
* This workaround sets the Nether as the End dimension to ignore this limit.
76+
*
77+
* @param isAboveNetherBedrockBuilding true if we should apply The End workaround
78+
*/
79+
public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) {
80+
// Change dimension ID to the End to allow for building above Bedrock
81+
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? END_ID : DEFAULT_NETHER_ID;
82+
}
83+
84+
public static boolean isCustomBedrockNetherId() {
85+
return BEDROCK_NETHER_ID == END_ID;
86+
}
87+
88+
public int maxY() {
89+
return minY + height;
90+
}
91+
92+
public int minY() {
93+
return minY;
94+
}
95+
96+
public int height() {
97+
return height;
98+
}
99+
100+
public boolean doUpperHeightWarn() {
101+
return doUpperHeightWarn;
102+
}
103+
104+
public int bedrockId() {
105+
return bedrockId;
106+
}
107+
41108
}

core/src/main/java/org/geysermc/geyser/level/JavaDimension.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,19 @@ public static JavaDimension read(RegistryEntryContext entry) {
6363
if ("minecraft".equals(id.namespace())) {
6464
String identifier = id.asString();
6565
bedrockId = DimensionUtils.javaToBedrock(identifier);
66-
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(identifier);
66+
isNetherLike = BedrockDimension.NETHER_IDENTIFIER.equals(identifier);
6767
} else {
6868
// Effects should give is a clue on how this (custom) dimension is supposed to look like
6969
String effects = dimension.getString("effects");
7070
bedrockId = DimensionUtils.javaToBedrock(effects);
71-
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(effects);
71+
isNetherLike = BedrockDimension.NETHER_IDENTIFIER.equals(effects);
72+
}
73+
74+
if (minY % 16 != 0) {
75+
throw new RuntimeException("Minimum Y must be a multiple of 16!");
76+
}
77+
if (maxY % 16 != 0) {
78+
throw new RuntimeException("Maximum Y must be a multiple of 16!");
7279
}
7380

7481
return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale, bedrockId, isNetherLike);

core/src/main/java/org/geysermc/geyser/session/GeyserSession.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
7575
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
7676
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
77+
import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition;
7778
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
7879
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
7980
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
@@ -84,6 +85,7 @@
8485
import org.cloudburstmc.protocol.bedrock.packet.ClientboundCloseFormPacket;
8586
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
8687
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
88+
import org.cloudburstmc.protocol.bedrock.packet.DimensionDataPacket;
8789
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
8890
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
8991
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
@@ -175,7 +177,6 @@
175177
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
176178
import org.geysermc.geyser.translator.text.MessageTranslator;
177179
import org.geysermc.geyser.util.ChunkUtils;
178-
import org.geysermc.geyser.util.DimensionUtils;
179180
import org.geysermc.geyser.util.EntityUtils;
180181
import org.geysermc.geyser.util.LoginEncryptionUtils;
181182
import org.geysermc.geyser.util.MinecraftAuthLogger;
@@ -388,6 +389,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
388389
@Setter
389390
private boolean sprinting;
390391

392+
/**
393+
* The overworld dimension which Bedrock Edition uses.
394+
*/
395+
private BedrockDimension bedrockOverworldDimension = BedrockDimension.OVERWORLD;
391396
/**
392397
* The dimension of the player.
393398
* As all entities are in the same world, this can be safely applied to all other entities.
@@ -401,7 +406,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
401406
* right before the StartGamePacket is sent.
402407
*/
403408
@Setter
404-
private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD;
409+
private BedrockDimension bedrockDimension = this.bedrockOverworldDimension;
405410

406411
@Setter
407412
private int breakingBlock;
@@ -711,6 +716,30 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio
711716
* Send all necessary packets to load Bedrock into the server
712717
*/
713718
public void connect() {
719+
int minY = this.dimensionType.minY();
720+
int maxY = this.dimensionType.maxY();
721+
for (JavaDimension javaDimension : this.getRegistryCache().dimensions().values()) {
722+
if (javaDimension.bedrockId() == BedrockDimension.OVERWORLD_ID) {
723+
minY = Math.min(minY, javaDimension.minY());
724+
maxY = Math.max(maxY, javaDimension.maxY());
725+
}
726+
}
727+
minY = Math.max(minY, -512);
728+
maxY = Math.min(maxY, 512);
729+
730+
if (minY < BedrockDimension.OVERWORLD.minY() || maxY > BedrockDimension.OVERWORLD.maxY()) {
731+
final boolean isInOverworld = this.bedrockDimension == this.bedrockOverworldDimension;
732+
this.bedrockOverworldDimension = new BedrockDimension(minY, maxY - minY, true, BedrockDimension.OVERWORLD_ID);
733+
if (isInOverworld) {
734+
this.bedrockDimension = this.bedrockOverworldDimension;
735+
}
736+
geyser.getLogger().debug("Extending overworld dimension to " + minY + " - " + maxY);
737+
738+
DimensionDataPacket dimensionDataPacket = new DimensionDataPacket();
739+
dimensionDataPacket.getDefinitions().add(new DimensionDefinition("minecraft:overworld", maxY, minY, 5 /* Void */));
740+
upstream.sendPacket(dimensionDataPacket);
741+
}
742+
714743
startGame();
715744
sentSpawnPacket = true;
716745
syncEntityProperties();
@@ -1594,7 +1623,7 @@ private void startGame() {
15941623
startGamePacket.setRotation(Vector2f.from(1, 1));
15951624

15961625
startGamePacket.setSeed(-1L);
1597-
startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(bedrockDimension));
1626+
startGamePacket.setDimensionId(bedrockDimension.bedrockId());
15981627
startGamePacket.setGeneratorId(1);
15991628
startGamePacket.setLevelGameType(GameType.SURVIVAL);
16001629
startGamePacket.setDifficulty(1);

core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
3434
import org.geysermc.geyser.api.network.AuthType;
3535
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
36+
import org.geysermc.geyser.level.BedrockDimension;
3637
import org.geysermc.geyser.level.JavaDimension;
3738
import org.geysermc.geyser.session.GeyserSession;
3839
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@@ -62,7 +63,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) {
6263
// If the player is already initialized and a join game packet is sent, they
6364
// are swapping servers
6465
if (session.isSpawned()) {
65-
int fakeDim = DimensionUtils.getTemporaryDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()), newDimension.bedrockId());
66+
int fakeDim = DimensionUtils.getTemporaryDimension(session.getBedrockDimension().bedrockId(), newDimension.bedrockId());
6667
if (fakeDim != newDimension.bedrockId()) {
6768
// The player's current dimension and new dimension are the same
6869
// We want a dimension switch to clear old chunks out, so switch to a dimension that isn't the one we're currently in.
@@ -121,9 +122,9 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) {
121122
}
122123
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8)));
123124

124-
if (DimensionUtils.javaToBedrock(session.getBedrockDimension()) != newDimension.bedrockId()) {
125+
if (session.getBedrockDimension().bedrockId() != newDimension.bedrockId()) {
125126
DimensionUtils.switchDimension(session, newDimension);
126-
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.isNetherLike()) {
127+
} else if (BedrockDimension.isCustomBedrockNetherId() && newDimension.isNetherLike()) {
127128
// If the player is spawning into the "fake" nether, send them some fog
128129
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
129130
}

core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929
import io.netty.buffer.ByteBufAllocator;
3030
import io.netty.buffer.ByteBufOutputStream;
3131
import io.netty.buffer.Unpooled;
32-
import it.unimi.dsi.fastutil.ints.*;
32+
import it.unimi.dsi.fastutil.ints.IntArrayList;
33+
import it.unimi.dsi.fastutil.ints.IntImmutableList;
34+
import it.unimi.dsi.fastutil.ints.IntList;
35+
import it.unimi.dsi.fastutil.ints.IntLists;
36+
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
37+
import it.unimi.dsi.fastutil.ints.IntSet;
3338
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
3439
import org.cloudburstmc.math.vector.Vector3i;
3540
import org.cloudburstmc.nbt.NBTOutputStream;
@@ -56,7 +61,6 @@
5661
import org.geysermc.geyser.translator.protocol.Translator;
5762
import org.geysermc.geyser.util.BlockEntityUtils;
5863
import org.geysermc.geyser.util.ChunkUtils;
59-
import org.geysermc.geyser.util.DimensionUtils;
6064
import org.geysermc.mcprotocollib.protocol.data.game.chunk.BitStorage;
6165
import org.geysermc.mcprotocollib.protocol.data.game.chunk.ChunkSection;
6266
import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette;
@@ -509,7 +513,7 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke
509513
levelChunkPacket.setChunkX(packet.getX());
510514
levelChunkPacket.setChunkZ(packet.getZ());
511515
levelChunkPacket.setData(Unpooled.wrappedBuffer(payload));
512-
levelChunkPacket.setDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()));
516+
levelChunkPacket.setDimension(session.getBedrockDimension().bedrockId());
513517
session.sendUpstreamPacket(levelChunkPacket);
514518

515519
for (Map.Entry<Vector3i, ItemFrameEntity> entry : session.getItemFrameCache().entrySet()) {

core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ,
167167
byteBuf.readBytes(payload);
168168

169169
LevelChunkPacket data = new LevelChunkPacket();
170-
data.setDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()));
170+
data.setDimension(session.getBedrockDimension().bedrockId());
171171
data.setChunkX(chunkX);
172172
data.setChunkZ(chunkZ);
173173
data.setSubChunksLength(0);
@@ -207,13 +207,6 @@ public static void loadDimension(GeyserSession session) {
207207
int minY = dimension.minY();
208208
int maxY = dimension.maxY();
209209

210-
if (minY % 16 != 0) {
211-
throw new RuntimeException("Minimum Y must be a multiple of 16!");
212-
}
213-
if (maxY % 16 != 0) {
214-
throw new RuntimeException("Maximum Y must be a multiple of 16!");
215-
}
216-
217210
BedrockDimension bedrockDimension = session.getBedrockDimension();
218211
// Yell in the console if the world height is too height in the current scenario
219212
// The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled

0 commit comments

Comments
 (0)