Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for extended world height #5002

Merged
merged 8 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/src/main/java/org/geysermc/geyser/GeyserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import org.geysermc.geyser.event.GeyserEventBus;
import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.impl.MinecraftVersionImpl;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.network.netty.GeyserServer;
Expand All @@ -95,7 +96,6 @@
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.AssetUtils;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.Metrics;
import org.geysermc.geyser.util.MinecraftAuthLogger;
import org.geysermc.geyser.util.NewsHandler;
Expand Down Expand Up @@ -425,7 +425,7 @@ private void startInstance() {
}

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

Integer bedrockThreadCount = Integer.getInteger("Geyser.BedrockNetworkThreads");
if (bedrockThreadCount == null) {
Expand Down
83 changes: 75 additions & 8 deletions core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,84 @@

package org.geysermc.geyser.level;

import lombok.ToString;

/**
* A data structure to represent what Bedrock believes are the height requirements for a specific dimension.
* As of 1.18.30, biome count is representative of the height of the world, and out-of-bounds chunks can crash
* the client.
*
* @param minY The minimum height Bedrock Edition will accept.
* @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
*/
public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) {
public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true);
public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false);
public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true);
@ToString
public class BedrockDimension {

public static final int OVERWORLD_ID = 0;
public static final int DEFAULT_NETHER_ID = 1;
public static final int END_ID = 2;

// Changes if the above-bedrock Nether building workaround is applied
public static int BEDROCK_NETHER_ID = DEFAULT_NETHER_ID;

public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true, OVERWORLD_ID);
public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false, -1) {
@Override
public int bedrockId() {
return BEDROCK_NETHER_ID;
}
};
public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true, END_ID);
public static final String NETHER_IDENTIFIER = "minecraft:the_nether";

private final int minY;
private final int height;
private final boolean doUpperHeightWarn;
private final int bedrockId;

/**
* @param minY The minimum height Bedrock Edition will accept.
* @param height The maximum chunk height Bedrock Edition will accept, from the lowest point to the highest.
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
* @param bedrockId the Bedrock dimension ID of this dimension.
*/
public BedrockDimension(int minY, int height, boolean doUpperHeightWarn, int bedrockId) {
this.minY = minY;
this.height = height;
this.doUpperHeightWarn = doUpperHeightWarn;
this.bedrockId = 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.
*
* @param isAboveNetherBedrockBuilding true if we should apply The End workaround
*/
public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) {
// Change dimension ID to the End to allow for building above Bedrock
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? END_ID : DEFAULT_NETHER_ID;
}

public static boolean isCustomBedrockNetherId() {
return BEDROCK_NETHER_ID == END_ID;
}

public int maxY() {
return minY + height;
}

public int minY() {
return minY;
}

public int height() {
return height;
}

public boolean doUpperHeightWarn() {
return doUpperHeightWarn;
}

public int bedrockId() {
return bedrockId;
}

}
11 changes: 9 additions & 2 deletions core/src/main/java/org/geysermc/geyser/level/JavaDimension.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,19 @@ public static JavaDimension read(RegistryEntryContext entry) {
if ("minecraft".equals(id.namespace())) {
String identifier = id.asString();
bedrockId = DimensionUtils.javaToBedrock(identifier);
isNetherLike = DimensionUtils.NETHER_IDENTIFIER.equals(identifier);
isNetherLike = BedrockDimension.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);
isNetherLike = BedrockDimension.NETHER_IDENTIFIER.equals(effects);
}

if (minY % 16 != 0) {
throw new RuntimeException("Minimum Y must be a multiple of 16!");
}
if (maxY % 16 != 0) {
throw new RuntimeException("Maximum Y must be a multiple of 16!");
}

return new JavaDimension(minY, maxY, piglinSafe, ultrawarm, coordinateScale, bedrockId, isNetherLike);
Expand Down
35 changes: 32 additions & 3 deletions core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.command.SoftEnumUpdateType;
import org.cloudburstmc.protocol.bedrock.data.definitions.DimensionDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.AvailableEntityIdentifiersPacket;
Expand All @@ -84,6 +85,7 @@
import org.cloudburstmc.protocol.bedrock.packet.ClientboundCloseFormPacket;
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.CreativeContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.DimensionDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.EmoteListPacket;
import org.cloudburstmc.protocol.bedrock.packet.GameRulesChangedPacket;
import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
Expand Down Expand Up @@ -175,7 +177,6 @@
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MinecraftAuthLogger;
Expand Down Expand Up @@ -388,6 +389,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private boolean sprinting;

/**
* The overworld dimension which Bedrock Edition uses.
*/
private BedrockDimension bedrockOverworldDimension = BedrockDimension.OVERWORLD;
/**
* The dimension of the player.
* As all entities are in the same world, this can be safely applied to all other entities.
Expand All @@ -401,7 +406,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
* right before the StartGamePacket is sent.
*/
@Setter
private BedrockDimension bedrockDimension = BedrockDimension.OVERWORLD;
private BedrockDimension bedrockDimension = this.bedrockOverworldDimension;

