Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Minestom platform #485

Open
wants to merge 25 commits into
base: ver/6.6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
983cb1f
feat: base terrain generation with minestom
Bloeckchengrafik Dec 28, 2024
7288373
feat: cache generated chunks
Bloeckchengrafik Dec 28, 2024
f953c50
feat: start generating features
Bloeckchengrafik Dec 28, 2024
fa6e0e0
feat: feature generation but only in even chunks
Bloeckchengrafik Dec 28, 2024
bfc1c32
feat: surface decoration generation but only in even chunks
Bloeckchengrafik Dec 29, 2024
ac66fc7
fix: different populators now know about each other
Bloeckchengrafik Dec 29, 2024
3a28551
fix: generate populators in every chunk
Bloeckchengrafik Dec 29, 2024
d5ab3e2
feat: better example server
Bloeckchengrafik Jan 1, 2025
b4ab376
Merge branch 'ver/6.6.0' into feat/platform/minestom
Bloeckchengrafik Jan 1, 2025
d1f881c
feat: minestom entities
Bloeckchengrafik Jan 1, 2025
23b846e
feat: get faster generation times
Bloeckchengrafik Jan 1, 2025
6279638
feat: implement platform biome
Bloeckchengrafik Jan 2, 2025
1d8a208
feat: implement platform enchantments
Bloeckchengrafik Jan 2, 2025
4f668ad
feat: add chunk filtering for debugging and remove feature caching
Bloeckchengrafik Jan 2, 2025
cb2841e
fix: Adjust progress bar update interval to every 60 ticks.
Bloeckchengrafik Jan 2, 2025
ff153dd
feat: allow external block entity implementations
Bloeckchengrafik Jan 2, 2025
c848c33
fix: add lighting engine to test server
Bloeckchengrafik Jan 3, 2025
5ba5d6e
revert: block type implementation hint
Bloeckchengrafik Jan 3, 2025
35bdc99
build: make available via maven repo
Bloeckchengrafik Jan 3, 2025
7711e67
feat: support reload
Bloeckchengrafik Jan 3, 2025
992ae59
fix: Implement GeneratorWrapper interface in Minestom wrapper
Bloeckchengrafik Jan 3, 2025
d0bc006
chore: reformat
Bloeckchengrafik Jan 4, 2025
aecc003
fix: minestom and slf4j as transitive dependencies
Bloeckchengrafik Jan 5, 2025
810d10a
fix: move minestom example to own module
Bloeckchengrafik Jan 5, 2025
7b29d25
fix: remove unused application plugin from Minestom platform build sc…
Bloeckchengrafik Jan 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ afterEvaluate {
configureDistribution()
}
project(":platforms:bukkit:common").configureDistribution()
project(":platforms:minestom:example").configureDistribution()
forSubProjects(":common:addons") {
apply(plugin = "com.gradleup.shadow")

Expand Down
51 changes: 32 additions & 19 deletions buildSrc/src/main/kotlin/DistributionConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -48,25 +69,17 @@ fun Project.configureDistribution() {
doLast {
// https://github.com/johnrengelman/shadow/issues/111
val dest = tasks.named<ShadowJar>("shadowJar").get().archiveFile.get().path


FileSystems.newFileSystem(dest, mapOf("create" to "false"), null).use { fs ->
forSubProjects(":common:addons") {
val jar = getJarTask()

logger.info("Packaging addon ${jar.archiveFileName.get()} to $dest. size: ${jar.archiveFile.get().asFile.length() / 1024}KB")

val boot = if (extra.has("bootstrap") && extra.get("bootstrap") as Boolean) "bootstrap/" else ""
val addonPath = fs.getPath("/addons/$boot${jar.archiveFileName.get()}")

if (!addonPath.exists()) {
addonPath.parent.createDirectories()
addonPath.createFile()
jar.archiveFile.get().asFile.toPath().copyTo(addonPath, overwrite = true)
}

}
}
installAddonsInto(dest)
}
}

tasks.create("installAddonsIntoDefaultJar") {
group = "terra"
dependsOn(compileAddons)

doLast {
val dest = tasks.named<Jar>("jar").get().archiveFile.get().path
installAddonsInto(dest)
}
}

Expand Down
4 changes: 4 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,8 @@ object Versions {
object Allay {
const val api = "0114e0b290"
}

object Minestom {
const val minestom = "187931e50b"
}
}
11 changes: 11 additions & 0 deletions platforms/minestom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")
}
28 changes: 28 additions & 0 deletions platforms/minestom/example/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -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))
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<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() {
File file = new File("./terra/");
if(!file.exists()) file.mkdirs();
return file;
}


public static MinestomPlatform getInstance() {
if(INSTANCE == null) {
INSTANCE = new MinestomPlatform();
}
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading