diff --git a/patches/api/0003-Add-fakeplayer-api.patch b/patches/api/0003-Add-fakeplayer-api.patch index a843538e..62003ac4 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..922ca5b27bc0dd443d635646f37f879559cc0252 +index 0000000000000000000000000000000000000000..7a1ee45d571687317883b896f3ec0a837a8ef450 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/Bot.java -@@ -0,0 +1,51 @@ +@@ -0,0 +1,80 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.entity.Player; @@ -97,33 +97,120 @@ index 0000000000000000000000000000000000000000..922ca5b27bc0dd443d635646f37f8795 + @NotNull + public String getRealName(); + ++ /** ++ * Gets the creator's UUID of the fakeplayer ++ * ++ * @return creator's UUID ++ */ + @Nullable + public UUID getCreatePlayerUUID(); + + /** -+ * Sets the fakeplayer action with args. ++ * Add an action to the fakeplayer + * -+ * @param action action name -+ * @param player player who create this action -+ * @param args passed action arguments ++ * @param action bot action + */ -+ public boolean setBotAction(@NotNull String action, @NotNull Player player, @NotNull String[] args); ++ public void addAction(@NotNull LeavesBotAction action); + + /** -+ * Sets the fakeplayer action with args. ++ * Get the copy action in giving index + * -+ * @param action leaves bot action -+ * @param player player who create this action -+ * @param args passed action arguments ++ * @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..98c7e87854eae9760a6f4427c6b052b192df2b45 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/entity/BotCreator.java +@@ -0,0 +1,52 @@ ++package org.leavesmc.leaves.entity; ++ ++import org.bukkit.Bukkit; ++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 { ++ ++ default BotCreator of(String realName, Location location) { ++ return Bukkit.getBotManager().botCreator(realName, location); ++ } ++ ++ public BotCreator name(String name); ++ ++ public BotCreator skinName(String skinName); ++ ++ 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 boolean setBotAction(@NotNull LeavesBotAction action, @NotNull Player player, @NotNull String[] args); ++ public BotCreator mojangAPISkin(); ++ ++ public BotCreator location(@NotNull Location location); ++ ++ 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 consumer); +} 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..ee6848b8990c516aa5c5490546dd16ae5c909740 +index 0000000000000000000000000000000000000000..105fc912c2cba717b6533771bfd85eee83954fef --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/BotManager.java -@@ -0,0 +1,124 @@ +@@ -0,0 +1,59 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.Location; @@ -133,7 +220,6 @@ index 0000000000000000000000000000000000000000..ee6848b8990c516aa5c5490546dd16ae + +import java.util.Collection; +import java.util.UUID; -+import java.util.function.Consumer; + +/** + * Simple fakeplayer manager @@ -159,72 +245,6 @@ index 0000000000000000000000000000000000000000..ee6848b8990c516aa5c5490546dd16ae + public Bot getBot(@NotNull String name); + + /** -+ * Creates a fakeplayer with given param. -+ *

-+ * prefix and suffix will not be added. -+ * -+ * @param name fakeplayer name -+ * @param realName fakeplayer real name -+ * @param skin fakeplayer skin arr -+ * @param skinName fakeplayer skin name -+ * @param location a location will create fakeplayer -+ * @return a fakeplayer if success, null otherwise -+ */ -+ @Deprecated(since = "1.21") -+ @Nullable -+ public Bot createBot(@NotNull String name, @NotNull String realName, @Nullable String[] skin, @Nullable String skinName, @NotNull Location location); -+ -+ /** -+ * Creates a fakeplayer with given param. -+ *

-+ * prefix and suffix will not be added. -+ * -+ * @param name fakeplayer name -+ * @param realName fakeplayer real name -+ * @param skin fakeplayer skin arr -+ * @param skinName fakeplayer skin name -+ * @param location a location will create fakeplayer -+ * @param consumer a consumer after create fakeplayer success -+ * @return a fakeplayer if you support skin arr and the creation is success, null otherwise -+ */ -+ @Nullable -+ public Bot createBot(@NotNull String name, @NotNull String realName, @NotNull String[] skin, @Nullable String skinName, @NotNull Location location, @Nullable Consumer consumer); -+ -+ /** -+ * Creates a fakeplayer with given param. -+ * -+ * @param name fakeplayer name -+ * @param skinName fakeplayer skin name -+ * @param location a location will create fakeplayer -+ * @param consumer a consumer after create fakeplayer success -+ */ -+ public void createBot(@NotNull String name, @Nullable String skinName, @NotNull Location location, @Nullable Consumer consumer); -+ -+ /** -+ * Removes a fakeplayer object by the given name. -+ * -+ * @param name the name to look up -+ */ -+ public void removeBot(@NotNull String name); -+ -+ /** -+ * Removes a fakeplayer object by the given uuid. -+ * -+ * @param uuid the uuid to look up -+ */ -+ public void removeBot(@NotNull UUID uuid); -+ -+ /** -+ * Removes all fakeplayers. -+ */ -+ public void removeAllBots(); -+ -+ /** -+ * Save fakeplayers data if resident-fakeplayer is true, or remove all fakeplayer. -+ */ -+ public void saveOrRemoveAllBots(); -+ -+ /** + * Gets a view of all currently logged in fakeplayers. This view is a reused object, making some operations like Collection.size() zero-allocation. + * + * @return a view of fakeplayers. @@ -234,7 +254,7 @@ index 0000000000000000000000000000000000000000..ee6848b8990c516aa5c5490546dd16ae + /** + * Register a custom bot action. + * -+ * @param name action name ++ * @param name action name + * @param action action executor + * @return true if success, or false + */ @@ -247,13 +267,55 @@ index 0000000000000000000000000000000000000000..ee6848b8990c516aa5c5490546dd16ae + * @return true if success, or false + */ + public boolean unregisterCustomBotAction(String name); ++ ++ public BotCreator botCreator(@NotNull String realName, @NotNull Location location); ++} +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..c952d7ddaf457f79d66baf42557e75600a471f81 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/botaction/CustomBotAction.java -@@ -0,0 +1,52 @@ +@@ -0,0 +1,54 @@ +package org.leavesmc.leaves.entity.botaction; + +import org.bukkit.entity.Player; @@ -283,14 +345,16 @@ index 0000000000000000000000000000000000000000..0b1648013d5f03d064c0719c23198108 + * @param args passed action arguments + * @return a new action instance with given args + */ -+ public @Nullable CustomBotAction getNew(Player player, String[] args); ++ @Nullable ++ public CustomBotAction getNew(@Nullable Player player, String[] args); + + /** + * Requests a list of possible completions for a action argument. + * + * @return A List of a List of possible completions for the argument. + */ -+ public @NotNull List> getTabComplete(); ++ @NotNull ++ public List> getTabComplete(); + + /** + * Return a ticks to wait between {@link CustomBotAction#doTick(Bot)} @@ -308,75 +372,105 @@ 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..a6fdf8c77318172621494af94a0efb2bd34ca651 +index 0000000000000000000000000000000000000000..8a73f5cc673a95c434677ad7578abfb5402118e3 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/botaction/LeavesBotAction.java -@@ -0,0 +1,36 @@ +@@ -0,0 +1,73 @@ +package org.leavesmc.leaves.entity.botaction; + -+/** -+ * A Leaves bot action enum -+ */ -+public enum LeavesBotAction { -+ ATTACK("attack"), -+ @Deprecated(since = "1.21.1", forRemoval = true) -+ ATTACK_SELF("attack_self"), -+ BREAK("break"), -+ DROP("drop"), -+ FISH("fish"), -+ JUMP("jump"), -+ LAY("lay"), -+ LOOK("look"), -+ ROTATE("rotate"), -+ 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..91ea5540387b7d7e1be5b6368a2f02b3b784614a +index 0000000000000000000000000000000000000000..1818aa77f8b051a00b4dbc3da0907cf3462ffcbb --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java -@@ -0,0 +1,49 @@ +@@ -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; + -+public class BotActionEvent extends BotEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); ++import java.util.UUID; ++ ++public abstract class BotActionEvent extends BotEvent { + + private final String actionName; -+ private final String[] actionArgs; -+ private boolean cancel = false; ++ private final UUID actionUUID; + -+ public BotActionEvent(@NotNull Bot who, String actionName, String[] actionArgs) { ++ public BotActionEvent(@NotNull Bot who, String actionName, UUID actionUUID) { + super(who); -+ this.actionArgs = actionArgs; + this.actionName = actionName; -+ } -+ -+ @NotNull -+ public String[] getActionArgs() { -+ return actionArgs; ++ this.actionUUID = actionUUID; + } + + @NotNull @@ -384,11 +478,93 @@ index 0000000000000000000000000000000000000000..91ea5540387b7d7e1be5b6368a2f02b3 + return actionName; + } + ++ 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; @@ -399,16 +575,64 @@ index 0000000000000000000000000000000000000000..91ea5540387b7d7e1be5b6368a2f02b3 + 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..5e55759fd3d7891e8e1d5d6a306dc8144d366469 +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; @@ -417,13 +641,14 @@ index 0000000000000000000000000000000000000000..5e55759fd3d7891e8e1d5d6a306dc814 +import org.leavesmc.leaves.entity.Bot; + +public class BotConfigModifyEvent extends BotEvent implements Cancellable { ++ + private static final HandlerList handlers = new HandlerList(); + + private final String configName; -+ private final String configValue; ++ private final String[] configValue; + private boolean cancel; + -+ public BotConfigModifyEvent(@NotNull Bot who, String configName, String configValue) { ++ public BotConfigModifyEvent(@NotNull Bot who, String configName, String[] configValue) { + super(who); + this.configName = configName; + this.configValue = configValue; @@ -435,16 +660,11 @@ index 0000000000000000000000000000000000000000..5e55759fd3d7891e8e1d5d6a306dc814 + } + + @NotNull -+ public String getConfigValue() { ++ public String[] getConfigValue() { + return configValue; + } + + @Override -+ public @NotNull HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ @Override + public boolean isCancelled() { + return cancel; + } @@ -454,16 +674,21 @@ index 0000000000000000000000000000000000000000..5e55759fd3d7891e8e1d5d6a306dc814 + 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..be510d565c5942efea3423190b06c01873a7abd2 +index 0000000000000000000000000000000000000000..08e382120feec65c2a842134a1643f236a120bbd --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java -@@ -0,0 +1,118 @@ +@@ -0,0 +1,119 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.Location; @@ -474,23 +699,24 @@ index 0000000000000000000000000000000000000000..be510d565c5942efea3423190b06c018 +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 ++ INTERNAL, ++ UNKNOWN, + } -+ private static final HandlerList handlers = new HandlerList(); + + private final String bot; + private final String skin; + private final CreateReason reason; -+ private final Optional creator; ++ private final CommandSender creator; + private Location createLocation; + private boolean cancel = false; + @@ -499,7 +725,7 @@ index 0000000000000000000000000000000000000000..be510d565c5942efea3423190b06c018 + this.skin = skin; + this.createLocation = createLocation; + this.reason = reason; -+ this.creator = Optional.ofNullable(creator); ++ this.creator = creator; + } + + /** @@ -552,12 +778,12 @@ index 0000000000000000000000000000000000000000..be510d565c5942efea3423190b06c018 + + /** + * Gets the creator of the bot -+ * if the create reason is not COMMAND, the creator might be Optional.empty() ++ * if the create reason is not COMMAND, the creator might be null + * + * @return An optional of creator + */ -+ @NotNull -+ public Optional getCreator() { ++ @Nullable ++ public CommandSender getCreator() { + return creator; + } + @@ -582,12 +808,87 @@ index 0000000000000000000000000000000000000000..be510d565c5942efea3423190b06c018 + return handlers; + } +} +diff --git a/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java b/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3366b50cf1835129a027b5342e4d8cf070cecf4a +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java +@@ -0,0 +1,69 @@ ++package org.leavesmc.leaves.event.bot; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.entity.Bot; ++ ++public class BotDeathEvent extends BotEvent implements Cancellable { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ ++ private boolean cancel = false; ++ private boolean sendDeathMessage; ++ private Component deathMessage; ++ ++ public BotDeathEvent(@NotNull Bot who, @Nullable Component deathMessage, boolean sendDeathMessage) { ++ super(who); ++ this.deathMessage = deathMessage; ++ this.sendDeathMessage = sendDeathMessage; ++ } ++ ++ @Override ++ public @NotNull HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++ ++ public Component deathMessage() { ++ return deathMessage; ++ } ++ ++ public void deathMessage(Component deathMessage) { ++ this.deathMessage = deathMessage; ++ } ++ ++ @Nullable ++ public String getDeathMessage() { ++ return this.deathMessage == null ? null : LegacyComponentSerializer.legacySection().serialize(this.deathMessage); ++ } ++ ++ public void setDeathMessage(@Nullable String deathMessage) { ++ this.deathMessage = deathMessage != null ? LegacyComponentSerializer.legacySection().deserialize(deathMessage) : null; ++ } ++ ++ public boolean isSendDeathMessage() { ++ return sendDeathMessage; ++ } ++ ++ public void setSendDeathMessage(boolean sendDeathMessage) { ++ this.sendDeathMessage = sendDeathMessage; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++} 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; @@ -598,13 +899,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; + } @@ -621,7 +923,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 @@ @@ -635,6 +937,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; @@ -660,7 +963,6 @@ index 0000000000000000000000000000000000000000..a369b468d4793b36dd0944a1368a70e0 + return player; + } + -+ + @Override + public @NotNull HandlerList getHandlers() { + return handlers; @@ -673,10 +975,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; @@ -690,6 +992,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; @@ -743,12 +1046,77 @@ index 0000000000000000000000000000000000000000..07f6d81c4cd897230bbd6712dac09b89 + return handlers; + } +} +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..d4472675af540cdeeebf428144c70b9a5c3f34ce +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java +@@ -0,0 +1,59 @@ ++package org.leavesmc.leaves.event.bot; ++ ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.UUID; ++ ++/** ++ * 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; ++ private final UUID botUUID; ++ private boolean cancel = false; ++ ++ public BotLoadEvent(@NotNull final String who, @NotNull final UUID uuid) { ++ this.bot = who; ++ this.botUUID = uuid; ++ } ++ ++ /** ++ * Gets the fakeplayer name ++ * ++ * @return fakeplayer name ++ */ ++ public String getBot() { ++ return bot; ++ } ++ ++ public UUID getBotUUID() { ++ return botUUID; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} 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..7af990a5ee020dfdbec2efc6aecf6393a4223730 +index 0000000000000000000000000000000000000000..408a7e39ee1923d595fb8ac3f91d60e14a2c446c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java -@@ -0,0 +1,105 @@ +@@ -0,0 +1,106 @@ +package org.leavesmc.leaves.event.bot; + +import net.kyori.adventure.text.Component; @@ -760,12 +1128,13 @@ index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393 +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, @@ -773,26 +1142,18 @@ index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393 + INTERNAL + } + -+ private static final HandlerList handlers = new HandlerList(); -+ + private final RemoveReason reason; -+ private final Optional remover; ++ private final CommandSender remover; + private Component removeMessage; ++ private boolean save; + private boolean cancel = false; + -+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason) { -+ this(who, reason, null); -+ } -+ -+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover) { -+ this(who, reason, remover, null); -+ } -+ -+ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage) { ++ public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage, boolean save) { + super(who); + this.reason = reason; -+ this.remover = Optional.ofNullable(remover); ++ this.remover = remover; + this.removeMessage = removeMessage; ++ this.save = save; + } + + /** @@ -807,12 +1168,12 @@ index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393 + + /** + * Gets the remover of the bot -+ * if the remove reason is not COMMAND, the creator might be Optional.empty() ++ * if the remove reason is not COMMAND, the creator might be null + * + * @return An optional of remover + */ -+ @NotNull -+ public Optional getRemover() { ++ @Nullable ++ public CommandSender getRemover() { + return remover; + } + @@ -843,6 +1204,14 @@ index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393 + this.cancel = cancel; + } + ++ public boolean shouldSave() { ++ return save; ++ } ++ ++ public void setSave(boolean save) { ++ this.save = save; ++ } ++ + @Override + @NotNull + public HandlerList getHandlers() { @@ -854,3 +1223,47 @@ index 0000000000000000000000000000000000000000..7af990a5ee020dfdbec2efc6aecf6393 + return handlers; + } +} +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..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.event.HandlerList; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.entity.Bot; ++ ++public class BotSpawnLocationEvent extends BotEvent { ++ ++ private static final HandlerList handlers = new HandlerList(); ++ ++ private Location spawnLocation; ++ ++ public BotSpawnLocationEvent(@NotNull final Bot who, @NotNull Location spawnLocation) { ++ super(who); ++ this.spawnLocation = spawnLocation; ++ } ++ ++ @NotNull ++ public Location getSpawnLocation() { ++ return spawnLocation; ++ } ++ ++ public void setSpawnLocation(@NotNull Location location) { ++ this.spawnLocation = location; ++ } ++ ++ @NotNull ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/server/0006-Leaves-Server-Config-And-Command.patch b/patches/server/0006-Leaves-Server-Config-And-Command.patch index f6fedf4a..db93bda8 100644 --- a/patches/server/0006-Leaves-Server-Config-And-Command.patch +++ b/patches/server/0006-Leaves-Server-Config-And-Command.patch @@ -85,10 +85,10 @@ index d97771ecaf06b92d92b5ca0224ae0866e36703a6..439305bb4f5ce232aa6237276c121d53 .withRequiredArg() diff --git a/src/main/java/org/leavesmc/leaves/LeavesConfig.java b/src/main/java/org/leavesmc/leaves/LeavesConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..59c98fa51afd4fd305852d14509b06f8bef859a1 +index 0000000000000000000000000000000000000000..10cf4d4aed544e0e0dd6698119734eb9e9cc92e2 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/LeavesConfig.java -@@ -0,0 +1,883 @@ +@@ -0,0 +1,889 @@ +package org.leavesmc.leaves; + +import com.destroystokyo.paper.util.SneakyThrow; @@ -228,13 +228,13 @@ index 0000000000000000000000000000000000000000..59c98fa51afd4fd305852d14509b06f8 + public static boolean fakeplayerSpawnPhantom = false; + + @GlobalConfig(name = "regen-amount", category = {"modify", "fakeplayer"}, verify = RegenAmountVerify.class) -+ public static double fakeplayerRegenAmount = 0.010; ++ public static double fakeplayerRegenAmount = 0.0; + + private static class RegenAmountVerify extends ConfigVerifyImpl.DoubleConfigVerify { + @Override + public void check(Double old, Double value) throws IllegalArgumentException { -+ if (value <= 0.0) { -+ throw new IllegalArgumentException("regen-amount need > 0.0f"); ++ if (value < 0.0) { ++ throw new IllegalArgumentException("regen-amount need >= 0.0"); + } + } + } @@ -245,6 +245,12 @@ index 0000000000000000000000000000000000000000..59c98fa51afd4fd305852d14509b06f8 + @GlobalConfig(name = "modify-config", category = {"modify", "fakeplayer"}) + public static boolean fakeplayerModifyConfig = false; + ++ @GlobalConfig(name = "manual-save-and-load", category = {"modify", "fakeplayer"}) ++ public static boolean fakeplayerManualSaveAndLoad = false; ++ ++ @GlobalConfig(name = "cache-skin", category = {"modify", "fakeplayer"}, lock = true) ++ public static boolean fakeplayerCacheSkin = false; ++ + // Leaves end - modify - fakeplayer + + // Leaves start - modify - minecraft-old @@ -974,10 +980,10 @@ index 0000000000000000000000000000000000000000..59c98fa51afd4fd305852d14509b06f8 +} diff --git a/src/main/java/org/leavesmc/leaves/command/CommandArgument.java b/src/main/java/org/leavesmc/leaves/command/CommandArgument.java new file mode 100644 -index 0000000000000000000000000000000000000000..2f0e6671dd8bfe4f320eab92c5f5bbc10abc3b05 +index 0000000000000000000000000000000000000000..0bccbf7816ef621316f0da4911ec112f4753f88e --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/command/CommandArgument.java -@@ -0,0 +1,49 @@ +@@ -0,0 +1,55 @@ +package org.leavesmc.leaves.command; + +import org.jetbrains.annotations.NotNull; @@ -988,10 +994,12 @@ index 0000000000000000000000000000000000000000..2f0e6671dd8bfe4f320eab92c5f5bbc1 + +public class CommandArgument { + ++ public static final CommandArgument EMPTY = new CommandArgument(); ++ + private final List> argumentTypes; + private final List> tabComplete; + -+ public CommandArgument(CommandArgumentType... argumentTypes) { ++ private CommandArgument(CommandArgumentType... argumentTypes) { + this.argumentTypes = List.of(argumentTypes); + this.tabComplete = new ArrayList<>(); + for (int i = 0; i < argumentTypes.length; i++) { @@ -999,6 +1007,10 @@ index 0000000000000000000000000000000000000000..2f0e6671dd8bfe4f320eab92c5f5bbc1 + } + } + ++ public static CommandArgument of(CommandArgumentType... argumentTypes) { ++ return new CommandArgument(argumentTypes); ++ } ++ + public List tabComplete(int n) { + if (tabComplete.size() > n) { + return tabComplete.get(n); @@ -1029,10 +1041,10 @@ index 0000000000000000000000000000000000000000..2f0e6671dd8bfe4f320eab92c5f5bbc1 +} diff --git a/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java b/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java new file mode 100644 -index 0000000000000000000000000000000000000000..3f0707940f22736f703c24f4da25c18fa6e5b309 +index 0000000000000000000000000000000000000000..46aa6eaa75b65aad6bdbe4a5f517b42e87bcca77 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java -@@ -0,0 +1,65 @@ +@@ -0,0 +1,69 @@ +package org.leavesmc.leaves.command; + +import net.minecraft.core.BlockPos; @@ -1065,6 +1077,10 @@ index 0000000000000000000000000000000000000000..3f0707940f22736f703c24f4da25c18f + return Objects.requireNonNullElse(read(String.class), def); + } + ++ public boolean readBoolean(boolean def) { ++ return Objects.requireNonNullElse(read(Boolean.class), def); ++ } ++ + public BlockPos readPos() { + Integer[] pos = {read(Integer.class), read(Integer.class), read(Integer.class)}; + for (Integer po : pos) { @@ -1100,10 +1116,10 @@ index 0000000000000000000000000000000000000000..3f0707940f22736f703c24f4da25c18f +} diff --git a/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java b/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java new file mode 100644 -index 0000000000000000000000000000000000000000..0d6d33e66461dda39c8f0e8395bf3f047ef88cbd +index 0000000000000000000000000000000000000000..4ca3508475bbd9771768704e300fe12b717489d6 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java -@@ -0,0 +1,48 @@ +@@ -0,0 +1,55 @@ +package org.leavesmc.leaves.command; + +import org.jetbrains.annotations.NotNull; @@ -1150,6 +1166,13 @@ index 0000000000000000000000000000000000000000..0d6d33e66461dda39c8f0e8395bf3f04 + } + }; + ++ public static final CommandArgumentType BOOLEAN = new CommandArgumentType<>() { ++ @Override ++ public Boolean pasre(@NotNull String arg) { ++ return Boolean.parseBoolean(arg); ++ } ++ }; ++ + public abstract E pasre(@NotNull String arg); +} diff --git a/src/main/java/org/leavesmc/leaves/command/LeavesCommand.java b/src/main/java/org/leavesmc/leaves/command/LeavesCommand.java diff --git a/patches/server/0010-Fakeplayer-support.patch b/patches/server/0010-Fakeplayer-support.patch index dbb42cb6..b8f0cef7 100644 --- a/patches/server/0010-Fakeplayer-support.patch +++ b/patches/server/0010-Fakeplayer-support.patch @@ -33,27 +33,74 @@ index 35772110e9318df46a2729dbc0b5879b290011b7..f26989a44cdda9baabf337d573436c6c PlayerAdvancements playerAdvancements = player.getAdvancements(); Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak if (set != null && !set.isEmpty()) { +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 3e550f8e7cd4f4e16f499a8a2a4b95420270f07a..46d9c77581b78c427692aa8645d17b3d0c2bb6a6 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -104,7 +104,7 @@ public class Connection extends SimpleChannelInboundHandler> { + @Nullable + private volatile PacketListener disconnectListener; + @Nullable +- private volatile PacketListener packetListener; ++ protected volatile PacketListener packetListener; // Leaves - private -> protected + @Nullable + private DisconnectionDetails disconnectionDetails; + private boolean encrypted; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7ce0fb36690e12f3f36c9a43e45ac71814be8e69..a625473805fcde57f3987f3d788efb36fa89073e 100644 +index 7ce0fb36690e12f3f36c9a43e45ac71814be8e69..89c568a24df0323b902b5236484644a6edbea7ff 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -746,6 +746,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> { // Paper - rewrite chunk system +@@ -746,6 +748,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior - boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); - -- if (!keepInventory) { -+ if (!keepInventory || this instanceof org.leavesmc.leaves.bot.ServerBot) { // Leaves - skip bot - for (ItemStack item : this.getInventory().getContents()) { - if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { - loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) -@@ -1417,6 +1421,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple +@@ -1417,6 +1421,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple this.lastSentHealth = -1.0F; this.lastSentFood = -1; + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { -+ org.leavesmc.leaves.bot.ServerBot.getBots().forEach(bot1 -> -+ bot1.sendFakeDataIfNeed(this, true)); // Leaves - render bot ++ this.server.getBotList().bots.forEach(bot -> bot.sendFakeDataIfNeed(this, true)); // Leaves - render bot + } + // Leaves end - bot support + @@ -221,7 +270,7 @@ index 763cffdc2e1e2e7cc9af88cc46bbaa240a20fd0d..647a6c9dd39e113625377273281d74ae PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); this.level().getCraftServer().getPluginManager().callEvent(changeEvent); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 8ccd40b562691e757c7b5efa1497d93a95040a9a..901e9ff7a89b48b7acca7e5b96f640045c1d265e 100644 +index 8ccd40b562691e757c7b5efa1497d93a95040a9a..69f44d0351e8127e38cbe9e74c05b365c37b2c44 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -123,6 +123,8 @@ import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; @@ -233,19 +282,17 @@ index 8ccd40b562691e757c7b5efa1497d93a95040a9a..901e9ff7a89b48b7acca7e5b96f64004 public abstract class PlayerList { public static final File USERBANLIST_FILE = new File("banned-players.json"); -@@ -351,6 +353,21 @@ public abstract class PlayerList { +@@ -351,6 +353,19 @@ public abstract class PlayerList { org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { -+ ServerBot bot = ServerBot.getBot(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); ++ ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); + if (bot != null) { -+ bot.die(bot.damageSources().fellOutOfWorld()); // Leaves - remove bot with the same name -+ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); -+ this.playersByUUID.put(player.getUUID(), player); ++ this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false); + } -+ ServerBot.getBots().forEach(bot1 -> { ++ this.server.getBotList().bots.forEach(bot1 -> { + bot1.sendPlayerInfo(player); + bot1.sendFakeDataIfNeed(player, true); + }); // Leaves - render bot @@ -255,55 +302,67 @@ index 8ccd40b562691e757c7b5efa1497d93a95040a9a..901e9ff7a89b48b7acca7e5b96f64004 final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure -@@ -937,6 +954,13 @@ public abstract class PlayerList { +@@ -937,6 +952,12 @@ public abstract class PlayerList { } // Paper end - Add PlayerPostRespawnEvent + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { -+ ServerBot.getBots().forEach(bot1 -> -+ bot1.sendFakeDataIfNeed(entityplayer1, true)); // Leaves - render bot ++ this.server.getBotList().bots.forEach(bot -> bot.sendFakeDataIfNeed(entityplayer1, true)); // Leaves - render bot + } + // Leaves end - bot support + // CraftBukkit end return entityplayer1; -@@ -1069,11 +1093,16 @@ public abstract class PlayerList { +@@ -1069,11 +1090,16 @@ public abstract class PlayerList { } public String[] getPlayerNamesArray() { - String[] astring = new String[this.players.size()]; -+ String[] astring = new String[this.players.size() + ServerBot.getBots().size()]; // Leaves - fakeplayer support ++ String[] astring = new String[this.players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support for (int i = 0; i < this.players.size(); ++i) { astring[i] = ((ServerPlayer) this.players.get(i)).getGameProfile().getName(); } + // Leaves start - fakeplayer support + for (int i = this.players.size(); i < astring.length; ++i) { -+ astring[i] = ((ServerPlayer) ServerBot.getBots().get(i - this.players.size())).getGameProfile().getName(); ++ astring[i] = ((ServerPlayer) this.server.getBotList().bots.get(i - this.players.size())).getGameProfile().getName(); + } + // Leaves end - fakeplayer support return astring; } -@@ -1553,4 +1582,16 @@ public abstract class PlayerList { - public boolean isAllowCommandsForAllPlayers() { - return this.allowCommandsForAllPlayers; +@@ -1159,7 +1185,13 @@ public abstract class PlayerList { + + @Nullable + public ServerPlayer getPlayerByName(String name) { +- return this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); // Spigot ++ // Leaves start - fakeplayer support ++ ServerPlayer player = this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); ++ if (player == null) { ++ player = this.server.getBotList().getBotByName(name); ++ } ++ return player; // Spigot ++ // Leaves end - fakeplayer support + } + + public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { +@@ -1499,7 +1531,13 @@ public abstract class PlayerList { + + @Nullable + public ServerPlayer getPlayer(UUID uuid) { +- return (ServerPlayer) this.playersByUUID.get(uuid); ++ // Leaves start - fakeplayer support ++ ServerPlayer player = this.playersByUUID.get(uuid); ++ if (player == null) { ++ player = this.server.getBotList().getBot(uuid); ++ } ++ return player; ++ // Leaves start - fakeplayer support } -+ -+ // Leaves start - fakeplayer support -+ public void addNewBot(ServerBot bot) { -+ playersByName.put(bot.getScoreboardName().toLowerCase(java.util.Locale.ROOT), bot); -+ playersByUUID.put(bot.getUUID(), bot); -+ } -+ -+ public void removeBot(ServerBot bot) { -+ playersByName.remove(bot.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); -+ playersByUUID.remove(bot.getUUID()); -+ } -+ // Leaves end - fakeplayer support - } + + public boolean canBypassPlayerLimit(GameProfile profile) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index 7d7258c56cfade2d82acdf83dfa20cd0416c0dab..1beefbd05c13181214e188f50e88fb67f6712a45 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java @@ -317,6 +376,41 @@ index 7d7258c56cfade2d82acdf83dfa20cd0416c0dab..1beefbd05c13181214e188f50e88fb67 // Paper start - optimise collisions final boolean xZero = movement.x == 0.0; final boolean yZero = movement.y == 0.0; +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 09bcbc0ae36e4e69fee87a7e0c49acf496117a39..65b002f2ae062327f48df0e157aa35721478c5fd 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -183,7 +183,7 @@ public abstract class Player extends LivingEntity { + private int lastLevelUpTime; + public GameProfile gameProfile; + private boolean reducedDebugInfo; +- private ItemStack lastItemInMainHand; ++ protected ItemStack lastItemInMainHand; // Leaves - private -> protected + private final ItemCooldowns cooldowns; + private Optional lastDeathLocation; + @Nullable +@@ -337,6 +337,12 @@ public abstract class Player extends LivingEntity { + + } + ++ // Leaves start - fakeplayer ++ protected void livingEntityTick() { ++ super.tick(); ++ } ++ // Leaves end - fakeplayer ++ + @Override + protected float getMaxHeadRotationRelativeToBody() { + return this.isBlocking() ? 15.0F : super.getMaxHeadRotationRelativeToBody(); +@@ -635,7 +641,7 @@ public abstract class Player extends LivingEntity { + + } + +- private void touch(Entity entity) { ++ public void touch(Entity entity) { // Leaves - private -> public + entity.playerTouch(this); + } + diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java index 1223c5d23d0ea6aed068bdf0f5725e2ad49fc82c..0e00f59a8962dd6356d483ef5be3209a3a410008 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java @@ -367,7 +461,7 @@ index 32910f677b0522ac8ec513fa0d00b714b52cfae4..961a7193fda00fa62acea9c39fda1c93 FeatureFlagSet featureflagset = player.level().enabledFeatures(); diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index 1b1b475ca27e799e251d6f8a8c9fe1a4fd8bae83..aff4e1e4d2462adf00d6e984b0a0540592257056 100644 +index 1b1b475ca27e799e251d6f8a8c9fe1a4fd8bae83..22f8e7d62df86a12c5b9ad709538d6ac564d3338 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java @@ -67,6 +67,11 @@ public class PhantomSpawner implements CustomSpawner { @@ -375,26 +469,60 @@ index 1b1b475ca27e799e251d6f8a8c9fe1a4fd8bae83..aff4e1e4d2462adf00d6e984b0a05405 int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); boolean flag2 = true; + // Leaves start - fakeplayer spawn -+ if (entityplayer instanceof org.leavesmc.leaves.bot.ServerBot bot && bot.spawnPhantom) { ++ if (entityplayer instanceof org.leavesmc.leaves.bot.ServerBot bot && bot.getConfigValue(org.leavesmc.leaves.bot.agent.Configs.SPAWN_PHANTOM)) { + j = Math.max(bot.notSleepTicks, 1); + } + // Leaves end - fakeplayer spawn if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21)); +diff --git a/src/main/java/net/minecraft/world/level/storage/LevelResource.java b/src/main/java/net/minecraft/world/level/storage/LevelResource.java +index fee8367d2812db559b15970f0a60023bedaaefc5..f6b59b00bb1611aff8d161d1ad03df7fc911f994 100644 +--- a/src/main/java/net/minecraft/world/level/storage/LevelResource.java ++++ b/src/main/java/net/minecraft/world/level/storage/LevelResource.java +@@ -15,7 +15,7 @@ public class LevelResource { + public static final LevelResource ROOT = new LevelResource("."); + private final String id; + +- private LevelResource(String relativePath) { ++ public LevelResource(String relativePath) { // Leaves - private -> public + this.id = relativePath; + } + +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index 8ab7ca373a885fbe658013c9c6a2e38d32d77bb2..2262eaab7af5f1d7c37ef028479842c0fb45f3ee 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -21,7 +21,7 @@ import net.minecraft.world.entity.player.Player; + import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.slf4j.Logger; + +-public class PlayerDataStorage { ++public class PlayerDataStorage implements org.leavesmc.leaves.bot.IPlayerDataStorage { + + private static final Logger LOGGER = LogUtils.getLogger(); + private final File playerDir; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f2a0a1f32bf456c302e5d18b91367aa0c041cc6c..59cdcf7b25c17705b613c83dea107934b683af28 100644 +index f2a0a1f32bf456c302e5d18b91367aa0c041cc6c..97d09246b5bab3fe85491d06c7b16f932bcd1cb2 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -310,6 +310,7 @@ public final class CraftServer implements Server { private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes public final io.papermc.paper.SparksFly spark; // Paper - spark -+ private final org.leavesmc.leaves.entity.CraftBotManager botManager = new org.leavesmc.leaves.entity.CraftBotManager(); // Leaves ++ private final org.leavesmc.leaves.entity.CraftBotManager botManager; // Leaves // Paper start - Folia region threading API private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); -@@ -1463,7 +1464,7 @@ public final class CraftServer implements Server { +@@ -477,6 +478,7 @@ public final class CraftServer implements Server { + datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol ++ this.botManager = new org.leavesmc.leaves.entity.CraftBotManager(); // Leaves + } + + public boolean getCommandBlockOverride(String command) { +@@ -1463,7 +1465,7 @@ public final class CraftServer implements Server { return false; } @@ -403,7 +531,7 @@ index f2a0a1f32bf456c302e5d18b91367aa0c041cc6c..59cdcf7b25c17705b613c83dea107934 return false; } -@@ -3229,4 +3230,11 @@ public final class CraftServer implements Server { +@@ -3229,4 +3231,11 @@ public final class CraftServer implements Server { return this.potionBrewer; } // Paper end @@ -496,80 +624,24 @@ index 8dd85b9ca3b3e3429de4d0ec0654982589c6e93e..de9f63fb3b8dcf11a9271794850ce448 // Paper start - make cancellable if (event.isCancelled()) { return event; -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 22f1ed383313829b8af4badda9ef8dc85cae8fd1..1c47e320e464af9651953ff308a2583fcb965891 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -1,7 +1,7 @@ - package org.bukkit.craftbukkit.scheduler; - - import com.google.common.base.Preconditions; --import com.google.common.util.concurrent.ThreadFactoryBuilder; -+ - import java.util.ArrayList; - import java.util.Comparator; - import java.util.Iterator; -@@ -10,7 +10,6 @@ import java.util.PriorityQueue; - import java.util.concurrent.Callable; - import java.util.concurrent.ConcurrentHashMap; - import java.util.concurrent.Executor; --import java.util.concurrent.Executors; - import java.util.concurrent.Future; - import java.util.concurrent.atomic.AtomicInteger; - import java.util.concurrent.atomic.AtomicReference; -@@ -23,6 +22,7 @@ import org.bukkit.scheduler.BukkitRunnable; - import org.bukkit.scheduler.BukkitScheduler; - import org.bukkit.scheduler.BukkitTask; - import org.bukkit.scheduler.BukkitWorker; -+import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; - - /** - * The fundamental concepts for this implementation: -@@ -46,6 +46,8 @@ import org.bukkit.scheduler.BukkitWorker; - */ - public class CraftScheduler implements BukkitScheduler { - -+ public static final Plugin MINECRAFT = new MinecraftInternalPlugin(); // Leaves - run async task -+ - /** - * The start ID for the counter. - */ -@@ -478,10 +480,14 @@ public class CraftScheduler implements BukkitScheduler { - "Task #%s for %s generated an exception", - task.getTaskId(), - task.getOwner().getDescription().getFullName()); -- task.getOwner().getLogger().log( -- Level.WARNING, -- logMessage, -- throwable); -+ if (task.getOwner() instanceof MinecraftInternalPlugin) { -+ net.minecraft.server.MinecraftServer.LOGGER.error(logMessage, throwable); -+ } else { -+ task.getOwner().getLogger().log( -+ Level.WARNING, -+ logMessage, -+ throwable); -+ } - org.bukkit.Bukkit.getServer().getPluginManager().callEvent( - new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerSchedulerException(logMessage, throwable, task))); - // Paper end 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..918cdffedddba7cebb8013600bf3a2f5ce1e32c0 +index 0000000000000000000000000000000000000000..a06f24253a2d941c8fd265f50dcaec356b5cfbcc --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java -@@ -0,0 +1,472 @@ +@@ -0,0 +1,522 @@ +package org.leavesmc.leaves.bot; + ++import io.papermc.paper.command.CommandUtil; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; -+import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.scheduler.CraftScheduler; +import org.bukkit.entity.Player; +import org.bukkit.generator.WorldInfo; +import org.bukkit.permissions.Permission; @@ -577,24 +649,32 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.bot.agent.Actions; +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.bot.agent.actions.CraftCustomBotAction; ++import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.Bot; -+import org.leavesmc.leaves.event.bot.BotActionEvent; +import org.leavesmc.leaves.event.bot.BotConfigModifyEvent; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; ++import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +import java.util.*; -+import java.util.stream.Stream; ++ ++import static net.kyori.adventure.text.Component.text; + +public class BotCommand extends Command { + ++ private final Component unknownMessage; ++ + public BotCommand(String name) { + super(name); + this.description = "FakePlayer Command"; + this.usageMessage = "/bot [create | remove | action | list | config]"; ++ this.unknownMessage = text("Usage: " + usageMessage, NamedTextColor.RED); + this.setPermission("bukkit.command.bot"); + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + if (pluginManager.getPermission("bukkit.command.bot") == null) { @@ -604,7 +684,8 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args, Location location) throws IllegalArgumentException { -+ var list = new ArrayList(); ++ List list = new ArrayList<>(); ++ BotList botList = BotList.INSTANCE; + + if (args.length <= 1) { + list.add("create"); @@ -615,14 +696,19 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + if (LeavesConfig.fakeplayerModifyConfig) { + list.add("config"); + } ++ if (LeavesConfig.fakeplayerManualSaveAndLoad) { ++ list.add("save"); ++ list.add("load"); ++ } + list.add("list"); + } + + if (args.length == 2) { + switch (args[0]) { + case "create" -> list.add(""); -+ case "remove", "action", "config" -> list.addAll(ServerBot.getBots().stream().map(e -> e.getName().getString()).toList()); ++ case "remove", "action", "config", "save" -> list.addAll(botList.bots.stream().map(e -> e.getName().getString()).toList()); + case "list" -> list.addAll(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList()); ++ case "load" -> list.addAll(botList.getSavedBotList().getAllKeys()); + } + } + @@ -630,6 +716,7 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + switch (args[0]) { + case "action" -> { + list.add("list"); ++ list.add("stop"); + list.addAll(Actions.getNames()); + } + case "create" -> list.add(""); @@ -638,39 +725,43 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + } + } + -+ if (args.length == 4) { -+ switch (args[0]) { -+ case "config" -> { -+ if (args[2].equals(BotConfig.SIMULATION_DISTANCE.configName)) { -+ list.add("10"); -+ list.add("2"); -+ list.add(""); -+ } else { -+ list.add("true"); -+ list.add("false"); -+ } -+ } -+ case "remove" -> { -+ if (!Objects.equals(args[3], "cancel")) { -+ list.add("[minute]"); -+ } ++ if (args[0].equals("remove") && args.length >= 3) { ++ if (!Objects.equals(args[3], "cancel")) { ++ switch (args.length) { ++ case 4 -> list.add("[minute]"); ++ case 5 -> list.add("[second]"); + } + } + } -+ if (args.length == 5 && args[0].equals("remove")) { -+ if (!Objects.equals(args[3], "cancel")) { -+ list.add("[second]"); ++ ++ if (args.length >= 4 && args[0].equals("action")) { ++ ServerBot bot = botList.getBotByName(args[1]); ++ ++ if (bot == null) { ++ return Collections.singletonList("<" + args[1] + " not found>"); ++ } ++ ++ if (args[2].equals("stop")) { ++ list.add("all"); ++ for (int i = 0; i < bot.getBotActions().size(); i++) { ++ list.add(String.valueOf(i)); ++ } ++ } else { ++ BotAction action = Actions.getForName(args[2]); ++ if (action != null) { ++ list.addAll(action.getArgument().tabComplete(args.length - 4)); ++ } + } + } + -+ if (args.length >= 4 && args[0].equals("action")) { -+ BotAction action = Actions.getForName(args[2]); -+ if (action != null) { -+ list.addAll(action.getArgument().tabComplete(args.length - 4)); ++ if (args.length >= 4 && args[0].equals("config")) { ++ Configs config = Configs.getConfig(args[2]); ++ if (config != null) { ++ list.addAll(config.config.getArgument().tabComplete(args.length - 4)); + } + } + -+ return list; ++ return CommandUtil.getListMatchingLast(sender, args, list); + } + + @Override @@ -678,23 +769,20 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + if (!testPermission(sender) || !LeavesConfig.fakeplayerSupport) return true; + + if (args.length == 0) { -+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ sender.sendMessage(unknownMessage); + return false; + } + + switch (args[0]) { + case "create" -> this.onCreate(sender, args); -+ + case "remove" -> this.onRemove(sender, args); -+ + case "action" -> this.onAction(sender, args); -+ + case "config" -> this.onConfig(sender, args); -+ + case "list" -> this.onList(sender, args); -+ ++ case "save" -> this.onSave(sender, args); ++ case "load" -> this.onLoad(sender, args); + default -> { -+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); ++ sender.sendMessage(unknownMessage); + return false; + } + } @@ -704,53 +792,59 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + + private void onCreate(CommandSender sender, String @NotNull [] args) { + if (args.length < 2) { -+ sender.sendMessage(ChatColor.RED + "Use /bot create [skin_name] to create a fakeplayer"); ++ sender.sendMessage(text("Use /bot create [skin_name] to create a fakeplayer", NamedTextColor.RED)); + return; + } + -+ if (canCreate(sender, args[1])) { -+ if (sender instanceof Player player) { -+ new ServerBot.BotCreateState(player.getLocation(), args[1], args.length < 3 ? args[1] : args[2], BotCreateEvent.CreateReason.COMMAND, player).create(null); -+ } else if (sender instanceof ConsoleCommandSender csender) { -+ if (args.length < 6) { -+ sender.sendMessage(ChatColor.RED + "Use /bot create to create a fakeplayer"); -+ return; -+ } ++ 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); + -+ try { -+ World world = Bukkit.getWorld(args[3]); -+ double x = Double.parseDouble(args[4]); -+ double y = Double.parseDouble(args[5]); -+ double z = Double.parseDouble(args[6]); ++ if (args.length >= 3) { ++ builder.skinName(args[2]); ++ } + -+ if (world != null) { -+ new ServerBot.BotCreateState(new Location(world, x, y, z), args[1], args[2], BotCreateEvent.CreateReason.COMMAND, csender).create(null); ++ if (sender instanceof Player player) { ++ builder.location(player.getLocation()); ++ } else if (sender instanceof ConsoleCommandSender) { ++ if (args.length >= 7) { ++ try { ++ World world = Bukkit.getWorld(args[3]); ++ double x = Double.parseDouble(args[4]); ++ double y = Double.parseDouble(args[5]); ++ double z = Double.parseDouble(args[6]); ++ if (world != null) { ++ builder.location(new Location(world, x, y, z)); ++ } ++ } catch (Exception e) { ++ LeavesLogger.LOGGER.warning("Can't build location", e); + } -+ } catch (Exception e) { -+ e.printStackTrace(); + } + } ++ ++ builder.spawnWithSkin(null); + } + } + + private boolean canCreate(CommandSender sender, @NotNull String name) { ++ BotList botList = BotList.INSTANCE; + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { -+ sender.sendMessage(ChatColor.RED + "This name is illegal"); ++ sender.sendMessage(text("This name is illegal", NamedTextColor.RED)); + return false; + } + -+ if (Bukkit.getPlayerExact(name) != null || ServerBot.getBot(name) != null) { -+ sender.sendMessage(ChatColor.RED + "This player is in server"); ++ if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) { ++ sender.sendMessage(text("This player is in server", NamedTextColor.RED)); + return false; + } + + if (org.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(name)) { -+ sender.sendMessage(ChatColor.RED + "This name is not allowed"); ++ sender.sendMessage(text("This name is not allowed", NamedTextColor.RED)); + return false; + } + -+ if (ServerBot.getBots().size() >= org.leavesmc.leaves.LeavesConfig.fakeplayerLimit) { -+ sender.sendMessage(ChatColor.RED + "Fakeplayer limit is full"); ++ if (botList.bots.size() >= org.leavesmc.leaves.LeavesConfig.fakeplayerLimit) { ++ sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED)); + return false; + } + @@ -759,26 +853,27 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + + private void onRemove(CommandSender sender, String @NotNull [] args) { + if (args.length < 2 || args.length > 5) { -+ sender.sendMessage(ChatColor.RED + "Use /bot remove [hour] [minute] [second] to remove a fakeplayer"); ++ sender.sendMessage(text("Use /bot remove [hour] [minute] [second] to remove a fakeplayer", NamedTextColor.RED)); + return; + } + -+ ServerBot bot = ServerBot.getBot(args[1]); ++ BotList botList = BotList.INSTANCE; ++ ServerBot bot = botList.getBotByName(args[1]); + + if (bot == null) { -+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server"); ++ sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (args.length > 2) { + if (args[2].equals("cancel")) { + if (bot.removeTaskId == -1) { -+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not scheduled to be removed"); ++ sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); + return; + } + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + bot.removeTaskId = -1; -+ sender.sendMessage("Remove cancel"); ++ sender.sendMessage(text("Remove cancel")); + return; + } + @@ -808,7 +903,7 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + time += s * 20; + } + } catch (NumberFormatException e) { -+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not scheduled to be removed"); ++ sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); + return; + } + @@ -817,9 +912,9 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + if (isReschedule) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + } -+ bot.removeTaskId = Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> { ++ bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> { + bot.removeTaskId = -1; -+ bot.onRemove(BotRemoveEvent.RemoveReason.COMMAND, sender); ++ botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + }, time).getTaskId(); + + sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : "")); @@ -827,7 +922,7 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + return; + } + -+ bot.onRemove(BotRemoveEvent.RemoveReason.COMMAND, sender); ++ botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + } + + private void onAction(CommandSender sender, String @NotNull [] args) { @@ -836,27 +931,54 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + } + + if (args.length < 3) { -+ sender.sendMessage(ChatColor.RED + "Use /bot action to make fakeplayer do action"); ++ sender.sendMessage(text("Use /bot action to make fakeplayer do action", NamedTextColor.RED)); + return; + } + -+ ServerBot bot = ServerBot.getBot(args[1]); ++ ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { -+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server"); ++ sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (args[2].equals("list")) { + sender.sendMessage(bot.getScoreboardName() + "'s action list:"); -+ for (BotAction action : bot.getBotActions()) { -+ sender.sendMessage(action.getName()); ++ for (int i = 0; i < bot.getBotActions().size(); i++) { ++ sender.sendMessage(i + " " + bot.getBotActions().get(i).getName()); ++ } ++ return; ++ } ++ ++ if (args[2].equals("stop")) { ++ if (args.length < 4) { ++ sender.sendMessage(text("Invalid index", NamedTextColor.RED)); ++ return; ++ } ++ ++ String index = args[3]; ++ if (index.equals("all")) { ++ bot.getBotActions().clear(); ++ sender.sendMessage(bot.getScoreboardName() + "'s action list cleared."); ++ } else { ++ try { ++ int i = Integer.parseInt(index); ++ if (i < 0 || i >= bot.getBotActions().size()) { ++ sender.sendMessage(text("Invalid index", NamedTextColor.RED)); ++ return; ++ } ++ ++ BotAction action = bot.getBotActions().remove(i); ++ sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped."); ++ } catch (NumberFormatException e) { ++ sender.sendMessage(text("Invalid index", NamedTextColor.RED)); ++ } + } + return; + } + -+ BotAction action = Actions.getForName(args[2]); ++ BotAction action = Actions.getForName(args[2]); + if (action == null) { -+ sender.sendMessage(ChatColor.RED + "Invalid action"); ++ sender.sendMessage(text("Invalid action", NamedTextColor.RED)); + return; + } + @@ -872,38 +994,28 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + System.arraycopy(args, 3, realArgs, 0, realArgs.length); + } + -+ BotAction newAction; -+ if (action instanceof CraftCustomBotAction customBotAction) { -+ newAction = customBotAction.getNew(player, realArgs); -+ } else { -+ newAction = action.getNew(player.getHandle(), action.getArgument().parse(0, realArgs)); ++ BotAction newAction = null; ++ try { ++ if (action instanceof CraftCustomBotAction customBotAction) { ++ newAction = customBotAction.createCraft(player, realArgs); ++ } else { ++ newAction = action.create(); ++ newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs)); ++ } ++ } catch (IllegalArgumentException ignore) { + } + + if (newAction == null) { -+ sender.sendMessage(ChatColor.RED + "Action create error, please check your arguments"); ++ sender.sendMessage(text("Action create error, please check your arguments", NamedTextColor.RED)); + return; + } + -+ BotActionEvent event = new BotActionEvent(bot.getBukkitEntity(), newAction.getName(), realArgs); -+ Bukkit.getPluginManager().callEvent(event); -+ -+ if (!event.isCancelled()) { -+ bot.setBotAction(newAction); ++ if (bot.addBotAction(newAction)) { + sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString()); + } + } + -+ public enum BotConfig { -+ SKIP_SLEEP, SPAWN_PHANTOM, ALWAYS_SEND_DATA, SIMULATION_DISTANCE; -+ -+ public final String configName; -+ -+ BotConfig() { -+ this.configName = this.name().toLowerCase(Locale.ROOT); -+ } -+ } -+ -+ private static final List acceptConfig = Stream.of(BotConfig.values()).map(config -> config.configName).toList(); ++ private static final List acceptConfig = Configs.getConfigs().stream().map(config -> config.config.getName()).toList(); + + private void onConfig(CommandSender sender, String @NotNull [] args) { + if (!LeavesConfig.fakeplayerModifyConfig) { @@ -911,114 +1023,123 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + } + + if (args.length < 3) { -+ sender.sendMessage(ChatColor.RED + "Use /bot config to modify fakeplayer's config"); ++ sender.sendMessage(text("Use /bot config to modify fakeplayer's config", NamedTextColor.RED)); + return; + } + -+ ServerBot bot = ServerBot.getBot(args[1]); ++ ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { -+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server"); ++ sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (!acceptConfig.contains(args[2])) { -+ sender.sendMessage(ChatColor.RED + "This config is not accept"); ++ sender.sendMessage(text("This config is not accept", NamedTextColor.RED)); + return; + } + -+ BotConfig config = BotConfig.valueOf(args[2].toUpperCase(Locale.ROOT)); ++ BotConfig config = Objects.requireNonNull(Configs.getConfig(args[2])).config; + if (args.length < 4) { -+ String value = null; -+ switch (config) { -+ case SKIP_SLEEP -> value = String.valueOf(bot.fauxSleeping); -+ case SPAWN_PHANTOM -> { -+ sender.sendMessage(bot.getScoreboardName() + "'s spawn_phantom: " + bot.spawnPhantom); -+ if (bot.spawnPhantom) { -+ sender.sendMessage(bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks); -+ } -+ return; -+ } -+ case ALWAYS_SEND_DATA -> value = String.valueOf(bot.alwaysSendData); -+ case SIMULATION_DISTANCE -> value = String.valueOf(bot.getBukkitEntity().getSimulationDistance()); -+ } -+ sender.sendMessage(bot.getScoreboardName() + "'s " + config.configName + ": " + value); ++ config.getMessage().forEach(sender::sendMessage); + } else { -+ String value = args[3]; ++ String[] realArgs = new String[args.length - 3]; ++ System.arraycopy(args, 3, realArgs, 0, realArgs.length); + -+ BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.configName, value); ++ BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } ++ CommandArgumentResult result = config.getArgument().parse(0, realArgs); + -+ switch (config) { -+ case SKIP_SLEEP -> { -+ boolean realValue = value.equals("true"); -+ bot.fauxSleeping = realValue; -+ value = String.valueOf(realValue); -+ } -+ case SPAWN_PHANTOM -> { -+ boolean realValue = value.equals("true"); -+ bot.spawnPhantom = realValue; -+ value = String.valueOf(realValue); -+ } -+ case ALWAYS_SEND_DATA -> { -+ boolean realValue = value.equals("true"); -+ bot.alwaysSendData = realValue; -+ value = String.valueOf(realValue); -+ } -+ case SIMULATION_DISTANCE -> { -+ try { -+ int realValue = Integer.parseInt(value); -+ if (realValue < 2 || realValue > 32) { -+ sender.sendMessage("simulation_distance must be a number between 2 and 32, got: " + value); -+ return; -+ } -+ bot.getBukkitEntity().setSimulationDistance(realValue); -+ } catch (NumberFormatException e) { -+ sender.sendMessage("simulation_distance must be a number between 2 and 32, got: " + value); -+ return; -+ } -+ } ++ try { ++ config.setValue(result); ++ config.getChangeMessage().forEach(sender::sendMessage); ++ } catch (IllegalArgumentException e) { ++ sender.sendMessage(text(e.getMessage(), NamedTextColor.RED)); + } -+ sender.sendMessage(bot.getScoreboardName() + "'s " + config.configName + " changed: " + value); ++ } ++ } ++ ++ private void onSave(CommandSender sender, String @NotNull [] args) { ++ if (!LeavesConfig.fakeplayerManualSaveAndLoad) { ++ return; ++ } ++ ++ if (args.length < 2) { ++ sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); ++ return; ++ } ++ ++ BotList botList = BotList.INSTANCE; ++ ServerBot bot = botList.getBotByName(args[1]); ++ ++ if (bot == null) { ++ sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); ++ return; ++ } ++ ++ botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true); ++ sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName()); ++ } ++ ++ private void onLoad(CommandSender sender, String @NotNull [] args) { ++ if (!LeavesConfig.fakeplayerManualSaveAndLoad) { ++ return; ++ } ++ ++ if (args.length < 2) { ++ sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); ++ return; ++ } ++ ++ String realName = args[1]; ++ BotList botList = BotList.INSTANCE; ++ if (!botList.getSavedBotList().contains(realName)) { ++ sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED)); ++ return; ++ } ++ ++ if (botList.loadNewBot(realName) == null) { ++ sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED)); + } + } + + private void onList(CommandSender sender, String @NotNull [] args) { ++ BotList botList = BotList.INSTANCE; + if (args.length < 2) { + Map> botMap = new HashMap<>(); + for (World world : Bukkit.getWorlds()) { + botMap.put(world, new ArrayList<>()); + } + -+ for (ServerBot bot : ServerBot.getBots()) { -+ Bot bukkitBot = bot.getBukkitPlayer(); ++ for (ServerBot bot : botList.bots) { ++ Bot bukkitBot = bot.getBukkitEntity(); + botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName()); + } + -+ sender.sendMessage("Total number: (" + ServerBot.getBots().size() + "/" + org.leavesmc.leaves.LeavesConfig.fakeplayerLimit + ")"); ++ sender.sendMessage("Total number: (" + botList.bots.size() + "/" + org.leavesmc.leaves.LeavesConfig.fakeplayerLimit + ")"); + for (World world : botMap.keySet()) { + sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world))); + } + } else { -+ World world = Bukkit.getWorld(args[2]); ++ World world = Bukkit.getWorld(args[1]); + + if (world == null) { -+ sender.sendMessage(ChatColor.RED + "Unknown world"); ++ sender.sendMessage(text("Unknown world", NamedTextColor.RED)); + return; + } + -+ List botList = new ArrayList<>(); -+ for (ServerBot bot : ServerBot.getBots()) { -+ Bot bukkitBot = bot.getBukkitPlayer(); ++ List snowBotList = new ArrayList<>(); ++ for (ServerBot bot : botList.bots) { ++ Bot bukkitBot = bot.getBukkitEntity(); + if (bukkitBot.getWorld() == world) { -+ botList.add(bukkitBot.getName()); ++ snowBotList.add(bukkitBot.getName()); + } + } + -+ sender.sendMessage(world.getName() + "(" + botList.size() + "): " + formatPlayerNameList(botList)); ++ sender.sendMessage(world.getName() + "(" + botList.bots.size() + "): " + formatPlayerNameList(snowBotList)); + } + } + @@ -1031,95 +1152,348 @@ index 0000000000000000000000000000000000000000..918cdffedddba7cebb8013600bf3a2f5 + return string.substring(1, string.length() - 1); + } +} -diff --git a/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java +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..4f5e6e5c1b9d8bd38c98e97fd31b38338f35faa6 +index 0000000000000000000000000000000000000000..ebb3aa94718382e8fac2a8fa617c796a9387709b --- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java -@@ -0,0 +1,191 @@ ++++ b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java +@@ -0,0 +1,120 @@ +package org.leavesmc.leaves.bot; + -+import com.google.common.collect.ImmutableList; -+import com.mojang.datafixers.util.Pair; -+import net.minecraft.core.NonNullList; -+import net.minecraft.core.component.DataComponentPatch; -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.chat.Component; -+import net.minecraft.world.ContainerHelper; -+import net.minecraft.world.SimpleContainer; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.item.component.CustomData; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.command.CommandSender; ++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; + -+import javax.annotation.Nonnull; -+import java.util.List; ++import java.util.Objects; ++import java.util.function.Consumer; + -+// Power by gugle-carpet-addition(https://github.com/Gu-ZT/gugle-carpet-addition) -+public class BotInventoryContainer extends SimpleContainer { ++public record BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) { + -+ public final NonNullList items; -+ public final NonNullList armor; -+ public final NonNullList offhand; -+ private final List> compartments; -+ private final NonNullList buttons = NonNullList.withSize(13, ItemStack.EMPTY); -+ private final ServerBot player; ++ private static final MinecraftServer server = MinecraftServer.getServer(); + -+ public BotInventoryContainer(ServerBot player) { -+ this.player = player; -+ this.items = this.player.getInventory().items; -+ this.armor = this.player.getInventory().armor; -+ this.offhand = this.player.getInventory().offhand; -+ this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons); -+ createButton(); ++ public ServerBot createNow() { ++ return server.getBotList().createNewBot(this); + } + -+ @Override -+ public int getContainerSize() { -+ return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size(); ++ @NotNull ++ public static Builder builder(@NotNull String realName, @Nullable Location location) { ++ return new Builder(realName, location); + } + -+ @Override -+ public boolean isEmpty() { -+ for (ItemStack itemStack : this.items) { -+ if (itemStack.isEmpty()) { -+ continue; -+ } -+ return false; ++ 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; + } -+ for (ItemStack itemStack : this.armor) { -+ if (itemStack.isEmpty()) { -+ continue; -+ } -+ return false; ++ ++ public Builder name(@NotNull String name) { ++ Objects.requireNonNull(name); ++ this.name = name; ++ return this; + } -+ for (ItemStack itemStack : this.offhand) { -+ if (itemStack.isEmpty()) { -+ continue; -+ } -+ return false; ++ ++ public Builder skinName(@Nullable String skinName) { ++ this.skinName = skinName; ++ return this; + } -+ return true; -+ } + -+ @Override -+ @Nonnull -+ public ItemStack getItem(int slot) { -+ Pair, Integer> pair = getItemSlot(slot); -+ if (pair != null) { -+ return pair.getFirst().get(pair.getSecond()); -+ } else { -+ return ItemStack.EMPTY; ++ public Builder skin(@Nullable String[] skin) { ++ this.skin = skin; ++ return this; + } -+ } + -+ public Pair, Integer> getItemSlot(int slot) { -+ switch (slot) { -+ case 0 -> { -+ return new Pair<>(buttons, 0); ++ public Builder mojangAPISkin() { ++ if (this.skinName != null) { ++ this.skin = MojangAPI.getSkin(this.skinName); + } -+ case 1, 2, 3, 4 -> { ++ return this; ++ } ++ ++ public Builder location(@NotNull Location location) { ++ this.location = location; ++ return this; ++ } ++ ++ public Builder createReason(@NotNull BotCreateEvent.CreateReason createReason) { ++ Objects.requireNonNull(createReason); ++ this.createReason = createReason; ++ return this; ++ } ++ ++ public Builder creator(CommandSender creator) { ++ this.creator = creator; ++ return this; ++ } ++ ++ public BotCreateState build() { ++ return new BotCreateState(realName, name, skinName, skin, location, createReason, creator); ++ } ++ ++ public void spawnWithSkin(Consumer consumer) { ++ Bukkit.getScheduler().runTaskAsynchronously(MinecraftInternalPlugin.INSTANCE, () -> { ++ this.mojangAPISkin(); ++ Bukkit.getScheduler().runTask(MinecraftInternalPlugin.INSTANCE, () -> { ++ CraftBot bot = this.spawn(); ++ if (bot != null && consumer != null) { ++ consumer.accept(bot); ++ } ++ }); ++ }); ++ } ++ ++ @Nullable ++ public CraftBot spawn() { ++ Objects.requireNonNull(this.location); ++ ServerBot bot = this.build().createNow(); ++ return bot != null ? bot.getBukkitEntity() : null; ++ } ++ } ++} +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 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java +@@ -0,0 +1,121 @@ ++package org.leavesmc.leaves.bot; ++ ++import com.mojang.logging.LogUtils; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtAccounter; ++import net.minecraft.nbt.NbtIo; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.level.storage.LevelResource; ++import net.minecraft.world.level.storage.LevelStorageSource; ++import org.jetbrains.annotations.NotNull; ++import org.slf4j.Logger; ++ ++import java.io.File; ++import java.io.IOException; ++import java.util.Optional; ++ ++public class BotDataStorage implements IPlayerDataStorage { ++ ++ private static final LevelResource BOT_DATA_DIR = new LevelResource("fakeplayerdata"); ++ private static final LevelResource BOT_LIST_FILE = new LevelResource("fakeplayer.dat"); ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ private final File botDir; ++ private final File botListFile; ++ ++ private CompoundTag savedBotList; ++ ++ public BotDataStorage(LevelStorageSource.@NotNull LevelStorageAccess session) { ++ this.botDir = session.getLevelPath(BOT_DATA_DIR).toFile(); ++ this.botListFile = session.getLevelPath(BOT_LIST_FILE).toFile(); ++ this.botDir.mkdirs(); ++ ++ this.savedBotList = new CompoundTag(); ++ if (this.botListFile.exists() && this.botListFile.isFile()) { ++ try { ++ Optional.of(NbtIo.readCompressed(this.botListFile.toPath(), NbtAccounter.unlimitedHeap())).ifPresent(tag -> this.savedBotList = tag); ++ } catch (Exception exception) { ++ BotDataStorage.LOGGER.warn("Failed to load player data list"); ++ } ++ } ++ } ++ ++ @Override ++ public void save(Player player) { ++ boolean flag = true; ++ try { ++ CompoundTag nbt = player.saveWithoutId(new CompoundTag()); ++ File file = new File(this.botDir, player.getStringUUID() + ".dat"); ++ ++ if (file.exists() && file.isFile()) { ++ if (!file.delete()) { ++ throw new IOException("Failed to delete file: " + file); ++ } ++ } ++ if (!file.createNewFile()) { ++ throw new IOException("Failed to create nbt file: " + file); ++ } ++ NbtIo.writeCompressed(nbt, file.toPath()); ++ } catch (Exception exception) { ++ BotDataStorage.LOGGER.warn("Failed to save fakeplayer data for {}", player.getScoreboardName(), exception); ++ flag = false; ++ } ++ ++ if (flag && player instanceof ServerBot bot) { ++ CompoundTag nbt = new CompoundTag(); ++ nbt.putString("name", bot.createState.name()); ++ nbt.putUUID("uuid", bot.getUUID()); ++ nbt.putBoolean("resume", bot.resume); ++ this.savedBotList.put(bot.createState.realName(), nbt); ++ this.saveBotList(); ++ } ++ } ++ ++ @Override ++ public Optional load(Player player) { ++ return this.load(player.getScoreboardName(), player.getStringUUID()).map((nbt) -> { ++ player.load(nbt); ++ return nbt; ++ }); ++ } ++ ++ private Optional load(String name, String uuid) { ++ File file = new File(this.botDir, uuid + ".dat"); ++ ++ if (file.exists() && file.isFile()) { ++ try { ++ Optional optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap())); ++ if (!file.delete()) { ++ throw new IOException("Failed to delete fakeplayer data"); ++ } ++ this.savedBotList.remove(name); ++ this.saveBotList(); ++ return optional; ++ } catch (Exception exception) { ++ BotDataStorage.LOGGER.warn("Failed to load fakeplayer data for {}", name); ++ } ++ } ++ ++ return Optional.empty(); ++ } ++ ++ private void saveBotList() { ++ try { ++ if (this.botListFile.exists() && this.botListFile.isFile()) { ++ if (!this.botListFile.delete()) { ++ throw new IOException("Failed to delete file: " + this.botListFile); ++ } ++ } ++ if (!this.botListFile.createNewFile()) { ++ throw new IOException("Failed to create nbt file: " + this.botListFile); ++ } ++ NbtIo.writeCompressed(this.savedBotList, this.botListFile.toPath()); ++ } catch (Exception exception) { ++ BotDataStorage.LOGGER.warn("Failed to save player data list"); ++ } ++ } ++ ++ public CompoundTag getSavedBotList() { ++ return savedBotList; ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f5e6e5c1b9d8bd38c98e97fd31b38338f35faa6 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java +@@ -0,0 +1,191 @@ ++package org.leavesmc.leaves.bot; ++ ++import com.google.common.collect.ImmutableList; ++import com.mojang.datafixers.util.Pair; ++import net.minecraft.core.NonNullList; ++import net.minecraft.core.component.DataComponentPatch; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.network.chat.Component; ++import net.minecraft.world.ContainerHelper; ++import net.minecraft.world.SimpleContainer; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.item.component.CustomData; ++ ++import javax.annotation.Nonnull; ++import java.util.List; ++ ++// Power by gugle-carpet-addition(https://github.com/Gu-ZT/gugle-carpet-addition) ++public class BotInventoryContainer extends SimpleContainer { ++ ++ public final NonNullList items; ++ public final NonNullList armor; ++ public final NonNullList offhand; ++ private final List> compartments; ++ private final NonNullList buttons = NonNullList.withSize(13, ItemStack.EMPTY); ++ private final ServerBot player; ++ ++ public BotInventoryContainer(ServerBot player) { ++ this.player = player; ++ this.items = this.player.getInventory().items; ++ this.armor = this.player.getInventory().armor; ++ this.offhand = this.player.getInventory().offhand; ++ this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons); ++ createButton(); ++ } ++ ++ @Override ++ public int getContainerSize() { ++ return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ for (ItemStack itemStack : this.items) { ++ if (itemStack.isEmpty()) { ++ continue; ++ } ++ return false; ++ } ++ for (ItemStack itemStack : this.armor) { ++ if (itemStack.isEmpty()) { ++ continue; ++ } ++ return false; ++ } ++ for (ItemStack itemStack : this.offhand) { ++ if (itemStack.isEmpty()) { ++ continue; ++ } ++ return false; ++ } ++ return true; ++ } ++ ++ @Override ++ @Nonnull ++ public ItemStack getItem(int slot) { ++ Pair, Integer> pair = getItemSlot(slot); ++ if (pair != null) { ++ return pair.getFirst().get(pair.getSecond()); ++ } else { ++ return ItemStack.EMPTY; ++ } ++ } ++ ++ public Pair, Integer> getItemSlot(int slot) { ++ switch (slot) { ++ case 0 -> { ++ return new Pair<>(buttons, 0); ++ } ++ case 1, 2, 3, 4 -> { + return new Pair<>(armor, 4 - slot); + } + case 5, 6 -> { @@ -1228,12 +1602,356 @@ 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..003f1e01ba56ea2fa9ff311675c58b1d0a38d53b +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/BotList.java +@@ -0,0 +1,338 @@ ++package org.leavesmc.leaves.bot; ++ ++import com.google.common.collect.Maps; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.Style; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.network.chat.Component; ++import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.level.Level; ++import org.bukkit.Bukkit; ++import org.bukkit.Location; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.event.bot.BotCreateEvent; ++import org.leavesmc.leaves.event.bot.BotJoinEvent; ++import org.leavesmc.leaves.event.bot.BotLoadEvent; ++import org.leavesmc.leaves.event.bot.BotRemoveEvent; ++import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent; ++import org.slf4j.Logger; ++ ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Optional; ++import java.util.UUID; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++public class BotList { ++ ++ public static BotList INSTANCE; ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private final MinecraftServer server; ++ ++ public final List bots = new CopyOnWriteArrayList<>(); ++ private final BotDataStorage dataStorage; ++ ++ private final Map botsByUUID = Maps.newHashMap(); ++ private final Map botsByName = Maps.newHashMap(); ++ ++ public BotList(MinecraftServer server) { ++ this.server = server; ++ this.dataStorage = new BotDataStorage(server.storageSource); ++ INSTANCE = this; ++ } ++ ++ public ServerBot createNewBot(BotCreateState state) { ++ BotCreateEvent event = new BotCreateEvent(state.name(), state.skinName(), state.location(), state.createReason(), state.creator()); ++ event.setCancelled(!isCreateLegal(state.name())); ++ this.server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ Location location = event.getCreateLocation(); ++ ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); ++ ++ CustomGameProfile profile = new CustomGameProfile(BotUtil.getBotUUID(state), state.name(), state.skin()); ++ ServerBot bot = new ServerBot(this.server, world, profile); ++ bot.createState = state; ++ if (event.getCreator() instanceof org.bukkit.entity.Player player) { ++ bot.createPlayer = player.getUniqueId(); ++ } ++ ++ return this.placeNewBot(bot, world, location, null); ++ } ++ ++ public ServerBot loadNewBot(String realName) { ++ return this.loadNewBot(realName, this.dataStorage); ++ } ++ ++ public ServerBot loadNewBot(String realName, IPlayerDataStorage playerIO) { ++ UUID uuid = BotUtil.getBotUUID(realName); ++ ++ BotLoadEvent event = new BotLoadEvent(realName, uuid); ++ this.server.server.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ ServerBot bot = new ServerBot(this.server, this.server.getLevel(Level.OVERWORLD), new GameProfile(uuid, realName)); ++ bot.connection = new ServerBotPacketListenerImpl(this.server, bot); ++ Optional optional = playerIO.load(bot); ++ ++ if (optional.isEmpty()) { ++ return null; ++ } ++ ++ ResourceKey resourcekey = null; ++ if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) { ++ org.bukkit.World bWorld = Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast"))); ++ if (bWorld != null) { ++ resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); ++ } ++ } ++ if (resourcekey == null) { ++ return null; ++ } ++ ++ ServerLevel world = this.server.getLevel(resourcekey); ++ return this.placeNewBot(bot, world, bot.getLocation(), optional.get()); ++ } ++ ++ public ServerBot placeNewBot(ServerBot bot, ServerLevel world, Location location, @Nullable CompoundTag nbt) { ++ bot.isRealPlayer = true; ++ bot.connection = new ServerBotPacketListenerImpl(this.server, bot); ++ bot.setServerLevel(world); ++ ++ BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location); ++ this.server.server.getPluginManager().callEvent(event); ++ location = event.getSpawnLocation(); ++ ++ bot.spawnIn(world); ++ bot.gameMode.setLevel((ServerLevel) bot.level()); ++ ++ bot.setPosRaw(location.getX(), location.getY(), location.getZ()); ++ bot.setRot(location.getYaw(), location.getPitch()); ++ ++ this.bots.add(bot); ++ this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), bot); ++ this.botsByUUID.put(bot.getUUID(), bot); ++ ++ bot.supressTrackerForLogin = true; ++ world.addNewPlayer(bot); ++ this.mountSavedVehicle(bot, world, nbt); ++ ++ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); ++ this.server.server.getPluginManager().callEvent(event1); ++ ++ net.kyori.adventure.text.Component joinMessage = event1.joinMessage(); ++ if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false); ++ } ++ ++ bot.renderAll(); ++ bot.supressTrackerForLogin = false; ++ bot.serverLevel().getChunkSource().chunkMap.addEntity(bot); ++ BotList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", bot.getName().getString(), "Local", bot.getId(), bot.serverLevel().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ()); ++ return bot; ++ } ++ ++ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, @Nullable CompoundTag nbt) { ++ Optional optional = Optional.ofNullable(nbt); ++ if (optional.isPresent() && optional.get().contains("RootVehicle", 10)) { ++ CompoundTag nbttagcompound = optional.get().getCompound("RootVehicle"); ++ Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver1, (entity1) -> { ++ return !worldserver1.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; ++ }); ++ ++ if (entity != null) { ++ UUID uuid; ++ ++ if (nbttagcompound.hasUUID("Attach")) { ++ uuid = nbttagcompound.getUUID("Attach"); ++ } else { ++ uuid = null; ++ } ++ ++ Iterator iterator; ++ Entity entity1; ++ ++ if (entity.getUUID().equals(uuid)) { ++ player.startRiding(entity, true); ++ } else { ++ iterator = entity.getIndirectPassengers().iterator(); ++ ++ while (iterator.hasNext()) { ++ entity1 = iterator.next(); ++ if (entity1.getUUID().equals(uuid)) { ++ player.startRiding(entity1, true); ++ break; ++ } ++ } ++ } ++ ++ if (!player.isPassenger()) { ++ BotList.LOGGER.warn("Couldn't reattach entity to fakeplayer"); ++ entity.discard(); ++ iterator = entity.getIndirectPassengers().iterator(); ++ ++ while (iterator.hasNext()) { ++ entity1 = iterator.next(); ++ entity1.discard(); ++ } ++ } ++ } ++ } ++ } ++ ++ 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 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); ++ this.server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) { ++ return; ++ } ++ ++ if (bot.removeTaskId != -1) { ++ Bukkit.getScheduler().cancelTask(bot.removeTaskId); ++ bot.removeTaskId = -1; ++ } ++ ++ if (this.server.isSameThread()) { ++ bot.doTick(); ++ } ++ ++ if (event.shouldSave()) { ++ playerIO.save(bot); ++ } else { ++ bot.dropAll(); ++ } ++ ++ if (bot.isPassenger()) { ++ Entity entity = bot.getRootVehicle(); ++ if (entity.hasExactlyOnePlayerPassenger()) { ++ bot.stopRiding(); ++ entity.getPassengersAndSelf().forEach((entity1) -> { ++ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { ++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ }); ++ } ++ } ++ ++ bot.unRide(); ++ bot.serverLevel().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ this.bots.remove(bot); ++ this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT)); ++ ++ UUID uuid = bot.getUUID(); ++ ServerBot bot1 = this.botsByUUID.get(uuid); ++ if (bot1 == bot) { ++ this.botsByUUID.remove(uuid); ++ } ++ ++ bot.removeTab(); ++ for (ServerPlayer player : bot.serverLevel().players()) { ++ if (!(player instanceof ServerBot) && !bot.needSendFakeData(player)) { ++ player.connection.send(new ClientboundRemoveEntitiesPacket(bot.getId())); ++ } ++ } ++ ++ net.kyori.adventure.text.Component removeMessage = event.removeMessage(); ++ if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false); ++ } ++ } ++ ++ public void removeAll() { ++ for (ServerBot bot : this.bots) { ++ bot.resume = LeavesConfig.fakeplayerResident; ++ this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, LeavesConfig.fakeplayerResident); ++ } ++ } ++ ++ public void loadResume() { ++ if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) { ++ for (String realName : this.getSavedBotList().getAllKeys()) { ++ CompoundTag nbt = this.getSavedBotList().getCompound(realName); ++ if (nbt.getBoolean("resume")) { ++ this.loadNewBot(realName); ++ } ++ } ++ } ++ } ++ ++ public void networkTick() { ++ this.bots.forEach(ServerBot::doTick); ++ } ++ ++ @Nullable ++ public ServerBot getBot(@NotNull UUID uuid) { ++ return this.botsByUUID.get(uuid); ++ } ++ ++ @Nullable ++ public ServerBot getBotByName(@NotNull String name) { ++ return this.botsByName.get(name.toLowerCase(Locale.ROOT)); ++ } ++ ++ public CompoundTag getSavedBotList() { ++ return this.dataStorage.getSavedBotList(); ++ } ++ ++ public boolean isCreateLegal(@NotNull String name) { ++ if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { ++ return false; ++ } ++ ++ if (Bukkit.getPlayerExact(name) != null || this.getBotByName(name) != null) { ++ return false; ++ } ++ ++ if (LeavesConfig.unableFakeplayerNames.contains(name)) { ++ return false; ++ } ++ ++ return this.bots.size() < LeavesConfig.fakeplayerLimit; ++ } ++ ++ public static class CustomGameProfile extends GameProfile { ++ ++ public CustomGameProfile(UUID uuid, String name, String[] skin) { ++ super(uuid, name); ++ this.setSkin(skin); ++ } ++ ++ public void setSkin(String[] skin) { ++ if (skin != null) { ++ this.getProperties().put("textures", new Property("textures", skin[0], skin[1])); ++ } ++ } ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java new file mode 100644 -index 0000000000000000000000000000000000000000..5bd34353b6ea86cd15ff48b8d6570167f35d75f0 +index 0000000000000000000000000000000000000000..10494446f915bc1720a18cfe75b2cab2404646e9 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java -@@ -0,0 +1,38 @@ +@@ -0,0 +1,36 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.datafixers.DataFixer; @@ -1241,6 +1959,7 @@ index 0000000000000000000000000000000000000000..5bd34353b6ea86cd15ff48b8d6570167 +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stat; +import net.minecraft.world.entity.player.Player; ++import org.jetbrains.annotations.NotNull; + +import java.io.File; + @@ -1254,57 +1973,35 @@ index 0000000000000000000000000000000000000000..5bd34353b6ea86cd15ff48b8d6570167 + + @Override + public void save() { -+ + } + + @Override -+ public void setValue(Player player, Stat stat, int value) { -+ ++ public void setValue(@NotNull Player player, @NotNull Stat stat, int value) { + } + + @Override -+ public void parseLocal(DataFixer dataFixer, String json) { -+ ++ public void parseLocal(@NotNull DataFixer dataFixer, @NotNull String json) { + } + + @Override -+ public int getValue(Stat stat) { ++ public int getValue(@NotNull Stat stat) { + return 0; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotUtil.java b/src/main/java/org/leavesmc/leaves/bot/BotUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..0882cd685a6c3a15dd8514e26cc4c31b1d5a8525 +index 0000000000000000000000000000000000000000..78414d1f53328cdc2963264ecb4f5a65e9783798 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotUtil.java -@@ -0,0 +1,228 @@ +@@ -0,0 +1,73 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.base.Charsets; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; +import net.minecraft.core.NonNullList; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.NbtAccounter; -+import net.minecraft.nbt.NbtIo; -+import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.storage.LevelResource; -+import org.bukkit.Bukkit; -+import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.LeavesLogger; -+import org.leavesmc.leaves.bot.agent.Actions; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.event.bot.BotCreateEvent; + -+import java.io.File; -+import java.io.IOException; -+import java.util.Collection; -+import java.util.Map; +import java.util.UUID; + +public class BotUtil { @@ -1362,175 +2059,57 @@ index 0000000000000000000000000000000000000000..0882cd685a6c3a15dd8514e26cc4c31b + } + + @NotNull -+ public static JsonObject saveBot(@NotNull ServerBot bot) { -+ JsonObject fakePlayer = getBotJson(bot); -+ -+ Collection actions = bot.getBotActions(); -+ JsonArray botActions = new JsonArray(); -+ for (BotAction action : actions) { -+ JsonObject actionObj = new JsonObject(); -+ actionObj.addProperty("name", action.getName()); -+ actionObj.addProperty("number", String.valueOf(action.getNumber())); -+ actionObj.addProperty("delay", String.valueOf(action.getTickDelay())); -+ botActions.add(actionObj); -+ } -+ fakePlayer.add("actions", botActions); -+ -+ CompoundTag invnbt = new CompoundTag(); -+ invnbt.put("Inventory", bot.getInventory().save(new ListTag())); -+ -+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fakeplayer/" + bot.getStringUUID() + ".dat").toFile(); -+ File parent = file.getParentFile(); -+ try { -+ if (!parent.exists() || !parent.isDirectory()) { -+ if (!parent.mkdirs()) { -+ throw new IOException("Failed to create directory: " + parent); -+ } -+ } -+ if (file.exists() && file.isFile()) { -+ if (!file.delete()) { -+ throw new IOException("Failed to delete file: " + file); -+ } -+ } -+ if (!file.createNewFile()) { -+ throw new IOException("Failed to create nbt file: " + file); -+ } -+ NbtIo.writeCompressed(invnbt, file.toPath()); -+ } catch (IOException e) { -+ LeavesLogger.LOGGER.warning("Failed to save fakeplayer inv: ", e); -+ } -+ -+ return fakePlayer; ++ public static UUID getBotUUID(@NotNull BotCreateState state) { ++ return getBotUUID(state.realName()); + } + -+ private static @NotNull JsonObject getBotJson(@NotNull ServerBot bot) { -+ double pos_x = bot.getX(); -+ double pos_y = bot.getY(); -+ double pos_z = bot.getZ(); -+ float yaw = bot.getYRot(); -+ float pitch = bot.getXRot(); -+ String dimension = bot.getLocation().getWorld().getName(); -+ String skin = bot.createState.skinName; -+ String realName = bot.createState.getRealName(); -+ String name = bot.createState.getName(); -+ String[] skinValue = bot.createState.skin; -+ -+ JsonObject fakePlayer = new JsonObject(); -+ fakePlayer.addProperty("pos_x", pos_x); -+ fakePlayer.addProperty("pos_y", pos_y); -+ fakePlayer.addProperty("pos_z", pos_z); -+ fakePlayer.addProperty("yaw", yaw); -+ fakePlayer.addProperty("pitch", pitch); -+ fakePlayer.addProperty("dimension", dimension); -+ fakePlayer.addProperty("skin", skin); -+ fakePlayer.addProperty("real_name", realName); -+ fakePlayer.addProperty("name", name); -+ -+ if (skinValue != null) { -+ JsonArray jsonArray = new JsonArray(); -+ for (String str : skinValue) { -+ jsonArray.add(str); -+ } -+ fakePlayer.add("skin_value", jsonArray); -+ } -+ -+ return fakePlayer; ++ public static UUID getBotUUID(@NotNull String realName) { ++ return UUID.nameUUIDFromBytes(("Fakeplayer:" + realName).getBytes(Charsets.UTF_8)); + } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java b/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ebe4d6c71e90be92387a585ea581c6b2c4af89d +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java +@@ -0,0 +1,13 @@ ++package org.leavesmc.leaves.bot; + -+ public static void loadBot(Map.@NotNull Entry entry) { -+ JsonObject fakePlayer = entry.getValue().getAsJsonObject(); -+ -+ String name = entry.getKey(); -+ String realName = name; -+ if (fakePlayer.has("real_name")) { -+ realName = fakePlayer.get("real_name").getAsString(); -+ name = fakePlayer.get("name").getAsString(); -+ } -+ -+ double pos_x = fakePlayer.get("pos_x").getAsDouble(); -+ double pos_y = fakePlayer.get("pos_y").getAsDouble(); -+ double pos_z = fakePlayer.get("pos_z").getAsDouble(); -+ float yaw = fakePlayer.get("yaw").getAsFloat(); -+ float pitch = fakePlayer.get("pitch").getAsFloat(); -+ String dimension = fakePlayer.get("dimension").getAsString(); -+ String skin = fakePlayer.get("skin").getAsString(); -+ -+ String[] skinValue = null; -+ if (fakePlayer.has("skin_value")) { -+ JsonArray jsonArray = fakePlayer.get("skin_value").getAsJsonArray(); -+ skinValue = new String[jsonArray.size()]; -+ for (int i = 0; i < jsonArray.size(); i++) { -+ skinValue[i] = jsonArray.get(i).getAsString(); -+ } -+ } -+ -+ Location location = new Location(Bukkit.getWorld(dimension), pos_x, pos_y, pos_z, yaw, pitch); -+ ServerBot.BotCreateState state = new ServerBot.BotCreateState(location, name, realName, skin, skinValue, BotCreateEvent.CreateReason.INTERNAL, null); ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.entity.player.Player; + -+ ListTag inv = null; -+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fakeplayer/" + getBotUUID(state) + ".dat").toFile(); -+ if (file.exists()) { -+ try { -+ CompoundTag nbt = NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()); -+ inv = nbt.getList("Inventory", 10); -+ } catch (IOException e) { -+ LeavesLogger.LOGGER.warning("Failed to load inventory: ", e); -+ } -+ if (!file.delete()) { -+ LeavesLogger.LOGGER.warning("Failed to delete file: " + file); -+ } -+ } ++import java.util.Optional; + -+ final JsonArray finalActions = fakePlayer.get("actions").getAsJsonArray(); -+ final ListTag finalInv = inv; -+ state.createNow(serverBot -> { -+ if (finalInv != null) { -+ serverBot.getInventory().load(finalInv); -+ } ++public interface IPlayerDataStorage { + -+ for (JsonElement element : finalActions) { -+ JsonObject actionObj = element.getAsJsonObject(); -+ BotAction action = Actions.getForName(actionObj.get("name").getAsString()); -+ if (action != null) { -+ BotAction newAction = action.getNew(serverBot, -+ action.getArgument().parse(0, new String[]{actionObj.get("delay").getAsString(), actionObj.get("number").getAsString()}) -+ ); -+ serverBot.setBotAction(newAction); -+ } -+ } -+ }); -+ } ++ void save(Player player); + -+ @NotNull -+ public static UUID getBotUUID(ServerBot.@NotNull BotCreateState state) { -+ return UUID.nameUUIDFromBytes(("Fakeplayer:" + state.getRealName()).getBytes(Charsets.UTF_8)); -+ } ++ Optional load(Player player); +} diff --git a/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java b/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java new file mode 100644 -index 0000000000000000000000000000000000000000..0db337866c71283464d026a4f230016b31d1a8cd +index 0000000000000000000000000000000000000000..517e3321b866abe9d17a6fe9e919528b50bb130a --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java -@@ -0,0 +1,41 @@ +@@ -0,0 +1,39 @@ +package org.leavesmc.leaves.bot; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; ++import org.leavesmc.leaves.LeavesConfig; + +import java.io.IOException; +import java.io.InputStreamReader; -+import java.net.URL; ++import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class MojangAPI { + -+ private static final boolean CACHE_ENABLED = false; -+ + private static final Map CACHE = new HashMap<>(); + + public static String[] getSkin(String name) { -+ if (CACHE_ENABLED && CACHE.containsKey(name)) { ++ if (LeavesConfig.fakeplayerCacheSkin && CACHE.containsKey(name)) { + return CACHE.get(name); + } + @@ -1542,246 +2121,131 @@ index 0000000000000000000000000000000000000000..0db337866c71283464d026a4f230016b + // Laggggggggggggggggggggggggggggggggggggggggg + public static String[] pullFromAPI(String name) { + try { -+ String uuid = new JsonParser().parse(new InputStreamReader(new URL("https://api.mojang.com/users/profiles/minecraft/" + name) -+ .openStream())).getAsJsonObject().get("id").getAsString(); -+ JsonObject property = new JsonParser() -+ .parse(new InputStreamReader(new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false") -+ .openStream())).getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject(); -+ return new String[] {property.get("value").getAsString(), property.get("signature").getAsString()}; -+ } catch (IOException | IllegalStateException e) { ++ String uuid = JsonParser.parseReader(new InputStreamReader(URI.create("https://api.mojang.com/users/profiles/minecraft/" + name).toURL().openStream())) ++ .getAsJsonObject().get("id").getAsString(); ++ JsonObject property = JsonParser.parseReader(new InputStreamReader(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false").toURL().openStream())) ++ .getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject(); ++ return new String[]{property.get("value").getAsString(), property.get("signature").getAsString()}; ++ } catch (IOException | IllegalStateException | IllegalArgumentException e) { + return null; + } + } +} 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..462d58ad184ebe6bd6f161bff1481745b5723cf0 +index 0000000000000000000000000000000000000000..18cf5182c33c43690d7329d6bd435ebc8ee517e5 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java -@@ -0,0 +1,782 @@ +@@ -0,0 +1,543 @@ +package org.leavesmc.leaves.bot; + -+import com.google.common.collect.Lists; -+import com.google.gson.Gson; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; ++import com.google.common.collect.ImmutableMap; +import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.event.entity.EntityKnockbackEvent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.Style; -+import net.minecraft.Util; +import net.minecraft.core.BlockPos; -+import net.minecraft.network.Connection; -+import net.minecraft.network.PacketSendListener; ++import net.minecraft.core.particles.BlockParticleOption; ++import net.minecraft.core.particles.ParticleTypes; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.StringTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; -+import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; -+import net.minecraft.network.syncher.EntityDataAccessor; -+import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.CommonListenerCookie; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.ServerPlayerConnection; +import net.minecraft.stats.ServerStatsCounter; ++import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ChestMenu; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.portal.DimensionTransition; -+import net.minecraft.world.level.storage.LevelResource; -+import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; -+import org.bukkit.Material; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.scheduler.CraftScheduler; -+import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.LeavesLogger; ++import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.bot.agent.actions.StopAction; -+import org.leavesmc.leaves.entity.Bot; ++import org.leavesmc.leaves.bot.agent.BotConfig; ++import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.entity.CraftBot; ++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; -+import org.leavesmc.leaves.event.bot.BotJoinEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; ++import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; +import org.leavesmc.leaves.util.MathUtils; + -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.File; -+import java.io.IOException; -+import java.nio.charset.StandardCharsets; -+import java.nio.file.Files; -+import java.util.Collection; ++import java.util.ArrayList; +import java.util.EnumSet; -+import java.util.HashMap; -+import java.util.Iterator; +import java.util.List; +import java.util.Map; ++import java.util.Objects; +import java.util.UUID; -+import java.util.concurrent.CopyOnWriteArrayList; -+import java.util.function.Consumer; -+import java.util.function.Predicate; -+ -+// TODO remake all -+public class ServerBot extends ServerPlayer { -+ -+ private final Map actions; -+ private final boolean removeOnDeath; -+ private final int tracingRange; -+ -+ private Vec3 velocity; -+ private int fireTicks; -+ private int jumpTicks; -+ private int noFallTicks; -+ public boolean waterSwim; -+ private Vec3 knockback; -+ public BotCreateState createState; -+ public UUID createPlayer; -+ -+ private final ServerStatsCounter stats; -+ private final BotInventoryContainer container; -+ -+ private static final List bots = new CopyOnWriteArrayList<>(); -+ -+ public boolean spawnPhantom; -+ public int notSleepTicks; -+ public boolean alwaysSendData; -+ -+ public int removeTaskId = -1; -+ -+ private ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) { -+ super(server, world, profile, ClientInformation.createDefault()); -+ this.entityData.set(new EntityDataAccessor<>(16, EntityDataSerializers.INT), 0xFF); -+ this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2); -+ -+ this.gameMode = new ServerBotGameMode(this); -+ this.velocity = new Vec3(this.xxa, this.yya, this.zza); -+ this.noFallTicks = 60; -+ this.fireTicks = 0; -+ this.actions = new HashMap<>(); -+ this.removeOnDeath = true; -+ this.stats = new BotStatsCounter(server); -+ this.container = new BotInventoryContainer(this); -+ this.waterSwim = true; -+ this.knockback = Vec3.ZERO; -+ this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange; -+ this.notSleepTicks = 0; -+ -+ this.fauxSleeping = LeavesConfig.fakeplayerSkipSleep; -+ this.spawnPhantom = LeavesConfig.fakeplayerSpawnPhantom; -+ this.alwaysSendData = LeavesConfig.alwaysSendFakeplayerData; -+ } -+ -+ public static ServerBot createBot(@NotNull BotCreateState state) { -+ if (!isCreateLegal(state.name)) { -+ return null; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ BotCreateEvent event = new BotCreateEvent(state.name, state.skinName, state.loc, state.createReason, state.creator); -+ server.server.getPluginManager().callEvent(event); -+ -+ if (event.isCancelled()) { -+ return null; -+ } -+ -+ Location location = event.getCreateLocation(); -+ -+ ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); -+ CustomGameProfile profile = new CustomGameProfile(BotUtil.getBotUUID(state), state.name, state.skin); -+ -+ ServerBot bot = new ServerBot(server, world, profile); -+ -+ bot.connection = new ServerGamePacketListenerImpl(server, new Connection(PacketFlow.SERVERBOUND) { // ? -+ @Override -+ public void send(@NotNull Packet packet) { -+ } ++import java.util.function.Predicate; + -+ @Override -+ public void send(@NotNull Packet packet, @Nullable PacketSendListener packetsendlistener) { -+ } ++// TODO test ++public class ServerBot extends ServerPlayer { + -+ @Override -+ public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { -+ } -+ }, bot, CommonListenerCookie.createInitial(profile, false)); -+ bot.isRealPlayer = true; -+ bot.createState = state; -+ if (event.getCreator().isPresent() && event.getCreator().get() instanceof org.bukkit.entity.Player player) { -+ bot.createPlayer = player.getUniqueId(); -+ } ++ private final Map, BotConfig> configs; ++ private final List> actions; + -+ bot.teleportTo(location.getX(), location.getY(), location.getZ()); -+ bot.setRot(location.getYaw(), location.getPitch()); -+ world.addFreshEntity(bot, CreatureSpawnEvent.SpawnReason.COMMAND); ++ public boolean resume = false; ++ public BotCreateState createState; ++ public UUID createPlayer; + -+ bot.renderAll(); -+ server.getPlayerList().addNewBot(bot); -+ bots.add(bot); ++ private final int tracingRange; ++ private final ServerStatsCounter stats; ++ private final BotInventoryContainer container; + -+ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitPlayer(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); -+ server.server.getPluginManager().callEvent(event1); ++ public int notSleepTicks; + -+ net.kyori.adventure.text.Component joinMessage = event1.joinMessage(); -+ if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) { -+ server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false); -+ } ++ public int removeTaskId = -1; + -+ return bot; -+ } ++ private Vec3 knockback = Vec3.ZERO; + -+ public static boolean isCreateLegal(@NotNull String name) { -+ if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { -+ return false; -+ } ++ public ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) { ++ super(server, world, profile, ClientInformation.createDefault()); ++ this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2); + -+ if (Bukkit.getPlayerExact(name) != null || ServerBot.getBot(name) != null) { -+ return false; -+ } ++ this.gameMode = new ServerBotGameMode(this); ++ this.actions = new ArrayList<>(); + -+ if (org.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(name)) { -+ return false; ++ ImmutableMap.Builder, BotConfig> configBuilder = ImmutableMap.builder(); ++ for (Configs config : Configs.getConfigs()) { ++ configBuilder.put(config, config.config.create(this)); + } ++ this.configs = configBuilder.build(); + -+ return ServerBot.getBots().size() < org.leavesmc.leaves.LeavesConfig.fakeplayerLimit; -+ } ++ this.stats = new BotStatsCounter(server); ++ this.container = new BotInventoryContainer(this); ++ this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange; + -+ public void renderAll() { -+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach( -+ player -> { -+ this.sendPlayerInfo(player); -+ this.sendFakeData(player.connection, false); -+ } -+ ); ++ this.notSleepTicks = 0; ++ this.fauxSleeping = LeavesConfig.fakeplayerSkipSleep; + } + + public void sendPlayerInfo(ServerPlayer player) { @@ -1789,7 +2253,7 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + } + + public boolean needSendFakeData(ServerPlayer player) { -+ return alwaysSendData && (player.level() == this.level() && player.position().distanceToSqr(this.position()) > this.tracingRange); ++ return this.getConfigValue(Configs.ALWAYS_SEND_DATA) && (player.level() == this.level() && player.position().distanceToSqr(this.position()) > this.tracingRange); + } + + public void sendFakeDataIfNeed(ServerPlayer player, boolean login) { @@ -1808,130 +2272,72 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + + playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity)); + if (login) { -+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> { -+ playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))); -+ }, 10); ++ Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10); + } else { + playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))); + } + } + -+ private void sendPacket(Packet packet) { -+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach( -+ player -> player.connection.send(packet) ++ public void renderAll() { ++ this.server.getPlayerList().getPlayers().forEach( ++ player -> { ++ this.sendPlayerInfo(player); ++ this.sendFakeDataIfNeed(player, false); ++ } + ); + } + -+ // die check start -+ @Override -+ public void die(@NotNull DamageSource damageSource) { -+ if (removeOnDeath) { -+ onRemove(BotRemoveEvent.RemoveReason.DEATH); -+ } ++ private void sendPacket(Packet packet) { ++ this.server.getPlayerList().getPlayers().forEach(player -> player.connection.send(packet)); + } + -+ public void onRemove(BotRemoveEvent.RemoveReason reason) { -+ onRemove(reason, null); -+ } ++ @Override ++ public void die(@NotNull DamageSource damageSource) { ++ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); ++ Component defaultMessage = this.getCombatTracker().getDeathMessage(); + -+ public void onRemove(BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover) { -+ BotRemoveEvent event = new BotRemoveEvent(this.getBukkitPlayer(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", this.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); ++ BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag); + this.server.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ if (this.getHealth() <= 0) { ++ this.setHealth(0.1f); ++ } + return; + } + -+ this.dropAll(); -+ if (this.removeTaskId != -1) { -+ Bukkit.getScheduler().cancelTask(this.removeTaskId); -+ this.removeTaskId = -1; -+ } -+ bots.remove(this); -+ this.server.getPlayerList().removeBot(this); -+ this.remove(RemovalReason.DISCARDED); -+ this.setDead(); -+ this.removeTab(); ++ this.gameEvent(GameEvent.ENTITY_DIE); + -+ net.kyori.adventure.text.Component removeMessage = event.removeMessage(); -+ if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) { -+ server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false); ++ net.kyori.adventure.text.Component deathMessage = event.deathMessage(); ++ if (event.isSendDeathMessage() && deathMessage != null && !deathMessage.equals(net.kyori.adventure.text.Component.empty())) { ++ this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(deathMessage), false); + } -+ } + -+ private void removeTab() { -+ sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID()))); ++ this.server.getBotList().removeBot(this, BotRemoveEvent.RemoveReason.DEATH, null, false); + } + -+ private void setDead() { -+ sendPacket(new ClientboundRemoveEntitiesPacket(getId())); -+ this.dead = true; -+ this.inventoryMenu.removed(this); -+ this.containerMenu.removed(this); ++ public void removeTab() { ++ this.sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID()))); + } + -+ // die check end -+ + @Nullable + @Override + public Entity changeDimension(@NotNull DimensionTransition teleportTarget) { + return null; // disable dimension change + } + -+ public Bot getBukkitPlayer() { -+ return getBukkitEntity(); -+ } -+ -+ @Override -+ @NotNull -+ public CraftBot getBukkitEntity() { -+ return (CraftBot) super.getBukkitEntity(); -+ } -+ -+ @Override -+ public boolean isInWater() { -+ Location loc = getLocation(); -+ for (int i = 0; i <= 2; i++) { -+ Material type = loc.getBlock().getType(); -+ if (type == Material.WATER || type == Material.LAVA) { -+ return true; -+ } -+ loc.add(0, 0.9, 0); -+ } -+ return false; -+ } -+ + @Override + public void tick() { -+ super.tick(); -+ this.doTick(); -+ -+ if (!isAlive()) { ++ if (!this.isAlive()) { + return; + } ++ super.tick(); + -+ if (spawnPhantom) { ++ if (this.getConfigValue(Configs.SPAWN_PHANTOM)) { + notSleepTicks++; + } + -+ if (fireTicks > 0) { -+ --fireTicks; -+ } -+ if (jumpTicks > 0) { -+ --jumpTicks; -+ } -+ if (noFallTicks > 0) { -+ --noFallTicks; -+ } -+ if (takeXpDelay > 0) { -+ --takeXpDelay; -+ } -+ -+ this.updateLocation(); -+ this.updatePlayerPose(); -+ this.serverLevel().getChunkSource().move(this); -+ -+ if (server.getTickCount() % 20 == 0) { ++ if (LeavesConfig.fakeplayerRegenAmount > 0.0 && server.getTickCount() % 20 == 0) { + float health = getHealth(); + float maxHealth = getMaxHealth(); + float regenAmount = (float) (LeavesConfig.fakeplayerRegenAmount * 20); @@ -1945,65 +2351,6 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + + this.setHealth(amount); + } -+ -+ BlockPos blockposition = this.getOnPosLegacy(); -+ BlockState iblockdata = this.level().getBlockState(blockposition); -+ Vec3 vec3d1 = this.collide(velocity); -+ this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition); -+ -+ ++this.attackStrengthTicker; -+ -+ if (this.getHealth() > 0.0F) { -+ AABB axisalignedbb; -+ -+ if (this.isPassenger() && !this.getVehicle().isRemoved()) { -+ axisalignedbb = this.getBoundingBox().minmax(this.getVehicle().getBoundingBox()).inflate(1.0D, 0.0D, 1.0D); -+ } else { -+ axisalignedbb = this.getBoundingBox().inflate(1.0D, 0.5D, 1.0D); -+ } -+ -+ List list = this.level().getEntities(this, axisalignedbb); -+ List list1 = Lists.newArrayList(); -+ -+ for (Entity entity : list) { -+ if (entity.getType() == EntityType.EXPERIENCE_ORB) { -+ list1.add(entity); -+ } else if (!entity.isRemoved()) { -+ this.touch(entity); -+ } -+ } -+ -+ if (!list1.isEmpty()) { -+ this.touch(Util.getRandom(list1, this.random)); -+ } -+ } -+ -+ Iterator> iterator = actions.entrySet().iterator(); -+ while (iterator.hasNext()) { -+ Map.Entry entry = iterator.next(); -+ if (entry.getValue().isCancel()) { -+ iterator.remove(); -+ } else { -+ entry.getValue().tryTick(this); -+ } -+ } -+ } -+ -+ public Entity getTargetEntity(int maxDistance, Predicate predicate) { -+ List entities = this.level().getEntities((Entity) null, this.getBoundingBox(), (e -> e != this && (predicate == null || predicate.test(e)))); -+ if (!entities.isEmpty()) { -+ return entities.getFirst(); -+ } else { -+ EntityHitResult result = this.getBukkitEntity().rayTraceEntity(maxDistance, false); -+ if (result != null && (predicate == null || predicate.test(result.getEntity()))) { -+ return result.getEntity(); -+ } -+ } -+ return null; -+ } -+ -+ private void touch(@NotNull Entity entity) { -+ entity.playerTouch(this); + } + + @Override @@ -2013,7 +2360,7 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + } + + public void updateItemInHand(InteractionHand hand) { -+ net.minecraft.world.item.ItemStack item = getItemInHand(hand); ++ ItemStack item = this.getItemInHand(hand); + + if (!item.isEmpty()) { + BotUtil.replenishment(item, getInventory().items); @@ -2021,15 +2368,54 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + BotUtil.replaceTool(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND, this); + } + } -+ detectEquipmentUpdatesPublic(); ++ this.detectEquipmentUpdatesPublic(); + } + -+ public long getEatStartTime() { -+ return eatStartTime; ++ @Override ++ public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { ++ if (LeavesConfig.openFakeplayerInventory) { ++ if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { ++ BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); ++ this.server.server.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ Component menuName = this.getDisplayName() != null ? this.getDisplayName() : Component.literal(this.createState.name()); ++ player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), menuName)); ++ return InteractionResult.SUCCESS; ++ } ++ } ++ } ++ return super.interact(player, hand); + } + + @Override + public void checkFallDamage(double heightDifference, boolean onGround, @NotNull BlockState state, @NotNull BlockPos landedPosition) { ++ if (onGround && this.fallDistance > 0.0F) { ++ this.onChangedBlock(this.serverLevel(), landedPosition); ++ double d1 = this.getAttributeValue(Attributes.SAFE_FALL_DISTANCE); ++ ++ if ((double) this.fallDistance > d1 && !state.isAir()) { ++ double d2 = this.getX(); ++ double d3 = this.getY(); ++ double d4 = this.getZ(); ++ BlockPos blockposition = this.blockPosition(); ++ ++ if (landedPosition.getX() != blockposition.getX() || landedPosition.getZ() != blockposition.getZ()) { ++ double d5 = d2 - (double) landedPosition.getX() - 0.5D; ++ double d6 = d4 - (double) landedPosition.getZ() - 0.5D; ++ double d7 = Math.max(Math.abs(d5), Math.abs(d6)); ++ ++ d2 = (double) landedPosition.getX() + 0.5D + d5 / d7 * 0.5D; ++ d4 = (double) landedPosition.getZ() + 0.5D + d6 / d7 * 0.5D; ++ } ++ ++ float f = (float) Mth.ceil((double) this.fallDistance - d1); ++ double d8 = Math.min(0.2F + f / 15.0F, 2.5D); ++ int i = (int) (150.0D * d8); ++ ++ this.serverLevel().sendParticles(this, new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false); ++ } ++ } ++ + if (onGround) { + if (this.fallDistance > 0.0F) { + state.getBlock().fallOn(this.level(), state, landedPosition, this, this.fallDistance); @@ -2046,311 +2432,263 @@ index 0000000000000000000000000000000000000000..462d58ad184ebe6bd6f161bff1481745 + + @Override + public void doTick() { -+ if (this.hurtTime > 0) { -+ this.hurtTime -= 1; -+ } -+ -+ baseTick(); ++ this.absMoveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + -+ this.lerpSteps = (int) this.zza; -+ this.animStep = this.run; -+ this.yRotO = this.getYRot(); -+ this.xRotO = this.getXRot(); -+ } ++ if (this.takeXpDelay > 0) { ++ --this.takeXpDelay; ++ } + -+ public Location getLocation() { -+ return getBukkitPlayer().getLocation(); -+ } ++ if (this.isSleeping()) { ++ ++this.sleepCounter; ++ if (this.sleepCounter > 100) { ++ this.sleepCounter = 100; ++ this.notSleepTicks = 0; ++ } + -+ @Override -+ public void knockback(double strength, double x, double z, @Nullable Entity attacker, @NotNull EntityKnockbackEvent.Cause cause) { -+ strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); -+ if (strength > 0.0D) { -+ this.hasImpulse = true; -+ Vec3 vec3d = this.getDeltaMovement(); -+ Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength); -+ knockback = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z); ++ if (!this.level().isClientSide && this.level().isDay()) { ++ this.stopSleepInBed(false, true); ++ } ++ } else if (this.sleepCounter > 0) { ++ ++this.sleepCounter; ++ if (this.sleepCounter >= 110) { ++ this.sleepCounter = 0; ++ } + } -+ } + -+ private void updateLocation() { -+ this.velocity = new Vec3(this.xxa, this.yya, this.zza); ++ this.updateIsUnderwater(); + -+ if (waterSwim && isInWater()) { -+ this.addDeltaMovement(new Vec3(0, 0.05, 0)); -+ } + this.addDeltaMovement(knockback); -+ knockback = Vec3.ZERO; ++ this.knockback = Vec3.ZERO; + -+ this.travel(this.velocity); -+ } ++ this.server.tell(this.server.wrapRunnable(this::runAction)); + -+ public void faceLocation(@NotNull Location loc) { -+ look(loc.toVector().subtract(getLocation().toVector()), false); -+ } ++ this.livingEntityTick(); + -+ public void look(Vector dir, boolean keepYaw) { -+ float yaw, pitch; ++ this.foodData.tick(this); + -+ if (keepYaw) { -+ yaw = this.getYHeadRot(); -+ pitch = MathUtils.fetchPitch(dir); -+ } else { -+ float[] vals = MathUtils.fetchYawPitch(dir); -+ yaw = vals[0]; -+ pitch = vals[1]; ++ ++this.attackStrengthTicker; ++ ItemStack itemstack = this.getMainHandItem(); ++ if (!ItemStack.matches(this.lastItemInMainHand, itemstack)) { ++ if (!ItemStack.isSameItem(this.lastItemInMainHand, itemstack)) { ++ this.resetAttackStrengthTicker(); ++ } + -+ sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f))); ++ this.lastItemInMainHand = itemstack.copy(); + } + -+ this.setRot(yaw, pitch); -+ } -+ -+ @Override -+ public void setRot(float yaw, float pitch) { -+ this.getBukkitEntity().setRotation(yaw, pitch); -+ } ++ this.getCooldowns().tick(); ++ this.updatePlayerPose(); + -+ public void attack(@NotNull Entity target) { -+ super.attack(target); -+ swing(InteractionHand.MAIN_HAND); ++ if (this.hurtTime > 0) { ++ this.hurtTime -= 1; ++ } + } + + @Override -+ public void jumpFromGround() { -+ double jumpPower = (double) this.getJumpPower() + this.getJumpBoostPower(); -+ this.addDeltaMovement(new Vec3(0, jumpPower, 0)); -+ } -+ -+ public void dropAll() { -+ getInventory().dropAll(); -+ detectEquipmentUpdatesPublic(); -+ } -+ -+ public void setBotAction(BotAction action) { -+ if (!LeavesConfig.fakeplayerUseAction) { -+ return; -+ } -+ if (action instanceof StopAction) { -+ this.actions.clear(); ++ public void knockback(double strength, double x, double z, @Nullable Entity attacker, @NotNull EntityKnockbackEvent.Cause cause) { ++ strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); ++ if (strength > 0.0D) { ++ Vec3 vec3d = this.getDeltaMovement(); ++ Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength); ++ this.hasImpulse = true; ++ this.knockback = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z).subtract(vec3d); + } -+ action.init(); -+ this.actions.put(action.getName(), action); -+ } -+ -+ public Collection getBotActions() { -+ return actions.values(); -+ } -+ -+ public BotAction getBotAction(String name) { -+ return actions.get(name); -+ } -+ -+ @Deprecated -+ public BotAction getBotAction() { -+ return null; + } + + @Override -+ public @NotNull ServerStatsCounter getStats() { -+ return stats; -+ } -+ -+ public BotInventoryContainer getContainer() { -+ return container; ++ public void setRot(float yaw, float pitch) { ++ this.getBukkitEntity().setRotation(yaw, pitch); + } + + @Override -+ public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { -+ if (LeavesConfig.openFakeplayerInventory) { -+ if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { -+ BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); -+ server.server.getPluginManager().callEvent(event); -+ if (!event.isCancelled()) { -+ Component menuName = this.getDisplayName(); -+ player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, container), menuName != null ? menuName : Component.literal(this.createState.name))); -+ return InteractionResult.SUCCESS; -+ } -+ } -+ } -+ return super.interact(player, hand); -+ } -+ -+ public static ServerBot getBot(ServerPlayer player) { -+ ServerBot bot = null; -+ for (ServerBot b : bots) { -+ if (b.getId() == player.getId()) { -+ bot = b; -+ break; -+ } -+ } -+ return bot; -+ } -+ -+ public static ServerBot getBot(String name) { -+ ServerBot bot = null; -+ for (ServerBot b : bots) { -+ if (b.getName().getString().equals(name)) { -+ bot = b; -+ break; -+ } -+ } -+ return bot; -+ } -+ -+ public static ServerBot getBot(UUID uuid) { -+ ServerBot bot = null; -+ for (ServerBot b : bots) { -+ if (b.uuid == uuid) { -+ bot = b; -+ break; -+ } -+ } -+ return bot; ++ public void attack(@NotNull Entity target) { ++ super.attack(target); ++ this.swing(InteractionHand.MAIN_HAND); + } + -+ public static void saveOrRemoveAllBot() { -+ if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) { -+ JsonObject fakePlayerList = new JsonObject(); -+ bots.forEach(bot -> fakePlayerList.add(bot.createState.realName, BotUtil.saveBot(bot))); -+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile(); -+ if (!file.isFile()) { -+ try { -+ if (!file.createNewFile()) { -+ throw new IOException("Failed to create fakeplayer file: " + file); -+ } -+ } catch (IOException e) { -+ LeavesLogger.LOGGER.severe("Failed to save fakeplayer", e); -+ return; -+ } -+ } -+ try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) { -+ bfw.write(new Gson().toJson(fakePlayerList)); -+ } catch (IOException e) { -+ LeavesLogger.LOGGER.severe("Failed to save fakeplayer", e); ++ @Override ++ public void addAdditionalSaveData(@NotNull CompoundTag nbt) { ++ super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("isShiftKeyDown", this.isShiftKeyDown()); ++ ++ CompoundTag createNbt = new CompoundTag(); ++ createNbt.putString("realName", this.createState.realName()); ++ createNbt.putString("name", this.createState.name()); ++ ++ createNbt.putString("skinName", this.createState.skinName()); ++ if (this.createState.skin() != null) { ++ ListTag skin = new ListTag(); ++ for (String s : this.createState.skin()) { ++ skin.add(StringTag.valueOf(s)); + } -+ } else { -+ removeAllBot(BotRemoveEvent.RemoveReason.INTERNAL); ++ createNbt.put("skin", skin); + } -+ } + -+ public static void loadAllBot() { -+ if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) { -+ JsonObject fakePlayerList = new JsonObject(); -+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile(); -+ if (!file.isFile()) { -+ return; -+ } -+ try (BufferedReader bfr = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { -+ fakePlayerList = new Gson().fromJson(bfr, JsonObject.class); -+ } catch (IOException e) { -+ LeavesLogger.LOGGER.severe("Failed to load fakeplayer", e); -+ } -+ for (Map.Entry entry : fakePlayerList.entrySet()) { -+ BotUtil.loadBot(entry); ++ nbt.put("createStatus", createNbt); ++ ++ if (!this.actions.isEmpty()) { ++ ListTag actionNbt = new ListTag(); ++ for (BotAction action : this.actions) { ++ actionNbt.add(action.save(new CompoundTag())); + } -+ if (!file.delete()) { -+ LeavesLogger.LOGGER.warning("Failed to delete " + file); ++ nbt.put("actions", actionNbt); ++ } ++ ++ if (!this.configs.isEmpty()) { ++ ListTag configNbt = new ListTag(); ++ for (BotConfig config : this.configs.values()) { ++ configNbt.add(config.save(new CompoundTag())); + } ++ nbt.put("configs", configNbt); + } + } + -+ public static void removeAllBot(BotRemoveEvent.RemoveReason reason) { -+ Iterator iterator = bots.iterator(); -+ while (iterator.hasNext()) { -+ ServerBot bot = iterator.next(); -+ bot.onRemove(reason); ++ @Override ++ public void readAdditionalSaveData(@NotNull CompoundTag nbt) { ++ super.readAdditionalSaveData(nbt); ++ this.setShiftKeyDown(nbt.getBoolean("isShiftKeyDown")); ++ ++ CompoundTag createNbt = nbt.getCompound("createStatus"); ++ BotCreateState.Builder createBuilder = BotCreateState.builder(createNbt.getString("realName"), null).name(createNbt.getString("name")); ++ ++ String[] skin = null; ++ if (createNbt.contains("skin")) { ++ ListTag skinTag = createNbt.getList("skin", 8); ++ skin = new String[skinTag.size()]; ++ for (int i = 0; i < skinTag.size(); i++) { ++ skin[i] = skinTag.getString(i); ++ } + } -+ } + -+ public static List getBots() { -+ return bots; -+ } ++ createBuilder.skinName(createNbt.getString("skinName")).skin(skin); ++ createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null); + -+ public static class CustomGameProfile extends GameProfile { ++ this.createState = createBuilder.build(); ++ this.gameProfile = new BotList.CustomGameProfile(this.getUUID(), this.createState.name(), this.createState.skin()); + -+ public CustomGameProfile(UUID uuid, String name, String[] skin) { -+ super(uuid, name); -+ setSkin(skin); ++ ++ if (nbt.contains("actions")) { ++ ListTag actionNbt = nbt.getList("actions", 10); ++ for (int i = 0; i < actionNbt.size(); i++) { ++ CompoundTag actionTag = actionNbt.getCompound(i); ++ BotAction action = Actions.getForName(actionTag.getString("actionName")); ++ if (action != null) { ++ BotAction newAction = action.create(); ++ newAction.load(actionTag); ++ this.actions.add(newAction); ++ } ++ } + } + -+ public void setSkin(String[] skin) { -+ if (skin != null) { -+ getProperties().put("textures", new Property("textures", skin[0], skin[1])); ++ if (nbt.contains("configs")) { ++ ListTag configNbt = nbt.getList("configs", 10); ++ for (int i = 0; i < configNbt.size(); i++) { ++ CompoundTag configTag = configNbt.getCompound(i); ++ Configs configKey = Configs.getConfig(configTag.getString("configName")); ++ if (configKey != null) { ++ this.configs.get(configKey).load(configTag); ++ } + } + } + } + -+ public static class BotCreateState { -+ -+ private final String realName; -+ private final String name; -+ -+ public Location loc; ++ public void faceLocation(@NotNull Location loc) { ++ this.look(loc.toVector().subtract(getLocation().toVector()), false); ++ } + -+ public String[] skin; -+ public String skinName; ++ public void look(Vector dir, boolean keepYaw) { ++ float yaw, pitch; + -+ public BotCreateEvent.CreateReason createReason; -+ public CommandSender creator; ++ if (keepYaw) { ++ yaw = this.getYHeadRot(); ++ pitch = MathUtils.fetchPitch(dir); ++ } else { ++ float[] vals = MathUtils.fetchYawPitch(dir); ++ yaw = vals[0]; ++ pitch = vals[1]; + -+ public BotCreateState(Location loc, String realName, String skinName, BotCreateEvent.CreateReason createReason, CommandSender creator) { -+ this(loc, LeavesConfig.fakeplayerPrefix + realName + LeavesConfig.fakeplayerSuffix, realName, skinName, null, createReason, creator); ++ this.sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f))); + } + -+ public BotCreateState(Location loc, String name, String realName, String skinName, String[] skin, BotCreateEvent.CreateReason createReason, CommandSender creator) { -+ this.loc = loc; -+ this.skinName = skinName; -+ this.skin = skin; -+ this.realName = realName; -+ this.name = name; -+ this.createReason = createReason; -+ this.creator = creator; -+ } ++ this.setRot(yaw, pitch); ++ } + -+ @Nullable -+ public Bot createNow(Consumer consumer) { -+ ServerBot bot = createBot(this); -+ if (bot != null && consumer != null) { -+ consumer.accept(bot); ++ public Location getLocation() { ++ return this.getBukkitEntity().getLocation(); ++ } ++ ++ public Entity getTargetEntity(int maxDistance, Predicate predicate) { ++ List entities = this.level().getEntities((Entity) null, this.getBoundingBox(), (e -> e != this && (predicate == null || predicate.test(e)))); ++ if (!entities.isEmpty()) { ++ return entities.getFirst(); ++ } else { ++ EntityHitResult result = this.getBukkitEntity().rayTraceEntity(maxDistance, false); ++ if (result != null && (predicate == null || predicate.test(result.getEntity()))) { ++ return result.getEntity(); + } -+ return bot != null ? bot.getBukkitEntity() : null; + } ++ return null; ++ } + -+ public void create(Consumer consumer) { -+ Bukkit.getScheduler().runTaskAsynchronously(CraftScheduler.MINECRAFT, () -> { -+ if (skin == null && skinName != null) { -+ this.skin = MojangAPI.getSkin(skinName); -+ } ++ public void dropAll() { ++ this.getInventory().dropAll(); ++ this.detectEquipmentUpdatesPublic(); ++ } + -+ Bukkit.getScheduler().runTask(CraftScheduler.MINECRAFT, () -> { -+ ServerBot bot = createBot(this); -+ if (bot != null && consumer != null) { -+ consumer.accept(bot); -+ } -+ }); -+ }); ++ private void runAction() { ++ if (LeavesConfig.fakeplayerUseAction) { ++ this.actions.forEach(action -> action.tryTick(this)); ++ this.actions.removeIf(BotAction::isCancelled); + } ++ } + -+ public String getName() { -+ return name; ++ public boolean addBotAction(BotAction action) { ++ if (!LeavesConfig.fakeplayerUseAction) { ++ return false; + } + -+ public String getRealName() { -+ return realName; ++ if (!new BotActionScheduleEvent(this.getBukkitEntity(), action.getName(), action.getUUID()).callEvent()) { ++ return false; + } ++ ++ action.init(); ++ this.actions.add(action); ++ return true; ++ } ++ ++ public List> getBotActions() { ++ return actions; ++ } ++ ++ @Override ++ public @NotNull ServerStatsCounter getStats() { ++ return stats; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public BotConfig getConfig(Configs config) { ++ return (BotConfig) Objects.requireNonNull(this.configs.get(config)); ++ } ++ ++ public E getConfigValue(Configs config) { ++ return this.getConfig(config).getValue(); ++ } ++ ++ @Override ++ @NotNull ++ public CraftBot getBukkitEntity() { ++ return (CraftBot) super.getBukkitEntity(); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java new file mode 100644 -index 0000000000000000000000000000000000000000..d626ac47af400d01993c358fa5a93671dce9abe9 +index 0000000000000000000000000000000000000000..bc1e29f6080c4783940848456620be8c06c32cce --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java -@@ -0,0 +1,139 @@ +@@ -0,0 +1,138 @@ +package org.leavesmc.leaves.bot; + +import net.kyori.adventure.text.Component; -+import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; @@ -2486,16 +2824,108 @@ index 0000000000000000000000000000000000000000..d626ac47af400d01993c358fa5a93671 + public void setLevel(@NotNull ServerLevel world) { + } +} +diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java b/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c62f9258e4114ff686642b7f472d0e14151f37d5 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java +@@ -0,0 +1,85 @@ ++package org.leavesmc.leaves.bot; ++ ++import net.minecraft.network.Connection; ++import net.minecraft.network.DisconnectionDetails; ++import net.minecraft.network.PacketSendListener; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.PacketFlow; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.network.CommonListenerCookie; ++import net.minecraft.server.network.ServerGamePacketListenerImpl; ++import org.bukkit.event.player.PlayerKickEvent; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class ServerBotPacketListenerImpl extends ServerGamePacketListenerImpl { ++ ++ public ServerBotPacketListenerImpl(MinecraftServer server, ServerBot bot) { ++ super(server, BotConnection.INSTANCE, bot, CommonListenerCookie.createInitial(bot.gameProfile, false)); ++ } ++ ++ @Override ++ public void sendPacket(@NotNull Packet packet) { ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet) { ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks) { ++ } ++ ++ @Override ++ public void disconnect(@NotNull DisconnectionDetails disconnectionInfo, PlayerKickEvent.@NotNull Cause cause) { ++ } ++ ++ @Override ++ public boolean isAcceptingMessages() { ++ return true; ++ } ++ ++ @Override ++ public void tick() { ++ } ++ ++ public static class BotConnection extends Connection { ++ ++ private static final BotConnection INSTANCE = new BotConnection(); ++ ++ public BotConnection() { ++ super(PacketFlow.SERVERBOUND); ++ } ++ ++ @Override ++ public void tick() { ++ } ++ ++ @Override ++ public boolean isConnected() { ++ return true; ++ } ++ ++ @Override ++ public boolean isConnecting() { ++ return false; ++ } ++ ++ @Override ++ public boolean isMemoryConnection() { ++ return false; ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet) { ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet, @Nullable PacketSendListener packetsendlistener) { ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { ++ } ++ } ++} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java new file mode 100644 -index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec897787158a7d534 +index 0000000000000000000000000000000000000000..a37513e1ba8443c702ab0c01fbe5e052e5f0f2ab --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java -@@ -0,0 +1,66 @@ +@@ -0,0 +1,67 @@ +package org.leavesmc.leaves.bot.agent; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.actions.*; + +import java.util.Collection; @@ -2505,7 +2935,7 @@ index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec8977871 + +public class Actions { + -+ private static final Map actions = new HashMap<>(); ++ private static final Map> actions = new HashMap<>(); + + public static void registerAll() { + register(new AttackAction()); @@ -2514,7 +2944,6 @@ index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec8977871 + register(new JumpAction()); + register(new RotateAction()); + register(new SneakAction()); -+ register(new StopAction()); + register(new UseItemAction()); + register(new UseItemOnAction()); + register(new UseItemToAction()); @@ -2527,7 +2956,7 @@ index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec8977871 + register(new RotationAction()); + } + -+ public static boolean register(@NotNull BotAction action) { ++ public static boolean register(@NotNull BotAction action) { + if (!actions.containsKey(action.getName())) { + actions.put(action.getName(), action); + return true; @@ -2545,7 +2974,7 @@ index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec8977871 + + @NotNull + @Contract(pure = true) -+ public static Collection getAll() { ++ public static Collection> getAll() { + return actions.values(); + } + @@ -2554,139 +2983,345 @@ index 0000000000000000000000000000000000000000..15cbf363f587a27d55f4bc7ec8977871 + return actions.keySet(); + } + -+ public static BotAction getForName(String name) { ++ @Nullable ++ public static BotAction getForName(String name) { + return actions.get(name); + } +} -diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java +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..3ad5484f26e3dc7fb45c5d2ee0687604e6974404 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java +@@ -0,0 +1,163 @@ ++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; ++import java.util.function.Supplier; ++ ++public abstract class BotAction> { ++ ++ private final String name; ++ private final CommandArgument argument; ++ private final Supplier creator; ++ ++ private boolean cancel; ++ private int tickDelay; ++ private int number; ++ private UUID uuid; ++ ++ private int needWaitTick; ++ private int canDoNumber; ++ ++ public BotAction(String name, CommandArgument argument, Supplier creator) { ++ this.name = name; ++ this.argument = argument; ++ this.uuid = UUID.randomUUID(); ++ this.creator = creator; ++ ++ this.cancel = false; ++ this.tickDelay = 20; ++ this.number = -1; ++ } ++ ++ public void init() { ++ this.needWaitTick = 0; ++ this.canDoNumber = this.getNumber(); ++ this.setCancelled(false); ++ } ++ ++ public String getName() { ++ return this.name; ++ } ++ ++ public UUID getUUID() { ++ return uuid; ++ } ++ ++ public int getTickDelay() { ++ return this.tickDelay; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public E setTickDelay(int tickDelay) { ++ this.tickDelay = Math.max(0, tickDelay); ++ return (E) this; ++ } ++ ++ public int getNumber() { ++ return this.number; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public E setNumber(int number) { ++ this.number = Math.max(-1, number); ++ return (E) this; ++ } ++ ++ public int getCanDoNumber() { ++ return this.canDoNumber; ++ } ++ ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { ++ new BotActionStopEvent(bot.getBukkitEntity(), this.name, this.uuid, reason).callEvent(); ++ this.setCancelled(true); ++ } ++ ++ public CommandArgument getArgument() { ++ return this.argument; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public E setTabComplete(int index, List list) { ++ this.argument.setTabComplete(index, list); ++ return (E) this; ++ } ++ ++ public void tryTick(ServerBot bot) { ++ if (this.canDoNumber == 0) { ++ this.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; ++ } else 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--; ++ } ++ this.needWaitTick = this.getTickDelay(); ++ } ++ } else { ++ this.needWaitTick--; ++ } ++ } ++ ++ @NotNull ++ public E create() { ++ return this.creator.get(); ++ } ++ ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ if (!this.cancel) { ++ nbt.putString("actionName", this.name); ++ nbt.putUUID("actionUUID", this.uuid); ++ ++ nbt.putInt("canDoNumber", this.canDoNumber); ++ nbt.putInt("needWaitTick", this.needWaitTick); ++ nbt.putInt("tickDelay", this.tickDelay); ++ } ++ return nbt; ++ } ++ ++ public void load(@NotNull CompoundTag nbt) { ++ this.tickDelay = nbt.getInt("tickDelay"); ++ this.needWaitTick = nbt.getInt("needWaitTick"); ++ this.canDoNumber = nbt.getInt("canDoNumber"); ++ this.uuid = nbt.getUUID("actionUUID"); ++ } ++ ++ public abstract void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result); ++ ++ public abstract boolean doTick(@NotNull ServerBot bot); ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..9abcb8852ac031abaa991881a7cd6b33bc523b26 +index 0000000000000000000000000000000000000000..c889a2409d8b9f5979a10b61c98638054bd8f5bc --- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java -@@ -0,0 +1,93 @@ ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java +@@ -0,0 +1,62 @@ +package org.leavesmc.leaves.bot.agent; + -+import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +import java.util.List; ++import java.util.function.Supplier; + -+public abstract class BotAction { ++public abstract class BotConfig { + + private final String name; + private final CommandArgument argument; ++ private final Supplier> creator; ++ protected ServerBot bot; + -+ private boolean cancel; -+ private int tickDelay; -+ private int number; -+ -+ private int needWaitTick; -+ private int canDoNumber; -+ -+ public BotAction(String name, CommandArgument argument) { ++ public BotConfig(String name, CommandArgument argument, Supplier> creator) { + this.name = name; + this.argument = argument; -+ -+ this.cancel = false; -+ this.tickDelay = 20; -+ this.number = -1; ++ this.creator = creator; + } + -+ public String getName() { -+ return name; ++ public BotConfig setBot(ServerBot bot) { ++ this.bot = bot; ++ return this; + } + -+ public int getTickDelay() { -+ return tickDelay; -+ } ++ public abstract E getValue(); + -+ public int getNumber() { -+ return number; -+ } ++ public abstract void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException; + -+ public boolean isCancel() { -+ return cancel; ++ public List getMessage() { ++ return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue()); + } + -+ public BotAction setTickDelay(int tickDelay) { -+ this.tickDelay = Math.max(0, tickDelay); -+ return this; ++ public List getChangeMessage() { ++ return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue()); + } + -+ public BotAction setTabComplete(int index, List list) { -+ argument.setTabComplete(index, list); -+ return this; ++ public String getName() { ++ return name; + } + -+ public BotAction setNumber(int number) { -+ this.number = Math.max(-1, number); -+ return this; ++ public CommandArgument getArgument() { ++ return argument; + } + -+ public void setCancel(boolean cancel) { -+ this.cancel = cancel; ++ @NotNull ++ public BotConfig create(ServerBot bot) { ++ return this.creator.get().setBot(bot); + } + -+ public void init() { -+ this.needWaitTick = 0; -+ this.canDoNumber = this.getNumber(); -+ this.setCancel(false); ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ nbt.putString("configName", this.name); ++ return nbt; + } + -+ public void tryTick(ServerBot bot) { -+ if (canDoNumber == 0) { -+ this.setCancel(true); -+ return; -+ } -+ if (needWaitTick-- <= 0) { -+ if (this.doTick(bot)) { -+ canDoNumber--; -+ needWaitTick = this.getTickDelay(); -+ } -+ } ++ public abstract void load(@NotNull CompoundTag nbt); ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java b/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d99f459b2e323474174cfd5d892cb7573a32ca12 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java +@@ -0,0 +1,44 @@ ++package org.leavesmc.leaves.bot.agent; ++ ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.bot.agent.configs.*; ++ ++import java.util.Collection; ++import java.util.HashMap; ++import java.util.Map; ++ ++public class Configs { ++ ++ private static final Map> configs = new HashMap<>(); ++ ++ public static final Configs SKIP_SLEEP = register(new SkipSleepConfig()); ++ public static final Configs ALWAYS_SEND_DATA = register(new AlwaysSendDataConfig()); ++ public static final Configs SPAWN_PHANTOM = register(new SpawnPhantomConfig()); ++ public static final Configs SIMULATION_DISTANCE = register(new SimulationDistanceConfig()); ++ ++ public final BotConfig config; ++ ++ private Configs(BotConfig config) { ++ this.config = config; + } + -+ public CommandArgument getArgument() { -+ return argument; ++ @NotNull ++ @Contract(pure = true) ++ public static Collection> getConfigs() { ++ return configs.values(); + } + -+ public abstract BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result); ++ @Nullable ++ public static Configs getConfig(String name) { ++ return configs.get(name); ++ } + -+ public abstract boolean doTick(@NotNull ServerBot bot); ++ @NotNull ++ private static Configs register(BotConfig botConfig) { ++ Configs config = new Configs<>(botConfig); ++ configs.put(botConfig.getName(), config); ++ return config; ++ } +} -diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java +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..1366939121876902669b264f2ffa05c039cbc4af +index 0000000000000000000000000000000000000000..be55a3085a53542c08e7f0209883a4f3f72602e7 --- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java -@@ -0,0 +1,36 @@ ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java +@@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.bot.ServerBot; ++import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; ++import java.util.function.Supplier; + -+public class AttackAction extends BotAction { ++public abstract class AbstractTimerAction> extends BotAction { + -+ public AttackAction() { -+ super("attack", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); ++ public AbstractTimerAction(String name, Supplier creator) { ++ super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator); ++ this.setTabComplete(0, List.of("[TickDelay]")).setTabComplete(1, List.of("[DoNumber]")); + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new AttackAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ this.setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java +new file mode 100644 +index 0000000000000000000000000000000000000000..03e9baf9b7c2da0fd1d7d9b0058b70daddedeeaa +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java +@@ -0,0 +1,22 @@ ++package org.leavesmc.leaves.bot.agent.actions; ++ ++import net.minecraft.world.entity.Entity; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.bot.ServerBot; ++ ++public class AttackAction extends AbstractTimerAction { ++ ++ public AttackAction() { ++ super("attack", AttackAction::new); + } + + @Override @@ -2701,61 +3336,32 @@ index 0000000000000000000000000000000000000000..1366939121876902669b264f2ffa05c0 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..adcce8f8c39b0a1e445f5552ce74436053b604e7 +index 0000000000000000000000000000000000000000..bf7d20374cd7bff7cb7e09d209c6da5d297fe1f1 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java -@@ -0,0 +1,105 @@ +@@ -0,0 +1,75 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; + -+import java.util.List; -+ -+public class BreakBlockAction extends BotAction { ++public class BreakBlockAction extends AbstractTimerAction { + + public BreakBlockAction() { -+ super("break", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new BreakBlockAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); -+ } -+ -+ @Override -+ public BotAction setTickDelay(int tickDelay) { -+ super.setTickDelay(0); -+ this.delay = tickDelay; -+ return this; ++ super("break", BreakBlockAction::new); + } + -+ private int delay = 0; -+ private int nowDelay = 0; -+ + private BlockPos lastPos = null; + private int destroyProgressTime = 0; + private int lastSentState = -1; + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ if (nowDelay > 0) { -+ nowDelay--; -+ return false; -+ } -+ + Block block = bot.getBukkitEntity().getTargetBlockExact(5); + if (block != null) { + BlockPos pos = ((CraftBlock) block).getPosition(); @@ -2795,7 +3401,6 @@ index 0000000000000000000000000000000000000000..adcce8f8c39b0a1e445f5552ce744360 + lastPos = null; + destroyProgressTime = 0; + lastSentState = -1; -+ nowDelay = delay; + } + + private float incrementDestroyProgress(ServerBot bot, @NotNull BlockState state, BlockPos pos) { @@ -2810,41 +3415,102 @@ index 0000000000000000000000000000000000000000..adcce8f8c39b0a1e445f5552ce744360 + 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..d96fc7b97ff826efe1bd36988f2d1a9ea04654cb +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java +@@ -0,0 +1,54 @@ ++package org.leavesmc.leaves.bot.agent.actions; ++ ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++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(@NotNull BotAction action) { ++ super(BotActionType.valueOf(action.getName()), action.getTickDelay(), action.getCanDoNumber()); ++ this.handle = action; ++ } ++ ++ @Contract("_ -> new") ++ @NotNull ++ public static LeavesBotAction asAPICopy(BotAction action) { ++ return new CraftBotAction(action); ++ } ++ ++ @NotNull ++ public static BotAction asInternalCopy(@NotNull 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!"); // TODO look 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..f9b48f38a9b530125849f30eeab497895da77a21 +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; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.botaction.CustomBotAction; + -+public class CraftCustomBotAction extends BotAction { ++public class CraftCustomBotAction extends BotAction { + + private final CustomBotAction realAction; + + public CraftCustomBotAction(String name, @NotNull CustomBotAction realAction) { -+ super(name, new CommandArgument().setAllTabComplete(realAction.getTabComplete())); ++ super(name, CommandArgument.of().setAllTabComplete(realAction.getTabComplete()), null); + this.realAction = realAction; + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + throw new UnsupportedOperationException("Not supported."); + } + -+ public BotAction getNew(@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(getName(), newRealAction); ++ return new CraftCustomBotAction(this.getName(), newRealAction); + } + return null; + } @@ -2861,33 +3527,32 @@ index 0000000000000000000000000000000000000000..f9b48f38a9b530125849f30eeab49789 + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ return realAction.doTick(bot.getBukkitPlayer()); ++ return realAction.doTick(bot.getBukkitEntity()); + } +} 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..c534e5f5a51021a5f08bae2e4bce55fcedf93cf0 +index 0000000000000000000000000000000000000000..c71e483e8938ef3b181c95d8e297e54203b5b914 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java -@@ -0,0 +1,26 @@ +@@ -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.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + -+public class DropAction extends BotAction { ++public class DropAction extends AbstractTimerAction { + + public DropAction() { -+ super("drop", new CommandArgument()); ++ super("drop", DropAction::new); + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return this.setTickDelay(0).setNumber(1); ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ this.setTickDelay(result.readInt(100)).setNumber(result.readInt(1)); + } + + @Override @@ -2898,53 +3563,56 @@ index 0000000000000000000000000000000000000000..c534e5f5a51021a5f08bae2e4bce55fc +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..18b2f81f9edcfc2f30dde82c832a899b84c8cd3a +index 0000000000000000000000000000000000000000..3a13f8ac73e042d939496fb5602e4aa4ea368e0d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java -@@ -0,0 +1,70 @@ +@@ -0,0 +1,73 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; + -+import java.util.List; -+ -+public class FishAction extends BotAction { ++public class FishAction extends AbstractTimerAction { + + public FishAction() { -+ super("fish", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); ++ super("fish", FishAction::new); + } + -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new FishAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); -+ } ++ private int delay = 0; ++ private int nowDelay = 0; + + @Override -+ public BotAction setTickDelay(int tickDelay) { ++ public FishAction setTickDelay(int tickDelay) { + super.setTickDelay(0); + this.delay = tickDelay; + return this; + } + -+ private int delay = 0; -+ private int nowDelay = 0; ++ @Override ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putInt("fishDelay", this.delay); ++ nbt.putInt("fishNowDelay", this.nowDelay); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ super.load(nbt); ++ this.delay = nbt.getInt("fishDelay"); ++ this.nowDelay = nbt.getInt("fishNowDelay"); ++ } + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ if (nowDelay > 0) { -+ nowDelay--; ++ if (this.nowDelay > 0) { ++ this.nowDelay--; + return false; + } + @@ -2957,12 +3625,12 @@ index 0000000000000000000000000000000000000000..18b2f81f9edcfc2f30dde82c832a899b + if (fishingHook != null) { + if (fishingHook.currentState == FishingHook.FishHookState.HOOKED_IN_ENTITY) { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); -+ nowDelay = 20; ++ this.nowDelay = 20; + return false; + } + if (fishingHook.nibble > 0) { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); -+ nowDelay = delay; ++ this.nowDelay = this.delay; + return true; + } + } else { @@ -2974,33 +3642,19 @@ index 0000000000000000000000000000000000000000..18b2f81f9edcfc2f30dde82c832a899b +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..de4c2aaf8f2f212e346ed41a514c803aa4fe7ac6 +index 0000000000000000000000000000000000000000..6fc9ba9bf94cb19ed32cfafa3a44fad0201b14a6 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java -@@ -0,0 +1,35 @@ +@@ -0,0 +1,21 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; -+ -+import java.util.List; + -+public class JumpAction extends BotAction { ++public class JumpAction extends AbstractTimerAction { + + public JumpAction() { -+ super("jump", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new JumpAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("jump", JumpAction::new); + } + + @Override @@ -3015,15 +3669,17 @@ index 0000000000000000000000000000000000000000..de4c2aaf8f2f212e346ed41a514c803a +} 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..bd8a62fb4385a00a7f532835f7d75e47ae45405b +index 0000000000000000000000000000000000000000..8be962cf7dc273ccb6a6754684a9be8353865225 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java -@@ -0,0 +1,49 @@ +@@ -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; @@ -3032,34 +3688,46 @@ index 0000000000000000000000000000000000000000..bd8a62fb4385a00a7f532835f7d75e47 + +import java.util.List; + -+public class LookAction extends BotAction { ++public class LookAction extends BotAction { + + public LookAction() { -+ super("look", new CommandArgument(CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE)); -+ setTabComplete(0, List.of("")); -+ setTabComplete(1, List.of("")); -+ setTabComplete(2, List.of("")); ++ super("look", CommandArgument.of(CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), LookAction::new); ++ this.setTabComplete(0, List.of("")); ++ this.setTabComplete(1, List.of("")); ++ this.setTabComplete(2, List.of("")); + } + ++ private Vector pos; ++ + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) throws IllegalArgumentException { + Vector pos = result.readVector(); + if (pos != null) { -+ return new LookAction().setPos(pos).setTickDelay(0).setNumber(1); ++ this.setPos(pos).setTickDelay(0).setNumber(1); + } else { -+ return null; ++ throw new IllegalArgumentException("pos?"); + } + } + -+ private Vector pos; ++ @Override ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putDouble("x", this.pos.getX()); ++ nbt.putDouble("y", this.pos.getY()); ++ nbt.putDouble("z", this.pos.getZ()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ super.load(nbt); ++ this.setPos(new Vector(nbt.getDouble("x"), nbt.getDouble("y"), nbt.getDouble("z"))); ++ } + + public LookAction setPos(Vector pos) { -+ if (pos != null) { -+ this.pos = pos; -+ return this; -+ } else { -+ return null; -+ } ++ this.pos = pos; ++ return this; + } + + @Override @@ -3070,38 +3738,56 @@ index 0000000000000000000000000000000000000000..bd8a62fb4385a00a7f532835f7d75e47 +} 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..fe872cad0cbf9ede825e0561cdb4ecb24df32821 +index 0000000000000000000000000000000000000000..84eb7bd727a0085d005a6ee518dfbb8b44fce991 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java -@@ -0,0 +1,33 @@ +@@ -0,0 +1,51 @@ +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; +import org.leavesmc.leaves.command.CommandArgumentResult; + -+public class RotateAction extends BotAction { ++public class RotateAction extends BotAction { + + public RotateAction() { -+ super("rotate", new CommandArgument()); ++ super("rotate", CommandArgument.EMPTY, RotateAction::new); + } + ++ private ServerPlayer player; ++ + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new RotateAction().setPlayer(player).setTickDelay(0).setNumber(1); ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ this.setPlayer(player).setTickDelay(0).setNumber(1); + } + -+ private ServerPlayer player; -+ + public RotateAction setPlayer(ServerPlayer player) { + this.player = player; + return this; + } + + @Override ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putString("actionName", "look"); // to player loc ++ nbt.putDouble("x", player.getX()); ++ nbt.putDouble("y", player.getY()); ++ nbt.putDouble("z", player.getZ()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.faceLocation(player.getBukkitEntity().getLocation()); + return true; @@ -3109,14 +3795,16 @@ index 0000000000000000000000000000000000000000..fe872cad0cbf9ede825e0561cdb4ecb2 +} 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..6983bf8555058cb715328f761df80d0b89c0b8f0 +index 0000000000000000000000000000000000000000..671d1aada7aa3cac0f3df8eec235b2f1ae389492 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java -@@ -0,0 +1,44 @@ +@@ -0,0 +1,65 @@ +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; @@ -3125,22 +3813,26 @@ index 0000000000000000000000000000000000000000..6983bf8555058cb715328f761df80d0b + +import java.util.List; + -+public class RotationAction extends BotAction { ++public class RotationAction extends BotAction { + + public RotationAction() { -+ super("rotation", new CommandArgument(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT)); -+ setTabComplete(0, List.of("")); -+ setTabComplete(1, List.of("")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new RotationAction().setYaw(result.readFloat(player.getYRot())).setPitch(result.readFloat(player.getXRot())).setTickDelay(0).setNumber(1); ++ super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), RotateAction::new); ++ this.setTabComplete(0, List.of("")); ++ this.setTabComplete(1, List.of("")); + } + + private float yaw; + private float pitch; + ++ @Override ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ if (player == null) { ++ return; ++ } ++ ++ this.setYaw(result.readFloat(player.getYRot())).setPitch(result.readFloat(player.getXRot())).setTickDelay(0).setNumber(1); ++ } ++ + public RotationAction setYaw(float yaw) { + this.yaw = yaw; + return this; @@ -3152,143 +3844,118 @@ index 0000000000000000000000000000000000000000..6983bf8555058cb715328f761df80d0b + } + + @Override -+ public boolean doTick(@NotNull ServerBot bot) { -+ bot.setRot(yaw, pitch); -+ return true; -+ } -+} -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..9b4406bc0b418abc6a253e047c504b4ad15f059a ---- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java -@@ -0,0 +1,27 @@ -+package org.leavesmc.leaves.bot.agent.actions; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Pose; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+ -+public class SneakAction extends BotAction { -+ -+ public SneakAction() { -+ super("sneak", new CommandArgument()); ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putFloat("yaw", this.yaw); ++ nbt.putFloat("pitch", this.pitch); ++ return nbt; + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return this.setTickDelay(0).setNumber(1); ++ public void load(@NotNull CompoundTag nbt) { ++ super.load(nbt); ++ this.setYaw(nbt.getFloat("yaw")).setPitch(nbt.getFloat("pitch")); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ bot.setShiftKeyDown(!bot.isShiftKeyDown()); ++ bot.setRot(yaw, pitch); + return true; + } +} -diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/StopAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/StopAction.java +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..bd02fe0e6b1f1b6048d56ab9c9379808dcf227c4 +index 0000000000000000000000000000000000000000..923cf55d81fce5cf9db9a1c7adc6f3aed5753b16 --- /dev/null -+++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/StopAction.java -@@ -0,0 +1,26 @@ ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java +@@ -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; +import org.leavesmc.leaves.command.CommandArgumentResult; + -+public class StopAction extends BotAction { ++public class SneakAction extends BotAction { + -+ public StopAction() { -+ super("stop", new CommandArgument()); ++ public SneakAction() { ++ super("sneak", CommandArgument.EMPTY, SneakAction::new); + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return this.setTickDelay(0).setNumber(0); ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ this.setTickDelay(0).setNumber(1); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ this.setCancel(true); ++ bot.setShiftKeyDown(!bot.isShiftKeyDown()); + return true; + } +} 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..c91ca987eb5922b8dbcd271deb33f80be5e1a9af +index 0000000000000000000000000000000000000000..b5ccedee17857bc955301512ee965d81fd12017f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java -@@ -0,0 +1,26 @@ +@@ -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; +import org.leavesmc.leaves.command.CommandArgumentResult; + -+public class SwimAction extends BotAction { ++public class SwimAction extends BotAction { + + public SwimAction() { -+ super("swim", new CommandArgument()); ++ super("swim", CommandArgument.EMPTY, SwimAction::new); + } + + @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return this.setTickDelay(0).setNumber(1); ++ public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { ++ this.setTickDelay(0).setNumber(-1); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { -+ bot.waterSwim = !bot.waterSwim; ++ if (bot.isInWater()) { ++ bot.addDeltaMovement(new Vec3(0, 0.03, 0)); ++ } + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..cf56fbdd461ae7f3a00043dbe979421a1dd3b5a4 +index 0000000000000000000000000000000000000000..1bdde4f8dc5e379d45fac19ba11aa07c4a1b735c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java -@@ -0,0 +1,34 @@ +@@ -0,0 +1,22 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.core.component.DataComponents; -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; + -+import java.util.List; -+ -+public class UseItemAction extends BotAction { ++public class UseItemAction extends AbstractTimerAction { + + public UseItemAction() { -+ super("use", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use", UseItemAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { ++ if (bot.isUsingItem()) { ++ return false; ++ } + bot.swing(InteractionHand.MAIN_HAND); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + return bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.MAIN_HAND), InteractionHand.MAIN_HAND).consumesAction(); @@ -3296,38 +3963,27 @@ index 0000000000000000000000000000000000000000..cf56fbdd461ae7f3a00043dbe979421a +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..475bf440d40ac35720b01da18440cf0d35166a5c +index 0000000000000000000000000000000000000000..f6de022b7177da0eb7c089f11ce039ab22c34903 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java -@@ -0,0 +1,33 @@ +@@ -0,0 +1,22 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; + -+import java.util.List; -+ -+public class UseItemOffHandAction extends BotAction { ++public class UseItemOffHandAction extends AbstractTimerAction { + + public UseItemOffHandAction() { -+ super("use_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemOffHandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use_offhand", UseItemOffHandAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { ++ if (bot.isUsingItem()) { ++ return false; ++ } + bot.swing(InteractionHand.OFF_HAND); + bot.updateItemInHand(InteractionHand.OFF_HAND); + return bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.OFF_HAND), InteractionHand.OFF_HAND).consumesAction(); @@ -3335,13 +3991,12 @@ index 0000000000000000000000000000000000000000..475bf440d40ac35720b01da18440cf0d +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..fe8c4ba7aff74dc297bdf4e271a2d7f1229c357d +index 0000000000000000000000000000000000000000..f73cd841009117e4032f953a3e754305d97a68be --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java -@@ -0,0 +1,56 @@ +@@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.block.Blocks; @@ -3351,27 +4006,14 @@ index 0000000000000000000000000000000000000000..fe8c4ba7aff74dc297bdf4e271a2d7f1 +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.scheduler.CraftScheduler; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; -+ -+import java.util.List; ++import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + -+public class UseItemOnAction extends BotAction { ++public class UseItemOnAction extends AbstractTimerAction { + + public UseItemOnAction() { -+ super("use_on", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemOnAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use_on", UseItemOnAction::new); + } + + @Override @@ -3384,7 +4026,7 @@ index 0000000000000000000000000000000000000000..fe8c4ba7aff74dc297bdf4e271a2d7f1 + BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos()); + if (entity instanceof TrappedChestBlockEntity chestBlockEntity) { + chestBlockEntity.startOpen(bot); -+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> chestBlockEntity.stopOpen(bot), 1); ++ Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1); + return true; + } + } else { @@ -3397,13 +4039,12 @@ index 0000000000000000000000000000000000000000..fe8c4ba7aff74dc297bdf4e271a2d7f1 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..a69ede01e5e5df31a144f98e9871183793f5f377 +index 0000000000000000000000000000000000000000..c0cd258151f690ccbf3df1ffd640b83d8f36aa7d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java -@@ -0,0 +1,56 @@ +@@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.block.Blocks; @@ -3413,27 +4054,14 @@ index 0000000000000000000000000000000000000000..a69ede01e5e5df31a144f98e98711837 +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.scheduler.CraftScheduler; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; -+ -+import java.util.List; ++import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + -+public class UseItemOnOffhandAction extends BotAction { ++public class UseItemOnOffhandAction extends AbstractTimerAction { + + public UseItemOnOffhandAction() { -+ super("use_on_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemOnOffhandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use_on_offhand", UseItemOnOffhandAction::new); + } + + @Override @@ -3446,7 +4074,7 @@ index 0000000000000000000000000000000000000000..a69ede01e5e5df31a144f98e98711837 + BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos()); + if (entity instanceof TrappedChestBlockEntity chestBlockEntity) { + chestBlockEntity.startOpen(bot); -+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> chestBlockEntity.stopOpen(bot), 1); ++ Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1); + return true; + } + } else { @@ -3459,35 +4087,21 @@ index 0000000000000000000000000000000000000000..a69ede01e5e5df31a144f98e98711837 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..0dbe7fd2e63cd47f892e383d71fdbff1c0d8d2ab +index 0000000000000000000000000000000000000000..2048024b754cce85d2bf4f2cbcb800a1f4727495 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java -@@ -0,0 +1,38 @@ +@@ -0,0 +1,24 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; -+ -+import java.util.List; + -+public class UseItemToAction extends BotAction { ++public class UseItemToAction extends AbstractTimerAction { + + public UseItemToAction() { -+ super("use_to", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemToAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use_to", UseItemToAction::new); + } + + @Override @@ -3503,35 +4117,21 @@ index 0000000000000000000000000000000000000000..0dbe7fd2e63cd47f892e383d71fdbff1 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java new file mode 100644 -index 0000000000000000000000000000000000000000..0e3895073bcfb5944c7147395338d750137e1386 +index 0000000000000000000000000000000000000000..e42288a4f99f7de6655d49ee1a05d37b79652c22 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java -@@ -0,0 +1,38 @@ +@@ -0,0 +1,24 @@ +package org.leavesmc.leaves.bot.agent.actions; + -+import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.BotAction; -+import org.leavesmc.leaves.command.CommandArgument; -+import org.leavesmc.leaves.command.CommandArgumentResult; -+import org.leavesmc.leaves.command.CommandArgumentType; + -+import java.util.List; -+ -+public class UseItemToOffhandAction extends BotAction { ++public class UseItemToOffhandAction extends AbstractTimerAction { + + public UseItemToOffhandAction() { -+ super("use_to_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER)); -+ setTabComplete(0, List.of("[TickDelay]")); -+ setTabComplete(1, List.of("[DoNumber]")); -+ } -+ -+ @Override -+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) { -+ return new UseItemToOffhandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); ++ super("use_to_offhand", UseItemToOffhandAction::new); + } + + @Override @@ -3545,23 +4145,234 @@ index 0000000000000000000000000000000000000000..0e3895073bcfb5944c7147395338d750 + return false; + } +} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9a584603edbbe4ccd8a88c90ef3e9125480635f1 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java +@@ -0,0 +1,45 @@ ++package org.leavesmc.leaves.bot.agent.configs; ++ ++import net.minecraft.nbt.CompoundTag; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.LeavesConfig; ++ ++import org.leavesmc.leaves.bot.agent.BotConfig; ++import org.leavesmc.leaves.command.CommandArgument; ++import org.leavesmc.leaves.command.CommandArgumentResult; ++import org.leavesmc.leaves.command.CommandArgumentType; ++ ++import java.util.List; ++ ++public class AlwaysSendDataConfig extends BotConfig { ++ ++ private boolean value; ++ ++ public AlwaysSendDataConfig() { ++ super("always_send_data", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), AlwaysSendDataConfig::new); ++ this.value = LeavesConfig.alwaysSendFakeplayerData; ++ } ++ ++ @Override ++ public Boolean getValue() { ++ return value; ++ } ++ ++ @Override ++ public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { ++ this.value = result.readBoolean(this.value); ++ } ++ ++ @Override ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putBoolean("always_send_data", this.getValue()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ this.value = nbt.getBoolean("always_send_data"); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c8a2243361cd03e9c64b6a04b37725b549e5b87f +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java +@@ -0,0 +1,47 @@ ++package org.leavesmc.leaves.bot.agent.configs; ++ ++import net.minecraft.nbt.CompoundTag; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.bot.agent.BotConfig; ++import org.leavesmc.leaves.command.CommandArgument; ++import org.leavesmc.leaves.command.CommandArgumentResult; ++import org.leavesmc.leaves.command.CommandArgumentType; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class SimulationDistanceConfig extends BotConfig { ++ ++ public SimulationDistanceConfig() { ++ super("simulation_distance", CommandArgument.of(CommandArgumentType.INTEGER).setTabComplete(0, List.of("2", "10", "")), SimulationDistanceConfig::new); ++ } ++ ++ @Override ++ public Integer getValue() { ++ return this.bot.getBukkitEntity().getSimulationDistance(); ++ } ++ ++ @Override ++ public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { ++ int realValue = result.readInt(this.bot.getBukkitEntity().getSimulationDistance()); ++ if (realValue < 2 || realValue > 32) { ++ throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + result); ++ } ++ this.bot.getBukkitEntity().setSimulationDistance(realValue); ++ } ++ ++ @Override ++ @NotNull ++ public CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putInt("simulation_distance", this.getValue()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ this.setValue(new CommandArgumentResult(new ArrayList<>(){{ ++ add(nbt.getInt("simulation_distance")); ++ }})); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d934910cff745ea9a53d651e20079635ea6781c +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java +@@ -0,0 +1,42 @@ ++package org.leavesmc.leaves.bot.agent.configs; ++ ++import net.minecraft.nbt.CompoundTag; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.bot.agent.BotConfig; ++import org.leavesmc.leaves.command.CommandArgument; ++import org.leavesmc.leaves.command.CommandArgumentResult; ++import org.leavesmc.leaves.command.CommandArgumentType; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class SkipSleepConfig extends BotConfig { ++ ++ public SkipSleepConfig() { ++ super("skip_sleep", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), SkipSleepConfig::new); ++ } ++ ++ @Override ++ public Boolean getValue() { ++ return bot.fauxSleeping; ++ } ++ ++ @Override ++ public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { ++ bot.fauxSleeping = result.readBoolean(bot.fauxSleeping); ++ } ++ ++ @Override ++ public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putBoolean("skip_sleep", this.getValue()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ this.setValue(new CommandArgumentResult(new ArrayList<>() {{ ++ add(nbt.getBoolean("skip_sleep")); ++ }})); ++ } ++} +diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a3f978318a67c3c5e147a50eb2b6c01c3f549dc2 +--- /dev/null ++++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java +@@ -0,0 +1,51 @@ ++package org.leavesmc.leaves.bot.agent.configs; ++ ++import net.minecraft.nbt.CompoundTag; ++import org.jetbrains.annotations.NotNull; ++import org.leavesmc.leaves.LeavesConfig; ++import org.leavesmc.leaves.bot.agent.BotConfig; ++import org.leavesmc.leaves.command.CommandArgument; ++import org.leavesmc.leaves.command.CommandArgumentResult; ++import org.leavesmc.leaves.command.CommandArgumentType; ++ ++import java.util.List; ++ ++public class SpawnPhantomConfig extends BotConfig { ++ ++ private boolean value; ++ ++ public SpawnPhantomConfig() { ++ super("spawn_phantom", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), SpawnPhantomConfig::new); ++ this.value = LeavesConfig.fakeplayerSpawnPhantom; ++ } ++ ++ @Override ++ public Boolean getValue() { ++ return value; ++ } ++ ++ @Override ++ public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { ++ this.value = result.readBoolean(this.value); ++ } ++ ++ @Override ++ public List getMessage() { ++ return List.of( ++ bot.getScoreboardName() + "'s spawn_phantom: " + this.getValue(), ++ bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks ++ ); ++ } ++ ++ @Override ++ public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { ++ super.save(nbt); ++ nbt.putBoolean("spawn_phantom", this.getValue()); ++ return nbt; ++ } ++ ++ @Override ++ public void load(@NotNull CompoundTag nbt) { ++ this.value = nbt.getBoolean("spawn_phantom"); ++ } ++} 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..fe1df01906f15e130cf947bbecb5df4bddf98c7c +index 0000000000000000000000000000000000000000..744f5533859bfe81267b3fd5133597e80e0fdc73 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBot.java -@@ -0,0 +1,67 @@ +@@ -0,0 +1,84 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; -+import org.leavesmc.leaves.bot.agent.Actions; +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.UUID; + @@ -3573,35 +4384,50 @@ index 0000000000000000000000000000000000000000..fe1df01906f15e130cf947bbecb5df4b + + @Override + public String getSkinName() { -+ return getHandle().createState.skinName; ++ return this.getHandle().createState.skinName(); + } + + @Override + public @NotNull String getRealName() { -+ return getHandle().createState.getRealName(); ++ return this.getHandle().createState.realName(); + } + + @Override + public @Nullable UUID getCreatePlayerUUID() { -+ return getHandle().createPlayer; ++ return this.getHandle().createPlayer; + } + + @Override -+ public boolean setBotAction(@NotNull String action, @NotNull Player player, @NotNull String[] args) { -+ BotAction botAction = Actions.getForName(action); -+ if (botAction != null) { -+ BotAction newAction = botAction.getNew(((CraftPlayer) player).getHandle(), botAction.getArgument().parse(0, args)); -+ if (newAction != null) { -+ getHandle().setBotAction(newAction); -+ return true; -+ } ++ public void addAction(@NotNull LeavesBotAction action) { ++ this.getHandle().addBotAction(CraftBotAction.asInternalCopy(action)); ++ } ++ ++ @Override ++ public LeavesBotAction getAction(int index) { ++ return CraftBotAction.asAPICopy(this.getHandle().getBotActions().get(index)); ++ } ++ ++ @Override ++ public int getActionSize() { ++ return this.getHandle().getBotActions().size(); ++ } ++ ++ @Override ++ public void stopAction(int index) { ++ this.getHandle().getBotActions().get(index).stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); ++ } ++ ++ @Override ++ public void stopAllActions() { ++ for (BotAction action : this.getHandle().getBotActions()) { ++ action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); + } -+ return false; + } + + @Override -+ public boolean setBotAction(@NotNull LeavesBotAction action, @NotNull Player player, @NotNull String[] args) { -+ return setBotAction(action.getName(), player, args); ++ public boolean remove(boolean save) { ++ BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); ++ return true; + } + + @Override @@ -3620,98 +4446,67 @@ index 0000000000000000000000000000000000000000..fe1df01906f15e130cf947bbecb5df4b +} 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..4eaee3c09372f314d8a5930b4162a7d88d7cc105 +index 0000000000000000000000000000000000000000..422640df346ccae612b2d3492780efa59d8b4d17 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java -@@ -0,0 +1,102 @@ +@@ -0,0 +1,80 @@ +package org.leavesmc.leaves.entity; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; ++import net.minecraft.server.MinecraftServer; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; ++import org.leavesmc.leaves.bot.BotCreateState; ++import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.Actions; ++import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction; +import org.leavesmc.leaves.entity.botaction.CustomBotAction; +import org.leavesmc.leaves.event.bot.BotCreateEvent; -+import org.leavesmc.leaves.event.bot.BotRemoveEvent; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; -+import java.util.function.Consumer; + +public class CraftBotManager implements BotManager { + -+ private final Collection botViews = Collections.unmodifiableList(Lists.transform(ServerBot.getBots(), new Function() { -+ @Override -+ public CraftBot apply(ServerBot bot) { -+ return bot.getBukkitEntity(); -+ } -+ })); -+ -+ @Override -+ public @Nullable Bot getBot(@NotNull UUID uuid) { -+ return ServerBot.getBot(uuid).getBukkitPlayer(); -+ } -+ -+ @Override -+ public @Nullable Bot getBot(@NotNull String name) { -+ return ServerBot.getBot(name).getBukkitPlayer(); -+ } -+ -+ @Override -+ public @Nullable Bot createBot(@NotNull String name, @NotNull String realName, @Nullable String[] skin, @Nullable String skinName, @NotNull Location location) { -+ return this.createBot(name, realName, skin, skinName, location, null); -+ } -+ -+ @Override -+ public @Nullable Bot createBot(@NotNull String name, @NotNull String realName, @Nullable String[] skin, @Nullable String skinName, @NotNull Location location, @Nullable Consumer consumer) { -+ return new ServerBot.BotCreateState(location, name, realName, skinName, skin, BotCreateEvent.CreateReason.PLUGIN, null).createNow((serverBot -> { -+ if (consumer != null) { -+ consumer.accept(serverBot.getBukkitPlayer()); -+ } -+ })); -+ } ++ private final BotList botList; ++ private final Collection botViews; + -+ @Override -+ public void createBot(@NotNull String name, @Nullable String skinName, @NotNull Location location, @Nullable Consumer consumer) { -+ new ServerBot.BotCreateState(location, name, skinName, BotCreateEvent.CreateReason.PLUGIN, null).create((serverBot -> { -+ if (consumer != null) { -+ consumer.accept(serverBot.getBukkitPlayer()); ++ public CraftBotManager() { ++ this.botList = MinecraftServer.getServer().getBotList(); ++ this.botViews = Collections.unmodifiableList(Lists.transform(botList.bots, new Function() { ++ @Override ++ public CraftBot apply(ServerBot bot) { ++ return bot.getBukkitEntity(); + } + })); + } + + @Override -+ public void removeBot(@NotNull String name) { -+ ServerBot bot = ServerBot.getBot(name); ++ public @Nullable Bot getBot(@NotNull UUID uuid) { ++ ServerBot bot = botList.getBot(uuid); + if (bot != null) { -+ bot.onRemove(org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.PLUGIN); ++ return bot.getBukkitEntity(); ++ } else { ++ return null; + } + } + + @Override -+ public void removeBot(@NotNull UUID uuid) { -+ ServerBot bot = ServerBot.getBot(uuid); ++ public @Nullable Bot getBot(@NotNull String name) { ++ ServerBot bot = botList.getBotByName(name); + if (bot != null) { -+ bot.onRemove(BotRemoveEvent.RemoveReason.PLUGIN); ++ return bot.getBukkitEntity(); ++ } else { ++ return null; + } + } + + @Override -+ public void removeAllBots() { -+ ServerBot.removeAllBot(BotRemoveEvent.RemoveReason.PLUGIN); -+ } -+ -+ @Override -+ public void saveOrRemoveAllBots() { -+ ServerBot.saveOrRemoveAllBot(); -+ } -+ -+ @Override + public Collection getBots() { + return botViews; + } @@ -3723,15 +4518,24 @@ index 0000000000000000000000000000000000000000..4eaee3c09372f314d8a5930b4162a7d8 + + @Override + public boolean unregisterCustomBotAction(String name) { -+ return Actions.unregister(name); ++ BotAction action = Actions.getForName(name); ++ if (action instanceof CraftCustomBotAction) { ++ return Actions.unregister(name); ++ } ++ return false; ++ } ++ ++ @Override ++ public BotCreator botCreator(@NotNull String realName, @NotNull Location location) { ++ return BotCreateState.builder(realName, location).createReason(BotCreateEvent.CreateReason.PLUGIN); + } +} diff --git a/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java b/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java new file mode 100644 -index 0000000000000000000000000000000000000000..356b2de6ffd82e42a5d0f20ac1e3de7f4b5a7013 +index 0000000000000000000000000000000000000000..de06c854a9a5242cf632b38806e8e710496b7e4e --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java -@@ -0,0 +1,149 @@ +@@ -0,0 +1,151 @@ +package org.leavesmc.leaves.plugin; + +import org.bukkit.Server; @@ -3752,6 +4556,9 @@ index 0000000000000000000000000000000000000000..356b2de6ffd82e42a5d0f20ac1e3de7f +import java.util.List; + +public class MinecraftInternalPlugin extends PluginBase { ++ ++ public static final MinecraftInternalPlugin INSTANCE = new MinecraftInternalPlugin(); ++ + private boolean enabled = true; + + private final PluginDescriptionFile pdf; @@ -3774,12 +4581,11 @@ index 0000000000000000000000000000000000000000..356b2de6ffd82e42a5d0f20ac1e3de7f + public PluginDescriptionFile getDescription() { + return pdf; + } -+ // Paper start ++ + @Override + public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { + return pdf; + } -+ // Paper end + + @Override + public FileConfiguration getConfig() { diff --git a/patches/server/0018-No-chat-sign.patch b/patches/server/0018-No-chat-sign.patch index f98fa4cd..909c01fe 100644 --- a/patches/server/0018-No-chat-sign.patch +++ b/patches/server/0018-No-chat-sign.patch @@ -107,10 +107,10 @@ index 5705cb920084b775cce4b361683b32c6b6e003ed..cbff868303d751d09b68f431c78bb13b } } diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index f97b2cb3ea855e6e250cabf357a050cd52be8f70..a80b7dfb5de950d0ebacfc041f903ed3322f77c9 100644 +index 4ac40259f31f3deef6b5fe4456576bbd44cc2569..e45acea31d61462c877c902b238209f14f2c8ad1 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -668,7 +668,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -669,7 +669,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface // Paper start - Add setting for proxy online mode status return dedicatedserverproperties.enforceSecureProfile && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() @@ -149,10 +149,10 @@ index 01c2e26f92d5d1e46b98ebd20727beb779c98095..ced6a08caf546e245bd6a631df3dc9f8 if (packet == null || this.processedDisconnect) { // Spigot return; diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 901e9ff7a89b48b7acca7e5b96f640045c1d265e..a06b642d38f8367a5115e6e8a7bb4bae0acdb0bd 100644 +index 69f44d0351e8127e38cbe9e74c05b365c37b2c44..8ea03896e1d7f247d484628ec787d8a4f4488664 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1443,7 +1443,7 @@ public abstract class PlayerList { +@@ -1446,7 +1446,7 @@ public abstract class PlayerList { } public boolean verifyChatTrusted(PlayerChatMessage message) { // Paper - private -> public diff --git a/patches/server/0023-Config-to-disable-method-profiler.patch b/patches/server/0023-Config-to-disable-method-profiler.patch index 2750108b..09b46942 100644 --- a/patches/server/0023-Config-to-disable-method-profiler.patch +++ b/patches/server/0023-Config-to-disable-method-profiler.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Config to disable method profiler This patch is Powered by Pufferfish(https://github.com/pufferfish-gg/Pufferfish) diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index a625473805fcde57f3987f3d788efb36fa89073e..0f07ee9d947235fc91f7d450d746f056a9b9e13a 100644 +index c68e0a6715c690298268457d6438b12cd5c6baf2..f742d01b441644f5de845c772a779eba0d49d6c1 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2556,6 +2556,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop OVERWORLD = Level.OVERWORLD; final ResourceKey THE_NETHER = Level.NETHER; if (!((fromDim != OVERWORLD || toDim != THE_NETHER) && (fromDim != THE_NETHER || toDim != OVERWORLD))) { @@ -493,22 +493,6 @@ index 7092a4d4a583f4e01cc02bca17f3bd1bd32677a0..32622ebdd9c5949ad995875d29e121a4 private static final int[] SLOTS_FOR_DOWN = new int[]{2, 1}; private static final int[] SLOTS_FOR_SIDES = new int[]{1}; public static final int DATA_LIT_DURATION = 1; -diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -index 8ab7ca373a885fbe658013c9c6a2e38d32d77bb2..bcf5e0045da9711f48689ffcd266411f71a7bae1 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -16,10 +16,10 @@ import net.minecraft.nbt.NbtAccounter; - import net.minecraft.nbt.NbtIo; - import net.minecraft.nbt.NbtUtils; - import net.minecraft.server.level.ServerPlayer; --import net.minecraft.util.datafix.DataFixTypes; - import net.minecraft.world.entity.player.Player; - import org.bukkit.craftbukkit.entity.CraftPlayer; - import org.slf4j.Logger; -+import org.leavesmc.leaves.util.ArrayConstants; - - public class PlayerDataStorage { - diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java index ae86c45c1d49c7646c721991910592091e7333f8..f3dce7156d518193fe27a69f5792800b72742632 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java diff --git a/patches/server/0091-Bow-infinity-fix.patch b/patches/server/0091-Bow-infinity-fix.patch index 1fe200b1..4683509c 100644 --- a/patches/server/0091-Bow-infinity-fix.patch +++ b/patches/server/0091-Bow-infinity-fix.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Bow infinity fix diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 0ee227c0f7d54ed7c686a830873fab6c0cdd2ab5..e381b2d0e64b8d9834b573223f6370afc7c00b11 100644 +index b737848442843b4d7decc140f51f77ed6f7c5f4b..015104dfdfe9673a928f20f2946571a543a16772 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -2342,7 +2342,7 @@ public abstract class Player extends LivingEntity { +@@ -2348,7 +2348,7 @@ public abstract class Player extends LivingEntity { } } diff --git a/patches/server/0095-Replay-Mod-API.patch b/patches/server/0095-Replay-Mod-API.patch index a701767d..bdcdbe22 100644 --- a/patches/server/0095-Replay-Mod-API.patch +++ b/patches/server/0095-Replay-Mod-API.patch @@ -108,10 +108,10 @@ index c8d39e6e1c570c9219f6066da273dc0130920519..96a074281d16a7f64058619da4b102f3 if (((List) object).size() >= i) { return (List) object; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9e5e89aa5220dcfa278931bc891b055ae4ef0ad8..49e5749d838883a9a292c283daba136b9629b8eb 100644 +index dc3972dd9340febc7540cc32160291c4945dec85..7ad8f7cbcf4238ba2d6771ef51fb09a42d0cdb96 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1690,7 +1690,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ this.server.getBotList().bots.forEach(bot1 -> { + bot1.sendPlayerInfo(player); + bot1.sendFakeDataIfNeed(player, true); + }); // Leaves - render bot @@ -309,7 +307,7 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed -@@ -328,6 +444,7 @@ public abstract class PlayerList { +@@ -328,6 +442,7 @@ public abstract class PlayerList { // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below this.players.add(player); @@ -317,7 +315,7 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot this.playersByUUID.put(player.getUUID(), player); // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below -@@ -399,6 +516,12 @@ public abstract class PlayerList { +@@ -397,6 +512,12 @@ public abstract class PlayerList { continue; } @@ -330,7 +328,7 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join } // Paper start - Use single player info update packet on join -@@ -603,6 +726,43 @@ public abstract class PlayerList { +@@ -601,6 +722,43 @@ public abstract class PlayerList { } @@ -374,7 +372,7 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component // Paper start - Fix kick event leave message not being sent return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); -@@ -671,6 +831,7 @@ public abstract class PlayerList { +@@ -669,6 +827,7 @@ public abstract class PlayerList { entityplayer.retireScheduler(); // Paper - Folia schedulers entityplayer.getAdvancements().stopListening(); this.players.remove(entityplayer); @@ -382,7 +380,7 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); UUID uuid = entityplayer.getUUID(); -@@ -765,7 +926,7 @@ public abstract class PlayerList { +@@ -763,7 +922,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; @@ -392,13 +390,13 @@ index 6e68ad42d4fff62e38f45fd09de26da3363dcdd9..d04ebc381ba3f52954b647bc76ccc9cf } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 59cdcf7b25c17705b613c83dea107934b683af28..b759025019120a0029c46506f1eede4eb85d7550 100644 +index 97d09246b5bab3fe85491d06c7b16f932bcd1cb2..d4b4b4a4baf2321fa682d26885e07186375b53e7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -311,6 +311,7 @@ public final class CraftServer implements Server { private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes public final io.papermc.paper.SparksFly spark; // Paper - spark - private final org.leavesmc.leaves.entity.CraftBotManager botManager = new org.leavesmc.leaves.entity.CraftBotManager(); // Leaves + private final org.leavesmc.leaves.entity.CraftBotManager botManager; // Leaves + private final org.leavesmc.leaves.entity.CraftPhotographerManager photographerManager = new org.leavesmc.leaves.entity.CraftPhotographerManager(); // Leaves // Paper start - Folia region threading API @@ -412,7 +410,7 @@ index 59cdcf7b25c17705b613c83dea107934b683af28..b759025019120a0029c46506f1eede4e @Override public CraftPlayer apply(ServerPlayer player) { return player.getBukkitEntity(); -@@ -3237,4 +3238,11 @@ public final class CraftServer implements Server { +@@ -3238,4 +3239,11 @@ public final class CraftServer implements Server { return botManager; } // Leaves end - Bot API diff --git a/patches/server/0096-Leaves-I18n.patch b/patches/server/0096-Leaves-I18n.patch index 66803d72..72724b25 100644 --- a/patches/server/0096-Leaves-I18n.patch +++ b/patches/server/0096-Leaves-I18n.patch @@ -51,10 +51,10 @@ index a9bca1d10553f1406f7dbce3f7c40378b6abdc10..aa7f4f738c637ffc3d50f2bdb5ee61ff Builder builder = ImmutableMap.builder(); BiConsumer biConsumer = builder::put; diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index a80b7dfb5de950d0ebacfc041f903ed3322f77c9..fc45960fb9fe03323b5f6e9733bf9c7665dba5b7 100644 +index e45acea31d61462c877c902b238209f14f2c8ad1..3e4d4c417904fba6aeba0dee2e01d3b5bd95ac20 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -240,6 +240,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -241,6 +241,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.leavesmc.leaves.LeavesConfig.init((java.io.File) options.valueOf("leaves-settings")); // Leaves - Server Config System.setProperty("spark.serverconfigs.extra", "leaves.yml"); // Leaves - spark config diff --git a/patches/server/0105-Disable-offline-warn-if-use-proxy.patch b/patches/server/0105-Disable-offline-warn-if-use-proxy.patch index 6537bb09..197a5386 100644 --- a/patches/server/0105-Disable-offline-warn-if-use-proxy.patch +++ b/patches/server/0105-Disable-offline-warn-if-use-proxy.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable offline warn if use proxy diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index fc45960fb9fe03323b5f6e9733bf9c7665dba5b7..18f053785df042c41f88341bb3c0f647beb52ebe 100644 +index 3e4d4c417904fba6aeba0dee2e01d3b5bd95ac20..85e7321c7b918db404fca1c75a16ec17bc6b0b2f 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -302,7 +302,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -303,7 +303,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface String proxyFlavor = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "Velocity" : "BungeeCord"; String proxyLink = (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) ? "https://docs.papermc.io/velocity/security" : "http://www.spigotmc.org/wiki/firewall-guide/"; // Paper end - Add Velocity IP Forwarding Support diff --git a/patches/server/0118-Fast-resume.patch b/patches/server/0118-Fast-resume.patch index 37dcd90c..9f4ead29 100644 --- a/patches/server/0118-Fast-resume.patch +++ b/patches/server/0118-Fast-resume.patch @@ -59,18 +59,18 @@ index 58d3d1a47e9f2423c467bb329c2d5f4b58a8b5ef..ea1ffe6b5e49ccf2b472829ed97e977b return this.removeTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkPos), level, identifier); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 49e5749d838883a9a292c283daba136b9629b8eb..ca0badf478777d840ddf730e2684f76d9cc596a9 100644 +index a598fcf9d67ec29668b36f70d6980831f7de2fea..d6c5b8ee987ba73643a88e4a9337a54bfa7f792f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -748,6 +748,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop