Merge remote-tracking branch 'everbuild-org/feat/platform/minestom' into ver/6.6.0

This commit is contained in:
Zoë Gidiere 2025-02-26 10:22:37 -07:00
commit a61c6b8a97
33 changed files with 1501 additions and 19 deletions

View File

@ -44,6 +44,7 @@ afterEvaluate {
configureDistribution()
}
project(":platforms:bukkit:common").configureDistribution()
project(":platforms:minestom:example").configureDistribution()
forSubProjects(":common:addons") {
apply(plugin = "com.gradleup.shadow")

View File

@ -4,9 +4,11 @@ import java.io.File
import java.io.FileWriter
import java.net.URL
import java.nio.file.FileSystems
import java.nio.file.Path
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.extra
@ -19,6 +21,25 @@ import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.exists
private fun Project.installAddonsInto(dest: Path) {
FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
val jar = getJarTask()
logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
if (!addonPath.exists()) {
addonPath.parent.createDirectories()
addonPath.createFile()
jar.archiveFile.get().asFile.toPath().copyTo(addonPath, overwrite = true)
}
}
}
}
fun Project.configureDistribution() {
apply(plugin = "com.gradleup.shadow")
@ -48,25 +69,17 @@ fun Project.configureDistribution() {
doLast {
// https://github.com/johnrengelman/shadow/issues/111
val dest = tasks.named<ShadowJar>("shadowJar").get().archiveFile.get().path
FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
val jar = getJarTask()
logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")
val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")
if (!addonPath.exists()) {
addonPath.parent.createDirectories()
addonPath.createFile()
jar.archiveFile.get().asFile.toPath().copyTo(addonPath, overwrite = true)
}
}
}
installAddonsInto(dest)
}
}
tasks.create("installAddonsIntoDefaultJar") {
group = "terra"
dependsOn(compileAddons)
doLast {
val dest = tasks.named<Jar>("jar").get().archiveFile.get().path
installAddonsInto(dest)
}
}

View File

@ -81,4 +81,8 @@ object Versions {
object Allay {
const val api = "0.1.3"
}
object Minestom {
const val minestom = "187931e50b"
}
}

View File

@ -0,0 +1,11 @@
dependencies {
shadedApi(project(":common:implementation:base"))
shadedApi("com.github.ben-manes.caffeine", "caffeine", Versions.Libraries.caffeine)
shadedImplementation("com.google.guava", "guava", Versions.Libraries.Internal.guava)
compileOnly("net.minestom", "minestom-snapshots", Versions.Minestom.minestom)
}
tasks.named("jar") {
finalizedBy("installAddonsIntoDefaultJar")
}

View File

@ -0,0 +1,28 @@
plugins {
application
}
val javaMainClass = "com.dfsek.terra.minestom.TerraMinestomExample"
dependencies {
shadedApi(project(":platforms:minestom"))
implementation("net.minestom", "minestom-snapshots", Versions.Minestom.minestom)
implementation("org.slf4j", "slf4j-simple", Versions.Libraries.slf4j)
}
tasks.withType<Jar> {
entryCompression = ZipEntryCompression.STORED
manifest {
attributes(
"Main-Class" to javaMainClass,
)
}
}
application {
mainClass.set(javaMainClass)
}
tasks.getByName("run").setProperty("workingDir", file("./run"))
addonDir(project.file("./run/terra/addons"), tasks.named("run").get())

View File

