diff --git a/patches/api/0003-Add-fakeplayer-api.patch b/patches/api/0003-Add-fakeplayer-api.patch index 69c6868b..b32578f9 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..7a1ee45d571687317883b896f3ec0a837a8ef450 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/Bot.java -@@ -0,0 +1,35 @@ +@@ -0,0 +1,80 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.entity.Player; @@ -97,22 +97,70 @@ index 0000000000000000000000000000000000000000..e5cf058a5c7bf14c0c2f017d4b6fd3bd + @NotNull + public String getRealName(); + ++ /** ++ * Gets the creator's UUID of the fakeplayer ++ * ++ * @return creator's UUID ++ */ + @Nullable + public UUID getCreatePlayerUUID(); + ++ /** ++ * Add an action to the fakeplayer ++ * ++ * @param action bot action ++ */ ++ public void addAction(@NotNull LeavesBotAction action); ++ ++ /** ++ * Get the copy action in giving index ++ * ++ * @param index index of actions ++ * @return Action of that index ++ */ ++ public LeavesBotAction getAction(int index); ++ ++ /** ++ * Get action size ++ * ++ * @return size ++ */ ++ public int getActionSize(); ++ ++ /** ++ * 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 ++ * ++ * @param save should save ++ * @return success ++ */ + public boolean 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..b65e4661f7641c2e0d94c47c7688934fe2be5054 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/BotCreator.java -@@ -0,0 +1,20 @@ +@@ -0,0 +1,47 @@ +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 java.util.function.Consumer; + +public interface BotCreator { + @@ -122,18 +170,42 @@ index 0000000000000000000000000000000000000000..fd18396f8d127f3f044ee407e4c8a3dc + + public BotCreator skin(String[] skin); + ++ /** ++ * Sets the skin of the bot using the Mojang API based on the provided skin name. ++ *
++ * Need Async. ++ * ++ * @return BotCreator ++ */ + public BotCreator mojangAPISkin(); + + public BotCreator location(@NotNull Location location); + -+ public BotCreator creator(CommandSender creator); ++ public BotCreator creator(@Nullable CommandSender creator); ++ ++ /** ++ * Create a bot directly ++ * ++ * @return a bot, null spawn fail ++ */ ++ @Nullable ++ public Bot spawn(); ++ ++ /** ++ * Create a bot and apply skin ++ *
++ * you can not get the bot instance instantly because get skin in on async thread
++ *
++ * @param consumer Consumer
++ */
++ public void spawnWithSkin(Consumer> getTabComplete();
++ @NotNull
++ public List
> getTabComplete();
+
+ /**
+ * Return a ticks to wait between {@link CustomBotAction#doTick(Bot)}
@@ -256,65 +367,100 @@ 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..8a73f5cc673a95c434677ad7578abfb5402118e3
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/entity/botaction/LeavesBotAction.java
-@@ -0,0 +1,34 @@
+@@ -0,0 +1,73 @@
+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;
++ this.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 void setActionPlayer(@Nullable Player actionPlayer) {
++ this.actionPlayer = actionPlayer;
++ }
++
++ @Nullable
++ public Player getActionPlayer() {
++ return actionPlayer;
++ }
++
++ public UUID getUuid() {
++ return uuid;
+ }
+}
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..1818aa77f8b051a00b4dbc3da0907cf3462ffcbb
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java
-@@ -0,0 +1,50 @@
+@@ -0,0 +1,27 @@
+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 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,12 +476,90 @@ 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..69a99679d407f974ef0e414945d3bcc7a1a710ea
+--- /dev/null
++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java
+@@ -0,0 +1,52 @@
++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 {
++
++ private static final HandlerList handlers = new HandlerList();
++
++ public enum Result {
++ PASS, SOFT_CANCEL, HARD_CANCEL;
++
++ }
++
++ private Result result = Result.PASS;
++
++ public BotActionExecuteEvent(@NotNull Bot who, String actionName, UUID actionUUID) {
++ super(who, actionName, actionUUID);
++ }
++
++ @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;
++ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return handlers;
+ }
+
++ 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..7189649e608d41511d4213c1c3938996361290df
+--- /dev/null
++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java
+@@ -0,0 +1,39 @@
++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 boolean isCancelled() {
+ return cancel;
@@ -346,16 +570,64 @@ index 0000000000000000000000000000000000000000..e85efcaef9f3575b6a85d57b02b2d814
+ this.cancel = cancel;
+ }
+
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+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..e8de7b19d72b3dfd6e4423096573b3a7ef737803
+--- /dev/null
++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java
+@@ -0,0 +1,36 @@
++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 {
++
++ private static final HandlerList handlers = new HandlerList();
++
++ public enum Reason {
++ DONE, COMMAND, PLUGIN, INTERNAL
++ }
++
++ private final Reason reason;
++
++ public BotActionStopEvent(@NotNull Bot who, String actionName, UUID actionUUID, Reason stopReason) {
++ super(who, actionName, actionUUID);
++ this.reason = stopReason;
++ }
++
++ public Reason getReason() {
++ return reason;
++ }
++
++ @Override
++ public @NotNull HandlerList getHandlers() {
++ return handlers;
++ }
++
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
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
+index 0000000000000000000000000000000000000000..053be37cb250d77b1c9f4c1bbd83a49da93027a7
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java
-@@ -0,0 +1,49 @@
+@@ -0,0 +1,50 @@
+package org.leavesmc.leaves.event.bot;
+
+import org.bukkit.event.Cancellable;
@@ -364,6 +636,7 @@ index 0000000000000000000000000000000000000000..1ebdd3657e615a825918d455691cc1d6
+import org.leavesmc.leaves.entity.Bot;
+
+public class BotConfigModifyEvent extends BotEvent implements Cancellable {
++
+ private static final HandlerList handlers = new HandlerList();
+
+ private final String configName;
@@ -387,11 +660,6 @@ index 0000000000000000000000000000000000000000..1ebdd3657e615a825918d455691cc1d6
+ }
+
+ @Override
-+ public @NotNull HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @Override
+ public boolean isCancelled() {
+ return cancel;
+ }
@@ -401,13 +669,18 @@ index 0000000000000000000000000000000000000000..1ebdd3657e615a825918d455691cc1d6
+ this.cancel = cancel;
+ }
+
++ @Override
++ public @NotNull HandlerList getHandlers() {
++ return handlers;
++ }
++
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ec0ed853a6d6dec263784da284ee521c81aebbc8
+index 0000000000000000000000000000000000000000..08e382120feec65c2a842134a1643f236a120bbd
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java
@@ -0,0 +1,119 @@
@@ -421,19 +694,19 @@ index 0000000000000000000000000000000000000000..ec0ed853a6d6dec263784da284ee521c
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
-+import java.util.Optional;
-+
+/**
+ * Call when a fakeplayer creates a server
+ */
+public class BotCreateEvent extends Event implements Cancellable {
++
++ private static final HandlerList handlers = new HandlerList();
++
+ public enum CreateReason {
+ COMMAND,
+ PLUGIN,
+ INTERNAL,
+ UNKNOWN,
+ }
-+ private static final HandlerList handlers = new HandlerList();
+
+ private final String bot;
+ private final String skin;
@@ -607,10 +880,10 @@ index 0000000000000000000000000000000000000000..3366b50cf1835129a027b5342e4d8cf0
+}
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ad358081f1e1da4075243d7ca0a01c1f7b00631b
+index 0000000000000000000000000000000000000000..ed9f954da5d381368977eae3ed19a334a3bc3e5a
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java
-@@ -0,0 +1,31 @@
+@@ -0,0 +1,32 @@
+package org.leavesmc.leaves.event.bot;
+
+import org.bukkit.event.Event;
@@ -621,13 +894,14 @@ index 0000000000000000000000000000000000000000..ad358081f1e1da4075243d7ca0a01c1f
+ * Represents a fakeplayer related event
+ */
+public abstract class BotEvent extends Event {
++
+ protected Bot bot;
+
+ public BotEvent(@NotNull final Bot who) {
+ bot = who;
+ }
+
-+ public BotEvent(@NotNull final Bot who, boolean async) { // Paper - public
++ public BotEvent(@NotNull final Bot who, boolean async) {
+ super(async);
+ bot = who;
+ }
@@ -644,7 +918,7 @@ 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..8191c0e1302234981212d2fa015425e25825ce61
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java
@@ -0,0 +1,46 @@
@@ -658,6 +932,7 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0
+import org.leavesmc.leaves.entity.Bot;
+
+public class BotInventoryOpenEvent extends BotEvent implements Cancellable {
++
+ private static final HandlerList handlers = new HandlerList();
+
+ private final Player player;
@@ -683,7 +958,6 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0
+ return player;
+ }
+
-+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return handlers;
@@ -696,10 +970,10 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0
+}
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..07f6d81c4cd897230bbd6712dac09b8995431104
+index 0000000000000000000000000000000000000000..24e5f4d833897000e0378d4d3c3ff75c08a5bad2
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java
-@@ -0,0 +1,66 @@
+@@ -0,0 +1,67 @@
+package org.leavesmc.leaves.event.bot;
+
+import net.kyori.adventure.text.Component;
@@ -713,6 +987,7 @@ index 0000000000000000000000000000000000000000..07f6d81c4cd897230bbd6712dac09b89
+ * Called when a fakeplayer joins a server
+ */
+public class BotJoinEvent extends BotEvent {
++
+ private static final HandlerList handlers = new HandlerList();
+
+ private Component joinMessage;
@@ -768,10 +1043,10 @@ index 0000000000000000000000000000000000000000..07f6d81c4cd897230bbd6712dac09b89
+}
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ff3243f420e8c64ea2675e8c8712b851dcce4c27
+index 0000000000000000000000000000000000000000..d4472675af540cdeeebf428144c70b9a5c3f34ce
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java
-@@ -0,0 +1,58 @@
+@@ -0,0 +1,59 @@
+package org.leavesmc.leaves.event.bot;
+
+import org.bukkit.event.Cancellable;
@@ -785,6 +1060,7 @@ index 0000000000000000000000000000000000000000..ff3243f420e8c64ea2675e8c8712b851
+ * Call when a fakeplayer loading a server
+ */
+public class BotLoadEvent extends Event implements Cancellable {
++
+ private static final HandlerList handlers = new HandlerList();
+
+ private final String bot;
@@ -832,10 +1108,10 @@ 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..408a7e39ee1923d595fb8ac3f91d60e14a2c446c
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java
-@@ -0,0 +1,107 @@
+@@ -0,0 +1,106 @@
+package org.leavesmc.leaves.event.bot;
+
+import net.kyori.adventure.text.Component;
@@ -847,12 +1123,13 @@ index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a6513
+import org.jetbrains.annotations.Nullable;
+import org.leavesmc.leaves.entity.Bot;
+
-+import java.util.Optional;
-+
+/**
+ * Call when a fakeplayer creates a server
+ */
+public class BotRemoveEvent extends BotEvent implements Cancellable {
++
++ private static final HandlerList handlers = new HandlerList();
++
+ public enum RemoveReason {
+ COMMAND,
+ PLUGIN,
@@ -860,20 +1137,18 @@ index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a6513
+ INTERNAL
+ }
+
-+ private static final HandlerList handlers = new HandlerList();
-+
+ 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 +1199,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
@@ -945,14 +1220,13 @@ index 0000000000000000000000000000000000000000..e950643e4b4da99b067d7392034a6513
+}
diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java
new file mode 100644
-index 0000000000000000000000000000000000000000..434e78be76750adfbeb033ab1aceaae222b978df
+index 0000000000000000000000000000000000000000..46ab3e9b5e398ec238e129d16fb020b481a88f76
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java
@@ -0,0 +1,38 @@
+package org.leavesmc.leaves.event.bot;
+
+import org.bukkit.Location;
-+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+import org.leavesmc.leaves.entity.Bot;
@@ -960,6 +1234,7 @@ index 0000000000000000000000000000000000000000..434e78be76750adfbeb033ab1aceaae2
+public class BotSpawnLocationEvent extends BotEvent {
+
+ private static final HandlerList handlers = new HandlerList();
++
+ private Location spawnLocation;
+
+ public BotSpawnLocationEvent(@NotNull final Bot who, @NotNull Location spawnLocation) {
diff --git a/patches/server/0010-Fakeplayer-support.patch b/patches/server/0010-Fakeplayer-support.patch
index b61bea1a..b8f0cef7 100644
--- a/patches/server/0010-Fakeplayer-support.patch
+++ b/patches/server/0010-Fakeplayer-support.patch
@@ -626,7 +626,7 @@ 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..a06f24253a2d941c8fd265f50dcaec356b5cfbcc
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java
@@ -0,0 +1,522 @@
@@ -822,7 +822,7 @@ index 0000000000000000000000000000000000000000..ab7c2fb890041c478b18a49beab87d95
+ }
+ }
+
-+ builder.createAsyncWithSkin(null);
++ builder.spawnWithSkin(null);
+ }
+ }
+
@@ -1154,10 +1154,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..ebb3aa94718382e8fac2a8fa617c796a9387709b
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java
-@@ -0,0 +1,115 @@
+@@ -0,0 +1,120 @@
+package org.leavesmc.leaves.bot;
+
+import net.minecraft.server.MinecraftServer;
@@ -1167,7 +1167,9 @@ 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.entity.CraftBot;
+import org.leavesmc.leaves.event.bot.BotCreateEvent;
+import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
+
@@ -1212,7 +1214,7 @@ index 0000000000000000000000000000000000000000..6df68cdbeeed27f4b1cc81a33f70689f
+ this.createReason = BotCreateEvent.CreateReason.UNKNOWN;
+ this.creator = null;
+ }
-+
++
+ public Builder name(@NotNull String name) {
+ Objects.requireNonNull(name);
+ this.name = name;
@@ -1256,20 +1258,23 @@ index 0000000000000000000000000000000000000000..6df68cdbeeed27f4b1cc81a33f70689f
+ return new BotCreateState(realName, name, skinName, skin, location, createReason, creator);
+ }
+
-+ public void createAsyncWithSkin(Consumer