From 8ce32613e845118ccde1068f3d312e0ab84e1dd3 Mon Sep 17 00:00:00 2001 From: RegadPoleCN Date: Sun, 18 Jan 2026 01:49:31 +0800 Subject: [PATCH 1/6] feat(hytale): add support to Hytale --- hytale/build.gradle.kts | 9 + .../dev/neovoxel/neobot/NeoBotHytale.java | 206 ++++++++++++++++++ .../neobot/adapter/HytaleCommandProvider.java | 39 ++++ .../neobot/adapter/HytaleCommandSender.java | 25 +++ .../neobot/adapter/HytaleOfflinePlayer.java | 21 ++ .../neovoxel/neobot/adapter/HytalePlayer.java | 34 +++ .../neobot/adapter/HytaleSchedulerTask.java | 28 +++ .../neobot/adapter/NHytaleLogger.java | 41 ++++ .../neobot/event/HytaleChatEvent.java | 21 ++ .../neobot/event/HytaleEventManager.java | 30 +++ .../neobot/event/HytaleLoginEvent.java | 22 ++ hytale/src/main/resources/manifest.json | 19 ++ settings.gradle.kts | 1 + 13 files changed, 496 insertions(+) create mode 100644 hytale/build.gradle.kts create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandSender.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleOfflinePlayer.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytalePlayer.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/NHytaleLogger.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/event/HytaleChatEvent.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/event/HytaleEventManager.java create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/event/HytaleLoginEvent.java create mode 100644 hytale/src/main/resources/manifest.json diff --git a/hytale/build.gradle.kts b/hytale/build.gradle.kts new file mode 100644 index 0000000..7c65566 --- /dev/null +++ b/hytale/build.gradle.kts @@ -0,0 +1,9 @@ +repositories { + maven ("https://nexus.lucko.me/repository/maven-hytale/") +} + +dependencies { + implementation(project(":common")) + // hytale + compileOnly("com.hypixel.hytale:HytaleServer:2026.01.13-dcad8778f-SNAPSHOT") +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java new file mode 100644 index 0000000..90ffd1a --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java @@ -0,0 +1,206 @@ +package dev.neovoxel.neobot; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.NameMatching; +import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerReadyEvent; +import com.hypixel.hytale.server.core.event.events.player.PlayerSetupConnectEvent; +import com.hypixel.hytale.server.core.plugin.JavaPlugin; +import com.hypixel.hytale.server.core.plugin.JavaPluginInit; +import com.hypixel.hytale.server.core.plugin.PluginBase; +import com.hypixel.hytale.server.core.plugin.PluginManager; +import com.hypixel.hytale.server.core.task.TaskRegistration; +import com.hypixel.hytale.server.core.universe.Universe; +import dev.neovoxel.neobot.adapter.*; +import dev.neovoxel.neobot.bot.BotProvider; +import dev.neovoxel.neobot.command.CommandProvider; +import dev.neovoxel.neobot.config.EnhancedConfig; +import dev.neovoxel.neobot.config.ScriptConfig; +import dev.neovoxel.neobot.event.HytaleEventManager; +import dev.neovoxel.neobot.game.GameEventListener; +import dev.neovoxel.neobot.scheduler.ScheduledTask; +import dev.neovoxel.neobot.script.ScriptProvider; +import dev.neovoxel.neobot.script.ScriptScheduler; +import dev.neovoxel.neobot.storage.StorageProvider; +import lombok.Getter; +import lombok.Setter; +import org.graalvm.polyglot.HostAccess; + +import java.io.File; +import java.time.Duration; +import java.util.UUID; + +public class NeoBotHytale extends JavaPlugin implements NeoBot { + + @Getter + private GameEventListener gameEventListener; + + @Getter + @Setter + private BotProvider botProvider; + + @Getter + @Setter + private ScriptProvider scriptProvider; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private StorageProvider storageProvider; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private ScriptScheduler scriptScheduler; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private EnhancedConfig messageConfig; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private EnhancedConfig generalConfig; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private ScriptConfig scriptConfig; + + @Getter(onMethod_ = {@HostAccess.Export}) + @Setter + private String storageType; + + @Getter + @Setter + private CommandProvider commandProvider; + + public NeoBotHytale(JavaPluginInit init) { + super(init); + } + + @Override + public void start() { + this.enable(); + } + + @HostAccess.Export + @Override + public NeoLogger getNeoLogger() { + return new NHytaleLogger(this); + } + + @Override + public File getDataFolder() { + return this.getDataFolder(); + } + + @Override + public void setGameEventListener(GameEventListener listener) { + HytaleEventManager manager = new HytaleEventManager(this); + getEventRegistry().registerGlobal(PlayerSetupConnectEvent.class, manager::onLogin); + getEventRegistry().registerGlobal(PlayerReadyEvent.class, manager::onJoin); + getEventRegistry().registerGlobal(PlayerDisconnectEvent.class, manager::onQuit); + getEventRegistry().registerGlobal(PlayerChatEvent.class, manager::onChat); + this.gameEventListener = listener; + } + + @Override + public void registerCommands() { + HytaleCommandProvider commandProvider1 = new HytaleCommandProvider(this); + commandProvider1.registerCommand(); + setCommandProvider(commandProvider1); + } + + @HostAccess.Export + @Override + public String getPlatform() { + return "Hytale"; + } + + @Override + public boolean isPluginLoaded(String name) { + boolean has = false; + for (PluginBase plugin: PluginManager.get().getPlugins()) { + String plname = plugin.getManifest().getName(); + if (plname.equals(name)) has = true; + } + return has; + } + + @HostAccess.Export + @Override + public RemoteExecutor getExecutorByName(String name) { + return null; + } + + @HostAccess.Export + @Override + public Player getOnlinePlayer(String name) { + return new HytalePlayer(Universe.get().getPlayer(name, NameMatching.EXACT)); + } + + @HostAccess.Export + @Override + public Player[] getOnlinePlayers() { + return Universe.get().getPlayers().stream().map(HytalePlayer::new).toArray(HytalePlayer[]::new); + } + + @HostAccess.Export + @Override + public OfflinePlayer getOfflinePlayer(String name) { + UUID uuid = Universe.get().getPlayer(name, NameMatching.EXACT).getUuid(); + return new HytaleOfflinePlayer(name, uuid); + } + + @HostAccess.Export + @Override + public void broadcast(String message) { + Universe.get().sendMessage(Message.parse(message)); + } + + @HostAccess.Export + @Override + public String externalParsePlaceholder(String message, OfflinePlayer player) { + return message; + } + + private TaskRegistration runTask(Runnable task) { + + } + + @Override + public ScheduledTask submit(Runnable task) { + Universe.get().getDefaultWorld().execute(); + return new HytaleSchedulerTask(); + } + + @Override + public ScheduledTask submitAsync(Runnable task) { + return submit(task); + } + + @Override + public ScheduledTask submit(Runnable task, long delay) { + return new HytaleSchedulerTask(proxyServer.getScheduler().buildTask(this, task) + .delay(Duration.ofSeconds(delay)).schedule()); + } + + @Override + public ScheduledTask submitAsync(Runnable task, long delay) { + return submit(task, delay); + } + + @Override + public ScheduledTask submit(Runnable task, long delay, long period) { + return new HytaleSchedulerTask(proxyServer.getScheduler().buildTask(this, task) + .delay(Duration.ofSeconds(delay)).repeat(Duration.ofSeconds(period)).schedule()); + } + + @Override + public ScheduledTask submitAsync(Runnable task, long delay, long period) { + return submit(task, delay, period); + } + + @Override + public void cancelAllTasks() { + HytaleSchedulerTask.cancelAllTasks(); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java new file mode 100644 index 0000000..7f54474 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java @@ -0,0 +1,39 @@ +package dev.neovoxel.neobot.adapter; + +import com.hypixel.hytale.server.core.command.system.AbstractCommand; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import dev.neovoxel.neobot.command.CommandProvider; +import dev.neovoxel.neobot.NeoBotHytale; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class HytaleCommandProvider extends CommandProvider { + private final NeoBotHytale plugin; + + public HytaleCommandProvider(NeoBotHytale plugin) { + super(plugin); + this.plugin = plugin; + } + + @Override + public void registerCommand() { + plugin.getCommandRegistry().registerCommand(new NHytaleCommand(this)); + } + + public class NHytaleCommand extends AbstractCommand { + private HytaleCommandProvider provider; + protected NHytaleCommand(HytaleCommandProvider provider) { + super("neobot", "Commands for NeoBot"); + this.provider = provider; + } + + @Override + protected @Nullable CompletableFuture execute(@NotNull CommandContext ctx) { + provider.onCommand(new HytaleCommandSender(ctx.sender()), ctx.getInputString().replaceFirst("/neobot ", "").split(" ")); + return null; + } + } + +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandSender.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandSender.java new file mode 100644 index 0000000..70325b7 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandSender.java @@ -0,0 +1,25 @@ +package dev.neovoxel.neobot.adapter; + +import com.hypixel.hytale.server.core.Message; +import org.graalvm.polyglot.HostAccess; +import com.hypixel.hytale.server.core.command.system.CommandSender; +public class HytaleCommandSender extends dev.neovoxel.neobot.adapter.CommandSender { + private final CommandSender sender; + + public HytaleCommandSender(CommandSender sender) { + super(sender.getDisplayName()); + this.sender = sender; + } + + @HostAccess.Export + @Override + public void sendMessage(String message) { + sender.sendMessage(Message.parse(message)); + } + + @HostAccess.Export + @Override + public boolean hasPermission(String node) { + return sender.hasPermission(node); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleOfflinePlayer.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleOfflinePlayer.java new file mode 100644 index 0000000..2081fcd --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleOfflinePlayer.java @@ -0,0 +1,21 @@ +package dev.neovoxel.neobot.adapter; + +import com.hypixel.hytale.server.core.universe.Universe; +import org.graalvm.polyglot.HostAccess; + +import java.util.UUID; + +public class HytaleOfflinePlayer extends OfflinePlayer { + private UUID uuid; + + public HytaleOfflinePlayer(String name, UUID uuid) { + super(name, uuid); + this.uuid = uuid; + } + + @HostAccess.Export + @Override + public boolean isOnline() { + return Universe.get().getPlayer(uuid).isValid(); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytalePlayer.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytalePlayer.java new file mode 100644 index 0000000..786e935 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytalePlayer.java @@ -0,0 +1,34 @@ +package dev.neovoxel.neobot.adapter; + +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.permissions.PermissionsModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import org.graalvm.polyglot.HostAccess; + +public class HytalePlayer extends Player { + + private final PlayerRef playerRef; + + public HytalePlayer(PlayerRef playerRef) { + super(playerRef.getUsername(), playerRef.getUuid()); + this.playerRef = playerRef; + } + + @HostAccess.Export + @Override + public void sendMessage(String message) { + playerRef.sendMessage(Message.parse(message)); + } + + @HostAccess.Export + @Override + public void kick(String message) { + playerRef.getPacketHandler().disconnect(message); + + } + + @Override + public boolean hasPermission(String node) { + return PermissionsModule.get().hasPermission(playerRef.getUuid(), node); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java new file mode 100644 index 0000000..fa72556 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java @@ -0,0 +1,28 @@ +package dev.neovoxel.neobot.adapter; +import com.hypixel.hytale.server.core.task.TaskRegistration; +import dev.neovoxel.neobot.scheduler.ScheduledTask; +import org.graalvm.polyglot.HostAccess; + +import java.util.HashSet; +import java.util.Set; + +public class HytaleSchedulerTask implements ScheduledTask { + private TaskRegistration taskRegistration; + private static final Set tasks = new HashSet<>(); + + public HytaleSchedulerTask(TaskRegistration task) { + this.taskRegistration = task; + tasks.add(task); + } + + @HostAccess.Export + @Override + public void cancel() { + taskRegistration.unregister(); + } + + public static void cancelAllTasks() { + tasks.forEach(TaskRegistration::unregister); + tasks.clear(); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/NHytaleLogger.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/NHytaleLogger.java new file mode 100644 index 0000000..35b1761 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/NHytaleLogger.java @@ -0,0 +1,41 @@ +package dev.neovoxel.neobot.adapter; + +import dev.neovoxel.neobot.NeoBotHytale; + +public class NHytaleLogger implements NeoLogger { + private NeoBotHytale plugin; + public NHytaleLogger(NeoBotHytale plugin) { + this.plugin = plugin; + } + + @Override + public void info(String message) { + plugin.getLogger().atInfo().log(message); + } + + @Override + public void warn(String message) { + plugin.getLogger().atWarning().log(message); + } + + @Override + public void error(String message) { + plugin.getLogger().atSevere().log(message); + } + + @Override + public void error(String message, Throwable throwable) { + plugin.getLogger().atSevere().log(message); + throwable.printStackTrace(); + } + + @Override + public void debug(String message) { + plugin.getLogger().atFine().log(message); + } + + @Override + public void trace(String message) { + plugin.getLogger().atFinest().log(message); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleChatEvent.java b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleChatEvent.java new file mode 100644 index 0000000..486f2f5 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleChatEvent.java @@ -0,0 +1,21 @@ +package dev.neovoxel.neobot.event; + +import com.hypixel.hytale.server.core.event.events.player.PlayerChatEvent; +import dev.neovoxel.neobot.adapter.HytalePlayer; +import dev.neovoxel.neobot.game.event.ChatEvent; +import org.graalvm.polyglot.HostAccess; + +public class HytaleChatEvent extends ChatEvent { + private final PlayerChatEvent event; + + public HytaleChatEvent(PlayerChatEvent event) { + super(new HytalePlayer(event.getSender()), event.getContent()); + this.event = event; + } + + @HostAccess.Export + public void disallow() { + event.setCancelled(true); + } + +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleEventManager.java b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleEventManager.java new file mode 100644 index 0000000..7b198c0 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleEventManager.java @@ -0,0 +1,30 @@ +package dev.neovoxel.neobot.event; + +import com.hypixel.hytale.server.core.event.events.player.*; +import dev.neovoxel.neobot.NeoBotHytale; +import dev.neovoxel.neobot.adapter.HytalePlayer; +import dev.neovoxel.neobot.game.event.PlayerEvent; + +public class HytaleEventManager { + private final NeoBotHytale plugin; + + public HytaleEventManager(NeoBotHytale plugin) { + this.plugin = plugin; + } + + public void onLogin(PlayerSetupConnectEvent loginEvent) { + plugin.getGameEventListener().onLogin(new HytaleLoginEvent(loginEvent)); + } + + public void onJoin(PlayerReadyEvent event) { + plugin.getGameEventListener().onJoin(new PlayerEvent(new HytalePlayer(event.getPlayer().getPlayerRef()))); + } + + public void onQuit(PlayerDisconnectEvent event) { + plugin.getGameEventListener().onQuit(new PlayerEvent(new HytalePlayer(event.getPlayerRef()))); + } + + public void onChat(PlayerChatEvent event) { + plugin.getGameEventListener().onChat(new HytaleChatEvent(event)); + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleLoginEvent.java b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleLoginEvent.java new file mode 100644 index 0000000..21dc7b1 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/event/HytaleLoginEvent.java @@ -0,0 +1,22 @@ +package dev.neovoxel.neobot.event; + +import com.hypixel.hytale.server.core.event.events.player.PlayerSetupConnectEvent; +import dev.neovoxel.neobot.game.event.LoginEvent; +import org.graalvm.polyglot.HostAccess; + +public class HytaleLoginEvent extends LoginEvent { + private final PlayerSetupConnectEvent loginEvent; + + public HytaleLoginEvent(PlayerSetupConnectEvent loginEvent) { + super(loginEvent.getUsername(), loginEvent.getUuid()); + this.loginEvent = loginEvent; + } + + @HostAccess.Export + @Override + public void disallow(String reason) { + loginEvent.getPacketHandler().disconnect(reason); + loginEvent.setReason(reason); + loginEvent.setCancelled(true); + } +} diff --git a/hytale/src/main/resources/manifest.json b/hytale/src/main/resources/manifest.json new file mode 100644 index 0000000..74b41a4 --- /dev/null +++ b/hytale/src/main/resources/manifest.json @@ -0,0 +1,19 @@ +{ + "Group": "dev.neovoxel", + "Name": "NeoBot", + "Version": "0.1", + "Description": "A bot plugin for QQ and Onebot.", + "Authors": [ + { + "Name": "NeoVoxelDev Team", + "Url": "https://github.com/NeoVoxelDev" + } + ], + "Website": "https://github.com/NeoVoxelDev/NeoBot", + "ServerVersion": "*", + "Dependencies": {}, + "OptionalDependencies": {}, + "DisabledByDefault": false, + "Main": "dev.neovoxel.neobot.NeoBotHytale", + "IncludesAssetPack": false +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f5d03d2..b7aa642 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,3 +3,4 @@ include("common") include("bukkit") include("folia") include("velocity") +include("hytale") From 87f447fc8fe10dd1cb9c38bd5c8a8988a167b024 Mon Sep 17 00:00:00 2001 From: RegadPoleCN Date: Sun, 18 Jan 2026 09:38:47 +0800 Subject: [PATCH 2/6] fix(hytale): try to fix hytale command --- .../neobot/adapter/HytaleCommandProvider.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java index 7f54474..6482b66 100644 --- a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java @@ -7,7 +7,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class HytaleCommandProvider extends CommandProvider { private final NeoBotHytale plugin; @@ -26,12 +29,21 @@ public class NHytaleCommand extends AbstractCommand { private HytaleCommandProvider provider; protected NHytaleCommand(HytaleCommandProvider provider) { super("neobot", "Commands for NeoBot"); + setAllowsExtraArguments(true); this.provider = provider; } @Override protected @Nullable CompletableFuture execute(@NotNull CommandContext ctx) { - provider.onCommand(new HytaleCommandSender(ctx.sender()), ctx.getInputString().replaceFirst("/neobot ", "").split(" ")); + String input = ctx.getInputString(); + List args = Arrays.stream(input.split(" ")).collect(Collectors.toList()); + if (!args.isEmpty()) { + String first = args.get(0); + if (first.equals("neobot") || first.equals("/neobot")) { + args.remove(0); + } + } + provider.onCommand(new HytaleCommandSender(ctx.sender()), args.toArray(new String[0])); return null; } } From eae3e2ff1290619b966fb3e1d99010a7a5f803c8 Mon Sep 17 00:00:00 2001 From: RegadPoleCN Date: Sun, 18 Jan 2026 13:44:00 +0800 Subject: [PATCH 3/6] fix(hytale): try to fix hytale schedler system --- .../dev/neovoxel/neobot/NeoBotHytale.java | 24 ++- .../neobot/adapter/HytaleScheduler.java | 139 ++++++++++++++++++ .../neobot/adapter/HytaleSchedulerTask.java | 14 +- 3 files changed, 156 insertions(+), 21 deletions(-) create mode 100644 hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleScheduler.java diff --git a/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java index 90ffd1a..a004596 100644 --- a/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java +++ b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java @@ -10,7 +10,6 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit; import com.hypixel.hytale.server.core.plugin.PluginBase; import com.hypixel.hytale.server.core.plugin.PluginManager; -import com.hypixel.hytale.server.core.task.TaskRegistration; import com.hypixel.hytale.server.core.universe.Universe; import dev.neovoxel.neobot.adapter.*; import dev.neovoxel.neobot.bot.BotProvider; @@ -28,8 +27,8 @@ import org.graalvm.polyglot.HostAccess; import java.io.File; -import java.time.Duration; import java.util.UUID; +import java.util.concurrent.TimeUnit; public class NeoBotHytale extends JavaPlugin implements NeoBot { @@ -72,8 +71,11 @@ public class NeoBotHytale extends JavaPlugin implements NeoBot { @Setter private CommandProvider commandProvider; + private HytaleScheduler scheduler; + public NeoBotHytale(JavaPluginInit init) { super(init); + scheduler = new HytaleScheduler(this); } @Override @@ -162,41 +164,35 @@ public String externalParsePlaceholder(String message, OfflinePlayer player) { return message; } - private TaskRegistration runTask(Runnable task) { - - } @Override public ScheduledTask submit(Runnable task) { - Universe.get().getDefaultWorld().execute(); - return new HytaleSchedulerTask(); + return new HytaleSchedulerTask(scheduler.sync(task)); } @Override public ScheduledTask submitAsync(Runnable task) { - return submit(task); + return new HytaleSchedulerTask(scheduler.async(task)); } @Override public ScheduledTask submit(Runnable task, long delay) { - return new HytaleSchedulerTask(proxyServer.getScheduler().buildTask(this, task) - .delay(Duration.ofSeconds(delay)).schedule()); + return new HytaleSchedulerTask(scheduler.syncLater(task, delay, TimeUnit.SECONDS)); } @Override public ScheduledTask submitAsync(Runnable task, long delay) { - return submit(task, delay); + return new HytaleSchedulerTask(scheduler.asyncLater(task, delay, TimeUnit.SECONDS)); } @Override public ScheduledTask submit(Runnable task, long delay, long period) { - return new HytaleSchedulerTask(proxyServer.getScheduler().buildTask(this, task) - .delay(Duration.ofSeconds(delay)).repeat(Duration.ofSeconds(period)).schedule()); + return new HytaleSchedulerTask(scheduler.syncRepeating(task, delay, period, TimeUnit.SECONDS)); } @Override public ScheduledTask submitAsync(Runnable task, long delay, long period) { - return submit(task, delay, period); + return new HytaleSchedulerTask(scheduler.asyncRepeating(task, delay, period, TimeUnit.SECONDS)); } @Override diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleScheduler.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleScheduler.java new file mode 100644 index 0000000..655a173 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleScheduler.java @@ -0,0 +1,139 @@ +/* + * This file is part of NeoBot, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.neovoxel.neobot.adapter; + +import com.hypixel.hytale.server.core.universe.Universe; +import dev.neovoxel.neobot.NeoBotHytale; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Arrays; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class HytaleScheduler { + private static final int PARALLELISM = 16; + private final NeoBotHytale plugin; + + private final ScheduledThreadPoolExecutor scheduler; + private final ForkJoinPool worker; + + public HytaleScheduler(NeoBotHytale plugin) { + this.plugin = plugin; + this.scheduler = new ScheduledThreadPoolExecutor(1, r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName("neobot-scheduler"); + return thread; + }); + this.scheduler.setRemoveOnCancelPolicy(true); + this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.worker = new ForkJoinPool(PARALLELISM, new WorkerThreadFactory(), new ExceptionHandler(), false); + } + + public ScheduledFuture sync(Runnable task) { + return this.scheduler.schedule(() -> this.worker.execute(() -> Universe.get().getDefaultWorld().execute(task)), 0, TimeUnit.MILLISECONDS); +// return async(); // lucko: I think this is OK? + } + + public ScheduledFuture syncLater(Runnable task, long delay, TimeUnit unit) { + return this.scheduler.schedule(() -> this.worker.execute(() -> Universe.get().getDefaultWorld().execute(task)), delay, unit); + } + + public ScheduledFuture syncRepeating(Runnable task, long delay, long period, TimeUnit unit) { + return this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(() -> Universe.get().getDefaultWorld().execute(task)), delay, period, unit); + } + + public ScheduledFuture async(Runnable task) { +// return this.worker; + return this.scheduler.schedule(() -> this.worker.execute(task), 0, TimeUnit.MILLISECONDS); + } + + public ScheduledFuture asyncLater(Runnable task, long delay, TimeUnit unit) { + return this.scheduler.schedule(() -> this.worker.execute(task), delay, unit); + } + + public ScheduledFuture asyncRepeating(Runnable task, long delay, long period, TimeUnit unit) { + return this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(task), delay, period, unit); + } + + public void shutdownScheduler() { + this.scheduler.shutdown(); + try { + if (!this.scheduler.awaitTermination(1, TimeUnit.MINUTES)) { + plugin.getNeoLogger().error("Timed out waiting for the NeoBot scheduler to terminate"); + reportRunningTasks(thread -> thread.getName().equals("neobot-scheduler")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void shutdownExecutor() { + this.worker.shutdown(); + try { + if (!this.worker.awaitTermination(1, TimeUnit.MINUTES)) { + plugin.getNeoLogger().error("Timed out waiting for the NeoBot worker thread pool to terminate"); + reportRunningTasks(thread -> thread.getName().startsWith("neobot-worker-")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void reportRunningTasks(Predicate predicate) { + Thread.getAllStackTraces().forEach((thread, stack) -> { + if (predicate.test(thread)) { + plugin.getNeoLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" + + Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n")) + ); + } + }); + } + + private static final class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + private static final AtomicInteger COUNT = new AtomicInteger(0); + + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + thread.setDaemon(true); + thread.setName("neobot-worker-" + COUNT.getAndIncrement()); + return thread; + } + } + + private final class ExceptionHandler implements UncaughtExceptionHandler { + public void uncaughtException(Thread t, Throwable e) { + HytaleScheduler.this.plugin.getNeoLogger().warn("Thread " + t.getName() + " threw an uncaught exception"); + e.printStackTrace(); + } + } +} diff --git a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java index fa72556..7fc5c1b 100644 --- a/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java @@ -1,28 +1,28 @@ package dev.neovoxel.neobot.adapter; -import com.hypixel.hytale.server.core.task.TaskRegistration; import dev.neovoxel.neobot.scheduler.ScheduledTask; import org.graalvm.polyglot.HostAccess; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.ScheduledFuture; public class HytaleSchedulerTask implements ScheduledTask { - private TaskRegistration taskRegistration; - private static final Set tasks = new HashSet<>(); + private ScheduledFuture task; + private static final Set> tasks = new HashSet<>(); - public HytaleSchedulerTask(TaskRegistration task) { - this.taskRegistration = task; + public HytaleSchedulerTask(ScheduledFuture task) { + this.task = task; tasks.add(task); } @HostAccess.Export @Override public void cancel() { - taskRegistration.unregister(); + task.cancel(false); } public static void cancelAllTasks() { - tasks.forEach(TaskRegistration::unregister); + tasks.forEach(t -> t.cancel(false)); tasks.clear(); } } From c6998e1fa9ac6c8986e6db91ffad06b6b6beebfb Mon Sep 17 00:00:00 2001 From: RegadPoleCN <69202360+RegadPoleCN@users.noreply.github.com> Date: Sun, 18 Jan 2026 14:40:59 +0800 Subject: [PATCH 4/6] docs(hytale): change manifest --- hytale/src/main/resources/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hytale/src/main/resources/manifest.json b/hytale/src/main/resources/manifest.json index 74b41a4..2bbb07a 100644 --- a/hytale/src/main/resources/manifest.json +++ b/hytale/src/main/resources/manifest.json @@ -2,7 +2,7 @@ "Group": "dev.neovoxel", "Name": "NeoBot", "Version": "0.1", - "Description": "A bot plugin for QQ and Onebot.", + "Description": "A bot plugin that connects Minecraft with QQ, Kook, Discord, etc.", "Authors": [ { "Name": "NeoVoxelDev Team", From ad83f620222b765992abff883838d778af0cc897 Mon Sep 17 00:00:00 2001 From: RegadPoleCN Date: Sun, 18 Jan 2026 15:01:07 +0800 Subject: [PATCH 5/6] fix(hytale): fix --- hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java index a004596..96c4cd7 100644 --- a/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java +++ b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java @@ -91,7 +91,7 @@ public NeoLogger getNeoLogger() { @Override public File getDataFolder() { - return this.getDataFolder(); + return getDataDirectory().toFile(); } @Override From 950fdc4a6d71f45945b85d333789f59739ae469e Mon Sep 17 00:00:00 2001 From: RegadPoleCN Date: Tue, 20 Jan 2026 12:59:55 +0800 Subject: [PATCH 6/6] fix(hytale): fix manifest.json --- hytale/src/main/resources/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hytale/src/main/resources/manifest.json b/hytale/src/main/resources/manifest.json index 2bbb07a..19ac011 100644 --- a/hytale/src/main/resources/manifest.json +++ b/hytale/src/main/resources/manifest.json @@ -1,5 +1,5 @@ { - "Group": "dev.neovoxel", + "Group": "NeoVoxelDev", "Name": "NeoBot", "Version": "0.1", "Description": "A bot plugin that connects Minecraft with QQ, Kook, Discord, etc.",