diff --git a/gradle.properties b/gradle.properties index 5cb97fc..124c0c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ mcVersion=1.21.8 release=2 # 0 for skip release, 1 for pre-release, 2 for release -luminolRef=e7176d188b9e4d3b1f258d56cda91e2d008282f4 +luminolRef=a2af823b646334cdc64abb32b859f394c55ce0cc org.gradle.configuration-cache=true org.gradle.caching=true diff --git a/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch b/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch index 1ffc77c..fa68fab 100644 --- a/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch +++ b/lophine-server/luminol-patches/features/0001-Rebrand-to-Lophine.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Rebrand to Lophine diff --git a/src/main/java/me/earthme/luminol/config/ConfigManager.java b/src/main/java/me/earthme/luminol/config/ConfigManager.java -index 4be82092886728b7862b6e8951cf1c8ab917ca55..1b822e9576573c360b997e5cb4fa02278c88b74f 100644 +index 5e341cdc1c6ff75552140ab16bc9ccd237d896d0..a780214137d10f2faa92042b2f32c77a6598fa8d 100644 --- a/src/main/java/me/earthme/luminol/config/ConfigManager.java +++ b/src/main/java/me/earthme/luminol/config/ConfigManager.java -@@ -21,6 +21,7 @@ public class ConfigManager { +@@ -22,6 +22,7 @@ public class ConfigManager { public static void initConfigs() { configfiles.put("luminol", ConfigsInstance.of(new File("luminol_config"), "luminol", "me.earthme.luminol.config.modules")); diff --git a/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java index 5d2df0d..8c47fd2 100644 --- a/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java +++ b/lophine-server/src/main/java/fun/bm/lophine/config/modules/function/FakeplayerConfig.java @@ -5,14 +5,10 @@ import me.earthme.luminol.config.IConfigModule; import me.earthme.luminol.config.flags.ConfigClassInfo; import me.earthme.luminol.config.flags.ConfigInfo; import me.earthme.luminol.enums.EnumConfigCategory; -import net.minecraft.server.MinecraftServer; -import org.bukkit.Bukkit; -import org.leavesmc.leaves.bot.BotCommand; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.command.bot.BotCommand; import java.util.List; -import java.util.Locale; @ConfigClassInfo(configAttribution = EnumConfigCategory.FUNCTION, mainName = "fakeplayer") public class FakeplayerConfig implements IConfigModule { @@ -86,23 +82,28 @@ public class FakeplayerConfig implements IConfigModule { public static ServerBot.TickType tickType = ServerBot.TickType.ENTITY_LIST; - public static void unregisterCommand(String name) { - name = name.toLowerCase(Locale.ENGLISH).trim(); - MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove(name); - MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove("leaves:" + name); - MinecraftServer.getServer().server.syncCommands(); - } + private BotCommand command = null; + + private boolean registered = false; public static int getSimulationDistance(ServerBot bot) { return simulationDistance == -1 ? bot.getBukkitEntity().getSimulationDistance() : simulationDistance; } + @Override public void onLoaded(CommentedFileConfig configInstance) { if (enable) { - Bukkit.getCommandMap().register("bot", "lophine", new BotCommand()); - Actions.registerAll(); - } else { - unregisterCommand("bot"); + command = new BotCommand(); + command.register(); + registered = true; + } + } + + @Override + public void onUnloaded(CommentedFileConfig configInstance) { + if (registered) { + command.unregister(); + command = null; } } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java deleted file mode 100644 index 51d97c9..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * This file is part of Leaves (https://github.com/LeavesMC/Leaves) - * - * Leaves is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Leaves is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Leaves. If not, see . - */ - -package org.leavesmc.leaves.bot; - -import com.mojang.logging.LogUtils; -import fun.bm.lophine.config.modules.function.FakeplayerConfig; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.bot.agent.Actions; -import org.leavesmc.leaves.bot.agent.Configs; -import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.entity.bot.Bot; -import org.leavesmc.leaves.event.bot.BotActionStopEvent; -import org.leavesmc.leaves.event.bot.BotConfigModifyEvent; -import org.leavesmc.leaves.event.bot.BotCreateEvent; -import org.leavesmc.leaves.event.bot.BotRemoveEvent; -import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; -import org.slf4j.Logger; - -import java.util.*; - -import static net.kyori.adventure.text.Component.text; - -public class BotCommand extends Command { - private static final Logger LOGGER = LogUtils.getLogger(); - - public BotCommand() { - super("bot"); - this.setPermission("lophine.bot"); - this.setDescription("FakePlayer Command"); - this.setUsage("/bot [args...]"); - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - if (!testPermission(sender)) { - return true; - } - - if (!FakeplayerConfig.enable) { - sender.sendMessage(Component.text("Fakeplayer feature is disabled!").color(TextColor.color(255, 0, 0))); - return true; - } - - if (args.length == 0) { - sender.sendMessage(Component.text("Usage: /bot [args...]").color(TextColor.color(255, 255, 0))); - return true; - } - - String subCommand = args[0].toLowerCase(); - switch (subCommand) { - case "create" -> { - return handleCreate(sender, args); - } - case "remove" -> { - return handleRemove(sender, args); - } - case "list" -> { - return handleList(sender, args); - } - case "action" -> { - return handleAction(sender, args); - } - case "config" -> { - return handleConfig(sender, args); - } - case "save" -> { - return handleSave(sender, args); - } - case "load" -> { - return handleLoad(sender, args); - } - default -> { - sender.sendMessage(Component.text("Unknown subcommand: " + subCommand).color(TextColor.color(255, 0, 0))); - sender.sendMessage(Component.text("Available commands: create, remove, list, action, config, save, load").color(TextColor.color(255, 255, 0))); - } - } - - return true; - } - - private boolean handleCreate(CommandSender sender, String[] args) { - if (args.length < 2) { - sender.sendMessage(text("Use /bot create [skin_name] to create a fakeplayer", NamedTextColor.RED)); - return true; - } - - String botName = args[1]; - String fullName = BotUtil.getFullName(botName); - if (this.canCreate(sender, fullName)) { - BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()) - .createReason(BotCreateEvent.CreateReason.COMMAND) - .creator(sender); - - if (args.length >= 3) { - builder.skinName(args[2]); - } - - if (sender instanceof Player player) { - builder.location(player.getLocation()); - } else if (sender instanceof ConsoleCommandSender) { - if (args.length >= 7) { - try { - World world = Bukkit.getWorld(args[3]); - double x = Double.parseDouble(args[4]); - double y = Double.parseDouble(args[5]); - double z = Double.parseDouble(args[6]); - if (world != null) { - builder.location(new Location(world, x, y, z)); - } - } catch (Exception e) { - LOGGER.warn("Can't build location", e); - } - } - } - - builder.spawnWithSkin(null); - } - return true; - } - - private boolean handleRemove(CommandSender sender, String[] args) { - if (args.length < 2 || args.length > 5) { - sender.sendMessage(text("Usage: /bot remove [hour] [minute] [second]", NamedTextColor.RED)); - return true; - } - - String botName = args[1]; - BotList botList = BotList.INSTANCE; - ServerBot bot = botList.getBotByName(BotUtil.getFullName(botName)); - - if (bot == null) { - sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); - return true; - } - - if (args.length == 3 && args[2].equals("cancel")) { - if (bot.removeTaskId == -1) { - sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); - return true; - } - Bukkit.getScheduler().cancelTask(bot.removeTaskId); - bot.removeTaskId = -1; - sender.sendMessage(text("Remove cancel")); - return true; - } - - if (args.length > 2) { - long time = 0; - int h; - long s = 0; - long m = 0; - - try { - h = Integer.parseInt(args[2]); - if (h < 0) { - throw new NumberFormatException(); - } - time += ((long) h) * 3600 * 20; - if (args.length > 3) { - m = Long.parseLong(args[3]); - if (m > 59 || m < 0) { - throw new NumberFormatException(); - } - time += m * 60 * 20; - } - if (args.length > 4) { - s = Long.parseLong(args[4]); - if (s > 59 || s < 0) { - throw new NumberFormatException(); - } - time += s * 20; - } - } catch (NumberFormatException e) { - sender.sendMessage(text("Usage: /bot remove [hour] [minute] [second]", NamedTextColor.RED)); - return true; - } - - boolean isReschedule = bot.removeTaskId != -1; - - if (isReschedule) { - Bukkit.getScheduler().cancelTask(bot.removeTaskId); - } - bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> { - bot.removeTaskId = -1; - botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); - }, time).getTaskId(); - - sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : "")); - return true; - } - - botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); - sender.sendMessage(text("Removed fakeplayer: " + botName, NamedTextColor.GREEN)); - return true; - } - - private boolean handleList(CommandSender sender, String[] args) { - BotList botList = BotList.INSTANCE; - - if (args.length < 2) { - Map> botMap = new HashMap<>(); - for (World world : Bukkit.getWorlds()) { - botMap.put(world, new ArrayList<>()); - } - - for (ServerBot bot : botList.bots) { - Bot bukkitBot = bot.getBukkitEntity(); - botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName()); - } - - sender.sendMessage("Total number: (" + botList.bots.size() + "/" + FakeplayerConfig.limit + ")"); - for (World world : botMap.keySet()) { - sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world))); - } - } else { - World world = Bukkit.getWorld(args[1]); - - if (world == null) { - sender.sendMessage(text("Unknown world", NamedTextColor.RED)); - return true; - } - - List snowBotList = new ArrayList<>(); - for (ServerBot bot : botList.bots) { - Bot bukkitBot = bot.getBukkitEntity(); - if (bukkitBot.getWorld() == world) { - snowBotList.add(bukkitBot.getName()); - } - } - - sender.sendMessage(world.getName() + "(" + snowBotList.size() + "): " + formatPlayerNameList(snowBotList)); - } - return true; - } - - private boolean handleAction(CommandSender sender, String[] args) { - if (!FakeplayerConfig.canUseAction) { - sender.sendMessage(text("Bot action feature is disabled!", NamedTextColor.RED)); - return true; - } - - if (args.length < 3) { - sender.sendMessage(text("Use /bot action to make fakeplayer do action", NamedTextColor.RED)); - return true; - } - - ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); - if (bot == null) { - sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); - return true; - } - - switch (args[2].toLowerCase()) { - case "list" -> { - sender.sendMessage(bot.getScoreboardName() + "'s action list:"); - for (int i = 0; i < bot.getBotActions().size(); i++) { - sender.sendMessage(i + " " + bot.getBotActions().get(i).getName()); - } - } - case "start" -> executeActionStart(bot, sender, args); - case "stop" -> executeActionStop(bot, sender, args); - default -> sender.sendMessage(text("Unknown action command. Use: list, start, stop", NamedTextColor.RED)); - } - return true; - } - - private void executeActionStart(ServerBot bot, CommandSender sender, String[] args) { - if (args.length < 4) { - sender.sendMessage(text("Invalid action", NamedTextColor.RED)); - return; - } - ServerBotAction action = Actions.getForName(args[3]); - if (action == null) { - sender.sendMessage(text("Invalid action", NamedTextColor.RED)); - return; - } - - CraftPlayer player; - if (sender instanceof CraftPlayer) { - player = (CraftPlayer) sender; - } else { - player = bot.getBukkitEntity(); - } - - String[] realArgs = Arrays.copyOfRange(args, 4, args.length); - ServerBotAction newAction; - try { - newAction = action.create(); - newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs)); - } catch (IllegalArgumentException e) { - sender.sendMessage(text("Action create error, please check your arguments, " + e.getMessage(), NamedTextColor.RED)); - return; - } - - if (bot.addBotAction(newAction, sender)) { - sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString()); - } - } - - private void executeActionStop(ServerBot bot, CommandSender sender, String[] args) { - if (args.length < 4) { - sender.sendMessage(text("Invalid index", NamedTextColor.RED)); - return; - } - - String index = args[3]; - if (index.equals("all")) { - Set> forRemoval = new HashSet<>(); - for (int i = 0; i < bot.getBotActions().size(); i++) { - ServerBotAction action = bot.getBotActions().get(i); - BotActionStopEvent event = new BotActionStopEvent( - bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender - ); - event.callEvent(); - if (!event.isCancelled()) { - forRemoval.add(action); - action.stop(bot, BotActionStopEvent.Reason.COMMAND); - } - } - bot.getBotActions().removeAll(forRemoval); - sender.sendMessage(bot.getScoreboardName() + "'s action list cleared."); - return; - } - try { - int i = Integer.parseInt(index); - if (i < 0 || i >= bot.getBotActions().size()) { - sender.sendMessage(text("Invalid index", NamedTextColor.RED)); - return; - } - - ServerBotAction action = bot.getBotActions().get(i); - BotActionStopEvent event = new BotActionStopEvent( - bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender - ); - event.callEvent(); - if (!event.isCancelled()) { - action.stop(bot, BotActionStopEvent.Reason.COMMAND); - bot.getBotActions().remove(i); - sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped."); - } - } catch (NumberFormatException e) { - sender.sendMessage(text("Invalid index", NamedTextColor.RED)); - } - } - - private boolean handleConfig(CommandSender sender, String[] args) { - if (!FakeplayerConfig.canModifyConfig) { - sender.sendMessage(text("Bot config feature is disabled!", NamedTextColor.RED)); - return true; - } - - if (args.length < 3) { - sender.sendMessage(text("Use /bot config to modify fakeplayer's config", NamedTextColor.RED)); - return true; - } - - ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); - if (bot == null) { - sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); - return true; - } - - if (!Configs.getConfigNames().contains(args[2])) { - sender.sendMessage(text("This config is not accept", NamedTextColor.RED)); - return true; - } - - AbstractBotConfig config = bot.getConfig(Objects.requireNonNull(Configs.getConfig(args[2]))); - if (args.length < 4) { - config.getMessage().forEach(sender::sendMessage); - } else { - String[] realArgs = Arrays.copyOfRange(args, 3, args.length); - - BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs, sender); - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) { - return true; - } - CommandArgumentResult result = config.getArgument().parse(0, realArgs); - - try { - config.setFromCommand(result); - config.getChangeMessage().forEach(sender::sendMessage); - } catch (IllegalArgumentException e) { - sender.sendMessage(text(e.getMessage(), NamedTextColor.RED)); - } - } - return true; - } - - private boolean handleSave(CommandSender sender, String[] args) { - if (!FakeplayerConfig.canManualSaveAndLoad) { - sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED)); - return true; - } - - if (args.length < 2) { - sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); - return true; - } - - BotList botList = BotList.INSTANCE; - ServerBot bot = botList.getBotByName(args[1]); - - if (bot == null) { - sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); - return true; - } - - if (botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true)) { - sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName()); - } - return true; - } - - private boolean handleLoad(CommandSender sender, String[] args) { - if (!FakeplayerConfig.canManualSaveAndLoad) { - sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED)); - return true; - } - - if (args.length < 2) { - sender.sendMessage(text("Use /bot load to load a fakeplayer", NamedTextColor.RED)); - return true; - } - - String realName = args[1]; - BotList botList = BotList.INSTANCE; - if (!botList.getSavedBotList().contains(realName)) { - sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED)); - return true; - } - - if (botList.loadNewBot(realName) == null) { - sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED)); - } else { - sender.sendMessage(text("Successfully loaded fakeplayer: " + realName, NamedTextColor.GREEN)); - } - return true; - } - - @NotNull - private static String formatPlayerNameList(@NotNull List list) { - if (list.isEmpty()) { - return ""; - } - String string = list.toString(); - return string.substring(1, string.length() - 1); - } - - private boolean canCreate(CommandSender sender, @NotNull String name) { - BotList botList = BotList.INSTANCE; - if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { - sender.sendMessage(text("This name is illegal", NamedTextColor.RED)); - return false; - } - - if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) { - sender.sendMessage(text("This player is in server", NamedTextColor.RED)); - return false; - } - - if (FakeplayerConfig.unableNames.contains(name)) { - sender.sendMessage(text("This name is not allowed", NamedTextColor.RED)); - return false; - } - - if (botList.bots.size() >= FakeplayerConfig.limit) { - sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED)); - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java index 50bf3af..151dbd5 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java @@ -147,4 +147,4 @@ public record BotCreateState(String realName, String name, String skinName, Stri return bot != null ? bot.getBukkitEntity() : null; } } -} \ No newline at end of file +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java index cff8981..fdbdd7c 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java @@ -96,7 +96,12 @@ public class BotList { } public ServerBot loadNewBot(String realName) { - return this.loadNewBot(realName, this.dataStorage); + try { + return this.loadNewBot(realName, this.dataStorage); + } catch (Exception e) { + LOGGER.error("Failed to load bot {}", realName, e); + return null; + } } public ServerBot loadNewBot(String realName, IPlayerDataStorage playerIO) { @@ -198,7 +203,7 @@ public class BotList { this.server.server.getPluginManager().callEvent(event); if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) { - return true; + return false; } if (bot.removeTaskId != -1) { @@ -240,18 +245,7 @@ public class BotList { } } - ServerLevel level = bot.level(); - int chunkX = bot.getBlockX() >> 4; - int chunkZ = bot.getBlockZ() >> 4; - if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, chunkX, chunkZ)) { - level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); - } else { - io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( - level, chunkX, chunkZ, () -> { - level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); - }); - } - + bot.level().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); this.bots.remove(bot); this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT)); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java index c93a6af..1f83a3e 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java @@ -68,6 +68,6 @@ public class BotRecipeBook extends ServerRecipeBook { @Override public @NotNull Packed pack() { - return new ServerRecipeBook.Packed(this.bookSettings.copy(), List.of(), List.of()); + return new Packed(this.bookSettings.copy(), List.of(), List.of()); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java index 6861d0e..0cec7fe 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java @@ -69,10 +69,10 @@ import org.bukkit.command.CommandSender; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; import org.leavesmc.leaves.bot.agent.Actions; import org.leavesmc.leaves.bot.agent.Configs; -import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.bot.agent.configs.AbstractBotConfig; import org.leavesmc.leaves.entity.bot.CraftBot; import org.leavesmc.leaves.event.bot.*; import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; @@ -84,9 +84,9 @@ import java.util.function.Predicate; public class ServerBot extends ServerPlayer { - private final List> actions; - private final Map, AbstractBotConfig> configs; - private static final Logger LOGGER = LogUtils.getLogger(); + private final List> actions; + private final Map> configs; + private static final Logger LOGGER = LogUtils.getClassLogger(); public boolean resume = false; public BotCreateState createState; @@ -108,9 +108,9 @@ public class ServerBot extends ServerPlayer { this.gameMode = new ServerBotGameMode(this); this.actions = new ArrayList<>(); - ImmutableMap.Builder, AbstractBotConfig> configBuilder = ImmutableMap.builder(); - for (Configs config : Configs.getConfigs()) { - configBuilder.put(config, config.createConfig(this)); + ImmutableMap.Builder> configBuilder = ImmutableMap.builder(); + for (AbstractBotConfig config : Configs.getConfigs()) { + configBuilder.put(config.getName(), config.create().setBot(this)); } this.configs = configBuilder.build(); @@ -297,7 +297,7 @@ public class ServerBot extends ServerPlayer { return this; } else { this.isChangingDimension = true; - fromLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + fromLevel.removePlayerImmediately(this, RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); this.setServerLevel(toLevel); this.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); @@ -389,14 +389,14 @@ public class ServerBot extends ServerPlayer { if (!this.actions.isEmpty()) { ValueOutput.TypedOutputList actionNbt = nbt.list("actions", CompoundTag.CODEC); - for (ServerBotAction action : this.actions) { + for (AbstractBotAction action : this.actions) { actionNbt.add(action.save(new CompoundTag())); } } if (!this.configs.isEmpty()) { ValueOutput.TypedOutputList configNbt = nbt.list("configs", CompoundTag.CODEC); - for (AbstractBotConfig config : this.configs.values()) { + for (AbstractBotConfig config : this.configs.values()) { configNbt.add(config.save(new CompoundTag())); } } @@ -429,9 +429,9 @@ public class ServerBot extends ServerPlayer { if (nbt.list("actions", CompoundTag.CODEC).isPresent()) { ValueInput.TypedInputList actionNbt = nbt.list("actions", CompoundTag.CODEC).orElseThrow(); actionNbt.forEach(actionTag -> { - ServerBotAction action = Actions.getForName(actionTag.getString("actionName").orElseThrow()); + AbstractBotAction action = Actions.getForName(actionTag.getString("actionName").orElseThrow()); if (action != null) { - ServerBotAction newAction = action.create(); + AbstractBotAction newAction = action.create(); newAction.load(actionTag); this.actions.add(newAction); } @@ -441,9 +441,9 @@ public class ServerBot extends ServerPlayer { if (nbt.list("configs", CompoundTag.CODEC).isPresent()) { ValueInput.TypedInputList configNbt = nbt.list("configs", CompoundTag.CODEC).orElseThrow(); for (CompoundTag configTag : configNbt) { - Configs configKey = Configs.getConfig(configTag.getString("configName").orElseThrow()); - if (configKey != null) { - this.configs.get(configKey).load(configTag); + AbstractBotConfig config = Configs.getConfig(configTag.getString("configName").orElseThrow()); + if (config != null) { + config.load(configTag); } } } @@ -641,11 +641,11 @@ public class ServerBot extends ServerPlayer { private void runAction() { if (FakeplayerConfig.canUseAction) { this.actions.forEach(action -> action.tryTick(this)); - this.actions.removeIf(ServerBotAction::isCancelled); + this.actions.removeIf(AbstractBotAction::isCancelled); } } - public boolean addBotAction(ServerBotAction action, CommandSender sender) { + public boolean addBotAction(AbstractBotAction action, CommandSender sender) { if (!FakeplayerConfig.canUseAction) { return false; } @@ -659,7 +659,7 @@ public class ServerBot extends ServerPlayer { return true; } - public List> getBotActions() { + public List> getBotActions() { return actions; } @@ -670,11 +670,15 @@ public class ServerBot extends ServerPlayer { } @SuppressWarnings("unchecked") - public AbstractBotConfig getConfig(Configs config) { - return (AbstractBotConfig) Objects.requireNonNull(this.configs.get(config)); + public > AbstractBotConfig getConfig(@NotNull AbstractBotConfig config) { + return (AbstractBotConfig) Objects.requireNonNull(this.configs.get(config.getName())); } - public E getConfigValue(Configs config) { + public Collection> getAllConfigs() { + return configs.values(); + } + + public > O getConfigValue(@NotNull AbstractBotConfig config) { return this.getConfig(config).getValue(); } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java deleted file mode 100644 index 13b75b3..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of Leaves (https://github.com/LeavesMC/Leaves) - * - * Leaves is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Leaves is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Leaves. If not, see . - */ - -package org.leavesmc.leaves.bot.agent; - -import net.minecraft.nbt.CompoundTag; -import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; - -import java.util.List; - -public abstract class AbstractBotConfig { - - private final String name; - private final CommandArgument argument; - - protected ServerBot bot; - - public AbstractBotConfig(String name, CommandArgument argument) { - this.name = name; - this.argument = argument; - } - - public AbstractBotConfig setBot(ServerBot bot) { - this.bot = bot; - return this; - } - - public abstract E getValue(); - - public abstract void setValue(E value) throws IllegalArgumentException; - - @SuppressWarnings("unchecked") - public void setFromCommand(@NotNull CommandArgumentResult result) throws IllegalArgumentException { - if (argument == CommandArgument.EMPTY) { - throw new IllegalArgumentException("No argument for " + this.getName()); - } - try { - this.setValue((E) result.read(argument.getArgumentTypes().getFirst().getType())); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Invalid argument type for " + this.getName() + ": " + e.getMessage()); - } - } - - public List getMessage() { - return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue()); - } - - public List getChangeMessage() { - return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue()); - } - - public String getName() { - return name; - } - - public CommandArgument getArgument() { - return argument; - } - - @NotNull - public CompoundTag save(@NotNull CompoundTag nbt) { - nbt.putString("configName", this.name); - return nbt; - } - - public abstract void load(@NotNull CompoundTag nbt); -} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java index ba627be..6a89c59 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java @@ -30,10 +30,10 @@ import java.util.Set; public class Actions { - private static final Map> actionsByName = new HashMap<>(); - private static final Map, ServerBotAction> actionsByClass = new HashMap<>(); + private static final Map> actionsByName = new HashMap<>(); + private static final Map, AbstractBotAction> actionsByClass = new HashMap<>(); - public static void registerAll() { + static { register(new ServerAttackAction(), AttackAction.class); register(new ServerBreakBlockAction(), BreakBlockAction.class); register(new ServerDropAction(), DropAction.class); @@ -55,7 +55,7 @@ public class Actions { register(new ServerSwapAction(), SwapAction.class); } - public static boolean register(@NotNull ServerBotAction action, Class> type) { + public static boolean register(@NotNull AbstractBotAction action, Class type) { if (!actionsByName.containsKey(action.getName())) { actionsByName.put(action.getName(), action); actionsByClass.put(type, action); @@ -64,14 +64,22 @@ public class Actions { return false; } + public static boolean register(@NotNull AbstractBotAction action) { + return register(action, action.getClass()); + } + public static boolean unregister(@NotNull String name) { - // TODO add in custom action api - return true; + AbstractBotAction action = actionsByName.remove(name); + if (action != null) { + actionsByClass.remove(action.getClass()); + return true; + } + return false; } @NotNull @Contract(pure = true) - public static Collection> getAll() { + public static Collection> getAll() { return actionsByName.values(); } @@ -81,12 +89,12 @@ public class Actions { } @Nullable - public static ServerBotAction getForName(String name) { + public static AbstractBotAction getForName(String name) { return actionsByName.get(name); } @Nullable - public static ServerBotAction getForClass(@NotNull Class type) { + public static AbstractBotAction getForClass(@NotNull Class type) { return actionsByClass.get(type); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java index c67b6cc..7ca24df 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java @@ -20,63 +20,40 @@ package org.leavesmc.leaves.bot.agent; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.agent.configs.*; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.function.Supplier; -@SuppressWarnings("unused") -public class Configs { +@SuppressWarnings({"unused"}) +public class Configs { + private static final Map, AbstractBotConfig> configs = new HashMap<>(); - private static final Map> configs = new HashMap<>(); - - public static final Configs SKIP_SLEEP = register(SkipSleepConfig.class, SkipSleepConfig::new); - public static final Configs ALWAYS_SEND_DATA = register(AlwaysSendDataConfig.class, AlwaysSendDataConfig::new); - public static final Configs SPAWN_PHANTOM = register(SpawnPhantomConfig.class, SpawnPhantomConfig::new); - public static final Configs SIMULATION_DISTANCE = register(SimulationDistanceConfig.class, SimulationDistanceConfig::new); - public static final Configs TICK_TYPE = register(TickTypeConfig.class, TickTypeConfig::new); - public static final Configs ENABLE_LOCATOR_BAR = register(LocatorBarConfig.class, LocatorBarConfig::new); - - private final Class> configClass; - private final Supplier> configCreator; - - private Configs(Class> configClass, Supplier> configCreator) { - this.configClass = configClass; - this.configCreator = configCreator; - } - - public Class> getConfigClass() { - return configClass; - } - - public AbstractBotConfig createConfig(ServerBot bot) { - return configCreator.get().setBot(bot); - } + public static final SkipSleepConfig SKIP_SLEEP = register(new SkipSleepConfig()); + public static final AlwaysSendDataConfig ALWAYS_SEND_DATA = register(new AlwaysSendDataConfig()); + public static final SpawnPhantomConfig SPAWN_PHANTOM = register(new SpawnPhantomConfig()); + public static final SimulationDistanceConfig SIMULATION_DISTANCE = register(new SimulationDistanceConfig()); + public static final TickTypeConfig TICK_TYPE = register(new TickTypeConfig()); + public static final LocatorBarConfig ENABLE_LOCATOR_BAR = register(new LocatorBarConfig()); @Nullable - public static Configs getConfig(String name) { - return configs.get(name); + public static AbstractBotConfig getConfig(String name) { + return configs.values().stream() + .filter(config -> config.getName().equals(name)) + .findFirst() + .orElse(null); } @NotNull @Contract(pure = true) - public static Collection> getConfigs() { + public static Collection> getConfigs() { return configs.values(); } - @NotNull - @Contract(pure = true) - public static Collection getConfigNames() { - return configs.keySet(); - } - - @NotNull - private static Configs register(Class> configClass, Supplier> configCreator) { - Configs config = new Configs<>(configClass, configCreator); - configs.put(config.createConfig(null).getName(), config); - return config; + @SuppressWarnings("unchecked") + private static > @NotNull E register(AbstractBotConfig instance) { + configs.put(instance.getClass(), instance); + return (E) instance; } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java similarity index 78% rename from lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java rename to lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java index e5dd207..69761d3 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractBotAction.java @@ -17,34 +17,33 @@ package org.leavesmc.leaves.bot.agent.actions; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.logging.LogUtils; import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.ServerPlayer; import org.apache.commons.lang3.tuple.Pair; -import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.WrappedArgument; import org.leavesmc.leaves.event.bot.BotActionExecuteEvent; import org.leavesmc.leaves.event.bot.BotActionStopEvent; import org.leavesmc.leaves.util.UpdateSuppressionException; import org.slf4j.Logger; -import java.util.List; -import java.util.UUID; -import java.util.function.BiFunction; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; @SuppressWarnings("unchecked") -public abstract class ServerBotAction> { +public abstract class AbstractBotAction> { private final String name; - private final CommandArgument argument; + private final Map>>> arguments; private final Supplier creator; private UUID uuid; + private int currentFork = 0; private int initialTickDelay; private int initialTickInterval; @@ -57,13 +56,13 @@ public abstract class ServerBotAction> { private Consumer onFail; private Consumer onSuccess; private Consumer onStop; - private static final Logger LOGGER = LogUtils.getLogger(); + private static final Logger LOGGER = LogUtils.getClassLogger(); - public ServerBotAction(String name, CommandArgument argument, Supplier creator) { + public AbstractBotAction(String name, Supplier creator) { this.name = name; - this.argument = argument; this.uuid = UUID.randomUUID(); this.creator = creator; + this.arguments = new HashMap<>(); this.cancel = false; this.setStartDelayTick(0); @@ -75,12 +74,40 @@ public abstract class ServerBotAction> { public abstract Object asCraft(); + public void provideActionData(@NotNull ActionData data) { + } + + public String getActionDataString() { + ActionData data = new ActionData(new ArrayList<>()); + provideActionData(data); + return data.raw.stream() + .map(pair -> pair.getLeft() + "=" + pair.getRight()) + .reduce((a, b) -> a + ", " + b) + .orElse("No arguments"); + } + public void init() { this.tickToNext = initialTickDelay; this.numberRemaining = this.getDoNumber(); this.setCancelled(false); } + public void fork(int fork) { + currentFork = fork; + } + + public WrappedArgument addArgument(String name, ArgumentType type) { + WrappedArgument argument = new WrappedArgument<>(name, type); + this.arguments + .computeIfAbsent(currentFork, k -> new ArrayList<>()) + .add(Pair.of(name, argument)); + return argument; + } + + public Map>>> getArguments() { + return this.arguments; + } + public void tryTick(ServerBot bot) { if (this.numberRemaining == 0) { this.stop(bot, BotActionStopEvent.Reason.DONE); @@ -168,19 +195,8 @@ public abstract class ServerBotAction> { } } - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - } - - public void setSuggestion(int n, BiFunction, String>> suggestion) { - this.argument.setSuggestion(n, suggestion); - } - - public void setSuggestion(int n, Pair, String> suggestion) { - this.setSuggestion(n, (sender, arg) -> suggestion); - } - - public void setSuggestion(int n, List tabComplete) { - this.setSuggestion(n, Pair.of(tabComplete, null)); + @SuppressWarnings("RedundantThrows") + public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException { } @NotNull @@ -188,10 +204,6 @@ public abstract class ServerBotAction> { return this.creator.get(); } - public CommandArgument getArgument() { - return this.argument; - } - public String getName() { return this.name; } @@ -251,4 +263,12 @@ public abstract class ServerBotAction> { public void setOnStop(Consumer onStop) { this.onStop = onStop; } + + public record ActionData( + List> raw + ) { + public void add(String key, String value) { + raw.add(Pair.of(key, value)); + } + } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractStateBotAction.java similarity index 74% rename from lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java rename to lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractStateBotAction.java index dd39d77..e7acdd5 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractStateBotAction.java @@ -17,14 +17,12 @@ package org.leavesmc.leaves.bot.agent.actions; -import org.leavesmc.leaves.command.CommandArgument; - import java.util.function.Supplier; -public abstract class ServerStateBotAction> extends ServerBotAction { +public abstract class AbstractStateBotAction> extends AbstractBotAction { - public ServerStateBotAction(String name, CommandArgument argument, Supplier creator) { - super(name, argument, creator); + public AbstractStateBotAction(String name, Supplier creator) { + super(name, creator); this.setDoNumber(-1); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java new file mode 100644 index 0000000..fb82be7 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerBotAction.java @@ -0,0 +1,59 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.command.CommandContext; + +import java.util.function.Supplier; + +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; +import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strings; + +public abstract class AbstractTimerBotAction> extends AbstractBotAction { + + public AbstractTimerBotAction(String name, Supplier creator) { + super(name, creator); + this.addArgument("delay", integer(0)) + .suggests(strings("0", "5", "10", "20")) + .setOptional(true); + this.addArgument("interval", integer(0)) + .suggests(strings("20", "0", "5", "10")) + .setOptional(true); + this.addArgument("do_number", integer(-1)) + .suggests(((context, builder) -> builder.suggest("-1", Component.literal("do infinite times")))) + .setOptional(true); + } + + @Override + public void loadCommand(@NotNull CommandContext context) { + this.setStartDelayTick(context.getIntegerOrDefault("delay", 0)); + this.setDoIntervalTick(context.getIntegerOrDefault("interval", 20)); + this.setDoNumber(context.getIntegerOrDefault("do_number", 1)); + } + + @Override + public void provideActionData(@NotNull ActionData data) { + super.provideActionData(data); + data.add("delay", String.valueOf(this.getStartDelayTick())); + data.add("interval", String.valueOf(this.getDoIntervalTick())); + data.add("do_number", String.valueOf(this.getDoNumber())); + data.add("remaining_do_number", String.valueOf(this.getDoNumberRemaining())); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java similarity index 76% rename from lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java rename to lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java index 815bc81..38403cf 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractUseBotAction.java @@ -18,33 +18,37 @@ package org.leavesmc.leaves.bot.agent.actions; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.ServerPlayer; +import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; -import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; import org.leavesmc.leaves.event.bot.BotActionStopEvent; -import java.util.List; import java.util.function.Supplier; -public abstract class ServerUseBotAction> extends ServerTimerBotAction { +import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; + +public abstract class AbstractUseBotAction> extends AbstractTimerBotAction { private int useTickTimeout = -1; private int alreadyUsedTick = 0; private int useItemRemainingTicks = 0; - public ServerUseBotAction(String name, Supplier supplier) { - super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), supplier); - this.setSuggestion(3, Pair.of(List.of("-1"), "[UseTickTimeout]")); + public AbstractUseBotAction(String name, Supplier supplier) { + super(name, supplier); + this.addArgument("use_timeout", integer(-1)) + .suggests((context, builder) -> { + builder.suggest("-1", Component.literal("no use timeout")); + builder.suggest("3", Component.literal("minimum bow shoot time")); + builder.suggest("10", Component.literal("minimum trident shoot time")); + }) + .setOptional(true); } @Override - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - super.loadCommand(player, result); - this.useTickTimeout = result.readInt(-1); + public void loadCommand(@NotNull CommandContext context) { + super.loadCommand(context); + this.useTickTimeout = context.getIntegerOrDefault("use_timeout", -1); } @Override @@ -110,6 +114,13 @@ public abstract class ServerUseBotAction> extend this.alreadyUsedTick++; } + @Override + public void provideActionData(@NotNull ActionData data) { + super.provideActionData(data); + data.add("use_timeout", String.valueOf(this.useTickTimeout)); + data.add("already_used_tick", String.valueOf(this.alreadyUsedTick)); + } + @Override @NotNull public CompoundTag save(@NotNull CompoundTag nbt) { diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java index 697e1d3..ffa24b8 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java @@ -22,7 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftAttackAction; -public class ServerAttackAction extends ServerTimerBotAction { +public class ServerAttackAction extends AbstractTimerBotAction { public ServerAttackAction() { super("attack", ServerAttackAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java index a5c6f84..b37c2c5 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java @@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftBreakBlockAction; -public class ServerBreakBlockAction extends ServerTimerBotAction { +public class ServerBreakBlockAction extends AbstractTimerBotAction { public ServerBreakBlockAction() { super("break", ServerBreakBlockAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java index 4f54aef..6a22074 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftDropAction; -public class ServerDropAction extends ServerTimerBotAction { +public class ServerDropAction extends AbstractTimerBotAction { public ServerDropAction() { super("drop", ServerDropAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java index 876fa0c..8a8f022 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftFishAction; -public class ServerFishAction extends ServerTimerBotAction { +public class ServerFishAction extends AbstractTimerBotAction { public ServerFishAction() { super("fish", ServerFishAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java index b744967..6ac04d8 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction; -public class ServerJumpAction extends ServerTimerBotAction { +public class ServerJumpAction extends AbstractTimerBotAction { public ServerJumpAction() { super("jump", ServerJumpAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java index 930896f..b694f02 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java @@ -17,40 +17,61 @@ package org.leavesmc.leaves.bot.agent.actions; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.commands.arguments.coordinates.Coordinates; +import net.minecraft.commands.arguments.coordinates.Vec3Argument; +import net.minecraft.commands.arguments.selector.EntitySelector; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; -import org.apache.commons.lang3.tuple.Pair; -import org.bukkit.entity.Player; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; import org.leavesmc.leaves.entity.bot.actions.CraftLookAction; -import java.text.DecimalFormat; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -public class ServerLookAction extends ServerBotAction { +public class ServerLookAction extends AbstractBotAction { private static final Vector ZERO_VECTOR = new Vector(0, 0, 0); - private static final DecimalFormat DF = new DecimalFormat("0.0"); private Vector pos = ZERO_VECTOR; private ServerPlayer target = null; public ServerLookAction() { - super("look", CommandArgument.of(CommandArgumentType.STRING, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), ServerLookAction::new); - this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ? - Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of(DF.format(player.getX()))).toList(), "|") : - Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of("0")).toList(), "|") - ); - this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getY())), "") : Pair.of(List.of("0"), "")); - this.setSuggestion(2, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getZ())), "") : Pair.of(List.of("0"), "")); + super("look", ServerLookAction::new); + declarePlayerBranch(); + declareLocationBranch(); + } + + private void declarePlayerBranch() { + this.fork(0); + this.addArgument("player", EntityArgument.player()) + .setOptional(true); + } + + private void declareLocationBranch() { + this.fork(1); + this.addArgument("location", Vec3Argument.vec3(false)); + } + + @Override + public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException { + EntitySelector selector = context.getArgumentOrDefault("player", EntitySelector.class, null); + Coordinates location = context.getArgumentOrDefault("location", Coordinates.class, null); + CommandSourceStack source = context.getSource(); + if (selector == null && location == null) { + Entity sender = source.getEntityOrException(); + this.setPos(new Vector(sender.getX(), sender.getY(), sender.getZ())); + } else if (selector != null) { + ServerPlayer player = selector.findSinglePlayer(source); + this.setTarget(player); + } else { + Vec3 vector = location.getPosition(source); + this.setPos(new Vector(vector.x, vector.y, vector.z)); + } } @Override @@ -94,6 +115,16 @@ public class ServerLookAction extends ServerBotAction { return target; } + @Override + public void provideActionData(@NotNull ActionData data) { + super.provideActionData(data); + if (target != null) { + data.add("target", target.getName().getString()); + } else { + data.add("position", String.format("(%.2f, %.2f, %.2f)", pos.getX(), pos.getY(), pos.getZ())); + } + } + @Override public boolean doTick(@NotNull ServerBot bot) { if (target != null) { @@ -104,22 +135,6 @@ public class ServerLookAction extends ServerBotAction { return true; } - @Override - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - String nameOrX = result.readString(player.getScoreboardName()); - ServerPlayer player1 = player.getServer().getPlayerList().getPlayerByName(nameOrX); - if (player1 != null) { - this.setTarget(player1); - return; - } - try { - Vector vector = result.readVectorYZ(Double.parseDouble(nameOrX)); - this.setPos(vector); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid vector"); - } - } - @Override public Object asCraft() { return new CraftLookAction(this); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java index 6aeb0c7..286ee10 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java @@ -23,16 +23,15 @@ import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.entity.Vehicle; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; import org.leavesmc.leaves.entity.bot.actions.CraftMountAction; import java.util.Comparator; import java.util.List; -public class ServerMountAction extends ServerBotAction { +public class ServerMountAction extends AbstractBotAction { public ServerMountAction() { - super("mount", CommandArgument.EMPTY, ServerMountAction::new); + super("mount", ServerMountAction::new); } @Override @@ -40,8 +39,8 @@ public class ServerMountAction extends ServerBotAction { Location center = bot.getBukkitEntity().getLocation(); List vehicles = center.getNearbyEntitiesByType( Vehicle.class, - 3, - vehicle -> manhattanDistance(bot, ((CraftEntity) vehicle).getHandle()) <= 2 + 4, + vehicle -> manhattanDistance(bot, ((CraftEntity) vehicle).getHandle()) <= 3 ).stream().sorted(Comparator.comparingDouble( (vehicle) -> center.distanceSquared(vehicle.getLocation()) )).toList(); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java index 9abe654..439d130 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java @@ -17,39 +17,46 @@ package org.leavesmc.leaves.bot.agent.actions; -import net.minecraft.server.level.ServerPlayer; -import org.apache.commons.lang3.tuple.Pair; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; import org.leavesmc.leaves.entity.bot.action.MoveAction.MoveDirection; import org.leavesmc.leaves.entity.bot.actions.CraftMoveAction; import org.leavesmc.leaves.event.bot.BotActionStopEvent; import java.util.Arrays; -import java.util.List; +import java.util.Map; -public class ServerMoveAction extends ServerStateBotAction { +import static java.util.stream.Collectors.toMap; +import static org.leavesmc.leaves.command.ArgumentNode.ArgumentSuggestions.strings; - private static final Pair, String> suggestions = Pair.of( - Arrays.stream(MoveDirection.values()).map((it) -> it.name).toList(), - "" - ); +public class ServerMoveAction extends AbstractStateBotAction { + private static final Map NAME_TO_DIRECTION = Arrays.stream(MoveDirection.values()).collect(toMap( + it -> it.name, + it -> it + )); private MoveDirection direction = MoveDirection.FORWARD; public ServerMoveAction() { - super("move", CommandArgument.of(CommandArgumentType.ofEnum(MoveDirection.class)), ServerMoveAction::new); - this.setSuggestion(0, suggestions); + super("move", ServerMoveAction::new); + this.addArgument("direction", StringArgumentType.word()) + .suggests(strings( + Arrays.stream(MoveDirection.values()) + .map((it) -> it.name) + .toList() + )); } @Override - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - this.direction = result.read(MoveDirection.class); + public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException { + String raw = context.getArgument("direction", String.class); + MoveDirection direction = NAME_TO_DIRECTION.get(raw); if (direction == null) { - throw new IllegalArgumentException("Invalid direction"); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); } + this.direction = direction; } @Override @@ -74,6 +81,12 @@ public class ServerMoveAction extends ServerStateBotAction { return true; } + @Override + public void provideActionData(@NotNull ActionData data) { + super.provideActionData(data); + data.add("direction", direction.name); + } + public MoveDirection getDirection() { return direction; } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java index ba9182a..cfa4383 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java @@ -17,42 +17,44 @@ package org.leavesmc.leaves.bot.agent.actions; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.ServerPlayer; -import org.apache.commons.lang3.tuple.Pair; -import org.bukkit.entity.Player; +import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; import org.leavesmc.leaves.entity.bot.actions.CraftRotationAction; import java.text.DecimalFormat; -import java.util.List; -import java.util.Objects; -public class ServerRotationAction extends ServerBotAction { +public class ServerRotationAction extends AbstractBotAction { private static final DecimalFormat DF = new DecimalFormat("0.00"); public ServerRotationAction() { - super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), ServerRotationAction::new); - this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getYaw())), "[yaw]") : Pair.of(List.of("0"), "")); - this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getPitch())), "[pitch]") : Pair.of(List.of("0"), "")); + super("rotation", ServerRotationAction::new); + this.addArgument("yaw", FloatArgumentType.floatArg(-180, 180)) + .suggests((context, builder) -> builder.suggest( + DF.format(context.getSource().getEntityOrException().getYRot()), + Component.literal("current player yaw") + )) + .setOptional(true); + this.addArgument("pitch", FloatArgumentType.floatArg(-90, 90)) + .suggests((context, builder) -> builder.suggest( + DF.format(context.getSource().getEntityOrException().getXRot()), + Component.literal("current player pitch") + )) + .setOptional(true); } private float yaw = 0.0f; private float pitch = 0.0f; @Override - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - try { - this.yaw = result.readFloat(Objects.requireNonNull(player).getYRot()); - this.pitch = result.readFloat(player.getXRot()); - } catch (Exception e) { - throw new IllegalArgumentException("No valid rotation specified", e); - } + public void loadCommand(@NotNull CommandContext context) throws CommandSyntaxException { + this.yaw = context.getFloatOrDefault("yaw", context.getSource().getEntityOrException().getYRot()); + this.pitch = context.getFloatOrDefault("pitch", context.getSource().getEntityOrException().getXRot()); } public void setYaw(float yaw) { @@ -71,6 +73,13 @@ public class ServerRotationAction extends ServerBotAction return this.pitch; } + @Override + public void provideActionData(@NotNull ActionData data) { + super.provideActionData(data); + data.add("yaw", DF.format(this.yaw)); + data.add("pitch", DF.format(this.pitch)); + } + @Override @NotNull public CompoundTag save(@NotNull CompoundTag nbt) { diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java index 47d5c15..fbe007e 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java @@ -19,14 +19,13 @@ package org.leavesmc.leaves.bot.agent.actions; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction; import org.leavesmc.leaves.event.bot.BotActionStopEvent; -public class ServerSneakAction extends ServerStateBotAction { +public class ServerSneakAction extends AbstractStateBotAction { public ServerSneakAction() { - super("sneak", CommandArgument.EMPTY, ServerSneakAction::new); + super("sneak", ServerSneakAction::new); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java index 89f9666..b7aafff 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java @@ -21,13 +21,12 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction; -public class ServerSwapAction extends ServerBotAction { +public class ServerSwapAction extends AbstractBotAction { public ServerSwapAction() { - super("swap", CommandArgument.EMPTY, ServerSwapAction::new); + super("swap", ServerSwapAction::new); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java index 97a7e64..eb9aad8 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java @@ -20,13 +20,12 @@ package org.leavesmc.leaves.bot.agent.actions; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.command.CommandArgument; import org.leavesmc.leaves.entity.bot.actions.CraftSwimAction; -public class ServerSwimAction extends ServerStateBotAction { +public class ServerSwimAction extends AbstractStateBotAction { public ServerSwimAction() { - super("swim", CommandArgument.EMPTY, ServerSwimAction::new); + super("swim", ServerSwimAction::new); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java deleted file mode 100644 index 842c147..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of Leaves (https://github.com/LeavesMC/Leaves) - * - * Leaves is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Leaves is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Leaves. If not, see . - */ - -package org.leavesmc.leaves.bot.agent.actions; - -import net.minecraft.server.level.ServerPlayer; -import org.apache.commons.lang3.tuple.Pair; -import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentResult; -import org.leavesmc.leaves.command.CommandArgumentType; - -import java.util.List; -import java.util.function.Supplier; - -public abstract class ServerTimerBotAction> extends ServerBotAction { - - public ServerTimerBotAction(String name, Supplier creator) { - this(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator); - } - - public ServerTimerBotAction(String name, CommandArgument argument, Supplier creator) { - super(name, argument, creator); - this.setSuggestion(0, Pair.of(List.of("0"), "[TickDelay]")); - this.setSuggestion(1, Pair.of(List.of("20"), "[TickInterval]")); - this.setSuggestion(2, Pair.of(List.of("1", "-1"), "[DoNumber]")); - } - - @Override - public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { - this.setStartDelayTick(result.readInt(0)); - this.setDoIntervalTick(result.readInt(20)); - this.setDoNumber(result.readInt(1)); - } -} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java index 84f3f5e..6d03d6c 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java @@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction; -public class ServerUseItemAction extends ServerUseBotAction { +public class ServerUseItemAction extends AbstractUseBotAction { public ServerUseItemAction() { super("use", ServerUseItemAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java index ed5792c..b2ed2cc 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java @@ -34,7 +34,7 @@ import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem; import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn; import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo; -public class ServerUseItemAutoAction extends ServerUseBotAction { +public class ServerUseItemAutoAction extends AbstractUseBotAction { public ServerUseItemAutoAction() { super("use_auto", ServerUseItemAutoAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java index 6d8ce4e..20d496f 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java @@ -24,7 +24,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOffhandAction; import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem; -public class ServerUseItemOffhandAction extends ServerUseBotAction { +public class ServerUseItemOffhandAction extends AbstractUseBotAction { public ServerUseItemOffhandAction() { super("use_offhand", ServerUseItemOffhandAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java index 6e0569a..bf0ccf0 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction; -public class ServerUseItemOnAction extends ServerUseBotAction { +public class ServerUseItemOnAction extends AbstractUseBotAction { public ServerUseItemOnAction() { super("use_on", ServerUseItemOnAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java index 9035709..ce9924b 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java @@ -25,7 +25,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnOffhandAction; import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn; -public class ServerUseItemOnOffhandAction extends ServerUseBotAction { +public class ServerUseItemOnOffhandAction extends AbstractUseBotAction { public ServerUseItemOnOffhandAction() { super("use_on_offhand", ServerUseItemOnOffhandAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java index 88d5348..68ba54e 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction; -public class ServerUseItemToAction extends ServerUseBotAction { +public class ServerUseItemToAction extends AbstractUseBotAction { public ServerUseItemToAction() { super("use_to", ServerUseItemToAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java index 806d201..c4bedf1 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java @@ -25,7 +25,7 @@ import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToOffhandAction; import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo; -public class ServerUseItemToOffhandAction extends ServerUseBotAction { +public class ServerUseItemToOffhandAction extends AbstractUseBotAction { public ServerUseItemToOffhandAction() { super("use_to_offhand", ServerUseItemToOffhandAction::new); diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AbstractBotConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AbstractBotConfig.java new file mode 100644 index 0000000..f3bb232 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AbstractBotConfig.java @@ -0,0 +1,119 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.bot.agent.configs; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.kyori.adventure.text.Component; +import net.minecraft.nbt.CompoundTag; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.WrappedArgument; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Supplier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.event.HoverEvent.showText; +import static net.kyori.adventure.text.format.NamedTextColor.AQUA; + +public abstract class AbstractBotConfig> { + private final String name; + private final WrappedArgument argument; + private final Supplier creator; + + protected ServerBot bot; + + public AbstractBotConfig(String name, ArgumentType type, Supplier creator) { + this.name = name; + this.argument = new WrappedArgument<>(name, type); + if (shouldApplySuggestions()) { + this.argument.suggests(this::applySuggestions); + } + this.creator = creator; + } + + @SuppressWarnings("RedundantThrows") + public void applySuggestions(final CommandContext context, final SuggestionsBuilder builder) throws CommandSyntaxException { + } + + public AbstractBotConfig setBot(ServerBot bot) { + this.bot = bot; + return this; + } + + public E create() { + return creator.get(); + } + + public abstract Value getValue(); + + public abstract void setValue(Value value) throws CommandSyntaxException; + + public abstract Value loadFromCommand(@NotNull CommandContext context) throws CommandSyntaxException; + + public List> getExtraData() { + return List.of(); + } + + public String getName() { + return name; + } + + public Component getNameComponent() { + Component result = text(getName(), AQUA); + if (!getExtraData().isEmpty()) { + result = result.hoverEvent(showText( + getExtraData().stream() + .map(pair -> text(pair.getKey() + "=" + pair.getValue())) + .reduce((a, b) -> a.append(text(", ")).append(b)) + .orElseGet(() -> text("")) + )); + } + return result; + } + + public WrappedArgument getArgument() { + return argument; + } + + public ServerBot getBot() { + return bot; + } + + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + nbt.putString("configName", this.name); + return nbt; + } + + public abstract void load(@NotNull CompoundTag nbt); + + private boolean shouldApplySuggestions() { + for (Method method : getClass().getDeclaredMethods()) { + if (method.getName().equals("applySuggestions")) { + return method.getDeclaringClass() != AbstractBotConfig.class; + } + } + return false; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java index 2ace5e2..6f667ef 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java @@ -17,23 +17,17 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.BoolArgumentType; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; - -import java.util.List; - -public class AlwaysSendDataConfig extends AbstractBotConfig { - - public static final String NAME = "always_send_data"; +import org.leavesmc.leaves.command.CommandContext; +public class AlwaysSendDataConfig extends AbstractBotConfig { private boolean value; public AlwaysSendDataConfig() { - super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + super("always_send_data", BoolArgumentType.bool(), AlwaysSendDataConfig::new); this.value = FakeplayerConfig.canSendDataAlways; } @@ -47,16 +41,21 @@ public class AlwaysSendDataConfig extends AbstractBotConfig { this.value = value; } + @Override + public Boolean loadFromCommand(@NotNull CommandContext context) { + return context.getBoolean(getName()); + } + @Override @NotNull public CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putBoolean(NAME, this.getValue()); + nbt.putBoolean(getName(), this.getValue()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSendDataAlways)); + this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSendDataAlways)); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java index 44727a5..2174d5c 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java @@ -17,25 +17,19 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.BoolArgumentType; import fun.bm.lophine.config.modules.experiment.CommandConfig; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.waypoints.ServerWaypointManager; import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; - -import java.util.List; - -public class LocatorBarConfig extends AbstractBotConfig { - - public static final String NAME = "enable_locator_bar"; +import org.leavesmc.leaves.command.CommandContext; +public class LocatorBarConfig extends AbstractBotConfig { private boolean value; public LocatorBarConfig() { - super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + super("enable_locator_bar", BoolArgumentType.bool(), LocatorBarConfig::new); this.value = FakeplayerConfig.enableLocatorBar && CommandConfig.waypoint; } @@ -45,7 +39,7 @@ public class LocatorBarConfig extends AbstractBotConfig { } @Override - public void setValue(Boolean value) throws IllegalArgumentException { + public void setValue(@NotNull Boolean value) throws IllegalArgumentException { this.value = value; ServerWaypointManager manager = this.bot.level().getWaypointManager(); if (value) { @@ -55,15 +49,20 @@ public class LocatorBarConfig extends AbstractBotConfig { } } + @Override + public Boolean loadFromCommand(@NotNull CommandContext context) { + return context.getBoolean(getName()); + } + @Override public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putBoolean(NAME, this.getValue()); + nbt.putBoolean(getName(), this.getValue()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.enableLocatorBar)); + this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.enableLocatorBar && CommandConfig.waypoint)); } } \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java index d5beef2..3bb2ea0 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java @@ -17,22 +17,28 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; -import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; -import java.util.List; +import static net.minecraft.network.chat.Component.literal; -public class SimulationDistanceConfig extends AbstractBotConfig { - - public static final String NAME = "simulation_distance"; +public class SimulationDistanceConfig extends AbstractBotConfig { public SimulationDistanceConfig() { - super(NAME, CommandArgument.of(CommandArgumentType.INTEGER).setSuggestion(0, Pair.of(List.of("2", "10"), ""))); + super("simulation_distance", IntegerArgumentType.integer(2, 32), SimulationDistanceConfig::new); + } + + @Override + public void applySuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) { + builder.suggest("2", literal("Minimum simulation distance")); + builder.suggest("8"); + builder.suggest("12"); + builder.suggest("16"); + builder.suggest("32", literal("Maximum simulation distance")); } @Override @@ -42,22 +48,24 @@ public class SimulationDistanceConfig extends AbstractBotConfig { @Override public void setValue(Integer value) { - if (value < 2 || value > 32) { - throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + value); - } this.bot.getBukkitEntity().setSimulationDistance(value); } + @Override + public Integer loadFromCommand(@NotNull CommandContext context) { + return context.getInteger(getName()); + } + @Override @NotNull public CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putInt(NAME, this.getValue()); + nbt.putInt(getName(), this.getValue()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(nbt.getIntOr(NAME, FakeplayerConfig.getSimulationDistance(this.bot))); + this.setValue(nbt.getIntOr(getName(), FakeplayerConfig.getSimulationDistance(this.bot))); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java index 1f502b9..83a05bd 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java @@ -17,21 +17,16 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.BoolArgumentType; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; -import java.util.List; - -public class SkipSleepConfig extends AbstractBotConfig { - - public static final String NAME = "skip_sleep"; +public class SkipSleepConfig extends AbstractBotConfig { public SkipSleepConfig() { - super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + super("skip_sleep", BoolArgumentType.bool(), SkipSleepConfig::new); } @Override @@ -44,15 +39,20 @@ public class SkipSleepConfig extends AbstractBotConfig { bot.fauxSleeping = value; } + @Override + public Boolean loadFromCommand(@NotNull CommandContext context) { + return context.getBoolean(getName()); + } + @Override public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putBoolean(NAME, this.getValue()); + nbt.putBoolean(getName(), this.getValue()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSkipSleep)); + this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSkipSleep)); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java index 30c2f86..986c389 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java @@ -17,23 +17,20 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.BoolArgumentType; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.command.CommandContext; import java.util.List; -public class SpawnPhantomConfig extends AbstractBotConfig { - - public static final String NAME = "spawn_phantom"; - +public class SpawnPhantomConfig extends AbstractBotConfig { private boolean value; public SpawnPhantomConfig() { - super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + super("spawn_phantom", BoolArgumentType.bool(), SpawnPhantomConfig::new); this.value = FakeplayerConfig.canSpawnPhantom; } @@ -48,22 +45,24 @@ public class SpawnPhantomConfig extends AbstractBotConfig { } @Override - public List getMessage() { - return List.of( - bot.getScoreboardName() + "'s spawn_phantom: " + this.getValue(), - bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks - ); + public List> getExtraData() { + return List.of(Pair.of("not_sleeping_ticks", String.valueOf(bot.notSleepTicks))); + } + + @Override + public Boolean loadFromCommand(@NotNull CommandContext context) { + return context.getBoolean(getName()); } @Override public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putBoolean(NAME, this.getValue()); + nbt.putBoolean(getName(), this.getValue()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSpawnPhantom)); + this.setValue(nbt.getBooleanOr(getName(), FakeplayerConfig.canSpawnPhantom)); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java index 01ab243..7145c9f 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java @@ -17,28 +17,39 @@ package org.leavesmc.leaves.bot.agent.configs; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import fun.bm.lophine.config.modules.function.FakeplayerConfig; import net.minecraft.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.bot.agent.AbstractBotConfig; -import org.leavesmc.leaves.command.CommandArgument; -import org.leavesmc.leaves.command.CommandArgumentType; - -import java.util.List; - -public class TickTypeConfig extends AbstractBotConfig { - - private static final String NAME = "tick_type"; - private static final CommandArgumentType TICK_TYPE_ARGUMENT = CommandArgumentType.ofEnum(ServerBot.TickType.class); +import org.leavesmc.leaves.command.CommandContext; +public class TickTypeConfig extends AbstractBotConfig { private ServerBot.TickType value; public TickTypeConfig() { - super(NAME, CommandArgument.of(TICK_TYPE_ARGUMENT).setSuggestion(0, List.of("network", "entity_list"))); + super("tick_type", StringArgumentType.word(), TickTypeConfig::new); this.value = FakeplayerConfig.tickType; } + @Override + public void applySuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) { + builder.suggest("network"); + builder.suggest("entity_list"); + } + + @Override + public ServerBot.TickType loadFromCommand(@NotNull CommandContext context) throws CommandSyntaxException { + String raw = context.getString(getName()); + return switch (raw) { + case "network" -> ServerBot.TickType.NETWORK; + case "entity_list" -> ServerBot.TickType.ENTITY_LIST; + default -> throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + }; + } + @Override public ServerBot.TickType getValue() { return value; @@ -53,12 +64,17 @@ public class TickTypeConfig extends AbstractBotConfig { @NotNull public CompoundTag save(@NotNull CompoundTag nbt) { super.save(nbt); - nbt.putString(NAME, this.getValue().toString()); + nbt.putString(getName(), this.getValue().toString()); return nbt; } @Override public void load(@NotNull CompoundTag nbt) { - this.setValue(TICK_TYPE_ARGUMENT.parse(nbt.getStringOr(NAME, FakeplayerConfig.tickType.name()))); + String raw = nbt.getStringOr(getName(), FakeplayerConfig.tickType.name()); + this.setValue(switch (raw) { + case "network" -> ServerBot.TickType.NETWORK; + case "entity_list" -> ServerBot.TickType.ENTITY_LIST; + default -> throw new IllegalStateException("Unexpected bot tick type value: " + raw); + }); } } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java deleted file mode 100644 index bc0c3dc..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.leavesmc.leaves.command; - -import org.apache.commons.lang3.tuple.Pair; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.BiFunction; - -public class CommandArgument { - - public static final CommandArgument EMPTY = new CommandArgument(); - - private static final Pair, String> EMPTY_SUGGESTION_RESULT = Pair.of(List.of(), null); - private static final BiFunction, String>> EMPTY_SUGGESTION = (sender, arg) -> EMPTY_SUGGESTION_RESULT; - - private final List, String>>> suggestions; - private final List> argumentTypes; - - private CommandArgument(CommandArgumentType... argumentTypes) { - this.argumentTypes = List.of(argumentTypes); - this.suggestions = new ArrayList<>(); - for (int i = 0; i < argumentTypes.length; i++) { - suggestions.add(EMPTY_SUGGESTION); - } - } - - public static CommandArgument of(CommandArgumentType... argumentTypes) { - return new CommandArgument(argumentTypes); - } - - public List> getArgumentTypes() { - return argumentTypes; - } - - public CommandArgument setSuggestion(int n, BiFunction, String>> suggestion) { - this.suggestions.set(n, suggestion); - return this; - } - - public CommandArgument setSuggestion(int n, Pair, String> suggestion) { - return this.setSuggestion(n, (sender, arg) -> suggestion); - } - - public CommandArgument setSuggestion(int n, List tabComplete) { - return this.setSuggestion(n, Pair.of(tabComplete, null)); - } - - public Pair, String> suggestion(int n, CommandSender sender, String arg) { - if (suggestions.size() > n) { - return suggestions.get(n).apply(sender, arg); - } else { - return EMPTY_SUGGESTION.apply(sender, arg); - } - } - - public CommandArgumentResult parse(int index, String @NotNull [] args) { - Object[] result = new Object[argumentTypes.size()]; - Arrays.fill(result, null); - for (int i = index, j = 0; i < args.length && j < result.length; i++, j++) { - result[j] = argumentTypes.get(j).parse(args[i]); - } - return new CommandArgumentResult(new ArrayList<>(Arrays.asList(result))); - } -} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java deleted file mode 100644 index a56cd7b..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.leavesmc.leaves.command; - -import net.minecraft.core.BlockPos; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Objects; - -public class CommandArgumentResult { - - private final List result; - - public CommandArgumentResult(List result) { - this.result = result; - } - - public int readInt(int def) { - return Objects.requireNonNullElse(read(Integer.class), def); - } - - public double readDouble(double def) { - return Objects.requireNonNullElse(read(Double.class), def); - } - - public float readFloat(float def) { - return Objects.requireNonNullElse(read(Float.class), def); - } - - public String readString(String def) { - return Objects.requireNonNullElse(read(String.class), def); - } - - public boolean readBoolean(boolean def) { - return Objects.requireNonNullElse(read(Boolean.class), def); - } - - public BlockPos readPos() { - Integer[] pos = {read(Integer.class), read(Integer.class), read(Integer.class)}; - for (Integer po : pos) { - if (po == null) { - return null; - } - } - return new BlockPos(pos[0], pos[1], pos[2]); - } - - public @Nullable Vector readVector() { - Double[] pos = {read(Double.class), read(Double.class), read(Double.class)}; - for (Double po : pos) { - if (po == null) { - return null; - } - } - return new Vector(pos[0], pos[1], pos[2]); - } - - public @NotNull Vector readVectorYZ(double x) { - Double[] pos = {x, read(Double.class), read(Double.class)}; - for (Double po : pos) { - if (po == null) { - throw new IllegalArgumentException("Failed to read vector!"); - } - } - return new Vector(pos[0], pos[1], pos[2]); - } - - public Object readObject() { - if (result.isEmpty()) { - return null; - } - return result.removeFirst(); - } - - public T read(Class tClass, T def) { - return Objects.requireNonNullElse(read(tClass), def); - } - - public T read(Class tClass) { - if (result.isEmpty()) { - return null; - } - - Object obj = result.removeFirst(); - if (tClass.isInstance(obj)) { - return tClass.cast(obj); - } else { - return null; - } - } -} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java deleted file mode 100644 index b39f6f4..0000000 --- a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.leavesmc.leaves.command; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -public abstract class CommandArgumentType { - - public static final CommandArgumentType STRING = CommandArgumentType.string(); - public static final CommandArgumentType INTEGER = CommandArgumentType.of(Integer.class, Integer::parseInt); - public static final CommandArgumentType DOUBLE = CommandArgumentType.of(Double.class, Double::parseDouble); - public static final CommandArgumentType FLOAT = CommandArgumentType.of(Float.class, Float::parseFloat); - public static final CommandArgumentType BOOLEAN = CommandArgumentType.of(Boolean.class, Boolean::parseBoolean); - - private final Class type; - - private CommandArgumentType(Class type) { - this.type = type; - } - - @NotNull - @Contract(value = "_, _ -> new", pure = true) - public static CommandArgumentType of(Class type, Function parse) { - return new CommandArgumentType<>(type) { - @Override - public E parse(@NotNull String arg) { - try { - return parse.apply(arg); - } catch (Exception ignore) { - return null; - } - } - }; - } - - @NotNull - @Contract(value = "_ -> new", pure = true) - public static > CommandArgumentType ofEnum(Class type) { - return of(type, (string -> Enum.valueOf(type, string.toUpperCase()))); - } - - @NotNull - @Contract(value = " -> new", pure = true) - private static CommandArgumentType string() { - return new CommandArgumentType<>(String.class) { - @Override - public String parse(@NotNull String arg) { - return arg; - } - }; - } - - public Class getType() { - return type; - } - - public abstract E parse(@NotNull String arg); -} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotArgument.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotArgument.java new file mode 100644 index 0000000..792e098 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotArgument.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.command.bot; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.network.chat.Component; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.CustomArgumentType; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public class BotArgument implements CustomArgumentType { + @Override + public ArgumentType getBaseArgumentType() { + return StringArgumentType.word(); + } + + @Override + public ServerBot transform(String value) throws CommandSyntaxException { + ServerBot bot = BotList.INSTANCE.getBotByName(value); + if (bot == null) { + throw new CommandSyntaxException( + CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument(), + Component.literal("Bot with name '" + value + "' does not exist") + ); + } + return bot; + } + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + Collection bots = BotList.INSTANCE.bots; + if (bots.isEmpty()) { + return builder + .suggest("", Component.literal("There are no bots in the server, create one first.")) + .buildFuture(); + } + bots.stream().map(ServerBot::getScoreboardName).forEach(builder::suggest); + return builder.buildFuture(); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotCommand.java new file mode 100644 index 0000000..b4dfc24 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotCommand.java @@ -0,0 +1,58 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.command.RootNode; +import org.leavesmc.leaves.command.bot.subcommands.*; + +import static org.leavesmc.leaves.command.CommandUtils.registerPermissions; + +public class BotCommand extends RootNode { + private static final String PERM_BASE = "bukkit.command.bot"; + + public BotCommand() { + super("bot", PERM_BASE); + this.children( + ListCommand::new, + ConfigCommand::new, + RemoveCommand::new, + LoadCommand::new, + SaveCommand::new, + ActionCommand::new, + CreateCommand::new + ); + } + + @Override + protected ArgumentBuilder compile() { + registerPermissions(PERM_BASE, this.children); + return super.compile(); + } + + public static boolean hasPermission(@NotNull CommandSender sender) { + return sender.hasPermission(PERM_BASE); + } + + public static boolean hasPermission(@NotNull CommandSender sender, String... subcommand) { + return hasPermission(PERM_BASE, sender, subcommand); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotSubcommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotSubcommand.java new file mode 100644 index 0000000..4c4ebda --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/BotSubcommand.java @@ -0,0 +1,34 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot; + +import net.minecraft.commands.CommandSourceStack; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.command.LiteralNode; + +public abstract class BotSubcommand extends LiteralNode { + + protected BotSubcommand(String name) { + super(name); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return BotCommand.hasPermission(source.getSender(), this.name); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ActionCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ActionCommand.java new file mode 100644 index 0000000..09691e5 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ActionCommand.java @@ -0,0 +1,59 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.minecraft.commands.CommandSourceStack; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.CustomArgumentNode; +import org.leavesmc.leaves.command.bot.BotSubcommand; +import org.leavesmc.leaves.command.bot.subcommands.action.ListCommand; +import org.leavesmc.leaves.command.bot.subcommands.action.StartCommand; +import org.leavesmc.leaves.command.bot.subcommands.action.StopCommand; + +public class ActionCommand extends BotSubcommand { + + public ActionCommand() { + super("action"); + children(BotArgument::new); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return FakeplayerConfig.canUseAction && super.requires(source); + } + + public static class BotArgument extends CustomArgumentNode { + + private BotArgument() { + super("bot", new org.leavesmc.leaves.command.bot.BotArgument()); + children( + StartCommand::new, + StopCommand::new, + ListCommand::new + ); + } + + public static @NotNull ServerBot getBot(@NotNull CommandContext context) throws CommandSyntaxException { + return context.getCustomArgument(BotArgument.class); + } + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ConfigCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ConfigCommand.java new file mode 100644 index 0000000..6c66d2c --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ConfigCommand.java @@ -0,0 +1,151 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.minecraft.commands.CommandSourceStack; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.bot.agent.configs.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.CustomArgumentNode; +import org.leavesmc.leaves.command.LiteralNode; +import org.leavesmc.leaves.command.bot.BotSubcommand; + +import java.util.Collection; +import java.util.function.Supplier; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; +import static net.kyori.adventure.text.format.NamedTextColor.AQUA; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; + +public class ConfigCommand extends BotSubcommand { + + public ConfigCommand() { + super("config"); + children(BotArgument::new); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return FakeplayerConfig.canModifyConfig && super.requires(source); + } + + private static class BotArgument extends CustomArgumentNode { + + private BotArgument() { + super("bot", new org.leavesmc.leaves.command.bot.BotArgument()); + Configs.getConfigs().stream() + .map(this::configNodeCreator) + .forEach(this::children); + } + + @Contract(pure = true) + private @NotNull Supplier configNodeCreator(AbstractBotConfig config) { + return () -> new ConfigNode<>(config); + } + + public static @NotNull ServerBot getBot(@NotNull CommandContext context) throws CommandSyntaxException { + return context.getCustomArgument(BotArgument.class); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + ServerBot bot = BotArgument.getBot(context); + CommandSender sender = context.getSender(); + Collection> botConfigs = bot.getAllConfigs(); + sender.sendMessage(join(spaces(), + text("Bot", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("configs:", GRAY) + )); + for (AbstractBotConfig botConfig : botConfigs) { + sender.sendMessage(join(spaces(), + botConfig.getNameComponent(), + text("=", GRAY), + text(String.valueOf(botConfig.getValue()), AQUA) + )); + } + return true; + } + } + + private static class ConfigNode extends LiteralNode { + private final AbstractBotConfig config; + + private ConfigNode(@NotNull AbstractBotConfig config) { + super(config.getName()); + this.config = config; + } + + @Override + protected ArgumentBuilder compileBase() { + RequiredArgumentBuilder argument = config.getArgument() + .compile() + .executes(mojangCtx -> { + CommandContext ctx = new CommandContext(mojangCtx); + return executeSet(ctx) ? 1 : 0; + }); + return super.compileBase() + .then(argument); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerBot bot = BotArgument.getBot(context); + AbstractBotConfig botConfig = bot.getConfig(config); + context.getSender().sendMessage(join(spaces(), + text("Bot", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("config", GRAY), + botConfig.getNameComponent(), + text("is", GRAY), + text(String.valueOf(bot.getConfig(config).getValue()), AQUA) + )); + return true; + } + + private boolean executeSet(CommandContext context) throws CommandSyntaxException { + ServerBot bot = BotArgument.getBot(context); + AbstractBotConfig botConfig = bot.getConfig(config); + try { + botConfig.setValue(botConfig.loadFromCommand(context)); + } catch (ClassCastException e) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + context.getSender().sendMessage(join(spaces(), + text("Bot", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("config", GRAY), + botConfig.getNameComponent(), + text("changed to", GRAY), + text(String.valueOf(botConfig.getValue()), AQUA) + )); + return true; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/CreateCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/CreateCommand.java new file mode 100644 index 0000000..2b60582 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/CreateCommand.java @@ -0,0 +1,168 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.DimensionArgument; +import net.minecraft.commands.arguments.coordinates.Coordinates; +import net.minecraft.commands.arguments.coordinates.Vec3Argument; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.BotCreateState; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.command.ArgumentNode; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.bot.BotSubcommand; +import org.leavesmc.leaves.event.bot.BotCreateEvent; + +import static net.kyori.adventure.text.Component.text; +import static net.minecraft.commands.arguments.DimensionArgument.getDimension; + +public class CreateCommand extends BotSubcommand { + + public CreateCommand() { + super("create"); + children(NameArgument::new); + } + + protected static boolean handleCreateCommand(@NotNull CommandContext context) throws CommandSyntaxException { + CommandSender sender = context.getSender(); + + String name = context.getArgument(NameArgument.class); + if (!canCreate(sender, name)) { + return false; + } + String skinName = context.getArgumentOrDefault(SkinNameArgument.class, name); + + World world; + try { + world = getDimension(context.getMojangContext(), "world").getWorld(); + } catch (IllegalArgumentException e) { + if (!(sender instanceof Entity entity)) { + sender.sendMessage(text("Must specify world and location when executed by console", NamedTextColor.RED)); + return false; + } + world = entity.getWorld(); + } + + Location location = Bukkit.getWorlds().getFirst().getSpawnLocation(); + Coordinates coords = context.getArgumentOrDefault(LocationArgument.class, null); + if (coords != null) { + Vec3 vec3 = coords.getPosition(context.getSource()); + location = new Location(world, vec3.x, vec3.y, vec3.z); + } else if (sender instanceof Entity entity) { + location = entity.getLocation(); + } + + BotCreateState + .builder(name, location) + .createReason(BotCreateEvent.CreateReason.COMMAND) + .skinName(skinName) + .creator(sender) + .spawnWithSkin(null); + + return true; + } + + private static boolean canCreate(CommandSender sender, @NotNull String name) { + BotList botList = BotList.INSTANCE; + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + sender.sendMessage(text("This name is illegal, bot name must be 4-16 characters and contain only letters, numbers, and underscores.", NamedTextColor.RED)); + return false; + } + + if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) { + sender.sendMessage(text("This bot is already in server", NamedTextColor.RED)); + return false; + } + + if (FakeplayerConfig.unableNames.contains(name)) { + sender.sendMessage(text("This name is not allowed in this server", NamedTextColor.RED)); + return false; + } + + if (botList.bots.size() >= FakeplayerConfig.limit) { + sender.sendMessage(text("Bot number limit exceeded", NamedTextColor.RED)); + return false; + } + + return true; + } + + private static class NameArgument extends ArgumentNode { + + private NameArgument() { + super("name", StringArgumentType.word()); + children(SkinNameArgument::new); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + return handleCreateCommand(context); + } + } + + private static class SkinNameArgument extends ArgumentNode { + + private SkinNameArgument() { + super("skin_name", StringArgumentType.word()); + children(WorldArgument::new); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + return handleCreateCommand(context); + } + } + + private static class WorldArgument extends ArgumentNode { + + private WorldArgument() { + super("world", DimensionArgument.dimension()); + children(LocationArgument::new); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return source.getSender() instanceof ConsoleCommandSender; + } + } + + private static class LocationArgument extends ArgumentNode { + + private LocationArgument() { + super("location", Vec3Argument.vec3(true)); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + return handleCreateCommand(context); + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ListCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ListCommand.java new file mode 100644 index 0000000..57d6ac1 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/ListCommand.java @@ -0,0 +1,115 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.arguments.DimensionArgument; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.ArgumentNode; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.bot.BotSubcommand; + +import java.util.List; +import java.util.Objects; + +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.noSeparators; +import static net.kyori.adventure.text.event.HoverEvent.showText; +import static net.kyori.adventure.text.format.NamedTextColor.*; +import static net.minecraft.commands.arguments.DimensionArgument.getDimension; + + +public class ListCommand extends BotSubcommand { + + public ListCommand() { + super("list"); + children(WorldArgument::new); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + Component msg = Bukkit.getWorlds().stream() + .map(ListCommand::getBotListMessage) + .filter(Objects::nonNull) + .reduce((a, b) -> a.append(text("\n")).append(b)) + .orElseGet(() -> text("No bots on the server", GRAY)); + context.getSender().sendMessage(join(noSeparators(), + text("Total bot number: ", GRAY), + text(BotList.INSTANCE.bots.size(), AQUA).hoverEvent(showText(text("current bot count"))), + text("/", GRAY), + text(FakeplayerConfig.limit, AQUA).hoverEvent(showText(text("bot count limit"))) + )); + context.getSender().sendMessage(msg); + return true; + } + + protected static @Nullable Component getBotListMessage(@NotNull World world) { + List botsInLevel = BotList.INSTANCE.bots.stream() + .filter((bot) -> bot.getBukkitEntity().getWorld().equals(world)) + .toList(); + if (botsInLevel.isEmpty()) { + return null; + } + + Component botsMsg = botsInLevel.stream() + .map(Player::getDisplayName) + .map(PaperAdventure::asAdventure) + .reduce((a, b) -> a.append(text(", ", GRAY)).append(b)) + .get(); + String worldLocation = ((CraftWorld) world).getHandle().dimension().location().toString(); + return join(noSeparators(), + text(world.getName(), AQUA).hoverEvent(showText(text(worldLocation))), + text(" (" + botsInLevel.size() + ")\n", GRAY), + botsMsg + ); + } + + private static class WorldArgument extends ArgumentNode { + + private WorldArgument() { + super("world", DimensionArgument.dimension()); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerLevel dimension = getDimension(context.getMojangContext(), "world"); + Component botListMessage = getBotListMessage(dimension.getWorld()); + CommandSender sender = context.getSender(); + if (botListMessage == null) { + sender.sendMessage(text("No bots in that world", RED)); + } else { + sender.sendMessage(text("Bot in ").append(botListMessage)); + } + return true; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/LoadCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/LoadCommand.java new file mode 100644 index 0000000..31c8884 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/LoadCommand.java @@ -0,0 +1,96 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.commands.CommandSourceStack; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.ArgumentNode; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.bot.BotSubcommand; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; + +public class LoadCommand extends BotSubcommand { + + public LoadCommand() { + super("load"); + children(BotNameArgument::new); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return FakeplayerConfig.canManualSaveAndLoad && super.requires(source); + } + + private static class BotNameArgument extends ArgumentNode { + + private BotNameArgument() { + super("bot_name", StringArgumentType.word()); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + String botName = context.getArgument(BotNameArgument.class); + BotList botList = BotList.INSTANCE; + CommandSender sender = context.getSender(); + if (!botList.getSavedBotList().contains(botName)) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + + ServerBot bot = botList.loadNewBot(botName); + if (bot == null) { + sender.sendMessage(text("Failed to load bot, please check log", NamedTextColor.RED)); + return false; + } + sender.sendMessage(join( + spaces(), + text("Successfully loaded bot", NamedTextColor.GRAY), + asAdventure(bot.getDisplayName()) + )); + return true; + } + + @Override + protected CompletableFuture getSuggestions(CommandContext context, @NotNull SuggestionsBuilder builder) { + BotList botList = BotList.INSTANCE; + Set bots = botList.getSavedBotList().keySet(); + if (bots.isEmpty()) { + return builder + .suggest("", net.minecraft.network.chat.Component.literal("There are no bots saved before, save one first.")) + .buildFuture(); + } + bots.forEach(builder::suggest); + return builder.buildFuture(); + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/RemoveCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/RemoveCommand.java new file mode 100644 index 0000000..ae7354b --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/RemoveCommand.java @@ -0,0 +1,178 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.adventure.PaperAdventure; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.ArgumentNode; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.CustomArgumentNode; +import org.leavesmc.leaves.command.bot.BotSubcommand; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; +import static net.kyori.adventure.text.format.NamedTextColor.*; +import static net.minecraft.network.chat.Component.literal; + +public class RemoveCommand extends BotSubcommand { + + public RemoveCommand() { + super("remove"); + children(BotArgument::new); + } + + private static boolean removeBot(@NotNull ServerBot bot, @Nullable CommandSender sender) { + boolean success = BotList.INSTANCE.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + if (!success) { + sender = sender == null ? Bukkit.getConsoleSender() : sender; + sender.sendMessage(text("Bot remove canceled by a plugin", RED)); + } + return success; + } + + private static class BotArgument extends CustomArgumentNode { + + private BotArgument() { + super("bot", new org.leavesmc.leaves.command.bot.BotArgument()); + children(RemoveTimeArgument::new); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerBot bot = context.getCustomArgument(BotArgument.class); + return removeBot(bot, context.getSender()); + } + } + + private static class RemoveTimeArgument extends ArgumentNode { + + private RemoveTimeArgument() { + super("remove_time", StringArgumentType.word()); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + String removeTimeStr = context.getArgument("remove_time", String.class); + int removeTimeSeconds = parseRemoveTime(removeTimeStr); + ServerBot bot = context.getCustomArgument(BotArgument.class); + CommandSender sender = context.getSender(); + + boolean isReschedule = bot.removeTaskId != -1; + + if (isReschedule) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + } + bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> { + bot.removeTaskId = -1; + removeBot(bot, sender); + }, removeTimeSeconds * 20L).getTaskId(); + + sender.sendMessage(join(spaces(), + text("Bot", GRAY), + PaperAdventure.asAdventure(bot.getDisplayName()), + text("scheduled for removal in", GRAY), + text(formatSeconds(removeTimeSeconds), AQUA), + text(isReschedule ? "(rescheduled)" : "", GRAY) + )); + return true; + } + + private static int parseRemoveTime(String timeStr) throws CommandSyntaxException { + if (timeStr == null || timeStr.trim().isEmpty()) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + + if (!timeStr.matches("^[\\d\\shmsHMS]+$")) { + throw new CommandSyntaxException( + CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(), + literal("Invalid time format: " + timeStr) + ); + } + + String remaining = timeStr.replaceAll("\\d+[hmsHMS]", "").trim(); + if (!remaining.isEmpty() && remaining.matches(".*\\d+.*")) { + throw new CommandSyntaxException( + CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(), + literal("Found trailing numbers without unit: " + timeStr) + ); + } + + Matcher matcher = Pattern.compile("(\\d+)([hmsHMS])").matcher(timeStr); + int seconds = 0; + boolean foundMatch = false; + + while (matcher.find()) { + foundMatch = true; + int value = Integer.parseInt(matcher.group(1)); + switch (matcher.group(2).toLowerCase()) { + case "h": + seconds += value * 3600; + break; + case "m": + seconds += value * 60; + break; + case "s": + seconds += value; + break; + } + } + + if (!foundMatch) { + throw new CommandSyntaxException( + CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException(), + literal("No valid time units found in: " + timeStr) + ); + } + + return seconds; + } + + private static @NotNull String formatSeconds(int totalSeconds) { + int h = totalSeconds / 3600; + int m = (totalSeconds % 3600) / 60; + int s = totalSeconds % 60; + StringBuilder sb = new StringBuilder(); + if (h > 0) { + sb.append(h).append("h"); + } + if (m > 0) { + sb.append(m).append("m"); + } + if (s > 0) { + sb.append(s).append("s"); + } + if (sb.isEmpty()) { + sb.append("0s"); + } + return sb.toString(); + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/SaveCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/SaveCommand.java new file mode 100644 index 0000000..75c1851 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/SaveCommand.java @@ -0,0 +1,75 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import fun.bm.lophine.config.modules.function.FakeplayerConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.commands.CommandSourceStack; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.CustomArgumentNode; +import org.leavesmc.leaves.command.bot.BotSubcommand; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; + +public class SaveCommand extends BotSubcommand { + + public SaveCommand() { + super("save"); + children(BotArgument::new); + } + + @Override + public boolean requires(@NotNull CommandSourceStack source) { + return FakeplayerConfig.canManualSaveAndLoad && super.requires(source); + } + + private static class BotArgument extends CustomArgumentNode { + + private BotArgument() { + super("bot", new org.leavesmc.leaves.command.bot.BotArgument()); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerBot bot = context.getCustomArgument(BotArgument.class); + CommandSender sender = context.getSender(); + BotList botList = BotList.INSTANCE; + + boolean success = botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true); + if (success) { + sender.sendMessage(join(spaces(), + text("Successfully saved bot", NamedTextColor.GRAY), + asAdventure(bot.getDisplayName()), + text("as " + bot.createState.realName(), NamedTextColor.GRAY) + )); + } else { + sender.sendMessage(text("Bot save canceled by a plugin", NamedTextColor.RED)); + } + return success; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/ListCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/ListCommand.java new file mode 100644 index 0000000..3c65fa4 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/ListCommand.java @@ -0,0 +1,70 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands.action; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.LiteralNode; +import org.leavesmc.leaves.command.bot.subcommands.ActionCommand; + +import java.util.List; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; +import static net.kyori.adventure.text.event.HoverEvent.showText; +import static net.kyori.adventure.text.format.NamedTextColor.AQUA; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; + +public class ListCommand extends LiteralNode { + + public ListCommand() { + super("list"); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerBot bot = ActionCommand.BotArgument.getBot(context); + + CommandSender sender = context.getSender(); + List> actions = bot.getBotActions(); + if (actions.isEmpty()) { + sender.sendMessage(text("This bot has no active actions", GRAY)); + return true; + } + + sender.sendMessage( + asAdventure(bot.getDisplayName()) + .append(text("'s action list:", GRAY)) + ); + for (int i = 0; i < actions.size(); i++) { + AbstractBotAction action = actions.get(i); + sender.sendMessage(join(spaces(), + text(i, GRAY), + text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))) + )); + } + + return true; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java new file mode 100644 index 0000000..2ef4faa --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java @@ -0,0 +1,132 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands.action; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.LiteralNode; +import org.leavesmc.leaves.command.WrappedArgument; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; +import static net.kyori.adventure.text.event.HoverEvent.showText; +import static net.kyori.adventure.text.format.NamedTextColor.AQUA; +import static net.kyori.adventure.text.format.NamedTextColor.GRAY; +import static org.leavesmc.leaves.command.bot.subcommands.ActionCommand.BotArgument.getBot; + +public class StartCommand extends LiteralNode { + + public StartCommand() { + super("start"); + Actions.getAll() + .stream() + .map(this::actionNodeCreator) + .forEach(this::children); + } + + private boolean handleStartCommand(CommandContext context, @NotNull AbstractBotAction action) throws CommandSyntaxException { + ServerBot bot = getBot(context); + CommandSender sender = context.getSender(); + + action.loadCommand(context); + if (bot.addBotAction(action, sender)) { + sender.sendMessage(join(spaces(), + text("Action", GRAY), + text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))), + text("has been issued to", GRAY), + asAdventure(bot.getDisplayName()) + )); + } + + return true; + } + + @Contract(pure = true) + private @NotNull Supplier actionNodeCreator(AbstractBotAction action) { + return () -> new ActionLiteralNode(action); + } + + private class ActionLiteralNode extends LiteralNode { + private final AbstractBotAction action; + + private ActionLiteralNode(@NotNull AbstractBotAction action) { + super(action.getName()); + this.action = action; + } + + @Override + protected ArgumentBuilder compile() { + ArgumentBuilder builder = super.compile(); + + Map>>> arguments = action.getArguments(); + Command executor = context -> { + if (handleStartCommand(new CommandContext(context), action)) { + return Command.SINGLE_SUCCESS; + } else { + return 0; + } + }; + for (Map.Entry>>> entry : arguments.entrySet()) { + List>> value = entry.getValue(); + ArgumentBuilder branchArgumentBuilder = null; + + for (Pair> stringWrappedArgumentPair : value.reversed()) { + WrappedArgument argument = stringWrappedArgumentPair.getRight(); + if (branchArgumentBuilder == null) { + branchArgumentBuilder = argument.compile().executes(executor); + } else { + branchArgumentBuilder = argument.compile().then(branchArgumentBuilder); + if (argument.isOptional()) { + branchArgumentBuilder = branchArgumentBuilder.executes(executor); + } + } + } + + if (value.getFirst().getRight().isOptional() || value.isEmpty()) { + builder = builder.executes(executor); + } + + if (branchArgumentBuilder != null) { + builder = builder.then(branchArgumentBuilder); + } + } + + if (arguments.isEmpty()) { + builder = builder.executes(executor); + } + + return builder; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java new file mode 100644 index 0000000..4f9b0fc --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StopCommand.java @@ -0,0 +1,161 @@ +/* + * This file is part of Leaves (https://github.com/LeavesMC/Leaves) + * + * Leaves is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaves is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Leaves. If not, see . + */ + +package org.leavesmc.leaves.command.bot.subcommands.action; + +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.network.chat.Component; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.command.ArgumentNode; +import org.leavesmc.leaves.command.CommandContext; +import org.leavesmc.leaves.command.LiteralNode; +import org.leavesmc.leaves.command.bot.subcommands.ActionCommand; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static io.papermc.paper.adventure.PaperAdventure.asAdventure; +import static net.kyori.adventure.text.Component.join; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.JoinConfiguration.spaces; +import static net.kyori.adventure.text.event.HoverEvent.showText; +import static net.kyori.adventure.text.format.NamedTextColor.*; + +public class StopCommand extends LiteralNode { + + public StopCommand() { + super("stop"); + children(StopIndexArgument::new); + children(StopAll::new); + } + + private static class StopIndexArgument extends ArgumentNode { + + private StopIndexArgument() { + super("index", IntegerArgumentType.integer(0)); + } + + @Override + protected CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + ServerBot bot = ActionCommand.BotArgument.getBot(context); + + for (int i = 0; i < bot.getBotActions().size(); i++) { + AbstractBotAction action = bot.getBotActions().get(i); + builder.suggest(String.valueOf(i), Component.literal(action.getName())); + } + + return builder.buildFuture(); + } + + @Override + protected boolean execute(CommandContext context) throws CommandSyntaxException { + ServerBot bot = ActionCommand.BotArgument.getBot(context); + CommandSender sender = context.getSender(); + + int index = context.getArgument(StopIndexArgument.class); + int maxIndex = bot.getBotActions().size() - 1; + if (maxIndex < 0) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + if (index > maxIndex) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().create(index, maxIndex); + } + + AbstractBotAction action = bot.getBotActions().get(index); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + action.stop(bot, BotActionStopEvent.Reason.COMMAND); + bot.getBotActions().remove(index); + sender.sendMessage(join(spaces(), + text("Already stopped", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("action", GRAY), + text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))) + )); + } else { + sender.sendMessage(text("Action stop cancelled by a plugin", RED)); + } + return true; + } + } + + private static class StopAll extends LiteralNode { + + private StopAll() { + super("all"); + } + + @Override + protected boolean execute(@NotNull CommandContext context) throws CommandSyntaxException { + ServerBot bot = ActionCommand.BotArgument.getBot(context); + + List> actions = bot.getBotActions(); + CommandSender sender = context.getSender(); + if (actions.isEmpty()) { + sender.sendMessage(text("This bot has no active actions", GRAY)); + return true; + } + + Set> canceled = new HashSet<>(); + Set> forRemoval = new HashSet<>(); + for (AbstractBotAction action : actions) { + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + forRemoval.add(action); + action.stop(bot, BotActionStopEvent.Reason.COMMAND); + } else { + canceled.add(action); + } + } + bot.getBotActions().removeAll(forRemoval); + + if (canceled.isEmpty()) { + sender.sendMessage(join(spaces(), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("'s action list cleared", GRAY) + )); + } else { + sender.sendMessage(join(spaces(), + text("Tried to clear", GRAY), + asAdventure(bot.getDisplayName()).append(text("'s", GRAY)), + text("'s action list, but following actions' stop was canceled by plugin:", GRAY) + )); + for (AbstractBotAction action : canceled) { + context.getSender().sendMessage( + text(action.getName(), AQUA).hoverEvent(showText(text(action.getActionDataString()))) + ); + } + } + return true; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java index a5ca4a8..6b7c489 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java @@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.leavesmc.leaves.bot.BotList; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; import org.leavesmc.leaves.entity.bot.action.BotAction; import org.leavesmc.leaves.entity.bot.actions.CraftBotAction; import org.leavesmc.leaves.event.bot.BotActionStopEvent; @@ -57,13 +57,17 @@ public class CraftBot extends CraftPlayer implements Bot { @Override public > void addAction(@NotNull T action) { - switch (action) { - case CraftBotAction act -> this.getHandle().addBotAction(act.getHandle(), null); - default -> - throw new IllegalArgumentException("Action " + action.getClass().getName() + " is not a valid BotAction type!"); + if (action instanceof CraftBotAction act) { + this.getHandle().addBotAction(act.getHandle(), null); + } else { + throw new IllegalArgumentException("Action " + action.getClass().getName() + " is not a valid BotAction type!"); } } + public void addAction(@NotNull AbstractBotAction action) { + this.getHandle().addBotAction(action, null); + } + @Override public BotAction getAction(int index) { return (BotAction) this.getHandle().getBotActions().get(index).asCraft(); @@ -81,15 +85,14 @@ public class CraftBot extends CraftPlayer implements Bot { @Override public void stopAllActions() { - for (ServerBotAction action : this.getHandle().getBotActions()) { + for (AbstractBotAction action : this.getHandle().getBotActions()) { action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); } } @Override public boolean remove(boolean save) { - BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); - return true; + return BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); } @Override diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java index 419a554..67cbcca 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java @@ -26,7 +26,7 @@ import org.leavesmc.leaves.bot.BotCreateState; import org.leavesmc.leaves.bot.BotList; import org.leavesmc.leaves.bot.ServerBot; import org.leavesmc.leaves.bot.agent.Actions; -import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; import org.leavesmc.leaves.entity.bot.action.BotAction; import org.leavesmc.leaves.event.bot.BotCreateEvent; @@ -72,7 +72,7 @@ public class CraftBotManager implements BotManager { @SuppressWarnings("unchecked") @Override public > T newAction(@NotNull Class type) { - ServerBotAction action = Actions.getForClass(type); + AbstractBotAction action = Actions.getForClass(type); if (action == null) { throw new IllegalArgumentException("No action registered for type: " + type.getName()); } else { diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java index 2b23e93..5f2cba1 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java @@ -19,14 +19,14 @@ package org.leavesmc.leaves.entity.bot.actions; import org.jetbrains.annotations.NotNull; import org.leavesmc.leaves.bot.ServerBot; -import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; import org.leavesmc.leaves.entity.bot.action.BotAction; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; -public abstract class CraftBotAction, S extends ServerBotAction> implements BotAction { +public abstract class CraftBotAction, S extends AbstractBotAction> implements BotAction { protected final S serverAction; protected final Function creator; @@ -40,7 +40,7 @@ public abstract class CraftBotAction, S extends ServerBot this.creator = creator; } - public ServerBotAction getHandle() { + public AbstractBotAction getHandle() { return serverAction; } diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java index 743b3bd..94a419a 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java @@ -17,12 +17,12 @@ package org.leavesmc.leaves.entity.bot.actions; -import org.leavesmc.leaves.bot.agent.actions.ServerTimerBotAction; +import org.leavesmc.leaves.bot.agent.actions.AbstractTimerBotAction; import org.leavesmc.leaves.entity.bot.action.TimerBotAction; import java.util.function.Function; -public class CraftTimerBotAction, S extends ServerTimerBotAction> extends CraftBotAction implements TimerBotAction { +public class CraftTimerBotAction, S extends AbstractTimerBotAction> extends CraftBotAction implements TimerBotAction { public CraftTimerBotAction(S serverAction, Function creator) { super(serverAction, creator);