@ -0,0 +1,132 @@
package com.dfsek.terra.minestom;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.LightingChunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import com.dfsek.terra.minestom.world.TerraMinestomWorldBuilder;
public class TerraMinestomExample {
private static final Logger logger = LoggerFactory.getLogger(TerraMinestomExample.class);
private final MinecraftServer server = MinecraftServer.init();
private Instance instance;
private TerraMinestomWorld world;
public void createNewInstance() {
instance = MinecraftServer.getInstanceManager().createInstanceContainer();
instance.setChunkSupplier(LightingChunk::new);
}
public void attachTerra() {
world = TerraMinestomWorldBuilder.from(instance)
.defaultPack()
.attach();
}
private void sendProgressBar(int current, int max) {
String left = "#".repeat((int) ((((float) current) / max) * 20));
String right = ".".repeat(20 - left.length());
int percent = (int) (((float) current) / max * 100);
String percentString = percent + "%";
percentString = " ".repeat(4 - percentString.length()) + percentString;
String message = percentString + " |" + left + right + "| " + current + "/" + max;
logger.info(message);
}
public void preloadWorldAndMeasure() {
int radius = 12;
int chunksLoading = (radius * 2 + 1) * (radius * 2 + 1);
AtomicInteger chunksLeft = new AtomicInteger(chunksLoading);
long start = System.nanoTime();
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
instance.loadChunk(x, z).thenAccept(chunk -> {
int left = chunksLeft.decrementAndGet();
if(left == 0) {
long end = System.nanoTime();
sendProgressBar(chunksLoading - left, chunksLoading);
double chunksPerSecond = chunksLoading / ((end - start) / 1000000000.0);
logger.info(
"Preloaded {} chunks in world in {}ms. That's {} Chunks/s",
chunksLoading,
(end - start) / 1000000.0,
chunksPerSecond
);
world.displayStats();
} else if(left % 60 == 0) {
sendProgressBar(chunksLoading - left, chunksLoading);
}
});
}
}
}
public void addListeners() {
MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> {
event.setSpawningInstance(instance);
event.getPlayer().setRespawnPoint(new Pos(0.0, 100.0, 0.0));
});
MinecraftServer.getGlobalEventHandler().addListener(PlayerSpawnEvent.class, event -> {
event.getPlayer().setGameMode(GameMode.SPECTATOR);
});
}
public void addScheduler() {
MinecraftServer.getSchedulerManager().buildTask(() -> world.displayStats())
.repeat(Duration.ofSeconds(10))
.schedule();
}
public void addCommands() {
MinecraftServer.getCommandManager().register(new RegenerateCommand());
}
public void bind() {
logger.info("Starting server on port 25565");
server.start("localhost", 25565);
}
public static void main(String[] args) {
TerraMinestomExample example = new TerraMinestomExample();
example.createNewInstance();
example.attachTerra();
example.preloadWorldAndMeasure();
example.addScheduler();
example.addListeners();
example.addCommands();
example.bind();
}
public class RegenerateCommand extends Command {
public RegenerateCommand() {
super("regenerate");
setDefaultExecutor((sender, context) -> regenerate());
}
private void regenerate() {
instance.sendMessage(Component.text("Regenerating world"));
createNewInstance();
attachTerra();
preloadWorldAndMeasure();
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player ->
player.setInstance(instance, new Pos(0, 100, 0))
);
}
}
}

View File

@ -0,0 +1,17 @@
package com.dfsek.terra.minestom;
import com.dfsek.terra.api.util.vector.Vector3;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
public class MinestomAdapter {
public static Vector3 adapt(Point point) {
return Vector3.of(point.x(), point.y(), point.z());
}
public static Pos adapt(Vector3 vector) {
return new Pos(vector.getX(), vector.getY(), vector.getZ());
}
}

View File

@ -0,0 +1,94 @@
package com.dfsek.terra.minestom;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.AbstractPlatform;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent;
import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.handle.WorldHandle;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import com.dfsek.terra.minestom.biome.MinestomBiomeLoader;
import com.dfsek.terra.minestom.entity.MinestomEntityType;
import com.dfsek.terra.minestom.item.MinestomItemHandle;
import com.dfsek.terra.minestom.world.MinestomChunkGeneratorWrapper;
import com.dfsek.terra.minestom.world.MinestomWorldHandle;
import net.minestom.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public final class MinestomPlatform extends AbstractPlatform {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomPlatform.class);
private static MinestomPlatform INSTANCE = null;
private final MinestomWorldHandle worldHandle = new MinestomWorldHandle();
private final MinestomItemHandle itemHandle = new MinestomItemHandle();
private MinestomPlatform() {
load();
getEventManager().callEvent(new PlatformInitializationEvent());
}
@Override
public void register(TypeRegistry registry) {
super.register(registry);
registry
.registerLoader(PlatformBiome.class, new MinestomBiomeLoader())
.registerLoader(EntityType.class, (TypeLoader<EntityType>) (annotatedType, o, configLoader, depthTracker) -> new MinestomEntityType((String) o))
.registerLoader(BlockState.class, (TypeLoader<BlockState>) (annotatedType, o, configLoader, depthTracker) -> worldHandle.createBlockState((String) o));
}
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
MinecraftServer.getInstanceManager().getInstances().forEach(world -> {
if(world.generator() instanceof MinestomChunkGeneratorWrapper wrapper) {
getConfigRegistry().get(wrapper.getPack().getRegistryKey()).ifPresent(pack -> {
wrapper.setPack(pack);
LOGGER.info("Replaced pack in chunk generator for instance {}", world.getUniqueId());
});
}
});
return succeed;
}
@Override
public @NotNull WorldHandle getWorldHandle() {
return worldHandle;
}
@Override
public @NotNull ItemHandle getItemHandle() {
return itemHandle;
}
@Override
public @NotNull String platformName() {
return "Minestom";
}
@Override
public @NotNull File getDataFolder() {
String pathName = System.getProperty("terra.datafolder");
if (pathName == null) pathName = "./terra/";
File file = new File(pathName);
if(!file.exists()) file.mkdirs();
return file;
}
public static MinestomPlatform getInstance() {
if(INSTANCE == null) {
INSTANCE = new MinestomPlatform();
}
return INSTANCE;
}
}

