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
- * 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