9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-19 14:59:32 +00:00
Files
LeavesMC/patches/server/0010-Fakeplayer-support.patch
2024-02-05 16:36:42 +08:00

3297 lines
125 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Thu, 3 Feb 2022 12:28:15 +0800
Subject: [PATCH] Fakeplayer support
diff --git a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java
index a327973e37b5b8d4e15683ef24548482ac3dc3d5..65d82963d611a6dbbd7ca58d363854e4fad59230 100644
--- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java
+++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java
@@ -42,6 +42,7 @@ public abstract class SimpleCriterionTrigger<T extends SimpleCriterionTrigger.Si
}
protected void trigger(ServerPlayer player, Predicate<T> predicate) {
+ if (player instanceof top.leavesmc.leaves.bot.ServerBot) return; // Leaves - bot skip
PlayerAdvancements playerAdvancements = player.getAdvancements();
Set<CriterionTrigger.Listener<T>> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak
if (set != null && !set.isEmpty()) {
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 4716f8bd8a64d4f20f0d5957c1e7fabf63020f43..13b0a965eaee5ba2f6da420c3c02dc719fa73dd6 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -339,6 +339,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
+ // Leaves start - fakeplayer
+ public void setListenerForce(PacketListener packetListener) {
+ Validate.notNull(packetListener, "packetListener");
+ this.packetListener = packetListener;
+ this.disconnectListener = null;
+ }
+ // Leaves end - fakeplayer
+
public void setListenerForServerboundHandshake(PacketListener packetListener) {
if (this.packetListener != null) {
throw new IllegalStateException("Listener already set");
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 77e952a502bebfb31311b47ceed0b88118278d4a..91d82c02eb152e3565e52fc1e01ce37855a39bdd 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -663,6 +663,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Paper end - Configurable player collision
+ top.leavesmc.leaves.bot.ServerBot.loadAllBot(); // Leaves - load resident bot
+
this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
this.connection.acceptConnections();
@@ -963,6 +965,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
MinecraftServer.LOGGER.info("Stopping server");
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
+ top.leavesmc.leaves.bot.ServerBot.saveOrRemoveAllBot(); // Leaves - save or remove bot
// CraftBukkit start
if (this.server != null) {
this.server.disablePlugins();
diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
index 24e5993b281448734eb67c7a8439a349bbf9fd72..677c4d5360509f212ccbe4ff7418e0e7ee1fbb59 100644
--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
@@ -221,6 +221,11 @@ public class PlayerAdvancements {
}
public boolean award(AdvancementHolder advancement, String criterionName) {
+ // Leaves start - bot can't get advancement
+ if (player instanceof top.leavesmc.leaves.bot.ServerBot) {
+ return false;
+ }
+ // Leaves end - bot can't get advancement
boolean flag = false;
AdvancementProgress advancementprogress = this.getOrStartProgress(advancement);
boolean flag1 = advancementprogress.isDone();
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 78f02c2e068a63648f6d650a48a1cf21c5da1545..85be9376fe30f18fe4fea437955f1a60c5c3f05a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -1407,6 +1407,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 top.leavesmc.leaves.bot.ServerBot bot) {
+ if (bot.alwaysSendData) {
+ bot.sendFakeData(player.connection, false);
+ }
+ }
+ // Leaves end - render bot
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 0dba30c41affafe7b1d585b515925043b37712fa..6767665e55ca1ec9cf2707918c349fd3ea5ada8a 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -186,6 +186,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.inventory.MainHand;
+import top.leavesmc.leaves.bot.ServerBot;
// CraftBukkit end
public class ServerPlayer extends Player {
@@ -729,16 +730,20 @@ public class ServerPlayer extends Player {
--this.invulnerableTime;
}
- // Paper start - Configurable container update tick rate
- if (--containerUpdateDelay <= 0) {
- this.containerMenu.broadcastChanges();
- containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate;
- }
- // Paper end - Configurable container update tick rate
- if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen
- this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
- this.containerMenu = this.inventoryMenu;
+ // Leaves start - skip bot
+ if (!(this instanceof ServerBot)) {
+ // Paper start - Configurable container update tick rate
+ if (--containerUpdateDelay <= 0) {
+ this.containerMenu.broadcastChanges();
+ containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate;
+ }
+ // Paper end - Configurable container update tick rate
+ if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen
+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason
+ this.containerMenu = this.inventoryMenu;
+ }
}
+ // Leaves end - skip bot
Entity entity = this.getCamera();
@@ -754,7 +759,7 @@ public class ServerPlayer extends Player {
}
}
- CriteriaTriggers.TICK.trigger(this);
+ if (!(this instanceof ServerBot)) CriteriaTriggers.TICK.trigger(this); // Leaves - skip bot
if (this.levitationStartPos != null) {
CriteriaTriggers.LEVITATION.trigger(this, this.levitationStartPos, this.tickCount - this.levitationStartTime);
}
@@ -947,7 +952,7 @@ public class ServerPlayer extends Player {
List<DefaultDrop> loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior
boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
- if (!keepInventory) {
+ if (!keepInventory || this instanceof ServerBot) { // Leaves - skip bot
for (ItemStack item : this.getInventory().getContents()) {
if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) {
loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event)
@@ -1272,6 +1277,13 @@ public class ServerPlayer extends Player {
this.lastSentHealth = -1.0F;
this.lastSentFood = -1;
+ // Leaves start - bot support
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
+ ServerBot.getBots().forEach(bot1 ->
+ bot1.sendFakeDataIfNeed(this, true)); // Leaves - render bot
+ }
+ // Leaves end - bot support
+
// CraftBukkit start
PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index f3b33fca6bc6c9c827cab78488b5aec782e92969..98b0cee150e9e27e6bafc7c2afc9d5008ca8ef82 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -297,7 +297,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
super(server, connection, clientData, player); // CraftBukkit
this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection());
- connection.setListener(this);
+ // Leaves start - fakeplayer
+ if (player instanceof top.leavesmc.leaves.bot.ServerBot) {
+ connection.setListenerForce(this);
+ } else {
+ connection.setListener(this);
+ }
+ // Leaves end - fakeplayer
this.player = player;
player.connection = this;
player.getTextFilter().join();
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 6d4f175e2820b48d04bd11fc694fcdfc73580da9..eb4baddfe05397c52d7722d81dd3078bb23ea077 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -120,6 +120,8 @@ import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
import org.bukkit.event.player.PlayerSpawnChangeEvent;
// CraftBukkit end
+import top.leavesmc.leaves.bot.ServerBot;
+
public abstract class PlayerList {
public static final File USERBANLIST_FILE = new File("banned-players.json");
@@ -348,6 +350,21 @@ public abstract class PlayerList {
top.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
+ // Leaves start - bot support
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
+ ServerBot bot = ServerBot.getBot(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT));
+ if (bot != null) {
+ bot.die(bot.damageSources().fellOutOfWorld()); // Leaves - remove bot with the same name
+ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player);
+ this.playersByUUID.put(player.getUUID(), player);
+ }
+ ServerBot.getBots().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
@@ -999,6 +1016,13 @@ public abstract class PlayerList {
}
// Paper end - Add PlayerPostRespawnEvent
+ // Leaves start - bot support
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
+ ServerBot.getBots().forEach(bot1 ->
+ bot1.sendFakeDataIfNeed(entityplayer1, true)); // Leaves - render bot
+ }
+ // Leaves end - bot support
+
// CraftBukkit end
return entityplayer1;
}
@@ -1115,11 +1139,16 @@ public abstract class PlayerList {
}
public String[] getPlayerNamesArray() {
- String[] astring = new String[this.players.size()];
+ String[] astring = new String[this.players.size() + ServerBot.getBots().size()]; // Leaves - fakeplayer support
for (int i = 0; i < this.players.size(); ++i) {
astring[i] = ((ServerPlayer) this.players.get(i)).getGameProfile().getName();
}
+ // Leaves start - fakeplayer support
+ for (int i = this.players.size(); i < astring.length; ++i) {
+ astring[i] = ((ServerPlayer) ServerBot.getBots().get(i - this.players.size())).getGameProfile().getName();
+ }
+ // Leaves end - fakeplayer support
return astring;
}
@@ -1598,4 +1627,16 @@ public abstract class PlayerList {
public boolean isAllowCheatsForAllPlayers() {
return this.allowCheatsForAllPlayers;
}
+
+ // Leaves start - fakeplayer support
+ public void addNewBot(ServerBot bot) {
+ playersByName.put(bot.getScoreboardName().toLowerCase(java.util.Locale.ROOT), bot);
+ playersByUUID.put(bot.getUUID(), bot);
+ }
+
+ public void removeBot(ServerBot bot) {
+ playersByName.remove(bot.getScoreboardName().toLowerCase(java.util.Locale.ROOT));
+ playersByUUID.remove(bot.getUUID());
+ }
+ // Leaves end - fakeplayer support
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index eb2e1468fdc7ba53cd907d3d7570921270ee2b67..51e7bce3813dec3c60677f8233d522c3d5d55cc5 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1457,7 +1457,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S
return offsetFactor;
}
- private Vec3 collide(Vec3 movement) {
+ public Vec3 collide(Vec3 movement) { // Leaves - private -> public
// Paper start - optimise collisions
final boolean xZero = movement.x == 0.0;
final boolean yZero = movement.y == 0.0;
diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
index 90a5f6bd729148f2adc745273536e48d704fcd1e..c07801c2eba35cc97a3a967d6d6f3fb15e5a84af 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
@@ -61,7 +61,7 @@ public class FishingHook extends Projectile {
public static final EntityDataAccessor<Integer> DATA_HOOKED_ENTITY = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> DATA_BITING = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.BOOLEAN);
private int life;
- private int nibble;
+ public int nibble; // Leaves - private -> public
public int timeUntilLured;
private int timeUntilHooked;
private float fishAngle;
diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
index 48f634a7521d31c1e9dfd3cfc83139d428dbd37a..7cef5c518207752f7e1bfdd5bbec55fe9fafca6b 100644
--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -406,6 +406,8 @@ public abstract class AbstractContainerMenu {
ItemStack itemstack1;
int l;
+ if (!doClickCheck(slotIndex, button, actionType, player)) return; // Leaves - doClick check
+
if (actionType == ClickType.QUICK_CRAFT) {
int i1 = this.quickcraftStatus;
@@ -680,6 +682,23 @@ 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();
+ if (itemStack.getTag() != null) {
+ if (itemStack.getTag().get("Leaves.Gui.Placeholder") != null) {
+ return !itemStack.getTag().getBoolean("Leaves.Gui.Placeholder");
+ }
+ }
+ return true;
+ }
+ // Leaves end - doClick check
+
private boolean tryItemClickBehaviourOverride(Player player, ClickAction clickType, Slot slot, ItemStack stack, ItemStack cursorStack) {
FeatureFlagSet featureflagset = player.level().enabledFeatures();
diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
index ed80960777b18faca2d6a99783e53daf5fa19e09..9d492a2a45a2088f4bf28c85f04c22fa70a2996a 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
@@ -68,6 +68,11 @@ public class PhantomSpawner implements CustomSpawner {
ServerStatsCounter serverstatisticmanager = entityplayer.getStats();
int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
boolean flag2 = true;
+ // Leaves start - fakeplayer spawn
+ if (entityplayer instanceof top.leavesmc.leaves.bot.ServerBot bot && bot.spawnPhantom) {
+ j = bot.notSleepTicks;
+ }
+ // Leaves end - fakeplayer spawn
if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms
BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21));
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index c21cc6231cfd6d542fc0ceaa4d62541709c1e3a8..1c79abf5952c29301bc9d83cd20a81696085fbf0 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -259,6 +259,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import net.md_5.bungee.api.chat.BaseComponent; // Spigot
+import top.leavesmc.leaves.entity.CraftBotManager;
import javax.annotation.Nullable; // Paper
import javax.annotation.Nonnull; // Paper
@@ -305,6 +306,7 @@ public final class CraftServer implements Server {
public static Exception excessiveVelEx; // Paper - Velocity warnings
private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
private final CraftPotionBrewer potionBrewer = new CraftPotionBrewer(); // Paper - Custom Potion Mixes
+ private final CraftBotManager botManager = new CraftBotManager();
// Paper start - Folia region threading API
private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler();
@@ -3238,4 +3240,11 @@ public final class CraftServer implements Server {
}
// Paper end
+
+ // Leaves start - Bot API
+ @Override
+ public CraftBotManager getBotManager() {
+ return botManager;
+ }
+ // Leaves end - Bot API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 8698104e3eb98e2cc5da5de87a8f538860c1d91d..dc7ba0ce76031e6dd7e550ceaf3b2f97cbc307f1 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -54,6 +54,8 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.NumberConversions;
import org.bukkit.util.Vector;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.entity.CraftBot;
import net.md_5.bungee.api.chat.BaseComponent; // Spigot
@@ -91,6 +93,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity);
}
+ if (entity instanceof ServerBot) { return new CraftBot(server, (ServerBot) entity); }
+
// 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/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index bb9383f1a457433f9db3e78d7913616280925200..55b41ca7630db143d70137324a9de8717397f0e8 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
@@ -46,7 +46,7 @@ import org.bukkit.scheduler.BukkitWorker;
*/
public class CraftScheduler implements BukkitScheduler {
- static Plugin MINECRAFT = new MinecraftInternalPlugin();
+ public static Plugin MINECRAFT = new MinecraftInternalPlugin(); // Leaves - package -> public
/**
* The start ID for the counter.
*/
diff --git a/src/main/java/top/leavesmc/leaves/bot/BotCommand.java b/src/main/java/top/leavesmc/leaves/bot/BotCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..25129195532cf2f22c758407580599332035e9c0
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/BotCommand.java
@@ -0,0 +1,331 @@
+package top.leavesmc.leaves.bot;
+
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.generator.WorldInfo;
+import org.bukkit.permissions.Permission;
+import org.bukkit.permissions.PermissionDefault;
+import org.bukkit.plugin.PluginManager;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.LeavesConfig;
+import top.leavesmc.leaves.bot.agent.Actions;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction;
+import top.leavesmc.leaves.entity.Bot;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BotCommand extends Command {
+
+ public BotCommand(String name) {
+ super(name);
+ this.description = "FakePlayer Command";
+ this.usageMessage = "/bot [create | remove | action | list]";
+ this.setPermission("bukkit.command.bot");
+ final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
+ if (pluginManager.getPermission("bukkit.command.bot") == null) {
+ pluginManager.addPermission(new Permission("bukkit.command.bot", PermissionDefault.OP));
+ }
+ }
+
+ @Override
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args, Location location) throws IllegalArgumentException {
+ var list = new ArrayList<String>();
+
+ if (args.length <= 1) {
+ list.add("create");
+ list.add("remove");
+ list.add("action");
+ list.add("config");
+ list.add("list");
+ }
+
+ if (args.length == 2) {
+ switch (args[0]) {
+ case "create" -> list.add("<BotName>");
+ case "remove", "action", "config" ->
+ list.addAll(ServerBot.getBots().stream().map(e -> e.getName().getString()).toList());
+ case "list" -> list.addAll(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList());
+ }
+ }
+
+ if (args.length == 3) {
+ switch (args[0]) {
+ case "action" -> {
+ list.add("list");
+ list.addAll(Actions.getNames());
+ }
+ case "create" -> list.add("<BotSkinName>");
+ case "config" -> list.addAll(acceptConfig);
+ }
+ }
+
+ if (args.length == 4) {
+ switch (args[0]) {
+ case "config" -> {
+ list.add("true");
+ list.add("false");
+ }
+ }
+ }
+
+ if (args.length >= 4 && args[0].equals("action")) {
+ BotAction action = Actions.getForName(args[2]);
+ if (action != null) {
+ list.addAll(action.getArgument().tabComplete(args.length - 4));
+ }
+ }
+
+ return list;
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String[] args) {
+ if (!testPermission(sender) || !LeavesConfig.fakeplayerSupport) return true;
+
+ if (args.length == 0) {
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
+ return false;
+ }
+
+ switch (args[0]) {
+ case "create" -> this.onCreate(sender, args);
+
+ case "remove" -> this.onRemove(sender, args);
+
+ case "action" -> this.onAction(sender, args);
+
+ case "config" -> this.onConfig(sender, args);
+
+ case "list" -> this.onList(sender, args);
+
+ default -> {
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void onCreate(CommandSender sender, String @NotNull [] args) {
+ if (args.length < 2) {
+ sender.sendMessage(ChatColor.RED + "Use /bot create <name> [skin_name] to create a fakeplayer");
+ return;
+ }
+
+ if (canCreate(sender, args[1])) {
+ if (sender instanceof Player player) {
+ new ServerBot.BotCreateState(player.getLocation(), args[1], args.length < 3 ? args[1] : args[2]).createAsync(bot -> bot.createPlayer = player.getUniqueId());
+ } else if (sender instanceof ConsoleCommandSender) {
+ if (args.length < 6) {
+ sender.sendMessage(ChatColor.RED + "Use /bot create <name> <skin_name> <bukkit_world_name> <x> <y> <z> to create a fakeplayer");
+ return;
+ }
+
+ 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) {
+ new ServerBot.BotCreateState(new Location(world, x, y, z), args[1], args[2]).createAsync(null);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private boolean canCreate(CommandSender sender, @NotNull String name) {
+ if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
+ sender.sendMessage(ChatColor.RED + "This name is illegal");
+ return false;
+ }
+
+ if (Bukkit.getPlayer(name) != null || ServerBot.getBot(name) != null) {
+ sender.sendMessage(ChatColor.RED + "This player is in server");
+ return false;
+ }
+
+ if (top.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(name)) {
+ sender.sendMessage(ChatColor.RED + "This name is not allowed");
+ return false;
+ }
+
+ if (ServerBot.getBots().size() >= top.leavesmc.leaves.LeavesConfig.fakeplayerLimit) {
+ sender.sendMessage(ChatColor.RED + "Fakeplayer limit is full");
+ return false;
+ }
+
+ return true;
+ }
+
+ private void onRemove(CommandSender sender, String @NotNull [] args) {
+ if (args.length < 2) {
+ sender.sendMessage(ChatColor.RED + "Use /bot remove <name> to remove a fakeplayer");
+ return;
+ }
+
+ ServerBot bot = ServerBot.getBot(args[1]);
+
+ if (bot == null) {
+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server");
+ return;
+ }
+
+ bot.die(bot.damageSources().fellOutOfWorld());
+ }
+
+ private void onAction(CommandSender sender, String @NotNull [] args) {
+ if (args.length < 3) {
+ sender.sendMessage(ChatColor.RED + "Use /bot action <name> <action> to make fakeplayer do action");
+ return;
+ }
+
+ ServerBot bot = ServerBot.getBot(args[1]);
+ if (bot == null) {
+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server");
+ return;
+ }
+
+ if (args[2].equals("list")) {
+ sender.sendMessage(bot.getScoreboardName() + "'s action list:");
+ for (BotAction action : bot.getBotActions()) {
+ sender.sendMessage(action.getName());
+ }
+ return;
+ }
+
+ BotAction action = Actions.getForName(args[2]);
+ if (action == null) {
+ sender.sendMessage(ChatColor.RED + "Invalid action");
+ return;
+ }
+
+ CraftPlayer player;
+ if (sender instanceof CraftPlayer) {
+ player = (CraftPlayer) sender;
+ } else {
+ player = bot.getBukkitEntity();
+ }
+
+ BotAction newAction;
+ if (action instanceof CraftCustomBotAction customBotAction) {
+ String[] realArgs = new String[args.length - 3];
+ if (realArgs.length != 0) {
+ System.arraycopy(args, 3, realArgs, 0, realArgs.length);
+ }
+ newAction = customBotAction.getNew(player, realArgs);
+ } else {
+ newAction = action.getNew(player.getHandle(), action.getArgument().parse(3, args));
+ }
+
+ if (newAction == null) {
+ sender.sendMessage(ChatColor.RED + "Action create error, please check your arguments");
+ return;
+ }
+
+ bot.setBotAction(newAction);
+ sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString());
+ }
+
+ private static final List<String> acceptConfig = List.of("skip_sleep", "spawn_phantom", "always_send_data");
+
+ private void onConfig(CommandSender sender, String @NotNull [] args) {
+ if (args.length < 3) {
+ sender.sendMessage(ChatColor.RED + "Use /bot config <name> <config> to modify fakeplayer's config");
+ return;
+ }
+
+ ServerBot bot = ServerBot.getBot(args[1]);
+ if (bot == null) {
+ sender.sendMessage(ChatColor.RED + "This fakeplayer is not in server");
+ return;
+ }
+
+ if (!acceptConfig.contains(args[2])) {
+ sender.sendMessage(ChatColor.RED + "This config is not accept");
+ return;
+ }
+
+ if (args.length < 4) {
+ switch (args[2]) {
+ case "skip_sleep" -> sender.sendMessage(bot.getScoreboardName() + "'s skip_sleep: " + bot.fauxSleeping);
+ case "spawn_phantom" -> {
+ sender.sendMessage(bot.getScoreboardName() + "'s spawn_phantom: " + bot.spawnPhantom);
+ if (bot.spawnPhantom) {
+ sender.sendMessage(bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks);
+ }
+ }
+ case "always_send_data" ->
+ sender.sendMessage(bot.getScoreboardName() + "'s always_send_data: " + bot.alwaysSendData);
+ }
+ } else {
+ boolean value = args[3].equals("true");
+ switch (args[2]) {
+ case "skip_sleep" -> bot.fauxSleeping = value;
+ case "spawn_phantom" -> bot.spawnPhantom = value;
+ case "always_send_data" -> bot.alwaysSendData = value;
+ }
+ sender.sendMessage(bot.getScoreboardName() + "'s " + args[2] + " changed: " + value);
+ }
+ }
+
+ private void onList(CommandSender sender, String @NotNull [] args) {
+ if (args.length < 2) {
+ Map<World, List<String>> botMap = new HashMap<>();
+ for (World world : Bukkit.getWorlds()) {
+ botMap.put(world, new ArrayList<>());
+ }
+
+ for (ServerBot bot : ServerBot.getBots()) {
+ Bot bukkitBot = bot.getBukkitPlayer();
+ botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName());
+ }
+
+ sender.sendMessage("Total number: (" + ServerBot.getBots().size() + "/" + top.leavesmc.leaves.LeavesConfig.fakeplayerLimit + ")");
+ for (World world : botMap.keySet()) {
+ sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world)));
+ }
+ } else {
+ World world = Bukkit.getWorld(args[2]);
+
+ if (world == null) {
+ sender.sendMessage(ChatColor.RED + "Unknown world");
+ return;
+ }
+
+ List<String> botList = new ArrayList<>();
+ for (ServerBot bot : ServerBot.getBots()) {
+ Bot bukkitBot = bot.getBukkitPlayer();
+ if (bukkitBot.getWorld() == world) {
+ botList.add(bukkitBot.getName());
+ }
+ }
+
+ sender.sendMessage(world.getName() + "(" + botList.size() + "): " + formatPlayerNameList(botList));
+ }
+ }
+
+ @NotNull
+ private static String formatPlayerNameList(@NotNull List<String> list) {
+ if (list.isEmpty()) {
+ return "";
+ }
+ String string = list.toString();
+ return string.substring(1, string.length() - 1);
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/BotInventoryContainer.java b/src/main/java/top/leavesmc/leaves/bot/BotInventoryContainer.java
new file mode 100644
index 0000000000000000000000000000000000000000..b83ad5bf2a338d589eb200d3f3b3153571ba2cad
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/BotInventoryContainer.java
@@ -0,0 +1,180 @@
+package top.leavesmc.leaves.bot;
+
+import com.google.common.collect.ImmutableList;
+import com.mojang.datafixers.util.Pair;
+import net.minecraft.core.NonNullList;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.ContainerHelper;
+import net.minecraft.world.SimpleContainer;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+// Power by gugle-carpet-addition(https://github.com/Gu-ZT/gugle-carpet-addition)
+public class BotInventoryContainer extends SimpleContainer {
+
+ public final NonNullList<ItemStack> items;
+ public final NonNullList<ItemStack> armor;
+ public final NonNullList<ItemStack> offhand;
+ private final List<NonNullList<ItemStack>> compartments;
+ private final NonNullList<ItemStack> buttons = NonNullList.withSize(13, ItemStack.EMPTY);
+ private final ServerBot player;
+
+ public BotInventoryContainer(ServerBot player) {
+ this.player = player;
+ this.items = this.player.getInventory().items;
+ this.armor = this.player.getInventory().armor;
+ this.offhand = this.player.getInventory().offhand;
+ this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons);
+ createButton();
+ }
+
+ @Override
+ public int getContainerSize() {
+ return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ for (ItemStack itemStack : this.items) {
+ if (itemStack.isEmpty()) {
+ continue;
+ }
+ return false;
+ }
+ for (ItemStack itemStack : this.armor) {
+ if (itemStack.isEmpty()) {
+ continue;
+ }
+ return false;
+ }
+ for (ItemStack itemStack : this.offhand) {
+ if (itemStack.isEmpty()) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack getItem(int slot) {
+ Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
+ if (pair != null) {
+ return pair.getFirst().get(pair.getSecond());
+ } else {
+ return ItemStack.EMPTY;
+ }
+ }
+
+ public Pair<NonNullList<ItemStack>, Integer> getItemSlot(int slot) {
+ switch (slot) {
+ case 0 -> {
+ return new Pair<>(buttons, 0);
+ }
+ case 1, 2, 3, 4 -> {
+ return new Pair<>(armor, 4 - slot);
+ }
+ case 5, 6 -> {
+ return new Pair<>(buttons, slot - 4);
+ }
+ case 7 -> {
+ return new Pair<>(offhand, 0);
+ }
+ case 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> {
+ return new Pair<>(buttons, slot - 5);
+ }
+ 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 -> {
+ return new Pair<>(items, slot - 9);
+ }
+ case 45, 46, 47, 48, 49, 50, 51, 52, 53 -> {
+ return new Pair<>(items, slot - 45);
+ }
+ default -> {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack removeItem(int slot, int amount) {
+ Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
+ NonNullList<ItemStack> list = null;
+ ItemStack itemStack = ItemStack.EMPTY;
+ if (pair != null) {
+ list = pair.getFirst();
+ slot = pair.getSecond();
+ }
+ if (list != null && !list.get(slot).isEmpty()) {
+ itemStack = ContainerHelper.removeItem(list, slot, amount);
+ player.detectEquipmentUpdatesPublic();
+ }
+ return itemStack;
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack removeItemNoUpdate(int slot) {
+ Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
+ NonNullList<ItemStack> list = null;
+ if (pair != null) {
+ list = pair.getFirst();
+ slot = pair.getSecond();
+ }
+ if (list != null && !list.get(slot).isEmpty()) {
+ ItemStack itemStack = list.get(slot);
+ list.set(slot, ItemStack.EMPTY);
+ return itemStack;
+ }
+ return ItemStack.EMPTY;
+ }
+
+ @Override
+ public void setItem(int slot, @Nonnull ItemStack stack) {
+ Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
+ NonNullList<ItemStack> list = null;
+ if (pair != null) {
+ list = pair.getFirst();
+ slot = pair.getSecond();
+ }
+ if (list != null) {
+ list.set(slot, stack);
+ player.detectEquipmentUpdatesPublic();
+ }
+ }
+
+ @Override
+ public void setChanged() {
+ }
+
+ @Override
+ public boolean stillValid(@Nonnull Player player) {
+ if (this.player.isRemoved()) {
+ return false;
+ }
+ return !(player.distanceToSqr(this.player) > 64.0);
+ }
+
+ @Override
+ public void clearContent() {
+ for (List<ItemStack> list : this.compartments) {
+ list.clear();
+ }
+ }
+
+ private void createButton() {
+ for (int i = 0; i < 13; i++) {
+ ItemStack button = new ItemStack(Items.STRUCTURE_VOID);
+ button.setHoverName(Component.empty());
+ button.getOrCreateTag().putBoolean("Leaves.Gui.Placeholder", true);
+ buttons.set(i, button);
+ }
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/BotStatsCounter.java b/src/main/java/top/leavesmc/leaves/bot/BotStatsCounter.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3f114fba0759221b5fea0ccc4862f0579260cef
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/BotStatsCounter.java
@@ -0,0 +1,38 @@
+package top.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 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(Player player, Stat<?> stat, int value) {
+
+ }
+
+ @Override
+ public void parseLocal(DataFixer dataFixer, String json) {
+
+ }
+
+ @Override
+ public int getValue(Stat<?> stat) {
+ return 0;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/BotUtil.java b/src/main/java/top/leavesmc/leaves/bot/BotUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0ad5ed3d2a179001312733b780dd532d3b4a001
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/BotUtil.java
@@ -0,0 +1,183 @@
+package top.leavesmc.leaves.bot;
+
+import com.google.common.base.Charsets;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.minecraft.core.NonNullList;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.NbtAccounter;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.storage.LevelResource;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.agent.Actions;
+import top.leavesmc.leaves.bot.agent.BotAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+public class BotUtil {
+
+ public static void replenishment(@NotNull ItemStack itemStack, NonNullList<ItemStack> 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.isSameItemSameTags(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 JsonObject saveBot(@NotNull ServerBot bot) {
+ double pos_x = bot.getX();
+ double pos_y = bot.getY();
+ double pos_z = bot.getZ();
+ float yaw = bot.getYRot();
+ float pitch = bot.getXRot();
+ String dimension = bot.getLocation().getWorld().getName();
+ String skin = bot.createState.skinName;
+
+ JsonObject fakePlayer = new JsonObject();
+ fakePlayer.addProperty("pos_x", pos_x);
+ fakePlayer.addProperty("pos_y", pos_y);
+ fakePlayer.addProperty("pos_z", pos_z);
+ fakePlayer.addProperty("yaw", yaw);
+ fakePlayer.addProperty("pitch", pitch);
+ fakePlayer.addProperty("dimension", dimension);
+ fakePlayer.addProperty("skin", skin);
+
+ Collection<BotAction> actions = bot.getBotActions();
+ JsonArray botActions = new JsonArray();
+ for (BotAction action : actions) {
+ JsonObject actionObj = new JsonObject();
+ actionObj.addProperty("name", action.getName());
+ actionObj.addProperty("number", String.valueOf(action.getNumber()));
+ actionObj.addProperty("delay", String.valueOf(action.getTickDelay()));
+ botActions.add(actionObj);
+ }
+ fakePlayer.add("actions", botActions);
+
+ CompoundTag invnbt = new CompoundTag();
+ invnbt.put("Inventory", bot.getInventory().save(new ListTag()));
+
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fakeplayer/" + bot.getStringUUID() + ".dat").toFile();
+ File parent = file.getParentFile();
+ try {
+ if (!parent.exists() || !parent.isDirectory()) {
+ parent.mkdirs();
+ }
+ if (file.exists() && file.isFile()) {
+ file.delete();
+ }
+ file.createNewFile();
+ NbtIo.writeCompressed(invnbt, file.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return fakePlayer;
+ }
+
+ public static void loadBot(Map.@NotNull Entry<String, JsonElement> entry) {
+ String username = entry.getKey();
+ JsonObject fakePlayer = entry.getValue().getAsJsonObject();
+ double pos_x = fakePlayer.get("pos_x").getAsDouble();
+ double pos_y = fakePlayer.get("pos_y").getAsDouble();
+ double pos_z = fakePlayer.get("pos_z").getAsDouble();
+ float yaw = fakePlayer.get("yaw").getAsFloat();
+ float pitch = fakePlayer.get("pitch").getAsFloat();
+ String dimension = fakePlayer.get("dimension").getAsString();
+ String skin = fakePlayer.get("skin").getAsString();
+
+ Location location = new Location(Bukkit.getWorld(dimension), pos_x, pos_y, pos_z, yaw, pitch);
+ ServerBot.BotCreateState state = new ServerBot.BotCreateState(location, username, skin);
+
+ ListTag inv = null;
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fakeplayer/" + getBotUUID(state) + ".dat").toFile();
+ if (file.exists()) {
+ try {
+ CompoundTag nbt = NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap());
+ inv = nbt.getList("Inventory", 10);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ file.delete();
+ }
+
+ final JsonArray finalActions = fakePlayer.get("actions").getAsJsonArray();
+ final ListTag finalInv = inv;
+ state.createAsync(serverBot -> {
+ if (finalInv != null) {
+ serverBot.getInventory().load(finalInv);
+ }
+
+ for (JsonElement element : finalActions) {
+ JsonObject actionObj = element.getAsJsonObject();
+ BotAction action = Actions.getForName(actionObj.get("name").getAsString());
+ if (action != null) {
+ BotAction newAction = action.getNew(serverBot,
+ action.getArgument().parse(0, new String[]{actionObj.get("delay").getAsString(), actionObj.get("number").getAsString()})
+ );
+ serverBot.setBotAction(newAction);
+ }
+ }
+ });
+ }
+
+ @NotNull
+ public static UUID getBotUUID(ServerBot.@NotNull BotCreateState state) {
+ return UUID.nameUUIDFromBytes(("Fakeplayer:" + state.getRealName()).getBytes(Charsets.UTF_8));
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java b/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..daaece30b2a3983f1cc9ee9a851e8f373974d5ec
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java
@@ -0,0 +1,41 @@
+package top.leavesmc.leaves.bot;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MojangAPI {
+
+ private static final boolean CACHE_ENABLED = false;
+
+ private static final Map<String, String[]> CACHE = new HashMap<>();
+
+ public static String[] getSkin(String name) {
+ if (CACHE_ENABLED && 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 = new JsonParser().parse(new InputStreamReader(new URL("https://api.mojang.com/users/profiles/minecraft/" + name)
+ .openStream())).getAsJsonObject().get("id").getAsString();
+ JsonObject property = new JsonParser()
+ .parse(new InputStreamReader(new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false")
+ .openStream())).getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject();
+ return new String[] {property.get("value").getAsString(), property.get("signature").getAsString()};
+ } catch (IOException | IllegalStateException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/ServerBot.java b/src/main/java/top/leavesmc/leaves/bot/ServerBot.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f5af75066e1a5e64ddf2e910857a2b407064214
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/ServerBot.java
@@ -0,0 +1,734 @@
+package top.leavesmc.leaves.bot;
+
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+import net.minecraft.Util;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.Connection;
+import net.minecraft.network.PacketSendListener;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
+import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
+import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
+import net.minecraft.network.syncher.EntityDataAccessor;
+import net.minecraft.network.syncher.EntityDataSerializers;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ClientInformation;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.CommonListenerCookie;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.stats.ServerStatsCounter;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.SimpleMenuProvider;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.ChestMenu;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.gameevent.GameEvent;
+import net.minecraft.world.level.storage.LevelResource;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.scheduler.CraftScheduler;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.LeavesConfig;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.bot.agent.actions.StopAction;
+import top.leavesmc.leaves.entity.Bot;
+import top.leavesmc.leaves.entity.CraftBot;
+import top.leavesmc.leaves.event.bot.BotCreateEvent;
+import top.leavesmc.leaves.event.bot.BotInventoryOpenEvent;
+import top.leavesmc.leaves.event.bot.BotJoinEvent;
+import top.leavesmc.leaves.util.MathUtils;
+
+import javax.annotation.Nullable;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+// TODO remake all
+public class ServerBot extends ServerPlayer {
+
+ private final Map<String, BotAction> actions;
+ private final boolean removeOnDeath;
+ private final int tracingRange;
+
+ private Vec3 velocity;
+ private int fireTicks;
+ private int jumpTicks;
+ private int noFallTicks;
+ public boolean waterSwim;
+ private Vec3 knockback;
+ public BotCreateState createState;
+ public UUID createPlayer;
+
+ private final ServerStatsCounter stats;
+ private final BotInventoryContainer container;
+
+ private static final List<ServerBot> bots = new CopyOnWriteArrayList<>();
+
+ public boolean spawnPhantom;
+ public int notSleepTicks;
+ public boolean alwaysSendData;
+
+ private ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) {
+ super(server, world, profile, ClientInformation.createDefault());
+ this.entityData.set(new EntityDataAccessor<>(16, EntityDataSerializers.INT), 0xFF);
+ this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2);
+
+ this.velocity = new Vec3(this.xxa, this.yya, this.zza);
+ this.noFallTicks = 60;
+ this.fireTicks = 0;
+ this.actions = new HashMap<>();
+ this.removeOnDeath = true;
+ this.stats = new BotStatsCounter(server);
+ this.container = new BotInventoryContainer(this);
+ this.waterSwim = true;
+ this.knockback = Vec3.ZERO;
+ this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange;
+ this.notSleepTicks = 0;
+
+ this.fauxSleeping = LeavesConfig.fakeplayerSkipSleep;
+ this.spawnPhantom = LeavesConfig.fakeplayerSpawnPhantom;
+ this.alwaysSendData = LeavesConfig.alwaysSendFakeplayerData;
+ }
+
+ public static ServerBot createBot(@NotNull BotCreateState state) {
+ if (!isCreateLegal(state.name)) {
+ return null;
+ }
+
+ MinecraftServer server = MinecraftServer.getServer();
+
+ BotCreateEvent event = new BotCreateEvent(state.name, state.skinName, state.loc, ChatColor.YELLOW + state.name + " joined the game");
+ server.server.getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ return null;
+ }
+
+ Location location = event.getCreateLocation();
+
+ ServerLevel world = ((CraftWorld) location.getWorld()).getHandle();
+ CustomGameProfile profile = new CustomGameProfile(BotUtil.getBotUUID(state), state.name, state.skin);
+
+ ServerBot bot = new ServerBot(server, world, profile);
+
+ bot.connection = new ServerGamePacketListenerImpl(server, new Connection(PacketFlow.SERVERBOUND) { // ?
+ @Override
+ public void send(@NotNull Packet<?> packet) {
+ }
+
+ @Override
+ public void send(@NotNull Packet<?> packet, @Nullable PacketSendListener packetsendlistener) {
+ }
+
+ @Override
+ public void send(@NotNull Packet<?> packet, @Nullable PacketSendListener callbacks, boolean flush) {
+ }
+ }, bot, CommonListenerCookie.createInitial(profile));
+ bot.isRealPlayer = true;
+ bot.createState = state;
+
+ if (event.getJoinMessage() != null) {
+ Bukkit.broadcastMessage(event.getJoinMessage());
+ }
+
+ bot.teleportTo(location.getX(), location.getY(), location.getZ());
+ bot.setRot(location.getYaw(), location.getPitch());
+ bot.getBukkitEntity().setRotation(location.getYaw(), location.getPitch());
+ world.addFreshEntity(bot, CreatureSpawnEvent.SpawnReason.COMMAND);
+
+ bot.renderAll();
+ server.getPlayerList().addNewBot(bot);
+ bots.add(bot);
+
+ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitPlayer());
+ server.server.getPluginManager().callEvent(event1);
+
+ return bot;
+ }
+
+ public static boolean isCreateLegal(@NotNull String name) {
+ if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
+ return false;
+ }
+
+ if (Bukkit.getPlayer(name) != null || ServerBot.getBot(name) != null) {
+ return false;
+ }
+
+ if (top.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(name)) {
+ return false;
+ }
+
+ return ServerBot.getBots().size() < top.leavesmc.leaves.LeavesConfig.fakeplayerLimit;
+ }
+
+ public void renderAll() {
+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(
+ player -> {
+ this.sendPlayerInfo(player);
+ this.sendFakeData(player.connection, false);
+ }
+ );
+ }
+
+ 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 alwaysSendData && (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) {
+ playerConnection.send(new ClientboundAddEntityPacket(this));
+ if (login) {
+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> {
+ connection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f)));
+ }, 10);
+ } else {
+ connection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f)));
+ }
+ }
+
+ private void sendPacket(Packet<?> packet) {
+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(
+ player -> player.connection.send(packet)
+ );
+ }
+
+ // die check start
+ @Override
+ public void die(@NotNull DamageSource damageSource) {
+ super.die(damageSource);
+ this.dieCheck();
+ }
+
+ private void dieCheck() {
+ if (removeOnDeath) {
+ bots.remove(this);
+ server.getPlayerList().removeBot(this);
+ remove(RemovalReason.KILLED);
+ this.setDead();
+ this.removeTab();
+ Bukkit.broadcastMessage(ChatColor.YELLOW + this.getName().getString() + " left the game"); // TODO i18n
+ }
+ }
+
+ private void removeTab() {
+ sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID())));
+ }
+
+ private void setDead() {
+ sendPacket(new ClientboundRemoveEntitiesPacket(getId()));
+ this.dead = true;
+ this.inventoryMenu.removed(this);
+ this.containerMenu.removed(this);
+ }
+
+ // die check end
+
+ @Nullable
+ @Override
+ public Entity changeDimension(@NotNull ServerLevel destination) {
+ return null; // disable dimension change
+ }
+
+ public Bot getBukkitPlayer() {
+ return getBukkitEntity();
+ }
+
+ @Override
+ @NotNull
+ public CraftBot getBukkitEntity() {
+ return (CraftBot) super.getBukkitEntity();
+ }
+
+ @Override
+ public boolean isInWater() {
+ Location loc = getLocation();
+ for (int i = 0; i <= 2; i++) {
+ Material type = loc.getBlock().getType();
+ if (type == Material.WATER || type == Material.LAVA) {
+ return true;
+ }
+ loc.add(0, 0.9, 0);
+ }
+ return false;
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ this.doTick();
+
+ if (!isAlive()) {
+ return;
+ }
+
+ if (spawnPhantom) {
+ notSleepTicks++;
+ }
+
+ if (fireTicks > 0) {
+ --fireTicks;
+ }
+ if (jumpTicks > 0) {
+ --jumpTicks;
+ }
+ if (noFallTicks > 0) {
+ --noFallTicks;
+ }
+ if (takeXpDelay > 0) {
+ --takeXpDelay;
+ }
+
+ this.updateLocation();
+ this.updatePlayerPose();
+
+ if (server.getTickCount() % 20 == 0) {
+ float health = getHealth();
+ float maxHealth = getMaxHealth();
+ float regenAmount = (float) (LeavesConfig.fakeplayerRegenAmount * 20);
+ float amount;
+
+ if (health < maxHealth - regenAmount) {
+ amount = health + regenAmount;
+ } else {
+ amount = maxHealth;
+ }
+
+ this.setHealth(amount);
+ }
+
+ BlockPos blockposition = this.getOnPosLegacy();
+ BlockState iblockdata = this.level().getBlockState(blockposition);
+ Vec3 vec3d1 = this.collide(velocity);
+ this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition);
+
+ ++this.attackStrengthTicker;
+
+ if (this.getHealth() > 0.0F) {
+ AABB axisalignedbb;
+
+ if (this.isPassenger() && !this.getVehicle().isRemoved()) {
+ axisalignedbb = this.getBoundingBox().minmax(this.getVehicle().getBoundingBox()).inflate(1.0D, 0.0D, 1.0D);
+ } else {
+ axisalignedbb = this.getBoundingBox().inflate(1.0D, 0.5D, 1.0D);
+ }
+
+ List<Entity> list = this.level().getEntities(this, axisalignedbb);
+ List<Entity> list1 = Lists.newArrayList();
+
+ for (Entity entity : list) {
+ if (entity.getType() == EntityType.EXPERIENCE_ORB) {
+ list1.add(entity);
+ } else if (!entity.isRemoved()) {
+ this.touch(entity);
+ }
+ }
+
+ if (!list1.isEmpty()) {
+ this.touch(Util.getRandom(list1, this.random));
+ }
+ }
+
+ Iterator<Map.Entry<String, BotAction>> iterator = actions.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, BotAction> entry = iterator.next();
+ if (entry.getValue().isCancel()) {
+ iterator.remove();
+ } else {
+ entry.getValue().tryTick(this);
+ }
+ }
+ }
+
+ private void touch(@NotNull Entity entity) {
+ entity.playerTouch(this);
+ }
+
+ @Override
+ public void onItemPickup(@NotNull ItemEntity item) {
+ super.onItemPickup(item);
+ this.updateItemInMainHand();
+ }
+
+ public void updateItemInMainHand() {
+ tryReplenishOrReplaceInMainHand();
+ detectEquipmentUpdatesPublic();
+ }
+
+ public void updateItemInOffHand() {
+ tryReplenishOrReplaceInOffHand();
+ detectEquipmentUpdatesPublic();
+ }
+
+ public void tryReplenishOrReplaceInOffHand() {
+ net.minecraft.world.item.ItemStack offhand = getOffhandItem();
+
+ if (!offhand.isEmpty()) {
+ BotUtil.replenishment(offhand, getInventory().items);
+ if (BotUtil.isDamage(offhand, 10)) {
+ BotUtil.replaceTool(EquipmentSlot.OFFHAND, this);
+ }
+ }
+ }
+
+ public void tryReplenishOrReplaceInMainHand() {
+ net.minecraft.world.item.ItemStack mainHand = getMainHandItem();
+
+ if (!mainHand.isEmpty()) {
+ BotUtil.replenishment(mainHand, getInventory().items);
+ if (BotUtil.isDamage(mainHand, 10)) {
+ BotUtil.replaceTool(EquipmentSlot.MAINHAND, this);
+ }
+ }
+ }
+
+ @Override
+ public void checkFallDamage(double heightDifference, boolean onGround, @NotNull BlockState state, @NotNull BlockPos landedPosition) {
+ if (onGround) {
+ if (this.fallDistance > 0.0F) {
+ state.getBlock().fallOn(this.level(), state, landedPosition, this, this.fallDistance);
+ this.level().gameEvent(GameEvent.HIT_GROUND, this.position(), GameEvent.Context.of(this, this.mainSupportingBlockPos.map((blockposition1) -> {
+ return this.level().getBlockState(blockposition1);
+ }).orElse(state)));
+ }
+
+ this.resetFallDistance();
+ } else if (heightDifference < 0.0D) {
+ this.fallDistance -= (float) heightDifference;
+ }
+ }
+
+ @Override
+ public void doTick() {
+ if (this.hurtTime > 0) {
+ this.hurtTime -= 1;
+ }
+
+ baseTick();
+
+ this.lerpSteps = (int) this.zza;
+ this.animStep = this.run;
+ this.yRotO = this.getYRot();
+ this.xRotO = this.getXRot();
+ }
+
+ public Location getLocation() {
+ return getBukkitPlayer().getLocation();
+ }
+
+ @Override
+ public void knockback(double strength, double x, double z, @NotNull Entity knockingBackEntity) {
+ strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
+ if (strength > 0.0D) {
+ this.hasImpulse = true;
+ Vec3 vec3d = this.getDeltaMovement();
+ Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength);
+ knockback = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z);
+ }
+ }
+
+ private void updateLocation() {
+ this.velocity = new Vec3(this.xxa, this.yya, this.zza);
+
+ if (waterSwim && isInWater()) {
+ this.addDeltaMovement(new Vec3(0, 0.05, 0));
+ }
+ this.addDeltaMovement(knockback);
+ knockback = Vec3.ZERO;
+
+ this.travel(this.velocity);
+ }
+
+ public void faceLocation(@NotNull Location loc) {
+ 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];
+
+ sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f)));
+ }
+
+ setRot(yaw, pitch);
+ this.getBukkitEntity().setRotation(yaw, pitch);
+ }
+
+ public void punch() {
+ swing(InteractionHand.MAIN_HAND);
+ }
+
+ public void attack(@NotNull Entity target) {
+ super.attack(target);
+ punch();
+ }
+
+ public void jumpFromGround() {
+ double jumpPower = (double) this.getJumpPower() + this.getJumpBoostPower();
+ this.addDeltaMovement(new Vec3(0, jumpPower, 0));
+ }
+
+ public void dropAll() {
+ getInventory().dropAll();
+ detectEquipmentUpdatesPublic();
+ }
+
+ public void setBotAction(BotAction action) {
+ if (action instanceof StopAction) {
+ this.actions.clear();
+ }
+ action.init();
+ this.actions.put(action.getName(), action);
+ }
+
+ public Collection<BotAction> getBotActions() {
+ return actions.values();
+ }
+
+ public BotAction getBotAction(String name) {
+ return actions.get(name);
+ }
+
+ @Deprecated
+ public BotAction getBotAction() {
+ return null;
+ }
+
+ @Override
+ public @NotNull ServerStatsCounter getStats() {
+ return stats;
+ }
+
+ public BotInventoryContainer getContainer() {
+ return container;
+ }
+
+ @Override
+ public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) {
+ if (LeavesConfig.openFakeplayerInventory) {
+ if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) {
+ BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
+ server.server.getPluginManager().callEvent(event);
+ if (!event.isCancelled()) {
+ player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, container), getDisplayName()));
+ return InteractionResult.SUCCESS;
+ }
+ }
+ }
+ return super.interact(player, hand);
+ }
+
+ public static ServerBot getBot(ServerPlayer player) {
+ ServerBot bot = null;
+ for (ServerBot b : bots) {
+ if (b.getId() == player.getId()) {
+ bot = b;
+ break;
+ }
+ }
+ return bot;
+ }
+
+ public static ServerBot getBot(String name) {
+ ServerBot bot = null;
+ for (ServerBot b : bots) {
+ if (b.getName().getString().equals(name)) {
+ bot = b;
+ break;
+ }
+ }
+ return bot;
+ }
+
+ public static ServerBot getBot(UUID uuid) {
+ ServerBot bot = null;
+ for (ServerBot b : bots) {
+ if (b.uuid == uuid) {
+ bot = b;
+ break;
+ }
+ }
+ return bot;
+ }
+
+ public static void saveOrRemoveAllBot() {
+ if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) {
+ JsonObject fakePlayerList = new JsonObject();
+ bots.forEach(bot -> fakePlayerList.add(bot.createState.realName, BotUtil.saveBot(bot)));
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile();
+ if (!file.isFile()) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ try (BufferedWriter bfw = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
+ bfw.write(new Gson().toJson(fakePlayerList));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ removeAllBot();
+ }
+ }
+
+ public static void loadAllBot() {
+ if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) {
+ JsonObject fakePlayerList = new JsonObject();
+ File file = MinecraftServer.getServer().getWorldPath(LevelResource.ROOT).resolve("fake_player.leaves.json").toFile();
+ if (!file.isFile()) {
+ return;
+ }
+ try (BufferedReader bfr = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
+ fakePlayerList = new Gson().fromJson(bfr, JsonObject.class);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ for (Map.Entry<String, JsonElement> entry : fakePlayerList.entrySet()) {
+ BotUtil.loadBot(entry);
+ }
+ file.delete();
+ }
+ }
+
+ public static boolean removeAllBot() {
+ Iterator<ServerBot> iterator = bots.iterator();
+ while (iterator.hasNext()) {
+ ServerBot bot = iterator.next();
+ bot.die(bot.damageSources().fellOutOfWorld());
+ }
+ return true;
+ }
+
+ public static List<ServerBot> getBots() {
+ return bots;
+ }
+
+ public static class CustomGameProfile extends GameProfile {
+
+ public CustomGameProfile(UUID uuid, String name, String[] skin) {
+ super(uuid, name);
+ setSkin(skin);
+ }
+
+ public void setSkin(String[] skin) {
+ if (skin != null) {
+ getProperties().put("textures", new Property("textures", skin[0], skin[1]));
+ }
+ }
+ }
+
+ public static class BotCreateState {
+
+ public Location loc;
+
+ public String[] skin;
+ public String skinName;
+
+ private String realName;
+ private String name;
+
+ public BotCreateState(Location loc, String realName, String skinName) {
+ this.loc = loc;
+ this.skinName = skinName;
+ this.setRealName(realName);
+ }
+
+ public BotCreateState(Location loc, String name, String realName, String skinName, String[] skin) {
+ this.loc = loc;
+ this.skinName = skinName;
+ this.skin = skin;
+ this.realName = realName;
+ this.name = name;
+ }
+
+ public ServerBot createSync() {
+ return createBot(this);
+ }
+
+ public void createAsync(Consumer<ServerBot> consumer) {
+ Bukkit.getScheduler().runTaskAsynchronously(CraftScheduler.MINECRAFT, () -> {
+ if (skinName != null) {
+ this.skin = MojangAPI.getSkin(skinName);
+ }
+
+ Bukkit.getScheduler().runTask(CraftScheduler.MINECRAFT, () -> {
+ ServerBot bot = createBot(this);
+ if (bot != null && consumer != null) {
+ consumer.accept(bot);
+ }
+ });
+ });
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setRealName(String realName) {
+ this.realName = realName;
+ this.name = LeavesConfig.fakeplayerPrefix + realName + LeavesConfig.fakeplayerSuffix;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getRealName() {
+ return realName;
+ }
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/Actions.java b/src/main/java/top/leavesmc/leaves/bot/agent/Actions.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7e98848e400641077ac567d9ea9a58c32123c98
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/Actions.java
@@ -0,0 +1,66 @@
+package top.leavesmc.leaves.bot.agent;
+
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.agent.actions.*;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class Actions {
+
+ private static final Map<String, BotAction> actions = new HashMap<>();
+
+ public static void registerAll() {
+ register(new AttackAction());
+ register(new BreakBlockAction());
+ register(new DropAction());
+ register(new JumpAction());
+ register(new RotateAction());
+ register(new SneakAction());
+ register(new StopAction());
+ register(new UseItemAction());
+ register(new UseItemOnAction());
+ register(new UseItemToAction());
+ register(new LookAction());
+ register(new FishAction());
+ register(new AttackSelfAction());
+ register(new SwimAction());
+ register(new UseItemOffHandAction());
+ register(new UseItemOnOffhandAction());
+ register(new UseItemToOffhandAction());
+ }
+
+ public static boolean register(@NotNull BotAction action) {
+ if (!actions.containsKey(action.getName())) {
+ actions.put(action.getName(), action);
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean unregister(@NotNull String name) {
+ if (actions.containsKey(name)) {
+ actions.remove(name);
+ return true;
+ }
+ return false;
+ }
+
+ @NotNull
+ @Contract(pure = true)
+ public static Collection<BotAction> getAll() {
+ return actions.values();
+ }
+
+ @NotNull
+ public static Set<String> getNames() {
+ return actions.keySet();
+ }
+
+ public static BotAction getForName(String name) {
+ return actions.get(name);
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/BotAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/BotAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..20ba4904219d015903cd3a5b2e1693eaba6088d8
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/BotAction.java
@@ -0,0 +1,93 @@
+package top.leavesmc.leaves.bot.agent;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+import java.util.List;
+
+public abstract class BotAction {
+
+ private final String name;
+ private final CommandArgument argument;
+
+ private boolean cancel;
+ private int tickDelay;
+ private int number;
+
+ private int needWaitTick;
+ private int canDoNumber;
+
+ public BotAction(String name, CommandArgument argument) {
+ this.name = name;
+ this.argument = argument;
+
+ this.cancel = false;
+ this.tickDelay = 20;
+ this.number = -1;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getTickDelay() {
+ return tickDelay;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public boolean isCancel() {
+ return cancel;
+ }
+
+ public BotAction setTickDelay(int tickDelay) {
+ this.tickDelay = Math.max(0, tickDelay);
+ return this;
+ }
+
+ public BotAction setTabComplete(int index, List<String> list) {
+ argument.setTabComplete(index, list);
+ return this;
+ }
+
+ public BotAction setNumber(int number) {
+ this.number = Math.max(-1, number);
+ return this;
+ }
+
+ public void setCancel(boolean cancel) {
+ this.cancel = cancel;
+ }
+
+ public void init() {
+ this.needWaitTick = 0;
+ this.canDoNumber = this.getNumber();
+ this.setCancel(false);
+ }
+
+ public void tryTick(ServerBot bot) {
+ if (canDoNumber == 0) {
+ this.setCancel(true);
+ return;
+ }
+ if (needWaitTick-- <= 0) {
+ if (this.doTick(bot)) {
+ canDoNumber--;
+ needWaitTick = this.getTickDelay();
+ }
+ }
+ }
+
+ public CommandArgument getArgument() {
+ return argument;
+ }
+
+ public abstract BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result);
+
+ public abstract boolean doTick(@NotNull ServerBot bot);
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..609605b21cfe5af8876f76ea4922e379c5dd166e
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackAction.java
@@ -0,0 +1,36 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.phys.EntityHitResult;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class AttackAction extends BotAction {
+
+ public AttackAction() {
+ super("attack", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new AttackAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ EntityHitResult result = bot.getTargetEntity(3);
+ if (result != null) {
+ bot.attack(result.getEntity());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackSelfAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackSelfAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a0e5626751d0b6ea12a6074b5626937b6668608
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/AttackSelfAction.java
@@ -0,0 +1,42 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import com.google.common.base.Predicates;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class AttackSelfAction extends BotAction {
+
+ public AttackSelfAction() {
+ super("attack_self", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new AttackSelfAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ List<Entity> entities = bot.level().getEntities((Entity) null, bot.getBoundingBox(), Predicates.alwaysTrue());
+ if (!entities.isEmpty()) {
+ for (int i = 0; i < entities.size(); i++) {
+ Entity entity = entities.get(i);
+ if (entity != bot) {
+ bot.attack(entity);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a3ca671b43fec658bf5cd8a6eb08b476a766c29
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java
@@ -0,0 +1,104 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.block.state.BlockState;
+import org.bukkit.block.Block;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class BreakBlockAction extends BotAction {
+
+ public BreakBlockAction() {
+ super("break", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new BreakBlockAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public BotAction setTickDelay(int tickDelay) {
+ super.setTickDelay(0);
+ this.delay = tickDelay;
+ return this;
+ }
+
+ private int delay = 0;
+ private int nowDelay = 0;
+
+ private BlockPos lastPos = null;
+ private int destroyProgressTime = 0;
+ private int lastSentState = -1;
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ if (nowDelay > 0) {
+ nowDelay--;
+ return false;
+ }
+
+ Block block = bot.getBukkitEntity().getTargetBlockExact(5);
+ if (block != null) {
+ BlockPos pos = ((CraftBlock) block).getPosition();
+
+ if (lastPos == null || !lastPos.equals(pos)) {
+ lastPos = pos;
+ destroyProgressTime = 0;
+ lastSentState = -1;
+ }
+
+ BlockState iblockdata = bot.level().getBlockState(pos);
+ if (!iblockdata.isAir()) {
+ bot.punch();
+
+ if (iblockdata.getDestroyProgress(bot, bot.level(), pos) >= 1.0F) {
+ bot.gameMode.destroyAndAck(pos, 0, "insta mine");
+ bot.level().destroyBlockProgress(bot.getId(), pos, -1);
+ bot.updateItemInMainHand();
+ finalBreak();
+ return true;
+ }
+
+ 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.updateItemInMainHand();
+ finalBreak();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void finalBreak() {
+ lastPos = null;
+ destroyProgressTime = 0;
+ lastSentState = -1;
+ nowDelay = delay;
+ }
+
+ 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;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..89a361249179d7a0a84768e715ced05aafc13272
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java
@@ -0,0 +1,48 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.entity.botaction.CustomBotAction;
+
+public class CraftCustomBotAction extends BotAction {
+
+ private final CustomBotAction realAction;
+
+ public CraftCustomBotAction(String name, @NotNull CustomBotAction realAction) {
+ super(name, new CommandArgument().setAllTabComplete(realAction.getTabComplete()));
+ this.realAction = realAction;
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+
+ public BotAction getNew(@NotNull Player player, String[] args) {
+ CustomBotAction newRealAction = realAction.getNew(player, args);
+ if (newRealAction != null) {
+ return new CraftCustomBotAction(getName(), newRealAction);
+ }
+ return null;
+ }
+
+ @Override
+ public int getNumber() {
+ return realAction.getNumber();
+ }
+
+ @Override
+ public int getTickDelay() {
+ return realAction.getTickDelay();
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ return realAction.doTick(bot.getBukkitPlayer());
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/DropAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/DropAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc72960b8490a72aca5db3e834c71f97e3742f7d
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/DropAction.java
@@ -0,0 +1,26 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+public class DropAction extends BotAction {
+
+ public DropAction() {
+ super("drop", new CommandArgument());
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return this.setTickDelay(0).setNumber(1);
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.dropAll();
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/FishAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/FishAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..d92ea54770bce73c2f10f1ebcb0dff5b9532e0e9
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/FishAction.java
@@ -0,0 +1,70 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+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 top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class FishAction extends BotAction {
+
+ public FishAction() {
+ super("fish", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new FishAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public BotAction setTickDelay(int tickDelay) {
+ super.setTickDelay(0);
+ this.delay = tickDelay;
+ return this;
+ }
+
+ private int delay = 0;
+ private int nowDelay = 0;
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ if (nowDelay > 0) {
+ nowDelay--;
+ 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);
+ nowDelay = 20;
+ return false;
+ }
+ if (fishingHook.nibble > 0) {
+ mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND);
+ nowDelay = delay;
+ return true;
+ }
+ } else {
+ mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/JumpAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/JumpAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..d99f667992e45e85c0fe0bd74682d563fe1315eb
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/JumpAction.java
@@ -0,0 +1,35 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class JumpAction extends BotAction {
+
+ public JumpAction() {
+ super("jump", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new JumpAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ if (bot.onGround()) {
+ bot.jumpFromGround();
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/LookAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/LookAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..5432e61c156a1a6d49dcf4b24e3bcfcc6c1aa7bb
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/LookAction.java
@@ -0,0 +1,49 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class LookAction extends BotAction {
+
+ public LookAction() {
+ super("look", new CommandArgument(CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE));
+ setTabComplete(0, List.of("<X>"));
+ setTabComplete(1, List.of("<Y>"));
+ setTabComplete(2, List.of("<Z>"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ Vector pos = result.readVector();
+ if (pos != null) {
+ return new LookAction().setPos(pos).setTickDelay(0).setNumber(1);
+ } else {
+ return null;
+ }
+ }
+
+ private Vector pos;
+
+ public LookAction setPos(Vector pos) {
+ if (pos != null) {
+ this.pos = pos;
+ return this;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.look(pos.subtract(bot.getLocation().toVector()), false);
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/RotateAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/RotateAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e5be54b33467591924cb2400639fb593dc50ec6
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/RotateAction.java
@@ -0,0 +1,33 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+public class RotateAction extends BotAction {
+
+ public RotateAction() {
+ super("rotate", new CommandArgument());
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new RotateAction().setPlayer(player).setTickDelay(0).setNumber(1);
+ }
+
+ private ServerPlayer player;
+
+ public RotateAction setPlayer(ServerPlayer player) {
+ this.player = player;
+ return this;
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.faceLocation(player.getBukkitEntity().getLocation());
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/SneakAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/SneakAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..18e276c0252fa867b2cdc9770e3a7ed0b9cc63de
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/SneakAction.java
@@ -0,0 +1,27 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.Pose;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+public class SneakAction extends BotAction {
+
+ public SneakAction() {
+ super("sneak", new CommandArgument());
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return this.setTickDelay(0).setNumber(1);
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.setShiftKeyDown(!bot.isShiftKeyDown());
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/StopAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/StopAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..36d199269afc46783b0815e3887842cd82b6e813
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/StopAction.java
@@ -0,0 +1,26 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+public class StopAction extends BotAction {
+
+ public StopAction() {
+ super("stop", new CommandArgument());
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return this.setTickDelay(0).setNumber(0);
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ this.setCancel(true);
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/SwimAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/SwimAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..58c815bd0ebfd455fcf4903ee5ced6b81be00982
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/SwimAction.java
@@ -0,0 +1,26 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+
+public class SwimAction extends BotAction {
+
+ public SwimAction() {
+ super("swim", new CommandArgument());
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return this.setTickDelay(0).setNumber(1);
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.waterSwim = !bot.waterSwim;
+ return true;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..5dc3fbf8e62ccffc8291962c835a568efd65d7af
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemAction.java
@@ -0,0 +1,33 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemAction extends BotAction {
+
+ public UseItemAction() {
+ super("use", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.punch();
+ bot.updateItemInMainHand();
+ return bot.getInventory().getSelected().use(bot.level(), bot, InteractionHand.MAIN_HAND).getResult().consumesAction();
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..345932e779f5187355ca722c2bb9b05f384660a1
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java
@@ -0,0 +1,33 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemOffHandAction extends BotAction {
+
+ public UseItemOffHandAction() {
+ super("use_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemOffHandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ bot.punch();
+ bot.updateItemInOffHand();
+ return bot.getInventory().getSelected().use(bot.level(), bot, InteractionHand.OFF_HAND).getResult().consumesAction();
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4837f60909763df89ea7474f70dd0236360e657
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java
@@ -0,0 +1,56 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.level.ClipContext;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.TrappedChestBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.scheduler.CraftScheduler;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemOnAction extends BotAction {
+
+ public UseItemOnAction() {
+ super("use_on", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemOnAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE);
+ if (result instanceof BlockHitResult blockHitResult) {
+ BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos());
+ bot.punch();
+ if (state.getBlock() == Blocks.TRAPPED_CHEST) {
+ BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos());
+ if (entity instanceof TrappedChestBlockEntity chestBlockEntity) {
+ chestBlockEntity.startOpen(bot);
+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> chestBlockEntity.stopOpen(bot), 1);
+ return true;
+ }
+ } else {
+ bot.updateItemInMainHand();
+ return bot.gameMode.useItemOn(bot, bot.level(), bot.getItemInHand(InteractionHand.MAIN_HAND), InteractionHand.MAIN_HAND, (BlockHitResult) result).consumesAction();
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ab84fba3624a8e8c4d345c03fe678a012a5c367
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java
@@ -0,0 +1,56 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.level.ClipContext;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.TrappedChestBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.HitResult;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.scheduler.CraftScheduler;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemOnOffhandAction extends BotAction {
+
+ public UseItemOnOffhandAction() {
+ super("use_on_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemOnOffhandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE);
+ if (result instanceof BlockHitResult blockHitResult) {
+ BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos());
+ bot.punch();
+ if (state.getBlock() == Blocks.TRAPPED_CHEST) {
+ BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos());
+ if (entity instanceof TrappedChestBlockEntity chestBlockEntity) {
+ chestBlockEntity.startOpen(bot);
+ Bukkit.getScheduler().runTaskLater(CraftScheduler.MINECRAFT, () -> chestBlockEntity.stopOpen(bot), 1);
+ return true;
+ }
+ } else {
+ bot.updateItemInMainHand();
+ return bot.gameMode.useItemOn(bot, bot.level(), bot.getItemInHand(InteractionHand.OFF_HAND), InteractionHand.OFF_HAND, (BlockHitResult) result).consumesAction();
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc8689ee726144f220e4ccc5cd418b79a29b79ab
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToAction.java
@@ -0,0 +1,38 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.phys.EntityHitResult;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemToAction extends BotAction {
+
+ public UseItemToAction() {
+ super("use_to", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemToAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ EntityHitResult result = bot.getTargetEntity(3);
+ if (result != null) {
+ bot.punch();
+ bot.updateItemInMainHand();
+ return result.getEntity().interact(bot, InteractionHand.MAIN_HAND).consumesAction();
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fde496c993ec0c961a63b32a8088479da88c91d
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java
@@ -0,0 +1,38 @@
+package top.leavesmc.leaves.bot.agent.actions;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.phys.EntityHitResult;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.command.CommandArgument;
+import top.leavesmc.leaves.command.CommandArgumentResult;
+import top.leavesmc.leaves.command.CommandArgumentType;
+
+import java.util.List;
+
+public class UseItemToOffhandAction extends BotAction {
+
+ public UseItemToOffhandAction() {
+ super("use_to_offhand", new CommandArgument(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER));
+ setTabComplete(0, List.of("[TickDelay]"));
+ setTabComplete(1, List.of("[DoNumber]"));
+ }
+
+ @Override
+ public BotAction getNew(@NotNull ServerPlayer player, @NotNull CommandArgumentResult result) {
+ return new UseItemToOffhandAction().setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
+ }
+
+ @Override
+ public boolean doTick(@NotNull ServerBot bot) {
+ EntityHitResult result = bot.getTargetEntity(3);
+ if (result != null) {
+ bot.punch();
+ bot.updateItemInOffHand();
+ return result.getEntity().interact(bot, InteractionHand.OFF_HAND).consumesAction();
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/command/CommandArgument.java b/src/main/java/top/leavesmc/leaves/command/CommandArgument.java
index eadc6d168fb13299348b0c275ae352ee2f1e1ea2..134c6d26acc612bf6142ae6b6885a0ee53d2a196 100644
--- a/src/main/java/top/leavesmc/leaves/command/CommandArgument.java
+++ b/src/main/java/top/leavesmc/leaves/command/CommandArgument.java
@@ -32,6 +32,12 @@ public class CommandArgument {
return this;
}
+ public CommandArgument setAllTabComplete(List<List<String>> tabComplete) {
+ this.tabComplete.clear();
+ this.tabComplete.addAll(tabComplete);
+ return this;
+ }
+
public CommandArgumentResult parse(int index, String @NotNull [] args) {
Object[] result = new Object[argumentTypes.size()];
Arrays.fill(result, null);
diff --git a/src/main/java/top/leavesmc/leaves/command/CommandArgumentResult.java b/src/main/java/top/leavesmc/leaves/command/CommandArgumentResult.java
index 340eaca64c96180b895a075ce9e44402cd104eed..39e90dcff0de259373d7955021c29397c2cc15d5 100644
--- a/src/main/java/top/leavesmc/leaves/command/CommandArgumentResult.java
+++ b/src/main/java/top/leavesmc/leaves/command/CommandArgumentResult.java
@@ -58,5 +58,4 @@ public class CommandArgumentResult {
return null;
}
}
-
}
diff --git a/src/main/java/top/leavesmc/leaves/entity/CraftBot.java b/src/main/java/top/leavesmc/leaves/entity/CraftBot.java
new file mode 100644
index 0000000000000000000000000000000000000000..713240da3ba37915b455d952a45ae7f68b8294ee
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/entity/CraftBot.java
@@ -0,0 +1,67 @@
+package top.leavesmc.leaves.entity;
+
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.Actions;
+import top.leavesmc.leaves.bot.agent.BotAction;
+import top.leavesmc.leaves.entity.botaction.LeavesBotAction;
+
+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 getHandle().createState.skinName;
+ }
+
+ @Override
+ public @NotNull String getRealName() {
+ return getHandle().createState.getRealName();
+ }
+
+ @Override
+ public @Nullable UUID getCreatePlayerUUID() {
+ return getHandle().createPlayer;
+ }
+
+ @Override
+ public boolean setBotAction(@NotNull String action, @NotNull Player player, @NotNull String[] args) {
+ BotAction botAction = Actions.getForName(action);
+ if (botAction != null) {
+ BotAction newAction = botAction.getNew(((CraftPlayer) player).getHandle(), botAction.getArgument().parse(0, args));
+ if (newAction != null) {
+ getHandle().setBotAction(newAction);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean setBotAction(@NotNull LeavesBotAction action, @NotNull Player player, @NotNull String[] args) {
+ return setBotAction(action.getName(), player, args);
+ }
+
+ @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/src/main/java/top/leavesmc/leaves/entity/CraftBotManager.java b/src/main/java/top/leavesmc/leaves/entity/CraftBotManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..df5796bfa333a287ccd486be9a9cdae4ca5dc757
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/entity/CraftBotManager.java
@@ -0,0 +1,93 @@
+package top.leavesmc.leaves.entity;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import org.bukkit.Location;
+import org.bukkit.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import top.leavesmc.leaves.bot.ServerBot;
+import top.leavesmc.leaves.bot.agent.Actions;
+import top.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction;
+import top.leavesmc.leaves.entity.botaction.CustomBotAction;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+
+public class CraftBotManager implements BotManager {
+
+ private final Collection<Bot> botViews = Collections.unmodifiableList(Lists.transform(ServerBot.getBots(), new Function<ServerBot, CraftBot>() {
+ @Override
+ public CraftBot apply(ServerBot bot) {
+ return bot.getBukkitEntity();
+ }
+ }));
+
+ @Override
+ public @Nullable Bot getBot(@NotNull UUID uuid) {
+ return ServerBot.getBot(uuid).getBukkitPlayer();
+ }
+
+ @Override
+ public @Nullable Bot getBot(@NotNull String name) {
+ return ServerBot.getBot(name).getBukkitPlayer();
+ }
+
+ @Override
+ public @Nullable Bot createBot(@NotNull String name, @NotNull String realName, @Nullable String[] skin, @Nullable String skinName, @NotNull Location location) {
+ ServerBot bot = new ServerBot.BotCreateState(location, name, realName, skinName, skin).createSync();
+ if (bot != null) {
+ return bot.getBukkitPlayer();
+ }
+ return null;
+ }
+
+ @Override
+ public void createBot(@NotNull String name, @Nullable String skinName, @NotNull Location location, Consumer<Bot> consumer) {
+ new ServerBot.BotCreateState(location, name, skinName).createAsync((serverBot -> {
+ consumer.accept(serverBot.getBukkitPlayer());
+ }));
+ }
+
+ @Override
+ public void removeBot(@NotNull String name) {
+ ServerBot bot = ServerBot.getBot(name);
+ if (bot != null) {
+ bot.die(bot.damageSources().fellOutOfWorld());
+ }
+ }
+
+ @Override
+ public void removeBot(@NotNull UUID uuid) {
+ ServerBot bot = ServerBot.getBot(uuid);
+ if (bot != null) {
+ bot.die(bot.damageSources().fellOutOfWorld());
+ }
+ }
+
+ @Override
+ public void removeAllBots() {
+ ServerBot.removeAllBot();
+ }
+
+ @Override
+ public void saveOrRemoveAllBots() {
+ ServerBot.saveOrRemoveAllBot();
+ }
+
+ @Override
+ public Collection<Bot> getBots() {
+ return botViews;
+ }
+
+ @Override
+ public boolean registerCustomBotAction(String name, CustomBotAction action) {
+ return Actions.register(new CraftCustomBotAction(name, action));
+ }
+
+ @Override
+ public boolean unregisterCustomBotAction(String name) {
+ return Actions.unregister(name);
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/util/MathUtils.java b/src/main/java/top/leavesmc/leaves/util/MathUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..349cd0c0d2d9dc2c9c745ef3469e548a798931ba
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/util/MathUtils.java
@@ -0,0 +1,78 @@
+package top.leavesmc.leaves.util;
+
+import org.bukkit.util.NumberConversions;
+import org.bukkit.util.Vector;
+
+import java.util.regex.Pattern;
+
+public class MathUtils {
+ // Lag ?
+ public static void clean(Vector vector) {
+ if (!NumberConversions.isFinite(vector.getX())) vector.setX(0);
+ if (!NumberConversions.isFinite(vector.getY())) vector.setY(0);
+ if (!NumberConversions.isFinite(vector.getZ())) vector.setZ(0);
+ }
+
+ 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[] fetchYawPitch(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(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;
+ }
+
+ 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;
+ }
+}