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/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 68a242f6aaa4aeebff5c2c566ac2aab538eccc87..8e9f72fe364bc4ad51d7a235531af09df5cdcfa9 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -121,6 +121,7 @@ import net.minecraft.util.profiling.metrics.storage.MetricsPersister; import net.minecraft.util.thread.ReentrantBlockableEventLoop; import net.minecraft.world.Difficulty; import net.minecraft.world.RandomSequences; +import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.village.VillageSiege; import net.minecraft.world.entity.npc.CatSpawner; @@ -617,6 +618,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop loot = new java.util.ArrayList(this.getInventory().getContainerSize()); 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(CraftItemStack.asCraftMirror(item)); @@ -1268,6 +1272,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/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index b58dea1fd418fa3199b403df079f87990ffdd6a8..cc610748dd0ae033c8e48d30f1077c1f9697dac3 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -101,6 +101,7 @@ import net.minecraft.world.scores.Objective; import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; // Paper import net.minecraft.world.scores.Team; +import top.leavesmc.leaves.bot.ServerBot; import org.slf4j.Logger; // CraftBukkit start @@ -110,7 +111,6 @@ import org.bukkit.Location; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerChangedWorldEvent; @@ -119,7 +119,6 @@ import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; -import org.bukkit.event.player.PlayerSpawnChangeEvent; // CraftBukkit end public abstract class PlayerList { @@ -329,6 +328,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 @@ -981,6 +995,13 @@ public abstract class PlayerList { } // Paper end + // Leaves start - bot support + if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { + top.leavesmc.leaves.bot.ServerBot.getBots().forEach(bot1 -> + bot1.sendFakeDataIfNeed(entityplayer1, true)); // Leaves - render bot + } + // Leaves end - bot support + // CraftBukkit end return entityplayer1; } @@ -1091,11 +1112,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; } @@ -1572,4 +1598,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 3f439b316960a734d0585dccdd331b251dfbfe14..75633a872e15d2367d7962bac97152a26ebd9f28 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1383,7 +1383,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { 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 a9eaa079a43bc8a5e81deaf6df5ce2f9c53cb319..d0752407684312982188abd0fa4af4f706edc3e3 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 DATA_HOOKED_ENTITY = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_BITING = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.BOOLEAN); private int life; - private int nibble; + public int nibble; // Leaves - private -> public public int timeUntilLured; 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 ffd349c1b80df0f1e8c02bda23700184825170fd..bc734baa18eb12b499bca2c0fc6b8d8960b3ba26 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; @@ -676,6 +678,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 dfeb3e336e06ef01f5401a362755030db942bb07..c51413bf02b3572a05f09d2f0d02493db29a0df7 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 (top.leavesmc.leaves.LeavesConfig.fakeplayerSpawnPhantom && entityplayer instanceof top.leavesmc.leaves.bot.ServerBot) { + j = world.paperConfig().entities.behavior.playerInsomniaStartTicks + 1; + } + // Leaves end - fakeplayer spawn if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper 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 f4f11103674860b676f11a867feadaf8c285d5c6..8d74a6b34ae4abefaa4ad5c4387bea6236e7f97c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -262,6 +262,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 @@ -307,6 +308,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 + 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(); @@ -3140,4 +3142,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 e932cfac619c30b8c7444a9fa41e0403a6eadf6a..8818daa3e89fd5a5b0a0ea4069ccbf0f15aed9aa 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -194,6 +194,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 @@ -231,7 +233,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { if (entity instanceof LivingEntity) { // Players if (entity instanceof net.minecraft.world.entity.player.Player) { - if (entity instanceof ServerPlayer) { return new CraftPlayer(server, (ServerPlayer) entity); } + // Leaves start - add CraftBot + if (entity instanceof ServerPlayer) { + if (entity instanceof ServerBot) { return new CraftBot(server, (ServerBot) entity); } + else { return new CraftPlayer(server, (ServerPlayer) entity); } + } + // Leaves end - add CraftBot else { return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity); } } // Water Animals diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java index 85fb668409eb3dea6c174b88654ac7db9fa020ca..e2b7517208edeb3b646d9c8f81bf3298ddfb7def 100644 --- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java @@ -8,6 +8,9 @@ import org.bukkit.command.Command; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import top.leavesmc.leaves.command.LeavesCommand; +import top.leavesmc.leaves.bot.BotCommand; +import top.leavesmc.leaves.bot.agent.Actions; +import top.leavesmc.leaves.util.MathUtils; import java.io.File; import java.lang.reflect.InvocationTargetException; @@ -70,6 +73,11 @@ public final class LeavesConfig { commands = new HashMap<>(); commands.put("leaves", new LeavesCommand("leaves")); + + if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { + commands.put("bot", new BotCommand("bot")); + Actions.registerAll(); + } } public static void load(final YamlConfiguration config) { 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..ad49275aca2649ae0e7a6f90cfda9f439d72b067 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/bot/BotCommand.java @@ -0,0 +1,275 @@ +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.LeavesLogger; +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 tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args, Location location) throws IllegalArgumentException { + var list = new ArrayList(); + + if (args.length <= 1) { + list.add("create"); + list.add("remove"); + list.add("action"); + list.add("list"); + } + + if (args.length == 2) { + switch (args[0]) { + case "create" -> list.add(""); + case "remove", "action" -> + 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(""); + } + } + + 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)) 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 "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 [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(null); + } else if (sender instanceof ConsoleCommandSender) { + if (args.length < 6) { + sender.sendMessage(ChatColor.RED + "Use /bot create 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 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 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 void onList(CommandSender sender, String @NotNull [] args) { + if (args.length < 2) { + Map> botMap = new HashMap<>(); + for (World world : Bukkit.getWorlds()) { + botMap.put(world, new ArrayList<>()); + } + + for (ServerBot bot : ServerBot.getBots()) { + Bot bukkitBot = bot.getBukkitPlayer(); + 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 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 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..9fce8c91bf0c211c2d87993cc0e2d4e8f5fe3d19 --- /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 items; + public final NonNullList armor; + public final NonNullList offhand; + private final List> compartments; + private final NonNullList buttons = NonNullList.withSize(13, ItemStack.EMPTY); + private final ServerBot player; + + public BotInventoryContainer(ServerBot player) { + this.player = player; + this.items = this.player.getInventory().items; + this.armor = this.player.getInventory().armor; + this.offhand = this.player.getInventory().offhand; + this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons); + createButton(); + } + + @Override + public int getContainerSize() { + return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size(); + } + + @Override + public boolean isEmpty() { + for (ItemStack itemStack : this.items) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + for (ItemStack itemStack : this.armor) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + for (ItemStack itemStack : this.offhand) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + return true; + } + + @Override + @Nonnull + public ItemStack getItem(int slot) { + Pair, Integer> pair = getItemSlot(slot); + if (pair != null) { + return pair.getFirst().get(pair.getSecond()); + } else { + return ItemStack.EMPTY; + } + } + + public Pair, Integer> getItemSlot(int slot) { + switch (slot) { + case 0 -> { + return new Pair<>(buttons, 0); + } + case 1, 2, 3, 4 -> { + return new Pair<>(armor, 4 - slot); + } + case 5, 6 -> { + 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, Integer> pair = getItemSlot(slot); + NonNullList 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.detectEquipmentUpdates(); + } + return itemStack; + } + + @Override + @Nonnull + public ItemStack removeItemNoUpdate(int slot) { + Pair, Integer> pair = getItemSlot(slot); + NonNullList 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, Integer> pair = getItemSlot(slot); + NonNullList list = null; + if (pair != null) { + list = pair.getFirst(); + slot = pair.getSecond(); + } + if (list != null) { + list.set(slot, stack); + player.detectEquipmentUpdates(); + } + } + + @Override + public void setChanged() { + } + + @Override + public boolean stillValid(@Nonnull Player player) { + if (this.player.isRemoved()) { + return false; + } + return !(player.distanceToSqr(this.player) > 64.0); + } + + @Override + public void clearContent() { + for (List 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..be3a40e3100ec838cf8de6437cc6b1b0c454bc68 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/bot/BotUtil.java @@ -0,0 +1,182 @@ +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.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 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 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); + } catch (IOException e) { + e.printStackTrace(); + } + + return fakePlayer; + } + + public static void loadBot(Map.@NotNull Entry 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); + 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 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..ad84c8195ae21d1faf85a7bf7e97b8a6f6b02151 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/bot/ServerBot.java @@ -0,0 +1,710 @@ +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.entity.CraftPlayer; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Contract; +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.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 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; + + private final ServerStatsCounter stats; + private final BotInventoryContainer container; + + private static final List bots = new CopyOnWriteArrayList<>(); + private static final Plugin MINECRAFT_PLUGIN = new MinecraftInternalPlugin(); + + 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.fauxSleeping = LeavesConfig.fakeplayerSkipSleep; + this.waterSwim = true; + this.knockback = Vec3.ZERO; + this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange; + } + + 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.CLIENTBOUND) { + @Override + public void send(@NotNull Packet packet, @Nullable PacketSendListener packetsendlistener) { + } + }, 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 LeavesConfig.alwaysSendFakeplayerData && (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(MINECRAFT_PLUGIN, () -> { + 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 (fireTicks > 0) { + --fireTicks; + } + if (jumpTicks > 0) { + --jumpTicks; + } + if (noFallTicks > 0) { + --noFallTicks; + } + if (takeXpDelay > 0) { + --takeXpDelay; + } + + this.updateLocation(); + this.updatePlayerPose(); + + float health = getHealth(); + float maxHealth = getMaxHealth(); + float regenAmount = 0.010f; + 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 list = this.level().getEntities(this, axisalignedbb); + List list1 = Lists.newArrayList(); + + for (Entity entity : list) { + if (entity.getType() == EntityType.EXPERIENCE_ORB) { + list1.add(entity); + } else if (!entity.isRemoved()) { + this.touch(entity); + } + } + + if (!list1.isEmpty()) { + this.touch(Util.getRandom(list1, this.random)); + } + } + + Iterator> iterator = actions.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue().isCancel()) { + iterator.remove(); + } else { + entry.getValue().tryTick(this); + } + } + } + + 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(); + detectEquipmentUpdates(); + } + + public void updateItemInOffHand() { + tryReplenishOrReplaceInOffHand(); + detectEquipmentUpdates(); + } + + 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(); + detectEquipmentUpdates(); + } + + public void setBotAction(BotAction action) { + if (action instanceof StopAction) { + this.actions.clear(); + } + action.init(); + this.actions.put(action.getName(), action); + } + + public Collection getBotActions() { + return actions.values(); + } + + public BotAction getBotAction(String name) { + return actions.get(name); + } + + @Deprecated + public BotAction getBotAction() { + return null; + } + + @Override + public @NotNull ServerStatsCounter getStats() { + return stats; + } + + public BotInventoryContainer getContainer() { + return container; + } + + @Override + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { + if (LeavesConfig.openFakeplayerInventory) { + if (player.getMainHandItem().isEmpty()) { + 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 entry : fakePlayerList.entrySet()) { + BotUtil.loadBot(entry); + } + file.delete(); + } + } + + public static boolean removeAllBot() { + Iterator iterator = bots.iterator(); + while (iterator.hasNext()) { + ServerBot bot = iterator.next(); + bot.die(bot.damageSources().fellOutOfWorld()); + } + return true; + } + + public static List 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 consumer) { + Bukkit.getScheduler().runTaskAsynchronously(MINECRAFT_PLUGIN, () -> { + if (skinName != null) { + this.skin = MojangAPI.getSkin(skinName); + } + + Bukkit.getScheduler().runTask(MINECRAFT_PLUGIN, () -> { + 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 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 getAll() { + return actions.values(); + } + + @NotNull + public static Set 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 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 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("")); + setTabComplete(1, List.of("")); + setTabComplete(2, List.of("")); + } + + @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..886e37227e66dc25653b8ad53fef600c705aa101 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java @@ -0,0 +1,40 @@ +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.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +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.getType() == HitResult.Type.BLOCK) { + bot.punch(); + 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..16ae37e2ffb4189041986e759d563d00a9126ad8 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java @@ -0,0 +1,40 @@ +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.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +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.getType() == HitResult.Type.BLOCK) { + bot.punch(); + bot.updateItemInOffHand(); + 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> 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..e30ce391f638778fc805ca75287917af99b37443 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/entity/CraftBot.java @@ -0,0 +1,60 @@ +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; + +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 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 botViews = Collections.unmodifiableList(Lists.transform(ServerBot.getBots(), new Function() { + @Override + public CraftBot apply(ServerBot bot) { + return bot.getBukkitEntity(); + } + })); + + @Override + public @Nullable Bot getBot(@NotNull UUID uuid) { + return ServerBot.getBot(uuid).getBukkitPlayer(); + } + + @Override + public @Nullable Bot getBot(@NotNull String name) { + return ServerBot.getBot(name).getBukkitPlayer(); + } + + @Override + public @Nullable Bot createBot(@NotNull String name, @NotNull String realName, @Nullable String[] skin, @Nullable String skinName, @NotNull Location location) { + 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 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 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; + } +}