@Setter
private int breakingBlock;
Expand Down Expand Up @@ -711,6 +716,30 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio
* Send all necessary packets to load Bedrock into the server
*/
public void connect() {
int minY = this.dimensionType.minY();
int maxY = this.dimensionType.maxY();
for (JavaDimension javaDimension : this.getRegistryCache().dimensions().values()) {
if (javaDimension.bedrockId() == BedrockDimension.OVERWORLD_ID) {
minY = Math.min(minY, javaDimension.minY());
maxY = Math.max(maxY, javaDimension.maxY());
}
}
minY = Math.max(minY, -512);
maxY = Math.min(maxY, 512);

if (minY < BedrockDimension.OVERWORLD.minY() || maxY > BedrockDimension.OVERWORLD.maxY()) {
final boolean isInOverworld = this.bedrockDimension == this.bedrockOverworldDimension;
this.bedrockOverworldDimension = new BedrockDimension(minY, maxY - minY, true, BedrockDimension.OVERWORLD_ID);
if (isInOverworld) {
this.bedrockDimension = this.bedrockOverworldDimension;
}
geyser.getLogger().debug("Extending overworld dimension to " + minY + " - " + maxY);

DimensionDataPacket dimensionDataPacket = new DimensionDataPacket();
dimensionDataPacket.getDefinitions().add(new DimensionDefinition("minecraft:overworld", maxY, minY, 5 /* Void */));
upstream.sendPacket(dimensionDataPacket);
}

startGame();
sentSpawnPacket = true;
syncEntityProperties();
Expand Down Expand Up @@ -1594,7 +1623,7 @@ private void startGame() {
startGamePacket.setRotation(Vector2f.from(1, 1));

startGamePacket.setSeed(-1L);
startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(bedrockDimension));
startGamePacket.setDimensionId(bedrockDimension.bedrockId());
startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGameType(GameType.SURVIVAL);
startGamePacket.setDifficulty(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) {
// If the player is already initialized and a join game packet is sent, they
// are swapping servers
if (session.isSpawned()) {
int fakeDim = DimensionUtils.getTemporaryDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()), newDimension.bedrockId());
int fakeDim = DimensionUtils.getTemporaryDimension(session.getBedrockDimension().bedrockId(), newDimension.bedrockId());
if (fakeDim != newDimension.bedrockId()) {
// The player's current dimension and new dimension are the same
// We want a dimension switch to clear old chunks out, so switch to a dimension that isn't the one we're currently in.
Expand Down Expand Up @@ -121,9 +122,9 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) {
}
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8)));

if (DimensionUtils.javaToBedrock(session.getBedrockDimension()) != newDimension.bedrockId()) {
if (session.getBedrockDimension().bedrockId() != newDimension.bedrockId()) {
DimensionUtils.switchDimension(session, newDimension);
} else if (DimensionUtils.isCustomBedrockNetherId() && newDimension.isNetherLike()) {
} else if (BedrockDimension.isCustomBedrockNetherId() && newDimension.isNetherLike()) {
// If the player is spawning into the "fake" nether, send them some fog
session.camera().sendFog(DimensionUtils.BEDROCK_FOG_HELL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntImmutableList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NBTOutputStream;
Expand All @@ -56,7 +61,6 @@
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.BlockEntityUtils;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.mcprotocollib.protocol.data.game.chunk.BitStorage;
import org.geysermc.mcprotocollib.protocol.data.game.chunk.ChunkSection;
import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette;
Expand Down Expand Up @@ -509,7 +513,7 @@ public void translate(GeyserSession session, ClientboundLevelChunkWithLightPacke
levelChunkPacket.setChunkX(packet.getX());
levelChunkPacket.setChunkZ(packet.getZ());
levelChunkPacket.setData(Unpooled.wrappedBuffer(payload));
levelChunkPacket.setDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()));
levelChunkPacket.setDimension(session.getBedrockDimension().bedrockId());
session.sendUpstreamPacket(levelChunkPacket);

for (Map.Entry<Vector3i, ItemFrameEntity> entry : session.getItemFrameCache().entrySet()) {
Expand Down
9 changes: 1 addition & 8 deletions core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public static void sendEmptyChunk(GeyserSession session, int chunkX, int chunkZ,
byteBuf.readBytes(payload);

LevelChunkPacket data = new LevelChunkPacket();
data.setDimension(DimensionUtils.javaToBedrock(session.getBedrockDimension()));
data.setDimension(session.getBedrockDimension().bedrockId());
data.setChunkX(chunkX);
data.setChunkZ(chunkZ);
data.setSubChunksLength(0);
Expand Down Expand Up @@ -207,13 +207,6 @@ public static void loadDimension(GeyserSession session) {
int minY = dimension.minY();
int maxY = dimension.maxY();

if (minY % 16 != 0) {
throw new RuntimeException("Minimum Y must be a multiple of 16!");
}
if (maxY % 16 != 0) {
throw new RuntimeException("Maximum Y must be a multiple of 16!");
}

BedrockDimension bedrockDimension = session.getBedrockDimension();
// Yell in the console if the world height is too height in the current scenario
// The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled
Expand Down
Loading