Skip to content

Commit

Permalink
feat: height map (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcmd authored Oct 19, 2024
1 parent 1e40699 commit 7f6cee8
Show file tree
Hide file tree
Showing 20 changed files with 376 additions and 166 deletions.
54 changes: 17 additions & 37 deletions api/src/main/java/org/allaymc/api/client/storage/PlayerData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
10 changes: 8 additions & 2 deletions api/src/main/java/org/allaymc/api/server/ServerSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 35 additions & 4 deletions api/src/main/java/org/allaymc/api/utils/AllayNbtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down
59 changes: 58 additions & 1 deletion api/src/main/java/org/allaymc/api/world/Dimension.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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.
* <p>
* 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<Position3ic> 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<Position3ic> 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;
}
}
2 changes: 1 addition & 1 deletion api/src/main/java/org/allaymc/api/world/World.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions api/src/main/java/org/allaymc/api/world/WorldData.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,6 @@ public <V> V getGameRule(GameRule gameRule) {
return gameRules.get(gameRule);
}

/**
* The overworld default spawn point
*/
public Vector3ic getSpawnPoint() {
return spawnPoint;
}
Expand Down
10 changes: 0 additions & 10 deletions api/src/main/java/org/allaymc/api/world/chunk/UnsafeChunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
*
Expand Down
40 changes: 37 additions & 3 deletions api/src/main/java/org/allaymc/api/world/heightmap/HeightMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
* HeightMap stores the height of each pos in a chunk.
*
Expand All @@ -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;
}

/**
Expand All @@ -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];
}

/**
Expand All @@ -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;
}

/**
Expand Down
Loading

0 comments on commit 7f6cee8

Please sign in to comment.