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 26 commits into
base: ver/6.6.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 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
3a7d1a6
feat: use system property for configurable data folder path
Bloeckchengrafik Jan 25, 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
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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

/**
* Represents a type of block
* <p>
* Important! You <b>need</b> to implement a proper equals() and hashCode() here in
* your platform implementation for surface decorations to show up
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
*/
public interface BlockType extends Handle {
/**
Expand Down
31 changes: 31 additions & 0 deletions platforms/minestom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
application
}

val javaMainClass = "com.dfsek.terra.minestom.TerraMinestomExample"

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)

implementation("net.minestom", "minestom-snapshots", Versions.Minestom.minestom)
implementation("org.slf4j", "slf4j-simple", Versions.Libraries.slf4j)
}

tasks.withType<Jar> {
entryCompression = ZipEntryCompression.STORED
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik somewhere you need to depend on the installAddons task.

see:

tasks {
remapJar {
dependsOn("installAddons")
injectAccessWidener.set(true)
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("${rootProject.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}-fabric-${project.version}.jar")
}
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed by the latest commit

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,80 @@
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.MinestomWorldHandle;

import org.jetbrains.annotations.NotNull;

import java.io.File;
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved


public final class MinestomPlatform extends AbstractPlatform {
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() {
return false;
}
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved

@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,158 @@
package com.dfsek.terra.minestom;

import com.dfsek.terra.minestom.api.filter.ChunkFilter;
import com.dfsek.terra.minestom.api.filter.EvenChunkFilter;
import com.dfsek.terra.minestom.api.filter.NoFeaturesFilter;
import com.dfsek.terra.minestom.api.filter.NoTerrainFilter;
import com.dfsek.terra.minestom.api.filter.SpecificChunkFilter;
import com.dfsek.terra.minestom.api.filter.XFilter;
import com.dfsek.terra.minestom.api.filter.ZFilter;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import com.dfsek.terra.minestom.world.TerraMinestomWorldBuilder;

import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentLiteral;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
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.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;


public class TerraMinestomExample {
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
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(ChunkFilter filter) {
world = TerraMinestomWorldBuilder.from(instance)
.defaultPack()
// .seed(0)
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
.filtered(filter)
.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(null);
example.preloadWorldAndMeasure();
example.addScheduler();
example.addListeners();
example.addCommands();
example.bind();
}

public class RegenerateCommand extends Command {
public RegenerateCommand() {
super("regenerate");

ArgumentInteger cx = new ArgumentInteger("cx");
ArgumentInteger cz = new ArgumentInteger("cz");

setDefaultExecutor((sender, context) -> regenerate(null));
addSyntax((sender, context) -> regenerate(new NoFeaturesFilter()), new ArgumentLiteral("noFeatures"));
addSyntax((sender, context) -> regenerate(new NoTerrainFilter()), new ArgumentLiteral("noTerrain"));
addSyntax((sender, context) -> regenerate(new EvenChunkFilter()), new ArgumentLiteral("evenChunks"));
addSyntax((sender, context) -> regenerate(new SpecificChunkFilter(context.get(cx), context.get(cz))), new ArgumentLiteral("chunk"), cx, cz);
addSyntax((sender, context) -> regenerate(new XFilter(context.get(cx))), new ArgumentLiteral("x"), cx);
addSyntax((sender, context) -> regenerate(new ZFilter(context.get(cz))), new ArgumentLiteral("z"), cz);
}

private void regenerate(@Nullable ChunkFilter filter) {
if (filter == null) {
instance.sendMessage(Component.text("Regenerating world without filter "));
} else {
instance.sendMessage(Component.text("Regenerating world with filter " + filter.getClass().getSimpleName()));
}
createNewInstance();
attachTerra(filter);
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,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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.dfsek.terra.minestom.api.filter;

/**
* This interface defines a filter for determining whether terrain and features
* should be placed during chunk generation. Implementations of this interface
* can apply custom logic to selectively control terrain and feature placement
* in specific chunks for debugging purposes.
*/
public interface ChunkFilter {
boolean shouldPlaceTerrain(int x, int z);
boolean shouldPlaceFeatures(int x, int z);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.dfsek.terra.minestom.api.filter;

public class EvenChunkFilter implements ChunkFilter {
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
@Override
public boolean shouldPlaceTerrain(int x, int z) {
return x % 2 == 0 && z % 2 == 0;
}

@Override
public boolean shouldPlaceFeatures(int x, int z) {
return x % 2 == 0 && z % 2 == 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.dfsek.terra.minestom.api.filter;

public class NoFeaturesFilter implements ChunkFilter {
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
@Override
public boolean shouldPlaceTerrain(int x, int z) {
return true;
}

@Override
public boolean shouldPlaceFeatures(int x, int z) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.dfsek.terra.minestom.api.filter;

public class NoTerrainFilter implements ChunkFilter {
Bloeckchengrafik marked this conversation as resolved.
Show resolved Hide resolved
@Override
public boolean shouldPlaceTerrain(int x, int z) {
return false;
}

@Override
public boolean shouldPlaceFeatures(int x, int z) {
return true;
}
}
Loading