Merge remote-tracking branch 'origin/master'

This commit is contained in:
Zoe Gidiere
2025-12-10 19:28:43 -07:00
8 changed files with 148 additions and 34 deletions

View File

@@ -27,6 +27,8 @@ public class TerraMinestomExample {
private TerraMinestomWorld world; private TerraMinestomWorld world;
public static void main(String[] args) { public static void main(String[] args) {
System.setProperty("minestom.registry.unsafe-ops", "true");
TerraMinestomExample example = new TerraMinestomExample(); TerraMinestomExample example = new TerraMinestomExample();
example.createNewInstance(); example.createNewInstance();
example.attachTerra(); example.attachTerra();

View File

@@ -1,6 +1,7 @@
package com.dfsek.terra.minestom.api; package com.dfsek.terra.minestom.api;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType; import net.minestom.server.entity.EntityType;
@@ -9,5 +10,22 @@ import net.minestom.server.entity.EntityType;
* Allows adding AI to generated entities using custom entity types * Allows adding AI to generated entities using custom entity types
*/ */
public interface EntityFactory { public interface EntityFactory {
/**
* Creates a new entity of the specified type.
*
* @param type the type of the entity to be created
* @return the created entity instance
*/
Entity createEntity(EntityType type); Entity createEntity(EntityType type);
/**
* Creates a new entity of the specified type with additional data.
*
* @param type the type of the entity to be created
* @param data the additional data for the entity, represented as a CompoundBinaryTag
* @return the created entity instance
*/
default Entity createEntity(EntityType type, CompoundBinaryTag data) {
return createEntity(type);
}
} }

View File

@@ -1,7 +1,10 @@
package com.dfsek.terra.minestom.block; package com.dfsek.terra.minestom.block;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -10,38 +13,84 @@ import com.dfsek.terra.api.block.BlockType;
import com.dfsek.terra.api.block.state.BlockState; import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.block.state.properties.Property; import com.dfsek.terra.api.block.state.properties.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MinestomBlockState implements BlockState {
public record MinestomBlockState(Block block) implements BlockState {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomBlockState.class);
public static final MinestomBlockState AIR = new MinestomBlockState(Block.AIR); public static final MinestomBlockState AIR = new MinestomBlockState(Block.AIR);
private final Block block; private static final TagStringIO tagStringIO = TagStringIO.tagStringIO();
public MinestomBlockState(Block block) { public MinestomBlockState {
if(block == null) { block = Objects.requireNonNullElse(block, Block.AIR);
this.block = Block.AIR; }
} else {
this.block = block; public static MinestomBlockState fromStateId(String data) {
CompoundBinaryTag nbt = CompoundBinaryTag.empty();
int splitIndex = data.indexOf('{');
if(splitIndex != -1) {
String fullId = data;
data = data.substring(0, splitIndex);
String dataString = fullId.substring(splitIndex);
try {
nbt = tagStringIO.asCompound(dataString);
} catch(IOException exception) {
LOGGER.warn("Invalid entity data, will be ignored: {}", dataString);
} }
} }
public MinestomBlockState(String data) { int openBracketIndex = data.indexOf('[');
if(!data.contains("[")) { int closeBracketIndex = data.indexOf(']');
block = Block.fromKey(data);
return; if(openBracketIndex == -1 || closeBracketIndex == -1 || closeBracketIndex < openBracketIndex) {
// no or invalid properties
Block block = Block.fromKey(data);
if(block != null && !nbt.isEmpty()) {
block = block.withNbt(nbt);
}
return new MinestomBlockState(block);
} }
String[] split = data.split("\\["); String namespaceId = data.substring(0, openBracketIndex);
String namespaceId = split[0]; String propertiesContent = data.substring(openBracketIndex + 1, closeBracketIndex);
String properties = split[1].substring(0, split[1].length() - 1);
Block block = Block.fromKey(namespaceId); Block block = Block.fromKey(namespaceId);
HashMap<String, String> propertiesMap = new HashMap<>(); if (block == null) {
LOGGER.error("Invalid block ID found during parsing: {}", namespaceId);
for(String property : properties.split(",")) { return new MinestomBlockState(Block.AIR);
String[] kv = property.split("=");
propertiesMap.put(kv[0].strip(), kv[1].strip());
} }
assert block != null; HashMap<String, String> propertiesMap = new HashMap<>();
this.block = block.withProperties(propertiesMap); int current = 0;
while (current < propertiesContent.length()) {
int nextComma = propertiesContent.indexOf(',', current);
String property;
if (nextComma == -1) {
property = propertiesContent.substring(current);
current = propertiesContent.length();
} else {
property = propertiesContent.substring(current, nextComma);
current = nextComma + 1;
}
int equalsIndex = property.indexOf('=');
if (equalsIndex == -1) {
LOGGER.warn("Invalid block property syntax (missing '=') in string: {}", property);
continue;
}
String key = property.substring(0, equalsIndex).strip();
String value = property.substring(equalsIndex + 1).strip();
propertiesMap.put(key, value);
}
if(!nbt.isEmpty()) {
block = block.withNbt(nbt);
}
return new MinestomBlockState(block.withProperties(propertiesMap));
} }
@Override @Override

View File

@@ -15,26 +15,27 @@ import com.dfsek.terra.minestom.block.MinestomBlockState;
public class CachedChunk implements ProtoChunk { public class CachedChunk implements ProtoChunk {
private final int minHeight; private final int minHeight;
private final int maxHeight; private final int maxHeight;
private final Block[] blocks; private final MinestomBlockState[] blocks;
public CachedChunk(int minHeight, int maxHeight) { public CachedChunk(int minHeight, int maxHeight) {
this.minHeight = minHeight; this.minHeight = minHeight;
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
this.blocks = new Block[16 * (maxHeight - minHeight + 1) * 16]; this.blocks = new MinestomBlockState[16 * (maxHeight - minHeight + 1) * 16];
Arrays.fill(blocks, Block.AIR); Arrays.fill(blocks, MinestomBlockState.AIR);
} }
public void writeRelative(UnitModifier modifier) { public void writeRelative(UnitModifier modifier) {
modifier.setAllRelative((x, y, z) -> blocks[getIndex(x, y + minHeight, z)]); modifier.setAllRelative((x, y, z) -> blocks[getIndex(x, y + minHeight, z)].block());
} }
@Override @Override
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) { public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
Block block = (Block) blockState.getHandle(); MinestomBlockState minestomBlockState = (MinestomBlockState) blockState;
Block block = minestomBlockState.block();
if(block == null) return; if(block == null) return;
int index = getIndex(x, y, z); int index = getIndex(x, y, z);
if(index > blocks.length || index < 0) return; if(index > blocks.length || index < 0) return;
blocks[index] = block; blocks[index] = minestomBlockState;
} }
private int getIndex(int x, int y, int z) { private int getIndex(int x, int y, int z) {
@@ -46,7 +47,7 @@ public class CachedChunk implements ProtoChunk {
public @NotNull BlockState getBlock(int x, int y, int z) { public @NotNull BlockState getBlock(int x, int y, int z) {
int index = getIndex(x, y, z); int index = getIndex(x, y, z);
if(index > blocks.length || index < 0) return MinestomBlockState.AIR; if(index > blocks.length || index < 0) return MinestomBlockState.AIR;
return new MinestomBlockState(blocks[index]); return blocks[index];
} }
@Override @Override

View File

@@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats; import com.github.benmanes.caffeine.cache.stats.CacheStats;
import net.minestom.server.world.DimensionType; import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -15,7 +16,7 @@ import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
public class GeneratedChunkCache { public class GeneratedChunkCache {
private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class); private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class);
private final LoadingCache<Pair<Integer, Integer>, CachedChunk> cache; private final LoadingCache<@NotNull Long, CachedChunk> cache;
private final DimensionType dimensionType; private final DimensionType dimensionType;
private final ChunkGenerator generator; private final ChunkGenerator generator;
private final ServerWorld world; private final ServerWorld world;
@@ -29,7 +30,7 @@ public class GeneratedChunkCache {
this.cache = Caffeine.newBuilder() this.cache = Caffeine.newBuilder()
.maximumSize(128) .maximumSize(128)
.recordStats() .recordStats()
.build((Pair<Integer, Integer> key) -> generateChunk(key.getLeft(), key.getRight())); .build((Long key) -> generateChunk(unpackX(key), unpackZ(key)));
} }
private CachedChunk generateChunk(int x, int z) { private CachedChunk generateChunk(int x, int z) {
@@ -50,6 +51,18 @@ public class GeneratedChunkCache {
} }
public CachedChunk at(int x, int z) { public CachedChunk at(int x, int z) {
return cache.get(Pair.of(x, z)); return cache.get(pack(x, z));
}
private long pack(final int x, final int z) {
return ((long) x) << 32 | z & 0xFFFFFFFFL;
}
private int unpackX(long key) {
return (int) (key >>> 32);
}
private int unpackZ(long key) {
return (int) key;
} }
} }

View File

@@ -22,7 +22,8 @@ public class MinestomEntity implements com.dfsek.terra.api.entity.Entity {
public static MinestomEntity spawn(double x, double y, double z, EntityType type, TerraMinestomWorld world) { public static MinestomEntity spawn(double x, double y, double z, EntityType type, TerraMinestomWorld world) {
Instance instance = world.getHandle(); Instance instance = world.getHandle();
Entity entity = world.getEntityFactory().createEntity(((MinestomEntityType) type).getHandle()); MinestomEntityType entityType = (MinestomEntityType) type;
Entity entity = world.getEntityFactory().createEntity(entityType.getHandle(), entityType.getData());
entity.setInstance(instance, new Pos(x, y, z)); entity.setInstance(instance, new Pos(x, y, z));
return new MinestomEntity(entity, world); return new MinestomEntity(entity, world);
} }

View File

@@ -1,12 +1,38 @@
package com.dfsek.terra.minestom.entity; package com.dfsek.terra.minestom.entity;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.minestom.server.entity.EntityType; import net.minestom.server.entity.EntityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType { public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomEntityType.class);
private static final TagStringIO tagStringIO = TagStringIO.tagStringIO();
private final EntityType delegate; private final EntityType delegate;
private final CompoundBinaryTag data;
public MinestomEntityType(String id) { public MinestomEntityType(String id) {
int splitIndex = id.indexOf('{');
if(splitIndex != -1) {
String fullId = id;
id = id.substring(0, splitIndex);
String dataString = fullId.substring(splitIndex);
CompoundBinaryTag data;
try {
data = tagStringIO.asCompound(dataString);
} catch(IOException exception) {
LOGGER.warn("Invalid entity data, will be ignored: {}", dataString);
data = CompoundBinaryTag.empty();
}
this.data = data;
} else {
this.data = CompoundBinaryTag.empty();
}
delegate = EntityType.fromKey(id); delegate = EntityType.fromKey(id);
} }
@@ -14,4 +40,8 @@ public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType
public EntityType getHandle() { public EntityType getHandle() {
return delegate; return delegate;
} }
public CompoundBinaryTag getData() {
return data;
}
} }

View File

@@ -15,7 +15,7 @@ public class MinestomWorldHandle implements WorldHandle {
@Override @Override
public @NotNull BlockState createBlockState(@NotNull String data) { public @NotNull BlockState createBlockState(@NotNull String data) {
return new MinestomBlockState(data); return MinestomBlockState.fromStateId(data);
} }
@Override @Override