View File

@ -0,0 +1,16 @@
package com.dfsek.terra.minestom.api;
import com.dfsek.terra.api.block.entity.BlockEntity;
import net.minestom.server.coordinate.BlockVec;
import org.jetbrains.annotations.Nullable;
/**
* Represents a factory interface for creating instances of BlockEntity
* at a specified BlockVec position. This is not implemented directly because
* Minestom does not define a way to build block entities out of the box.
*/
public interface BlockEntityFactory {
@Nullable BlockEntity createBlockEntity(BlockVec position);
}

View File

@ -0,0 +1,13 @@
package com.dfsek.terra.minestom.api;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
/**
* Allows adding AI to generated entities using custom entity types
*/
public interface EntityFactory {
Entity createEntity(EntityType type);
}

View File

@ -0,0 +1,17 @@
package com.dfsek.terra.minestom.biome;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.minestom.server.world.biome.Biome;
public class MinestomBiome implements PlatformBiome {
private final Biome biome;
public MinestomBiome(Biome biome) { this.biome = biome; }
@Override
public Biome getHandle() {
return biome;
}
}

View File

@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.biome;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.minestom.server.MinecraftServer;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.biome.Biome;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
public class MinestomBiomeLoader implements TypeLoader<PlatformBiome> {
private final DynamicRegistry<Biome> biomeRegistry = MinecraftServer.getBiomeRegistry();
@Override
public PlatformBiome load(@NotNull AnnotatedType annotatedType, @NotNull Object o, @NotNull ConfigLoader configLoader,
DepthTracker depthTracker) throws LoadException {
String id = (String) o;
NamespaceID biomeID = NamespaceID.from(id);
Biome biome = biomeRegistry.get(biomeID);
if(biome == null) throw new LoadException("Biome %s does not exist in registry".formatted(id), depthTracker);
return new MinestomBiome(biome);
}
}

View File

@ -0,0 +1,14 @@
package com.dfsek.terra.minestom.block;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import net.minestom.server.coordinate.BlockVec;
public class DefaultBlockEntityFactory implements BlockEntityFactory {
@Override
public BlockEntity createBlockEntity(BlockVec position) {
return null;
}
}

View File

@ -0,0 +1,98 @@
package com.dfsek.terra.minestom.block;
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 net.minestom.server.instance.block.Block;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;
public class MinestomBlockState implements BlockState {
private final Block block;
public MinestomBlockState(Block block) {
if(block == null) {
this.block = Block.AIR;
} else {
this.block = block;
}
}
public MinestomBlockState(String data) {
if(!data.contains("[")) {
block = Block.fromNamespaceId(data);
return;
}
String[] split = data.split("\\[");
String namespaceId = split[0];
String properties = split[1].substring(0, split[1].length() - 1);
Block block = Block.fromNamespaceId(namespaceId);
HashMap<String, String> propertiesMap = new HashMap<>();
for(String property : properties.split(",")) {
String[] kv = property.split("=");
propertiesMap.put(kv[0].strip(), kv[1].strip());
}
assert block != null;
this.block = block.withProperties(propertiesMap);
}
@Override
public boolean matches(BlockState other) {
return ((MinestomBlockState) other).block.compare(block);
}
@Override
public <T extends Comparable<T>> boolean has(Property<T> property) {
return false;
}
@Override
public <T extends Comparable<T>> T get(Property<T> property) {
return null;
}
@Override
public <T extends Comparable<T>> BlockState set(Property<T> property, T value) {
return null;
}
@Override
public BlockType getBlockType() {
return new MinestomBlockType(block);
}
@Override
public String getAsString(boolean properties) {
String name = block.namespace().asString();
if(!properties || block.properties().isEmpty()) {
return name;
}
name += "[" + block.properties().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(
Collectors.joining(",")) + "]";
return name;
}
@Override
public boolean isAir() {
return block.isAir();
}
@Override
public Object getHandle() {
return block;
}
@Override
public int hashCode() {
return Objects.hashCode(block.id());
}
}

