Initial commit: JS-powered Minestom server with hot-reloading and Gitea Action
Some checks failed
Build JStom / build (push) Has been cancelled
Some checks failed
Build JStom / build (push) Has been cancelled
This commit is contained in:
74
src/main/java/net/jstom/Main.java
Normal file
74
src/main/java/net/jstom/Main.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package net.jstom;
|
||||
|
||||
import net.jstom.script.ScriptManager;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.event.GlobalEventHandler;
|
||||
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.LightingChunk;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
// Initialize server
|
||||
MinecraftServer minecraftServer = MinecraftServer.init();
|
||||
|
||||
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
|
||||
// Create the instance
|
||||
InstanceContainer instanceContainer = instanceManager.createInstanceContainer();
|
||||
// Set the ChunkGenerator
|
||||
instanceContainer.setGenerator(unit ->
|
||||
unit.modifier().fillHeight(0, 40, Block.GRASS_BLOCK)
|
||||
);
|
||||
instanceContainer.setChunkSupplier(LightingChunk::new);
|
||||
|
||||
// Add an event callback to specify the spawning instance (and the spawn position)
|
||||
GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler();
|
||||
globalEventHandler.addListener(AsyncPlayerConfigurationEvent.class, event -> {
|
||||
final Player player = event.getPlayer();
|
||||
event.setSpawningInstance(instanceContainer);
|
||||
player.setRespawnPoint(new Pos(0, 42, 0));
|
||||
});
|
||||
|
||||
// Initialize Script Manager
|
||||
ScriptManager scriptManager = new ScriptManager(new File("scripts"));
|
||||
scriptManager.load();
|
||||
|
||||
// Console thread for commands
|
||||
new Thread(() -> {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
String[] parts = line.split(" ");
|
||||
String command = parts[0];
|
||||
|
||||
if (command.equalsIgnoreCase("reload")) {
|
||||
if (parts.length > 1) {
|
||||
String fileName = parts[1];
|
||||
System.out.println("Reloading " + fileName + "...");
|
||||
scriptManager.reload(fileName);
|
||||
} else {
|
||||
System.out.println("Reloading all scripts...");
|
||||
scriptManager.reload();
|
||||
}
|
||||
} else if (command.equalsIgnoreCase("stop")) {
|
||||
MinecraftServer.stopCleanly();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
System.out.println("Server starting on port 25565");
|
||||
System.out.println("Type 'reload' to reload all scripts, or 'reload <filename.js>' for a specific file.");
|
||||
|
||||
// Start the server
|
||||
minecraftServer.start("0.0.0.0", 25565);
|
||||
}
|
||||
}
|
||||
67
src/main/java/net/jstom/script/ScriptApi.java
Normal file
67
src/main/java/net/jstom/script/ScriptApi.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package net.jstom.script;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.event.Event;
|
||||
import net.minestom.server.event.EventNode;
|
||||
import org.graalvm.polyglot.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ScriptApi {
|
||||
private final List<EventNode<Event>> registeredNodes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Registers an event listener from JavaScript.
|
||||
* Usage in JS: server.on('net.minestom.server.event.player.PlayerLoginEvent', (event) => { ... });
|
||||
*/
|
||||
public void on(String eventClassName, Value callback) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(eventClassName);
|
||||
if (!Event.class.isAssignableFrom(clazz)) {
|
||||
System.err.println("[JStom] Class " + eventClassName + " is not an Event.");
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Event> eventClass = (Class<? extends Event>) clazz;
|
||||
|
||||
// Create a node for this listener so we can easily remove it later
|
||||
var node = EventNode.all("script-node-" + System.nanoTime());
|
||||
|
||||
node.addListener(eventClass, event -> {
|
||||
// Execute JS callback
|
||||
synchronized (callback) {
|
||||
if (callback.canExecute()) {
|
||||
try {
|
||||
callback.executeVoid(event);
|
||||
} catch (Exception e) {
|
||||
System.err.println("[JStom] Error in JS event listener:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MinecraftServer.getGlobalEventHandler().addChild(node);
|
||||
registeredNodes.add(node);
|
||||
System.out.println("[JStom] Registered listener for " + eventClass.getSimpleName());
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("[JStom] Could not find event class: " + eventClassName);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
System.out.println("[JStom] Cleaning up " + registeredNodes.size() + " event nodes...");
|
||||
for (var node : registeredNodes) {
|
||||
MinecraftServer.getGlobalEventHandler().removeChild(node);
|
||||
}
|
||||
registeredNodes.clear();
|
||||
}
|
||||
|
||||
public void log(String message) {
|
||||
System.out.println("[JS] " + message);
|
||||
}
|
||||
}
|
||||
104
src/main/java/net/jstom/script/ScriptManager.java
Normal file
104
src/main/java/net/jstom/script/ScriptManager.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package net.jstom.script;
|
||||
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.Source;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ScriptManager {
|
||||
private final File scriptsDir;
|
||||
private final Map<String, ScriptEnv> loadedScripts = new HashMap<>();
|
||||
|
||||
public ScriptManager(File scriptsDir) {
|
||||
this.scriptsDir = scriptsDir;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
if (!scriptsDir.exists()) {
|
||||
scriptsDir.mkdirs();
|
||||
}
|
||||
|
||||
File[] files = scriptsDir.listFiles((dir, name) -> name.endsWith(".js"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
loadScript(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadScript(File file) {
|
||||
String fileName = file.getName();
|
||||
unloadScript(fileName); // Ensure clean slate if reloading
|
||||
|
||||
System.out.println("[JStom] Loading script: " + fileName);
|
||||
|
||||
ScriptApi api = new ScriptApi();
|
||||
Context context = Context.newBuilder("js")
|
||||
.allowHostAccess(HostAccess.ALL)
|
||||
.allowHostClassLookup(s -> true)
|
||||
.allowIO(true)
|
||||
.build();
|
||||
|
||||
context.getBindings("js").putMember("server", api);
|
||||
|
||||
try {
|
||||
context.eval(Source.newBuilder("js", file).build());
|
||||
loadedScripts.put(fileName, new ScriptEnv(context, api, file));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
System.err.println("[JStom] Error executing script " + fileName);
|
||||
e.printStackTrace();
|
||||
// Cleanup if failed
|
||||
api.cleanup();
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void unloadScript(String fileName) {
|
||||
ScriptEnv env = loadedScripts.remove(fileName);
|
||||
if (env != null) {
|
||||
env.api.cleanup();
|
||||
env.context.close();
|
||||
System.out.println("[JStom] Unloaded script: " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
// Copy keys to avoid ConcurrentModificationException
|
||||
for (String fileName : new java.util.ArrayList<>(loadedScripts.keySet())) {
|
||||
unloadScript(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
unload();
|
||||
load();
|
||||
}
|
||||
|
||||
public void reload(String fileName) {
|
||||
File file = new File(scriptsDir, fileName);
|
||||
if (file.exists()) {
|
||||
loadScript(file);
|
||||
} else {
|
||||
System.err.println("[JStom] File not found: " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScriptEnv {
|
||||
final Context context;
|
||||
final ScriptApi api;
|
||||
final File file;
|
||||
|
||||
public ScriptEnv(Context context, ScriptApi api, File file) {
|
||||
this.context = context;
|
||||
this.api = api;
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user