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..96c4cd7 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/NeoBotHytale.java @@ -0,0 +1,202 @@ +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.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.util.UUID; +import java.util.concurrent.TimeUnit; + +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; + + private HytaleScheduler scheduler; + + public NeoBotHytale(JavaPluginInit init) { + super(init); + scheduler = new HytaleScheduler(this); + } + + @Override + public void start() { + this.enable(); + } + + @HostAccess.Export + @Override + public NeoLogger getNeoLogger() { + return new NHytaleLogger(this); + } + + @Override + public File getDataFolder() { + return getDataDirectory().toFile(); + } + + @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; + } + + + @Override + public ScheduledTask submit(Runnable task) { + return new HytaleSchedulerTask(scheduler.sync(task)); + } + + @Override + public ScheduledTask submitAsync(Runnable task) { + return new HytaleSchedulerTask(scheduler.async(task)); + } + + @Override + public ScheduledTask submit(Runnable task, long delay) { + return new HytaleSchedulerTask(scheduler.syncLater(task, delay, TimeUnit.SECONDS)); + } + + @Override + public ScheduledTask submitAsync(Runnable task, long delay) { + return new HytaleSchedulerTask(scheduler.asyncLater(task, delay, TimeUnit.SECONDS)); + } + + @Override + public ScheduledTask submit(Runnable task, long delay, long period) { + return new HytaleSchedulerTask(scheduler.syncRepeating(task, delay, period, TimeUnit.SECONDS)); + } + + @Override + public ScheduledTask submitAsync(Runnable task, long delay, long period) { + return new HytaleSchedulerTask(scheduler.asyncRepeating(task, delay, period, TimeUnit.SECONDS)); + } + + @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..6482b66 --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleCommandProvider.java @@ -0,0 +1,51 @@ +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.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +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"); + setAllowsExtraArguments(true); + this.provider = provider; + } + + @Override + protected @Nullable CompletableFuture execute(@NotNull CommandContext ctx) { + 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; + } + } + +} 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/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 new file mode 100644 index 0000000..7fc5c1b --- /dev/null +++ b/hytale/src/main/java/dev/neovoxel/neobot/adapter/HytaleSchedulerTask.java @@ -0,0 +1,28 @@ +package dev.neovoxel.neobot.adapter; +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 ScheduledFuture task; + private static final Set> tasks = new HashSet<>(); + + public HytaleSchedulerTask(ScheduledFuture task) { + this.task = task; + tasks.add(task); + } + + @HostAccess.Export + @Override + public void cancel() { + task.cancel(false); + } + + public static void cancelAllTasks() { + tasks.forEach(t -> t.cancel(false)); + 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..19ac011 --- /dev/null +++ b/hytale/src/main/resources/manifest.json @@ -0,0 +1,19 @@ +{ + "Group": "NeoVoxelDev", + "Name": "NeoBot", + "Version": "0.1", + "Description": "A bot plugin that connects Minecraft with QQ, Kook, Discord, etc.", + "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")