View File

@ -0,0 +1,48 @@
package com.dfsek.terra.minestom.block;
import com.dfsek.terra.api.block.BlockType;
import com.dfsek.terra.api.block.state.BlockState;
import net.minestom.server.instance.block.Block;
public class MinestomBlockType implements BlockType {
private final Block block;
public MinestomBlockType(Block block) {
this.block = block;
}
@Override
public BlockState getDefaultState() {
return new MinestomBlockState(block);
}
@Override
public boolean isSolid() {
return block.isSolid();
}
@Override
public boolean isWater() {
return block.isLiquid();
}
@Override
public Object getHandle() {
return block;
}
@Override
public int hashCode() {
return block.id();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof MinestomBlockType other) {
return block.id() == other.block.id();
}
return false;
}
}

View File

@ -0,0 +1,58 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.chunk.generation.ProtoChunk;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.UnitModifier;
import org.jetbrains.annotations.NotNull;
public class CachedChunk implements ProtoChunk {
private final int minHeight;
private final int maxHeight;
private final Block[][][] blocks;
public CachedChunk(int minHeight, int maxHeight) {
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.blocks = new Block[16][maxHeight - minHeight + 1][16];
for(int x = 0; x < 16; x++) {
for(int z = 0; z < 16; z++) {
for(int y = 0; y < maxHeight - minHeight + 1; y++) {
blocks[x][y][z] = Block.AIR;
}
}
}
}
public void writeRelative(UnitModifier modifier) {
modifier.setAllRelative((x, y, z) -> blocks[x][y][z]);
}
@Override
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
Block block = (Block) blockState.getHandle();
if(block == null) return;
blocks[x][y - minHeight][z] = block;
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return new MinestomBlockState(blocks[x][y - minHeight][z]);
}
@Override
public Object getHandle() {
return blocks;
}
@Override
public int getMaxHeight() {
return maxHeight;
}
}

View File

@ -0,0 +1,49 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.util.generic.pair.Pair;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GeneratedChunkCache {
private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class);
private final LoadingCache<Pair<Integer, Integer>, CachedChunk> cache;
private final DimensionType dimensionType;
private final ChunkGenerator generator;
private final ServerWorld world;
private final BiomeProvider biomeProvider;
public GeneratedChunkCache(DimensionType dimensionType, ChunkGenerator generator, ServerWorld world) {
this.dimensionType = dimensionType;
this.generator = generator;
this.world = world;
this.biomeProvider = world.getBiomeProvider();
this.cache = Caffeine.newBuilder().maximumSize(128).recordStats().build(
(Pair<Integer, Integer> key) -> generateChunk(key.getLeft(), key.getRight()));
}
private CachedChunk generateChunk(int x, int z) {
CachedChunk chunk = new CachedChunk(dimensionType.minY(), dimensionType.maxY());
generator.generateChunkData(chunk, world, biomeProvider, x, z);
return chunk;
}
public void displayStats() {
CacheStats stats = cache.stats();
log.info("Avg load time: {}ms | Hit rate: {}% | Load Count: {}", stats.averageLoadPenalty(), stats.hitRate() * 100,
stats.loadCount());
}
public CachedChunk at(int x, int z) {
return cache.get(Pair.of(x, z));
}
}

View File

