From b61d6cf1ac7de9423c228036208aadca5e626392 Mon Sep 17 00:00:00 2001 From: Bacteriawa Date: Sat, 30 Aug 2025 23:08:21 +0800 Subject: [PATCH] Add Fakeplayer --- gradle.properties | 2 +- .../features/0002-Add-fakeplayer-api.patch | 44 + .../org/leavesmc/leaves/entity/bot/Bot.java | 77 ++ .../leaves/entity/bot/BotCreator.java | 52 ++ .../leaves/entity/bot/BotManager.java | 48 + .../entity/bot/action/AttackAction.java | 12 + .../leaves/entity/bot/action/BotAction.java | 85 ++ .../entity/bot/action/BreakBlockAction.java | 12 + .../leaves/entity/bot/action/DropAction.java | 12 + .../leaves/entity/bot/action/FishAction.java | 12 + .../leaves/entity/bot/action/JumpAction.java | 12 + .../leaves/entity/bot/action/LookAction.java | 54 ++ .../leaves/entity/bot/action/MountAction.java | 12 + .../leaves/entity/bot/action/MoveAction.java | 44 + .../entity/bot/action/RotationAction.java | 43 + .../leaves/entity/bot/action/SneakAction.java | 12 + .../entity/bot/action/StateBotAction.java | 10 + .../leaves/entity/bot/action/SwapAction.java | 12 + .../leaves/entity/bot/action/SwimAction.java | 12 + .../entity/bot/action/TimerBotAction.java | 68 ++ .../entity/bot/action/UseItemAction.java | 29 + .../entity/bot/action/UseItemAutoAction.java | 29 + .../bot/action/UseItemOffhandAction.java | 29 + .../entity/bot/action/UseItemOnAction.java | 29 + .../bot/action/UseItemOnOffhandAction.java | 29 + .../entity/bot/action/UseItemToAction.java | 29 + .../bot/action/UseItemToOffhandAction.java | 29 + .../leaves/event/bot/BotActionEvent.java | 27 + .../event/bot/BotActionExecuteEvent.java | 51 ++ .../event/bot/BotActionScheduleEvent.java | 47 + .../leaves/event/bot/BotActionStopEvent.java | 55 ++ .../event/bot/BotConfigModifyEvent.java | 53 ++ .../leaves/event/bot/BotCreateEvent.java | 118 +++ .../leaves/event/bot/BotDeathEvent.java | 69 ++ .../leavesmc/leaves/event/bot/BotEvent.java | 32 + .../event/bot/BotInventoryOpenEvent.java | 46 + .../leaves/event/bot/BotJoinEvent.java | 67 ++ .../leaves/event/bot/BotLoadEvent.java | 59 ++ .../leaves/event/bot/BotRemoveEvent.java | 105 +++ .../event/bot/BotSpawnLocationEvent.java | 38 + .../player/PlayerOperationLimitEvent.java | 56 ++ .../event/player/UpdateSuppressionEvent.java | 46 + .../features/0020-Leaves-Fakeplayer.patch | 821 ++++++++++++++++++ .../0003-Leaves-Leaves-Protocol-Core.patch | 2 +- .../features/0004-Leaves-Fakeplayer.patch | 171 ++++ .../config/modules/misc/FakeplayerConfig.java | 108 +++ .../org/leavesmc/leaves/bot/BotCommand.java | 486 +++++++++++ .../leavesmc/leaves/bot/BotCreateState.java | 132 +++ .../leavesmc/leaves/bot/BotDataStorage.java | 139 +++ .../leaves/bot/BotInventoryContainer.java | 125 +++ .../java/org/leavesmc/leaves/bot/BotList.java | 359 ++++++++ .../leavesmc/leaves/bot/BotRecipeBook.java | 56 ++ .../leavesmc/leaves/bot/BotStatsCounter.java | 36 + .../java/org/leavesmc/leaves/bot/BotUtil.java | 112 +++ .../leaves/bot/IPlayerDataStorage.java | 14 + .../org/leavesmc/leaves/bot/MojangAPI.java | 39 + .../org/leavesmc/leaves/bot/ServerBot.java | 684 +++++++++++++++ .../leaves/bot/ServerBotGameMode.java | 119 +++ .../bot/ServerBotPacketListenerImpl.java | 68 ++ .../leaves/bot/agent/AbstractBotConfig.java | 67 ++ .../leavesmc/leaves/bot/agent/Actions.java | 75 ++ .../leavesmc/leaves/bot/agent/Configs.java | 70 ++ .../bot/agent/actions/ServerAttackAction.java | 29 + .../bot/agent/actions/ServerBotAction.java | 237 +++++ .../agent/actions/ServerBreakBlockAction.java | 108 +++ .../bot/agent/actions/ServerDropAction.java | 23 + .../bot/agent/actions/ServerFishAction.java | 80 ++ .../bot/agent/actions/ServerJumpAction.java | 27 + .../bot/agent/actions/ServerLookAction.java | 110 +++ .../bot/agent/actions/ServerMountAction.java | 51 ++ .../bot/agent/actions/ServerMoveAction.java | 72 ++ .../agent/actions/ServerRotationAction.java | 83 ++ .../bot/agent/actions/ServerSneakAction.java | 35 + .../agent/actions/ServerStateBotAction.java | 13 + .../bot/agent/actions/ServerSwapAction.java | 29 + .../bot/agent/actions/ServerSwimAction.java | 27 + .../agent/actions/ServerTimerBotAction.java | 32 + .../bot/agent/actions/ServerUseBotAction.java | 127 +++ .../agent/actions/ServerUseItemAction.java | 33 + .../actions/ServerUseItemAutoAction.java | 86 ++ .../actions/ServerUseItemOffhandAction.java | 25 + .../agent/actions/ServerUseItemOnAction.java | 46 + .../actions/ServerUseItemOnOffhandAction.java | 27 + .../agent/actions/ServerUseItemToAction.java | 52 ++ .../actions/ServerUseItemToOffhandAction.java | 27 + .../agent/configs/AlwaysSendDataConfig.java | 45 + .../bot/agent/configs/LocatorBarConfig.java | 51 ++ .../configs/SimulationDistanceConfig.java | 46 + .../bot/agent/configs/SkipSleepConfig.java | 41 + .../bot/agent/configs/SpawnPhantomConfig.java | 52 ++ .../bot/agent/configs/TickTypeConfig.java | 47 + .../leaves/command/CommandArgument.java | 67 ++ .../leaves/command/CommandArgumentResult.java | 92 ++ .../leaves/command/CommandArgumentType.java | 59 ++ .../leavesmc/leaves/entity/bot/CraftBot.java | 97 +++ .../leaves/entity/bot/CraftBotManager.java | 74 ++ .../entity/bot/actions/CraftAttackAction.java | 11 + .../entity/bot/actions/CraftBotAction.java | 86 ++ .../bot/actions/CraftBreakBlockAction.java | 11 + .../entity/bot/actions/CraftDropAction.java | 11 + .../entity/bot/actions/CraftFishAction.java | 11 + .../entity/bot/actions/CraftJumpAction.java | 11 + .../entity/bot/actions/CraftLookAction.java | 36 + .../entity/bot/actions/CraftMountAction.java | 11 + .../entity/bot/actions/CraftMoveAction.java | 22 + .../bot/actions/CraftRotationAction.java | 33 + .../entity/bot/actions/CraftSneakAction.java | 11 + .../entity/bot/actions/CraftSwapAction.java | 11 + .../entity/bot/actions/CraftSwimAction.java | 11 + .../bot/actions/CraftTimerBotAction.java | 53 ++ .../bot/actions/CraftUseItemAction.java | 28 + .../bot/actions/CraftUseItemAutoAction.java | 22 + .../actions/CraftUseItemOffhandAction.java | 22 + .../bot/actions/CraftUseItemOnAction.java | 22 + .../actions/CraftUseItemOnOffhandAction.java | 22 + .../bot/actions/CraftUseItemToAction.java | 22 + .../actions/CraftUseItemToOffhandAction.java | 22 + .../org/leavesmc/leaves/util/MathUtils.java | 96 ++ .../util/UpdateSuppressionException.java | 127 +++ 119 files changed, 8150 insertions(+), 2 deletions(-) create mode 100644 lophine-api/paper-patches/features/0002-Add-fakeplayer-api.patch create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/Bot.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotCreator.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/AttackAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BotAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BreakBlockAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/DropAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/FishAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/JumpAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/LookAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MountAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MoveAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/RotationAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SneakAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/StateBotAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwapAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwimAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/TimerBotAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAutoAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOffhandAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnOffhandAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToOffhandAction.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/player/PlayerOperationLimitEvent.java create mode 100644 lophine-api/src/main/java/org/leavesmc/leaves/event/player/UpdateSuppressionEvent.java create mode 100644 lophine-server/minecraft-patches/features/0020-Leaves-Fakeplayer.patch create mode 100644 lophine-server/paper-patches/features/0004-Leaves-Fakeplayer.patch create mode 100644 lophine-server/src/main/java/fun/bm/lophine/config/modules/misc/FakeplayerConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/BotUtil.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftAttackAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBreakBlockAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftDropAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftFishAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftJumpAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftLookAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMountAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMoveAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftRotationAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSneakAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwapAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwimAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAutoAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToOffhandAction.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/util/MathUtils.java create mode 100644 lophine-server/src/main/java/org/leavesmc/leaves/util/UpdateSuppressionException.java diff --git a/gradle.properties b/gradle.properties index 7efe208..8247c7d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ mcVersion=1.21.8 release=2 # 0 for skip release, 1 for pre-release, 2 for release -luminolRef=09014c29e45c4d8c1e2ba08bc8d6d58c5f3cd004 +luminolRef=fa4e778f2ab92b0ccef57fad830660766b3f2f18 org.gradle.configuration-cache=true org.gradle.caching=true diff --git a/lophine-api/paper-patches/features/0002-Add-fakeplayer-api.patch b/lophine-api/paper-patches/features/0002-Add-fakeplayer-api.patch new file mode 100644 index 0000000..16a0df6 --- /dev/null +++ b/lophine-api/paper-patches/features/0002-Add-fakeplayer-api.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bacteriawa +Date: Sat, 30 Aug 2025 16:51:26 +0800 +Subject: [PATCH] Add fakeplayer api + + +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index 85b778980e762835ac278528087e91b2e3effb43..89f00f4b6ff64305fa44ebb71f22a113c1296d26 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -3052,4 +3052,15 @@ public final class Bukkit { + public static void restart() { + server.restart(); + } ++ ++ // Leaves start - Bot API ++ /** ++ * Returns a bot manager. ++ * ++ * @return Bot Manager ++ */ ++ public static @NotNull org.leavesmc.leaves.entity.bot.BotManager getBotManager() { ++ return server.getBotManager(); ++ } ++ // Leaves end - Bot API + } +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index a0540d7de5c2536aed5231f9e67b73345817921d..c7ae463fb166750bbd0638a2ccf81753b293a381 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -2753,4 +2753,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + */ + double @Nullable [] getRegionTPS(@NotNull World world, int chunkX, int chunkZ); + // Folia end - region TPS API ++ ++ // Leaves start - Bot API ++ /** ++ * Returns a bot manager. ++ * ++ * @return Bot Manager ++ */ ++ @NotNull org.leavesmc.leaves.entity.bot.BotManager getBotManager(); ++ // Leaves end - Bot API + } diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/Bot.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/Bot.java new file mode 100644 index 0000000..df7dbdd --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/Bot.java @@ -0,0 +1,77 @@ +package org.leavesmc.leaves.entity.bot; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.entity.bot.action.BotAction; + +import java.util.UUID; + +/** + * Represents a fakeplayer + */ +public interface Bot extends Player { + + /** + * Gets the fakeplayer skin + * + * @return fakeplayer skin name + */ + @Nullable String getSkinName(); + + /** + * Gets the fakeplayer name without prefix and suffix + * + * @return fakeplayer real name + */ + @NotNull String getRealName(); + + /** + * Gets the creator's UUID of the fakeplayer + * + * @return creator's UUID + */ + @Nullable UUID getCreatePlayerUUID(); + + /** + * Add an action to the fakeplayer + * + * @param action bot action + */ + > void addAction(@NotNull T action); + + /** + * Get the copy action in giving index + * + * @param index index of actions + * @return Action of that index + */ + BotAction getAction(int index); + + /** + * Get action size + * + * @return size + */ + int getActionSize(); + + /** + * Stop the action in giving index + * + * @param index index of actions + */ + void stopAction(int index); + + /** + * Stop all the actions of the fakeplayer + */ + void stopAllActions(); + + /** + * Remove the fakeplayer + * + * @param save should save + * @return success + */ + boolean remove(boolean save); +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotCreator.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotCreator.java new file mode 100644 index 0000000..e70b1f3 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotCreator.java @@ -0,0 +1,52 @@ +package org.leavesmc.leaves.entity.bot; + +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 { + + static BotCreator of(String realName, Location location) { + return Bukkit.getBotManager().botCreator(realName, location); + } + + BotCreator name(String name); + + BotCreator skinName(String skinName); + + BotCreator skin(String[] skin); + + /** + * Sets the skin of the bot using the Mojang API based on the provided skin name. + *

