diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java index c434364ff..8c8d1034d 100644 --- a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java @@ -4,8 +4,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.leavesmc.leaves.entity.bot.action.BotAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomActionProvider; import java.util.Collection; +import java.util.List; import java.util.UUID; /** @@ -44,5 +47,13 @@ public interface BotManager { */ > T newAction(@NotNull Class type); + CustomAction newCustomAction(String actionId); + + void registerAction(CustomActionProvider executor); + + void unregisterAction(String id); + + List getCustomActions(); + BotCreator botCreator(@NotNull String realName, @NotNull Location location); } diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BrigadierLikeProcessor.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BrigadierLikeProcessor.java new file mode 100644 index 000000000..d6bfb39ce --- /dev/null +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BrigadierLikeProcessor.java @@ -0,0 +1,10 @@ +package org.leavesmc.leaves.entity.bot.action.custom; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; + +public interface BrigadierLikeProcessor extends CommandProcessor { + + void buildCommand(LiteralArgumentBuilder root); + +} diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BukkitLikeProcessor.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BukkitLikeProcessor.java new file mode 100644 index 000000000..c4374f260 --- /dev/null +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/BukkitLikeProcessor.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action.custom; + +import org.bukkit.command.CommandSender; + +import java.util.List; + +public interface BukkitLikeProcessor extends CommandProcessor { + + List getSuggestion(CommandSender sender, String[] args); + + void loadAction(CommandSender sender, String[] args, CustomAction action); +} diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CommandProcessor.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CommandProcessor.java new file mode 100644 index 000000000..f822832eb --- /dev/null +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CommandProcessor.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action.custom; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface CommandProcessor { + + @ApiStatus.Internal + CommandProcessor DUMMY_PROCESSOR = new CommandProcessor() { + + }; +} diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomAction.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomAction.java new file mode 100644 index 000000000..94a70e164 --- /dev/null +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomAction.java @@ -0,0 +1,10 @@ +package org.leavesmc.leaves.entity.bot.action.custom; + +import org.leavesmc.leaves.entity.bot.action.BotAction; + +public interface CustomAction extends BotAction { + + T getMeta(String key); + + void setMeta(String key, T meta); +} diff --git a/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomActionProvider.java b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomActionProvider.java new file mode 100644 index 000000000..436228253 --- /dev/null +++ b/leaves-api/src/main/java/org/leavesmc/leaves/entity/bot/action/custom/CustomActionProvider.java @@ -0,0 +1,25 @@ +package org.leavesmc.leaves.entity.bot.action.custom; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.ApiStatus; +import org.leavesmc.leaves.entity.bot.Bot; + +public abstract class CustomActionProvider { + + private CommandProcessor processor = CommandProcessor.DUMMY_PROCESSOR; + + public abstract String id(); + + public abstract Plugin provider(); + + public abstract boolean doTick(Bot bot, CustomAction action); + + public void withProcessor(CommandProcessor processor) { + this.processor = processor; + } + + @ApiStatus.Internal + public CommandProcessor processor() { + return processor; + } +} diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java index 214165eb4..c0742248a 100644 --- a/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java +++ b/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import org.leavesmc.leaves.bot.agent.actions.*; import org.leavesmc.leaves.entity.bot.action.*; +import org.leavesmc.leaves.entity.bot.action.custom.CustomActionProvider; import java.util.Collection; import java.util.HashMap; @@ -16,7 +17,10 @@ public class Actions { private static final Map> actionsByName = new HashMap<>(); private static final Map, AbstractBotAction> actionsByClass = new HashMap<>(); - static { + + private static final Map customActionsById = new HashMap<>(); + + static { register(new ServerAttackAction(), AttackAction.class); register(new ServerBreakBlockAction(), BreakBlockAction.class); register(new ServerDropAction(), DropAction.class); @@ -60,6 +64,22 @@ public static boolean unregister(@NotNull String name) { return false; } + public static void addCustom(CustomActionProvider provider) { + customActionsById.put(provider.id(), provider); + } + + public static @Nullable CustomActionProvider getCustom(String id) { + return customActionsById.get(id); + } + + public static void removeCustom(String id) { + customActionsById.remove(id); + } + + public static Collection getCustomActions() { + return customActionsById.keySet(); + } + @NotNull @Contract(pure = true) public static Collection> getAll() { diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/custom/ServerCustomAction.java b/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/custom/ServerCustomAction.java new file mode 100644 index 000000000..848df5aa9 --- /dev/null +++ b/leaves-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/custom/ServerCustomAction.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.actions.custom; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.AbstractBotAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomActionProvider; +import org.leavesmc.leaves.entity.bot.actions.CraftCustomAction; + +import java.util.HashMap; +import java.util.Map; + +public class ServerCustomAction extends AbstractBotAction { + + private final CustomActionProvider customActionProvider; + + private final Map metaMap = new HashMap<>(); + + public ServerCustomAction(CustomActionProvider customActionProvider) { + super(customActionProvider.id(), null); // Don't create here + this.customActionProvider = customActionProvider; + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + return customActionProvider.doTick(bot.getBukkitEntity(), (CustomAction) this.asCraft()); + } + + @Override + public Object asCraft() { + return new CraftCustomAction(this); + } + + @SuppressWarnings("unchecked") + public T getMeta(String key) { + return (T) metaMap.get(key); + } + + public void setMeta(String key, T meta) { + metaMap.put(key, meta); + } +} diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java b/leaves-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java index 126c8ea2b..adfb32645 100644 --- a/leaves-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java +++ b/leaves-server/src/main/java/org/leavesmc/leaves/command/bot/subcommands/action/StartCommand.java @@ -1,9 +1,13 @@ package org.leavesmc.leaves.command.bot.subcommands.action; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.Contract; @@ -11,12 +15,19 @@ 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.bot.agent.actions.custom.ServerCustomAction; +import org.leavesmc.leaves.command.ArgumentNode; import org.leavesmc.leaves.command.CommandContext; import org.leavesmc.leaves.command.LiteralNode; import org.leavesmc.leaves.command.WrappedArgument; +import org.leavesmc.leaves.entity.bot.action.custom.BukkitLikeProcessor; +import org.leavesmc.leaves.entity.bot.action.custom.CustomAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomActionProvider; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import static io.papermc.paper.adventure.PaperAdventure.asAdventure; @@ -33,6 +44,7 @@ public class StartCommand extends LiteralNode { public StartCommand() { super("start"); Actions.getAll().stream().map(this::actionNodeCreator).forEach(this::children); + this.children.add(new CustomActionNode()); } private boolean handleStartCommand(CommandContext context, @NotNull AbstractBotAction action) throws CommandSyntaxException { @@ -109,4 +121,61 @@ private ActionLiteralNode(@NotNull AbstractBotAction action) { return builder; } } + + private static class CustomActionNode extends ArgumentNode { + + protected CustomActionNode() { + super("custom", StringArgumentType.greedyString()); + } + + @Override + protected boolean execute(CommandContext context) { + try { + ServerBot bot = getBot(context); + CommandSender sender = context.getSender(); + String[] args = StringUtils.split(context.getArgument("custom", String.class), ' '); + CustomActionProvider provider = Actions.getCustom(args[0]); + if (provider == null || !(provider.processor() instanceof BukkitLikeProcessor processor)) { + return false; + } + String[] realArg = Arrays.copyOfRange(args, 1, args.length); + ServerCustomAction action = new ServerCustomAction(provider); + processor.loadAction(sender, realArg, (CustomAction) action.asCraft()); + 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; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + @Override + protected CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + try { + String[] args = builder.getRemaining().split(" ", -1); + if (args.length <= 1) { + Actions.getCustomActions().forEach(builder::suggest); + } else { + CustomActionProvider provider = Actions.getCustom(args[0]); + if (provider == null || !(provider.processor() instanceof BukkitLikeProcessor processor)) { + return builder.buildFuture(); + } + String[] realArg = Arrays.copyOfRange(args, 1, args.length); + List suggestion = processor.getSuggestion(context.getSender(), realArg); + builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); + suggestion.forEach(builder::suggest); + } + } catch (Exception e) { + e.printStackTrace(); + } + return builder.buildFuture(); + } + } } diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java b/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java index 550985c48..eb2d4d92f 100644 --- a/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java +++ b/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java @@ -10,11 +10,15 @@ 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.bot.agent.actions.custom.ServerCustomAction; import org.leavesmc.leaves.entity.bot.action.BotAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomActionProvider; import org.leavesmc.leaves.event.bot.BotCreateEvent; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.UUID; public class CraftBotManager implements BotManager { @@ -67,6 +71,30 @@ public > T newAction(@NotNull Class type) { } } + @Override + public CustomAction newCustomAction(String actionId) { + CustomActionProvider provider = Actions.getCustom(actionId); + if (provider == null) { + throw new IllegalArgumentException("Can't find custom action " + actionId); + } + return (CustomAction) new ServerCustomAction(provider).asCraft(); + } + + @Override + public void registerAction(CustomActionProvider provider) { + Actions.addCustom(provider); + } + + @Override + public void unregisterAction(String id) { + Actions.removeCustom(id); + } + + @Override + public List getCustomActions() { + return List.of(); + } + @Override public BotCreator botCreator(@NotNull String realName, @NotNull Location location) { return BotCreateState.builder(realName, location).createReason(BotCreateEvent.CreateReason.PLUGIN); diff --git a/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftCustomAction.java b/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftCustomAction.java new file mode 100644 index 000000000..cf9350813 --- /dev/null +++ b/leaves-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftCustomAction.java @@ -0,0 +1,21 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.custom.ServerCustomAction; +import org.leavesmc.leaves.entity.bot.action.custom.CustomAction; + +public class CraftCustomAction extends CraftBotAction implements CustomAction { + + public CraftCustomAction(ServerCustomAction serverAction) { + super(serverAction, CraftCustomAction::new); + } + + @Override + public T getMeta(String key) { + return serverAction.getMeta(key); + } + + @Override + public void setMeta(String key, T meta) { + serverAction.setMeta(key, meta); + } +}