@ -0,0 +1,51 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
public class TerraMinestomChunk implements Chunk {
private net.minestom.server.instance.Chunk delegate;
private final ServerWorld world;
public TerraMinestomChunk(net.minestom.server.instance.Chunk delegate, ServerWorld world) {
this.delegate = delegate;
this.world = world;
}
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
delegate.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return new MinestomBlockState(delegate.getBlock(x, y, z));
}
@Override
public int getX() {
return delegate.getChunkX();
}
@Override
public int getZ() {
return delegate.getChunkZ();
}
@Override
public ServerWorld getWorld() {
return world;
}
@Override
public Object getHandle() {
return delegate;
}
}

View File

@ -0,0 +1,14 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.minestom.api.EntityFactory;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
public class DefaultEntityFactory implements EntityFactory {
@Override
public Entity createEntity(EntityType type) {
return new Entity(type);
}
}

View File

@ -0,0 +1,68 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import net.minestom.server.coordinate.Pos;
public class DeferredMinestomEntity implements Entity {
private final EntityType type;
private double x;
private double y;
private double z;
private TerraMinestomWorld world;
public DeferredMinestomEntity(double x, double y, double z, EntityType type, TerraMinestomWorld world) {
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.world = world;
}
@Override
public Vector3 position() {
return Vector3.of(x, y, z);
}
public Pos pos() {
return new Pos(x, y, z);
}
@Override
public void position(Vector3 position) {
x = position.getX();
y = position.getY();
z = position.getZ();
}
@Override
public void world(ServerWorld world) {
this.world = (TerraMinestomWorld) world;
}
@Override
public ServerWorld world() {
return world;
}
@Override
public Object getHandle() {
return this;
}
public void spawn() {
int chunkX = (int) x >> 4;
int chunkZ = (int) z >> 4;
if(!world.getHandle().isChunkLoaded(chunkX, chunkZ)) {
return;
}
MinestomEntity.spawn(x, y, z, type, world);
}
}

View File

@ -0,0 +1,55 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.minestom.MinestomAdapter;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;
public class MinestomEntity implements com.dfsek.terra.api.entity.Entity {
private final Entity delegate;
private final TerraMinestomWorld world;
public MinestomEntity(Entity delegate, TerraMinestomWorld world) {
this.delegate = delegate;
this.world = world;
}
@Override
public Vector3 position() {
return MinestomAdapter.adapt(delegate.getPosition());
}
@Override
public void position(Vector3 position) {
delegate.teleport(MinestomAdapter.adapt(position));
}
@Override
public void world(ServerWorld world) {
delegate.setInstance(((TerraMinestomWorld) world).getHandle());
}
@Override
public ServerWorld world() {
return world;
}
@Override
public Object getHandle() {
return delegate;
}
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());
entity.setInstance(instance, new Pos(x, y, z));
return new MinestomEntity(entity, world);
}
}

View File

@ -0,0 +1,18 @@
package com.dfsek.terra.minestom.entity;
import net.minestom.server.entity.EntityType;
public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType {
private final EntityType delegate;
public MinestomEntityType(String id) {
delegate = EntityType.fromNamespaceId(id);
}
@Override
public EntityType getHandle() {
return delegate;
}
}

View File

@ -0,0 +1,51 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.ItemStack;
import com.dfsek.terra.api.inventory.item.Enchantment;
import net.minestom.server.MinecraftServer;
import net.minestom.server.item.Material;
import net.minestom.server.utils.NamespaceID;
import java.util.Objects;
public class MinestomEnchantment implements Enchantment {
private final net.minestom.server.item.enchant.Enchantment delegate;
private final String id;
public MinestomEnchantment(net.minestom.server.item.enchant.Enchantment delegate) {
this.delegate = delegate;
id = Objects.requireNonNull(delegate.registry()).raw();
}
public MinestomEnchantment(String id) {
this.delegate = MinecraftServer.getEnchantmentRegistry().get(NamespaceID.from(id));
this.id = id;
}
@Override
public boolean canEnchantItem(ItemStack itemStack) {
return delegate.supportedItems().contains((Material) itemStack.getType().getHandle());
}
@Override
public boolean conflictsWith(Enchantment other) {
return delegate.exclusiveSet().contains(NamespaceID.from(((MinestomEnchantment) other).id));
}
@Override
public String getID() {
return id;
}
@Override
public int getMaxLevel() {
return delegate.maxLevel();
}
@Override
public net.minestom.server.item.enchant.Enchantment getHandle() {
return delegate;
}
}

