diff --git a/build.gradle.kts b/build.gradle.kts index 2d6105c73c..44ca08e955 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,6 +44,7 @@ afterEvaluate { configureDistribution() } project(":platforms:bukkit:common").configureDistribution() + project(":platforms:minestom:example").configureDistribution() forSubProjects(":common:addons") { apply(plugin = "com.gradleup.shadow") diff --git a/buildSrc/src/main/kotlin/DistributionConfig.kt b/buildSrc/src/main/kotlin/DistributionConfig.kt index a31588a518..72a4146f63 100644 --- a/buildSrc/src/main/kotlin/DistributionConfig.kt +++ b/buildSrc/src/main/kotlin/DistributionConfig.kt @@ -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").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").get().archiveFile.get().path + installAddonsInto(dest) } } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 1f15525129..c5221b8c7b 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -81,4 +81,8 @@ object Versions { object Allay { const val api = "0114e0b290" } + + object Minestom { + const val minestom = "187931e50b" + } } \ No newline at end of file diff --git a/platforms/minestom/build.gradle.kts b/platforms/minestom/build.gradle.kts new file mode 100644 index 0000000000..59b7752207 --- /dev/null +++ b/platforms/minestom/build.gradle.kts @@ -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") +} diff --git a/platforms/minestom/example/build.gradle.kts b/platforms/minestom/example/build.gradle.kts new file mode 100644 index 0000000000..31d903136e --- /dev/null +++ b/platforms/minestom/example/build.gradle.kts @@ -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 { + 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()) 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 new file mode 100644 index 0000000000..1cf95a226f --- /dev/null +++ b/platforms/minestom/example/src/main/java/com/dfsek/terra/minestom/TerraMinestomExample.java @@ -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)) + ); + } + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomAdapter.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomAdapter.java new file mode 100644 index 0000000000..697997c0bf --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomAdapter.java @@ -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()); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomPlatform.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomPlatform.java new file mode 100644 index 0000000000..5dd149133a --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/MinestomPlatform.java @@ -0,0 +1,92 @@ +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) (annotatedType, o, configLoader, depthTracker) -> new MinestomEntityType((String) o)) + .registerLoader(BlockState.class, (TypeLoader) (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() { + File file = new File("./terra/"); + if(!file.exists()) file.mkdirs(); + return file; + } + + + public static MinestomPlatform getInstance() { + if(INSTANCE == null) { + INSTANCE = new MinestomPlatform(); + } + return INSTANCE; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/BlockEntityFactory.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/BlockEntityFactory.java new file mode 100644 index 0000000000..c5fd33ca1a --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/BlockEntityFactory.java @@ -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); +} 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 new file mode 100644 index 0000000000..5cfbecd8df --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/api/EntityFactory.java @@ -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); +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiome.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiome.java new file mode 100644 index 0000000000..a4c146359c --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiome.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiomeLoader.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiomeLoader.java new file mode 100644 index 0000000000..9cb3eb960e --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/biome/MinestomBiomeLoader.java @@ -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 { + private final DynamicRegistry 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); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/DefaultBlockEntityFactory.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/DefaultBlockEntityFactory.java new file mode 100644 index 0000000000..dbcbf2833b --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/DefaultBlockEntityFactory.java @@ -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; + } +} 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 new file mode 100644 index 0000000000..01bad55faf --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockState.java @@ -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 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 > boolean has(Property property) { + return false; + } + + @Override + public > T get(Property property) { + return null; + } + + @Override + public > BlockState set(Property 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()); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockType.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockType.java new file mode 100644 index 0000000000..8e4bd4360e --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/block/MinestomBlockType.java @@ -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; + } +} 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 new file mode 100644 index 0000000000..ed9ad86220 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/CachedChunk.java @@ -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; + } +} 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 new file mode 100644 index 0000000000..018c82c726 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/GeneratedChunkCache.java @@ -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, 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 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)); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/TerraMinestomChunk.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/TerraMinestomChunk.java new file mode 100644 index 0000000000..fdcf01aa73 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/chunk/TerraMinestomChunk.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DefaultEntityFactory.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DefaultEntityFactory.java new file mode 100644 index 0000000000..23226fc99c --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DefaultEntityFactory.java @@ -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); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DeferredMinestomEntity.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DeferredMinestomEntity.java new file mode 100644 index 0000000000..4a053ec2d6 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/DeferredMinestomEntity.java @@ -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); + } +} 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 new file mode 100644 index 0000000000..c46a6a1d31 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntity.java @@ -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); + } +} 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 new file mode 100644 index 0000000000..60fbac5d90 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/entity/MinestomEntityType.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomEnchantment.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomEnchantment.java new file mode 100644 index 0000000000..0ef720b31a --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomEnchantment.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemHandle.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemHandle.java new file mode 100644 index 0000000000..c31983ed3b --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemHandle.java @@ -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 getEnchantments() { + return MinecraftServer.getEnchantmentRegistry().values().stream().map(MinestomEnchantment::new).collect(Collectors.toSet()); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemMeta.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemMeta.java new file mode 100644 index 0000000000..861a9549c6 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemMeta.java @@ -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 enchantments; + + public MinestomItemMeta(HashMap enchantments) { + this.enchantments = enchantments; + } + + @Override + public void addEnchantment(Enchantment enchantment, int level) { + enchantments.put(enchantment, level); + } + + @Override + public Map getEnchantments() { + return enchantments; + } + + @Override + public Object getHandle() { + return enchantments; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemStack.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemStack.java new file mode 100644 index 0000000000..2b488a8c80 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomItemStack.java @@ -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 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, Integer> enchantments = new HashMap<>(); + DynamicRegistry 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); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomMaterial.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomMaterial.java new file mode 100644 index 0000000000..6bd0a19089 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/item/MinestomMaterial.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomChunkGeneratorWrapper.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomChunkGeneratorWrapper.java new file mode 100644 index 0000000000..81529db487 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomChunkGeneratorWrapper.java @@ -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; + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomProtoWorld.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomProtoWorld.java new file mode 100644 index 0000000000..65ec226545 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomProtoWorld.java @@ -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; + } +} 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 new file mode 100644 index 0000000000..98429ab6d0 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/MinestomWorldHandle.java @@ -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); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorld.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorld.java new file mode 100644 index 0000000000..4f4aef0919 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorld.java @@ -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 action) { + instance.loadChunk(position.chunkX(), position.chunkZ()).thenAccept(action); + } +} diff --git a/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorldBuilder.java b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorldBuilder.java new file mode 100644 index 0000000000..46ae1691c8 --- /dev/null +++ b/platforms/minestom/src/main/java/com/dfsek/terra/minestom/world/TerraMinestomWorldBuilder.java @@ -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, 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); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c5bed6f737..86bb41bfbb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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 {