diff --git a/platforms/minestom/example/src/main/java/com/dfsek/terra/minestom/TerraMinestomExample.java b/platforms/minestom/example/src/main/java/com/dfsek/terra/minestom/TerraMinestomExample.java index 5ee453042..b90a4211e 100644 --- a/platforms/minestom/example/src/main/java/com/dfsek/terra/minestom/TerraMinestomExample.java +++ b/platforms/minestom/example/src/main/java/com/dfsek/terra/minestom/TerraMinestomExample.java @@ -27,6 +27,8 @@ public class TerraMinestomExample { private TerraMinestomWorld world; public static void main(String[] args) { + System.setProperty("minestom.registry.unsafe-ops", "true"); + TerraMinestomExample example = new TerraMinestomExample(); example.createNewInstance(); example.attachTerra(); diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/EntityFactory.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/EntityFactory.java index 5cfbecd8d..7930d3e3b 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/EntityFactory.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/EntityFactory.java @@ -1,6 +1,7 @@ package com.dfsek.terra.minestom.api; +import net.kyori.adventure.nbt.CompoundBinaryTag; import net.minestom.server.entity.Entity; 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 */ 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); + + /** + * 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); + } } diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockState.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockState.java index 237449907..6725e947f 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockState.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockState.java @@ -1,7 +1,10 @@ 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 java.io.IOException; import java.util.HashMap; import java.util.Objects; 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.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); - private final Block block; + private static final TagStringIO tagStringIO = TagStringIO.tagStringIO(); - public MinestomBlockState(Block block) { - if(block == null) { - this.block = Block.AIR; - } else { - this.block = block; - } + public MinestomBlockState { + block = Objects.requireNonNullElse(block, Block.AIR); } - public MinestomBlockState(String data) { - if(!data.contains("[")) { - block = Block.fromKey(data); - return; + 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); + } } - String[] split = data.split("\\["); - String namespaceId = split[0]; - String properties = split[1].substring(0, split[1].length() - 1); + int openBracketIndex = data.indexOf('['); + int closeBracketIndex = data.indexOf(']'); + + 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 namespaceId = data.substring(0, openBracketIndex); + String propertiesContent = data.substring(openBracketIndex + 1, closeBracketIndex); Block block = Block.fromKey(namespaceId); - HashMap propertiesMap = new HashMap<>(); - - for(String property : properties.split(",")) { - String[] kv = property.split("="); - propertiesMap.put(kv[0].strip(), kv[1].strip()); + if (block == null) { + LOGGER.error("Invalid block ID found during parsing: {}", namespaceId); + return new MinestomBlockState(Block.AIR); } - assert block != null; - this.block = block.withProperties(propertiesMap); + HashMap propertiesMap = new HashMap<>(); + 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 diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/CachedChunk.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/CachedChunk.java index a92633c95..fa48f7c5b 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/CachedChunk.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/CachedChunk.java @@ -15,26 +15,27 @@ import com.dfsek.terra.minestom.block.MinestomBlockState; public class CachedChunk implements ProtoChunk { private final int minHeight; private final int maxHeight; - private final Block[] blocks; + private final MinestomBlockState[] blocks; public CachedChunk(int minHeight, int maxHeight) { this.minHeight = minHeight; this.maxHeight = maxHeight; - this.blocks = new Block[16 * (maxHeight - minHeight + 1) * 16]; - Arrays.fill(blocks, Block.AIR); + this.blocks = new MinestomBlockState[16 * (maxHeight - minHeight + 1) * 16]; + Arrays.fill(blocks, MinestomBlockState.AIR); } 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 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; int index = getIndex(x, y, z); if(index > blocks.length || index < 0) return; - blocks[index] = block; + blocks[index] = minestomBlockState; } 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) { int index = getIndex(x, y, z); if(index > blocks.length || index < 0) return MinestomBlockState.AIR; - return new MinestomBlockState(blocks[index]); + return blocks[index]; } @Override diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/GeneratedChunkCache.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/GeneratedChunkCache.java index a84477fef..6a8fe71e9 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/GeneratedChunkCache.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/GeneratedChunkCache.java @@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.stats.CacheStats; import net.minestom.server.world.DimensionType; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +16,7 @@ import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator; public class GeneratedChunkCache { private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class); - private final LoadingCache, CachedChunk> cache; + private final LoadingCache<@NotNull Long, CachedChunk> cache; private final DimensionType dimensionType; private final ChunkGenerator generator; private final ServerWorld world; @@ -29,7 +30,7 @@ public class GeneratedChunkCache { this.cache = Caffeine.newBuilder() .maximumSize(128) .recordStats() - .build((Pair key) -> generateChunk(key.getLeft(), key.getRight())); + .build((Long key) -> generateChunk(unpackX(key), unpackZ(key))); } private CachedChunk generateChunk(int x, int z) { @@ -50,6 +51,18 @@ public class GeneratedChunkCache { } 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; } } diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntity.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntity.java index 0818b19fa..110aca47e 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntity.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntity.java @@ -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) { 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)); return new MinestomEntity(entity, world); } diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntityType.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntityType.java index 1efa1cbd5..cf5d5ed95 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntityType.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntityType.java @@ -1,12 +1,38 @@ 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; 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 CompoundBinaryTag data; 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); } @@ -14,4 +40,8 @@ public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType public EntityType getHandle() { return delegate; } + + public CompoundBinaryTag getData() { + return data; + } } \ No newline at end of file diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomWorldHandle.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomWorldHandle.java index 777850952..3ff92ccf6 100644 --- a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomWorldHandle.java +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomWorldHandle.java @@ -15,7 +15,7 @@ public class MinestomWorldHandle implements WorldHandle { @Override public @NotNull BlockState createBlockState(@NotNull String data) { - return new MinestomBlockState(data); + return MinestomBlockState.fromStateId(data); } @Override