+ * Need Async. + * + * @return BotCreator + */ + BotCreator mojangAPISkin(); + + BotCreator location(@NotNull Location location); + + BotCreator creator(@Nullable CommandSender creator); + + /** + * Create a bot directly + * + * @return a bot, null spawn fail + */ + @Nullable Bot spawn(); + + /** + * Create a bot and apply skin of player names `skinName` from MojangAPI + * just like `mojangAPISkin().spawn()`, but async + *

+ * you can not get the bot instance instantly because get skin in on async thread + * + * @param consumer Consumer + */ + void spawnWithSkin(Consumer consumer); +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java new file mode 100644 index 0000000..c434364 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/BotManager.java @@ -0,0 +1,48 @@ +package org.leavesmc.leaves.entity.bot; + +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.entity.bot.action.BotAction; + +import java.util.Collection; +import java.util.UUID; + +/** + * Simple fakeplayer manager + */ +public interface BotManager { + + /** + * Gets a fakeplayer object by the given uuid. + * + * @param uuid the uuid to look up + * @return a fakeplayer if one was found, null otherwise + */ + @Nullable Bot getBot(@NotNull UUID uuid); + + /** + * Gets a fakeplayer object by the given name. + * + * @param name the name to look up + * @return a fakeplayer if one was found, null otherwise + */ + @Nullable Bot getBot(@NotNull String name); + + /** + * 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. + */ + Collection getBots(); + + /** + * Create a bot action by class. + * + * @param type action class + * @return a bot action instance if one was found, null otherwise + */ + > T newAction(@NotNull Class type); + + BotCreator botCreator(@NotNull String realName, @NotNull Location location); +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/AttackAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/AttackAction.java new file mode 100644 index 0000000..9b773f7 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/AttackAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to attack entities. + */ +public interface AttackAction extends TimerBotAction { + static AttackAction create() { + return Bukkit.getBotManager().newAction(AttackAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BotAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BotAction.java new file mode 100644 index 0000000..8e64511 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BotAction.java @@ -0,0 +1,85 @@ +package org.leavesmc.leaves.entity.bot.action; + +import java.util.UUID; +import java.util.function.Consumer; + +/** + * Represents an action that can be performed by a Bot. + *

+ * This interface defines the basic properties of an action (such as name and UUID) + * and lifecycle event callbacks (success, failure, stop). + * + * @param the type of the callback parameter, usually a context object related to the action + */ +public interface BotAction { + + /** + * Gets the name of this action. + * + * @return the action name + */ + String getName(); + + /** + * Gets the UUID of this action. + * + * @return the UUID of the action + */ + UUID getUUID(); + + /** + * Sets whether this action is cancelled. + * + * @param cancel true to cancel the action, false otherwise + */ + void setCancelled(boolean cancel); + + /** + * Checks whether this action has been cancelled. + * + * @return true if cancelled, false otherwise + */ + boolean isCancelled(); + + /** + * Sets the callback to be executed when the action fails. + * + * @param onFail the callback to execute on failure, with a parameter of type T + */ + void setOnFail(Consumer onFail); + + /** + * Gets the callback to be executed when the action fails. + * + * @return the failure callback + */ + Consumer getOnFail(); + + /** + * Sets the callback to be executed when the action succeeds. + * + * @param onSuccess the callback to execute on success, with a parameter of type T + */ + void setOnSuccess(Consumer onSuccess); + + /** + * Gets the callback to be executed when the action succeeds. + * + * @return the success callback + */ + Consumer getOnSuccess(); + + /** + * Sets the callback to be executed when the action is stopped. + * + * @param onStop the callback to execute on stop, with a parameter of type T + */ + void setOnStop(Consumer onStop); + + /** + * Gets the callback to be executed when the action is stopped. + * + * @return the stop callback + */ + Consumer getOnStop(); +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BreakBlockAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BreakBlockAction.java new file mode 100644 index 0000000..a314fd1 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/BreakBlockAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to break a block. + */ +public interface BreakBlockAction extends TimerBotAction { + static BreakBlockAction create() { + return Bukkit.getBotManager().newAction(BreakBlockAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/DropAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/DropAction.java new file mode 100644 index 0000000..606ebad --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/DropAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to drop all items in its inventory. + */ +public interface DropAction extends TimerBotAction { + static DropAction create() { + return Bukkit.getBotManager().newAction(DropAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/FishAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/FishAction.java new file mode 100644 index 0000000..2fc985c --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/FishAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to perform auto fishing. + */ +public interface FishAction extends TimerBotAction { + static FishAction create() { + return Bukkit.getBotManager().newAction(FishAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/JumpAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/JumpAction.java new file mode 100644 index 0000000..5daf5fc --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/JumpAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to perform a jump. + */ +public interface JumpAction extends TimerBotAction { + static JumpAction create() { + return Bukkit.getBotManager().newAction(JumpAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/LookAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/LookAction.java new file mode 100644 index 0000000..ea0db1d --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/LookAction.java @@ -0,0 +1,54 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +/** + * Represents an action for a bot to look at a specific position or player. + *

+ * If both a position and a player are set, the player target takes precedence. + */ +public interface LookAction extends BotAction { + + /** + * Sets the position in the world for the bot to look at. + *

+ * If a player target is set via {@link #setTarget(Player)}, the bot will look at the player instead of this position. + * + * @param pos the {@link Vector} representing the position to look at + * @return this {@code LookAction} instance for method chaining + */ + LookAction setPos(Vector pos); + + /** + * Gets the position in the world that the bot is set to look at. + *

+ * If a player target is set, this value may be ignored. + * + * @return the {@link Vector} position to look at, or {@code null} if not set + */ + Vector getPos(); + + /** + * Sets the player for the bot to look at. + *

+ * When a player is set as the target, the bot will continuously look at the player's current position, + * overriding any position set by {@link #setPos(Vector)}. + * + * @param player the {@link Player} to look at, or {@code null} to clear the target + * @return this {@code LookAction} instance for method chaining + */ + LookAction setTarget(Player player); + + /** + * Gets the player that the bot is set to look at. + * + * @return the {@link Player} target, or {@code null} if not set + */ + Player getTarget(); + + static LookAction create() { + return Bukkit.getBotManager().newAction(LookAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MountAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MountAction.java new file mode 100644 index 0000000..8452485 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MountAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to mount to nearby vehicles. + */ +public interface MountAction extends BotAction { + static MountAction create() { + return Bukkit.getBotManager().newAction(MountAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MoveAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MoveAction.java new file mode 100644 index 0000000..eb855bb --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/MoveAction.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to move to a specific direction. + */ +public interface MoveAction extends StateBotAction { + + /** + * Gets the direction of the move action. + * + * @return the direction of the move action + */ + MoveDirection getDirection(); + + /** + * Sets the direction of the move action. + * + * @param direction the direction to set + * @return this action instance + */ + MoveAction setDirection(MoveDirection direction); + + /** + * Represents possible movement directions for the bot. + */ + enum MoveDirection { + FORWARD("forward"), + BACKWARD("backward"), + LEFT("left"), + RIGHT("right"); + + public final String name; + + MoveDirection(String name) { + this.name = name; + } + } + + static MoveAction create() { + return Bukkit.getBotManager().newAction(MoveAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/RotationAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/RotationAction.java new file mode 100644 index 0000000..9fb7544 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/RotationAction.java @@ -0,0 +1,43 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to rotate to a specific yaw and pitch. + */ +public interface RotationAction extends BotAction { + + /** + * Sets the yaw of the rotation. + * + * @param yaw the yaw to set + * @return this action instance + */ + RotationAction setYaw(float yaw); + + /** + * Sets the pitch of the rotation. + * + * @param pitch the pitch to set + * @return this action instance + */ + RotationAction setPitch(float pitch); + + /** + * Gets the yaw of the rotation. + * + * @return the yaw + */ + float getYaw(); + + /** + * Gets the pitch of the rotation. + * + * @return the pitch + */ + float getPitch(); + + static RotationAction create() { + return Bukkit.getBotManager().newAction(RotationAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SneakAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SneakAction.java new file mode 100644 index 0000000..23c68e6 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SneakAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to sneak. + */ +public interface SneakAction extends StateBotAction { + static SneakAction create() { + return Bukkit.getBotManager().newAction(SneakAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/StateBotAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/StateBotAction.java new file mode 100644 index 0000000..7199f34 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/StateBotAction.java @@ -0,0 +1,10 @@ +package org.leavesmc.leaves.entity.bot.action; + +/** + * Represents a type of bot action that places the bot in a specific state while the action is active. + * When the action is stopped, the associated state is removed from the bot. + * + * @param The type of entity that this action operates on. + */ +public interface StateBotAction extends BotAction { +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwapAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwapAction.java new file mode 100644 index 0000000..b7407b9 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwapAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to swap items between the main hand and off-hand. + */ +public interface SwapAction extends BotAction { + static SwapAction create() { + return Bukkit.getBotManager().newAction(SwapAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwimAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwimAction.java new file mode 100644 index 0000000..6fa03c6 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/SwimAction.java @@ -0,0 +1,12 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action for a bot to float up in water. + */ +public interface SwimAction extends StateBotAction { + static SwimAction create() { + return Bukkit.getBotManager().newAction(SwimAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/TimerBotAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/TimerBotAction.java new file mode 100644 index 0000000..c75129f --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/TimerBotAction.java @@ -0,0 +1,68 @@ +package org.leavesmc.leaves.entity.bot.action; + +/** + * Represents a scheduled bot task that runs periodically. + *

+ * TimerBotAction allows configuration of start delay, execution interval, and the number of executions. + * It is intended for bot actions that need to be triggered at regular intervals. + * + * @param the type of entity this action operates on + */ +public interface TimerBotAction extends BotAction { + + /** + * Sets the delay in ticks before the task starts for the first time. + * + * @param delayTick the number of ticks to delay before the first execution + */ + void setStartDelayTick(int delayTick); + + /** + * Gets the delay in ticks before the task starts for the first time. + * + * @return the number of ticks to delay before the first execution + */ + int getStartDelayTick(); + + /** + * Sets the interval in ticks between each execution of the task. + * + * @param intervalTick the number of ticks between executions + */ + void setDoIntervalTick(int intervalTick); + + /** + * Gets the interval in ticks between each execution of the task. + * + * @return the number of ticks between executions + */ + int getDoIntervalTick(); + + /** + * Sets the total number of times the task should be executed. + * + * @param doNumber the total number of executions + */ + void setDoNumber(int doNumber); + + /** + * Gets the total number of times the task should be executed. + * + * @return the total number of executions + */ + int getDoNumber(); + + /** + * Gets the number of ticks remaining until the next execution. + * + * @return the number of ticks until the next execution + */ + int getTickToNext(); + + /** + * Gets the number of executions remaining for this task. + * + * @return the number of executions remaining + */ + int getDoNumberRemaining(); +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAction.java new file mode 100644 index 0000000..8cc70df --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot only uses an item, without using it on blocks or entities. + */ +public interface UseItemAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemAction setUseTickTimeout(int timeout); + + static UseItemAction create() { + return Bukkit.getBotManager().newAction(UseItemAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAutoAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAutoAction.java new file mode 100644 index 0000000..a4d7af3 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemAutoAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot uses an item, fully simulating the effect of a player right-clicking. + */ +public interface UseItemAutoAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemAutoAction setUseTickTimeout(int timeout); + + static UseItemAutoAction create() { + return Bukkit.getBotManager().newAction(UseItemAutoAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOffhandAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOffhandAction.java new file mode 100644 index 0000000..4bd4cc3 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOffhandAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot only uses the item in its offhand, without using it on blocks or entities. + */ +public interface UseItemOffhandAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemOffhandAction setUseTickTimeout(int timeout); + + static UseItemOffhandAction create() { + return Bukkit.getBotManager().newAction(UseItemOffhandAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnAction.java new file mode 100644 index 0000000..6697bb2 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot uses an item on a block. + */ +public interface UseItemOnAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemOnAction setUseTickTimeout(int timeout); + + static UseItemOnAction create() { + return Bukkit.getBotManager().newAction(UseItemOnAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnOffhandAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnOffhandAction.java new file mode 100644 index 0000000..cd1859e --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemOnOffhandAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot uses an item in its offhand on a block. + */ +public interface UseItemOnOffhandAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemOnOffhandAction setUseTickTimeout(int timeout); + + static UseItemOnOffhandAction create() { + return Bukkit.getBotManager().newAction(UseItemOnOffhandAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToAction.java new file mode 100644 index 0000000..d9c3b73 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot uses an item to an entity. + */ +public interface UseItemToAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemToAction setUseTickTimeout(int timeout); + + static UseItemToAction create() { + return Bukkit.getBotManager().newAction(UseItemToAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToOffhandAction.java b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToOffhandAction.java new file mode 100644 index 0000000..9469c6e --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/entity/bot/action/UseItemToOffhandAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.entity.bot.action; + +import org.bukkit.Bukkit; + +/** + * Represents an action where a bot uses an item in its offhand to an entity. + */ +public interface UseItemToOffhandAction extends TimerBotAction { + + /** + * Gets the equivalent right-click hold duration timeout in ticks. + * Default is -1, which means no timeout. + * + * @return the equivalent right-click hold duration timeout + */ + int getUseTickTimeout(); + + /** + * Sets the equivalent right-click hold duration timeout in ticks. + * + * @param timeout the equivalent right-click hold duration timeout + * @return this action instance + */ + UseItemToOffhandAction setUseTickTimeout(int timeout); + + static UseItemToOffhandAction create() { + return Bukkit.getBotManager().newAction(UseItemToOffhandAction.class); + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java new file mode 100644 index 0000000..fcaf302 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionEvent.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.event.bot; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.entity.bot.Bot; + +import java.util.UUID; + +public abstract class BotActionEvent extends BotEvent { + + private final String actionName; + private final UUID actionUUID; + + public BotActionEvent(@NotNull Bot who, String actionName, UUID actionUUID) { + super(who); + this.actionName = actionName; + this.actionUUID = actionUUID; + } + + @NotNull + public String getActionName() { + return actionName; + } + + public UUID getActionUUID() { + return actionUUID; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java new file mode 100644 index 0000000..9c298ae --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionExecuteEvent.java @@ -0,0 +1,51 @@ +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.Bot; + +import java.util.UUID; + +public class BotActionExecuteEvent extends BotActionEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private Result result = Result.PASS; + + public BotActionExecuteEvent(@NotNull Bot who, String actionName, UUID actionUUID) { + super(who, actionName, actionUUID); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return result != Result.PASS; + } + + @Override + public void setCancelled(boolean cancel) { + this.result = cancel ? Result.SOFT_CANCEL : Result.PASS; + } + + public void hardCancel() { + this.result = Result.HARD_CANCEL; + } + + public Result getResult() { + return this.result; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public enum Result { + PASS, SOFT_CANCEL, HARD_CANCEL + + } +} + diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java new file mode 100644 index 0000000..f93f1e9 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionScheduleEvent.java @@ -0,0 +1,47 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.command.CommandSender; +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.Bot; + +import java.util.UUID; + +public class BotActionScheduleEvent extends BotActionEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final CommandSender sender; + private boolean cancel = false; + + public BotActionScheduleEvent(@NotNull Bot who, String actionName, UUID actionUUID, CommandSender sender) { + super(who, actionName, actionUUID); + this.sender = sender; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Nullable + public CommandSender getSender() { + return sender; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java new file mode 100644 index 0000000..ee79a86 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotActionStopEvent.java @@ -0,0 +1,55 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.command.CommandSender; +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.Bot; + +import java.util.UUID; + +public class BotActionStopEvent extends BotActionEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final CommandSender sender; + private final Reason reason; + private boolean cancel = false; + + public BotActionStopEvent(@NotNull Bot who, String actionName, UUID actionUUID, Reason stopReason, CommandSender sender) { + super(who, actionName, actionUUID); + this.reason = stopReason; + this.sender = sender; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public Reason getReason() { + return reason; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Nullable + public CommandSender getSender() { + return sender; + } + + public enum Reason { + DONE, COMMAND, PLUGIN, INTERNAL + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java new file mode 100644 index 0000000..ce3ec9d --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotConfigModifyEvent.java @@ -0,0 +1,53 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.entity.bot.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 CommandSender sender; + private boolean cancel; + + public BotConfigModifyEvent(@NotNull Bot who, String configName, String[] configValue, CommandSender sender) { + super(who); + this.configName = configName; + this.configValue = configValue; + this.sender = sender; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @NotNull + public String getConfigName() { + return configName; + } + + @NotNull + public String[] getConfigValue() { + return configValue; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java new file mode 100644 index 0000000..90dd5ec --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotCreateEvent.java @@ -0,0 +1,118 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Call when a fakeplayer created + */ +public class BotCreateEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final String bot; + private final String skin; + private final CreateReason reason; + private final CommandSender creator; + private Location createLocation; + private boolean cancel = false; + + public BotCreateEvent(@NotNull final String who, @NotNull final String skin, @NotNull final Location createLocation, @NotNull CreateReason reason, @Nullable CommandSender creator) { + this.bot = who; + this.skin = skin; + this.createLocation = createLocation; + this.reason = reason; + this.creator = creator; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Gets the fakeplayer name + * + * @return fakeplayer name + */ + public String getBot() { + return bot; + } + + /** + * Gets the location to create the fakeplayer + * + * @return Location to create the fakeplayer + */ + @NotNull + public Location getCreateLocation() { + return createLocation; + } + + /** + * Sets the location to create the fakeplayer + * + * @param createLocation location to create the fakeplayer + */ + public void setCreateLocation(@NotNull Location createLocation) { + this.createLocation = createLocation; + } + + /** + * Gets the fakeplayer skin + * + * @return fakeplayer skin name + */ + @Nullable + public String getSkin() { + return skin; + } + + /** + * Gets the create reason of the bot + * + * @return create reason + */ + @NotNull + public CreateReason getReason() { + return reason; + } + + /** + * Gets the creator of the bot + * if the create reason is not COMMAND, the creator might be null + * + * @return An optional of creator + */ + @Nullable + public CommandSender getCreator() { + return creator; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + public enum CreateReason { + COMMAND, + PLUGIN, + INTERNAL, + UNKNOWN, + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotDeathEvent.java new file mode 100644 index 0000000..8709a0f --- /dev/null +++ b/lophine-api/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.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; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public @NotNull HandlerList getHandlers() { + 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/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java new file mode 100644 index 0000000..f046c48 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotEvent.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.entity.bot.Bot; + +/** + * 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) { + super(async); + bot = who; + } + + /** + * Returns the fakeplayer involved in this event + * + * @return Fakeplayer who is involved in this event + */ + @NotNull + public final Bot getBot() { + return bot; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java new file mode 100644 index 0000000..e1587ef --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotInventoryOpenEvent.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.event.bot; + +import org.bukkit.entity.Player; +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.Bot; + +public class BotInventoryOpenEvent extends BotEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private boolean cancel = false; + + public BotInventoryOpenEvent(@NotNull Bot who, @Nullable Player player) { + super(who); + this.player = player; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Nullable + public Player getOpenPlayer() { + return player; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java new file mode 100644 index 0000000..bef0e30 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotJoinEvent.java @@ -0,0 +1,67 @@ +package org.leavesmc.leaves.event.bot; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.entity.bot.Bot; + +/** + * Called when a fakeplayer joins a server + */ +public class BotJoinEvent extends BotEvent { + + private static final HandlerList handlers = new HandlerList(); + + private Component joinMessage; + + public BotJoinEvent(@NotNull final Bot who, @Nullable final Component joinMessage) { + super(who); + this.joinMessage = joinMessage; + } + + public BotJoinEvent(@NotNull final Bot who, @Nullable final String joinMessage) { + super(who); + this.joinMessage = joinMessage != null ? LegacyComponentSerializer.legacySection().deserialize(joinMessage) : null; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public void joinMessage(@Nullable final Component joinMessage) { + this.joinMessage = joinMessage; + } + + @Nullable + public Component joinMessage() { + return joinMessage; + } + + /** + * Gets the join message to send to all online players + * + * @return string join message. Can be null + */ + @Nullable + public String getJoinMessage() { + return this.joinMessage == null ? null : LegacyComponentSerializer.legacySection().serialize(this.joinMessage); + } + + /** + * Sets the join message to send to all online players + * + * @param joinMessage join message. If null, no message will be sent + */ + public void setJoinMessage(@Nullable String joinMessage) { + this.joinMessage = joinMessage != null ? LegacyComponentSerializer.legacySection().deserialize(joinMessage) : null; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotLoadEvent.java new file mode 100644 index 0000000..2e2b034 --- /dev/null +++ b/lophine-api/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 loaded + */ +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; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * 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; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java new file mode 100644 index 0000000..794e859 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotRemoveEvent.java @@ -0,0 +1,105 @@ +package org.leavesmc.leaves.event.bot; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.command.CommandSender; +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.Bot; + +/** + * Call when a fakeplayer removed + */ +public class BotRemoveEvent extends BotEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final RemoveReason reason; + private final CommandSender remover; + private Component removeMessage; + private boolean save; + private boolean cancel = false; + + public BotRemoveEvent(@NotNull final Bot who, @NotNull RemoveReason reason, @Nullable CommandSender remover, @Nullable Component removeMessage, boolean save) { + super(who); + this.reason = reason; + this.remover = remover; + this.removeMessage = removeMessage; + this.save = save; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Gets the remove reason of the bot + * + * @return remove reason + */ + @NotNull + public RemoveReason getReason() { + return reason; + } + + /** + * Gets the remover of the bot + * if the remove reason is not COMMAND, the creator might be null + * + * @return An optional of remover + */ + @Nullable + public CommandSender getRemover() { + return remover; + } + + public Component removeMessage() { + return removeMessage; + } + + public void removeMessage(Component removeMessage) { + this.removeMessage = removeMessage; + } + + @Nullable + public String getRemoveMessage() { + return this.removeMessage == null ? null : LegacyComponentSerializer.legacySection().serialize(this.removeMessage); + } + + public void setRemoveMessage(@Nullable String removeMessage) { + this.removeMessage = removeMessage != null ? LegacyComponentSerializer.legacySection().deserialize(removeMessage) : null; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public boolean shouldSave() { + return save; + } + + public void setSave(boolean save) { + this.save = save; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + public enum RemoveReason { + COMMAND, + PLUGIN, + DEATH, + INTERNAL + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/bot/BotSpawnLocationEvent.java new file mode 100644 index 0000000..7528ccd --- /dev/null +++ b/lophine-api/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.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 static HandlerList getHandlerList() { + return handlers; + } + + @NotNull + public Location getSpawnLocation() { + return spawnLocation; + } + + public void setSpawnLocation(@NotNull Location location) { + this.spawnLocation = location; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/player/PlayerOperationLimitEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/player/PlayerOperationLimitEvent.java new file mode 100644 index 0000000..090d1ba --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/player/PlayerOperationLimitEvent.java @@ -0,0 +1,56 @@ +package org.leavesmc.leaves.event.bot.player; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player operation is limited + */ +public class PlayerOperationLimitEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + + private final Block block; + private final Operation operation; + + public PlayerOperationLimitEvent(@NotNull Player who, Operation operation, Block block) { + super(who); + this.block = block; + this.operation = operation; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Gets the operated block + * + * @return block + */ + public Block getBlock() { + return block; + } + + /** + * Gets the type of operation + * + * @return operation type + */ + public Operation getOperation() { + return operation; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + public enum Operation { + MINE, PLACE + } +} diff --git a/lophine-api/src/main/java/org/leavesmc/leaves/event/player/UpdateSuppressionEvent.java b/lophine-api/src/main/java/org/leavesmc/leaves/event/player/UpdateSuppressionEvent.java new file mode 100644 index 0000000..61215a7 --- /dev/null +++ b/lophine-api/src/main/java/org/leavesmc/leaves/event/player/UpdateSuppressionEvent.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.event.player; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class UpdateSuppressionEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final @Nullable Player player; + private final @Nullable Location position; + private final @Nullable Material material; + private final @NotNull Throwable throwable; + + public UpdateSuppressionEvent(@Nullable Player player, @Nullable Location position, @Nullable Material material, @NotNull Throwable throwable) { + this.player = player; + this.position = position; + this.material = material; + this.throwable = throwable; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + public @Nullable Player getPlayer() { + return player; + } + + public @NotNull Throwable getThrowable() { + return throwable; + } + + public @Nullable Location getPosition() { + return position; + } + + public @Nullable Material getMaterial() { + return material; + } +} diff --git a/lophine-server/minecraft-patches/features/0020-Leaves-Fakeplayer.patch b/lophine-server/minecraft-patches/features/0020-Leaves-Fakeplayer.patch new file mode 100644 index 0000000..1275e92 --- /dev/null +++ b/lophine-server/minecraft-patches/features/0020-Leaves-Fakeplayer.patch @@ -0,0 +1,821 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bacteriawa +Date: Sat, 30 Aug 2025 17:16:32 +0800 +Subject: [PATCH] Leaves Fakeplayer + + +diff --git a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +index a82d84283632342bd30bc3449983431ba43583e0..f59526f6bfa1b4af5b474f0b438513c96afb491c 100644 +--- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -39,6 +39,7 @@ public abstract class SimpleCriterionTrigger testTrigger) { ++ if (player instanceof org.leavesmc.leaves.bot.ServerBot) return; // Leaves - bot skip + PlayerAdvancements advancements = player.getAdvancements(); + Set> set = (Set) advancements.criterionData.get(this); // Paper - fix PlayerAdvancements leak + if (set != null && !set.isEmpty()) { +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index 66ec0424a46dcd49cf44467357d80b1a2d84d3b2..04ae8de63af0a8abe578f14c8ef85fd4beca0969 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -96,7 +96,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/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index af13707933af14171447d5ab6faacb64ddf194a7..e5e3a9c6443019d31496b1cf2154f5c3eaf6f972 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -348,6 +348,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system + AtomicReference atomicReference = new AtomicReference<>(); +@@ -1066,6 +1068,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Folia - region threading - this is complicated to implement, and even if done correctly is messy +- if (this.playerList.getPlayerCount() == 0 && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping ++ if (this.playerList.getPlayerCount() == 0 && this.botList.bots.isEmpty() && !this.tickRateManager.isSprinting() && this.pluginsBlockingSleep.isEmpty()) { // Paper - API to allow/disallow tick sleeping // Leaves - fakeplayer + this.emptyTicks++; + } else { + this.emptyTicks = 0; +@@ -1912,6 +1915,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop gameProfiles, @Nullable Component reason) throws CommandSyntaxException { + UserBanList bans = source.getServer().getPlayerList().getBans(); + int i = 0; +- ++ boolean hasBot = false; // Leaves - disable kick + for (GameProfile gameProfile : gameProfiles) { ++ // Leaves start - disable ban ++ if (gameProfile instanceof org.leavesmc.leaves.bot.BotList.CustomGameProfile) { ++ source.sendFailure(Component.literal("Permission denied")); ++ hasBot = true; ++ continue; ++ } ++ // Leaves end - disable ban ++ ServerPlayer player = source.getServer().getPlayerList().getPlayer(gameProfile.getId()); + if (!bans.isBanned(gameProfile)) { + UserBanListEntry userBanListEntry = new UserBanListEntry( + gameProfile, null, source.getTextName(), null, reason == null ? null : reason.getString() +@@ -55,7 +63,6 @@ public class BanPlayerCommands { + source.sendSuccess( + () -> Component.translatable("commands.ban.success", Component.literal(gameProfile.getName()), userBanListEntry.getReason()), true + ); +- ServerPlayer player = source.getServer().getPlayerList().getPlayer(gameProfile.getId()); + if (player != null) { + player.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause + } +@@ -63,7 +70,13 @@ public class BanPlayerCommands { + } + + if (i == 0) { +- throw ERROR_ALREADY_BANNED.create(); ++ // Leaves start - disable kick ++ if (hasBot) { ++ return i; ++ } else { ++ throw ERROR_ALREADY_BANNED.create(); ++ } ++ // Leaves end - disable kick + } else { + return i; + } +diff --git a/net/minecraft/server/commands/KickCommand.java b/net/minecraft/server/commands/KickCommand.java +index 14e2e0fcf20c8fa875bbefb97a673be4928d099a..8af40a77dc3da1599da2793168488f88686b8785 100644 +--- a/net/minecraft/server/commands/KickCommand.java ++++ b/net/minecraft/server/commands/KickCommand.java +@@ -46,9 +46,17 @@ public class KickCommand { + if (!source.getServer().isPublished()) { + throw ERROR_SINGLEPLAYER.create(); + } else { ++ boolean hasBot = false; // Leaves - disable kick + int i = 0; + + for (ServerPlayer serverPlayer : players) { ++ // Leaves start - disable kick ++ if (serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot) { ++ source.sendFailure(Component.literal("Permission denied")); ++ hasBot = true; ++ continue; ++ } ++ // Leaves end - disable kick + if (!source.getServer().isSingleplayerOwner(serverPlayer.getGameProfile())) { + serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause + source.sendSuccess(() -> Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); +@@ -57,7 +65,13 @@ public class KickCommand { + } + + if (i == 0) { +- throw ERROR_KICKING_OWNER.create(); ++ // Leaves start - disable kick ++ if (hasBot) { ++ return i; ++ } else { ++ throw ERROR_KICKING_OWNER.create(); ++ } ++ // Leaves end - disable kick + } else { + return i; + } +diff --git a/net/minecraft/server/commands/OpCommand.java b/net/minecraft/server/commands/OpCommand.java +index f2286b96b8f40b4588f817913c42ae7b4a92340f..e6c7bbb023000b9de90c1256274ff5aba4a6478a 100644 +--- a/net/minecraft/server/commands/OpCommand.java ++++ b/net/minecraft/server/commands/OpCommand.java +@@ -43,6 +43,7 @@ public class OpCommand { + int i = 0; + + for (GameProfile gameProfile : gameProfiles) { ++ if (gameProfile instanceof org.leavesmc.leaves.bot.BotList.CustomGameProfile) continue; // Leaves - disable op + if (!playerList.isOp(gameProfile)) { + playerList.op(gameProfile); + i++; +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 79c911003c7b1c3d94e7e9d3974eeadcc16bde3a..328428f0ef8a2305f661c167e6fe1f604b0d6890 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -162,6 +162,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + // Spigot start ++ this.setBotList(new org.leavesmc.leaves.bot.BotList(this)); // Leaves - fakeplayer + this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); + org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); +@@ -173,6 +174,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + // Paper end - initialize global and world-defaults configuration + me.earthme.luminol.config.ConfigManager.loadConfigFiles(); // Luminol - load config file + fun.bm.lophine.utils.ServerI18nUtil.init(); // Lophine - I18n support ++ this.getBotList().loadBotInfo(); // Leaves - load resident bot info + if (false) this.server.spark.enableEarlyIfRequested(); // Paper - spark // Luminol - Force disable builtin spark + // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save + if (this.convertOldUsers()) { +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 70740381c6501c1a518c52b24381edd16792507f..5e31b499a894113b4be1982a1071348fcbaf9ded 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -134,7 +134,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final AtomicInteger tickingGenerated = new AtomicInteger(); // Paper - public + private final String storageName; + //private final PlayerMap playerMap = new PlayerMap(); // Folia - region threading +- //public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading ++ public final Int2ObjectMap entityMap = new Int2ObjectOpenHashMap<>(); // Folia - region threading + private final Long2ByteMap chunkTypeCache = new Long2ByteOpenHashMap(); + // Paper - rewrite chunk system + public int serverViewDistance; +@@ -1309,6 +1309,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } else if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); ++ // Leaves start - render bot ++ if (entity instanceof org.leavesmc.leaves.bot.ServerBot bot) { ++ if (bot.needSendFakeData(player)) { ++ bot.sendFakeData(player.connection, false); ++ } ++ } ++ // Leaves end - render bot + } + } + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 576dde41a4b48aac9079cca902f4371a31470af3..952b92c55d7192791a19ecebe80894f6cf992049 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -207,6 +207,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final StructureCheck structureCheck; + public final boolean tickTime; // Folia - region threading + private final RandomSequences randomSequences; ++ final List realPlayers; // Leaves - skip + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess levelStorageAccess; +@@ -635,6 +636,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - rewrite chunk system + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked ++ this.realPlayers = Lists.newArrayList(); // Leaves - skip + } + + // Folia start - region threading +@@ -2389,6 +2391,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.players; + } + ++ // Leaves start - fakeplayer skip ++ public List realPlayers() { ++ return this.realPlayers; ++ } ++ // Leaves end - fakeplayer skip ++ + @Override + public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { + Optional> optional = PoiTypes.forState(oldState); +@@ -2826,6 +2834,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.add(serverPlayer); ++ // Leaves start - skip ++ if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot)) { ++ ServerLevel.this.realPlayers.add(serverPlayer); ++ } ++ // Leaves end - skip + if (serverPlayer.isReceivingWaypoints()) { + ServerLevel.this.getWaypointManager().addPlayer(serverPlayer); + } +@@ -2913,6 +2926,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ServerLevel.this.getChunkSource().removeEntity(entity); + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.remove(serverPlayer); ++ // Leaves start - skip ++ if (!(serverPlayer instanceof org.leavesmc.leaves.bot.ServerBot)) { ++ ServerLevel.this.realPlayers.remove(serverPlayer); ++ } ++ // Leaves end - skip + ServerLevel.this.getWaypointManager().removePlayer(serverPlayer); + ServerLevel.this.updateSleepingPlayerList(); + } +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index c29e9c6cb2513fe4b01a619e16433893efb44e6f..58ffb996e0f8dce56c4def265dc51ed49ee31df4 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -219,7 +219,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private static final boolean DEFAULT_SPAWN_EXTRA_PARTICLES_ON_FALL = false; + public ServerGamePacketListenerImpl connection; + private final MinecraftServer server; +- public final ServerPlayerGameMode gameMode; ++ public ServerPlayerGameMode gameMode; // Leaves - not final + private final PlayerAdvancements advancements; + private final ServerStatsCounter stats; + private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; +@@ -232,7 +232,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private int lastSentFood = -99999999; + private boolean lastFoodSaturationZero = true; + public int lastSentExp = -99999999; +- private int spawnInvulnerableTime = 60; // Lophine - spawn invulnerable time ++ public int spawnInvulnerableTime = 60; // Lophine - spawn invulnerable time + private ChatVisiblity chatVisibility = ChatVisiblity.FULL; + public ParticleStatus particleStatus = ParticleStatus.ALL; + private boolean canChatColor = true; +@@ -241,7 +241,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + private Entity camera; + public boolean isChangingDimension; + public boolean seenCredits = false; +- private final ServerRecipeBook recipeBook; ++ protected ServerRecipeBook recipeBook; // Leaves - not final and private -> protected + @Nullable + private Vec3 levitationStartPos; + private int levitationStartTime; +@@ -436,6 +436,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + // Paper start - rewrite chunk system + private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; + private final ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder viewDistanceHolder = new ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.ViewDistanceHolder(); ++ // Leaves start - player operation limiter ++ private int instaBreakCountPerTick = 0; ++ private int placeBlockCountPerTick = 0; ++ // Leaves end - player operation limiter + + @Override + public final boolean moonrise$isRealPlayer() { +@@ -924,6 +928,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + // CraftBukkit end + this.tickClientLoadTimeout(); ++ this.resetOperationCountPerTick(); // Leaves - player operation limiter + this.gameMode.tick(); + this.wardenSpawnTracker.tick(); + if (this.spawnInvulnerableTime > 0) --this.spawnInvulnerableTime; // Lophine - spawn invulnerable time +@@ -2029,6 +2034,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; + this.teleportSpectators(teleportTransition, serverLevel); ++ // Leaves start - bot support ++ if (fun.bm.lophine.config.modules.misc.FakeplayerConfig.enable) { ++ this.server.getBotList().bots.forEach(bot -> bot.sendFakeDataIfNeed(this, true)); // Leaves - render bot ++ } ++ // Leaves end - bot support + // CraftBukkit start + org.bukkit.event.player.PlayerChangedWorldEvent changeEvent = new org.bukkit.event.player.PlayerChangedWorldEvent(this.getBukkitEntity(), serverLevel.getWorld()); + this.level().getCraftServer().getPluginManager().callEvent(changeEvent); +@@ -3577,4 +3587,31 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity(); + } + // CraftBukkit end ++ ++ // Leaves start - player operation limiter ++ protected void resetOperationCountPerTick() { ++ instaBreakCountPerTick = 0; ++ placeBlockCountPerTick = 0; ++ } ++ ++ public int getInstaBreakCountPerTick() { ++ return instaBreakCountPerTick; ++ } ++ ++ public int getPlaceBlockCountPerTick() { ++ return placeBlockCountPerTick; ++ } ++ ++ public void addInstaBreakCountPerTick() { ++ ++instaBreakCountPerTick; ++ } ++ ++ public void addPlaceBlockCountPerTick() { ++ ++placeBlockCountPerTick; ++ } ++ ++ public boolean allowOperation() { ++ return (instaBreakCountPerTick == 0 || placeBlockCountPerTick == 0) && (instaBreakCountPerTick <= 1 && placeBlockCountPerTick <= 2); ++ } ++ // Leaves end - player operation limiter + } +diff --git a/net/minecraft/server/players/GameProfileCache.java b/net/minecraft/server/players/GameProfileCache.java +index 144a2644c15f276f02bb3be859dc5d05a677ac55..c6a89e8936cffa49b3de153ed3c40e3cc9b8ae61 100644 +--- a/net/minecraft/server/players/GameProfileCache.java ++++ b/net/minecraft/server/players/GameProfileCache.java +@@ -125,6 +125,12 @@ public class GameProfileCache { + // Paper end + + public Optional get(String name) { ++ // Leaves start - fix bot ++ org.leavesmc.leaves.bot.ServerBot bot = org.leavesmc.leaves.bot.BotList.INSTANCE.getBotByName(name); ++ if (bot != null) { ++ return Optional.of(bot.getGameProfile()); ++ } ++ // Leaves end - fix bot + String string = name.toLowerCase(Locale.ROOT); + boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByName.get(string); +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index cb863a36fc67e993956ec3be655e6e4b9764c051..cd50b39a910f6a4bc07a4065cc4e6a84ab1e8750 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -425,6 +425,19 @@ public abstract class PlayerList { + + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); + ++ // Leaves start - bot support ++ if (fun.bm.lophine.config.modules.misc.FakeplayerConfig.enable) { ++ org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); ++ if (bot != null) { ++ this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false); ++ } ++ this.server.getBotList().bots.forEach(bot1 -> { ++ bot1.sendPlayerInfo(player); ++ bot1.sendFakeDataIfNeed(player, true); ++ }); // Leaves - render bot ++ } ++ // Leaves end - bot support ++ + final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); + + if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure +@@ -922,6 +935,12 @@ public abstract class PlayerList { + } + // Paper end - Add PlayerPostRespawnEvent + ++ // Leaves start - bot support ++ if (fun.bm.lophine.config.modules.misc.FakeplayerConfig.enable) { ++ this.server.getBotList().bots.forEach(bot -> bot.sendFakeDataIfNeed(serverPlayer, true)); // Leaves - render bot ++ } ++ // Leaves end - bot support ++ + // CraftBukkit end + + return serverPlayer; +@@ -1026,11 +1045,16 @@ public abstract class PlayerList { + + public String[] getPlayerNamesArray() { + List players = new java.util.ArrayList<>(this.players); // Folia - region threading +- String[] strings = new String[this.players.size()]; ++ String[] strings = new String[this.players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support + + for (int i = 0; i < players.size(); i++) { // Folia - region threading + strings[i] = players.get(i).getGameProfile().getName(); // Folia - region threading + } ++ // Leaves start - fakeplayer support ++ for (int i = this.players.size(); i < strings.length; ++i) { ++ strings[i] = this.server.getBotList().bots.get(i - this.players.size()).getGameProfile().getName(); ++ } ++ // Leaves end - fakeplayer support + + return strings; + } +@@ -1141,7 +1165,14 @@ public abstract class PlayerList { + + @Nullable + public ServerPlayer getPlayerByName(String username) { +- return this.playersByName.get(username.toLowerCase(java.util.Locale.ROOT)); // Spigot ++ // Leaves start - fakeplayer support ++ username = username.toLowerCase(java.util.Locale.ROOT); ++ ServerPlayer player = this.playersByName.get(username); ++ if (player == null) { ++ player = this.server.getBotList().getBotByName(username); ++ } ++ return player; // Spigot ++ // Leaves end - fakeplayer support + } + + public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey dimension, Packet packet) { +@@ -1482,7 +1513,13 @@ public abstract class PlayerList { + + @Nullable + public ServerPlayer getPlayer(UUID playerUUID) { +- return this.playersByUUID.get(playerUUID); ++ // Leaves start - fakeplayer support ++ ServerPlayer player = this.playersByUUID.get(playerUUID); ++ if (player == null) { ++ player = this.server.getBotList().getBot(playerUUID); ++ } ++ return player; ++ // Leaves start - fakeplayer support + } + + public boolean canBypassPlayerLimit(GameProfile profile) { +diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java +index 36e9b027ce5213f8914728293633e78e9905c1b2..a8c2a93cc12bd5aa6760bed91baa680f3801ee5e 100644 +--- a/net/minecraft/server/waypoints/ServerWaypointManager.java ++++ b/net/minecraft/server/waypoints/ServerWaypointManager.java +@@ -22,6 +22,11 @@ public class ServerWaypointManager implements WaypointManager public + final boolean xZero = movement.x == 0.0; + final boolean yZero = movement.y == 0.0; + final boolean zZero = movement.z == 0.0; +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index a8348a073682eec31990e9ab963b11ad32cf8bf8..8ce6f3f1a931628e018ce9a2e0c404b8c8a4d992 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -3212,7 +3212,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + private void travelRidden(Player player, Vec3 travelVector) { + Vec3 riddenInput = this.getRiddenInput(player, travelVector); + this.tickRidden(player, riddenInput); +- if (this.canSimulateMovement()) { ++ if (this.canSimulateMovement() || this.getControllingPassenger() instanceof org.leavesmc.leaves.bot.ServerBot) { // Leaves - Fakeplayer + this.setSpeed(this.getRiddenSpeed(player)); + this.travel(riddenInput); + } else { +@@ -4046,7 +4046,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + // Paper start - lag compensate eating + // we add 1 to the expected time to avoid lag compensating when we should not + final boolean shouldLagCompensate = this.useItem.has(DataComponents.FOOD) && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1L + this.totalEatTimeTicks) * 50L * (1000L * 1000L)); +- if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level().isClientSide && !usingItem.useOnRelease()) { ++ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !(this instanceof org.leavesmc.leaves.bot.ServerBot) && !this.level().isClientSide && !usingItem.useOnRelease()) { // Leaves - Fakeplayer skip this check + this.useItemRemaining = 0; + // Paper end - lag compensate eating + this.completeUsingItem(); +@@ -4223,6 +4223,23 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + this.stopUsingItem(); + } + ++ // Leaves start - Fakeplayer ++ public boolean releaseUsingItemWithResult() { ++ ItemStack itemInHand = this.getItemInHand(this.getUsedItemHand()); ++ boolean result = false; ++ if (!this.useItem.isEmpty() && ItemStack.isSameItem(itemInHand, this.useItem)) { ++ this.useItem = itemInHand; ++ result = this.useItem.releaseUsingWithResult(this.level(), this, this.getUseItemRemainingTicks()); ++ if (this.useItem.useOnRelease()) { ++ this.updatingUsingItem(); ++ } ++ } ++ ++ this.stopUsingItem(); ++ return result; ++ } ++ // Leaves end - Fakeplayer ++ + public void stopUsingItem() { + if (!this.level().isClientSide) { + boolean isUsingItem = this.isUsingItem(); +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index 6c933c27ce155eb516a9ede96fb2c3bd19251bfa..6e0784af90aab5c25100613f97d4492fa7d1b277 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -205,7 +205,7 @@ public abstract class Player extends LivingEntity { + private int lastLevelUpTime; + public GameProfile gameProfile; + private boolean reducedDebugInfo; +- private ItemStack lastItemInMainHand = ItemStack.EMPTY; ++ protected ItemStack lastItemInMainHand = ItemStack.EMPTY; + private final ItemCooldowns cooldowns = this.createItemCooldowns(); + private Optional lastDeathLocation = Optional.empty(); + @Nullable +@@ -412,6 +412,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(); +@@ -714,7 +720,7 @@ public abstract class Player extends LivingEntity { + } + } + +- private void touch(Entity entity) { ++ public void touch(Entity entity) { // Leaves - private -> public + entity.playerTouch(this); + } + +@@ -1339,7 +1345,7 @@ public abstract class Player extends LivingEntity { + this.sweepAttack(); + } + +- if (target instanceof ServerPlayer && target.hurtMarked) { ++ if ((target instanceof ServerPlayer && !(target instanceof org.leavesmc.leaves.bot.ServerBot)) && target.hurtMarked) { // Leaves - bot knockback + // CraftBukkit start - Add Velocity Event + boolean cancelled = false; + org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity(); +diff --git a/net/minecraft/world/entity/projectile/FishingHook.java b/net/minecraft/world/entity/projectile/FishingHook.java +index 7760ab1fe365b4bf1ab6c2ff953e8659a28008a8..76f6b0932633bc5670a1cdc76ff78b3bc725401d 100644 +--- a/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/net/minecraft/world/entity/projectile/FishingHook.java +@@ -58,7 +58,7 @@ public class FishingHook extends Projectile { + public static final EntityDataAccessor DATA_HOOKED_ENTITY = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_BITING = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.BOOLEAN); + private int life; +- private int nibble; ++ public int nibble; // Leaves - private -> public + public int timeUntilLured; + public int timeUntilHooked; + public float fishAngle; +diff --git a/net/minecraft/world/entity/vehicle/AbstractBoat.java b/net/minecraft/world/entity/vehicle/AbstractBoat.java +index 91ac54fb0ae9a06945476cbd1caa20be32c4a9fe..be380db0cb2f243b4a3cf8bd1b06d38debc5f6fd 100644 +--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java ++++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java +@@ -271,6 +271,11 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { + } + + this.move(MoverType.SELF, this.getDeltaMovement()); ++ } else if (this.getControllingPassenger() instanceof org.leavesmc.leaves.bot.ServerBot) { // Leaves start - Fakeplayer ++ this.floatBoat(); ++ this.controlBoat(); ++ this.move(MoverType.SELF, this.getDeltaMovement()); ++ // Leaves end - Fakeplayer + } else { + this.setDeltaMovement(Vec3.ZERO); + } +@@ -383,6 +388,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { + } + } + ++ // Leaves start - Fakeplayer ++ @Override ++ public boolean canSimulateMovement() { ++ return super.canSimulateMovement() || this.getControllingPassenger() instanceof org.leavesmc.leaves.bot.ServerBot; ++ } ++ // Leaves end - Fakeplayer ++ + @Nullable + protected SoundEvent getPaddleSound() { + return switch (this.getStatus()) { +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index bdc0ca1f625dc264bef6e5722ec66121ba48625a..525610be29d5077a3199287347b1b71cf9fd8abf 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -407,6 +407,7 @@ public abstract class AbstractContainerMenu { + + private void doClick(int slotId, int button, ClickType clickType, Player player) { + Inventory inventory = player.getInventory(); ++ if (!doClickCheck(slotId, button, clickType, player)) return; // Leaves - doClick check + if (clickType == ClickType.QUICK_CRAFT) { + int i = this.quickcraftStatus; + this.quickcraftStatus = getQuickcraftHeader(button); +@@ -685,6 +686,22 @@ public abstract class AbstractContainerMenu { + } + } + ++ // Leaves start - doClick check ++ private boolean doClickCheck(int slotIndex, int button, ClickType actionType, Player player) { ++ if (slotIndex < 0) { ++ return true; ++ } ++ ++ Slot slot = getSlot(slotIndex); ++ ItemStack itemStack = slot.getItem(); ++ net.minecraft.world.item.component.CustomData customData = itemStack.get(net.minecraft.core.component.DataComponents.CUSTOM_DATA); ++ if (customData != null && customData.contains("Leaves.Gui.Placeholder")) { ++ return !customData.copyTag().getBoolean("Leaves.Gui.Placeholder").orElse(false); ++ } ++ return true; ++ } ++ // Leaves end - doClick check ++ + private boolean tryItemClickBehaviourOverride(Player player, ClickAction action, Slot slot, ItemStack clickedItem, ItemStack carriedItem) { + FeatureFlagSet featureFlagSet = player.level().enabledFeatures(); + return carriedItem.isItemEnabled(featureFlagSet) && carriedItem.overrideStackedOnOther(slot, action, player) +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index a27e1d53abc2d8b8912226add33510067e10ed52..be0a267e5564a30c16f408cb311a448f354d60b4 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -450,7 +450,7 @@ public final class ItemStack implements DataComponentHolder, ChangePublisher= 72000) { + BlockPos blockPos1 = blockPos.above(20 + randomSource.nextInt(15)) + .east(-10 + randomSource.nextInt(21)) +diff --git a/net/minecraft/world/level/storage/LevelResource.java b/net/minecraft/world/level/storage/LevelResource.java +index bef794c3f58c41d910aa0bcc63fbdeea7225fddf..a601da588e6973cc5b87d3e3eeba49b53f6d9a6d 100644 +--- a/net/minecraft/world/level/storage/LevelResource.java ++++ b/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 id) { ++ public LevelResource(String id) { // Leaves - private -> public + this.id = id; + } + +diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java +index fe44d8d17d2622b3d6021c11579af85ef96737bb..0aae211dc2048f8cd14213c2a868394d1ed16070 100644 +--- a/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -19,7 +19,7 @@ import net.minecraft.util.datafix.DataFixTypes; + import net.minecraft.world.entity.player.Player; + 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; + protected final DataFixer fixerUpper; diff --git a/lophine-server/paper-patches/features/0003-Leaves-Leaves-Protocol-Core.patch b/lophine-server/paper-patches/features/0003-Leaves-Leaves-Protocol-Core.patch index adda780..02b387d 100644 --- a/lophine-server/paper-patches/features/0003-Leaves-Leaves-Protocol-Core.patch +++ b/lophine-server/paper-patches/features/0003-Leaves-Leaves-Protocol-Core.patch @@ -8,7 +8,7 @@ As a part of : Leaves (https://github.com/LeavesMC/Leaves/blob/ea91106ae57fc4cc1 Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4f7077b592f84334483f8edf3656fccd5b90a9ca..26d5a96edd283a8f8fd50222e2671cef2c4b257b 100644 +index 5d05a8a843e605b6cb6d16883d17dd73d4c21e06..57ea85aebc72eb002d3dcf04144499feeb82081a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -497,6 +497,7 @@ public final class CraftServer implements Server { diff --git a/lophine-server/paper-patches/features/0004-Leaves-Fakeplayer.patch b/lophine-server/paper-patches/features/0004-Leaves-Fakeplayer.patch new file mode 100644 index 0000000..bf97012 --- /dev/null +++ b/lophine-server/paper-patches/features/0004-Leaves-Fakeplayer.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bacteriawa +Date: Sat, 30 Aug 2025 17:07:02 +0800 +Subject: [PATCH] Leaves Fakeplayer + + +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 9180f23529a31b6b0a5b38bb7cda3e32d487f691..5a047006895cf6e0d58502f9502d1b49abaf84bf 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -47,6 +47,23 @@ class PaperEventManager { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + ++ // Leaves start - skip bot ++ if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.bot.Bot) { ++ return; ++ } ++ // Leaves end - skip bot ++ ++ // Leaves start - process bot load/save ++ if (fun.bm.lophine.config.modules.misc.FakeplayerConfig.enable && fun.bm.lophine.config.modules.misc.FakeplayerConfig.canResident) { ++ if (event instanceof org.bukkit.event.world.WorldLoadEvent worldLoadEvent) { ++ org.leavesmc.leaves.bot.BotList.INSTANCE.loadResume(worldLoadEvent.getWorld().getUID().toString()); ++ } else if (event instanceof org.bukkit.event.world.WorldUnloadEvent worldUnloadEvent) { ++ org.leavesmc.leaves.bot.BotList.INSTANCE.removeAllIn(worldUnloadEvent.getWorld().getUID().toString()); ++ } ++ } ++ // Leaves end - process bot load/save ++ ++ + for (RegisteredListener registration : listeners) { + if (!registration.getPlugin().isEnabled()) { + continue; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 0a10f49ee410d93e95ceb90108200a1a9d12b54b..d2eee37d810a6d5cf514bc71dea66a4d2b0d09e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -423,6 +423,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + @SuppressWarnings("unchecked") + public T addEntity(T entity) { + Preconditions.checkArgument(!entity.isInWorld(), "Entity has already been added to a world"); ++ Preconditions.checkState(!(entity instanceof org.leavesmc.leaves.entity.bot.CraftBot), "[Leaves] Fakeplayers do not support changing world, Please use leaves fakeplayer-api instead!"); + net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle(); + if (nmsEntity.level() != this.getHandle().getLevel()) { + nmsEntity = nmsEntity.teleport(new TeleportTransition(this.getHandle().getLevel(), nmsEntity, TeleportTransition.DO_NOTHING)); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 57ea85aebc72eb002d3dcf04144499feeb82081a..e03de696e7652b7e211bf61ff6d6cdf44a587146 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -316,6 +316,7 @@ public final class CraftServer implements Server { + private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; + public io.papermc.paper.SparksFly spark; // Paper - spark // Luminol - Force disable builtin spark + private final ServerConfiguration serverConfig = new PaperServerConfiguration(); ++ private final org.leavesmc.leaves.entity.bot.CraftBotManager botManager; // Leaves + + // Paper start - Folia region threading API + private final io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaRegionScheduler(); // Folia - region threading +@@ -498,6 +499,7 @@ public final class CraftServer implements Server { + datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + if (false) this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark // Luminol - Force disable builtin spark + org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol ++ this.botManager = new org.leavesmc.leaves.entity.bot.CraftBotManager(); // Leaves + } + + public boolean getCommandBlockOverride(String command) { +@@ -1482,7 +1484,7 @@ public final class CraftServer implements Server { + return false; + } + +- if (!handle.players().isEmpty()) { ++ if (!handle.realPlayers().isEmpty()) { // Leaves - skip + return false; + } + +@@ -3260,6 +3262,13 @@ public final class CraftServer implements Server { + this.console.addPluginAllowingSleep(plugin.getName(), value); + } + ++ // Leaves start - Bot API ++ @Override ++ public org.leavesmc.leaves.entity.bot.CraftBotManager getBotManager() { ++ return botManager; ++ } ++ // Leaves end - Bot API ++ + // Folia start - region TPS API + /** + * Gets the TPS from the region which owns the specified location, or {@code null} if no region owns +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9b9eea4babcd67a088e9400b369d168d11c06ac1..3177db282013e6dc3c7215fc2a851e2630a29409 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -249,7 +249,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getPlayerCount() { +- return world.players().size(); ++ return world.realPlayers().size(); // Leaves - skip + } + + @Override +@@ -1279,9 +1279,9 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public List getPlayers() { +- List list = new ArrayList(this.world.players().size()); ++ List list = new ArrayList(this.world.realPlayers().size()); // Leaves - skip + +- for (net.minecraft.world.entity.player.Player human : this.world.players()) { ++ for (net.minecraft.world.entity.player.Player human : this.world.realPlayers()) { // Leaves - skip + HumanEntity bukkitEntity = human.getBukkitEntity(); + + if ((bukkitEntity != null) && (bukkitEntity instanceof Player)) { +@@ -1988,7 +1988,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public void playSound(final net.kyori.adventure.sound.Sound sound) { + org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + final long seed = sound.seed().orElseGet(this.world.getRandom()::nextLong); +- for (ServerPlayer player : this.getHandle().players()) { ++ for (ServerPlayer player : this.getHandle().realPlayers()) { // Leaves - skip + player.connection.send(io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, player.getX(), player.getY(), player.getZ(), seed, null)); + } + } +@@ -2016,7 +2016,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper + final long seed = sound.seed().orElseGet(this.getHandle().getRandom()::nextLong); + if (emitter == net.kyori.adventure.sound.Sound.Emitter.self()) { +- for (ServerPlayer player : this.getHandle().players()) { ++ for (ServerPlayer player : this.getHandle().realPlayers()) { // Leaves - skip + player.connection.send(io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, player, seed, null)); + } + } else if (emitter instanceof CraftEntity craftEntity) { +@@ -2248,7 +2248,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); + } + this.getHandle().sendParticlesSource( +- receivers == null ? this.getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API ++ receivers == null ? this.getHandle().realPlayers() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API // Leaves - skip + sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API + CraftParticle.createParticleParam(particle, data), // Particle + force, +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 852e1ffef6a022caad7c8eff34091e50112a2290..8eb5d014d9ed688ffebaffb4ce0bb40896961a04 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -126,6 +126,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity); + } + ++ if (entity instanceof org.leavesmc.leaves.bot.ServerBot bot) { return new org.leavesmc.leaves.entity.bot.CraftBot(server, bot); } ++ + // Special case complex part, since there is no extra entity type for them + if (entity instanceof EnderDragonPart complexPart) { + if (complexPart.parentMob instanceof EnderDragon) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index ffb10b412d19907e6f63b7e3ca67ef061eec96ba..de14df49b11f1243de9aa6870a3c8e3f429502fd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -897,7 +897,11 @@ public class CraftEventFactory { + event.setKeepInventory(keepInventory); + event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel + populateFields(victim, event); // Paper - make cancellable +- Bukkit.getServer().getPluginManager().callEvent(event); ++ // Leaves start - disable bot death event ++ if (!(victim instanceof org.leavesmc.leaves.bot.ServerBot)) { ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ } ++ // Leaves end - disable bot death event + // Paper start - make cancellable + if (event.isCancelled()) { + return event; diff --git a/lophine-server/src/main/java/fun/bm/lophine/config/modules/misc/FakeplayerConfig.java b/lophine-server/src/main/java/fun/bm/lophine/config/modules/misc/FakeplayerConfig.java new file mode 100644 index 0000000..b712c07 --- /dev/null +++ b/lophine-server/src/main/java/fun/bm/lophine/config/modules/misc/FakeplayerConfig.java @@ -0,0 +1,108 @@ +package fun.bm.lophine.config.modules.misc; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import me.earthme.luminol.config.IConfigModule; +import me.earthme.luminol.config.flags.ConfigClassInfo; +import me.earthme.luminol.config.flags.ConfigInfo; +import me.earthme.luminol.enums.EnumConfigCategory; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.leavesmc.leaves.bot.BotCommand; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.Actions; + +import java.util.List; +import java.util.Locale; + +@ConfigClassInfo(configAttribution = EnumConfigCategory.MISC, mainName = "fakeplayer") +public class FakeplayerConfig implements IConfigModule { + @ConfigInfo(baseName = "enable", comments = """ + Enable fakeplayer functionality""") + public static boolean enable = true; + + @ConfigInfo(baseName = "unable-fakeplayer-names", comments = """ + List of names that cannot be used for fakeplayers""") + public static List unableNames = List.of("player-name"); + + @ConfigInfo(baseName = "limit", comments = """ + Maximum number of fakeplayers allowed""") + public static int limit = 10; + + @ConfigInfo(baseName = "prefix", comments = """ + Prefix for fakeplayer names""") + public static String prefix = ""; + + @ConfigInfo(baseName = "suffix", comments = """ + Suffix for fakeplayer names""") + public static String suffix = ""; + + @ConfigInfo(baseName = "regen-amount", comments = """ + Regeneration amount for fakeplayers""") + public static double regenAmount = 0.0; + + @ConfigInfo(baseName = "resident-fakeplayer", comments = """ + Allow fakeplayers to be resident""") + public static boolean canResident = false; + + @ConfigInfo(baseName = "open-fakeplayer-inventory", comments = """ + Allow opening fakeplayer inventory""") + public static boolean canOpenInventory = false; + + @ConfigInfo(baseName = "use-action", comments = """ + Allow fakeplayers to use actions""") + public static boolean canUseAction = true; + + @ConfigInfo(baseName = "modify-config", comments = """ + Allow modifying fakeplayer config""") + public static boolean canModifyConfig = false; + + @ConfigInfo(baseName = "manual-save-and-load", comments = """ + Allow manual save and load of fakeplayers""") + public static boolean canManualSaveAndLoad = false; + + @ConfigInfo(baseName = "cache-skin", comments = """ + Use skin cache for fakeplayers""") + public static boolean useSkinCache = false; + + @ConfigInfo(baseName = "always-send-data", comments = """ + Always send data for fakeplayers""") + public static boolean canSendDataAlways = true; + + @ConfigInfo(baseName = "skip-sleep-check", comments = """ + Skip sleep check for fakeplayers""") + public static boolean canSkipSleep = false; + + @ConfigInfo(baseName = "spawn-phantom", comments = """ + Allow phantoms to spawn for fakeplayers""") + public static boolean canSpawnPhantom = false; + + @ConfigInfo(baseName = "simulation-distance", comments = """ + Simulation distance for fakeplayers (-1 for default)""") + public static int simulationDistance = -1; + + @ConfigInfo(baseName = "enable-locator-bar", comments = """ + Enable locator bar for fakeplayers""") + public static boolean enableLocatorBar = false; + + public static ServerBot.TickType tickType = ServerBot.TickType.ENTITY_LIST; + + public static void unregisterCommand(String name) { + name = name.toLowerCase(Locale.ENGLISH).trim(); + MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove(name); + MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove("leaves:" + name); + MinecraftServer.getServer().server.syncCommands(); + } + + public static int getSimulationDistance(ServerBot bot) { + return simulationDistance == -1 ? bot.getBukkitEntity().getSimulationDistance() : simulationDistance; + } + + public void onLoaded(CommentedFileConfig configInstance) { + if (enable) { + Bukkit.getCommandMap().register("bot", "lophine", new BotCommand()); + Actions.registerAll(); + } else { + unregisterCommand("bot"); + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java new file mode 100644 index 0000000..67ad9aa --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCommand.java @@ -0,0 +1,486 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.logging.LogUtils; +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.bot.Bot; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.event.bot.BotConfigModifyEvent; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; +import org.slf4j.Logger; + +import java.util.*; + +import static net.kyori.adventure.text.Component.text; + +public class BotCommand extends Command { + private static final Logger LOGGER = LogUtils.getLogger(); + public BotCommand() { + super("bot"); + this.setPermission("lophine.bot"); + this.setDescription("FakePlayer Command"); + this.setUsage("/bot [args...]"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + if (!testPermission(sender)) { + return true; + } + + if (!FakeplayerConfig.enable) { + sender.sendMessage(Component.text("Fakeplayer feature is disabled!").color(TextColor.color(255, 0, 0))); + return true; + } + + if (args.length == 0) { + sender.sendMessage(Component.text("Usage: /bot [args...]").color(TextColor.color(255, 255, 0))); + return true; + } + + String subCommand = args[0].toLowerCase(); + switch (subCommand) { + case "create" -> { + return handleCreate(sender, args); + } + case "remove" -> { + return handleRemove(sender, args); + } + case "list" -> { + return handleList(sender, args); + } + case "action" -> { + return handleAction(sender, args); + } + case "config" -> { + return handleConfig(sender, args); + } + case "save" -> { + return handleSave(sender, args); + } + case "load" -> { + return handleLoad(sender, args); + } + default -> { + sender.sendMessage(Component.text("Unknown subcommand: " + subCommand).color(TextColor.color(255, 0, 0))); + sender.sendMessage(Component.text("Available commands: create, remove, list, action, config, save, load").color(TextColor.color(255, 255, 0))); + } + } + + return true; + } + + private boolean handleCreate(CommandSender sender, String[] args) { + if (args.length < 2) { + sender.sendMessage(text("Use /bot create [skin_name] to create a fakeplayer", NamedTextColor.RED)); + return true; + } + + String botName = args[1]; + String fullName = BotUtil.getFullName(botName); + if (this.canCreate(sender, fullName)) { + BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()) + .createReason(BotCreateEvent.CreateReason.COMMAND) + .creator(sender); + + if (args.length >= 3) { + builder.skinName(args[2]); + } + + if (sender instanceof Player player) { + builder.location(player.getLocation()); + } else if (sender instanceof ConsoleCommandSender) { + if (args.length >= 7) { + try { + World world = Bukkit.getWorld(args[3]); + double x = Double.parseDouble(args[4]); + double y = Double.parseDouble(args[5]); + double z = Double.parseDouble(args[6]); + if (world != null) { + builder.location(new Location(world, x, y, z)); + } + } catch (Exception e) { + LOGGER.warn("Can't build location", e); + } + } + } + + builder.spawnWithSkin(null); + } + return true; + } + + private boolean handleRemove(CommandSender sender, String[] args) { + if (args.length < 2 || args.length > 5) { + sender.sendMessage(text("Usage: /bot remove [hour] [minute] [second]", NamedTextColor.RED)); + return true; + } + + String botName = args[1]; + BotList botList = BotList.INSTANCE; + ServerBot bot = botList.getBotByName(BotUtil.getFullName(botName)); + + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return true; + } + + if (args.length == 3 && args[2].equals("cancel")) { + if (bot.removeTaskId == -1) { + sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); + return true; + } + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + bot.removeTaskId = -1; + sender.sendMessage(text("Remove cancel")); + return true; + } + + if (args.length > 2) { + long time = 0; + int h; + long s = 0; + long m = 0; + + try { + h = Integer.parseInt(args[2]); + if (h < 0) { + throw new NumberFormatException(); + } + time += ((long) h) * 3600 * 20; + if (args.length > 3) { + m = Long.parseLong(args[3]); + if (m > 59 || m < 0) { + throw new NumberFormatException(); + } + time += m * 60 * 20; + } + if (args.length > 4) { + s = Long.parseLong(args[4]); + if (s > 59 || s < 0) { + throw new NumberFormatException(); + } + time += s * 20; + } + } catch (NumberFormatException e) { + sender.sendMessage(text("Usage: /bot remove [hour] [minute] [second]", NamedTextColor.RED)); + return true; + } + + boolean isReschedule = bot.removeTaskId != -1; + + if (isReschedule) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + } + bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> { + bot.removeTaskId = -1; + botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + }, time).getTaskId(); + + sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : "")); + return true; + } + + botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + sender.sendMessage(text("Removed fakeplayer: " + botName, NamedTextColor.GREEN)); + return true; + } + + private boolean handleList(CommandSender sender, String[] args) { + BotList botList = BotList.INSTANCE; + + if (args.length < 2) { + Map> botMap = new HashMap<>(); + for (World world : Bukkit.getWorlds()) { + botMap.put(world, new ArrayList<>()); + } + + for (ServerBot bot : botList.bots) { + Bot bukkitBot = bot.getBukkitEntity(); + botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName()); + } + + sender.sendMessage("Total number: (" + botList.bots.size() + "/" + FakeplayerConfig.limit + ")"); + for (World world : botMap.keySet()) { + sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world))); + } + } else { + World world = Bukkit.getWorld(args[1]); + + if (world == null) { + sender.sendMessage(text("Unknown world", NamedTextColor.RED)); + return true; + } + + List snowBotList = new ArrayList<>(); + for (ServerBot bot : botList.bots) { + Bot bukkitBot = bot.getBukkitEntity(); + if (bukkitBot.getWorld() == world) { + snowBotList.add(bukkitBot.getName()); + } + } + + sender.sendMessage(world.getName() + "(" + snowBotList.size() + "): " + formatPlayerNameList(snowBotList)); + } + return true; + } + + private boolean handleAction(CommandSender sender, String[] args) { + if (!FakeplayerConfig.canUseAction) { + sender.sendMessage(text("Bot action feature is disabled!", NamedTextColor.RED)); + return true; + } + + if (args.length < 3) { + sender.sendMessage(text("Use /bot action to make fakeplayer do action", NamedTextColor.RED)); + return true; + } + + ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return true; + } + + switch (args[2].toLowerCase()) { + case "list" -> { + sender.sendMessage(bot.getScoreboardName() + "'s action list:"); + for (int i = 0; i < bot.getBotActions().size(); i++) { + sender.sendMessage(i + " " + bot.getBotActions().get(i).getName()); + } + } + case "start" -> executeActionStart(bot, sender, args); + case "stop" -> executeActionStop(bot, sender, args); + default -> sender.sendMessage(text("Unknown action command. Use: list, start, stop", NamedTextColor.RED)); + } + return true; + } + + private void executeActionStart(ServerBot bot, CommandSender sender, String[] args) { + if (args.length < 4) { + sender.sendMessage(text("Invalid action", NamedTextColor.RED)); + return; + } + ServerBotAction action = Actions.getForName(args[3]); + if (action == null) { + sender.sendMessage(text("Invalid action", NamedTextColor.RED)); + return; + } + + CraftPlayer player; + if (sender instanceof CraftPlayer) { + player = (CraftPlayer) sender; + } else { + player = bot.getBukkitEntity(); + } + + String[] realArgs = Arrays.copyOfRange(args, 4, args.length); + ServerBotAction newAction; + try { + newAction = action.create(); + newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs)); + } catch (IllegalArgumentException e) { + sender.sendMessage(text("Action create error, please check your arguments, " + e.getMessage(), NamedTextColor.RED)); + return; + } + + if (bot.addBotAction(newAction, sender)) { + sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString()); + } + } + + private void executeActionStop(ServerBot bot, CommandSender sender, String[] args) { + if (args.length < 4) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + return; + } + + String index = args[3]; + if (index.equals("all")) { + Set> forRemoval = new HashSet<>(); + for (int i = 0; i < bot.getBotActions().size(); i++) { + ServerBotAction action = bot.getBotActions().get(i); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + forRemoval.add(action); + action.stop(bot, BotActionStopEvent.Reason.COMMAND); + } + } + bot.getBotActions().removeAll(forRemoval); + sender.sendMessage(bot.getScoreboardName() + "'s action list cleared."); + return; + } + try { + int i = Integer.parseInt(index); + if (i < 0 || i >= bot.getBotActions().size()) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + return; + } + + ServerBotAction action = bot.getBotActions().get(i); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + action.stop(bot, BotActionStopEvent.Reason.COMMAND); + bot.getBotActions().remove(i); + sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped."); + } + } catch (NumberFormatException e) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + } + } + + private boolean handleConfig(CommandSender sender, String[] args) { + if (!FakeplayerConfig.canModifyConfig) { + sender.sendMessage(text("Bot config feature is disabled!", NamedTextColor.RED)); + return true; + } + + if (args.length < 3) { + sender.sendMessage(text("Use /bot config to modify fakeplayer's config", NamedTextColor.RED)); + return true; + } + + ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return true; + } + + if (!Configs.getConfigNames().contains(args[2])) { + sender.sendMessage(text("This config is not accept", NamedTextColor.RED)); + return true; + } + + AbstractBotConfig config = bot.getConfig(Objects.requireNonNull(Configs.getConfig(args[2]))); + if (args.length < 4) { + config.getMessage().forEach(sender::sendMessage); + } else { + String[] realArgs = Arrays.copyOfRange(args, 3, args.length); + + BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs, sender); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return true; + } + CommandArgumentResult result = config.getArgument().parse(0, realArgs); + + try { + config.setFromCommand(result); + config.getChangeMessage().forEach(sender::sendMessage); + } catch (IllegalArgumentException e) { + sender.sendMessage(text(e.getMessage(), NamedTextColor.RED)); + } + } + return true; + } + + private boolean handleSave(CommandSender sender, String[] args) { + if (!FakeplayerConfig.canManualSaveAndLoad) { + sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED)); + return true; + } + + if (args.length < 2) { + sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); + return true; + } + + BotList botList = BotList.INSTANCE; + ServerBot bot = botList.getBotByName(args[1]); + + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return true; + } + + if (botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true)) { + sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName()); + } + return true; + } + + private boolean handleLoad(CommandSender sender, String[] args) { + if (!FakeplayerConfig.canManualSaveAndLoad) { + sender.sendMessage(text("Bot save/load feature is disabled!", NamedTextColor.RED)); + return true; + } + + if (args.length < 2) { + sender.sendMessage(text("Use /bot load to load a fakeplayer", NamedTextColor.RED)); + return true; + } + + String realName = args[1]; + BotList botList = BotList.INSTANCE; + if (!botList.getSavedBotList().contains(realName)) { + sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED)); + return true; + } + + if (botList.loadNewBot(realName) == null) { + sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED)); + } else { + sender.sendMessage(text("Successfully loaded fakeplayer: " + realName, NamedTextColor.GREEN)); + } + return true; + } + + @NotNull + private static String formatPlayerNameList(@NotNull List list) { + if (list.isEmpty()) { + return ""; + } + String string = list.toString(); + return string.substring(1, string.length() - 1); + } + + private boolean canCreate(CommandSender sender, @NotNull String name) { + BotList botList = BotList.INSTANCE; + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + sender.sendMessage(text("This name is illegal", NamedTextColor.RED)); + return false; + } + + if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) { + sender.sendMessage(text("This player is in server", NamedTextColor.RED)); + return false; + } + + if (FakeplayerConfig.unableNames.contains(name)) { + sender.sendMessage(text("This name is not allowed", NamedTextColor.RED)); + return false; + } + + if (botList.bots.size() >= FakeplayerConfig.limit) { + sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED)); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java new file mode 100644 index 0000000..30dff55 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java @@ -0,0 +1,132 @@ +package org.leavesmc.leaves.bot; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +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.entity.bot.Bot; +import org.leavesmc.leaves.entity.bot.BotCreator; +import org.leavesmc.leaves.entity.bot.CraftBot; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +import java.util.Objects; +import java.util.function.Consumer; + +public record BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) { + + private static final MinecraftServer server = MinecraftServer.getServer(); + + public ServerBot createNow() { + return server.getBotList().createNewBot(this); + } + + @NotNull + public static Builder builder(@NotNull String realName, @Nullable Location location) { + return new Builder(realName, location); + } + + public 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 = FakeplayerConfig.prefix + realName + FakeplayerConfig.suffix; + this.skinName = this.realName; + this.skin = null; + this.createReason = BotCreateEvent.CreateReason.UNKNOWN; + this.creator = null; + } + + public Builder name(@NotNull String name) { + Objects.requireNonNull(name); + this.name = name; + return this; + } + + public Builder skinName(@Nullable String skinName) { + this.skinName = skinName; + return this; + } + + public Builder skin(@Nullable String[] skin) { + this.skin = skin; + return this; + } + + public Builder mojangAPISkin() { + if (this.skinName != null) { + this.skin = MojangAPI.getSkin(this.skinName); + } + 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.getRegionScheduler().execute( + MinecraftInternalPlugin.INSTANCE, + location.getWorld(), + location.getBlockX() >> 4, + location.getBlockZ() >> 4, + () -> { + this.mojangAPISkin(); + Bukkit.getRegionScheduler().execute( + MinecraftInternalPlugin.INSTANCE, + location.getWorld(), + location.getBlockX() >> 4, + location.getBlockZ() >> 4, + () -> { + 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; + } + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java new file mode 100644 index 0000000..a23420a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java @@ -0,0 +1,139 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.logging.LogUtils; +import net.minecraft.core.UUIDUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.ValueInput; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.util.TagUtil; +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 = TagUtil.saveEntityWithoutId(player); + 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.store("uuid", UUIDUtil.CODEC, bot.getUUID()); + nbt.putBoolean("resume", bot.resume); + this.savedBotList.put(bot.createState.realName(), nbt); + this.saveBotList(); + } + } + + @Override + public Optional load(Player player, ProblemReporter reporter) { + return this.load(player.getScoreboardName(), player.getStringUUID()).map(nbt -> { + ValueInput valueInput = TagValueInput.create(reporter, player.registryAccess(), nbt); + player.load(valueInput); + return valueInput; + }); + } + + 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(); + } + + public Optional read(String uuid) { + File file = new File(this.botDir, uuid + ".dat"); + if (file.exists() && file.isFile()) { + try { + return Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap())); + } catch (Exception exception) { + BotDataStorage.LOGGER.warn("Failed to read fakeplayer data for {}", uuid); + } + } + 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/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java new file mode 100644 index 0000000..1a50675 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java @@ -0,0 +1,125 @@ +package org.leavesmc.leaves.bot; + +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.entity.player.Inventory; +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; + +public class BotInventoryContainer extends Inventory { + + private static final ItemStack button; + + static { + CompoundTag customData = new CompoundTag(); + customData.putBoolean("Leaves.Gui.Placeholder", true); + + DataComponentPatch patch = DataComponentPatch.builder() + .set(DataComponents.CUSTOM_NAME, Component.empty()) + .set(DataComponents.CUSTOM_DATA, CustomData.of(customData)) + .build(); + + button = new ItemStack(Items.STRUCTURE_VOID); + button.applyComponents(patch); + } + + private final Inventory original; + + public BotInventoryContainer(Inventory original) { + super(original.player, original.equipment); + this.original = original; + } + + @Override + public int getContainerSize() { + return 54; + } + + @Override + @Nonnull + public ItemStack getItem(int slot) { + int realSlot = convertSlot(slot); + if (realSlot == -999) { + // buttons are the same + return button; + } + return original.getItem(realSlot); + } + + public int convertSlot(int slot) { + return switch (slot) { + // Mainhand is always store at slot 0 + case 6 -> 0; + + // Offhand + case 7 -> 40; + + // Equipment slot start at 36 + case 1, 2, 3, 4 -> 40 - slot; + + // Inventory storage + case 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44 -> slot - 9; + + // Hotbar, 45 -> Mainhand (0) + case 45, 46, 47, 48, 49, 50, 51, 52, 53 -> slot - 45; + + // Buttons + default -> -999; + }; + } + + @Override + @Nonnull + public ItemStack removeItem(int slot, int amount) { + int realSlot = convertSlot(slot); + if (realSlot == -999) { + // Don't remove buttons + return ItemStack.EMPTY; + } + ItemStack removed = original.removeItem(realSlot, amount); + player.detectEquipmentUpdates(); + return removed; + } + + @Override + @Nonnull + public ItemStack removeItemNoUpdate(int slot) { + int realSlot = convertSlot(slot); + if (realSlot == -999) { + // Don't remove buttons + return ItemStack.EMPTY; + } + return original.removeItemNoUpdate(realSlot); + } + + @Override + public void setItem(int slot, @Nonnull ItemStack stack) { + int realSlot = convertSlot(slot); + if (realSlot == -999) { + // Don't modify buttons + return; + } + original.setItem(realSlot, stack); + player.detectEquipmentUpdates(); + } + + @Override + public void setChanged() { + } + + @Override + public boolean stillValid(@Nonnull Player player) { + if (this.player.isRemoved()) { + return false; + } + return !(player.distanceToSqr(this.player) > 64.0); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java new file mode 100644 index 0000000..202b2f2 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java @@ -0,0 +1,359 @@ +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 fun.bm.lophine.config.modules.misc.FakeplayerConfig; +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.util.ProblemReporter; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.npc.AbstractVillager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.ThrownEnderpearl; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.ValueInput; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.event.entity.EntityRemoveEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.event.bot.*; +import org.slf4j.Logger; + +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +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(); + private final Map> botsNameByWorldUuid = 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(!BotUtil.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; + try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(bot.problemPath(), LOGGER)) { + optional = playerIO.load(bot, scopedCollector); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (optional.isEmpty()) { + return null; + } + ValueInput nbt = optional.get(); + + ResourceKey resourcekey = null; + if (nbt.getLong("WorldUUIDMost").isPresent() && nbt.getLong("WorldUUIDLeast").isPresent()) { + org.bukkit.World bWorld = Bukkit.getServer().getWorld(new UUID(nbt.getLong("WorldUUIDMost").orElseThrow(), nbt.getLong("WorldUUIDLeast").orElseThrow())); + 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(), nbt); + } + + public ServerBot placeNewBot(@NotNull ServerBot bot, ServerLevel world, Location location, ValueInput save) { + Optional optional = Optional.ofNullable(save); + + bot.isRealPlayer = true; + bot.loginTime = System.currentTimeMillis(); + 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(bot.level()); + + bot.setPosRaw(location.getX(), location.getY(), location.getZ()); + bot.setRot(location.getYaw(), location.getPitch()); + + bot.connection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot()); + + 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); + optional.ifPresent(nbt -> { + bot.loadAndSpawnEnderPearls(nbt); + bot.loadAndSpawnParentVehicle(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.renderInfo(); + bot.supressTrackerForLogin = false; + + bot.level().getChunkSource().chunkMap.addEntity(bot); + bot.renderData(); + bot.initInventoryMenu(); + botsNameByWorldUuid + .computeIfAbsent(bot.level().uuid.toString(), (k) -> new HashSet<>()) + .add(bot.getBukkitEntity().getRealName()); + BotList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", bot.getName().getString(), "Local", bot.getId(), bot.level().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ()); + return bot; + } + + public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) { + return this.removeBot(bot, reason, remover, saved, this.dataStorage); + } + + public boolean 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 true; + } + + if (bot.removeTaskId != -1) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + bot.removeTaskId = -1; + } + + bot.disconnect(); + + if (event.shouldSave()) { + playerIO.save(bot); + } else { + bot.dropAll(true); + botsNameByWorldUuid.getOrDefault(bot.level().uuid.toString(), new HashSet<>()).remove(bot.getBukkitEntity().getRealName()); + } + + if (bot.isPassenger() && event.shouldSave()) { + Entity entity = bot.getRootVehicle(); + if (entity.hasExactlyOnePlayerPassenger()) { + bot.stopRiding(); + entity.getPassengersAndSelf().forEach((entity1) -> { + if (!false && entity1 instanceof AbstractVillager villager) { + final Player human = villager.getTradingPlayer(); + if (human != null) { + villager.setTradingPlayer(null); + } + } + entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); + }); + } + } + + bot.unRide(); + for (ThrownEnderpearl thrownEnderpearl : bot.getEnderPearls()) { + if (!thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) { + thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); + } else { + thrownEnderpearl.setOwner(null); + } + } + + ServerLevel level = bot.level(); + int chunkX = bot.getBlockX() >> 4; + int chunkZ = bot.getBlockZ() >> 4; + if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level, chunkX, chunkZ)) { + level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); + } else { + io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( + level, chunkX, chunkZ, () -> { + level.removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); + }); + } + + 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(); + ClientboundRemoveEntitiesPacket packet = new ClientboundRemoveEntitiesPacket(bot.getId()); + for (ServerPlayer player : bot.level().players()) { + if (!(player instanceof ServerBot)) { + player.connection.send(packet); + } + } + + 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); + } + return true; + } + + public void removeAllIn(String worldUuid) { + for (String realName : this.botsNameByWorldUuid.getOrDefault(worldUuid, new HashSet<>())) { + ServerBot bot = this.getBotByName(realName); + if (bot != null) { + this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, FakeplayerConfig.canResident); + } + } + } + + public void removeAll() { + for (ServerBot bot : this.bots) { + bot.resume = FakeplayerConfig.canResident; + this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, FakeplayerConfig.canResident); + } + } + + public void loadBotInfo() { + if (!FakeplayerConfig.enable || !FakeplayerConfig.canResident) { + return; + } + CompoundTag savedBotList = this.getSavedBotList().copy(); + for (String realName : savedBotList.keySet()) { + CompoundTag nbt = savedBotList.getCompound(realName).orElseThrow(); + if (!nbt.getBoolean("resume").orElse(false)) { + continue; + } + UUID levelUuid = BotUtil.getBotLevel(realName, this.dataStorage); + if (levelUuid == null) { + LOGGER.warn("Bot {} has no world UUID, skipping loading.", realName); + continue; + } + this.botsNameByWorldUuid + .computeIfAbsent(levelUuid.toString(), (k) -> new HashSet<>()) + .add(realName); + } + } + + public void loadResume(String worldUuid) { + if (!FakeplayerConfig.enable || !FakeplayerConfig.canResident) { + return; + } + Set bots = this.botsNameByWorldUuid.get(worldUuid); + if (bots == null) { + return; + } + Set botsCopy = new HashSet<>(bots); + botsCopy.forEach(this::loadNewBot); + } + + public void updateBotLevel(ServerBot bot, ServerLevel level) { + String prevUuid = bot.level().uuid.toString(); + String newUuid = level.uuid.toString(); + this.botsNameByWorldUuid + .computeIfAbsent(newUuid, (k) -> new HashSet<>()) + .add(bot.getBukkitEntity().getRealName()); + this.botsNameByWorldUuid + .computeIfAbsent(prevUuid, (k) -> new HashSet<>()) + .remove(bot.getBukkitEntity().getRealName()); + } + + public void networkTick() { + this.bots.forEach(ServerBot::networkTick); + } + + @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 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/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java new file mode 100644 index 0000000..1ca36ca --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotRecipeBook.java @@ -0,0 +1,56 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.ServerRecipeBook; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeHolder; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; + +public class BotRecipeBook extends ServerRecipeBook { + + public BotRecipeBook() { + super(($, $1) -> { + }); + } + + @Override + public void add(@NotNull ResourceKey> recipe) { + } + + @Override + public void remove(@NotNull ResourceKey> recipe) { + } + + @Override + public boolean contains(@NotNull ResourceKey> recipe) { + return false; + } + + @Override + public void removeHighlight(@NotNull ResourceKey> recipe) { + } + + @Override + public int addRecipes(@NotNull Collection> recipes, @NotNull ServerPlayer player) { + return 0; + } + + @Override + public int removeRecipes(@NotNull Collection> recipes, @NotNull ServerPlayer player) { + return 0; + } + + @Override + public void loadUntrusted(@NotNull Packed recipeBook, @NotNull Predicate>> predicate) { + } + + @Override + public @NotNull Packed pack() { + return new ServerRecipeBook.Packed(this.bookSettings.copy(), List.of(), List.of()); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java new file mode 100644 index 0000000..1049444 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java @@ -0,0 +1,36 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +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; + +public class BotStatsCounter extends ServerStatsCounter { + + private static final File UNKOWN_FILE = new File("BOT_STATS_REMOVE_THIS"); + + public BotStatsCounter(MinecraftServer server) { + super(server, UNKOWN_FILE); + } + + @Override + public void save() { + } + + @Override + public void setValue(@NotNull Player player, @NotNull Stat stat, int value) { + } + + @Override + public void parseLocal(@NotNull DataFixer dataFixer, @NotNull String json) { + } + + @Override + public int getValue(@NotNull Stat stat) { + return 0; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotUtil.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotUtil.java new file mode 100644 index 0000000..a58722c --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotUtil.java @@ -0,0 +1,112 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.base.Charsets; +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.UUID; + +public class BotUtil { + + public static void replenishment(@NotNull ItemStack itemStack, NonNullList itemStackList) { + int count = itemStack.getMaxStackSize() / 2; + if (itemStack.getCount() <= 8 && count > 8) { + for (ItemStack itemStack1 : itemStackList) { + if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) { + continue; + } + + if (ItemStack.isSameItemSameComponents(itemStack1, itemStack)) { + if (itemStack1.getCount() > count) { + itemStack.setCount(itemStack.getCount() + count); + itemStack1.setCount(itemStack1.getCount() - count); + } else { + itemStack.setCount(itemStack.getCount() + itemStack1.getCount()); + itemStack1.setCount(0); + } + break; + } + } + } + } + + public static void replaceTool(@NotNull EquipmentSlot slot, @NotNull ServerBot bot) { + ItemStack itemStack = bot.getItemBySlot(slot); + for (int i = 0; i < 36; i++) { + ItemStack itemStack1 = bot.getInventory().getItem(i); + if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) { + continue; + } + + if (itemStack1.getItem().getClass() == itemStack.getItem().getClass() && !isDamage(itemStack1, 10)) { + ItemStack itemStack2 = itemStack1.copy(); + bot.getInventory().setItem(i, itemStack); + bot.setItemSlot(slot, itemStack2); + return; + } + } + + for (int i = 0; i < 36; i++) { + ItemStack itemStack1 = bot.getInventory().getItem(i); + if (itemStack1 == ItemStack.EMPTY && itemStack1 != itemStack) { + bot.getInventory().setItem(i, itemStack); + bot.setItemSlot(slot, ItemStack.EMPTY); + return; + } + } + } + + public static boolean isDamage(@NotNull ItemStack item, int minDamage) { + return item.isDamageableItem() && (item.getMaxDamage() - item.getDamageValue()) <= minDamage; + } + + @NotNull + public static UUID getBotUUID(@NotNull BotCreateState state) { + return getBotUUID(state.realName()); + } + + public static UUID getBotUUID(@NotNull String realName) { + return UUID.nameUUIDFromBytes(("Fakeplayer:" + realName).getBytes(Charsets.UTF_8)); + } + + public static UUID getBotLevel(@NotNull String realName, BotDataStorage botDataStorage) { + UUID uuid = BotUtil.getBotUUID(realName); + Optional tagOptional = botDataStorage.read(uuid.toString()); + if (tagOptional.isEmpty()) { + return null; + } + CompoundTag tag = tagOptional.get(); + Optional worldUUIDMost = tag.getLong("WorldUUIDMost"); + Optional worldUUIDLeast = tag.getLong("WorldUUIDLeast"); + if (worldUUIDMost.isEmpty() || worldUUIDLeast.isEmpty()) { + return null; + } + return new UUID(worldUUIDMost.get(), worldUUIDLeast.get()); + } + + public static String getFullName(String inputName) { + return FakeplayerConfig.prefix + inputName + FakeplayerConfig.suffix; + } + + public static boolean isCreateLegal(@NotNull String name) { + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + return false; + } + + if (Bukkit.getPlayerExact(name) != null || BotList.INSTANCE.getBotByName(name) != null) { + return false; + } + + if (FakeplayerConfig.unableNames.contains(name)) { + return false; + } + + return BotList.INSTANCE.bots.size() < FakeplayerConfig.limit; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java new file mode 100644 index 0000000..614fb64 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java @@ -0,0 +1,14 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.storage.ValueInput; + +import java.util.Optional; + +public interface IPlayerDataStorage { + + void save(Player player); + + Optional load(Player player, ProblemReporter reporter); +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java new file mode 100644 index 0000000..99da6dc --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java @@ -0,0 +1,39 @@ +package org.leavesmc.leaves.bot; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class MojangAPI { + + private static final Map CACHE = new HashMap<>(); + + public static String[] getSkin(String name) { + if (FakeplayerConfig.useSkinCache && CACHE.containsKey(name)) { + return CACHE.get(name); + } + + String[] values = pullFromAPI(name); + CACHE.put(name, values); + return values; + } + + // Laggggggggggggggggggggggggggggggggggggggggg + public static String[] pullFromAPI(String name) { + try { + 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/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java new file mode 100644 index 0000000..bb9878a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBot.java @@ -0,0 +1,684 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.ImmutableMap; +import com.mojang.authlib.GameProfile; +import com.mojang.logging.LogUtils; +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.event.entity.EntityKnockbackEvent; +import net.minecraft.core.NonNullList; +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.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +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.ServerPlayerConnection; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stat; +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.EntitySelector; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Input; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.entity.vehicle.AbstractBoat; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.portal.TeleportTransition; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.entity.bot.CraftBot; +import org.leavesmc.leaves.event.bot.*; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; +import org.leavesmc.leaves.util.MathUtils; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; + +public class ServerBot extends ServerPlayer { + + private final List> actions; + private final Map, AbstractBotConfig> configs; + private static final Logger LOGGER = LogUtils.getLogger(); + + public boolean resume = false; + public BotCreateState createState; + public UUID createPlayer; + public boolean handsBusy = false; + + private final int tracingRange; + private final BotStatsCounter stats; + private final BotInventoryContainer container; + + public int notSleepTicks; + + public int removeTaskId = -1; + + public ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) { + super(server, world, profile, ClientInformation.createDefault()); + this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2); + + this.gameMode = new ServerBotGameMode(this); + + this.actions = new ArrayList<>(); + ImmutableMap.Builder, AbstractBotConfig> configBuilder = ImmutableMap.builder(); + for (Configs config : Configs.getConfigs()) { + configBuilder.put(config, config.createConfig(this)); + } + this.configs = configBuilder.build(); + + this.stats = new BotStatsCounter(server); + this.recipeBook = new BotRecipeBook(); + this.container = new BotInventoryContainer(this.getInventory()); + this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange; + + this.notSleepTicks = 0; + this.fauxSleeping = FakeplayerConfig.canSkipSleep; + this.getBukkitEntity().setSimulationDistance(FakeplayerConfig.getSimulationDistance(this)); + this.setClientLoaded(true); + } + + @Override + public void tick() { + if (!this.isAlive()) { + return; + } + + if (this.getConfigValue(Configs.TICK_TYPE) == TickType.ENTITY_LIST) { + this.runAction(); + } + + // copy ServerPlayer start + if (this.joining) { + this.joining = false; + } + + this.resetOperationCountPerTick(); // Leaves - player operation limiter + this.wardenSpawnTracker.tick(); + if (this.invulnerableTime > 0) { + this.invulnerableTime--; + } + if (this.spawnInvulnerableTime > 0) { + --this.spawnInvulnerableTime; // Leaves - spawn invulnerable time + } + // copy ServerPlayer end + + if (this.getConfigValue(Configs.SPAWN_PHANTOM)) { + this.notSleepTicks++; + } + + if (FakeplayerConfig.regenAmount > 0.0 && getServer().getTickCount() % 20 == 0) { + float regenAmount = (float) (FakeplayerConfig.regenAmount * 20); + this.setHealth(Math.min(this.getHealth() + regenAmount, this.getMaxHealth())); + } + + if (this.getConfigValue(Configs.TICK_TYPE) == TickType.ENTITY_LIST) { + this.doTick(); + } + + Input input = this.getLastClientInput(); + this.setLastClientInput( + new Input( + this.zza > 0, + this.zza < 0, + this.xxa > 0, + this.xxa < 0, + input.jump(), + input.shift(), + input.sprint() + ) + ); + } + + @Override + public void doTick() { + if (!this.isAlive()) { + this.die(this.damageSources().generic()); + return; + } + + this.absSnapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + + if (this.isPassenger()) { + this.setOnGround(false); + } + + if (this.takeXpDelay > 0) { + --this.takeXpDelay; + } + + if (this.isSleeping()) { + ++this.sleepCounter; + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + this.notSleepTicks = 0; + } + + if (!this.level().isClientSide && this.level().isBrightOutside()) { + this.stopSleepInBed(false, true); + } + } else if (this.sleepCounter > 0) { + ++this.sleepCounter; + if (this.sleepCounter >= 110) { + this.sleepCounter = 0; + } + } + + this.updateIsUnderwater(); + + if (this.getConfigValue(Configs.TICK_TYPE) == TickType.NETWORK) { + try { + Bukkit.getRegionScheduler().execute( + MinecraftInternalPlugin.INSTANCE, + this.level().getWorld(), + this.getBlockX() >> 4, + this.getBlockZ() >> 4, + this::runAction + ); + } catch (Exception e) { + this.runAction(); + } + } + + this.livingEntityTick(); + + this.foodData.tick(this); + + double d = Mth.clamp(this.getX(), -2.9999999E7, 2.9999999E7); + double d1 = Mth.clamp(this.getZ(), -2.9999999E7, 2.9999999E7); + if (d != this.getX() || d1 != this.getZ()) { + this.setPos(d, this.getY(), d1); + } + + ++this.attackStrengthTicker; + ItemStack itemstack = this.getMainHandItem(); + if (!ItemStack.matches(this.lastItemInMainHand, itemstack)) { + if (!ItemStack.isSameItem(this.lastItemInMainHand, itemstack)) { + this.resetAttackStrengthTicker(); + } + + this.lastItemInMainHand = itemstack.copy(); + } + + this.getCooldowns().tick(); + this.updatePlayerPose(); + } + + public void networkTick() { + if (this.getConfigValue(Configs.TICK_TYPE) == TickType.NETWORK) { + this.doTick(); + } + } + + @Override + public boolean canSimulateMovement() { + return true; + } + + @Override + public void removeVehicle() { + super.removeVehicle(); + this.handsBusy = false; + } + + @Override + public void rideTick() { + super.rideTick(); + this.handsBusy = false; + if (this.getControlledVehicle() instanceof AbstractBoat abstractBoat) { + Input input = this.getLastClientInput(); + abstractBoat.setInput(input.left(), input.right(), input.forward(), input.backward()); + this.handsBusy = this.handsBusy | (input.left() || input.right() || input.forward() || input.backward()); + } + } + + @Override + public @Nullable ServerBot teleport(@NotNull TeleportTransition teleportTransition) { + if (this.isSleeping() || this.isRemoved()) { + return null; + } + if (!teleportTransition.asPassenger()) { + this.removeVehicle(); + } + + ServerLevel fromLevel = this.level(); + ServerLevel toLevel = teleportTransition.newLevel(); + + if (toLevel.dimension() == fromLevel.dimension()) { + this.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); + teleportTransition.postTeleportTransition().onTransition(this); + return this; + } else { + this.isChangingDimension = true; + fromLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + this.unsetRemoved(); + this.setServerLevel(toLevel); + this.teleportSetPosition(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); + toLevel.addDuringTeleport(this); + this.stopUsingItem(); + teleportTransition.postTeleportTransition().onTransition(this); + this.isChangingDimension = false; + + if (this.isBlocking()) { + this.stopUsingItem(); + } + } + + return this; + } + + @Override + public void setServerLevel(@NotNull ServerLevel level) { + BotList.INSTANCE.updateBotLevel(this, level); + super.setServerLevel(level); + } + + @Override + public void knockback(double strength, double x, double z, @Nullable Entity attacker, EntityKnockbackEvent.@NotNull Cause eventCause) { + if (!this.hurtMarked) { + return; + } + super.knockback(strength, x, z, attacker, eventCause); + } + + @Override + public void onItemPickup(@NotNull ItemEntity item) { + super.onItemPickup(item); + this.updateItemInHand(InteractionHand.MAIN_HAND); + } + + public void updateItemInHand(InteractionHand hand) { + ItemStack item = this.getItemInHand(hand); + + if (!item.isEmpty()) { + BotUtil.replenishment(item, getInventory().getNonEquipmentItems()); + if (BotUtil.isDamage(item, 10)) { + BotUtil.replaceTool(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND, this); + } + } + this.detectEquipmentUpdates(); + } + + @Override + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { + if (FakeplayerConfig.canOpenInventory) { + if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { + BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); + this.getServer().server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName())); + return InteractionResult.SUCCESS; + } + } + } + return super.interact(player, hand); + } + + @Override + public void attack(@NotNull Entity target) { + super.attack(target); + this.swing(InteractionHand.MAIN_HAND); + } + + @Override + public void addAdditionalSaveData(@NotNull ValueOutput 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)); + } + createNbt.put("skin", skin); + } + + nbt.store("createStatus", CompoundTag.CODEC, createNbt); + + if (!this.actions.isEmpty()) { + ValueOutput.TypedOutputList actionNbt = nbt.list("actions", CompoundTag.CODEC); + for (ServerBotAction action : this.actions) { + actionNbt.add(action.save(new CompoundTag())); + } + } + + if (!this.configs.isEmpty()) { + ValueOutput.TypedOutputList configNbt = nbt.list("configs", CompoundTag.CODEC); + for (AbstractBotConfig config : this.configs.values()) { + configNbt.add(config.save(new CompoundTag())); + } + } + } + + @Override + public void readAdditionalSaveData(@NotNull ValueInput nbt) { + super.readAdditionalSaveData(nbt); + this.setShiftKeyDown(nbt.getBooleanOr("isShiftKeyDown", false)); + + CompoundTag createNbt = nbt.read("createStatus", CompoundTag.CODEC).orElseThrow(); + BotCreateState.Builder createBuilder = BotCreateState.builder(createNbt.getString("realName").orElseThrow(), null).name(createNbt.getString("name").orElseThrow()); + + String[] skin = null; + if (createNbt.contains("skin")) { + ListTag skinTag = createNbt.getList("skin").orElseThrow(); + skin = new String[skinTag.size()]; + for (int i = 0; i < skinTag.size(); i++) { + skin[i] = skinTag.getString(i).orElseThrow(); + } + } + + createBuilder.skinName(createNbt.getString("skinName").orElseThrow()).skin(skin); + createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null); + + this.createState = createBuilder.build(); + this.gameProfile = new BotList.CustomGameProfile(this.getUUID(), this.createState.name(), this.createState.skin()); + + + if (nbt.list("actions", CompoundTag.CODEC).isPresent()) { + ValueInput.TypedInputList actionNbt = nbt.list("actions", CompoundTag.CODEC).orElseThrow(); + actionNbt.forEach(actionTag -> { + ServerBotAction action = Actions.getForName(actionTag.getString("actionName").orElseThrow()); + if (action != null) { + ServerBotAction newAction = action.create(); + newAction.load(actionTag); + this.actions.add(newAction); + } + }); + } + + if (nbt.list("configs", CompoundTag.CODEC).isPresent()) { + ValueInput.TypedInputList configNbt = nbt.list("configs", CompoundTag.CODEC).orElseThrow(); + for (CompoundTag configTag : configNbt) { + Configs configKey = Configs.getConfig(configTag.getString("configName").orElseThrow()); + if (configKey != null) { + this.configs.get(configKey).load(configTag); + } + } + } + } + + public void sendPlayerInfo(ServerPlayer player) { + player.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), List.of(this))); + } + + public boolean needSendFakeData(ServerPlayer player) { + 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) { + if (needSendFakeData(player)) { + this.sendFakeData(player.connection, login); + } + } + + public void sendFakeData(ServerPlayerConnection playerConnection, boolean login) { + ChunkMap.TrackedEntity entityTracker = this.level().getChunkSource().chunkMap.entityMap.get(this.getId()); + + if (entityTracker == null) { + LOGGER.warn("Fakeplayer cant get entity tracker for " + this.getId()); + return; + } + + playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity)); + if (login) { + 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))); + } + } + + public void renderInfo() { + this.getServer().getPlayerList().getPlayers().forEach(this::sendPlayerInfo); + } + + public void renderData() { + this.getServer().getPlayerList().getPlayers().forEach( + player -> this.sendFakeDataIfNeed(player, false) + ); + } + + private void sendPacket(Packet packet) { + this.getServer().getPlayerList().getPlayers().forEach(player -> player.connection.send(packet)); + } + + @Override + public void die(@NotNull DamageSource damageSource) { + boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + + BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag); + this.getServer().server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + if (this.getHealth() <= 0) { + this.setHealth(0.1f); + } + return; + } + + this.gameEvent(GameEvent.ENTITY_DIE); + + net.kyori.adventure.text.Component deathMessage = event.deathMessage(); + if (event.isSendDeathMessage() && deathMessage != null && !deathMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.getServer().getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(deathMessage), false); + } + + this.getServer().getBotList().removeBot(this, BotRemoveEvent.RemoveReason.DEATH, null, false); + } + + @Override + public boolean startRiding(@NotNull Entity vehicle, boolean force) { + if (super.startRiding(vehicle, force)) { + if (vehicle.getControllingPassenger() == this) { // see net.minecraft.server.networkServerGamePacketListenerImpl#handleMoveVehicle + this.setDeltaMovement(Vec3.ZERO); + this.setYRot(vehicle.yRotO); + } + return true; + } else { + return false; + } + } + + @Override + public int awardRecipes(@NotNull Collection> recipes) { + return 0; + } + + @Override + public int resetRecipes(@NotNull Collection> recipes) { + return 0; + } + + @Override + public void triggerRecipeCrafted(@NotNull RecipeHolder recipe, @NotNull List items) { + } + + @Override + public void awardKillScore(@NotNull Entity entity, @NotNull DamageSource damageSource) { + } + + @Override + public void awardStat(@NotNull Stat stat) { + } + + @Override + public void resetStat(@NotNull Stat stat) { + } + + public void removeTab() { + this.sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID()))); + } + + public void faceLocation(@NotNull Location loc) { + this.look(loc.toVector().subtract(getLocation().toVector()), false); + } + + public void look(Vector dir, boolean keepYaw) { + float yaw, pitch; + + if (keepYaw) { + yaw = this.getYHeadRot(); + pitch = MathUtils.fetchPitch(dir); + } else { + float[] vals = MathUtils.fetchYawPitch(dir); + yaw = vals[0]; + pitch = vals[1]; + + this.sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f))); + } + + this.setRot(yaw, pitch); + } + + public Location getLocation() { + return this.getBukkitEntity().getLocation(); + } + + public EntityHitResult getEntityHitResult() { + return this.getEntityHitResult(null); + } + + public EntityHitResult getEntityHitResult(Predicate predicate) { + EntityHitResult result = this.pick(this, this.entityInteractionRange()); + if (result != null && (predicate == null || predicate.test(result.getEntity()))) { + return result; + } + return null; + } + + public BlockHitResult getBlockHitResult() { + return (BlockHitResult) this.pick(this.blockInteractionRange(), 1.0f, false); + } + + private EntityHitResult pick(Entity entity, double maxDistance) { + double d = maxDistance; + double d1 = Mth.square(maxDistance); + Vec3 vec3 = entity.getEyePosition(1.0f); + HitResult hitResult = entity.pick(maxDistance, 1.0f, false); + double d2 = hitResult.getLocation().distanceToSqr(vec3); + if (hitResult.getType() != HitResult.Type.MISS) { + d1 = d2; + d = Math.sqrt(d2); + } + + Vec3 viewStart = entity.getViewVector(1.0f); + Vec3 viewEnd = vec3.add(viewStart.x * d, viewStart.y * d, viewStart.z * d); + AABB aABB = entity.getBoundingBox().expandTowards(viewStart.scale(d)).inflate(1.0, 1.0, 1.0); + return ProjectileUtil.getEntityHitResult(entity, vec3, viewEnd, aABB, EntitySelector.CAN_BE_PICKED, d1); + } + + public void dropAll(boolean death) { + NonNullList items = this.getInventory().getNonEquipmentItems(); + for (int i = 0; i < items.size(); i++) { + ItemStack itemStack = items.get(i); + if (!itemStack.isEmpty()) { + this.drop(itemStack, death, false); + items.set(i, ItemStack.EMPTY); + } + } + for (EquipmentSlot slot : EquipmentSlot.values()) { + ItemStack itemStack; + if (!(itemStack = this.equipment.get(slot)).isEmpty()) { + this.drop(itemStack, death, false); + this.equipment.set(slot, ItemStack.EMPTY); + } + } + this.detectEquipmentUpdates(); + } + + private void runAction() { + if (FakeplayerConfig.canUseAction) { + this.actions.forEach(action -> action.tryTick(this)); + this.actions.removeIf(ServerBotAction::isCancelled); + } + } + + public boolean addBotAction(ServerBotAction action, CommandSender sender) { + if (!FakeplayerConfig.canUseAction) { + return false; + } + + if (!new BotActionScheduleEvent(this.getBukkitEntity(), action.getName(), action.getUUID(), sender).callEvent()) { + return false; + } + + action.init(); + this.actions.add(action); + return true; + } + + public List> getBotActions() { + return actions; + } + + @Override + @NotNull + public ServerStatsCounter getStats() { + return stats; + } + + @SuppressWarnings("unchecked") + public AbstractBotConfig getConfig(Configs config) { + return (AbstractBotConfig) 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(); + } + + public enum TickType { + NETWORK, + ENTITY_LIST + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java new file mode 100644 index 0000000..4cdd613 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java @@ -0,0 +1,119 @@ +package org.leavesmc.leaves.bot; + +import net.kyori.adventure.text.Component; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.GameMasterBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ServerBotGameMode extends ServerPlayerGameMode { + + public ServerBotGameMode(ServerBot bot) { + super(bot); + super.setGameModeForPlayer(GameType.SURVIVAL, null); + } + + @Override + public boolean changeGameModeForPlayer(@NotNull GameType gameMode) { + return false; + } + + @Nullable + @Override + public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) { + return null; + } + + @Override + protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) { + } + + @Override + public void tick() { + } + + @Override + public void destroyAndAck(@NotNull BlockPos pos, int sequence, @NotNull String reason) { + this.destroyBlock(pos); + } + + @Override + public boolean destroyBlock(@NotNull BlockPos pos) { + BlockState blockState = this.level.getBlockState(pos); + if (!this.player.getMainHandItem().canDestroyBlock(blockState, this.level, pos, this.player)) { + return false; + } else { + BlockEntity blockEntity = this.level.getBlockEntity(pos); + Block block = blockState.getBlock(); + if (block instanceof GameMasterBlock) { + this.level.sendBlockUpdated(pos, blockState, blockState, 3); + return false; + } else { + BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player); // Leaves - no block update + boolean flag = this.level.removeBlock(pos, false); + if (flag) { + block.destroy(this.level, pos, blockState1); + } + + ItemStack mainHandItem = this.player.getMainHandItem(); + ItemStack itemStack = mainHandItem.copy(); + boolean hasCorrectToolForDrops = this.player.hasCorrectToolForDrops(blockState1); + mainHandItem.getItem().mineBlock(mainHandItem, this.level, blockState1, pos, this.player); + if (flag && hasCorrectToolForDrops) { + block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack, true, true); + } + + return true; + } + } + } + + @NotNull + @Override + public InteractionResult useItemOn(@NotNull ServerPlayer player, Level level, @NotNull ItemStack stack, @NotNull InteractionHand hand, BlockHitResult hitResult) { + BlockPos blockPos = hitResult.getBlockPos(); + BlockState blockState = level.getBlockState(blockPos); + + if (!blockState.getBlock().isEnabled(level.enabledFeatures())) { + return InteractionResult.FAIL; + } + + boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty(); + boolean flag1 = player.isSecondaryUseActive() && flag; + + if (!flag1) { + InteractionResult iteminteractionresult = blockState.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult); + + if (iteminteractionresult.consumesAction()) { + return iteminteractionresult; + } + + if (iteminteractionresult instanceof InteractionResult.TryEmptyHandInteraction && hand == InteractionHand.MAIN_HAND) { + InteractionResult interactionResult = blockState.useWithoutItem(level, player, hitResult); + if (interactionResult.consumesAction()) { + return interactionResult; + } + } + } + + if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) { + UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult); + return stack.useOn(itemactioncontext); + } else { + return InteractionResult.PASS; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java new file mode 100644 index 0000000..35fb17f --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java @@ -0,0 +1,68 @@ +package org.leavesmc.leaves.bot; + +import io.netty.channel.ChannelFutureListener; +import net.minecraft.network.Connection; +import net.minecraft.network.DisconnectionDetails; +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.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 send(@NotNull Packet packet, @Nullable ChannelFutureListener listener) { + } + + @Override + public void disconnect(@NotNull DisconnectionDetails disconnectionInfo) { + } + + @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, @javax.annotation.Nullable ChannelFutureListener channelFutureListener, boolean flag) { + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java new file mode 100644 index 0000000..4d85f5b --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/AbstractBotConfig.java @@ -0,0 +1,67 @@ +package org.leavesmc.leaves.bot.agent; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +import java.util.List; + +public abstract class AbstractBotConfig { + + private final String name; + private final CommandArgument argument; + + protected ServerBot bot; + + public AbstractBotConfig(String name, CommandArgument argument) { + this.name = name; + this.argument = argument; + } + + public AbstractBotConfig setBot(ServerBot bot) { + this.bot = bot; + return this; + } + + public abstract E getValue(); + + public abstract void setValue(E value) throws IllegalArgumentException; + + @SuppressWarnings("unchecked") + public void setFromCommand(@NotNull CommandArgumentResult result) throws IllegalArgumentException { + if (argument == CommandArgument.EMPTY) { + throw new IllegalArgumentException("No argument for " + this.getName()); + } + try { + this.setValue((E) result.read(argument.getArgumentTypes().getFirst().getType())); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid argument type for " + this.getName() + ": " + e.getMessage()); + } + } + + public List getMessage() { + return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue()); + } + + public List getChangeMessage() { + return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue()); + } + + public String getName() { + return name; + } + + public CommandArgument getArgument() { + return argument; + } + + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + nbt.putString("configName", this.name); + return nbt; + } + + public abstract void load(@NotNull CompoundTag nbt); +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java new file mode 100644 index 0000000..6b9cabc --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java @@ -0,0 +1,75 @@ +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 org.leavesmc.leaves.entity.bot.action.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Actions { + + private static final Map> actionsByName = new HashMap<>(); + private static final Map, ServerBotAction> actionsByClass = new HashMap<>(); + + public static void registerAll() { + register(new ServerAttackAction(), AttackAction.class); + register(new ServerBreakBlockAction(), BreakBlockAction.class); + register(new ServerDropAction(), DropAction.class); + register(new ServerJumpAction(), JumpAction.class); + register(new ServerSneakAction(), SneakAction.class); + register(new ServerUseItemAutoAction(), UseItemAutoAction.class); + register(new ServerUseItemAction(), UseItemAction.class); + register(new ServerUseItemOnAction(), UseItemOnAction.class); + register(new ServerUseItemToAction(), UseItemToAction.class); + register(new ServerUseItemOffhandAction(), UseItemOffhandAction.class); + register(new ServerUseItemOnOffhandAction(), UseItemOnOffhandAction.class); + register(new ServerUseItemToOffhandAction(), UseItemToOffhandAction.class); + register(new ServerLookAction(), LookAction.class); + register(new ServerFishAction(), FishAction.class); + register(new ServerSwimAction(), SwimAction.class); + register(new ServerRotationAction(), RotationAction.class); + register(new ServerMoveAction(), MoveAction.class); + register(new ServerMountAction(), MountAction.class); + register(new ServerSwapAction(), SwapAction.class); + } + + public static boolean register(@NotNull ServerBotAction action, Class> type) { + if (!actionsByName.containsKey(action.getName())) { + actionsByName.put(action.getName(), action); + actionsByClass.put(type, action); + return true; + } + return false; + } + + public static boolean unregister(@NotNull String name) { + // TODO add in custom action api + return true; + } + + @NotNull + @Contract(pure = true) + public static Collection> getAll() { + return actionsByName.values(); + } + + @NotNull + public static Set getNames() { + return actionsByName.keySet(); + } + + @Nullable + public static ServerBotAction getForName(String name) { + return actionsByName.get(name); + } + + @Nullable + public static ServerBotAction getForClass(@NotNull Class type) { + return actionsByClass.get(type); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java new file mode 100644 index 0000000..8608a26 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java @@ -0,0 +1,70 @@ +package org.leavesmc.leaves.bot.agent; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.configs.AlwaysSendDataConfig; +import org.leavesmc.leaves.bot.agent.configs.LocatorBarConfig; +import org.leavesmc.leaves.bot.agent.configs.SimulationDistanceConfig; +import org.leavesmc.leaves.bot.agent.configs.SkipSleepConfig; +import org.leavesmc.leaves.bot.agent.configs.SpawnPhantomConfig; +import org.leavesmc.leaves.bot.agent.configs.TickTypeConfig; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +@SuppressWarnings("unused") +public class Configs { + + private static final Map> configs = new HashMap<>(); + + public static final Configs SKIP_SLEEP = register(SkipSleepConfig.class, SkipSleepConfig::new); + public static final Configs ALWAYS_SEND_DATA = register(AlwaysSendDataConfig.class, AlwaysSendDataConfig::new); + public static final Configs SPAWN_PHANTOM = register(SpawnPhantomConfig.class, SpawnPhantomConfig::new); + public static final Configs SIMULATION_DISTANCE = register(SimulationDistanceConfig.class, SimulationDistanceConfig::new); + public static final Configs TICK_TYPE = register(TickTypeConfig.class, TickTypeConfig::new); + public static final Configs ENABLE_LOCATOR_BAR = register(LocatorBarConfig.class, LocatorBarConfig::new); + + private final Class> configClass; + private final Supplier> configCreator; + + private Configs(Class> configClass, Supplier> configCreator) { + this.configClass = configClass; + this.configCreator = configCreator; + } + + public Class> getConfigClass() { + return configClass; + } + + public AbstractBotConfig createConfig(ServerBot bot) { + return configCreator.get().setBot(bot); + } + + @Nullable + public static Configs getConfig(String name) { + return configs.get(name); + } + + @NotNull + @Contract(pure = true) + public static Collection> getConfigs() { + return configs.values(); + } + + @NotNull + @Contract(pure = true) + public static Collection getConfigNames() { + return configs.keySet(); + } + + @NotNull + private static Configs register(Class> configClass, Supplier> configCreator) { + Configs config = new Configs<>(configClass, configCreator); + configs.put(config.createConfig(null).getName(), config); + return config; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java new file mode 100644 index 0000000..c062b3e --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerAttackAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftAttackAction; + +public class ServerAttackAction extends ServerTimerBotAction { + + public ServerAttackAction() { + super("attack", ServerAttackAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + EntityHitResult hitResult = bot.getEntityHitResult(target -> target.isAttackable() && !target.skipAttackInteraction(bot)); + if (hitResult == null) { + return false; + } else { + bot.attack(hitResult.getEntity()); + return true; + } + } + + @Override + public Object asCraft() { + return new CraftAttackAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java new file mode 100644 index 0000000..885d76a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBotAction.java @@ -0,0 +1,237 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import com.mojang.logging.LogUtils; +import net.minecraft.core.UUIDUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.event.bot.BotActionExecuteEvent; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.util.UpdateSuppressionException; +import org.slf4j.Logger; + +import java.util.List; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; + +@SuppressWarnings("unchecked") +public abstract class ServerBotAction> { + + private final String name; + private final CommandArgument argument; + private final Supplier creator; + private UUID uuid; + + private int initialTickDelay; + private int initialTickInterval; + private int initialNumber; + + private int tickToNext; + private int numberRemaining; + private boolean cancel; + + private Consumer onFail; + private Consumer onSuccess; + private Consumer onStop; + private static final Logger LOGGER = LogUtils.getLogger(); + + public ServerBotAction(String name, CommandArgument argument, Supplier creator) { + this.name = name; + this.argument = argument; + this.uuid = UUID.randomUUID(); + this.creator = creator; + + this.cancel = false; + this.setStartDelayTick(0); + this.setDoIntervalTick(1); + this.setDoNumber(1); + } + + public abstract boolean doTick(@NotNull ServerBot bot); + + public abstract Object asCraft(); + + public void init() { + this.tickToNext = initialTickDelay; + this.numberRemaining = this.getDoNumber(); + this.setCancelled(false); + } + + public void tryTick(ServerBot bot) { + if (this.numberRemaining == 0) { + this.stop(bot, BotActionStopEvent.Reason.DONE); + return; + } + + if (this.cancel) { + this.stop(bot, BotActionStopEvent.Reason.PLUGIN); + return; + } + + if (this.tickToNext <= 0) { + BotActionExecuteEvent event = new BotActionExecuteEvent(bot.getBukkitEntity(), name, uuid); + + event.callEvent(); + if (event.getResult() == BotActionExecuteEvent.Result.SOFT_CANCEL) { + this.tickToNext = this.getDoIntervalTick(); + return; + } else if (event.getResult() == BotActionExecuteEvent.Result.HARD_CANCEL) { + if (this.numberRemaining > 0) { + this.numberRemaining--; + } + this.tickToNext = this.getDoIntervalTick(); + return; + } + + boolean result = false; + try { + result = this.doTick(bot); + } catch (UpdateSuppressionException e) { + e.providePlayer(bot); + e.consume(); + } catch (Exception e) { + LOGGER.warn("An error occurred while executing bot " + bot.displayName + ", action " + this.name, e); + } + + if (result) { + if (this.numberRemaining > 0) { + this.numberRemaining--; + } + this.tickToNext = this.getDoIntervalTick(); + if (this.onSuccess != null) { + this.onSuccess.accept((E) this); + } + } else if (this.onFail != null) { + this.onFail.accept((E) this); + } + } else { + this.tickToNext--; + } + } + + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + if (!this.cancel) { + nbt.putString("actionName", this.name); + nbt.store("actionUUID", UUIDUtil.CODEC, this.uuid); + + nbt.putInt("initialTickDelay", this.initialTickDelay); + nbt.putInt("initialTickInterval", this.initialTickInterval); + nbt.putInt("initialNumber", this.initialNumber); + + nbt.putInt("tickToNext", this.tickToNext); + nbt.putInt("numberRemaining", this.numberRemaining); + } + return nbt; + } + + public void load(@NotNull CompoundTag nbt) { + this.uuid = nbt.read("actionUUID", UUIDUtil.CODEC).orElse(UUID.randomUUID()); + + this.initialTickDelay = nbt.getInt("initialTickDelay").orElse(0); + this.initialTickInterval = nbt.getInt("initialTickInterval").orElse(0); + this.initialNumber = nbt.getInt("initialNumber").orElse(0); + + this.tickToNext = nbt.getInt("tickToNext").orElse(0); + this.numberRemaining = nbt.getInt("numberRemaining").orElse(0); + } + + public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { + new BotActionStopEvent(bot.getBukkitEntity(), this.name, this.uuid, reason, null).callEvent(); + this.setCancelled(true); + if (this.onStop != null) { + this.onStop.accept((E) this); + } + } + + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + } + + public void setSuggestion(int n, BiFunction, String>> suggestion) { + this.argument.setSuggestion(n, suggestion); + } + + public void setSuggestion(int n, Pair, String> suggestion) { + this.setSuggestion(n, (sender, arg) -> suggestion); + } + + public void setSuggestion(int n, List tabComplete) { + this.setSuggestion(n, Pair.of(tabComplete, null)); + } + + @NotNull + public E create() { + return this.creator.get(); + } + + public CommandArgument getArgument() { + return this.argument; + } + + public String getName() { + return this.name; + } + + public UUID getUUID() { + return uuid; + } + + public void setStartDelayTick(int initialTickDelay) { + this.initialTickDelay = initialTickDelay; + } + + public int getStartDelayTick() { + return this.initialTickDelay; + } + + public void setDoIntervalTick(int initialTickInterval) { + this.initialTickInterval = Math.max(0, initialTickInterval); + } + + public int getDoIntervalTick() { + return this.initialTickInterval; + } + + public void setDoNumber(int initialNumber) { + this.initialNumber = Math.max(-1, initialNumber); + } + + public int getDoNumber() { + return this.initialNumber; + } + + public int getTickToNext() { + return this.tickToNext; + } + + public int getDoNumberRemaining() { + return this.numberRemaining; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public void setOnFail(Consumer onFail) { + this.onFail = onFail; + } + + public void setOnSuccess(Consumer onSuccess) { + this.onSuccess = onSuccess; + } + + public void setOnStop(Consumer onStop) { + this.onStop = onStop; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java new file mode 100644 index 0000000..470c62f --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerBreakBlockAction.java @@ -0,0 +1,108 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +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.entity.bot.actions.CraftBreakBlockAction; + +public class ServerBreakBlockAction extends ServerTimerBotAction { + + public ServerBreakBlockAction() { + super("break", ServerBreakBlockAction::new); + } + + private ItemStack lastItem = null; + private BlockPos lastPos = null; + private int destroyProgressTime = 0; + private int lastSentState = -1; + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Block block = bot.getBukkitEntity().getTargetBlockExact(5); + if (block != null) { + BlockPos pos = ((CraftBlock) block).getPosition(); + + BlockState iblockdata = bot.level().getBlockState(pos); + if (lastPos == null || !lastPos.equals(pos) || lastItem == null || !lastItem.equals(bot.getMainHandItem())) { + if (lastPos != null && destroyProgressTime > 0) { + bot.level().destroyBlockProgress(bot.getId(), lastPos, -1); + } + lastItem = bot.getMainHandItem(); + lastPos = pos; + destroyProgressTime = 0; + lastSentState = -1; + + if (!iblockdata.isAir()) { + bot.swing(InteractionHand.MAIN_HAND); + EnchantmentHelper.onHitBlock( + bot.level(), bot.getMainHandItem(), bot, bot, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, + item -> bot.onEquippedItemBroken(item, EquipmentSlot.MAINHAND) + ); + iblockdata.attack(bot.level(), pos, bot); + float f = iblockdata.getDestroyProgress(bot, bot.level(), pos); + if (f >= 1.0F) { + bot.gameMode.destroyAndAck(pos, 0, "insta mine"); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + finalBreak(); + return true; + } else { + destroyProgressTime++; + int k = (int) (f * 10.0F); + bot.level().destroyBlockProgress(bot.getId(), pos, k); + lastSentState = k; + } + } + } else { + if (!iblockdata.isAir()) { + bot.swing(InteractionHand.MAIN_HAND); + float damage = this.incrementDestroyProgress(bot, iblockdata, pos); + if (damage >= 1.0F) { + bot.gameMode.destroyAndAck(pos, 0, "destroyed"); + bot.level().destroyBlockProgress(bot.getId(), pos, -1); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + finalBreak(); + return true; + } + } + } + } else { + if (lastPos != null) { + bot.level().destroyBlockProgress(bot.getId(), lastPos, -1); + } + finalBreak(); + } + return false; + } + + private void finalBreak() { + lastPos = null; + lastItem = null; + destroyProgressTime = 0; + lastSentState = -1; + } + + private float incrementDestroyProgress(ServerBot bot, @NotNull BlockState state, BlockPos pos) { + float f = state.getDestroyProgress(bot, bot.level(), pos) * (float) (++destroyProgressTime); + int k = (int) (f * 10.0F); + + if (k != lastSentState) { + bot.level().destroyBlockProgress(bot.getId(), pos, k); + lastSentState = k; + } + + return f; + } + + @Override + public Object asCraft() { + return new CraftBreakBlockAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java new file mode 100644 index 0000000..a2818e7 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerDropAction.java @@ -0,0 +1,23 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftDropAction; + +public class ServerDropAction extends ServerTimerBotAction { + + public ServerDropAction() { + super("drop", ServerDropAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.dropAll(false); + return true; + } + + @Override + public Object asCraft() { + return new CraftDropAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java new file mode 100644 index 0000000..015c877 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerFishAction.java @@ -0,0 +1,80 @@ +package org.leavesmc.leaves.bot.agent.actions; + +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.entity.bot.actions.CraftFishAction; + +public class ServerFishAction extends ServerTimerBotAction { + + public ServerFishAction() { + super("fish", ServerFishAction::new); + } + + private static final int CATCH_ENTITY_DELAY = 20; + + private int initialFishInterval = 0; + private int tickToNextFish = 0; + + @Override + public void setDoIntervalTick(int initialTickInterval) { + super.setDoIntervalTick(0); + this.initialFishInterval = initialTickInterval; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putInt("initialFishInterval", this.initialFishInterval); + nbt.putInt("tickToNextFish", this.tickToNextFish); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.initialFishInterval = nbt.getInt("initialFishInterval").orElseThrow(); + this.tickToNextFish = nbt.getInt("tickToNextFish").orElseThrow(); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (this.tickToNextFish > 0) { + this.tickToNextFish--; + return false; + } + + ItemStack mainHand = bot.getMainHandItem(); + if (mainHand == ItemStack.EMPTY || mainHand.getItem().getClass() != FishingRodItem.class) { + return false; + } + + FishingHook fishingHook = bot.fishing; + if (fishingHook != null) { + if (fishingHook.currentState == FishingHook.FishHookState.HOOKED_IN_ENTITY) { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); + this.tickToNextFish = CATCH_ENTITY_DELAY; + return false; + } + if (fishingHook.nibble > 0) { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); + this.tickToNextFish = this.initialFishInterval - 1; + return true; + } + } else { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); + } + + return false; + } + + @Override + public Object asCraft() { + return new CraftFishAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java new file mode 100644 index 0000000..1930c1a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerJumpAction.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftJumpAction; + +public class ServerJumpAction extends ServerTimerBotAction { + + public ServerJumpAction() { + super("jump", ServerJumpAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (!bot.onGround()) { + return false; + } else { + bot.jumpFromGround(); + } + return true; + } + + @Override + public Object asCraft() { + return new CraftJumpAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java new file mode 100644 index 0000000..abc1085 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerLookAction.java @@ -0,0 +1,110 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.entity.bot.actions.CraftLookAction; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +public class ServerLookAction extends ServerBotAction { + + private static final Vector ZERO_VECTOR = new Vector(0, 0, 0); + private static final DecimalFormat DF = new DecimalFormat("0.0"); + + private Vector pos = ZERO_VECTOR; + private ServerPlayer target = null; + + public ServerLookAction() { + super("look", CommandArgument.of(CommandArgumentType.STRING, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), ServerLookAction::new); + this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ? + Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of(DF.format(player.getX()))).toList(), "|") : + Pair.of(Stream.concat(Arrays.stream(MinecraftServer.getServer().getPlayerNames()), Stream.of("0")).toList(), "|") + ); + this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getY())), "") : Pair.of(List.of("0"), "")); + this.setSuggestion(2, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getZ())), "") : Pair.of(List.of("0"), "")); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + if (target != null) { + this.pos.setX(this.target.getX()); + this.pos.setY(this.target.getY()); + this.pos.setZ(this.target.getZ()); + } + 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").orElse(0.0), + nbt.getDouble("y").orElse(0.0), + nbt.getDouble("z").orElse(0.0) + )); + } + + public void setPos(Vector pos) { + this.pos = pos; + } + + public Vector getPos() { + return this.pos; + } + + public void setTarget(ServerPlayer player) { + this.target = player; + } + + public ServerPlayer getTarget() { + return target; + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (target != null) { + bot.faceLocation(target.getBukkitEntity().getLocation()); + } else { + bot.look(pos.subtract(bot.getLocation().toVector()), false); + } + return true; + } + + @Override + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + String nameOrX = result.readString(player.getScoreboardName()); + ServerPlayer player1 = player.getServer().getPlayerList().getPlayerByName(nameOrX); + if (player1 != null) { + this.setTarget(player1); + return; + } + try { + Vector vector = result.readVectorYZ(Double.parseDouble(nameOrX)); + this.setPos(vector); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid vector"); + } + } + + @Override + public Object asCraft() { + return new CraftLookAction(this); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java new file mode 100644 index 0000000..c4a4a44 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMountAction.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.entity.Entity; +import org.bukkit.Location; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.entity.Vehicle; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.entity.bot.actions.CraftMountAction; + +import java.util.Comparator; +import java.util.List; + +public class ServerMountAction extends ServerBotAction { + + public ServerMountAction() { + super("mount", CommandArgument.EMPTY, ServerMountAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Location center = bot.getBukkitEntity().getLocation(); + List vehicles = center.getNearbyEntitiesByType( + Vehicle.class, + 3, + vehicle -> manhattanDistance(bot, ((CraftEntity) vehicle).getHandle()) <= 2 + ).stream().sorted(Comparator.comparingDouble( + (vehicle) -> center.distanceSquared(vehicle.getLocation()) + )).toList(); + + for (Vehicle vehicle : vehicles) { + if (bot.startRiding(((CraftEntity) vehicle).getHandle(), false)) { + return true; + } + } + + return false; + } + + @Override + public Object asCraft() { + return new CraftMountAction(this); + } + + private double manhattanDistance(@NotNull Entity entity1, @NotNull Entity entity2) { + return Math.abs(entity1.getX() - entity2.getX()) + + Math.abs(entity1.getY() - entity2.getY()) + + Math.abs(entity1.getZ() - entity2.getZ()); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java new file mode 100644 index 0000000..6f3e8b8 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerMoveAction.java @@ -0,0 +1,72 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.entity.bot.action.MoveAction.MoveDirection; +import org.leavesmc.leaves.entity.bot.actions.CraftMoveAction; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +import java.util.Arrays; +import java.util.List; + +public class ServerMoveAction extends ServerStateBotAction { + + private static final Pair, String> suggestions = Pair.of( + Arrays.stream(MoveDirection.values()).map((it) -> it.name).toList(), + "" + ); + private MoveDirection direction = MoveDirection.FORWARD; + + public ServerMoveAction() { + super("move", CommandArgument.of(CommandArgumentType.ofEnum(MoveDirection.class)), ServerMoveAction::new); + this.setSuggestion(0, suggestions); + } + + @Override + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + this.direction = result.read(MoveDirection.class); + if (direction == null) { + throw new IllegalArgumentException("Invalid direction"); + } + } + + @Override + public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { + super.stop(bot, reason); + switch (direction) { + case FORWARD, BACKWARD -> bot.zza = 0.0f; + case LEFT, RIGHT -> bot.xxa = 0.0f; + } + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + boolean isSneaking = bot.isShiftKeyDown(); + float velocity = isSneaking ? 0.3f : 1.0f; + switch (direction) { + case FORWARD -> bot.zza = velocity; + case BACKWARD -> bot.zza = -velocity; + case LEFT -> bot.xxa = velocity; + case RIGHT -> bot.xxa = -velocity; + } + return true; + } + + public MoveDirection getDirection() { + return direction; + } + + public void setDirection(MoveDirection direction) { + this.direction = direction; + } + + @Override + public Object asCraft() { + return new CraftMoveAction(this); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java new file mode 100644 index 0000000..0c278ea --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerRotationAction.java @@ -0,0 +1,83 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.entity.bot.actions.CraftRotationAction; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.Objects; + +public class ServerRotationAction extends ServerBotAction { + + private static final DecimalFormat DF = new DecimalFormat("0.00"); + + public ServerRotationAction() { + super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), ServerRotationAction::new); + this.setSuggestion(0, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getYaw())), "[yaw]") : Pair.of(List.of("0"), "")); + this.setSuggestion(1, (sender, arg) -> sender instanceof Player player ? Pair.of(List.of(DF.format(player.getPitch())), "[pitch]") : Pair.of(List.of("0"), "")); + } + + private float yaw = 0.0f; + private float pitch = 0.0f; + + @Override + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + try { + this.yaw = result.readFloat(Objects.requireNonNull(player).getYRot()); + this.pitch = result.readFloat(player.getXRot()); + } catch (Exception e) { + throw new IllegalArgumentException("No valid rotation specified", e); + } + } + + public void setYaw(float yaw) { + this.yaw = yaw; + } + + public void setPitch(float pitch) { + this.pitch = pitch; + } + + public float getYaw() { + return this.yaw; + } + + public float getPitch() { + return this.pitch; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putFloat("yaw", this.yaw); + nbt.putFloat("pitch", this.pitch); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.setYaw(nbt.getFloat("yaw").orElseThrow()); + this.setPitch(nbt.getFloat("pitch").orElseThrow()); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.setRot(yaw, pitch); + return true; + } + + @Override + public Object asCraft() { + return new CraftRotationAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java new file mode 100644 index 0000000..104cb19 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSneakAction.java @@ -0,0 +1,35 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.entity.bot.actions.CraftSneakAction; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +public class ServerSneakAction extends ServerStateBotAction { + + public ServerSneakAction() { + super("sneak", CommandArgument.EMPTY, ServerSneakAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.isShiftKeyDown()) { + return false; + } + + bot.setShiftKeyDown(true); + return true; + } + + @Override + public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { + super.stop(bot, reason); + bot.setShiftKeyDown(false); + } + + @Override + public Object asCraft() { + return new CraftSneakAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java new file mode 100644 index 0000000..c67b7ac --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerStateBotAction.java @@ -0,0 +1,13 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.leavesmc.leaves.command.CommandArgument; + +import java.util.function.Supplier; + +public abstract class ServerStateBotAction> extends ServerBotAction { + + public ServerStateBotAction(String name, CommandArgument argument, Supplier creator) { + super(name, argument, creator); + this.setDoNumber(-1); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java new file mode 100644 index 0000000..8363b11 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwapAction.java @@ -0,0 +1,29 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.entity.bot.actions.CraftSwapAction; + +public class ServerSwapAction extends ServerBotAction { + + public ServerSwapAction() { + super("swap", CommandArgument.EMPTY, ServerSwapAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + ItemStack mainHandItem = bot.getMainHandItem(); + ItemStack offHandItem = bot.getOffhandItem(); + bot.setItemInHand(InteractionHand.MAIN_HAND, offHandItem); + bot.setItemInHand(InteractionHand.OFF_HAND, mainHandItem); + return true; + } + + @Override + public Object asCraft() { + return new CraftSwapAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java new file mode 100644 index 0000000..50576fe --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerSwimAction.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.entity.bot.actions.CraftSwimAction; + +public class ServerSwimAction extends ServerStateBotAction { + + public ServerSwimAction() { + super("swim", CommandArgument.EMPTY, ServerSwimAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.isInWater()) { + bot.addDeltaMovement(new Vec3(0, 0.03, 0)); + } + return true; + } + + @Override + public Object asCraft() { + return new CraftSwimAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java new file mode 100644 index 0000000..40edda6 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerTimerBotAction.java @@ -0,0 +1,32 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; +import java.util.function.Supplier; + +public abstract class ServerTimerBotAction> extends ServerBotAction { + + public ServerTimerBotAction(String name, Supplier creator) { + this(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator); + } + + public ServerTimerBotAction(String name, CommandArgument argument, Supplier creator) { + super(name, argument, creator); + this.setSuggestion(0, Pair.of(List.of("0"), "[TickDelay]")); + this.setSuggestion(1, Pair.of(List.of("20"), "[TickInterval]")); + this.setSuggestion(2, Pair.of(List.of("1", "-1"), "[DoNumber]")); + } + + @Override + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setStartDelayTick(result.readInt(0)); + this.setDoIntervalTick(result.readInt(20)); + this.setDoNumber(result.readInt(1)); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java new file mode 100644 index 0000000..48c7a29 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseBotAction.java @@ -0,0 +1,127 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionResult; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +import java.util.List; +import java.util.function.Supplier; + +public abstract class ServerUseBotAction> extends ServerTimerBotAction { + private int useTickTimeout = -1; + private int alreadyUsedTick = 0; + private int useItemRemainingTicks = 0; + + public ServerUseBotAction(String name, Supplier supplier) { + super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), supplier); + this.setSuggestion(3, Pair.of(List.of("-1"), "[UseTickTimeout]")); + } + + @Override + public void loadCommand(ServerPlayer player, @NotNull CommandArgumentResult result) { + super.loadCommand(player, result); + this.useTickTimeout = result.readInt(-1); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (shouldStartUseItem()) { + boolean isSuccess = interact(bot); + syncUseItemRemainingTicks(bot); + if (alreadyUseOver()) { + resetAlreadyUsedTick(); + return isSuccess; + } + } else { + syncUseItemRemainingTicks(bot); + } + + if (alreadyUseOver()) { + resetAlreadyUsedTick(); + bot.completeUsingItem(); + return true; + } else { + increaseAlreadyUsedTick(); + if (isUseTickLimitExceeded()) { + resetAlreadyUsedTick(); + shouldStartUseItemNextTick(); + return bot.releaseUsingItemWithResult(); + } + return false; + } + } + + protected abstract boolean interact(ServerBot bot); + + public static boolean shouldSwing(InteractionResult result) { + return result instanceof InteractionResult.Success success && success.swingSource() != InteractionResult.SwingSource.NONE; + } + + private boolean shouldStartUseItem() { + return useItemRemainingTicks == 0; + } + + private boolean alreadyUseOver() { + return useItemRemainingTicks == 0; + } + + private boolean isUseTickLimitExceeded() { + int useTickLimit = useTickTimeout == -1 ? Integer.MAX_VALUE : useTickTimeout; + return alreadyUsedTick > useTickLimit; + } + + private void shouldStartUseItemNextTick() { + this.useItemRemainingTicks = 0; + } + + private void resetAlreadyUsedTick() { + this.alreadyUsedTick = 0; + } + + private void syncUseItemRemainingTicks(@NotNull ServerBot bot) { + this.useItemRemainingTicks = bot.getUseItemRemainingTicks(); + } + + private void increaseAlreadyUsedTick() { + this.alreadyUsedTick++; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putInt("useTick", this.useTickTimeout); + nbt.putInt("alreadyUsedTick", this.alreadyUsedTick); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.useTickTimeout = nbt.getInt("useTick").orElseThrow(); + this.alreadyUsedTick = nbt.getInt("alreadyUsedTick").orElseGet( + () -> this.useTickTimeout - nbt.getInt("tickToRelease").orElseThrow() + ); + } + + public int getUseTickTimeout() { + return useTickTimeout; + } + + public void setUseTickTimeout(int useTickTimeout) { + this.useTickTimeout = useTickTimeout; + } + + @Override + public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { + super.stop(bot, reason); + bot.releaseUsingItem(); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java new file mode 100644 index 0000000..0172692 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAction.java @@ -0,0 +1,33 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemAction; + +public class ServerUseItemAction extends ServerUseBotAction { + + public ServerUseItemAction() { + super("use", ServerUseItemAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + return useItem(bot, InteractionHand.MAIN_HAND).consumesAction(); + } + + public static @NotNull InteractionResult useItem(@NotNull ServerBot bot, InteractionHand hand) { + bot.updateItemInHand(hand); + InteractionResult result = bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(hand), hand); + if (shouldSwing(result)) { + bot.swing(hand); + } + return result; + } + + @Override + public Object asCraft() { + return new CraftUseItemAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java new file mode 100644 index 0000000..ce97921 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemAutoAction.java @@ -0,0 +1,86 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +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.entity.bot.actions.CraftUseItemAutoAction; + +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem; +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn; +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo; + +public class ServerUseItemAutoAction extends ServerUseBotAction { + + public ServerUseItemAutoAction() { + super("use_auto", ServerUseItemAutoAction::new); + } + + @Override + protected boolean interact(ServerBot bot) { + HitResult hitResult = getHitResult(bot); + for (InteractionHand hand : InteractionHand.values()) { + ItemStack itemStack = bot.getItemInHand(hand); + if (!itemStack.isItemEnabled(bot.level().enabledFeatures())) { + return false; + } + + if (hitResult != null) { + switch (hitResult.getType()) { + case ENTITY -> { + EntityHitResult entityHitResult = (EntityHitResult) hitResult; + InteractionResult entityResult = useItemTo(bot, entityHitResult, hand); + if (entityResult instanceof InteractionResult.Success) { + return true; + } else if (entityResult instanceof InteractionResult.Pass && entityHitResult.getEntity() instanceof ArmorStand) { + return false; + } + } + case BLOCK -> { + InteractionResult blockResult = useItemOn(bot, (BlockHitResult) hitResult, hand); + if (blockResult instanceof InteractionResult.Success) { + return true; + } else if (blockResult instanceof InteractionResult.Fail) { + return false; + } + } + } + } + if (!itemStack.isEmpty() && useItem(bot, hand) instanceof InteractionResult.Success) { + return true; + } + } + + return false; + } + + private static @Nullable HitResult getHitResult(@NotNull ServerBot bot) { + Vec3 eyePos = bot.getEyePosition(); + + EntityHitResult entityHitResult = bot.getEntityHitResult(); + double entityDistance = entityHitResult != null ? entityHitResult.getLocation().distanceToSqr(eyePos) : Double.MAX_VALUE; + + BlockHitResult blockHitResult = bot.getBlockHitResult(); + double blockDistance = blockHitResult != null ? blockHitResult.getLocation().distanceToSqr(eyePos) : Double.MAX_VALUE; + + if (entityDistance == Double.MAX_VALUE && blockDistance == Double.MAX_VALUE) { + return null; + } else if (entityDistance < blockDistance) { + return entityHitResult; + } else { + return blockHitResult; + } + } + + @Override + public Object asCraft() { + return new CraftUseItemAutoAction(this); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java new file mode 100644 index 0000000..4983edb --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOffhandAction.java @@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOffhandAction; + +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction.useItem; + +public class ServerUseItemOffhandAction extends ServerUseBotAction { + + public ServerUseItemOffhandAction() { + super("use_offhand", ServerUseItemOffhandAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + return useItem(bot, InteractionHand.OFF_HAND).consumesAction(); + } + + @Override + public Object asCraft() { + return new CraftUseItemOffhandAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java new file mode 100644 index 0000000..bec8358 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnAction.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnAction; + +public class ServerUseItemOnAction extends ServerUseBotAction { + + public ServerUseItemOnAction() { + super("use_on", ServerUseItemOnAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + BlockHitResult hitResult = bot.getBlockHitResult(); + return useItemOn(bot, hitResult, InteractionHand.MAIN_HAND).consumesAction(); + } + + public static InteractionResult useItemOn(ServerBot bot, BlockHitResult hitResult, InteractionHand hand) { + if (hitResult == null) { + return InteractionResult.FAIL; + } + + BlockPos blockPos = hitResult.getBlockPos(); + if (!bot.level().getWorldBorder().isWithinBounds(blockPos)) { + return InteractionResult.FAIL; + } + + bot.updateItemInHand(hand); + InteractionResult interactionResult = bot.gameMode.useItemOn(bot, bot.level(), bot.getItemInHand(hand), hand, hitResult); + if (shouldSwing(interactionResult)) { + bot.swing(hand); + } + + return interactionResult; + } + + @Override + public Object asCraft() { + return new CraftUseItemOnAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java new file mode 100644 index 0000000..09f6956 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemOnOffhandAction.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemOnOffhandAction; + +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction.useItemOn; + +public class ServerUseItemOnOffhandAction extends ServerUseBotAction { + + public ServerUseItemOnOffhandAction() { + super("use_on_offhand", ServerUseItemOnOffhandAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + BlockHitResult hitResult = bot.getBlockHitResult(); + return useItemOn(bot, hitResult, InteractionHand.OFF_HAND).consumesAction(); + } + + @Override + public Object asCraft() { + return new CraftUseItemOnOffhandAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java new file mode 100644 index 0000000..5f29405 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToAction.java @@ -0,0 +1,52 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToAction; + +public class ServerUseItemToAction extends ServerUseBotAction { + + public ServerUseItemToAction() { + super("use_to", ServerUseItemToAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + EntityHitResult hitResult = bot.getEntityHitResult(); + return useItemTo(bot, hitResult, InteractionHand.MAIN_HAND).consumesAction(); + } + + public static InteractionResult useItemTo(ServerBot bot, EntityHitResult hitResult, InteractionHand hand) { + if (hitResult == null) { + return InteractionResult.FAIL; + } + + Entity entity = hitResult.getEntity(); + if (!bot.level().getWorldBorder().isWithinBounds(entity.blockPosition())) { + return InteractionResult.FAIL; + } + + Vec3 vec3 = hitResult.getLocation().subtract(entity.getX(), entity.getY(), entity.getZ()); + bot.updateItemInHand(hand); + InteractionResult interactionResult = entity.interactAt(bot, vec3, hand); + if (!interactionResult.consumesAction()) { + interactionResult = bot.interactOn(hitResult.getEntity(), hand); + } + + if (shouldSwing(interactionResult)) { + bot.swing(hand); + } + + return interactionResult; + } + + @Override + public Object asCraft() { + return new CraftUseItemToAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java new file mode 100644 index 0000000..733246d --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/actions/ServerUseItemToOffhandAction.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.EntityHitResult; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.entity.bot.actions.CraftUseItemToOffhandAction; + +import static org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction.useItemTo; + +public class ServerUseItemToOffhandAction extends ServerUseBotAction { + + public ServerUseItemToOffhandAction() { + super("use_to_offhand", ServerUseItemToOffhandAction::new); + } + + @Override + protected boolean interact(@NotNull ServerBot bot) { + EntityHitResult hitResult = bot.getEntityHitResult(); + return useItemTo(bot, hitResult, InteractionHand.OFF_HAND).consumesAction(); + } + + @Override + public Object asCraft() { + return new CraftUseItemToOffhandAction(this); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java new file mode 100644 index 0000000..f954c20 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java @@ -0,0 +1,45 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class AlwaysSendDataConfig extends AbstractBotConfig { + + public static final String NAME = "always_send_data"; + + private boolean value; + + public AlwaysSendDataConfig() { + super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + this.value = FakeplayerConfig.canSendDataAlways; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public void setValue(Boolean value) { + this.value = value; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean(NAME, this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSendDataAlways)); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java new file mode 100644 index 0000000..0167f38 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/LocatorBarConfig.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.waypoints.ServerWaypointManager; +import org.jetbrains.annotations.NotNull; +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class LocatorBarConfig extends AbstractBotConfig { + + public static final String NAME = "enable_locator_bar"; + + private boolean value; + + public LocatorBarConfig() { + super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + this.value = FakeplayerConfig.enableLocatorBar; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public void setValue(Boolean value) throws IllegalArgumentException { + this.value = value; + ServerWaypointManager manager = this.bot.level().getWaypointManager(); + if (value) { + manager.trackWaypoint(this.bot); + } else { + manager.untrackWaypoint(this.bot); + } + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean(NAME, this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.enableLocatorBar)); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java new file mode 100644 index 0000000..71f49f1 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java @@ -0,0 +1,46 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.nbt.CompoundTag; +import org.apache.commons.lang3.tuple.Pair; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class SimulationDistanceConfig extends AbstractBotConfig { + + public static final String NAME = "simulation_distance"; + + public SimulationDistanceConfig() { + super(NAME, CommandArgument.of(CommandArgumentType.INTEGER).setSuggestion(0, Pair.of(List.of("2", "10"), ""))); + } + + @Override + public Integer getValue() { + return this.bot.getBukkitEntity().getSimulationDistance(); + } + + @Override + public void setValue(Integer value) { + if (value < 2 || value > 32) { + throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + value); + } + this.bot.getBukkitEntity().setSimulationDistance(value); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putInt(NAME, this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(nbt.getIntOr(NAME, FakeplayerConfig.getSimulationDistance(this.bot))); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java new file mode 100644 index 0000000..940fedf --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java @@ -0,0 +1,41 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class SkipSleepConfig extends AbstractBotConfig { + + public static final String NAME = "skip_sleep"; + + public SkipSleepConfig() { + super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + } + + @Override + public Boolean getValue() { + return bot.fauxSleeping; + } + + @Override + public void setValue(Boolean value) throws IllegalArgumentException { + bot.fauxSleeping = value; + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean(NAME, this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSkipSleep)); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java new file mode 100644 index 0000000..acb57ae --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java @@ -0,0 +1,52 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class SpawnPhantomConfig extends AbstractBotConfig { + + public static final String NAME = "spawn_phantom"; + + private boolean value; + + public SpawnPhantomConfig() { + super(NAME, CommandArgument.of(CommandArgumentType.BOOLEAN).setSuggestion(0, List.of("true", "false"))); + this.value = FakeplayerConfig.canSpawnPhantom; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public void setValue(Boolean value) throws IllegalArgumentException { + this.value = 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(NAME, this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(nbt.getBooleanOr(NAME, FakeplayerConfig.canSpawnPhantom)); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java new file mode 100644 index 0000000..72f303a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/agent/configs/TickTypeConfig.java @@ -0,0 +1,47 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import fun.bm.lophine.config.modules.misc.FakeplayerConfig; +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.AbstractBotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class TickTypeConfig extends AbstractBotConfig { + + private static final String NAME = "tick_type"; + private static final CommandArgumentType TICK_TYPE_ARGUMENT = CommandArgumentType.ofEnum(ServerBot.TickType.class); + + private ServerBot.TickType value; + + public TickTypeConfig() { + super(NAME, CommandArgument.of(TICK_TYPE_ARGUMENT).setSuggestion(0, List.of("network", "entity_list"))); + this.value = FakeplayerConfig.tickType; + } + + @Override + public ServerBot.TickType getValue() { + return value; + } + + @Override + public void setValue(ServerBot.TickType value) throws IllegalArgumentException { + this.value = value; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putString(NAME, this.getValue().toString()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(TICK_TYPE_ARGUMENT.parse(nbt.getStringOr(NAME, FakeplayerConfig.tickType.name()))); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java new file mode 100644 index 0000000..bc0c3dc --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgument.java @@ -0,0 +1,67 @@ +package org.leavesmc.leaves.command; + +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; + +public class CommandArgument { + + public static final CommandArgument EMPTY = new CommandArgument(); + + private static final Pair, String> EMPTY_SUGGESTION_RESULT = Pair.of(List.of(), null); + private static final BiFunction, String>> EMPTY_SUGGESTION = (sender, arg) -> EMPTY_SUGGESTION_RESULT; + + private final List, String>>> suggestions; + private final List> argumentTypes; + + private CommandArgument(CommandArgumentType... argumentTypes) { + this.argumentTypes = List.of(argumentTypes); + this.suggestions = new ArrayList<>(); + for (int i = 0; i < argumentTypes.length; i++) { + suggestions.add(EMPTY_SUGGESTION); + } + } + + public static CommandArgument of(CommandArgumentType... argumentTypes) { + return new CommandArgument(argumentTypes); + } + + public List> getArgumentTypes() { + return argumentTypes; + } + + public CommandArgument setSuggestion(int n, BiFunction, String>> suggestion) { + this.suggestions.set(n, suggestion); + return this; + } + + public CommandArgument setSuggestion(int n, Pair, String> suggestion) { + return this.setSuggestion(n, (sender, arg) -> suggestion); + } + + public CommandArgument setSuggestion(int n, List tabComplete) { + return this.setSuggestion(n, Pair.of(tabComplete, null)); + } + + public Pair, String> suggestion(int n, CommandSender sender, String arg) { + if (suggestions.size() > n) { + return suggestions.get(n).apply(sender, arg); + } else { + return EMPTY_SUGGESTION.apply(sender, arg); + } + } + + public CommandArgumentResult parse(int index, String @NotNull [] args) { + Object[] result = new Object[argumentTypes.size()]; + Arrays.fill(result, null); + for (int i = index, j = 0; i < args.length && j < result.length; i++, j++) { + result[j] = argumentTypes.get(j).parse(args[i]); + } + return new CommandArgumentResult(new ArrayList<>(Arrays.asList(result))); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java new file mode 100644 index 0000000..a56cd7b --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentResult.java @@ -0,0 +1,92 @@ +package org.leavesmc.leaves.command; + +import net.minecraft.core.BlockPos; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public class CommandArgumentResult { + + private final List result; + + public CommandArgumentResult(List result) { + this.result = result; + } + + public int readInt(int def) { + return Objects.requireNonNullElse(read(Integer.class), def); + } + + public double readDouble(double def) { + return Objects.requireNonNullElse(read(Double.class), def); + } + + public float readFloat(float def) { + return Objects.requireNonNullElse(read(Float.class), def); + } + + public String readString(String def) { + return Objects.requireNonNullElse(read(String.class), def); + } + + public boolean readBoolean(boolean def) { + return Objects.requireNonNullElse(read(Boolean.class), def); + } + + public BlockPos readPos() { + Integer[] pos = {read(Integer.class), read(Integer.class), read(Integer.class)}; + for (Integer po : pos) { + if (po == null) { + return null; + } + } + return new BlockPos(pos[0], pos[1], pos[2]); + } + + public @Nullable Vector readVector() { + Double[] pos = {read(Double.class), read(Double.class), read(Double.class)}; + for (Double po : pos) { + if (po == null) { + return null; + } + } + return new Vector(pos[0], pos[1], pos[2]); + } + + public @NotNull Vector readVectorYZ(double x) { + Double[] pos = {x, read(Double.class), read(Double.class)}; + for (Double po : pos) { + if (po == null) { + throw new IllegalArgumentException("Failed to read vector!"); + } + } + return new Vector(pos[0], pos[1], pos[2]); + } + + public Object readObject() { + if (result.isEmpty()) { + return null; + } + return result.removeFirst(); + } + + public T read(Class tClass, T def) { + return Objects.requireNonNullElse(read(tClass), def); + } + + public T read(Class tClass) { + if (result.isEmpty()) { + return null; + } + + Object obj = result.removeFirst(); + if (tClass.isInstance(obj)) { + return tClass.cast(obj); + } else { + return null; + } + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java new file mode 100644 index 0000000..b39f6f4 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/command/CommandArgumentType.java @@ -0,0 +1,59 @@ +package org.leavesmc.leaves.command; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Function; + +public abstract class CommandArgumentType { + + public static final CommandArgumentType STRING = CommandArgumentType.string(); + public static final CommandArgumentType INTEGER = CommandArgumentType.of(Integer.class, Integer::parseInt); + public static final CommandArgumentType DOUBLE = CommandArgumentType.of(Double.class, Double::parseDouble); + public static final CommandArgumentType FLOAT = CommandArgumentType.of(Float.class, Float::parseFloat); + public static final CommandArgumentType BOOLEAN = CommandArgumentType.of(Boolean.class, Boolean::parseBoolean); + + private final Class type; + + private CommandArgumentType(Class type) { + this.type = type; + } + + @NotNull + @Contract(value = "_, _ -> new", pure = true) + public static CommandArgumentType of(Class type, Function parse) { + return new CommandArgumentType<>(type) { + @Override + public E parse(@NotNull String arg) { + try { + return parse.apply(arg); + } catch (Exception ignore) { + return null; + } + } + }; + } + + @NotNull + @Contract(value = "_ -> new", pure = true) + public static > CommandArgumentType ofEnum(Class type) { + return of(type, (string -> Enum.valueOf(type, string.toUpperCase()))); + } + + @NotNull + @Contract(value = " -> new", pure = true) + private static CommandArgumentType string() { + return new CommandArgumentType<>(String.class) { + @Override + public String parse(@NotNull String arg) { + return arg; + } + }; + } + + public Class getType() { + return type; + } + + public abstract E parse(@NotNull String arg); +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java new file mode 100644 index 0000000..de83f04 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBot.java @@ -0,0 +1,97 @@ +package org.leavesmc.leaves.entity.bot; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.entity.bot.action.BotAction; +import org.leavesmc.leaves.entity.bot.actions.CraftBotAction; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; + +import java.util.UUID; + +public class CraftBot extends CraftPlayer implements Bot { + + public CraftBot(CraftServer server, ServerBot entity) { + super(server, entity); + } + + @Override + public String getSkinName() { + return this.getHandle().createState.skinName(); + } + + @Override + public @NotNull String getRealName() { + return this.getHandle().createState.realName(); + } + + @Override + public @Nullable UUID getCreatePlayerUUID() { + return this.getHandle().createPlayer; + } + + @Override + public > void addAction(@NotNull T action) { + switch (action) { + case CraftBotAction act -> this.getHandle().addBotAction(act.getHandle(), null); + default -> throw new IllegalArgumentException("Action " + action.getClass().getName() + " is not a valid BotAction type!"); + } + } + + @Override + public BotAction getAction(int index) { + return (BotAction) this.getHandle().getBotActions().get(index).asCraft(); + } + + @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 (ServerBotAction action : this.getHandle().getBotActions()) { + action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); + } + } + + @Override + public boolean remove(boolean save) { + BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); + return true; + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.@NotNull TeleportCause cause, io.papermc.paper.entity.TeleportFlag @NotNull ... flags) { + Preconditions.checkArgument(location != null, "location cannot be null"); + Preconditions.checkState(location.getWorld().equals(this.getWorld()), "[Leaves] Fakeplayers do not support changing world, Please use leaves fakeplayer-api instead!"); + return super.teleport(location, cause, flags); + } + + @Override + public ServerBot getHandle() { + return (ServerBot) entity; + } + + public void setHandle(final ServerBot entity) { + super.setHandle(entity); + } + + @Override + public String toString() { + return "CraftBot{" + "name=" + getName() + '}'; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java new file mode 100644 index 0000000..dcbf233 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/CraftBotManager.java @@ -0,0 +1,74 @@ +package org.leavesmc.leaves.entity.bot; + +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.actions.ServerBotAction; +import org.leavesmc.leaves.entity.bot.action.BotAction; +import org.leavesmc.leaves.event.bot.BotCreateEvent; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +public class CraftBotManager implements BotManager { + + private final BotList botList; + private final Collection botViews; + + public CraftBotManager() { + this.botList = MinecraftServer.getServer().getBotList(); + this.botViews = Collections.unmodifiableList(Lists.transform(botList.bots, ServerBot::getBukkitEntity)); + } + + @Override + public @Nullable Bot getBot(@NotNull UUID uuid) { + ServerBot bot = botList.getBot(uuid); + if (bot != null) { + return bot.getBukkitEntity(); + } else { + return null; + } + } + + @Override + public @Nullable Bot getBot(@NotNull String name) { + ServerBot bot = botList.getBotByName(name); + if (bot != null) { + return bot.getBukkitEntity(); + } else { + return null; + } + } + + @Override + public Collection getBots() { + return botViews; + } + + @SuppressWarnings("unchecked") + @Override + public > T newAction(@NotNull Class type) { + ServerBotAction action = Actions.getForClass(type); + if (action == null) { + throw new IllegalArgumentException("No action registered for type: " + type.getName()); + } else { + try { + return (T) action.create().asCraft(); + } catch (Exception e) { + throw new RuntimeException("Failed to create action of type: " + type.getName(), e); + } + } + } + + @Override + public BotCreator botCreator(@NotNull String realName, @NotNull Location location) { + return BotCreateState.builder(realName, location).createReason(BotCreateEvent.CreateReason.PLUGIN); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftAttackAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftAttackAction.java new file mode 100644 index 0000000..cdf4d0d --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftAttackAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerAttackAction; +import org.leavesmc.leaves.entity.bot.action.AttackAction; + +public class CraftAttackAction extends CraftTimerBotAction implements AttackAction { + + public CraftAttackAction(ServerAttackAction serverAction) { + super(serverAction, CraftAttackAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java new file mode 100644 index 0000000..f3fbd60 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBotAction.java @@ -0,0 +1,86 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.ServerBotAction; +import org.leavesmc.leaves.entity.bot.action.BotAction; + +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class CraftBotAction, S extends ServerBotAction> implements BotAction { + + protected final S serverAction; + protected final Function creator; + + protected Consumer onFail = null; + protected Consumer onSuccess = null; + protected Consumer onStop = null; + + public CraftBotAction(S serverAction, Function creator) { + this.serverAction = serverAction; + this.creator = creator; + } + + public ServerBotAction getHandle() { + return serverAction; + } + + @Override + public String getName() { + return serverAction.getName(); + } + + @Override + public UUID getUUID() { + return serverAction.getUUID(); + } + + public boolean doTick(@NotNull ServerBot bot) { + return serverAction.doTick(bot); + } + + @Override + public void setOnFail(Consumer onFail) { + this.onFail = onFail; + serverAction.setOnFail(it -> onFail.accept(creator.apply(it))); + } + + @Override + public Consumer getOnFail() { + return onFail; + } + + @Override + public void setOnSuccess(Consumer onSuccess) { + this.onSuccess = onSuccess; + serverAction.setOnSuccess(it -> onSuccess.accept(creator.apply(it))); + } + + @Override + public Consumer getOnSuccess() { + return onSuccess; + } + + @Override + public void setOnStop(Consumer onStop) { + this.onStop = onStop; + serverAction.setOnStop(it -> onStop.accept(creator.apply(it))); + } + + @Override + public Consumer getOnStop() { + return onStop; + } + + @Override + public void setCancelled(boolean cancel) { + serverAction.setCancelled(cancel); + } + + @Override + public boolean isCancelled() { + return serverAction.isCancelled(); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBreakBlockAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBreakBlockAction.java new file mode 100644 index 0000000..6abced3 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftBreakBlockAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerBreakBlockAction; +import org.leavesmc.leaves.entity.bot.action.BreakBlockAction; + +public class CraftBreakBlockAction extends CraftTimerBotAction implements BreakBlockAction { + + public CraftBreakBlockAction(ServerBreakBlockAction serverAction) { + super(serverAction, CraftBreakBlockAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftDropAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftDropAction.java new file mode 100644 index 0000000..656f45d --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftDropAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerDropAction; +import org.leavesmc.leaves.entity.bot.action.DropAction; + +public class CraftDropAction extends CraftTimerBotAction implements DropAction { + + public CraftDropAction(ServerDropAction serverAction) { + super(serverAction, CraftDropAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftFishAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftFishAction.java new file mode 100644 index 0000000..eee701e --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftFishAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerFishAction; +import org.leavesmc.leaves.entity.bot.action.FishAction; + +public class CraftFishAction extends CraftTimerBotAction implements FishAction { + + public CraftFishAction(ServerFishAction serverAction) { + super(serverAction, CraftFishAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftJumpAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftJumpAction.java new file mode 100644 index 0000000..26cc30f --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftJumpAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerJumpAction; +import org.leavesmc.leaves.entity.bot.action.JumpAction; + +public class CraftJumpAction extends CraftTimerBotAction implements JumpAction { + + public CraftJumpAction(ServerJumpAction serverAction) { + super(serverAction, CraftJumpAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftLookAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftLookAction.java new file mode 100644 index 0000000..f3f02d2 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftLookAction.java @@ -0,0 +1,36 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.leavesmc.leaves.bot.agent.actions.ServerLookAction; +import org.leavesmc.leaves.entity.bot.action.LookAction; + +public class CraftLookAction extends CraftBotAction implements LookAction { + + public CraftLookAction(ServerLookAction serverAction) { + super(serverAction, CraftLookAction::new); + } + + @Override + public LookAction setPos(Vector pos) { + serverAction.setPos(pos); + return this; + } + + @Override + public Vector getPos() { + return serverAction.getPos(); + } + + @Override + public LookAction setTarget(Player player) { + serverAction.setTarget(((CraftPlayer) player).getHandle()); + return this; + } + + @Override + public Player getTarget() { + return serverAction.getTarget().getBukkitEntity(); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMountAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMountAction.java new file mode 100644 index 0000000..ec7a80c --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMountAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerMountAction; +import org.leavesmc.leaves.entity.bot.action.MountAction; + +public class CraftMountAction extends CraftBotAction implements MountAction { + + public CraftMountAction(ServerMountAction serverAction) { + super(serverAction, CraftMountAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMoveAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMoveAction.java new file mode 100644 index 0000000..5891cca --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftMoveAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerMoveAction; +import org.leavesmc.leaves.entity.bot.action.MoveAction; + +public class CraftMoveAction extends CraftBotAction implements MoveAction { + + public CraftMoveAction(ServerMoveAction serverAction) { + super(serverAction, CraftMoveAction::new); + } + + @Override + public MoveDirection getDirection() { + return serverAction.getDirection(); + } + + @Override + public MoveAction setDirection(MoveDirection direction) { + serverAction.setDirection(direction); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftRotationAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftRotationAction.java new file mode 100644 index 0000000..80ded8a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftRotationAction.java @@ -0,0 +1,33 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerRotationAction; +import org.leavesmc.leaves.entity.bot.action.RotationAction; + +public class CraftRotationAction extends CraftBotAction implements RotationAction { + + public CraftRotationAction(ServerRotationAction serverAction) { + super(serverAction, CraftRotationAction::new); + } + + @Override + public RotationAction setYaw(float yaw) { + serverAction.setYaw(yaw); + return this; + } + + @Override + public RotationAction setPitch(float pitch) { + serverAction.setPitch(pitch); + return this; + } + + @Override + public float getYaw() { + return serverAction.getYaw(); + } + + @Override + public float getPitch() { + return serverAction.getPitch(); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSneakAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSneakAction.java new file mode 100644 index 0000000..6504158 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSneakAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerSneakAction; +import org.leavesmc.leaves.entity.bot.action.SneakAction; + +public class CraftSneakAction extends CraftBotAction implements SneakAction { + + public CraftSneakAction(ServerSneakAction serverAction) { + super(serverAction, CraftSneakAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwapAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwapAction.java new file mode 100644 index 0000000..455a89a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwapAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerSwapAction; +import org.leavesmc.leaves.entity.bot.action.SwapAction; + +public class CraftSwapAction extends CraftBotAction implements SwapAction { + + public CraftSwapAction(ServerSwapAction serverAction) { + super(serverAction, CraftSwapAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwimAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwimAction.java new file mode 100644 index 0000000..ab8986a --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftSwimAction.java @@ -0,0 +1,11 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerSwimAction; +import org.leavesmc.leaves.entity.bot.action.SwimAction; + +public class CraftSwimAction extends CraftBotAction implements SwimAction { + + public CraftSwimAction(ServerSwimAction serverAction) { + super(serverAction, CraftSwimAction::new); + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java new file mode 100644 index 0000000..532c3e3 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftTimerBotAction.java @@ -0,0 +1,53 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerTimerBotAction; +import org.leavesmc.leaves.entity.bot.action.TimerBotAction; + +import java.util.function.Function; + +public class CraftTimerBotAction, S extends ServerTimerBotAction> extends CraftBotAction implements TimerBotAction { + + public CraftTimerBotAction(S serverAction, Function creator) { + super(serverAction, creator); + } + + @Override + public void setStartDelayTick(int delayTick) { + serverAction.setStartDelayTick(delayTick); + } + + @Override + public int getStartDelayTick() { + return serverAction.getStartDelayTick(); + } + + @Override + public void setDoIntervalTick(int intervalTick) { + serverAction.setDoIntervalTick(intervalTick); + } + + @Override + public int getDoIntervalTick() { + return serverAction.getDoIntervalTick(); + } + + @Override + public void setDoNumber(int doNumber) { + serverAction.setDoNumber(doNumber); + } + + @Override + public int getDoNumber() { + return serverAction.getDoNumber(); + } + + @Override + public int getTickToNext() { + return serverAction.getTickToNext(); + } + + @Override + public int getDoNumberRemaining() { + return serverAction.getDoNumberRemaining(); + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAction.java new file mode 100644 index 0000000..23f551e --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAction.java @@ -0,0 +1,28 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemAction; +import org.leavesmc.leaves.entity.bot.action.UseItemAction; + +public class CraftUseItemAction extends CraftTimerBotAction implements UseItemAction { + + public CraftUseItemAction(ServerUseItemAction serverAction) { + super(serverAction, CraftUseItemAction::new); + } + + public boolean doTick(@NotNull ServerBot bot) { + return serverAction.doTick(bot); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAutoAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAutoAction.java new file mode 100644 index 0000000..8b28cf7 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemAutoAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemAutoAction; +import org.leavesmc.leaves.entity.bot.action.UseItemAutoAction; + +public class CraftUseItemAutoAction extends CraftTimerBotAction implements UseItemAutoAction { + + public CraftUseItemAutoAction(ServerUseItemAutoAction serverAction) { + super(serverAction, CraftUseItemAutoAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemAutoAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOffhandAction.java new file mode 100644 index 0000000..04f1d13 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOffhandAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemOffhandAction; +import org.leavesmc.leaves.entity.bot.action.UseItemOffhandAction; + +public class CraftUseItemOffhandAction extends CraftTimerBotAction implements UseItemOffhandAction { + + public CraftUseItemOffhandAction(ServerUseItemOffhandAction serverAction) { + super(serverAction, CraftUseItemOffhandAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemOffhandAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnAction.java new file mode 100644 index 0000000..b3891b6 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnAction; +import org.leavesmc.leaves.entity.bot.action.UseItemOnAction; + +public class CraftUseItemOnAction extends CraftTimerBotAction implements UseItemOnAction { + + public CraftUseItemOnAction(ServerUseItemOnAction serverAction) { + super(serverAction, CraftUseItemOnAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemOnAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnOffhandAction.java new file mode 100644 index 0000000..6019048 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemOnOffhandAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemOnOffhandAction; +import org.leavesmc.leaves.entity.bot.action.UseItemOnOffhandAction; + +public class CraftUseItemOnOffhandAction extends CraftTimerBotAction implements UseItemOnOffhandAction { + + public CraftUseItemOnOffhandAction(ServerUseItemOnOffhandAction serverAction) { + super(serverAction, CraftUseItemOnOffhandAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemOnOffhandAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToAction.java new file mode 100644 index 0000000..01dbd0f --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemToAction; +import org.leavesmc.leaves.entity.bot.action.UseItemToAction; + +public class CraftUseItemToAction extends CraftTimerBotAction implements UseItemToAction { + + public CraftUseItemToAction(ServerUseItemToAction serverAction) { + super(serverAction, CraftUseItemToAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemToAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToOffhandAction.java b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToOffhandAction.java new file mode 100644 index 0000000..ad7e5e6 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/entity/bot/actions/CraftUseItemToOffhandAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.entity.bot.actions; + +import org.leavesmc.leaves.bot.agent.actions.ServerUseItemToOffhandAction; +import org.leavesmc.leaves.entity.bot.action.UseItemToOffhandAction; + +public class CraftUseItemToOffhandAction extends CraftTimerBotAction implements UseItemToOffhandAction { + + public CraftUseItemToOffhandAction(ServerUseItemToOffhandAction serverAction) { + super(serverAction, CraftUseItemToOffhandAction::new); + } + + @Override + public int getUseTickTimeout() { + return serverAction.getUseTickTimeout(); + } + + @Override + public CraftUseItemToOffhandAction setUseTickTimeout(int timeout) { + serverAction.setUseTickTimeout(timeout); + return this; + } +} \ No newline at end of file diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/util/MathUtils.java b/lophine-server/src/main/java/org/leavesmc/leaves/util/MathUtils.java new file mode 100644 index 0000000..5605015 --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/util/MathUtils.java @@ -0,0 +1,96 @@ +package org.leavesmc.leaves.util; + +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.regex.Pattern; + +public class MathUtils { + + private static final Pattern numericPattern = Pattern.compile("^-?[1-9]\\d*$|^0$"); + + public static boolean isNumeric(String str) { + return numericPattern.matcher(str).matches(); + } + + public static float @NotNull [] fetchYawPitch(@NotNull Vector dir) { + double x = dir.getX(); + double z = dir.getZ(); + + float[] out = new float[2]; + + if (x == 0.0D && z == 0.0D) { + out[1] = (float) (dir.getY() > 0.0D ? -90 : 90); + } else { + double theta = Math.atan2(-x, z); + out[0] = (float) Math.toDegrees((theta + 6.283185307179586D) % 6.283185307179586D); + + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + out[1] = (float) Math.toDegrees(Math.atan(-dir.getY() / xz)); + } + + return out; + } + + public static float fetchPitch(@NotNull Vector dir) { + double x = dir.getX(); + double z = dir.getZ(); + + float result; + + if (x == 0.0D && z == 0.0D) { + result = (float) (dir.getY() > 0.0D ? -90 : 90); + } else { + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + result = (float) Math.toDegrees(Math.atan(-dir.getY() / xz)); + } + + return result; + } + + @NotNull + public static Vector getDirection(double rotX, double rotY) { + Vector vector = new Vector(); + + rotX = Math.toRadians(rotX); + rotY = Math.toRadians(rotY); + + double xz = Math.abs(Math.cos(rotY)); + + vector.setX(-Math.sin(rotX) * xz); + vector.setZ(Math.cos(rotX) * xz); + vector.setY(-Math.sin(rotY)); + + return vector; + } + + private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + + public static int floorLog2(int value) { + return ceilLog2(value) - (isPowerOfTwo(value) ? 0 : 1); + } + + public static int ceilLog2(int value) { + value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value); + return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int) ((long) value * 125613361L >> 27) & 31]; + } + + public static boolean isPowerOfTwo(int value) { + return value != 0 && (value & value - 1) == 0; + } + + public static int smallestEncompassingPowerOfTwo(int value) { + int i = value - 1; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return i + 1; + } +} diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/util/UpdateSuppressionException.java b/lophine-server/src/main/java/org/leavesmc/leaves/util/UpdateSuppressionException.java new file mode 100644 index 0000000..043297d --- /dev/null +++ b/lophine-server/src/main/java/org/leavesmc/leaves/util/UpdateSuppressionException.java @@ -0,0 +1,127 @@ +package org.leavesmc.leaves.util; + +import com.mojang.logging.LogUtils; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.event.player.UpdateSuppressionEvent; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class UpdateSuppressionException extends RuntimeException { + private @Nullable BlockPos pos; + private @Nullable Level level; + private @Nullable Block source; + private @Nullable ServerPlayer player; + private final @NotNull Throwable throwable; + private static final Logger LOGGER = LogUtils.getLogger(); + + public UpdateSuppressionException( + @Nullable BlockPos pos, + @Nullable Level level, + @Nullable Block source, + @Nullable ServerPlayer player, + @NotNull Throwable throwable + ) { + super("Update Suppression"); + this.pos = pos; + this.level = level; + this.source = source; + this.player = player; + this.throwable = throwable; + } + + public void providePlayer(@NotNull ServerPlayer player) { + if (this.level == null) { + this.level = player.level(); + } + this.player = player; + } + + public void provideLevel(@NotNull Level level) { + if (this.level != null) { + this.level = level; + } + } + + public void provideBlock(@NotNull Level level, @NotNull BlockPos pos, @NotNull Block source) { + provideLevel(level); + provideBlock(pos, source); + } + + public void provideBlock(@NotNull BlockPos pos, @NotNull Block source) { + if (this.pos != null) { + this.pos = pos; + } + if (this.source != null) { + this.source = source; + } + } + + public void consume() { + submitEvent(); + LOGGER.info(getMessage()); + } + + private void submitEvent() { + Location location = null; + if (pos != null && level != null) { + location = new Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + Material material = null; + if (source != null) { + material = source.defaultBlockState().getBukkitMaterial(); + } + Player bukkitPlayer = null; + if (player != null) { + bukkitPlayer = player.getBukkitEntity(); + } + new UpdateSuppressionEvent(bukkitPlayer, location, material, throwable).callEvent(); + } + + @Override + public String getMessage() { + List messages = new ArrayList<>(); + messages.add("An %s update suppression was triggered".formatted(getTypeName())); + if (source != null) { + messages.add("from %s".formatted(source.defaultBlockState().getBukkitMaterial().name())); + } + if (pos != null) { + messages.add("at [x:%d,y:%d,z:%d]".formatted(pos.getX(), pos.getY(), pos.getZ())); + } + if (level != null) { + messages.add("in %s".formatted(level.dimension().location())); + } + if (player != null) { + if (player instanceof ServerBot) { + messages.add("by %s[bot]".formatted(player.displayName)); + } else { + messages.add("by %s".formatted(player.displayName)); + } + } + return String.join(" ", messages); + } + + @Contract(pure = true) + private @NotNull String getTypeName() { + Class type = throwable.getClass(); + if (type == ClassCastException.class) { + return "CCE"; + } else if (type == StackOverflowError.class) { + return "SOE"; + } else if (type == IllegalArgumentException.class) { + return "IAE"; + } + return type.getSimpleName(); + } +}