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() configureDistribution()
} }
project(":platforms:bukkit:common").configureDistribution() project(":platforms:bukkit:common").configureDistribution()
project(":platforms:minestom:example").configureDistribution()
forSubProjects(":common:addons") { forSubProjects(":common:addons") {
apply(plugin = "com.gradleup.shadow") apply(plugin = "com.gradleup.shadow")

View File

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

View File

@ -81,4 +81,8 @@ object Versions {
object Allay { object Allay {
const val api = "0.1.3" 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") includeImmediateChildren(file("platforms/bukkit/nms"), "Bukkit NMS")
include(":platforms:bukkit:common") include(":platforms:bukkit:common")
include(":platforms:minestom:example")
pluginManagement { pluginManagement {
repositories { repositories {