From 7f6cee8ccca9311280d0cfe56ab9c3613d09b731 Mon Sep 17 00:00:00 2001 From: daoge <3523206925@qq.com> Date: Sat, 19 Oct 2024 16:43:58 +0800 Subject: [PATCH] feat: height map (#458) --- .../api/client/storage/PlayerData.java | 54 ++++------- .../entity/component/EntityBaseComponent.java | 24 +++++ .../allaymc/api/server/ServerSettings.java | 10 +- .../org/allaymc/api/utils/AllayNbtUtils.java | 39 +++++++- .../java/org/allaymc/api/world/Dimension.java | 59 +++++++++++- .../java/org/allaymc/api/world/World.java | 2 +- .../java/org/allaymc/api/world/WorldData.java | 3 - .../allaymc/api/world/chunk/UnsafeChunk.java | 10 -- .../api/world/heightmap/HeightMap.java | 40 +++++++- .../command/defaults/GameTestCommand.java | 14 +-- .../server/command/defaults/WorldCommand.java | 2 +- .../player/EntityPlayerBaseComponentImpl.java | 48 ++++++++-- .../EntityPlayerNetworkComponentImpl.java | 69 +++++--------- .../org/allaymc/server/world/AllayWorld.java | 94 ++++++++++++++++++- .../allaymc/server/world/AllayWorldPool.java | 7 +- .../server/world/chunk/AllayChunk.java | 18 +--- .../server/world/chunk/AllayUnsafeChunk.java | 32 +++++-- .../server/world/chunk/ChunkSection.java | 10 +- .../server/world/generator/FlatNoiser.java | 1 - .../world/storage/LevelDBChunkSerializer.java | 6 +- 20 files changed, 376 insertions(+), 166 deletions(-) diff --git a/api/src/main/java/org/allaymc/api/client/storage/PlayerData.java b/api/src/main/java/org/allaymc/api/client/storage/PlayerData.java index c8c085258..5545b111e 100644 --- a/api/src/main/java/org/allaymc/api/client/storage/PlayerData.java +++ b/api/src/main/java/org/allaymc/api/client/storage/PlayerData.java @@ -6,7 +6,6 @@ import org.allaymc.api.server.Server; import org.cloudburstmc.nbt.NbtMap; import org.joml.Vector3i; -import org.joml.Vector3ic; import static org.allaymc.api.utils.AllayNbtUtils.writeVector3f; @@ -17,16 +16,15 @@ @Setter @Builder public class PlayerData { - // EntityPlayer's NBT which can be generated through method EntityPlayer#saveNBT() - protected NbtMap playerNBT; + // EntityPlayer's nbt which can be generated through method EntityPlayer#saveNBT() + protected NbtMap nbt; + // The following fields are not included in the return object of method EntityPlayer#saveNBT() - protected String currentWorldName; - protected int currentDimensionId; - // Can be null - protected Vector3ic spawnPoint; - // Can be null - protected String spawnPointWorldName; - protected int spawnPointDimensionId; + // We should store the world and dimension information of the player + // because the player data is not stored in chunk like other entities. + // Without these information, we can't know which world and dimension the player is in. + protected String world; + protected int dimension; public static PlayerData createEmpty() { var server = Server.getInstance(); @@ -36,43 +34,25 @@ public static PlayerData createEmpty() { var worldName = globalSpawnPoint.dimension().getWorld().getWorldData().getName(); var dimId = globalSpawnPoint.dimension().getDimensionInfo().dimensionId(); return builder() - .playerNBT(builder.build()) - .currentWorldName(worldName) - .currentDimensionId(dimId) - .spawnPoint(globalSpawnPoint) - .spawnPointWorldName(worldName) - .spawnPointDimensionId(dimId) + .nbt(builder.build()) + .world(worldName) + .dimension(dimId) .build(); } public static PlayerData fromNBT(NbtMap nbt) { var builder = builder(); - builder.playerNBT(nbt.getCompound("PlayerNBT")) - .currentWorldName(nbt.getString("CurrentWorldName")) - .currentDimensionId(nbt.getInt("CurrentDimensionId")); - if (nbt.containsKey("SpawnPointX")) { - int spx = nbt.getInt("SpawnPointX"); - int spy = nbt.getInt("SpawnPointY"); - int spz = nbt.getInt("SpawnPointZ"); - builder.spawnPoint(new Vector3i(spx, spy, spz)) - .spawnPointWorldName(nbt.getString("SpawnPointWorldName")) - .spawnPointDimensionId(nbt.getInt("SpawnPointDimensionId")); - } + builder.nbt(nbt.getCompound("NBT")) + .world(nbt.getString("World")) + .dimension(nbt.getInt("Dimension")); return builder.build(); } public NbtMap toNBT() { var builder = NbtMap.builder() - .putCompound("PlayerNBT", playerNBT) - .putString("CurrentWorldName", currentWorldName) - .putInt("CurrentDimensionId", currentDimensionId); - if (spawnPoint != null) { - builder.putInt("SpawnPointX", spawnPoint.x()) - .putInt("SpawnPointY", spawnPoint.y()) - .putInt("SpawnPointZ", spawnPoint.z()) - .putString("SpawnPointWorldName", spawnPointWorldName) - .putInt("SpawnPointDimensionId", spawnPointDimensionId); - } + .putCompound("NBT", nbt) + .putString("World", world) + .putInt("Dimension", dimension); return builder.build(); } } diff --git a/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java b/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java index c8e668c10..51849cf32 100644 --- a/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java +++ b/api/src/main/java/org/allaymc/api/entity/component/EntityBaseComponent.java @@ -14,6 +14,7 @@ import org.allaymc.api.entity.type.EntityType; import org.allaymc.api.item.ItemStack; import org.allaymc.api.math.location.Location3fc; +import org.allaymc.api.math.position.Position3ic; import org.allaymc.api.utils.MathUtils; import org.allaymc.api.world.Dimension; import org.allaymc.api.world.World; @@ -851,4 +852,27 @@ default BlockState getBlockStateStandingOn() { if (currentBlockState != air) return currentBlockState; else return getDimension().getBlockState(loc.x(), loc.y() - 1, loc.z()); } + + default boolean canStandSafely(Position3ic pos) { + return canStandSafely(pos.x(), pos.y(), pos.z(), getDimension()); + } + + /** + * Check if the specified position is a safe standing position. + * + * @param x the x coordinate. + * @param y the y coordinate. + * @param z the z coordinate. + * + * @return {@code true} if the specified position is a safe standing position, otherwise {@code false}. + */ + default boolean canStandSafely(int x, int y, int z, Dimension dimension) { + var blockUnder = dimension.getBlockState(x, y - 1, z); + var blockTypeUnder = blockUnder.getBlockType(); + if (!blockTypeUnder.getMaterial().isSolid()) { + return false; + } + return dimension.getBlockState(x, y, z).getBlockType() == BlockTypes.AIR && + dimension.getBlockState(x, y + 1, z).getBlockType() == BlockTypes.AIR; + } } diff --git a/api/src/main/java/org/allaymc/api/server/ServerSettings.java b/api/src/main/java/org/allaymc/api/server/ServerSettings.java index 6a2db004e..54f34d623 100644 --- a/api/src/main/java/org/allaymc/api/server/ServerSettings.java +++ b/api/src/main/java/org/allaymc/api/server/ServerSettings.java @@ -141,13 +141,19 @@ public static class WorldConfig extends OkaeriConfig { @CustomKey("chunk-sending-strategy") private ChunkSendingStrategy chunkSendingStrategy = ChunkSendingStrategy.ASYNC; - @CustomKey("do-first-spawn-chunk-threshold") - private int doFirstSpawnChunkThreshold = 56; + @CustomKey("fully-join-chunk-threshold") + private int fullyJoinChunkThreshold = 56; @Comment("Determines how long a chunk without chunk loaders will remain loaded (gt)") @CustomKey("remove-unneeded-chunk-cycle") private int removeUnneededChunkCycle = 1200; + @Comment("If set to true, the server will load chunks around the spawn point") + @Comment("Which will reduce the time on joining server") + @Comment("However, this will increase the server's memory usage") + @CustomKey("load-spawn-point-chunks") + private boolean loadSpawnPointChunks = true; + public enum ChunkSendingStrategy { ASYNC, SYNC diff --git a/api/src/main/java/org/allaymc/api/utils/AllayNbtUtils.java b/api/src/main/java/org/allaymc/api/utils/AllayNbtUtils.java index 290947044..0abb1e81d 100644 --- a/api/src/main/java/org/allaymc/api/utils/AllayNbtUtils.java +++ b/api/src/main/java/org/allaymc/api/utils/AllayNbtUtils.java @@ -5,10 +5,7 @@ import org.cloudburstmc.nbt.NBTOutputStream; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; -import org.joml.Vector2f; -import org.joml.Vector2fc; -import org.joml.Vector3f; -import org.joml.Vector3fc; +import org.joml.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -109,6 +106,40 @@ public static void writeVector3f(NbtMapBuilder nbt, String rootName, String f1, nbt.putCompound(rootName, pos); } + /** + * Read a vector3 from NBT. + * + * @param nbt the NBT map. + * @param rootName the root name. + * @param f1 the x field. + * @param f2 the y field. + * @param f3 the z field. + * @return the vector3. + */ + public static Vector3i readVector3i(NbtMap nbt, String rootName, String f1, String f2, String f3) { + var pos = nbt.getCompound(rootName); + return new Vector3i(pos.getInt(f1), pos.getInt(f2), pos.getInt(f3)); + } + + /** + * Write a vector3 to NBT. + * + * @param nbt the NBT builder. + * @param rootName the root name. + * @param f1 the x field. + * @param f2 the y field. + * @param f3 the z field. + * @param vector3i the vector3. + */ + public static void writeVector3i(NbtMapBuilder nbt, String rootName, String f1, String f2, String f3, Vector3ic vector3i) { + var pos = NbtMap.builder() + .putInt(f1, vector3i.x()) + .putInt(f2, vector3i.y()) + .putInt(f3, vector3i.z()) + .build(); + nbt.putCompound(rootName, pos); + } + /** * Read a vector2 from NBT. * diff --git a/api/src/main/java/org/allaymc/api/world/Dimension.java b/api/src/main/java/org/allaymc/api/world/Dimension.java index 6abc5a2f1..c155c6262 100644 --- a/api/src/main/java/org/allaymc/api/world/Dimension.java +++ b/api/src/main/java/org/allaymc/api/world/Dimension.java @@ -1,6 +1,5 @@ package org.allaymc.api.world; -import com.google.common.base.Function; import com.google.common.base.Preconditions; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.allaymc.api.block.data.BlockFace; @@ -16,6 +15,7 @@ import org.allaymc.api.entity.type.EntityTypes; import org.allaymc.api.item.ItemStack; import org.allaymc.api.math.position.Position3i; +import org.allaymc.api.math.position.Position3ic; import org.allaymc.api.utils.MathUtils; import org.allaymc.api.utils.Utils; import org.allaymc.api.world.generator.WorldGenerator; @@ -30,6 +30,7 @@ import org.cloudburstmc.protocol.bedrock.data.ParticleType; import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.packet.*; +import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.UnmodifiableView; import org.joml.Vector3fc; @@ -38,6 +39,7 @@ import java.util.*; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Predicate; import static org.allaymc.api.block.type.BlockTypes.AIR; @@ -910,4 +912,59 @@ default void breakBlock(Vector3ic pos, ItemStack usedItem, EntityPlayer player) * @return Whether the block is successfully broken. */ boolean breakBlock(int x, int y, int z, ItemStack usedItem, EntityPlayer player); + + /** + * Get the height of the highest non-air block at the specified x and z coordinates. + *

+ * Please note that this method will load the chunk if it's not loaded. + * + * @param x the x coordinate. + * @param z the z coordinate. + * + * @return the height of the highest non-air block at the specified x and z coordinates. + */ + default int getHeight(int x, int z) { + var chunk = getChunkService().getChunkByDimensionPos(x, z); + if (chunk == null) chunk = getChunkService().getOrLoadChunkSync(x >> 4, z >> 4); + return chunk.getHeight(x & 15, z & 15); + } + + /** + * Get the highest block at the specified x and z coordinates. + * + * @param x the x coordinate. + * @param z the z coordinate. + * + * @return the highest blockstate at the specified x and z coordinates. + */ + default BlockState getHighestBlockState(int x, int z) { + return getBlockState(x, getHeight(x, z), z); + } + + default Vector3ic findSuitableGroundPosAround(Predicate predicate, int x, int z, @Range(from = 0, to = Integer.MAX_VALUE) int range) { + return findSuitableGroundPosAround(predicate, x, z, range, 10); + } + + /** + * Find a safe standing position around the specified x and z coordinates. + * + * @param x the x coordinate. + * @param z the z coordinate. + * @param range the range to search. + * + * @return a safe standing position around the specified x and z coordinates, or {@code null} if not found. + */ + default Vector3ic findSuitableGroundPosAround(Predicate predicate, int x, int z, @Range(from = 0, to = Integer.MAX_VALUE) int range, @Range(from = 0, to = Integer.MAX_VALUE) int attemptCount) { + var rand = ThreadLocalRandom.current(); + while (attemptCount > 0) { + attemptCount--; + var px = x + rand.nextInt(-range, range + 1); + var pz = z + rand.nextInt(-range, range + 1); + var py = getHeight(px, pz) + 1; + if (predicate.test(new Position3i(px, py, pz, this))) { + return new org.joml.Vector3i(px, py, pz); + } + } + return null; + } } diff --git a/api/src/main/java/org/allaymc/api/world/World.java b/api/src/main/java/org/allaymc/api/world/World.java index a0d52e452..1708b3f37 100644 --- a/api/src/main/java/org/allaymc/api/world/World.java +++ b/api/src/main/java/org/allaymc/api/world/World.java @@ -20,7 +20,7 @@ public interface World { * * @return the thread which the world is running on. */ - Thread getThread(); + Thread getWorldThread(); /** * Get the tick of the world. diff --git a/api/src/main/java/org/allaymc/api/world/WorldData.java b/api/src/main/java/org/allaymc/api/world/WorldData.java index d56497939..10bce9519 100644 --- a/api/src/main/java/org/allaymc/api/world/WorldData.java +++ b/api/src/main/java/org/allaymc/api/world/WorldData.java @@ -286,9 +286,6 @@ public V getGameRule(GameRule gameRule) { return gameRules.get(gameRule); } - /** - * The overworld default spawn point - */ public Vector3ic getSpawnPoint() { return spawnPoint; } diff --git a/api/src/main/java/org/allaymc/api/world/chunk/UnsafeChunk.java b/api/src/main/java/org/allaymc/api/world/chunk/UnsafeChunk.java index 85b89bf49..85b22a9fd 100644 --- a/api/src/main/java/org/allaymc/api/world/chunk/UnsafeChunk.java +++ b/api/src/main/java/org/allaymc/api/world/chunk/UnsafeChunk.java @@ -178,16 +178,6 @@ default BlockState getBlockState(@Range(from = 0, to = 15) int x, int y, @Range( */ void setHeight(@Range(from = 0, to = 15) int x, @Range(from = 0, to = 15) int z, int height); - /** - * Get height array. - *

- * Different from getHeight(), all values in the short[] array returned by this method are - * greater than or equal to 0 (can be understood as getHeight() - minHeight()) - * - * @return the height array - */ - short[] getHeightArray(); - /** * Get height of the specified position. * diff --git a/api/src/main/java/org/allaymc/api/world/heightmap/HeightMap.java b/api/src/main/java/org/allaymc/api/world/heightmap/HeightMap.java index 2930348bf..a4cbc1fcc 100644 --- a/api/src/main/java/org/allaymc/api/world/heightmap/HeightMap.java +++ b/api/src/main/java/org/allaymc/api/world/heightmap/HeightMap.java @@ -3,6 +3,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.util.Arrays; + /** * HeightMap stores the height of each pos in a chunk. * @@ -14,7 +16,19 @@ public final class HeightMap { private final short[] heights; public HeightMap() { - this(new short[256]); + this.heights = new short[256]; + Arrays.fill(this.heights, (short) -1); + } + + /** + * Compute the index of the specified position. + * + * @param x the x coordinate of the pos. + * @param z the z coordinate of the pos. + * @return the index of the pos. + */ + public static int computeIndex(int x, int z) { + return (x << 4) | z; } /** @@ -25,7 +39,17 @@ public HeightMap() { * @return the height of the pos. */ public short get(int x, int z) { - return heights[(x << 4) | z]; + return heights[computeIndex(x, z)]; + } + + /** + * Get the height of the specified position. + * + * @param index the index of the pos. + * @return the height of the pos. + */ + public short get(int index) { + return heights[index]; } /** @@ -36,7 +60,17 @@ public short get(int x, int z) { * @param height the height of the pos. */ public void set(int x, int z, short height) { - heights[(x << 4) | z] = height; + heights[computeIndex(x, z)] = height; + } + + /** + * Set the height of the specified position. + * + * @param index the index of the pos. + * @param height the height of the pos. + */ + public void set(int index, short height) { + heights[index] = height; } /** diff --git a/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java b/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java index fbab197b6..38ab361c9 100644 --- a/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java +++ b/server/src/main/java/org/allaymc/server/command/defaults/GameTestCommand.java @@ -180,13 +180,6 @@ public void prepareCommandTree(CommandTree tree) { return context.success(); }, SenderType.PLAYER) .root() - .key("food") - .exec((context, player) -> { - player.setFoodLevel(Math.min(20, player.getFoodLevel() + 5)); // bread 5 food - player.setFoodSaturationLevel(player.getFoodSaturationLevel() + 6); // bread 6 saturation - return context.success(); - }, SenderType.PLAYER) - .root() .key("setperm") .str("perm") .bool("value") @@ -278,6 +271,13 @@ public void prepareCommandTree(CommandTree tree) { player.notifyItemInHandChange(); player.sendText("Lore is set"); return context.success(); + }, SenderType.PLAYER) + .root() + .key("getheight") + .exec((context, player) -> { + var floorLoc = player.getLocation().floor(new Vector3f()); + player.sendText("Height is " + player.getDimension().getHeight((int) floorLoc.x, (int) floorLoc.z)); + return context.success(); }, SenderType.PLAYER); } } diff --git a/server/src/main/java/org/allaymc/server/command/defaults/WorldCommand.java b/server/src/main/java/org/allaymc/server/command/defaults/WorldCommand.java index 4d824303f..5e31b9b0a 100644 --- a/server/src/main/java/org/allaymc/server/command/defaults/WorldCommand.java +++ b/server/src/main/java/org/allaymc/server/command/defaults/WorldCommand.java @@ -43,7 +43,7 @@ public void prepareCommandTree(CommandTree tree) { .root() .key("tp") .str("world") - .enums("dimension", "overworld", "nether", "the_end") + .enums("dimension", "overworld", new String[]{"overworld", "nether", "the_end"}) .optional() .exec((context, entity) -> { String worldName = context.getResult(1); diff --git a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java index 097a293a2..e9a1e5e16 100644 --- a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java +++ b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerBaseComponentImpl.java @@ -31,6 +31,7 @@ import org.allaymc.api.i18n.TrContainer; import org.allaymc.api.math.location.Location3f; import org.allaymc.api.math.location.Location3fc; +import org.allaymc.api.math.location.Location3i; import org.allaymc.api.math.location.Location3ic; import org.allaymc.api.perm.tree.PermTree; import org.allaymc.api.scoreboard.Scoreboard; @@ -39,6 +40,7 @@ import org.allaymc.api.scoreboard.data.SortOrder; import org.allaymc.api.scoreboard.scorer.PlayerScorer; import org.allaymc.api.server.Server; +import org.allaymc.api.utils.AllayNbtUtils; import org.allaymc.api.utils.MathUtils; import org.allaymc.api.utils.TextFormat; import org.allaymc.api.utils.Utils; @@ -89,7 +91,7 @@ public class EntityPlayerBaseComponentImpl extends EntityBaseComponentImpl imple @Dependency protected EntityPlayerNetworkComponent networkComponent; @Getter - protected GameType gameType = GameType.CREATIVE; + protected GameType gameType = Server.SETTINGS.genericSettings().defaultGameType(); @Getter protected Skin skin; @Getter @@ -448,9 +450,19 @@ public NbtMap saveNBT() { NbtType.COMPOUND, containerHolderComponent.getContainer(FullContainerType.ARMOR).saveNBT()) .putInt("EnchantmentSeed", enchantmentSeed) + .putInt("GameType", gameType.ordinal()) + .putCompound("SpawnPoint", saveSpawnPoint()) .build(); } + protected NbtMap saveSpawnPoint() { + var builder = NbtMap.builder() + .putString("World", spawnPoint.dimension().getWorld().getWorldData().getName()) + .putInt("Dimension", spawnPoint.dimension().getDimensionInfo().dimensionId()); + AllayNbtUtils.writeVector3i(builder, "Pos", "x", "y", "z", spawnPoint); + return builder.build(); + } + @Override public void loadNBT(NbtMap nbt) { super.loadNBT(nbt); @@ -465,6 +477,31 @@ public void loadNBT(NbtMap nbt) { containerHolderComponent.getContainer(FullContainerType.ARMOR).loadNBT(armorNbt) ); nbt.listenForInt("EnchantmentSeed", this::setEnchantmentSeed); + nbt.listenForInt("GameType", id -> setGameType(GameType.from(id))); + if (nbt.containsKey("SpawnPoint")) { + loadSpawnPoint(nbt.getCompound("SpawnPoint")); + } else { + spawnPoint = Server.getInstance().getWorldPool().getGlobalSpawnPoint(); + } + } + + protected void loadSpawnPoint(NbtMap nbt) { + var world = Server.getInstance().getWorldPool().getWorld(nbt.getString("World")); + if (world == null) { + spawnPoint = Server.getInstance().getWorldPool().getGlobalSpawnPoint(); + return; + } + var dimension = world.getDimension(nbt.getInt("Dimension")); + if (dimension == null) { + spawnPoint = Server.getInstance().getWorldPool().getGlobalSpawnPoint(); + return; + } + var pos = AllayNbtUtils.readVector3i(nbt, "Pos", "x", "y", "z"); + spawnPoint = new Location3i( + pos, + 0, 0, 0, + dimension + ); } @Override @@ -515,12 +552,9 @@ public void sendPopup(String message) { @Override public PlayerData savePlayerData() { return PlayerData.builder() - .playerNBT(saveNBT()) - .currentWorldName(getWorld().getWorldData().getName()) - .currentDimensionId(getDimension().getDimensionInfo().dimensionId()) - .spawnPoint(new Vector3i(spawnPoint.x(), spawnPoint.y(), spawnPoint.z())) - .spawnPointWorldName(spawnPoint.dimension().getWorld().getWorldData().getName()) - .spawnPointDimensionId(spawnPoint.dimension().getDimensionInfo().dimensionId()) + .nbt(saveNBT()) + .world(getWorld().getWorldData().getName()) + .dimension(getDimension().getDimensionInfo().dimensionId()) .build(); } diff --git a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerNetworkComponentImpl.java b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerNetworkComponentImpl.java index 52f171042..146bbd7e2 100644 --- a/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerNetworkComponentImpl.java +++ b/server/src/main/java/org/allaymc/server/entity/component/player/EntityPlayerNetworkComponentImpl.java @@ -4,7 +4,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.allaymc.api.client.data.LoginData; -import org.allaymc.api.client.storage.PlayerData; import org.allaymc.api.component.interfaces.ComponentManager; import org.allaymc.api.container.FullContainerType; import org.allaymc.api.container.UnopenedContainerId; @@ -20,8 +19,6 @@ import org.allaymc.api.item.ItemStack; import org.allaymc.api.item.recipe.Recipe; import org.allaymc.api.math.location.Location3f; -import org.allaymc.api.math.location.Location3i; -import org.allaymc.api.math.location.Location3ic; import org.allaymc.api.pack.Pack; import org.allaymc.api.registry.Registries; import org.allaymc.api.server.Server; @@ -44,10 +41,7 @@ import org.cloudburstmc.netty.channel.raknet.RakServerChannel; import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; import org.cloudburstmc.protocol.bedrock.BedrockServerSession; -import org.cloudburstmc.protocol.bedrock.data.AuthoritativeMovementMode; -import org.cloudburstmc.protocol.bedrock.data.ChatRestrictionLevel; -import org.cloudburstmc.protocol.bedrock.data.GamePublishSetting; -import org.cloudburstmc.protocol.bedrock.data.SpawnBiomeType; +import org.cloudburstmc.protocol.bedrock.data.*; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; @@ -86,7 +80,7 @@ public class EntityPlayerNetworkComponentImpl implements EntityPlayerNetworkComp protected boolean networkEncryptionEnabled = false; @Getter protected boolean initialized = false; - protected AtomicInteger doFirstSpawnChunkThreshold = new AtomicInteger(Server.SETTINGS.worldSettings().doFirstSpawnChunkThreshold()); + protected AtomicInteger fullyJoinChunkThreshold = new AtomicInteger(Server.SETTINGS.worldSettings().fullyJoinChunkThreshold()); @Manager protected ComponentManager manager; @ComponentedObject @@ -175,8 +169,8 @@ public void onDisconnect(String reason) { } public void onChunkInRangeSent() { - if (doFirstSpawnChunkThreshold.get() > 0 && doFirstSpawnChunkThreshold.decrementAndGet() == 0) { - doFirstSpawn(); + if (fullyJoinChunkThreshold.get() > 0 && fullyJoinChunkThreshold.decrementAndGet() == 0) { + onFullyJoin(); } } @@ -219,10 +213,10 @@ public boolean isDisconnected() { return disconnected.get(); } - protected void doFirstSpawn() { + protected void onFullyJoin() { var world = thisPlayer.getWorld(); // Load EntityPlayer's NBT - thisPlayer.loadNBT(server.getPlayerStorage().readPlayerData(thisPlayer).getPlayerNBT()); + thisPlayer.loadNBT(server.getPlayerStorage().readPlayerData(thisPlayer).getNbt()); var setEntityDataPacket = new SetEntityDataPacket(); setEntityDataPacket.setRuntimeEntityId(thisPlayer.getRuntimeId()); @@ -263,29 +257,30 @@ private void sendInventories() { } public void initializePlayer() { - // initializePlayer() method will read all the data in PlayerData except playerNBT - // To be more exactly, we will validate and set player's current pos and spawn point in this method - // And playerNBT will be used in EntityPlayer::loadNBT() in doFirstSpawnPlayer() method + // initializePlayer() method will read all the data in PlayerData except nbt + // To be more exactly, we will validate and set player's current pos in this method + // And nbt will be used in EntityPlayer::loadNBT() in doFirstSpawn() method var playerData = server.getPlayerStorage().readPlayerData(thisPlayer); // Validate and set player pos Dimension dimension; Vector3fc currentPos; - var logOffWorld = server.getWorldPool().getWorld(playerData.getCurrentWorldName()); - if (logOffWorld == null) { - // The world where player logged off doesn't exist + var logOffWorld = server.getWorldPool().getWorld(playerData.getWorld()); + if (logOffWorld == null || logOffWorld.getDimension(playerData.getDimension()) == null) { + // The world or dimension where player logged off doesn't exist + // Fallback to global spawn point dimension = server.getWorldPool().getGlobalSpawnPoint().dimension(); currentPos = new org.joml.Vector3f(server.getWorldPool().getGlobalSpawnPoint()); // The old pos stored in playerNBT is invalid, we should replace it with the new one! - var builder = playerData.getPlayerNBT().toBuilder(); + var builder = playerData.getNbt().toBuilder(); writeVector3f(builder, "Pos", "x", "y", "z", currentPos); - playerData.setPlayerNBT(builder.build()); + playerData.setNbt(builder.build()); + // Save new player data back to storage + server.getPlayerStorage().savePlayerData(thisPlayer.getUUID(), playerData); } else { - dimension = logOffWorld.getDimension(playerData.getCurrentDimensionId()); + dimension = logOffWorld.getDimension(playerData.getDimension()); // Read current pos from playerNBT - currentPos = readVector3f(playerData.getPlayerNBT(), "Pos", "x", "y", "z"); + currentPos = readVector3f(playerData.getNbt(), "Pos", "x", "y", "z"); } - // Validate and set spawn point - validateAndSetSpawnPoint(playerData); // Load the current point chunk firstly so that we can add player entity into the chunk dimension.getChunkService().getOrLoadChunkSync( (int) currentPos.x() >> 4, @@ -299,19 +294,18 @@ public void initializePlayer() { startGamePacket.getGamerules().addAll(spawnWorld.getWorldData().getGameRules().toNetworkGameRuleData()); startGamePacket.setUniqueEntityId(thisPlayer.getRuntimeId()); startGamePacket.setRuntimeEntityId(thisPlayer.getRuntimeId()); - startGamePacket.setPlayerGameType(thisPlayer.getGameType()); + startGamePacket.setPlayerGameType(GameType.from(playerData.getNbt().getInt("GameType", Server.SETTINGS.genericSettings().defaultGameType().ordinal()))); var loc = thisPlayer.getLocation(); var worldSpawn = spawnWorld.getWorldData().getSpawnPoint(); startGamePacket.setDefaultSpawn(Vector3i.from(worldSpawn.x(), worldSpawn.y(), worldSpawn.z())); startGamePacket.setPlayerPosition(Vector3f.from(loc.x(), loc.y(), loc.z())); startGamePacket.setRotation(Vector2f.from(loc.pitch(), loc.yaw())); - // We don't send world send to client for security reason + // We don't send world seed to client for security reason startGamePacket.setSeed(0L); startGamePacket.setDimensionId(dimension.getDimensionInfo().dimensionId()); startGamePacket.setGeneratorId(dimension.getWorldGenerator().getType().getId()); startGamePacket.setLevelGameType(spawnWorld.getWorldData().getGameType()); startGamePacket.setDifficulty(spawnWorld.getWorldData().getDifficulty().ordinal()); - // TODO: add it to server-settings.yml startGamePacket.setTrustingPlayers(true); startGamePacket.setLevelName(Server.SETTINGS.genericSettings().motd()); startGamePacket.setLevelId(""); @@ -323,7 +317,6 @@ public void initializePlayer() { startGamePacket.setItemDefinitions(DeferredData.getItemDefinitions()); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.SERVER); startGamePacket.setServerAuthoritativeBlockBreaking(true); - // TODO: add it to server-settings.yml startGamePacket.setCommandsEnabled(true); startGamePacket.setMultiplayerGame(true); // TODO: add it to server-settings.yml @@ -362,31 +355,11 @@ public void initializePlayer() { ); sendPacket(DeferredData.getAvailableEntityIdentifiersPacket()); - sendPacket(DeferredData.getBiomeDefinitionListPacket()); - sendPacket(DeferredData.getCreativeContentPacket()); - sendPacket(DeferredData.getCraftingDataPacket()); } - protected void validateAndSetSpawnPoint(PlayerData playerData) { - Location3ic spawnPoint; - var spawnWorld = server.getWorldPool().getWorld(playerData.getSpawnPointWorldName()); - if (spawnWorld == null) { - // The world where the spawn point is located does not exist - // Using global spawn point instead - spawnPoint = server.getWorldPool().getGlobalSpawnPoint(); - playerData.setSpawnPoint(spawnPoint); - playerData.setSpawnPointWorldName(spawnPoint.dimension().getWorld().getWorldData().getName()); - playerData.setSpawnPointDimensionId(spawnPoint.dimension().getDimensionInfo().dimensionId()); - } else { - var vec = playerData.getSpawnPoint(); - spawnPoint = new Location3i(vec.x(), vec.y(), vec.z(), spawnWorld.getDimension(playerData.getSpawnPointDimensionId())); - } - thisPlayer.setSpawnPoint(spawnPoint); - } - public void completeLogin() { if (server.getOnlinePlayerCount() >= Server.SETTINGS.genericSettings().maxClientCount()) { disconnect(TrKeys.M_DISCONNECTIONSCREEN_SERVERFULL_TITLE); diff --git a/server/src/main/java/org/allaymc/server/world/AllayWorld.java b/server/src/main/java/org/allaymc/server/world/AllayWorld.java index 575823859..2d26ca2df 100644 --- a/server/src/main/java/org/allaymc/server/world/AllayWorld.java +++ b/server/src/main/java/org/allaymc/server/world/AllayWorld.java @@ -5,8 +5,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.allaymc.api.block.type.BlockTypes; +import org.allaymc.api.entity.Entity; import org.allaymc.api.entity.interfaces.EntityPlayer; import org.allaymc.api.eventbus.event.world.WorldDataSaveEvent; +import org.allaymc.api.math.location.Location3f; +import org.allaymc.api.math.location.Location3fc; +import org.allaymc.api.math.position.Position3i; +import org.allaymc.api.math.position.Position3ic; import org.allaymc.api.scheduler.Scheduler; import org.allaymc.api.server.Server; import org.allaymc.api.utils.GameLoop; @@ -14,12 +20,16 @@ import org.allaymc.api.world.Weather; import org.allaymc.api.world.World; import org.allaymc.api.world.WorldData; +import org.allaymc.api.world.chunk.Chunk; +import org.allaymc.api.world.chunk.ChunkLoader; import org.allaymc.api.world.gamerule.GameRule; import org.allaymc.api.world.storage.WorldStorage; import org.allaymc.server.entity.component.player.EntityPlayerNetworkComponentImpl; import org.allaymc.server.scheduler.AllayScheduler; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket; import org.jetbrains.annotations.UnmodifiableView; +import org.joml.Vector3i; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,7 +58,7 @@ public class AllayWorld implements World { protected final Scheduler scheduler = new AllayScheduler(Server.getInstance().getVirtualThreadPool()); protected final GameLoop gameLoop; @Getter - protected final Thread thread; + protected final Thread worldThread; protected final Thread networkThread; protected long nextTimeSendTick; protected int rainTimer = Weather.CLEAR.generateRandomTimeLength(); @@ -56,6 +66,7 @@ public class AllayWorld implements World { protected boolean isRaining = false; protected boolean isThundering = false; protected Set effectiveWeathers = new HashSet<>(); + protected boolean isFirstTick = false; public AllayWorld(WorldStorage worldStorage) { this.worldStorage = worldStorage; @@ -86,7 +97,7 @@ public AllayWorld(WorldStorage worldStorage) { } } }).build(); - this.thread = Thread.ofPlatform() + this.worldThread = Thread.ofPlatform() .name("World Thread - " + this.getWorldData().getName()) .unstarted(gameLoop::startLoop); if (ENABLE_INDEPENDENT_NETWORK_THREAD) { @@ -129,6 +140,7 @@ protected void handleSyncPackets() { } protected void tick(long currentTick) { + checkFirstTick(); if (!ENABLE_INDEPENDENT_NETWORK_THREAD) { handleSyncPackets(); } @@ -140,6 +152,37 @@ protected void tick(long currentTick) { worldStorage.tick(currentTick); } + protected void checkFirstTick() { + if (isFirstTick) { + return; + } + isFirstTick = true; + + // Check spawn point + if (!isSafeStandingPos(new Position3i(worldData.getSpawnPoint(), getOverWorld()))) { + var newSpawnPoint = getOverWorld().findSuitableGroundPosAround(this::isSafeStandingPos, 0, 0, 32); + if (newSpawnPoint == null) { + log.warn("Cannot find a safe spawn point in the overworld dimension of world {}", worldData.getName()); + newSpawnPoint = new Vector3i(0, getOverWorld().getHeight(0, 0) + 1, 0); + } + worldData.setSpawnPoint(newSpawnPoint); + } + + if (Server.SETTINGS.worldSettings().loadSpawnPointChunks()) { + // Add spawn point chunk loader + getOverWorld().getChunkService().addChunkLoader(new SpawnPointChunkLoader()); + } + } + + protected boolean isSafeStandingPos(Position3ic pos) { + var blockUnder = pos.dimension().getBlockState(pos.x(), pos.y() - 1, pos.z()); + var blockTypeUnder = blockUnder.getBlockType(); + if (!blockTypeUnder.getMaterial().isSolid()) { + return false; + } + return pos.dimension().getBlockState(pos.x(), pos.y(), pos.z()).getBlockType() == BlockTypes.AIR && + pos.dimension().getBlockState(pos.x(), pos.y() + 1, pos.z()).getBlockType() == BlockTypes.AIR; + } public void addSyncPacketToQueue(EntityPlayer player, BedrockPacket packet, long time) { packetQueue.add(new PacketQueueEntry(player, packet, time)); @@ -219,10 +262,10 @@ protected void syncData() { } public void startTick() { - if (thread.getState() != Thread.State.NEW) { + if (worldThread.getState() != Thread.State.NEW) { throw new IllegalStateException("World is already start ticking!"); } else { - thread.start(); + worldThread.start(); if (ENABLE_INDEPENDENT_NETWORK_THREAD) { networkThread.start(); } @@ -251,7 +294,7 @@ public Collection getPlayers() { ); } - public void setDimension(Dimension dimension) { + public void addDimension(Dimension dimension) { Preconditions.checkArgument(!this.dimensionMap.containsKey(dimension.getDimensionInfo().dimensionId())); this.dimensionMap.put(dimension.getDimensionInfo().dimensionId(), dimension); } @@ -329,4 +372,45 @@ protected void onWeatherUpdate(Set weatherRemoved, Set weather } protected record PacketQueueEntry(EntityPlayer player, BedrockPacket packet, long time) {} + + protected class SpawnPointChunkLoader implements ChunkLoader { + + @Override + public Location3fc getLocation() { + var spawnPoint = worldData.getSpawnPoint(); + return new Location3f(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), getOverWorld()); + } + + @Override + public boolean isLoaderActive() { + return true; + } + + @Override + public int getChunkLoadingRadius() { + return Server.SETTINGS.worldSettings().viewDistance(); + } + + @Override + public void setChunkLoadingRadius(int radius) {} + + @Override + public int getChunkTrySendCountPerTick() { + return Server.SETTINGS.worldSettings().chunkTrySendCountPerTick(); + } + + @Override + public void sendPacket(BedrockPacket packet) { + if (packet instanceof LevelChunkPacket lcp) { + lcp.release(); + } + } + + @Override public void beforeSendChunks() {} + @Override public void onChunkInRangeSend(Chunk chunk) {} + @Override public void spawnEntity(Entity entity) {} + @Override public void despawnEntity(Entity entity) {} + @Override public void onChunkOutOfRange(Set chunkHashes) {} + @Override public void sendPacketImmediately(BedrockPacket packet) {} + } } diff --git a/server/src/main/java/org/allaymc/server/world/AllayWorldPool.java b/server/src/main/java/org/allaymc/server/world/AllayWorldPool.java index fece7e463..263a9b033 100644 --- a/server/src/main/java/org/allaymc/server/world/AllayWorldPool.java +++ b/server/src/main/java/org/allaymc/server/world/AllayWorldPool.java @@ -1,7 +1,6 @@ package org.allaymc.server.world; import eu.okaeri.configs.ConfigManager; -import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.allaymc.api.eventbus.event.world.WorldLoadEvent; @@ -64,16 +63,16 @@ public void loadWorld(String name, WorldSettings.WorldEntry settings) { var world = new AllayWorld(storage); // Load overworld dimension var overworld = new AllayDimension(world, tryCreateWorldGenerator(overworldSettings), DimensionInfo.OVERWORLD); - world.setDimension(overworld); + world.addDimension(overworld); // Load nether and the end dimension if they are not null if (netherSettings != null) { var nether = new AllayDimension(world, tryCreateWorldGenerator(netherSettings), DimensionInfo.NETHER); - world.setDimension(nether); + world.addDimension(nether); } if (theEndSettings != null) { var theEnd = new AllayDimension(world, tryCreateWorldGenerator(theEndSettings), DimensionInfo.THE_END); - world.setDimension(theEnd); + world.addDimension(theEnd); } if (addWorld(world)) { diff --git a/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java b/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java index 377272fbb..9dfa892e6 100644 --- a/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java +++ b/server/src/main/java/org/allaymc/server/world/chunk/AllayChunk.java @@ -15,7 +15,6 @@ import org.allaymc.api.blockentity.BlockEntity; import org.allaymc.api.entity.Entity; import org.allaymc.api.server.Server; -import org.allaymc.api.server.ServerSettings; import org.allaymc.api.world.Dimension; import org.allaymc.api.world.DimensionInfo; import org.allaymc.api.world.biome.BiomeType; @@ -33,7 +32,7 @@ import java.util.function.Predicate; /** - * @author Cool_Loong + * @author Cool_Loong | daoge_cmd */ @ThreadSafe @Slf4j @@ -82,21 +81,6 @@ protected void checkAutoSave(WorldStorage worldStorage) { } } - @Override - public short[] getHeightArray() { - var stamp = heightAndBiomeLock.tryOptimisticRead(); - try { - for (; ; stamp = heightAndBiomeLock.readLock()) { - if (stamp == 0L) continue; - var result = unsafeChunk.getHeightArray(); - if (!heightAndBiomeLock.validate(stamp)) continue; - return result; - } - } finally { - if (StampedLock.isReadLockStamp(stamp)) heightAndBiomeLock.unlockRead(stamp); - } - } - @Override public int getHeight(int x, int z) { checkXZ(x, z); diff --git a/server/src/main/java/org/allaymc/server/world/chunk/AllayUnsafeChunk.java b/server/src/main/java/org/allaymc/server/world/chunk/AllayUnsafeChunk.java index f036bb37b..b17f25100 100644 --- a/server/src/main/java/org/allaymc/server/world/chunk/AllayUnsafeChunk.java +++ b/server/src/main/java/org/allaymc/server/world/chunk/AllayUnsafeChunk.java @@ -6,6 +6,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.allaymc.api.block.type.BlockState; +import org.allaymc.api.block.type.BlockTypes; import org.allaymc.api.blockentity.BlockEntity; import org.allaymc.api.blockentity.BlockEntityHelper; import org.allaymc.api.entity.Entity; @@ -47,6 +48,7 @@ public class AllayUnsafeChunk implements UnsafeChunk { protected final DimensionInfo dimensionInfo; @Getter protected final ChunkSection[] sections; + @Getter protected final HeightMap heightMap; protected final Map entities; protected final Map blockEntities; @@ -164,7 +166,11 @@ public Collection getSectionBlockEntities(int sectionY) { public int getHeight(int x, int z) { Preconditions.checkArgument(x >= 0 && x <= 15); Preconditions.checkArgument(z >= 0 && z <= 15); - return this.heightMap.get(x, z) + dimensionInfo.minHeight(); + return getHeightUnsafe(HeightMap.computeIndex(x, z)); + } + + protected int getHeightUnsafe(int index) { + return this.heightMap.get(index) + dimensionInfo.minHeight(); } @Override @@ -172,7 +178,11 @@ public void setHeight(int x, int z, int height) { Preconditions.checkArgument(x >= 0 && x <= 15); Preconditions.checkArgument(z >= 0 && z <= 15); Preconditions.checkArgument(height >= -512 && height <= 511); - this.heightMap.set(x, z, (short) (height - dimensionInfo.minHeight())); + setHeightUnsafe(HeightMap.computeIndex(x, z), height); + } + + protected void setHeightUnsafe(int index, int height) { + this.heightMap.set(index, (short) (height - dimensionInfo.minHeight())); } @Override @@ -192,11 +202,19 @@ public void setBlockState(int x, int y, int z, BlockState blockState, int layer) var section = this.getSection(sectionY); if (section == null) section = this.getOrCreateSection(sectionY); section.setBlockState(x, y & 0xf, z, blockState, layer); - } - - @Override - public short[] getHeightArray() { - return this.heightMap.getHeights(); + if (layer != 0) return; + + // Update height map + var index = HeightMap.computeIndex(x, z); + var currentHeight = getHeightUnsafe(index); + if (blockState.getBlockType() == BlockTypes.AIR) { + // Use >= here because some maps may be broken + if (currentHeight >= y) { + setHeightUnsafe(index, y - 1); + } + } else if (currentHeight < y) { + setHeightUnsafe(index, y); + } } @Override diff --git a/server/src/main/java/org/allaymc/server/world/chunk/ChunkSection.java b/server/src/main/java/org/allaymc/server/world/chunk/ChunkSection.java index e07ff70d3..210fd127a 100644 --- a/server/src/main/java/org/allaymc/server/world/chunk/ChunkSection.java +++ b/server/src/main/java/org/allaymc/server/world/chunk/ChunkSection.java @@ -19,7 +19,7 @@ @NotThreadSafe public record ChunkSection( byte sectionY, - Palette[] blockLayer, + Palette[] blockLayers, Palette biomes, NibbleArray blockLights, NibbleArray skyLights @@ -48,11 +48,11 @@ public ChunkSection(byte sectionY, Palette[] blockLayer) { } public BlockState getBlockState(int x, int y, int z, int layer) { - return blockLayer[layer].get(index(x, y, z)); + return blockLayers[layer].get(index(x, y, z)); } public void setBlockState(int x, int y, int z, BlockState blockState, int layer) { - blockLayer[layer].set(index(x, y, z), blockState); + blockLayers[layer].set(index(x, y, z), blockState); } public void setBiomeType(int x, int y, int z, BiomeType biomeType) { @@ -80,7 +80,7 @@ public void setSkyLight(int x, int y, int z, byte light) { } public boolean isEmpty() { - return blockLayer[0].isEmpty() && blockLayer[0].get(0) == AIR.getDefaultState(); + return blockLayers[0].isEmpty() && blockLayers[0].get(0) == AIR.getDefaultState(); } public void writeToNetwork(ByteBuf byteBuf) { @@ -89,6 +89,6 @@ public void writeToNetwork(ByteBuf byteBuf) { byteBuf.writeByte(LAYER_COUNT); byteBuf.writeByte(sectionY & 0xFF); - for (var palette : blockLayer) palette.writeToNetwork(byteBuf, BlockState::blockStateHash); + for (var blockLayer : blockLayers) blockLayer.writeToNetwork(byteBuf, BlockState::blockStateHash); } } diff --git a/server/src/main/java/org/allaymc/server/world/generator/FlatNoiser.java b/server/src/main/java/org/allaymc/server/world/generator/FlatNoiser.java index a556bdfc0..459838c25 100644 --- a/server/src/main/java/org/allaymc/server/world/generator/FlatNoiser.java +++ b/server/src/main/java/org/allaymc/server/world/generator/FlatNoiser.java @@ -59,7 +59,6 @@ public boolean apply(NoiseContext context) { var flatChunk = context.getCurrentChunk(); for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - flatChunk.setHeight(x, z, layers.length + 1); for (int y = 0; y < layers.length; y++) { flatChunk.setBlockState(x, y, z, layers[y]); } diff --git a/server/src/main/java/org/allaymc/server/world/storage/LevelDBChunkSerializer.java b/server/src/main/java/org/allaymc/server/world/storage/LevelDBChunkSerializer.java index 03a758a1e..d0bce466c 100644 --- a/server/src/main/java/org/allaymc/server/world/storage/LevelDBChunkSerializer.java +++ b/server/src/main/java/org/allaymc/server/world/storage/LevelDBChunkSerializer.java @@ -73,7 +73,7 @@ private void serializeBlock(WriteBatch writeBatch, AllayUnsafeChunk chunk) { buffer.writeByte(ChunkSection.LAYER_COUNT); buffer.writeByte(ySection); for (int i = 0; i < ChunkSection.LAYER_COUNT; i++) { - section.blockLayer()[i].writeToStoragePersistent(buffer, BlockState::getBlockStateTag); + section.blockLayers()[i].writeToStoragePersistent(buffer, BlockState::getBlockStateTag); } writeBatch.put(LevelDBKeyUtils.CHUNK_SECTION_PREFIX.getKey(chunk.getX(), chunk.getZ(), ySection, chunk.getDimensionInfo()), Utils.convertByteBuf2Array(buffer)); } finally { @@ -110,7 +110,7 @@ private void deserializeBlock(DB db, AllayUnsafeChunk.Builder builder) { section = new ChunkSection((byte) ySection, palettes); } for (int layer = 0; layer < layers; layer++) { - section.blockLayer()[layer].readFromStoragePersistent(byteBuf, hash -> { + section.blockLayers()[layer].readFromStoragePersistent(byteBuf, hash -> { BlockState blockState = Registries.BLOCK_STATE_PALETTE.get(hash); if (blockState == null) { log.error("Unknown block state hash: " + hash); @@ -133,7 +133,7 @@ private void serializeHeightAndBiome(WriteBatch writeBatch, AllayUnsafeChunk chu ByteBuf heightAndBiomesBuffer = ByteBufAllocator.DEFAULT.ioBuffer(); try { // Serialize height map - for (short height : chunk.getHeightArray()) { + for (short height : chunk.getHeightMap().getHeights()) { heightAndBiomesBuffer.writeShortLE(height); } // Serialize biome