From cff1ddaceacd014f95c8b4b4b14a4ace8cecf522 Mon Sep 17 00:00:00 2001 From: Lumine1909 <133463833+Lumine1909@users.noreply.github.com> Date: Sat, 14 Sep 2024 22:03:21 -0400 Subject: [PATCH] Update bot API --- patches/api/0003-Add-fakeplayer-api.patch | 430 ++++++++++++++++--- patches/server/0010-Fakeplayer-support.patch | 426 +++++++++++------- 2 files changed, 645 insertions(+), 211 deletions(-) diff --git a/patches/api/0003-Add-fakeplayer-api.patch b/patches/api/0003-Add-fakeplayer-api.patch index 69c6868b..1021e7f4 100644 --- a/patches/api/0003-Add-fakeplayer-api.patch +++ b/patches/api/0003-Add-fakeplayer-api.patch @@ -63,10 +63,10 @@ index 594deedd08c3b3255fe6838471d945759f09a182..6fa638198f75458177af795f00250ce9 } diff --git a/src/main/java/org/leavesmc/leaves/entity/Bot.java b/src/main/java/org/leavesmc/leaves/entity/Bot.java new file mode 100644 -index 0000000000000000000000000000000000000000..e5cf058a5c7bf14c0c2f017d4b6fd3bd8ed0aa3d +index 0000000000000000000000000000000000000000..4ebb865b3e31d69281fb4076a5c12bd2189bc78b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/Bot.java -@@ -0,0 +1,35 @@ +@@ -0,0 +1,72 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.entity.Player; @@ -97,43 +97,140 @@ index 0000000000000000000000000000000000000000..e5cf058a5c7bf14c0c2f017d4b6fd3bd + @NotNull + public String getRealName(); + ++ /** ++ * Gets the creator's UUID of the fakeplayer ++ * ++ * @return creator's UUID ++ */ + @Nullable + public UUID getCreatePlayerUUID(); + -+ public boolean remove(boolean save); ++ /** ++ * Add an action to the fakeplayer ++ * ++ * @param action bot action ++ */ ++ public void addAction(@NotNull LeavesBotAction action); ++ ++ /** ++ * Get the action in giving index ++ * ++ * @param index index of actions ++ * @return Action of that index ++ */ ++ public LeavesBotAction getAction(int index); ++ ++ ++ /** ++ * Stop the action in giving index ++ * ++ * @param index index of actions ++ */ ++ public void stopAction(int index); ++ ++ /** ++ * Stop all the actions of the fakeplayer ++ * ++ */ ++ public void stopAllActions(); ++ ++ /** ++ * Remove the fakeplayer ++ */ ++ public void remove(boolean save); +} diff --git a/src/main/java/org/leavesmc/leaves/entity/BotCreator.java b/src/main/java/org/leavesmc/leaves/entity/BotCreator.java new file mode 100644 -index 0000000000000000000000000000000000000000..fd18396f8d127f3f044ee407e4c8a3dc7b2c02b5 +index 0000000000000000000000000000000000000000..fe6789578a0ff47ad15977c67e063064b80d742f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/BotCreator.java -@@ -0,0 +1,20 @@ +@@ -0,0 +1,80 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.event.bot.BotCreateEvent; + -+public interface BotCreator { ++import java.util.Objects; + -+ public BotCreator name(String name); ++public class BotCreator { + -+ public BotCreator skinName(String skinName); ++ private String name; ++ private Location location; ++ private String skinName; ++ private String[] skin; ++ private CommandSender creator; ++ private boolean forceName = false; + -+ public BotCreator skin(String[] skin); ++ public BotCreator(@NotNull String name, @Nullable Location location) { ++ Objects.requireNonNull(name); ++ this.name = name; ++ this.location = location; ++ } + -+ public BotCreator mojangAPISkin(); ++ public BotCreator name(@NotNull String name) { ++ Objects.requireNonNull(name); ++ this.name = name; ++ return this; ++ } + -+ public BotCreator location(@NotNull Location location); ++ public BotCreator skinName(@Nullable String skinName) { ++ this.skinName = skinName; ++ return this; ++ } + -+ public BotCreator creator(CommandSender creator); ++ public BotCreator skin(@Nullable String[] skin) { ++ this.skin = skin; ++ return this; ++ } ++ ++ public BotCreator location(@NotNull Location location) { ++ this.location = location; ++ return this; ++ } ++ ++ public BotCreator forceName(boolean forceName) { ++ this.forceName = forceName; ++ return this; ++ } ++ ++ public BotCreator creator(@NotNull CommandSender creator) { ++ this.creator = creator; ++ return this; ++ } ++ ++ public String getName() { ++ return name; ++ } ++ ++ public Location getLocation() { ++ return location; ++ } ++ ++ public CommandSender getCreator() { ++ return creator; ++ } ++ ++ public String getSkinName() { ++ return skinName; ++ } ++ ++ public String[] getSkin() { ++ return skin; ++ } ++ ++ public boolean isForceName() { ++ return forceName; ++ } +} diff --git a/src/main/java/org/leavesmc/leaves/entity/BotManager.java b/src/main/java/org/leavesmc/leaves/entity/BotManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..1724336de0aec0b569d4d3456448c63be3021018 +index 0000000000000000000000000000000000000000..36f036c964714279544df5d10e665fac4eda45df --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/BotManager.java -@@ -0,0 +1,62 @@ +@@ -0,0 +1,76 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.Location; @@ -192,13 +289,67 @@ index 0000000000000000000000000000000000000000..1724336de0aec0b569d4d3456448c63b + */ + public boolean unregisterCustomBotAction(String name); + -+ public BotCreator botCreator(@NotNull String realName, @NotNull Location location); ++ /** ++ * Create a bot and apply skin ++ * you can not get the bot instance instantly because get skin in on async thread ++ * ++ * @param creator BotCreator ++ * @param consumer Consumer ++ */ ++ public void createBotWithSkin(@NotNull BotCreator creator, @Nullable Consumer consumer); + -+ public void createBot(@NotNull BotCreator creator, @Nullable Consumer consumer); ++ /** ++ * Create a bot directly ++ * ++ * @param creator BotCreator ++ * @param consumer Consumer ++ */ ++ public Bot createBot(@NotNull BotCreator creator, @Nullable Consumer consumer); ++ ++} +diff --git a/src/main/java/org/leavesmc/leaves/entity/botaction/BotActionType.java b/src/main/java/org/leavesmc/leaves/entity/botaction/BotActionType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..601a40b28211027db66063681130c0f25fd62152 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/entity/botaction/BotActionType.java +@@ -0,0 +1,34 @@ ++package org.leavesmc.leaves.entity.botaction; ++ ++/** ++ * A Leaves bot action enum ++ */ ++public enum BotActionType { ++ ATTACK("attack"), ++ BREAK("break"), ++ DROP("drop"), ++ FISH("fish"), ++ JUMP("jump"), ++ LOOK("look"), ++ ROTATE("rotate"), ++ ROTATION("rotation"), ++ SNEAK("sneak"), ++ STOP("stop"), ++ SWIM("swim"), ++ USE("use"), ++ USE_ON("use_on"), ++ USE_TO("use_to"), ++ USE_OFFHAND("use_offhand"), ++ USE_ON_OFFHAND("use_on_offhand"), ++ USE_TO_OFFHAND("use_to_offhand"); ++ ++ private final String name; ++ ++ private BotActionType(String name) { ++ this.name = name; ++ } ++ ++ public String getName() { ++ return name; ++ } +} diff --git a/src/main/java/org/leavesmc/leaves/entity/botaction/CustomBotAction.java b/src/main/java/org/leavesmc/leaves/entity/botaction/CustomBotAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..0b1648013d5f03d064c0719c231981082ab563be +index 0000000000000000000000000000000000000000..d0b047a059ed28e6b988cbe66e3d5a66c21912b1 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/botaction/CustomBotAction.java @@ -0,0 +1,52 @@ @@ -231,7 +382,7 @@ index 0000000000000000000000000000000000000000..0b1648013d5f03d064c0719c23198108 + * @param args passed action arguments + * @return a new action instance with given args + */ -+ public @Nullable CustomBotAction getNew(Player player, String[] args); ++ public @Nullable CustomBotAction getNew(@Nullable Player player, String[] args); + + /** + * Requests a list of possible completions for a action argument. @@ -256,50 +407,88 @@ index 0000000000000000000000000000000000000000..0b1648013d5f03d064c0719c23198108 +} diff --git a/src/main/java/org/leavesmc/leaves/entity/botaction/LeavesBotAction.java b/src/main/java/org/leavesmc/leaves/entity/botaction/LeavesBotAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..3437aea093fd67536e73a1068f17167057d935c2 +index 0000000000000000000000000000000000000000..f93c4c5de44c9f2b9d4a8f4336886dff75001934 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/botaction/LeavesBotAction.java -@@ -0,0 +1,34 @@ +@@ -0,0 +1,72 @@ +package org.leavesmc.leaves.entity.botaction; + -+/** -+ * A Leaves bot action enum -+ */ -+public enum LeavesBotAction { -+ ATTACK("attack"), -+ BREAK("break"), -+ DROP("drop"), -+ FISH("fish"), -+ JUMP("jump"), -+ LOOK("look"), -+ ROTATE("rotate"), -+ ROTATION("rotation"), -+ SNEAK("sneak"), -+ STOP("stop"), -+ SWIM("swim"), -+ USE("use"), -+ USE_ON("use_on"), -+ USE_TO("use_to"), -+ USE_OFFHAND("use_offhand"), -+ USE_ON_OFFHAND("use_on_offhand"), -+ USE_TO_OFFHAND("use_to_offhand"); ++import org.bukkit.entity.Player; ++import org.jetbrains.annotations.Nullable; + -+ private final String name; ++import java.util.UUID; + -+ private LeavesBotAction(String name) { -+ this.name = name; ++public class LeavesBotAction { ++ ++ private final String actionName; ++ private int tickToExecute; ++ private int executeInterval; ++ private int remainingExecuteTime; ++ private final UUID uuid; ++ private Player actionPlayer; ++ ++ public LeavesBotAction(BotActionType type, int executeInterval, int remainingExecuteTime) { ++ this(type.getName(), executeInterval, remainingExecuteTime, UUID.randomUUID()); + } + -+ public String getName() { -+ return name; ++ public LeavesBotAction(String name, int executeInterval, int remainingExecuteTime) { ++ this(name, executeInterval, remainingExecuteTime, UUID.randomUUID()); ++ } ++ ++ protected LeavesBotAction(String name, int executeInterval, int remainingExecuteTime, UUID actionUUID) { ++ this.actionName = name; ++ this.remainingExecuteTime = remainingExecuteTime; ++ this.executeInterval = executeInterval; ++ this.uuid = actionUUID; ++ tickToExecute = executeInterval; ++ } ++ ++ public void setTickToExecute(int tickToExecute) { ++ this.tickToExecute = tickToExecute; ++ } ++ ++ public int getTickToExecute() { ++ return tickToExecute; ++ } ++ ++ public void setExecuteInterval(int executeInterval) { ++ this.executeInterval = executeInterval; ++ } ++ ++ public int getExecuteInterval() { ++ return executeInterval; ++ } ++ ++ public void setRemainingExecuteTime(int remainingExecuteTime) { ++ this.remainingExecuteTime = remainingExecuteTime; ++ } ++ ++ public int getRemainingExecuteTime() { ++ return remainingExecuteTime; ++ } ++ ++ public String getActionName() { ++ return actionName; ++ } ++ ++ public UUID getUuid() { ++ return uuid; ++ } ++ ++ public void setActionPlayer(Player actionPlayer) { ++ this.actionPlayer = actionPlayer; ++ } ++ ++ public @Nullable Player getActionPlayer() { ++ return actionPlayer; + } +} diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..e85efcaef9f3575b6a85d57b02b2d814b21ef6b9 +index 0000000000000000000000000000000000000000..465a854a9d8b123326af0b6a72af016bc399079b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java -@@ -0,0 +1,50 @@ +@@ -0,0 +1,29 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.event.Cancellable; @@ -309,12 +498,10 @@ index 0000000000000000000000000000000000000000..e85efcaef9f3575b6a85d57b02b2d814 + +import java.util.UUID; + -+public class BotActionEvent extends BotEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); ++public abstract class BotActionEvent extends BotEvent { + + private final String actionName; + private final UUID actionUUID; -+ private boolean cancel = false; + + public BotActionEvent(@NotNull Bot who, String actionName, UUID actionUUID) { + super(who); @@ -330,6 +517,84 @@ index 0000000000000000000000000000000000000000..e85efcaef9f3575b6a85d57b02b2d814 + public UUID getActionUUID() { + return actionUUID; + } ++} +diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb65a99832708ccb2d0077797681fe131bc76fe4 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java +@@ -0,0 +1,47 @@ ++package org.leavesmc.leaves.event.bot; ++ ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.entity.Bot; ++ ++import java.util.UUID; ++ ++public class BotActionExecuteEvent extends BotActionEvent implements Cancellable { ++ public enum Result { ++ PASS, SOFT_CANCEL, HARD_CANCEL ++ } ++ private static final HandlerList handlers = new HandlerList(); ++ ++ private Result result = Result.PASS; ++ ++ public BotActionExecuteEvent(@NotNull Bot who, String actionName, UUID actionUUID) { ++ super(who, actionName, actionUUID); ++ } ++ @Override ++ public @NotNull HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return result != Result.PASS; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.result = cancel ? Result.SOFT_CANCEL : Result.PASS; ++ } ++ ++ public void hardCancel() { ++ this.result = Result.HARD_CANCEL; ++ } ++ ++ public Result getResult() { ++ return this.result; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b7690e396dbc24542ce466edbe995a38c2e82635 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java +@@ -0,0 +1,38 @@ ++package org.leavesmc.leaves.event.bot; ++ ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.entity.Bot; ++ ++import java.util.UUID; ++ ++public class BotActionScheduleEvent extends BotActionEvent implements Cancellable { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ ++ private boolean cancel = false; ++ ++ public BotActionScheduleEvent(@NotNull Bot who, String actionName, UUID actionUUID) { ++ super(who, actionName, actionUUID); ++ } + + @Override + public @NotNull HandlerList getHandlers() { @@ -350,6 +615,48 @@ index 0000000000000000000000000000000000000000..e85efcaef9f3575b6a85d57b02b2d814 + return handlers; + } +} +\ No newline at end of file +diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41492657921f9d55d0cdd635fd55635ae1a62716 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java +@@ -0,0 +1,35 @@ ++package org.leavesmc.leaves.event.bot; ++ ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.entity.Bot; ++ ++import java.util.UUID; ++ ++public class BotActionStopEvent extends BotActionEvent { ++ public enum Reason { ++ DONE, COMMAND, PLUGIN, INTERNAL ++ } ++ ++ private static final HandlerList handlers = new HandlerList(); ++ ++ private final Reason reason; ++ ++ public BotActionStopEvent(@NotNull Bot who, String actionName, UUID actionUUID, Reason stopReason) { ++ super(who, actionName, actionUUID); ++ this.reason = stopReason; ++ } ++ ++ @Override ++ public @NotNull HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++ ++ public Reason getReason() { ++ return reason; ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..1ebdd3657e615a825918d455691cc1d6df78cfba @@ -644,10 +951,10 @@ index 0000000000000000000000000000000000000000..ad358081f1e1da4075243d7ca0a01c1f +} diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e07b9fc10f +index 0000000000000000000000000000000000000000..8b68286711c2341008933aec51a58de94a4dae70 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java -@@ -0,0 +1,46 @@ +@@ -0,0 +1,45 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.entity.Player; @@ -683,7 +990,6 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0 + return player; + } + -+ + @Override + public @NotNull HandlerList getHandlers() { + return handlers; @@ -832,7 +1138,7 @@ index 0000000000000000000000000000000000000000..ff3243f420e8c64ea2675e8c8712b851 +} diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a651361094853 +index 0000000000000000000000000000000000000000..a61464bb64a1c6d899d5cd841d6f3021c3eab531 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java @@ -0,0 +1,107 @@ @@ -865,15 +1171,15 @@ index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a6513 + private final RemoveReason reason; + private final CommandSender remover; + private Component removeMessage; -+ private boolean saved; ++ private boolean save; + private boolean cancel = false; + -+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage, boolean saved) { ++ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage, boolean save) { + super(who); + this.reason = reason; + this.remover = remover; + this.removeMessage = removeMessage; -+ this.saved = saved; ++ this.save = save; + } + + /** @@ -924,12 +1230,12 @@ index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a6513 + this.cancel = cancel; + } + -+ public boolean isSaved() { -+ return saved; ++ public boolean shouldSave() { ++ return save; + } + -+ public void setSaved(boolean saved) { -+ this.saved = saved; ++ public void setSave(boolean save) { ++ this.save = save; + } + + @Override diff --git a/patches/server/0010-Fakeplayer-support.patch b/patches/server/0010-Fakeplayer-support.patch index b61bea1a..0cf5ff37 100644 --- a/patches/server/0010-Fakeplayer-support.patch +++ b/patches/server/0010-Fakeplayer-support.patch @@ -626,10 +626,10 @@ index 8dd85b9ca3b3e3429de4d0ec0654982589c6e93e..de9f63fb3b8dcf11a9271794850ce448 return event; diff --git a/src/main/java/org/leavesmc/leaves/bot/BotCommand.java b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java new file mode 100644 -index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d9554cd20c8 +index 0000000000000000000000000000000000000000..a273dc7e5dd16db17de3acb94f25454d2987e070 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java -@@ -0,0 +1,522 @@ +@@ -0,0 +1,524 @@ +package org.leavesmc.leaves.bot; + +import io.papermc.paper.command.CommandUtil; @@ -657,6 +657,8 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 +import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.Bot; ++import org.leavesmc.leaves.entity.BotCreator; ++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; @@ -798,7 +800,7 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 + + String botName = args[1]; + if (this.canCreate(sender, botName)) { -+ BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()).createReason(BotCreateEvent.CreateReason.COMMAND).creator(sender); ++ BotCreator builder = new BotCreator(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()).creator(sender); + + if (args.length >= 3) { + builder.skinName(args[2]); @@ -821,8 +823,7 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 + } + } + } -+ -+ builder.createAsyncWithSkin(null); ++ new BotCreateState(builder, BotCreateEvent.CreateReason.COMMAND).createAsyncWithSkin(null); + } + } + @@ -957,7 +958,7 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 + + String index = args[3]; + if (index.equals("all")) { -+ bot.getBotActions().clear(); ++ bot.getBotActions().forEach(action -> action.stop(bot, BotActionStopEvent.Reason.COMMAND)); + sender.sendMessage(bot.getScoreboardName() + "'s action list cleared."); + } else { + try { @@ -967,7 +968,8 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 + return; + } + -+ BotAction action = bot.getBotActions().remove(i); ++ BotAction action = bot.getBotActions().get(i); ++ action.stop(bot, BotActionStopEvent.Reason.COMMAND); + sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped."); + } catch (NumberFormatException e) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); @@ -1154,10 +1156,10 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java new file mode 100644 -index 0000000000000000000000000000000000000000..6df68cdbeeed27f4b1cc81a33f70689fae5dd339 +index 0000000000000000000000000000000000000000..959a7c9e7fa6a7cf472f2323dfd2feaf89cd7104 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java -@@ -0,0 +1,115 @@ +@@ -0,0 +1,109 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.server.MinecraftServer; @@ -1167,6 +1169,7 @@ index 0000000000000000000000000000000000000000..6df68cdbeeed27f4b1cc81a33f70689f +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.entity.Bot; +import org.leavesmc.leaves.entity.BotCreator; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; @@ -1174,105 +1177,99 @@ index 0000000000000000000000000000000000000000..6df68cdbeeed27f4b1cc81a33f70689f +import java.util.Objects; +import java.util.function.Consumer; + -+public record BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) { ++public class BotCreateState { ++ ++ private final String realName; ++ private final String name; ++ private final String skinName; ++ private String[] skin; ++ private final Location location; ++ private final BotCreateEvent.CreateReason createReason; ++ private final CommandSender creator; ++ ++ public BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) { ++ this.realName = realName; ++ this.name = name; ++ this.skinName = skinName; ++ this.skin = skin; ++ this.location = location; ++ this.createReason = createReason; ++ this.creator = creator; ++ } + + private static final MinecraftServer server = MinecraftServer.getServer(); + ++ public BotCreateState(BotCreator creator, BotCreateEvent.CreateReason reason) { ++ this(creator.getName(), ++ creator.isForceName() ? LeavesConfig.fakeplayerPrefix + creator.getName() + LeavesConfig.fakeplayerSuffix : creator.getName(), ++ creator.getSkinName(), ++ creator.getSkin(), ++ creator.getLocation(), ++ reason, ++ creator.getCreator()); ++ } ++ ++ + public ServerBot createNow() { + return server.getBotList().createNewBot(this); + } + -+ @NotNull -+ public static Builder builder(@NotNull String realName, @Nullable Location location) { -+ return new Builder(realName, location); ++ public BotCreateState build() { ++ return new BotCreateState(realName, name, skinName, skin, location, createReason, creator); + } + -+ public static class Builder implements BotCreator { -+ -+ private final String realName; -+ -+ private String name; -+ private Location location; -+ -+ private String skinName; -+ private String[] skin; -+ -+ private BotCreateEvent.CreateReason createReason; -+ private CommandSender creator; -+ -+ private Builder(@NotNull String realName, @Nullable Location location) { -+ Objects.requireNonNull(realName); -+ -+ this.realName = realName; -+ this.location = location; -+ -+ this.name = LeavesConfig.fakeplayerPrefix + realName + LeavesConfig.fakeplayerSuffix; -+ this.skinName = this.realName; -+ this.skin = null; -+ this.createReason = BotCreateEvent.CreateReason.UNKNOWN; -+ this.creator = null; -+ } -+ -+ public Builder name(@NotNull String name) { -+ Objects.requireNonNull(name); -+ this.name = name; -+ return this; -+ } -+ -+ public Builder skinName(@Nullable String skinName) { -+ this.skinName = skinName; -+ return this; -+ } ++ public void createAsyncWithSkin(Consumer consumer) { ++ Bukkit.getScheduler().runTaskAsynchronously(MinecraftInternalPlugin.INSTANCE, () -> this.mojangAPISkin().create(consumer)); ++ } + -+ public Builder skin(@Nullable String[] skin) { -+ this.skin = skin; -+ return this; ++ public BotCreateState mojangAPISkin() { ++ if (this.skinName != null) { ++ this.skin = MojangAPI.getSkin(this.skinName); + } ++ return this; ++ } + -+ public Builder mojangAPISkin() { -+ if (this.skinName != null) { -+ this.skin = MojangAPI.getSkin(this.skinName); ++ public void create(Consumer consumer) { ++ Objects.requireNonNull(this.location); ++ if (!server.isSameThread()) { ++ server.executeIfPossible(() -> this.create(consumer)); ++ } else { ++ ServerBot bot = this.build().createNow(); ++ if (bot != null && consumer != null) { ++ consumer.accept(bot); + } -+ return this; + } ++ } + -+ public Builder location(@NotNull Location location) { -+ this.location = location; -+ return this; -+ } ++ public String name() { ++ return name; ++ } + -+ public Builder createReason(@NotNull BotCreateEvent.CreateReason createReason) { -+ Objects.requireNonNull(createReason); -+ this.createReason = createReason; -+ return this; -+ } ++ public String[] skin() { ++ return skin; ++ } + -+ public Builder creator(CommandSender creator) { -+ this.creator = creator; -+ return this; -+ } ++ public String skinName() { ++ return skinName; ++ } + -+ public BotCreateState build() { -+ return new BotCreateState(realName, name, skinName, skin, location, createReason, creator); -+ } ++ public CommandSender creator() { ++ return creator; ++ } + -+ public void createAsyncWithSkin(Consumer consumer) { -+ Bukkit.getScheduler().runTaskAsynchronously(MinecraftInternalPlugin.INSTANCE, () -> this.mojangAPISkin().create(consumer)); -+ } ++ public Location location() { ++ return location; ++ } + -+ public void create(Consumer consumer) { -+ Objects.requireNonNull(this.location); -+ if (!server.isSameThread()) { -+ server.executeIfPossible(() -> this.create(consumer)); -+ } else { -+ ServerBot bot = this.build().createNow(); -+ if (bot != null && consumer != null) { -+ consumer.accept(bot); -+ } -+ } -+ } ++ public BotCreateEvent.CreateReason createReason() { ++ return createReason; ++ } ++ ++ public String realName() { ++ return realName; + } +} +\ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java b/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..53bece66534df40ef8cf559c12e2c472a791b9c3 @@ -1599,7 +1596,7 @@ index 0000000000000000000000000000000000000000..4f5e6e5c1b9d8bd38c98e97fd31b3833 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotList.java b/src/main/java/org/leavesmc/leaves/bot/BotList.java new file mode 100644 -index 0000000000000000000000000000000000000000..8357ada4c3a237021df58fee33d66a5a9b0bceaa +index 0000000000000000000000000000000000000000..8626d1e1169e042a9450d377cd4cb23431f2d183 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotList.java @@ -0,0 +1,338 @@ @@ -1808,12 +1805,12 @@ index 0000000000000000000000000000000000000000..8357ada4c3a237021df58fee33d66a5a + } + } + -+ public void removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) { -+ this.removeBot(bot, reason, remover, saved, this.dataStorage); ++ public void removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean save) { ++ this.removeBot(bot, reason, remover, save, this.dataStorage); + } + -+ public void removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved, IPlayerDataStorage playerIO) { -+ BotRemoveEvent event = new BotRemoveEvent(bot.getBukkitEntity(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)), saved); ++ public void removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean save, IPlayerDataStorage playerIO) { ++ BotRemoveEvent event = new BotRemoveEvent(bot.getBukkitEntity(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)), save); + this.server.server.getPluginManager().callEvent(event); + + if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) { @@ -1829,7 +1826,7 @@ index 0000000000000000000000000000000000000000..8357ada4c3a237021df58fee33d66a5a + bot.doTick(); + } + -+ if (event.isSaved()) { ++ if (event.shouldSave()) { + playerIO.save(bot); + } else { + bot.dropAll(); @@ -2128,10 +2125,10 @@ index 0000000000000000000000000000000000000000..517e3321b866abe9d17a6fe9e919528b +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java new file mode 100644 -index 0000000000000000000000000000000000000000..e48bb6e0e9b745d23637529dd421613d39af3bcd +index 0000000000000000000000000000000000000000..9f94dc133ca2439e62df408c1a74b1a4a52b0baa --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java -@@ -0,0 +1,545 @@ +@@ -0,0 +1,542 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.ImmutableMap; @@ -2185,8 +2182,9 @@ index 0000000000000000000000000000000000000000..e48bb6e0e9b745d23637529dd421613d +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.bot.agent.Configs; ++import org.leavesmc.leaves.entity.BotCreator; +import org.leavesmc.leaves.entity.CraftBot; -+import org.leavesmc.leaves.event.bot.BotActionEvent; ++import org.leavesmc.leaves.event.bot.BotActionScheduleEvent; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotDeathEvent; +import org.leavesmc.leaves.event.bot.BotInventoryOpenEvent; @@ -2544,7 +2542,7 @@ index 0000000000000000000000000000000000000000..e48bb6e0e9b745d23637529dd421613d + this.setShiftKeyDown(nbt.getBoolean("isShiftKeyDown")); + + CompoundTag createNbt = nbt.getCompound("createStatus"); -+ BotCreateState.Builder createBuilder = BotCreateState.builder(createNbt.getString("realName"), null).name(createNbt.getString("name")); ++ BotCreator createBuilder = new BotCreator(createNbt.getString("realName"), null).name(createNbt.getString("name")); + + String[] skin = null; + if (createNbt.contains("skin")) { @@ -2556,9 +2554,9 @@ index 0000000000000000000000000000000000000000..e48bb6e0e9b745d23637529dd421613d + } + + createBuilder.skinName(createNbt.getString("skinName")).skin(skin); -+ createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null); ++ createBuilder.creator(null); + -+ this.createState = createBuilder.build(); ++ this.createState = new BotCreateState(createBuilder, BotCreateEvent.CreateReason.INTERNAL); + this.gameProfile = new BotList.CustomGameProfile(this.getUUID(), this.createState.name(), this.createState.skin()); + + @@ -2641,13 +2639,9 @@ index 0000000000000000000000000000000000000000..e48bb6e0e9b745d23637529dd421613d + if (!LeavesConfig.fakeplayerUseAction) { + return false; + } -+ -+ BotActionEvent event = new BotActionEvent(this.getBukkitEntity(), action.getName(), action.getUUID()); -+ Bukkit.getPluginManager().callEvent(event); -+ if (event.isCancelled()) { ++ if (!new BotActionScheduleEvent(this.getBukkitEntity(), action.getName(), action.getUUID()).callEvent()) { + return false; + } -+ + action.init(); + this.actions.add(action); + return true; @@ -2987,18 +2981,21 @@ index 0000000000000000000000000000000000000000..a37513e1ba8443c702ab0c01fbe5e052 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..55b981f5d56c5df26f2709c0450bb39e1d6f1500 +index 0000000000000000000000000000000000000000..bb319fe4f04382484ea845bd6ab9ca6969c69b58 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java -@@ -0,0 +1,138 @@ +@@ -0,0 +1,161 @@ +package org.leavesmc.leaves.bot.agent; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; ++import org.leavesmc.leaves.event.bot.BotActionExecuteEvent; ++import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +import java.util.List; +import java.util.UUID; @@ -3058,6 +3055,9 @@ index 0000000000000000000000000000000000000000..55b981f5d56c5df26f2709c0450bb39e + return this.number; + } + ++ public int getCanDoNumber() { ++ return canDoNumber; ++ } + @SuppressWarnings("unchecked") + public E setNumber(int number) { + this.number = Math.max(-1, number); @@ -3072,6 +3072,11 @@ index 0000000000000000000000000000000000000000..55b981f5d56c5df26f2709c0450bb39e + this.cancel = cancel; + } + ++ public void stop(ServerBot bot, BotActionStopEvent.Reason reason) { ++ new BotActionStopEvent(bot.getBukkitEntity(), name, uuid, reason).callEvent(); ++ this.setCancelled(true); ++ } ++ + public CommandArgument getArgument() { + return this.argument; + } @@ -3084,11 +3089,23 @@ index 0000000000000000000000000000000000000000..55b981f5d56c5df26f2709c0450bb39e + + public void tryTick(ServerBot bot) { + if (this.canDoNumber == 0) { -+ this.setCancelled(true); ++ stop(bot, BotActionStopEvent.Reason.DONE); + return; + } -+ + if (this.needWaitTick <= 0) { ++ BotActionExecuteEvent event = new BotActionExecuteEvent(bot.getBukkitEntity(), name, uuid); ++ event.callEvent(); ++ if (event.getResult() == BotActionExecuteEvent.Result.SOFT_CANCEL) { ++ this.needWaitTick = this.getTickDelay(); ++ return; ++ } ++ if (event.getResult() == BotActionExecuteEvent.Result.HARD_CANCEL) { ++ if (this.canDoNumber > 0) { ++ this.canDoNumber--; ++ } ++ this.needWaitTick = this.getTickDelay(); ++ return; ++ } + if (this.doTick(bot)) { + if (this.canDoNumber > 0) { + this.canDoNumber--; @@ -3125,7 +3142,7 @@ index 0000000000000000000000000000000000000000..55b981f5d56c5df26f2709c0450bb39e + this.uuid = nbt.getUUID("actionUUID"); + } + -+ public abstract void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result); ++ public abstract void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result); + + public abstract boolean doTick(@NotNull ServerBot bot); +} @@ -3249,14 +3266,15 @@ index 0000000000000000000000000000000000000000..d99f459b2e323474174cfd5d892cb757 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..c197dd7726c9f3224b8fe44dac9c707155019c3b +index 0000000000000000000000000000000000000000..be55a3085a53542c08e7f0209883a4f3f72602e7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java -@@ -0,0 +1,24 @@ +@@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; @@ -3273,7 +3291,7 @@ index 0000000000000000000000000000000000000000..c197dd7726c9f3224b8fe44dac9c7071 + } + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); + } +} @@ -3386,17 +3404,74 @@ index 0000000000000000000000000000000000000000..bf7d20374cd7bff7cb7e09d209c6da5d + return f; + } +} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c33c4301efb89c79349c21a7ab9bd429a66a2997 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java +@@ -0,0 +1,50 @@ ++package org.leavesmc.leaves.bot.agent.actions; ++ ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.leavesmc.leaves.bot.agent.Actions; ++import org.leavesmc.leaves.bot.agent.BotAction; ++import org.leavesmc.leaves.entity.botaction.BotActionType; ++import org.leavesmc.leaves.entity.botaction.LeavesBotAction; ++ ++public class CraftBotAction extends LeavesBotAction { ++ ++ private final BotAction handle; ++ ++ public CraftBotAction(BotAction action) { ++ super(BotActionType.valueOf(action.getName().toUpperCase()), action.getTickDelay(), action.getCanDoNumber()); ++ handle = action; ++ } ++ ++ public static LeavesBotAction asAPICopy(BotAction action) { ++ return new CraftBotAction(action); ++ } ++ ++ public static BotAction asInternalCopy(LeavesBotAction action) { ++ ++ BotAction act = Actions.getForName(action.getActionName()); ++ if (act == null) { ++ throw new IllegalArgumentException("Invalid action name!"); ++ } ++ ++ BotAction newAction = null; ++ String[] args = new String[]{String.valueOf(action.getExecuteInterval()), String.valueOf(action.getRemainingExecuteTime())}; ++ try { ++ if (act instanceof CraftCustomBotAction customBotAction) { ++ newAction = customBotAction.createCraft(action.getActionPlayer(), args); ++ } else { ++ newAction = act.create(); ++ newAction.loadCommand(action.getActionPlayer() == null ? null : ((CraftPlayer) action.getActionPlayer()).getHandle(), act.getArgument().parse(0, args)); ++ } ++ } catch (IllegalArgumentException ignore) { ++ } ++ ++ if (newAction == null) { ++ throw new IllegalArgumentException("Invalid action!"); ++ } ++ return newAction; ++ } ++ ++ public BotAction getHandle() { ++ return handle; ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..35cbb218df21bee28f3b3b1dcb09c29e617ddb6d +index 0000000000000000000000000000000000000000..7b149243b08a44f1181e82217a8645ccab7732d7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java -@@ -0,0 +1,48 @@ +@@ -0,0 +1,49 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3413,11 +3488,11 @@ index 0000000000000000000000000000000000000000..35cbb218df21bee28f3b3b1dcb09c29e + } + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + throw new UnsupportedOperationException("Not supported."); + } + -+ public CraftCustomBotAction createCraft(@NotNull Player player, String[] args) { ++ public CraftCustomBotAction createCraft(@Nullable Player player, String[] args) { + CustomBotAction newRealAction = realAction.getNew(player, args); + if (newRealAction != null) { + return new CraftCustomBotAction(this.getName(), newRealAction); @@ -3442,14 +3517,15 @@ index 0000000000000000000000000000000000000000..35cbb218df21bee28f3b3b1dcb09c29e +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..b0a86493a2567ee8278aed27797b41eba1343660 +index 0000000000000000000000000000000000000000..c71e483e8938ef3b181c95d8e297e54203b5b914 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java -@@ -0,0 +1,24 @@ +@@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgumentResult; + @@ -3460,7 +3536,7 @@ index 0000000000000000000000000000000000000000..b0a86493a2567ee8278aed27797b41eb + } + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(result.readInt(100)).setNumber(result.readInt(1)); + } + @@ -3578,16 +3654,17 @@ index 0000000000000000000000000000000000000000..6fc9ba9bf94cb19ed32cfafa3a44fad0 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..edd2cb2cd53e742c7ad1916a57a6f9555c37a58f +index 0000000000000000000000000000000000000000..8be962cf7dc273ccb6a6754684a9be8353865225 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java -@@ -0,0 +1,62 @@ +@@ -0,0 +1,63 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3608,7 +3685,7 @@ index 0000000000000000000000000000000000000000..edd2cb2cd53e742c7ad1916a57a6f955 + private Vector pos; + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) throws IllegalArgumentException { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) throws IllegalArgumentException { + Vector pos = result.readVector(); + if (pos != null) { + this.setPos(pos).setTickDelay(0).setNumber(1); @@ -3646,15 +3723,16 @@ index 0000000000000000000000000000000000000000..edd2cb2cd53e742c7ad1916a57a6f955 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..8e4740ac745af67029443719c083258b4f261718 +index 0000000000000000000000000000000000000000..e2d6daab7e1dfe54ad88222d28d6e58720876b8c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java -@@ -0,0 +1,50 @@ +@@ -0,0 +1,54 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3669,7 +3747,10 @@ index 0000000000000000000000000000000000000000..8e4740ac745af67029443719c083258b + private ServerPlayer player; + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ if (player == null) { ++ throw new NullPointerException("Player is null on RotateAction!"); ++ } + this.setPlayer(player).setTickDelay(0).setNumber(1); + } + @@ -3702,15 +3783,16 @@ index 0000000000000000000000000000000000000000..8e4740ac745af67029443719c083258b +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..78d7cf38c28c5512955cf2c59a78ac493e99f829 +index 0000000000000000000000000000000000000000..89262b5ede9255b77c5ce773e251e857e2caa302 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java -@@ -0,0 +1,60 @@ +@@ -0,0 +1,64 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3731,7 +3813,10 @@ index 0000000000000000000000000000000000000000..78d7cf38c28c5512955cf2c59a78ac49 + private float pitch; + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ if (player == null) { ++ throw new NullPointerException("Player is null on RotationAction!"); ++ } + this.setYaw(result.readFloat(player.getYRot())).setPitch(result.readFloat(player.getXRot())).setTickDelay(0).setNumber(1); + } + @@ -3768,14 +3853,15 @@ index 0000000000000000000000000000000000000000..78d7cf38c28c5512955cf2c59a78ac49 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..6cfc1986aa3fc895ba854226170555dc2583d7fa +index 0000000000000000000000000000000000000000..923cf55d81fce5cf9db9a1c7adc6f3aed5753b16 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java -@@ -0,0 +1,26 @@ +@@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3788,7 +3874,7 @@ index 0000000000000000000000000000000000000000..6cfc1986aa3fc895ba854226170555dc + } + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(0).setNumber(1); + } + @@ -3800,15 +3886,16 @@ index 0000000000000000000000000000000000000000..6cfc1986aa3fc895ba854226170555dc +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..b51658329ee851c62e74000631e79185baf5eee0 +index 0000000000000000000000000000000000000000..b5ccedee17857bc955301512ee965d81fd12017f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java -@@ -0,0 +1,29 @@ +@@ -0,0 +1,30 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; @@ -3821,7 +3908,7 @@ index 0000000000000000000000000000000000000000..b51658329ee851c62e74000631e79185 + } + + @Override -+ public void loadCommand(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(0).setNumber(-1); + } + @@ -4256,10 +4343,10 @@ index 0000000000000000000000000000000000000000..a3f978318a67c3c5e147a50eb2b6c01c +} diff --git a/src/main/java/org/leavesmc/leaves/entity/CraftBot.java b/src/main/java/org/leavesmc/leaves/entity/CraftBot.java new file mode 100644 -index 0000000000000000000000000000000000000000..4d44a49abd5bdeca978d8dcf00a15023ebe02666 +index 0000000000000000000000000000000000000000..385729bc0964a1966141b850a033a61f7efc0e96 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBot.java -@@ -0,0 +1,53 @@ +@@ -0,0 +1,90 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.craftbukkit.CraftServer; @@ -4268,14 +4355,22 @@ index 0000000000000000000000000000000000000000..4d44a49abd5bdeca978d8dcf00a15023 +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; ++import org.leavesmc.leaves.bot.agent.BotAction; ++import org.leavesmc.leaves.bot.agent.actions.CraftBotAction; ++import org.leavesmc.leaves.entity.botaction.LeavesBotAction; ++import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; + ++import java.util.List; +import java.util.UUID; + +public class CraftBot extends CraftPlayer implements Bot { + ++ private final ServerBot bot; ++ + public CraftBot(CraftServer server, ServerBot entity) { + super(server, entity); ++ this.bot = entity; + } + + @Override @@ -4294,9 +4389,38 @@ index 0000000000000000000000000000000000000000..4d44a49abd5bdeca978d8dcf00a15023 + } + + @Override -+ public boolean remove(boolean save) { ++ public void addAction(@NotNull LeavesBotAction action) { ++ bot.addBotAction(CraftBotAction.asInternalCopy(action)); ++ } ++ ++ @Override ++ public LeavesBotAction getAction(int index) { ++ List> actions = bot.getBotActions(); ++ if (index >= actions.size()) { ++ throw new IndexOutOfBoundsException("Index " + index + " is out of size " + actions.size() + " of the bot"); ++ } ++ return CraftBotAction.asAPICopy(actions.get(index)); ++ } ++ ++ @Override ++ public void stopAction(int index) { ++ List> actions = bot.getBotActions(); ++ if (index >= actions.size()) { ++ throw new IndexOutOfBoundsException("Index " + index + " is out of size " + actions.size() + " of the bot"); ++ } ++ actions.get(index).stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); ++ } ++ ++ @Override ++ public void stopAllActions() { ++ for (BotAction action : bot.getBotActions()) { ++ action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); ++ } ++ } ++ ++ @Override ++ public void remove(boolean save) { + BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); -+ return true; + } + + @Override @@ -4315,10 +4439,10 @@ index 0000000000000000000000000000000000000000..4d44a49abd5bdeca978d8dcf00a15023 +} diff --git a/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java b/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..e748d49d329ba814e3906a8bd9cba017653be51b +index 0000000000000000000000000000000000000000..edc556622d0390dc8de070cf8ca2616530052100 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java -@@ -0,0 +1,92 @@ +@@ -0,0 +1,96 @@ +package org.leavesmc.leaves.entity; + +import com.google.common.base.Function; @@ -4396,19 +4520,23 @@ index 0000000000000000000000000000000000000000..e748d49d329ba814e3906a8bd9cba017 + } + + @Override -+ public BotCreator botCreator(@NotNull String realName, @NotNull Location location) { -+ return BotCreateState.builder(realName, location); ++ public Bot createBot(@NotNull BotCreator creator, @Nullable Consumer consumer) { ++ BotCreateState state = new BotCreateState(creator, BotCreateEvent.CreateReason.PLUGIN); ++ Bot bot = state.createNow().getBukkitEntity(); ++ if (consumer != null) { ++ consumer.accept(bot); ++ } ++ return bot; + } + + @Override -+ public void createBot(@NotNull BotCreator creator, @Nullable Consumer consumer) { -+ if (creator instanceof BotCreateState.Builder builder) { -+ builder.createReason(BotCreateEvent.CreateReason.PLUGIN).create((serverBot) -> { -+ if (consumer != null && serverBot != null) { -+ consumer.accept(serverBot.getBukkitEntity()); -+ } -+ }); -+ } ++ public void createBotWithSkin(@NotNull BotCreator creator, @Nullable Consumer consumer) { ++ BotCreateState state = new BotCreateState(creator, BotCreateEvent.CreateReason.PLUGIN); ++ state.create((serverBot) -> { ++ if (consumer != null && serverBot != null) { ++ consumer.accept(serverBot.getBukkitEntity()); ++ } ++ }); + } +} diff --git a/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java b/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java