View File

@ -0,0 +1,28 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import net.minestom.server.MinecraftServer;
import java.util.Set;
import java.util.stream.Collectors;
public class MinestomItemHandle implements ItemHandle {
@Override
public Item createItem(String data) {
return new MinestomMaterial(data);
}
@Override
public Enchantment getEnchantment(String id) {
return new MinestomEnchantment(id);
}
@Override
public Set<Enchantment> getEnchantments() {
return MinecraftServer.getEnchantmentRegistry().values().stream().map(MinestomEnchantment::new).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import com.dfsek.terra.api.inventory.item.ItemMeta;
import java.util.HashMap;
import java.util.Map;
public class MinestomItemMeta implements ItemMeta {
private final HashMap<Enchantment, Integer> enchantments;
public MinestomItemMeta(HashMap<Enchantment, Integer> enchantments) {
this.enchantments = enchantments;
}
@Override
public void addEnchantment(Enchantment enchantment, int level) {
enchantments.put(enchantment, level);
}
@Override
public Map<Enchantment, Integer> getEnchantments() {
return enchantments;
}
@Override
public Object getHandle() {
return enchantments;
}
}

View File

@ -0,0 +1,72 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import com.dfsek.terra.api.inventory.item.ItemMeta;
import net.minestom.server.MinecraftServer;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.component.EnchantmentList;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.registry.DynamicRegistry.Key;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Objects;
public class MinestomItemStack implements com.dfsek.terra.api.inventory.ItemStack {
private ItemStack base;
public MinestomItemStack(net.minestom.server.item.@NotNull ItemStack base) {
this.base = base;
}
@Override
public Object getHandle() {
return base;
}
@Override
public int getAmount() {
return base.amount();
}
@Override
public void setAmount(int i) {
base = base.withAmount(i);
}
@Override
public Item getType() {
return new MinestomMaterial(base.material());
}
@Override
public ItemMeta getItemMeta() {
HashMap<Enchantment, Integer> enchantments = new HashMap<>();
EnchantmentList enchantmentList = base.get(ItemComponent.ENCHANTMENTS);
if(enchantmentList != null) {
enchantmentList.enchantments().forEach((enchantmentKey, integer) -> {
enchantments.put(
new MinestomEnchantment(Objects.requireNonNull(MinecraftServer.getEnchantmentRegistry().get(enchantmentKey))), integer);
});
}
return new MinestomItemMeta(enchantments);
}
@Override
public void setItemMeta(ItemMeta meta) {
HashMap<Key<net.minestom.server.item.enchant.Enchantment>, Integer> enchantments = new HashMap<>();
DynamicRegistry<net.minestom.server.item.enchant.Enchantment> registry = MinecraftServer.getEnchantmentRegistry();
meta.getEnchantments().forEach((key, value) -> {
MinestomEnchantment enchantment = (MinestomEnchantment) key;
enchantments.put(registry.getKey(enchantment.getHandle()), value);
});
EnchantmentList list = new EnchantmentList(enchantments);
base = base.with(ItemComponent.ENCHANTMENTS, list);
}
}

View File

@ -0,0 +1,34 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.ItemStack;
import net.minestom.server.item.Material;
public class MinestomMaterial implements Item {
private final Material delegate;
public MinestomMaterial(Material delegate) {
this.delegate = delegate;
}
public MinestomMaterial(String id) {
this.delegate = Material.fromNamespaceId(id);
}
@Override
public ItemStack newItemStack(int amount) {
return new MinestomItemStack(net.minestom.server.item.ItemStack.builder(delegate).amount(amount).build());
}
@Override
public double getMaxDurability() {
return 0;
}
@Override
public Material getHandle() {
return delegate;
}
}

View File

@ -0,0 +1,68 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
import com.dfsek.terra.api.world.chunk.generation.util.GeneratorWrapper;
import com.dfsek.terra.minestom.chunk.CachedChunk;
import com.dfsek.terra.minestom.chunk.GeneratedChunkCache;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
public class MinestomChunkGeneratorWrapper implements Generator, GeneratorWrapper {
private final GeneratedChunkCache cache;
private ChunkGenerator generator;
private final TerraMinestomWorld world;
private ConfigPack pack;
public MinestomChunkGeneratorWrapper(ChunkGenerator generator, TerraMinestomWorld world, ConfigPack pack) {
this.generator = generator;
this.world = world;
this.pack = pack;
this.cache = new GeneratedChunkCache(world.getDimensionType(), generator, world);
}
public ChunkGenerator getGenerator() {
return generator;
}
@Override
public void generate(@NotNull GenerationUnit unit) {
Point start = unit.absoluteStart();
int x = start.chunkX();
int z = start.chunkZ();
CachedChunk chunk = cache.at(x, z);
chunk.writeRelative(unit.modifier());
unit.fork(setter -> {
MinestomProtoWorld protoWorld = new MinestomProtoWorld(cache, x, z, world, setter);
for(GenerationStage stage : world.getPack().getStages()) {
stage.populate(protoWorld);
}
});
}
public ConfigPack getPack() {
return pack;
}
public void setPack(ConfigPack pack) {
this.pack = pack;
this.generator = pack.getGeneratorProvider().newInstance(pack);
}
public void displayStats() {
cache.displayStats();
}
@Override
public ChunkGenerator getHandle() {
return generator;
}
}

View File

@ -0,0 +1,111 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
import com.dfsek.terra.minestom.chunk.CachedChunk;
import com.dfsek.terra.minestom.chunk.GeneratedChunkCache;
import com.dfsek.terra.minestom.entity.DeferredMinestomEntity;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.Block.Setter;
public class MinestomProtoWorld implements ProtoWorld {
private final GeneratedChunkCache cache;
private final int x;
private final int z;
private final TerraMinestomWorld world;
private final Setter modifier;
public MinestomProtoWorld(GeneratedChunkCache cache, int x, int z, TerraMinestomWorld world, Setter modifier) {
this.cache = cache;
this.x = x;
this.z = z;
this.world = world;
this.modifier = modifier;
}
@Override
public int centerChunkX() {
return x;
}
@Override
public int centerChunkZ() {
return z;
}
@Override
public ServerWorld getWorld() {
return world;
}
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
modifier.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public Entity spawnEntity(double x, double y, double z, EntityType entityType) {
TerraMinestomWorld world = this.world;
DeferredMinestomEntity entity = new DeferredMinestomEntity(x, y, z, entityType, world);
world.enqueue(entity.pos(), (chunk) -> entity.spawn());
return entity;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int chunkX = x >> 4;
int chunkZ = z >> 4;
CachedChunk chunk = cache.at(chunkX, chunkZ);
return chunk.getBlock(x & 15, y, z & 15);
}
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return world.getBlockEntity(x, y, z);
}
@Override
public ChunkGenerator getGenerator() {
return world.getGenerator();
}
@Override
public BiomeProvider getBiomeProvider() {
return world.getBiomeProvider();
}
@Override
public ConfigPack getPack() {
return world.getPack();
}
@Override
public long getSeed() {
return world.getSeed();
}
@Override
public int getMaxHeight() {
return world.getMaxHeight();
}
@Override
public int getMinHeight() {
return world.getMinHeight();
}
@Override
public Object getHandle() {
return world;
}
}

View File

@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.handle.WorldHandle;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import com.dfsek.terra.minestom.entity.MinestomEntityType;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
public class MinestomWorldHandle implements WorldHandle {
private static final MinestomBlockState AIR = new MinestomBlockState(Block.AIR);
@Override
public @NotNull BlockState createBlockState(@NotNull String data) {
return new MinestomBlockState(data);
}
@Override
public @NotNull BlockState air() {
return AIR;
}
@Override
public @NotNull EntityType getEntity(@NotNull String id) {
return new MinestomEntityType(id);
}
}

View File

@ -0,0 +1,130 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.info.WorldProperties;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import com.dfsek.terra.minestom.api.EntityFactory;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import com.dfsek.terra.minestom.entity.MinestomEntity;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.BlockVec;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.world.DimensionType;
import java.util.function.Consumer;
public final class TerraMinestomWorld implements ServerWorld, WorldProperties {
private final Instance instance;
private final ConfigPack pack;
private final long seed;
private final DimensionType dimensionType;
private final MinestomChunkGeneratorWrapper wrapper;
private final EntityFactory entityFactory;
private final BlockEntityFactory blockEntityFactory;
public TerraMinestomWorld(Instance instance, ConfigPack pack, long seed, EntityFactory entityFactory,
BlockEntityFactory blockEntityFactory) {
this.instance = instance;
this.pack = pack;
this.seed = seed;
this.dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType());
this.blockEntityFactory = blockEntityFactory;
this.wrapper = new MinestomChunkGeneratorWrapper(pack.getGeneratorProvider().newInstance(pack), this, pack);
this.entityFactory = entityFactory;
instance.setGenerator(this.wrapper);
}
@Override
public Chunk getChunkAt(int x, int z) {
return null;
}
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
instance.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public Entity spawnEntity(double x, double y, double z, EntityType entityType) {
return MinestomEntity.spawn(x, y, z, entityType, this);
}
public void displayStats() {
wrapper.displayStats();
}
@Override
public BlockState getBlockState(int x, int y, int z) {
return new MinestomBlockState(instance.getBlock(x, y, z));
}
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return blockEntityFactory.createBlockEntity(new BlockVec(x, y, z));
}
@Override
public ChunkGenerator getGenerator() {
return wrapper.getGenerator();
}
@Override
public BiomeProvider getBiomeProvider() {
return pack.getBiomeProvider();
}
@Override
public ConfigPack getPack() {
return pack;
}
@Override
public long getSeed() {
return seed;
}
@Override
public int getMaxHeight() {
return dimensionType.maxY();
}
@Override
public int getMinHeight() {
return dimensionType.minY();
}
@Override
public Instance getHandle() {
return instance;
}
public DimensionType getDimensionType() {
return dimensionType;
}
public EntityFactory getEntityFactory() {
return entityFactory;
}
public void enqueue(Point position, Consumer<net.minestom.server.instance.Chunk> action) {
instance.loadChunk(position.chunkX(), position.chunkZ()).thenAccept(action);
}
}

View File

@ -0,0 +1,75 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.minestom.MinestomPlatform;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import com.dfsek.terra.minestom.api.EntityFactory;
import com.dfsek.terra.minestom.block.DefaultBlockEntityFactory;
import com.dfsek.terra.minestom.entity.DefaultEntityFactory;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.Instance;
import java.util.Random;
import java.util.function.Function;
public class TerraMinestomWorldBuilder {
private final Instance instance;
private ConfigPack pack;
private long seed = new Random().nextLong();
private EntityFactory entityFactory = new DefaultEntityFactory();
private BlockEntityFactory blockEntityFactory = new DefaultBlockEntityFactory();
private TerraMinestomWorldBuilder(Instance instance) { this.instance = instance; }
public static TerraMinestomWorldBuilder from(Instance instance) {
return new TerraMinestomWorldBuilder(instance);
}
public static TerraMinestomWorldBuilder builder() {
return new TerraMinestomWorldBuilder(MinecraftServer.getInstanceManager().createInstanceContainer());
}
public TerraMinestomWorldBuilder pack(ConfigPack pack) {
this.pack = pack;
return this;
}
public TerraMinestomWorldBuilder packById(String id) {
this.pack = MinestomPlatform.getInstance().getConfigRegistry().getByID(id).orElseThrow();
return this;
}
public TerraMinestomWorldBuilder findPack(Function<CheckedRegistry<ConfigPack>, ConfigPack> fn) {
this.pack = fn.apply(MinestomPlatform.getInstance().getConfigRegistry());
return this;
}
public TerraMinestomWorldBuilder defaultPack() {
return this.packById("OVERWORLD");
}
public TerraMinestomWorldBuilder seed(long seed) {
this.seed = seed;
return this;
}
public TerraMinestomWorldBuilder entityFactory(EntityFactory factory) {
this.entityFactory = factory;
return this;
}
public TerraMinestomWorldBuilder blockEntityFactory(BlockEntityFactory factory) {
this.blockEntityFactory = factory;
return this;
}
public TerraMinestomWorld attach() {
return new TerraMinestomWorld(instance, pack, seed, entityFactory, blockEntityFactory);
}
}

View File

@ -21,6 +21,7 @@ includeImmediateChildren(file("platforms"), "platform")
includeImmediateChildren(file("platforms/bukkit/nms"), "Bukkit NMS")
include(":platforms:bukkit:common")
include(":platforms:minestom:example")
pluginManagement {
repositories {