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/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java index a1c9726d25479b5326fe2fa2b0f5a98d6b2da4c5..0724bd95143cb5dc69b5f1eb2e67ecd679e09a99 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java @@ -41,6 +41,12 @@ class PaperEventManager { throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } + // Leaves start - skip bot + if (event instanceof org.bukkit.event.player.PlayerEvent playerEvent && playerEvent.getPlayer() instanceof org.leavesmc.leaves.entity.Bot) { + return; + } + // Leaves end - skip bot + HandlerList handlers = event.getHandlers(); RegisteredListener[] listeners = handlers.getRegisteredListeners(); diff --git a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java index 35772110e9318df46a2729dbc0b5879b290011b7..f26989a44cdda9baabf337d573436c6c115c9884 100644 --- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java @@ -39,6 +39,7 @@ public abstract class SimpleCriterionTrigger predicate) { + if (player instanceof org.leavesmc.leaves.bot.ServerBot) return; // Leaves - bot skip PlayerAdvancements playerAdvancements = player.getAdvancements(); Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak if (set != null && !set.isEmpty()) { diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 3e550f8e7cd4f4e16f499a8a2a4b95420270f07a..46d9c77581b78c427692aa8645d17b3d0c2bb6a6 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -104,7 +104,7 @@ public class Connection extends SimpleChannelInboundHandler> { @Nullable private volatile PacketListener disconnectListener; @Nullable - private volatile PacketListener packetListener; + protected volatile PacketListener packetListener; // Leaves - private -> protected @Nullable private DisconnectionDetails disconnectionDetails; private boolean encrypted; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 7baa336edec37d8ca1d63f71b25d0daf035cdaf5..cb863f07d00e96b92e73dc312606110c259b4d5c 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -318,6 +318,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); Thread thread = new ca.spottedleaf.moonrise.common.util.TickThread(() -> { // Paper - rewrite chunk system @@ -745,6 +747,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop realPlayers; // Leaves - skip public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately @@ -589,6 +590,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. this.chunkTaskScheduler = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler((ServerLevel)(Object)this, ca.spottedleaf.moonrise.common.util.MoonriseCommon.WORKER_POOL); // Paper end - rewrite chunk system this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.realPlayers = Lists.newArrayList(); // Leaves - skip } // Paper start @@ -2024,6 +2026,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. return this.players; } + // Leaves start - fakeplayer skip + public List realPlayers() { + return this.realPlayers; + } + // Leaves end - fakeplayer skip + @Override public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) { Optional> optional = PoiTypes.forState(oldBlock); @@ -2495,6 +2503,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true if (entity instanceof ServerPlayer entityplayer) { ServerLevel.this.players.add(entityplayer); + // Leaves start - skip + if (!(entityplayer instanceof org.leavesmc.leaves.bot.ServerBot)) { + ServerLevel.this.realPlayers.add(entityplayer); + } + // Leaves end - skip ServerLevel.this.updateSleepingPlayerList(); } @@ -2572,6 +2585,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. ServerLevel.this.getChunkSource().removeEntity(entity); if (entity instanceof ServerPlayer entityplayer) { ServerLevel.this.players.remove(entityplayer); + // Leaves start - skip + if (!(entityplayer instanceof org.leavesmc.leaves.bot.ServerBot)) { + ServerLevel.this.realPlayers.remove(entityplayer); + } + // Leaves end - skip ServerLevel.this.updateSleepingPlayerList(); } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index c396580a9cfd86ff261bed439bb4662ae88010b5..c2263ef7fb67db7c01bac7aecf218ee8cb7369cb 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -212,7 +212,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple private static final AttributeModifier CREATIVE_ENTITY_INTERACTION_RANGE_MODIFIER = new AttributeModifier(ResourceLocation.withDefaultNamespace("creative_mode_entity_range"), 2.0D, AttributeModifier.Operation.ADD_VALUE); public ServerGamePacketListenerImpl connection; public final MinecraftServer server; - public final ServerPlayerGameMode gameMode; + public ServerPlayerGameMode gameMode; // Leaves - final -> null private final PlayerAdvancements advancements; private final ServerStatsCounter stats; private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; @@ -770,16 +770,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple --this.invulnerableTime; } - // Paper start - Configurable container update tick rate - if (--containerUpdateDelay <= 0) { - this.containerMenu.broadcastChanges(); - containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; - } - // Paper end - Configurable container update tick rate - if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen - this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; + // Leaves start - skip bot + if (!(this instanceof org.leavesmc.leaves.bot.ServerBot)) { + // Paper start - Configurable container update tick rate + if (--containerUpdateDelay <= 0) { + this.containerMenu.broadcastChanges(); + containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; + } + // Paper end - Configurable container update tick rate + if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } } + // Leaves end - skip bot Entity entity = this.getCamera(); @@ -795,7 +799,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple } } - CriteriaTriggers.TICK.trigger(this); + if (!(this instanceof org.leavesmc.leaves.bot.ServerBot)) CriteriaTriggers.TICK.trigger(this); // Leaves - skip bot if (this.levitationStartPos != null) { CriteriaTriggers.LEVITATION.trigger(this, this.levitationStartPos, this.tickCount - this.levitationStartTime); } @@ -1416,6 +1420,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple this.lastSentHealth = -1.0F; this.lastSentFood = -1; + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { + this.server.getBotList().bots.forEach(bot -> bot.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 e6a1942d44c589d6815824646b5517a5938385c3..e53e5011af49da4222f4c38e31f1500158c5a5a7 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -123,6 +123,8 @@ import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; import org.bukkit.event.player.PlayerSpawnChangeEvent; // CraftBukkit end +import org.leavesmc.leaves.bot.ServerBot; + public abstract class PlayerList { public static final File USERBANLIST_FILE = new File("banned-players.json"); @@ -351,6 +353,19 @@ public abstract class PlayerList { org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { + ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); + if (bot != null) { + this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false); + } + this.server.getBotList().bots.forEach(bot1 -> { + bot1.sendPlayerInfo(player); + bot1.sendFakeDataIfNeed(player, true); + }); // Leaves - render bot + } + // Leaves end - bot support + final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure @@ -941,6 +956,12 @@ public abstract class PlayerList { } // Paper end - Add PlayerPostRespawnEvent + // Leaves start - bot support + if (org.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { + this.server.getBotList().bots.forEach(bot -> bot.sendFakeDataIfNeed(entityplayer1, true)); // Leaves - render bot + } + // Leaves end - bot support + // CraftBukkit end return entityplayer1; @@ -1078,11 +1099,16 @@ public abstract class PlayerList { } public String[] getPlayerNamesArray() { - String[] astring = new String[this.players.size()]; + String[] astring = new String[this.players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support for (int i = 0; i < this.players.size(); ++i) { astring[i] = ((ServerPlayer) this.players.get(i)).getGameProfile().getName(); } + // Leaves start - fakeplayer support + for (int i = this.players.size(); i < astring.length; ++i) { + astring[i] = ((ServerPlayer) this.server.getBotList().bots.get(i - this.players.size())).getGameProfile().getName(); + } + // Leaves end - fakeplayer support return astring; } @@ -1168,7 +1194,13 @@ public abstract class PlayerList { @Nullable public ServerPlayer getPlayerByName(String name) { - return this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); // Spigot + // Leaves start - fakeplayer support + ServerPlayer player = this.playersByName.get(name.toLowerCase(java.util.Locale.ROOT)); + if (player == null) { + player = this.server.getBotList().getBotByName(name); + } + return player; // Spigot + // Leaves end - fakeplayer support } public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { @@ -1508,7 +1540,13 @@ public abstract class PlayerList { @Nullable public ServerPlayer getPlayer(UUID uuid) { - return (ServerPlayer) this.playersByUUID.get(uuid); + // Leaves start - fakeplayer support + ServerPlayer player = this.playersByUUID.get(uuid); + if (player == null) { + player = this.server.getBotList().getBot(uuid); + } + return player; + // Leaves start - fakeplayer support } public boolean canBypassPlayerLimit(GameProfile profile) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index a1946e6346a8d4afa0f7ebe69e7ac866432e08ab..cb67c961ad3a34fb58172c3f4176e82fadfe199e 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1455,7 +1455,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 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/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index 09bcbc0ae36e4e69fee87a7e0c49acf496117a39..65b002f2ae062327f48df0e157aa35721478c5fd 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -183,7 +183,7 @@ public abstract class Player extends LivingEntity { private int lastLevelUpTime; public GameProfile gameProfile; private boolean reducedDebugInfo; - private ItemStack lastItemInMainHand; + protected ItemStack lastItemInMainHand; // Leaves - private -> protected private final ItemCooldowns cooldowns; private Optional lastDeathLocation; @Nullable @@ -337,6 +337,12 @@ public abstract class Player extends LivingEntity { } + // Leaves start - fakeplayer + protected void livingEntityTick() { + super.tick(); + } + // Leaves end - fakeplayer + @Override protected float getMaxHeadRotationRelativeToBody() { return this.isBlocking() ? 15.0F : super.getMaxHeadRotationRelativeToBody(); @@ -635,7 +641,7 @@ public abstract class Player extends LivingEntity { } - private void touch(Entity entity) { + public void touch(Entity entity) { // Leaves - private -> public entity.playerTouch(this); } diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java index 1223c5d23d0ea6aed068bdf0f5725e2ad49fc82c..0e00f59a8962dd6356d483ef5be3209a3a410008 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java @@ -63,7 +63,7 @@ public class FishingHook extends Projectile { public static final EntityDataAccessor DATA_HOOKED_ENTITY = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_BITING = SynchedEntityData.defineId(FishingHook.class, EntityDataSerializers.BOOLEAN); private int life; - private int nibble; + public int nibble; // Leaves - private -> public public int timeUntilLured; public int timeUntilHooked; public float fishAngle; diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index dd4218e108f87f3305b76fbc8d88f488b447c609..9bb703f2af1f201772b5309a4d80b3fbe23a7201 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -406,6 +406,8 @@ public abstract class AbstractContainerMenu { ItemStack itemstack1; int l; + if (!doClickCheck(slotIndex, button, actionType, player)) return; // Leaves - doClick check + if (actionType == ClickType.QUICK_CRAFT) { int i1 = this.quickcraftStatus; @@ -680,6 +682,22 @@ public abstract class AbstractContainerMenu { } + // Leaves start - doClick check + private boolean doClickCheck(int slotIndex, int button, ClickType actionType, Player player) { + if (slotIndex < 0) { + return true; + } + + Slot slot = getSlot(slotIndex); + ItemStack itemStack = slot.getItem(); + net.minecraft.world.item.component.CustomData customData = itemStack.get(net.minecraft.core.component.DataComponents.CUSTOM_DATA); + if (customData != null && customData.contains("Leaves.Gui.Placeholder")) { + return !customData.copyTag().getBoolean("Leaves.Gui.Placeholder"); + } + 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 1b1b475ca27e799e251d6f8a8c9fe1a4fd8bae83..22f8e7d62df86a12c5b9ad709538d6ac564d3338 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java @@ -67,6 +67,11 @@ public class PhantomSpawner implements CustomSpawner { ServerStatsCounter serverstatisticmanager = entityplayer.getStats(); int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); boolean flag2 = true; + // Leaves start - fakeplayer spawn + if (entityplayer instanceof org.leavesmc.leaves.bot.ServerBot bot && bot.getConfigValue(org.leavesmc.leaves.bot.agent.Configs.SPAWN_PHANTOM)) { + j = Math.max(bot.notSleepTicks, 1); + } + // Leaves end - fakeplayer spawn if (randomsource.nextInt(j) >= world.paperConfig().entities.behavior.playerInsomniaStartTicks) { // Paper - Ability to control player's insomnia and phantoms BlockPos blockposition1 = blockposition.above(20 + randomsource.nextInt(15)).east(-10 + randomsource.nextInt(21)).south(-10 + randomsource.nextInt(21)); diff --git a/src/main/java/net/minecraft/world/level/storage/LevelResource.java b/src/main/java/net/minecraft/world/level/storage/LevelResource.java index fee8367d2812db559b15970f0a60023bedaaefc5..f6b59b00bb1611aff8d161d1ad03df7fc911f994 100644 --- a/src/main/java/net/minecraft/world/level/storage/LevelResource.java +++ b/src/main/java/net/minecraft/world/level/storage/LevelResource.java @@ -15,7 +15,7 @@ public class LevelResource { public static final LevelResource ROOT = new LevelResource("."); private final String id; - private LevelResource(String relativePath) { + public LevelResource(String relativePath) { // Leaves - private -> public this.id = relativePath; } diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java index b148cf247acdd36f856d0495cde4cc5ad32b5a2f..011d6c813781251c7f4041ad3a8396fbde28ff75 100644 --- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java @@ -21,7 +21,7 @@ import net.minecraft.world.entity.player.Player; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.slf4j.Logger; -public class PlayerDataStorage { +public class PlayerDataStorage implements org.leavesmc.leaves.bot.IPlayerDataStorage { private static final Logger LOGGER = LogUtils.getLogger(); private final File playerDir; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 275704db5b984584504749437e8ecdc60d4aa871..51365bf3a487725c79a8a3adae0821ced4f7abab 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -310,6 +310,7 @@ public final class CraftServer implements Server { private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes public final io.papermc.paper.SparksFly spark; // Paper - spark + private final org.leavesmc.leaves.entity.CraftBotManager botManager; // Leaves // Paper start - Folia region threading API private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); @@ -477,6 +478,7 @@ public final class CraftServer implements Server { datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol + this.botManager = new org.leavesmc.leaves.entity.CraftBotManager(); // Leaves } public boolean getCommandBlockOverride(String command) { @@ -1463,7 +1465,7 @@ public final class CraftServer implements Server { return false; } - if (handle.players().size() > 0) { + if (handle.realPlayers().size() > 0) { // Leaves - skip return false; } @@ -3226,4 +3228,11 @@ public final class CraftServer implements Server { return this.potionBrewer; } // Paper end + + // Leaves start - Bot API + @Override + public org.leavesmc.leaves.entity.CraftBotManager getBotManager() { + return botManager; + } + // Leaves end - Bot API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index cd3381d8225e0322b3e285b3c4aa0a1265a91cc0..9fbf9772b94cbead2567e3485e6e0e3f6fe57aa1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -210,7 +210,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getPlayerCount() { - return world.players().size(); + return world.realPlayers().size(); // Leaves - skip } @Override @@ -1252,9 +1252,9 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public List getPlayers() { - List list = new ArrayList(this.world.players().size()); + List list = new ArrayList(this.world.realPlayers().size()); // Leaves - skip - for (net.minecraft.world.entity.player.Player human : this.world.players()) { + for (net.minecraft.world.entity.player.Player human : this.world.realPlayers()) { // Leaves - skip HumanEntity bukkitEntity = human.getBukkitEntity(); if ((bukkitEntity != null) && (bukkitEntity instanceof Player)) { @@ -1939,7 +1939,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { public void playSound(final net.kyori.adventure.sound.Sound sound) { org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper final long seed = sound.seed().orElseGet(this.world.getRandom()::nextLong); - for (ServerPlayer player : this.getHandle().players()) { + for (ServerPlayer player : this.getHandle().realPlayers()) { // Leaves - skip player.connection.send(io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, player.getX(), player.getY(), player.getZ(), seed, null)); } } @@ -1955,7 +1955,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper final long seed = sound.seed().orElseGet(this.getHandle().getRandom()::nextLong); if (emitter == net.kyori.adventure.sound.Sound.Emitter.self()) { - for (ServerPlayer player : this.getHandle().players()) { + for (ServerPlayer player : this.getHandle().realPlayers()) { // Leaves - skip player.connection.send(io.papermc.paper.adventure.PaperAdventure.asSoundPacket(sound, player, seed, null)); } } else if (emitter instanceof CraftEntity craftEntity) { @@ -2176,7 +2176,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); } this.getHandle().sendParticles( - receivers == null ? this.getHandle().players() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API + receivers == null ? this.getHandle().realPlayers() : receivers.stream().map(player -> ((CraftPlayer) player).getHandle()).collect(java.util.stream.Collectors.toList()), // Paper - Particle API // Leaves - skip sender != null ? ((CraftPlayer) sender).getHandle() : null, // Sender // Paper - Particle API CraftParticle.createParticleParam(particle, data), // Particle x, y, z, // Position diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index cd789c235acf740ec29c30b180e7fbe1a140caa9..9c3df47ae65289c27a1d63ac06ca41c074ff374e 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -94,6 +94,8 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return new CraftHumanEntity(server, (net.minecraft.world.entity.player.Player) entity); } + if (entity instanceof org.leavesmc.leaves.bot.ServerBot bot) { return new org.leavesmc.leaves.entity.CraftBot(server, bot); } + // Special case complex part, since there is no extra entity type for them if (entity instanceof EnderDragonPart complexPart) { if (complexPart.parentMob instanceof EnderDragon) { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 4632eb883e9f5efde520ee543bcad25827c0da2c..d710803137a325f34e0628405d5ddfd0bce16a95 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -1027,7 +1027,10 @@ public class CraftEventFactory { event.setKeepInventory(keepInventory); event.setKeepLevel(victim.keepLevel); // SPIGOT-2222: pre-set keepLevel populateFields(victim, event); // Paper - make cancellable - Bukkit.getServer().getPluginManager().callEvent(event); + // Leaves start - disable bot death event + if (!(victim instanceof org.leavesmc.leaves.bot.ServerBot)) { + Bukkit.getServer().getPluginManager().callEvent(event); + } // Leaves end // Paper start - make cancellable if (event.isCancelled()) { return event; diff --git a/src/main/java/org/leavesmc/leaves/bot/BotCommand.java b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..5a1054414f5313b59d38d89fb84987cad397ff12 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotCommand.java @@ -0,0 +1,543 @@ +package org.leavesmc.leaves.bot; + +import io.papermc.paper.command.CommandUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.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 org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.Bot; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.event.bot.BotConfigModifyEvent; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +import java.util.*; + +import static net.kyori.adventure.text.Component.text; + +public class BotCommand extends Command { + + private final Component unknownMessage; + + public BotCommand(String name) { + super(name); + this.description = "FakePlayer Command"; + this.usageMessage = "/bot [create | remove | action | list | config]"; + this.unknownMessage = text("Usage: " + usageMessage, NamedTextColor.RED); + this.setPermission("bukkit.command.bot"); + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + if (pluginManager.getPermission("bukkit.command.bot") == null) { + 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 { + List list = new ArrayList<>(); + BotList botList = BotList.INSTANCE; + + if (args.length <= 1) { + list.add("create"); + list.add("remove"); + if (LeavesConfig.fakeplayerUseAction) { + list.add("action"); + } + if (LeavesConfig.fakeplayerModifyConfig) { + list.add("config"); + } + if (LeavesConfig.fakeplayerManualSaveAndLoad) { + list.add("save"); + list.add("load"); + } + list.add("list"); + } + + if (args.length == 2) { + switch (args[0]) { + case "create" -> list.add(""); + case "remove", "action", "config", "save" -> list.addAll(botList.bots.stream().map(e -> e.getName().getString()).toList()); + case "list" -> list.addAll(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList()); + case "load" -> list.addAll(botList.getSavedBotList().getAllKeys()); + } + } + + if (args.length == 3) { + switch (args[0]) { + case "action" -> { + list.add("list"); + list.add("stop"); + list.addAll(Actions.getNames()); + } + case "create" -> list.add(""); + case "config" -> list.addAll(acceptConfig); + case "remove" -> list.addAll(List.of("cancel", "[hour]")); + } + } + + if (args[0].equals("remove") && args.length >= 3) { + if (!Objects.equals(args[3], "cancel")) { + switch (args.length) { + case 4 -> list.add("[minute]"); + case 5 -> list.add("[second]"); + } + } + } + + if (args.length >= 4 && args[0].equals("action")) { + ServerBot bot = botList.getBotByName(args[1]); + + if (bot == null) { + return Collections.singletonList("<" + args[1] + " not found>"); + } + + if (args[2].equals("stop")) { + list.add("all"); + for (int i = 0; i < bot.getBotActions().size(); i++) { + list.add(String.valueOf(i)); + } + } else { + BotAction action = Actions.getForName(args[2]); + if (action != null) { + list.addAll(action.getArgument().tabComplete(args.length - 4)); + } + } + } + + if (args.length >= 4 && args[0].equals("config")) { + Configs config = Configs.getConfig(args[2]); + if (config != null) { + list.addAll(config.config.getArgument().tabComplete(args.length - 4)); + } + } + + return CommandUtil.getListMatchingLast(sender, args, list); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String[] args) { + if (!testPermission(sender) || !LeavesConfig.fakeplayerSupport) return true; + + if (args.length == 0) { + sender.sendMessage(unknownMessage); + return false; + } + + switch (args[0]) { + case "create" -> this.onCreate(sender, args); + case "remove" -> this.onRemove(sender, args); + case "action" -> this.onAction(sender, args); + case "config" -> this.onConfig(sender, args); + case "list" -> this.onList(sender, args); + case "save" -> this.onSave(sender, args); + case "load" -> this.onLoad(sender, args); + default -> { + sender.sendMessage(unknownMessage); + return false; + } + } + + return true; + } + + private void onCreate(CommandSender sender, String @NotNull [] args) { + if (args.length < 2) { + sender.sendMessage(text("Use /bot create [skin_name] to create a fakeplayer", NamedTextColor.RED)); + return; + } + + String botName = args[1]; + if (this.canCreate(sender, botName)) { + BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()).createReason(BotCreateEvent.CreateReason.COMMAND).creator(sender); + + if (args.length >= 3) { + builder.skinName(args[2]); + } + + if (sender instanceof Player player) { + builder.location(player.getLocation()); + } else if (sender instanceof ConsoleCommandSender) { + if (args.length >= 7) { + try { + World world = Bukkit.getWorld(args[3]); + double x = Double.parseDouble(args[4]); + double y = Double.parseDouble(args[5]); + double z = Double.parseDouble(args[6]); + if (world != null) { + builder.location(new Location(world, x, y, z)); + } + } catch (Exception e) { + LeavesLogger.LOGGER.warning("Can't build location", e); + } + } + } + + builder.spawnWithSkin(null); + } + } + + private boolean canCreate(CommandSender sender, @NotNull String name) { + BotList botList = BotList.INSTANCE; + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + sender.sendMessage(text("This name is illegal", NamedTextColor.RED)); + return false; + } + + if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) { + sender.sendMessage(text("This player is in server", NamedTextColor.RED)); + return false; + } + + if (org.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(name)) { + sender.sendMessage(text("This name is not allowed", NamedTextColor.RED)); + return false; + } + + if (botList.bots.size() >= org.leavesmc.leaves.LeavesConfig.fakeplayerLimit) { + sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED)); + return false; + } + + return true; + } + + private void onRemove(CommandSender sender, String @NotNull [] args) { + if (args.length < 2 || args.length > 5) { + sender.sendMessage(text("Use /bot remove [hour] [minute] [second] to remove a fakeplayer", NamedTextColor.RED)); + return; + } + + BotList botList = BotList.INSTANCE; + ServerBot bot = botList.getBotByName(args[1]); + + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (args.length > 2) { + if (args[2].equals("cancel")) { + if (bot.removeTaskId == -1) { + sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); + return; + } + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + bot.removeTaskId = -1; + sender.sendMessage(text("Remove cancel")); + return; + } + + long time = 0; + int h; // Preventing out-of-range + long s = 0; + long m = 0; + + try { + h = Integer.parseInt(args[2]); + if (h < 0) { + throw new NumberFormatException(); + } + time += ((long) h) * 3600 * 20; + if (args.length > 3) { + m = Long.parseLong(args[3]); + if (m > 59 || m < 0) { + throw new NumberFormatException(); + } + time += m * 60 * 20; + } + if (args.length > 4) { + s = Long.parseLong(args[4]); + if (s > 59 || s < 0) { + throw new NumberFormatException(); + } + time += s * 20; + } + } catch (NumberFormatException e) { + sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED)); + return; + } + + boolean isReschedule = bot.removeTaskId != -1; + + if (isReschedule) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + } + bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> { + bot.removeTaskId = -1; + botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + }, time).getTaskId(); + + sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : "")); + + return; + } + + botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false); + } + + private void onAction(CommandSender sender, String @NotNull [] args) { + if (!LeavesConfig.fakeplayerUseAction) { + return; + } + + if (args.length < 3) { + sender.sendMessage(text("Use /bot action to make fakeplayer do action", NamedTextColor.RED)); + return; + } + + ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (args[2].equals("list")) { + sender.sendMessage(bot.getScoreboardName() + "'s action list:"); + for (int i = 0; i < bot.getBotActions().size(); i++) { + sender.sendMessage(i + " " + bot.getBotActions().get(i).getName()); + } + return; + } + + if (args[2].equals("stop")) { + if (args.length < 4) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + return; + } + + String index = args[3]; + if (index.equals("all")) { + Set> forRemoval = new HashSet<>(); + for (int i = 0; i < bot.getBotActions().size(); i++) { + BotAction action = bot.getBotActions().get(i); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + forRemoval.add(action); + } + } + bot.getBotActions().removeAll(forRemoval); + sender.sendMessage(bot.getScoreboardName() + "'s action list cleared."); + } else { + try { + int i = Integer.parseInt(index); + if (i < 0 || i >= bot.getBotActions().size()) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + return; + } + + BotAction action = bot.getBotActions().get(i); + BotActionStopEvent event = new BotActionStopEvent( + bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender + ); + event.callEvent(); + if (!event.isCancelled()) { + bot.getBotActions().remove(i); + sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped."); + + } + } catch (NumberFormatException e) { + sender.sendMessage(text("Invalid index", NamedTextColor.RED)); + } + } + return; + } + + BotAction action = Actions.getForName(args[2]); + if (action == null) { + sender.sendMessage(text("Invalid action", NamedTextColor.RED)); + return; + } + + CraftPlayer player; + if (sender instanceof CraftPlayer) { + player = (CraftPlayer) sender; + } else { + player = bot.getBukkitEntity(); + } + + String[] realArgs = new String[args.length - 3]; + if (realArgs.length != 0) { + System.arraycopy(args, 3, realArgs, 0, realArgs.length); + } + + BotAction newAction = null; + try { + if (action instanceof CraftCustomBotAction customBotAction) { + newAction = customBotAction.createCraft(player, realArgs); + } else { + newAction = action.create(); + newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs)); + } + } catch (IllegalArgumentException ignore) { + } + + if (newAction == null) { + sender.sendMessage(text("Action create error, please check your arguments", NamedTextColor.RED)); + return; + } + + if (bot.addBotAction(newAction, sender)) { + sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString()); + } + } + + private static final List acceptConfig = Configs.getConfigs().stream().map(config -> config.config.getName()).toList(); + + private void onConfig(CommandSender sender, String @NotNull [] args) { + if (!LeavesConfig.fakeplayerModifyConfig) { + return; + } + + if (args.length < 3) { + sender.sendMessage(text("Use /bot config to modify fakeplayer's config", NamedTextColor.RED)); + return; + } + + ServerBot bot = BotList.INSTANCE.getBotByName(args[1]); + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (!acceptConfig.contains(args[2])) { + sender.sendMessage(text("This config is not accept", NamedTextColor.RED)); + return; + } + + BotConfig config = Objects.requireNonNull(Configs.getConfig(args[2])).config; + if (args.length < 4) { + config.getMessage().forEach(sender::sendMessage); + } else { + String[] realArgs = new String[args.length - 3]; + System.arraycopy(args, 3, realArgs, 0, realArgs.length); + + BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs, sender); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + CommandArgumentResult result = config.getArgument().parse(0, realArgs); + + try { + config.setValue(result); + config.getChangeMessage().forEach(sender::sendMessage); + } catch (IllegalArgumentException e) { + sender.sendMessage(text(e.getMessage(), NamedTextColor.RED)); + } + } + } + + private void onSave(CommandSender sender, String @NotNull [] args) { + if (!LeavesConfig.fakeplayerManualSaveAndLoad) { + return; + } + + if (args.length < 2) { + sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); + return; + } + + BotList botList = BotList.INSTANCE; + ServerBot bot = botList.getBotByName(args[1]); + + if (bot == null) { + sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED)); + return; + } + + if (botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true)) { + sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName()); + } + } + + private void onLoad(CommandSender sender, String @NotNull [] args) { + if (!LeavesConfig.fakeplayerManualSaveAndLoad) { + return; + } + + if (args.length < 2) { + sender.sendMessage(text("Use /bot save to save a fakeplayer", NamedTextColor.RED)); + return; + } + + String realName = args[1]; + BotList botList = BotList.INSTANCE; + if (!botList.getSavedBotList().contains(realName)) { + sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED)); + return; + } + + if (botList.loadNewBot(realName) == null) { + sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED)); + } + } + + private void onList(CommandSender sender, String @NotNull [] args) { + BotList botList = BotList.INSTANCE; + if (args.length < 2) { + Map> botMap = new HashMap<>(); + for (World world : Bukkit.getWorlds()) { + botMap.put(world, new ArrayList<>()); + } + + for (ServerBot bot : botList.bots) { + Bot bukkitBot = bot.getBukkitEntity(); + botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName()); + } + + sender.sendMessage("Total number: (" + botList.bots.size() + "/" + org.leavesmc.leaves.LeavesConfig.fakeplayerLimit + ")"); + for (World world : botMap.keySet()) { + sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world))); + } + } else { + World world = Bukkit.getWorld(args[1]); + + if (world == null) { + sender.sendMessage(text("Unknown world", NamedTextColor.RED)); + return; + } + + List snowBotList = new ArrayList<>(); + for (ServerBot bot : botList.bots) { + Bot bukkitBot = bot.getBukkitEntity(); + if (bukkitBot.getWorld() == world) { + snowBotList.add(bukkitBot.getName()); + } + } + + sender.sendMessage(world.getName() + "(" + botList.bots.size() + "): " + formatPlayerNameList(snowBotList)); + } + } + + @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/org/leavesmc/leaves/bot/BotCreateState.java b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java new file mode 100644 index 0000000000000000000000000000000000000000..ebb3aa94718382e8fac2a8fa617c796a9387709b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotCreateState.java @@ -0,0 +1,120 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.entity.Bot; +import org.leavesmc.leaves.entity.BotCreator; +import org.leavesmc.leaves.entity.CraftBot; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +import java.util.Objects; +import java.util.function.Consumer; + +public record BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) { + + private static final MinecraftServer server = MinecraftServer.getServer(); + + public ServerBot createNow() { + return server.getBotList().createNewBot(this); + } + + @NotNull + public static Builder builder(@NotNull String realName, @Nullable Location location) { + return new Builder(realName, location); + } + + public static class Builder implements BotCreator { + + private final String realName; + + private String name; + private Location location; + + private String skinName; + private String[] skin; + + private BotCreateEvent.CreateReason createReason; + private CommandSender creator; + + private Builder(@NotNull String realName, @Nullable Location location) { + Objects.requireNonNull(realName); + + this.realName = realName; + this.location = location; + + this.name = LeavesConfig.fakeplayerPrefix + realName + LeavesConfig.fakeplayerSuffix; + this.skinName = this.realName; + this.skin = null; + this.createReason = BotCreateEvent.CreateReason.UNKNOWN; + this.creator = null; + } + + public Builder name(@NotNull String name) { + Objects.requireNonNull(name); + this.name = name; + return this; + } + + public Builder skinName(@Nullable String skinName) { + this.skinName = skinName; + return this; + } + + public Builder skin(@Nullable String[] skin) { + this.skin = skin; + return this; + } + + public Builder mojangAPISkin() { + if (this.skinName != null) { + this.skin = MojangAPI.getSkin(this.skinName); + } + return this; + } + + public Builder location(@NotNull Location location) { + this.location = location; + return this; + } + + public Builder createReason(@NotNull BotCreateEvent.CreateReason createReason) { + Objects.requireNonNull(createReason); + this.createReason = createReason; + return this; + } + + public Builder creator(CommandSender creator) { + this.creator = creator; + return this; + } + + public BotCreateState build() { + return new BotCreateState(realName, name, skinName, skin, location, createReason, creator); + } + + public void spawnWithSkin(Consumer consumer) { + Bukkit.getScheduler().runTaskAsynchronously(MinecraftInternalPlugin.INSTANCE, () -> { + this.mojangAPISkin(); + Bukkit.getScheduler().runTask(MinecraftInternalPlugin.INSTANCE, () -> { + CraftBot bot = this.spawn(); + if (bot != null && consumer != null) { + consumer.accept(bot); + } + }); + }); + } + + @Nullable + public CraftBot spawn() { + Objects.requireNonNull(this.location); + ServerBot bot = this.build().createNow(); + return bot != null ? bot.getBukkitEntity() : null; + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java b/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..53bece66534df40ef8cf559c12e2c472a791b9c3 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotDataStorage.java @@ -0,0 +1,121 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.logging.LogUtils; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +public class BotDataStorage implements IPlayerDataStorage { + + private static final LevelResource BOT_DATA_DIR = new LevelResource("fakeplayerdata"); + private static final LevelResource BOT_LIST_FILE = new LevelResource("fakeplayer.dat"); + + private static final Logger LOGGER = LogUtils.getLogger(); + private final File botDir; + private final File botListFile; + + private CompoundTag savedBotList; + + public BotDataStorage(LevelStorageSource.@NotNull LevelStorageAccess session) { + this.botDir = session.getLevelPath(BOT_DATA_DIR).toFile(); + this.botListFile = session.getLevelPath(BOT_LIST_FILE).toFile(); + this.botDir.mkdirs(); + + this.savedBotList = new CompoundTag(); + if (this.botListFile.exists() && this.botListFile.isFile()) { + try { + Optional.of(NbtIo.readCompressed(this.botListFile.toPath(), NbtAccounter.unlimitedHeap())).ifPresent(tag -> this.savedBotList = tag); + } catch (Exception exception) { + BotDataStorage.LOGGER.warn("Failed to load player data list"); + } + } + } + + @Override + public void save(Player player) { + boolean flag = true; + try { + CompoundTag nbt = player.saveWithoutId(new CompoundTag()); + File file = new File(this.botDir, player.getStringUUID() + ".dat"); + + if (file.exists() && file.isFile()) { + if (!file.delete()) { + throw new IOException("Failed to delete file: " + file); + } + } + if (!file.createNewFile()) { + throw new IOException("Failed to create nbt file: " + file); + } + NbtIo.writeCompressed(nbt, file.toPath()); + } catch (Exception exception) { + BotDataStorage.LOGGER.warn("Failed to save fakeplayer data for {}", player.getScoreboardName(), exception); + flag = false; + } + + if (flag && player instanceof ServerBot bot) { + CompoundTag nbt = new CompoundTag(); + nbt.putString("name", bot.createState.name()); + nbt.putUUID("uuid", bot.getUUID()); + nbt.putBoolean("resume", bot.resume); + this.savedBotList.put(bot.createState.realName(), nbt); + this.saveBotList(); + } + } + + @Override + public Optional load(Player player) { + return this.load(player.getScoreboardName(), player.getStringUUID()).map((nbt) -> { + player.load(nbt); + return nbt; + }); + } + + private Optional load(String name, String uuid) { + File file = new File(this.botDir, uuid + ".dat"); + + if (file.exists() && file.isFile()) { + try { + Optional optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap())); + if (!file.delete()) { + throw new IOException("Failed to delete fakeplayer data"); + } + this.savedBotList.remove(name); + this.saveBotList(); + return optional; + } catch (Exception exception) { + BotDataStorage.LOGGER.warn("Failed to load fakeplayer data for {}", name); + } + } + + return Optional.empty(); + } + + private void saveBotList() { + try { + if (this.botListFile.exists() && this.botListFile.isFile()) { + if (!this.botListFile.delete()) { + throw new IOException("Failed to delete file: " + this.botListFile); + } + } + if (!this.botListFile.createNewFile()) { + throw new IOException("Failed to create nbt file: " + this.botListFile); + } + NbtIo.writeCompressed(this.savedBotList, this.botListFile.toPath()); + } catch (Exception exception) { + BotDataStorage.LOGGER.warn("Failed to save player data list"); + } + } + + public CompoundTag getSavedBotList() { + return savedBotList; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..4f5e6e5c1b9d8bd38c98e97fd31b38338f35faa6 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotInventoryContainer.java @@ -0,0 +1,191 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.NonNullList; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomData; + +import javax.annotation.Nonnull; +import java.util.List; + +// Power by gugle-carpet-addition(https://github.com/Gu-ZT/gugle-carpet-addition) +public class BotInventoryContainer extends SimpleContainer { + + public final NonNullList items; + public final NonNullList armor; + public final NonNullList offhand; + private final List> compartments; + private final NonNullList buttons = NonNullList.withSize(13, ItemStack.EMPTY); + private final ServerBot player; + + public BotInventoryContainer(ServerBot player) { + this.player = player; + this.items = this.player.getInventory().items; + this.armor = this.player.getInventory().armor; + this.offhand = this.player.getInventory().offhand; + this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons); + createButton(); + } + + @Override + public int getContainerSize() { + return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size(); + } + + @Override + public boolean isEmpty() { + for (ItemStack itemStack : this.items) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + for (ItemStack itemStack : this.armor) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + for (ItemStack itemStack : this.offhand) { + if (itemStack.isEmpty()) { + continue; + } + return false; + } + return true; + } + + @Override + @Nonnull + public ItemStack getItem(int slot) { + Pair, Integer> pair = getItemSlot(slot); + if (pair != null) { + return pair.getFirst().get(pair.getSecond()); + } else { + return ItemStack.EMPTY; + } + } + + public Pair, Integer> getItemSlot(int slot) { + switch (slot) { + case 0 -> { + return new Pair<>(buttons, 0); + } + case 1, 2, 3, 4 -> { + return new Pair<>(armor, 4 - slot); + } + case 5, 6 -> { + 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.detectEquipmentUpdatesPublic(); + } + 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.detectEquipmentUpdatesPublic(); + } + } + + @Override + public void setChanged() { + } + + @Override + public boolean stillValid(@Nonnull Player player) { + if (this.player.isRemoved()) { + return false; + } + return !(player.distanceToSqr(this.player) > 64.0); + } + + @Override + public void clearContent() { + for (List list : this.compartments) { + list.clear(); + } + } + + private void createButton() { + CompoundTag customData = new CompoundTag(); + customData.putBoolean("Leaves.Gui.Placeholder", true); + + DataComponentPatch patch = DataComponentPatch.builder() + .set(DataComponents.CUSTOM_NAME, Component.empty()) + .set(DataComponents.CUSTOM_DATA, CustomData.of(customData)) + .build(); + + for (int i = 0; i < 13; i++) { + ItemStack button = new ItemStack(Items.STRUCTURE_VOID); + button.applyComponents(patch); + buttons.set(i, button); + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotList.java b/src/main/java/org/leavesmc/leaves/bot/BotList.java new file mode 100644 index 0000000000000000000000000000000000000000..33deb7eeda2c4cda396ac4ed7f05e6a851e89050 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotList.java @@ -0,0 +1,339 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.Maps; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.logging.LogUtils; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotJoinEvent; +import org.leavesmc.leaves.event.bot.BotLoadEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; +import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent; +import org.slf4j.Logger; + +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +public class BotList { + + public static BotList INSTANCE; + + private static final Logger LOGGER = LogUtils.getLogger(); + + private final MinecraftServer server; + + public final List bots = new CopyOnWriteArrayList<>(); + private final BotDataStorage dataStorage; + + private final Map botsByUUID = Maps.newHashMap(); + private final Map botsByName = Maps.newHashMap(); + + public BotList(MinecraftServer server) { + this.server = server; + this.dataStorage = new BotDataStorage(server.storageSource); + INSTANCE = this; + } + + public ServerBot createNewBot(BotCreateState state) { + BotCreateEvent event = new BotCreateEvent(state.name(), state.skinName(), state.location(), state.createReason(), state.creator()); + event.setCancelled(!isCreateLegal(state.name())); + this.server.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return null; + } + + Location location = event.getCreateLocation(); + ServerLevel world = ((CraftWorld) location.getWorld()).getHandle(); + + CustomGameProfile profile = new CustomGameProfile(BotUtil.getBotUUID(state), state.name(), state.skin()); + ServerBot bot = new ServerBot(this.server, world, profile); + bot.createState = state; + if (event.getCreator() instanceof org.bukkit.entity.Player player) { + bot.createPlayer = player.getUniqueId(); + } + + return this.placeNewBot(bot, world, location, null); + } + + public ServerBot loadNewBot(String realName) { + return this.loadNewBot(realName, this.dataStorage); + } + + public ServerBot loadNewBot(String realName, IPlayerDataStorage playerIO) { + UUID uuid = BotUtil.getBotUUID(realName); + + BotLoadEvent event = new BotLoadEvent(realName, uuid); + this.server.server.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return null; + } + + ServerBot bot = new ServerBot(this.server, this.server.getLevel(Level.OVERWORLD), new GameProfile(uuid, realName)); + bot.connection = new ServerBotPacketListenerImpl(this.server, bot); + Optional optional = playerIO.load(bot); + + if (optional.isEmpty()) { + return null; + } + + ResourceKey resourcekey = null; + if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) { + org.bukkit.World bWorld = Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast"))); + if (bWorld != null) { + resourcekey = ((CraftWorld) bWorld).getHandle().dimension(); + } + } + if (resourcekey == null) { + return null; + } + + ServerLevel world = this.server.getLevel(resourcekey); + return this.placeNewBot(bot, world, bot.getLocation(), optional.get()); + } + + public ServerBot placeNewBot(ServerBot bot, ServerLevel world, Location location, @Nullable CompoundTag nbt) { + bot.isRealPlayer = true; + bot.connection = new ServerBotPacketListenerImpl(this.server, bot); + bot.setServerLevel(world); + + BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location); + this.server.server.getPluginManager().callEvent(event); + location = event.getSpawnLocation(); + + bot.spawnIn(world); + bot.gameMode.setLevel((ServerLevel) bot.level()); + + bot.setPosRaw(location.getX(), location.getY(), location.getZ()); + bot.setRot(location.getYaw(), location.getPitch()); + + this.bots.add(bot); + this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), bot); + this.botsByUUID.put(bot.getUUID(), bot); + + bot.supressTrackerForLogin = true; + world.addNewPlayer(bot); + this.mountSavedVehicle(bot, world, nbt); + + BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); + this.server.server.getPluginManager().callEvent(event1); + + net.kyori.adventure.text.Component joinMessage = event1.joinMessage(); + if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false); + } + + bot.renderAll(); + bot.supressTrackerForLogin = false; + bot.serverLevel().getChunkSource().chunkMap.addEntity(bot); + BotList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", bot.getName().getString(), "Local", bot.getId(), bot.serverLevel().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ()); + return bot; + } + + private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, @Nullable CompoundTag nbt) { + Optional optional = Optional.ofNullable(nbt); + if (optional.isPresent() && optional.get().contains("RootVehicle", 10)) { + CompoundTag nbttagcompound = optional.get().getCompound("RootVehicle"); + Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver1, (entity1) -> { + return !worldserver1.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; + }); + + if (entity != null) { + UUID uuid; + + if (nbttagcompound.hasUUID("Attach")) { + uuid = nbttagcompound.getUUID("Attach"); + } else { + uuid = null; + } + + Iterator iterator; + Entity entity1; + + if (entity.getUUID().equals(uuid)) { + player.startRiding(entity, true); + } else { + iterator = entity.getIndirectPassengers().iterator(); + + while (iterator.hasNext()) { + entity1 = iterator.next(); + if (entity1.getUUID().equals(uuid)) { + player.startRiding(entity1, true); + break; + } + } + } + + if (!player.isPassenger()) { + BotList.LOGGER.warn("Couldn't reattach entity to fakeplayer"); + entity.discard(); + iterator = entity.getIndirectPassengers().iterator(); + + while (iterator.hasNext()) { + entity1 = iterator.next(); + entity1.discard(); + } + } + } + } + } + + public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) { + return this.removeBot(bot, reason, remover, saved, this.dataStorage); + } + + public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved, IPlayerDataStorage playerIO) { + BotRemoveEvent event = new BotRemoveEvent(bot.getBukkitEntity(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)), saved); + this.server.server.getPluginManager().callEvent(event); + + if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) { + return event.isCancelled(); + } + + if (bot.removeTaskId != -1) { + Bukkit.getScheduler().cancelTask(bot.removeTaskId); + bot.removeTaskId = -1; + } + + if (this.server.isSameThread()) { + bot.doTick(); + } + + if (event.shouldSave()) { + playerIO.save(bot); + } else { + bot.dropAll(); + } + + if (bot.isPassenger()) { + Entity entity = bot.getRootVehicle(); + if (entity.hasExactlyOnePlayerPassenger()) { + bot.stopRiding(); + entity.getPassengersAndSelf().forEach((entity1) -> { + if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { + final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); + if (human != null) { + villager.setTradingPlayer(null); + } + } + entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); + }); + } + } + + bot.unRide(); + bot.serverLevel().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER); + this.bots.remove(bot); + this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT)); + + UUID uuid = bot.getUUID(); + ServerBot bot1 = this.botsByUUID.get(uuid); + if (bot1 == bot) { + this.botsByUUID.remove(uuid); + } + + bot.removeTab(); + for (ServerPlayer player : bot.serverLevel().players()) { + if (!(player instanceof ServerBot) && !bot.needSendFakeData(player)) { + player.connection.send(new ClientboundRemoveEntitiesPacket(bot.getId())); + } + } + + net.kyori.adventure.text.Component removeMessage = event.removeMessage(); + if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false); + } + return true; + } + + public void removeAll() { + for (ServerBot bot : this.bots) { + bot.resume = LeavesConfig.fakeplayerResident; + this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, LeavesConfig.fakeplayerResident); + } + } + + public void loadResume() { + if (LeavesConfig.fakeplayerSupport && LeavesConfig.fakeplayerResident) { + for (String realName : this.getSavedBotList().getAllKeys()) { + CompoundTag nbt = this.getSavedBotList().getCompound(realName); + if (nbt.getBoolean("resume")) { + this.loadNewBot(realName); + } + } + } + } + + public void networkTick() { + this.bots.forEach(ServerBot::doTick); + } + + @Nullable + public ServerBot getBot(@NotNull UUID uuid) { + return this.botsByUUID.get(uuid); + } + + @Nullable + public ServerBot getBotByName(@NotNull String name) { + return this.botsByName.get(name.toLowerCase(Locale.ROOT)); + } + + public CompoundTag getSavedBotList() { + return this.dataStorage.getSavedBotList(); + } + + public boolean isCreateLegal(@NotNull String name) { + if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) { + return false; + } + + if (Bukkit.getPlayerExact(name) != null || this.getBotByName(name) != null) { + return false; + } + + if (LeavesConfig.unableFakeplayerNames.contains(name)) { + return false; + } + + return this.bots.size() < LeavesConfig.fakeplayerLimit; + } + + public static class CustomGameProfile extends GameProfile { + + public CustomGameProfile(UUID uuid, String name, String[] skin) { + super(uuid, name); + this.setSkin(skin); + } + + public void setSkin(String[] skin) { + if (skin != null) { + this.getProperties().put("textures", new Property("textures", skin[0], skin[1])); + } + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..10494446f915bc1720a18cfe75b2cab2404646e9 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotStatsCounter.java @@ -0,0 +1,36 @@ +package org.leavesmc.leaves.bot; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stat; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class BotStatsCounter extends ServerStatsCounter { + + private static final File UNKOWN_FILE = new File("BOT_STATS_REMOVE_THIS"); + + public BotStatsCounter(MinecraftServer server) { + super(server, UNKOWN_FILE); + } + + @Override + public void save() { + } + + @Override + public void setValue(@NotNull Player player, @NotNull Stat stat, int value) { + } + + @Override + public void parseLocal(@NotNull DataFixer dataFixer, @NotNull String json) { + } + + @Override + public int getValue(@NotNull Stat stat) { + return 0; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotUtil.java b/src/main/java/org/leavesmc/leaves/bot/BotUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..78414d1f53328cdc2963264ecb4f5a65e9783798 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotUtil.java @@ -0,0 +1,73 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.base.Charsets; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class BotUtil { + + public static void replenishment(@NotNull ItemStack itemStack, NonNullList itemStackList) { + int count = itemStack.getMaxStackSize() / 2; + if (itemStack.getCount() <= 8 && count > 8) { + for (ItemStack itemStack1 : itemStackList) { + if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) { + continue; + } + + if (ItemStack.isSameItemSameComponents(itemStack1, itemStack)) { + if (itemStack1.getCount() > count) { + itemStack.setCount(itemStack.getCount() + count); + itemStack1.setCount(itemStack1.getCount() - count); + } else { + itemStack.setCount(itemStack.getCount() + itemStack1.getCount()); + itemStack1.setCount(0); + } + break; + } + } + } + } + + public static void replaceTool(@NotNull EquipmentSlot slot, @NotNull ServerBot bot) { + ItemStack itemStack = bot.getItemBySlot(slot); + for (int i = 0; i < 36; i++) { + ItemStack itemStack1 = bot.getInventory().getItem(i); + if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) { + continue; + } + + if (itemStack1.getItem().getClass() == itemStack.getItem().getClass() && !isDamage(itemStack1, 10)) { + ItemStack itemStack2 = itemStack1.copy(); + bot.getInventory().setItem(i, itemStack); + bot.setItemSlot(slot, itemStack2); + return; + } + } + + for (int i = 0; i < 36; i++) { + ItemStack itemStack1 = bot.getInventory().getItem(i); + if (itemStack1 == ItemStack.EMPTY && itemStack1 != itemStack) { + bot.getInventory().setItem(i, itemStack); + bot.setItemSlot(slot, ItemStack.EMPTY); + return; + } + } + } + + public static boolean isDamage(@NotNull ItemStack item, int minDamage) { + return item.isDamageableItem() && (item.getMaxDamage() - item.getDamageValue()) <= minDamage; + } + + @NotNull + public static UUID getBotUUID(@NotNull BotCreateState state) { + return getBotUUID(state.realName()); + } + + public static UUID getBotUUID(@NotNull String realName) { + return UUID.nameUUIDFromBytes(("Fakeplayer:" + realName).getBytes(Charsets.UTF_8)); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java b/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java new file mode 100644 index 0000000000000000000000000000000000000000..7ebe4d6c71e90be92387a585ea581c6b2c4af89d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/IPlayerDataStorage.java @@ -0,0 +1,13 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.player.Player; + +import java.util.Optional; + +public interface IPlayerDataStorage { + + void save(Player player); + + Optional load(Player player); +} diff --git a/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java b/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java new file mode 100644 index 0000000000000000000000000000000000000000..517e3321b866abe9d17a6fe9e919528b50bb130a --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/MojangAPI.java @@ -0,0 +1,39 @@ +package org.leavesmc.leaves.bot; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.leavesmc.leaves.LeavesConfig; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class MojangAPI { + + private static final Map CACHE = new HashMap<>(); + + public static String[] getSkin(String name) { + if (LeavesConfig.fakeplayerCacheSkin && CACHE.containsKey(name)) { + return CACHE.get(name); + } + + String[] values = pullFromAPI(name); + CACHE.put(name, values); + return values; + } + + // Laggggggggggggggggggggggggggggggggggggggggg + public static String[] pullFromAPI(String name) { + try { + String uuid = JsonParser.parseReader(new InputStreamReader(URI.create("https://api.mojang.com/users/profiles/minecraft/" + name).toURL().openStream())) + .getAsJsonObject().get("id").getAsString(); + JsonObject property = JsonParser.parseReader(new InputStreamReader(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false").toURL().openStream())) + .getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject(); + return new String[]{property.get("value").getAsString(), property.get("signature").getAsString()}; + } catch (IOException | IllegalStateException | IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java new file mode 100644 index 0000000000000000000000000000000000000000..bab7790db401135cb9ea5b05f749dca5e7d0b95f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java @@ -0,0 +1,544 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.ImmutableMap; +import com.mojang.authlib.GameProfile; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.event.entity.EntityKnockbackEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.BlockParticleOption; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerPlayerConnection; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.portal.DimensionTransition; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.LeavesLogger; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.bot.agent.Configs; +import org.leavesmc.leaves.entity.CraftBot; +import org.leavesmc.leaves.event.bot.BotActionScheduleEvent; +import org.leavesmc.leaves.event.bot.BotCreateEvent; +import org.leavesmc.leaves.event.bot.BotDeathEvent; +import org.leavesmc.leaves.event.bot.BotInventoryOpenEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; +import org.leavesmc.leaves.util.MathUtils; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; + +// TODO test +public class ServerBot extends ServerPlayer { + + private final Map, BotConfig> configs; + private final List> actions; + + public boolean resume = false; + public BotCreateState createState; + public UUID createPlayer; + + private final int tracingRange; + private final ServerStatsCounter stats; + private final BotInventoryContainer container; + + public int notSleepTicks; + + public int removeTaskId = -1; + + private Vec3 knockback = Vec3.ZERO; + + public ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) { + super(server, world, profile, ClientInformation.createDefault()); + this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2); + + this.gameMode = new ServerBotGameMode(this); + this.actions = new ArrayList<>(); + + ImmutableMap.Builder, BotConfig> configBuilder = ImmutableMap.builder(); + for (Configs config : Configs.getConfigs()) { + configBuilder.put(config, config.config.create(this)); + } + this.configs = configBuilder.build(); + + this.stats = new BotStatsCounter(server); + this.container = new BotInventoryContainer(this); + this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange; + + this.notSleepTicks = 0; + this.fauxSleeping = LeavesConfig.fakeplayerSkipSleep; + } + + public void sendPlayerInfo(ServerPlayer player) { + player.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), List.of(this))); + } + + public boolean needSendFakeData(ServerPlayer player) { + return this.getConfigValue(Configs.ALWAYS_SEND_DATA) && (player.level() == this.level() && player.position().distanceToSqr(this.position()) > this.tracingRange); + } + + public void sendFakeDataIfNeed(ServerPlayer player, boolean login) { + if (needSendFakeData(player)) { + this.sendFakeData(player.connection, login); + } + } + + public void sendFakeData(ServerPlayerConnection playerConnection, boolean login) { + ChunkMap.TrackedEntity entityTracker = ((ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId()); + + if (entityTracker == null) { + LeavesLogger.LOGGER.warning("Fakeplayer cant get entity tracker for " + this.getId()); + return; + } + + playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity)); + if (login) { + Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10); + } else { + playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))); + } + } + + public void renderAll() { + this.server.getPlayerList().getPlayers().forEach( + player -> { + this.sendPlayerInfo(player); + this.sendFakeDataIfNeed(player, false); + } + ); + } + + private void sendPacket(Packet packet) { + this.server.getPlayerList().getPlayers().forEach(player -> player.connection.send(packet)); + } + + @Override + public void die(@NotNull DamageSource damageSource) { + boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + + BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag); + this.server.server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { + if (this.getHealth() <= 0) { + this.setHealth(0.1f); + } + return; + } + + this.gameEvent(GameEvent.ENTITY_DIE); + + net.kyori.adventure.text.Component deathMessage = event.deathMessage(); + if (event.isSendDeathMessage() && deathMessage != null && !deathMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(deathMessage), false); + } + + this.server.getBotList().removeBot(this, BotRemoveEvent.RemoveReason.DEATH, null, false); + } + + public void removeTab() { + this.sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID()))); + } + + @Nullable + @Override + public Entity changeDimension(@NotNull DimensionTransition teleportTarget) { + return null; // disable dimension change + } + + @Override + public void tick() { + if (!this.isAlive()) { + return; + } + super.tick(); + + if (this.getConfigValue(Configs.SPAWN_PHANTOM)) { + notSleepTicks++; + } + + if (LeavesConfig.fakeplayerRegenAmount > 0.0 && server.getTickCount() % 20 == 0) { + float health = getHealth(); + float maxHealth = getMaxHealth(); + float regenAmount = (float) (LeavesConfig.fakeplayerRegenAmount * 20); + float amount; + + if (health < maxHealth - regenAmount) { + amount = health + regenAmount; + } else { + amount = maxHealth; + } + + this.setHealth(amount); + } + } + + @Override + public void onItemPickup(@NotNull ItemEntity item) { + super.onItemPickup(item); + this.updateItemInHand(InteractionHand.MAIN_HAND); + } + + public void updateItemInHand(InteractionHand hand) { + ItemStack item = this.getItemInHand(hand); + + if (!item.isEmpty()) { + BotUtil.replenishment(item, getInventory().items); + if (BotUtil.isDamage(item, 10)) { + BotUtil.replaceTool(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND, this); + } + } + this.detectEquipmentUpdatesPublic(); + } + + @Override + public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) { + if (LeavesConfig.openFakeplayerInventory) { + if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) { + BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity()); + this.server.server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + Component menuName = this.getDisplayName() != null ? this.getDisplayName() : Component.literal(this.createState.name()); + player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), menuName)); + return InteractionResult.SUCCESS; + } + } + } + return super.interact(player, hand); + } + + @Override + public void checkFallDamage(double heightDifference, boolean onGround, @NotNull BlockState state, @NotNull BlockPos landedPosition) { + if (onGround && this.fallDistance > 0.0F) { + this.onChangedBlock(this.serverLevel(), landedPosition); + double d1 = this.getAttributeValue(Attributes.SAFE_FALL_DISTANCE); + + if ((double) this.fallDistance > d1 && !state.isAir()) { + double d2 = this.getX(); + double d3 = this.getY(); + double d4 = this.getZ(); + BlockPos blockposition = this.blockPosition(); + + if (landedPosition.getX() != blockposition.getX() || landedPosition.getZ() != blockposition.getZ()) { + double d5 = d2 - (double) landedPosition.getX() - 0.5D; + double d6 = d4 - (double) landedPosition.getZ() - 0.5D; + double d7 = Math.max(Math.abs(d5), Math.abs(d6)); + + d2 = (double) landedPosition.getX() + 0.5D + d5 / d7 * 0.5D; + d4 = (double) landedPosition.getZ() + 0.5D + d6 / d7 * 0.5D; + } + + float f = (float) Mth.ceil((double) this.fallDistance - d1); + double d8 = Math.min(0.2F + f / 15.0F, 2.5D); + int i = (int) (150.0D * d8); + + this.serverLevel().sendParticles(this, new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false); + } + } + + if (onGround) { + if (this.fallDistance > 0.0F) { + state.getBlock().fallOn(this.level(), state, landedPosition, this, this.fallDistance); + 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() { + this.absMoveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + + if (this.takeXpDelay > 0) { + --this.takeXpDelay; + } + + if (this.isSleeping()) { + ++this.sleepCounter; + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + this.notSleepTicks = 0; + } + + if (!this.level().isClientSide && this.level().isDay()) { + this.stopSleepInBed(false, true); + } + } else if (this.sleepCounter > 0) { + ++this.sleepCounter; + if (this.sleepCounter >= 110) { + this.sleepCounter = 0; + } + } + + this.updateIsUnderwater(); + + this.addDeltaMovement(knockback); + this.knockback = Vec3.ZERO; + + this.server.tell(this.server.wrapRunnable(this::runAction)); + + this.livingEntityTick(); + + this.foodData.tick(this); + + ++this.attackStrengthTicker; + ItemStack itemstack = this.getMainHandItem(); + if (!ItemStack.matches(this.lastItemInMainHand, itemstack)) { + if (!ItemStack.isSameItem(this.lastItemInMainHand, itemstack)) { + this.resetAttackStrengthTicker(); + } + + this.lastItemInMainHand = itemstack.copy(); + } + + this.getCooldowns().tick(); + this.updatePlayerPose(); + + if (this.hurtTime > 0) { + this.hurtTime -= 1; + } + } + + @Override + public void knockback(double strength, double x, double z, @Nullable Entity attacker, @NotNull EntityKnockbackEvent.Cause cause) { + strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE); + if (strength > 0.0D) { + Vec3 vec3d = this.getDeltaMovement(); + Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength); + this.hasImpulse = true; + this.knockback = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z).subtract(vec3d); + } + } + + @Override + public void setRot(float yaw, float pitch) { + this.getBukkitEntity().setRotation(yaw, pitch); + } + + @Override + public void attack(@NotNull Entity target) { + super.attack(target); + this.swing(InteractionHand.MAIN_HAND); + } + + @Override + public void addAdditionalSaveData(@NotNull CompoundTag nbt) { + super.addAdditionalSaveData(nbt); + nbt.putBoolean("isShiftKeyDown", this.isShiftKeyDown()); + + CompoundTag createNbt = new CompoundTag(); + createNbt.putString("realName", this.createState.realName()); + createNbt.putString("name", this.createState.name()); + + createNbt.putString("skinName", this.createState.skinName()); + if (this.createState.skin() != null) { + ListTag skin = new ListTag(); + for (String s : this.createState.skin()) { + skin.add(StringTag.valueOf(s)); + } + createNbt.put("skin", skin); + } + + nbt.put("createStatus", createNbt); + + if (!this.actions.isEmpty()) { + ListTag actionNbt = new ListTag(); + for (BotAction action : this.actions) { + actionNbt.add(action.save(new CompoundTag())); + } + nbt.put("actions", actionNbt); + } + + if (!this.configs.isEmpty()) { + ListTag configNbt = new ListTag(); + for (BotConfig config : this.configs.values()) { + configNbt.add(config.save(new CompoundTag())); + } + nbt.put("configs", configNbt); + } + } + + @Override + public void readAdditionalSaveData(@NotNull CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.setShiftKeyDown(nbt.getBoolean("isShiftKeyDown")); + + CompoundTag createNbt = nbt.getCompound("createStatus"); + BotCreateState.Builder createBuilder = BotCreateState.builder(createNbt.getString("realName"), null).name(createNbt.getString("name")); + + String[] skin = null; + if (createNbt.contains("skin")) { + ListTag skinTag = createNbt.getList("skin", 8); + skin = new String[skinTag.size()]; + for (int i = 0; i < skinTag.size(); i++) { + skin[i] = skinTag.getString(i); + } + } + + createBuilder.skinName(createNbt.getString("skinName")).skin(skin); + createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null); + + this.createState = createBuilder.build(); + this.gameProfile = new BotList.CustomGameProfile(this.getUUID(), this.createState.name(), this.createState.skin()); + + + if (nbt.contains("actions")) { + ListTag actionNbt = nbt.getList("actions", 10); + for (int i = 0; i < actionNbt.size(); i++) { + CompoundTag actionTag = actionNbt.getCompound(i); + BotAction action = Actions.getForName(actionTag.getString("actionName")); + if (action != null) { + BotAction newAction = action.create(); + newAction.load(actionTag); + this.actions.add(newAction); + } + } + } + + if (nbt.contains("configs")) { + ListTag configNbt = nbt.getList("configs", 10); + for (int i = 0; i < configNbt.size(); i++) { + CompoundTag configTag = configNbt.getCompound(i); + Configs configKey = Configs.getConfig(configTag.getString("configName")); + if (configKey != null) { + this.configs.get(configKey).load(configTag); + } + } + } + } + + public void faceLocation(@NotNull Location loc) { + this.look(loc.toVector().subtract(getLocation().toVector()), false); + } + + public void look(Vector dir, boolean keepYaw) { + float yaw, pitch; + + if (keepYaw) { + yaw = this.getYHeadRot(); + pitch = MathUtils.fetchPitch(dir); + } else { + float[] vals = MathUtils.fetchYawPitch(dir); + yaw = vals[0]; + pitch = vals[1]; + + this.sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f))); + } + + this.setRot(yaw, pitch); + } + + public Location getLocation() { + return this.getBukkitEntity().getLocation(); + } + + public Entity getTargetEntity(int maxDistance, Predicate predicate) { + List entities = this.level().getEntities((Entity) null, this.getBoundingBox(), (e -> e != this && (predicate == null || predicate.test(e)))); + if (!entities.isEmpty()) { + return entities.getFirst(); + } else { + EntityHitResult result = this.getBukkitEntity().rayTraceEntity(maxDistance, false); + if (result != null && (predicate == null || predicate.test(result.getEntity()))) { + return result.getEntity(); + } + } + return null; + } + + public void dropAll() { + this.getInventory().dropAll(); + this.detectEquipmentUpdatesPublic(); + } + + private void runAction() { + if (LeavesConfig.fakeplayerUseAction) { + this.actions.forEach(action -> action.tryTick(this)); + this.actions.removeIf(BotAction::isCancelled); + } + } + + public boolean addBotAction(BotAction action, CommandSender sender) { + if (!LeavesConfig.fakeplayerUseAction) { + return false; + } + + if (!new BotActionScheduleEvent(this.getBukkitEntity(), action.getName(), action.getUUID(), sender).callEvent()) { + return false; + } + + action.init(); + this.actions.add(action); + return true; + } + + public List> getBotActions() { + return actions; + } + + @Override + public @NotNull ServerStatsCounter getStats() { + return stats; + } + + @SuppressWarnings("unchecked") + public BotConfig getConfig(Configs config) { + return (BotConfig) Objects.requireNonNull(this.configs.get(config)); + } + + public E getConfigValue(Configs config) { + return this.getConfig(config).getValue(); + } + + @Override + @NotNull + public CraftBot getBukkitEntity() { + return (CraftBot) super.getBukkitEntity(); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java new file mode 100644 index 0000000000000000000000000000000000000000..bc1e29f6080c4783940848456620be8c06c32cce --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java @@ -0,0 +1,138 @@ +package org.leavesmc.leaves.bot; + +import net.kyori.adventure.text.Component; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.ItemInteractionResult; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ServerBotGameMode extends ServerPlayerGameMode { + + public ServerBotGameMode(ServerBot bot) { + super(bot); + super.setGameModeForPlayer(GameType.SURVIVAL, null); + } + + @Override + public boolean changeGameModeForPlayer(@NotNull GameType gameMode) { + return false; + } + + @Nullable + @Override + public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) { + return null; + } + + @Override + protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) { + } + + @Override + public void tick() { + } + + @Override + public void destroyAndAck(@NotNull BlockPos pos, int sequence, @NotNull String reason) { + this.destroyBlock(pos); + } + + @Override + public boolean destroyBlock(@NotNull BlockPos pos) { + BlockState iblockdata = this.level.getBlockState(pos); + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + + if (this.player.blockActionRestricted(this.level, pos, this.getGameModeForPlayer())) { + return false; + } else { + this.level.captureDrops = null; + BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player); + boolean flag = this.level.removeBlock(pos, false); + + if (flag) { + block.destroy(this.level, pos, iblockdata1); + } + + ItemStack itemstack = this.player.getMainHandItem(); + ItemStack itemstack1 = itemstack.copy(); + + boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); + + itemstack.mineBlock(this.level, iblockdata1, pos, this.player); + if (flag && flag1) { + Block.dropResources(iblockdata1, this.level, pos, tileentity, this.player, itemstack1, true); + } + + if (flag) { + iblockdata.getBlock().popExperience(this.level, pos, block.getExpDrop(iblockdata, this.level, pos, itemstack, true), this.player); + } + + return true; + } + } + + @NotNull + @Override + public InteractionResult useItemOn(@NotNull ServerPlayer player, Level world, @NotNull ItemStack stack, @NotNull InteractionHand hand, BlockHitResult hitResult) { + BlockPos blockposition = hitResult.getBlockPos(); + BlockState iblockdata = world.getBlockState(blockposition); + InteractionResult enuminteractionresult = InteractionResult.PASS; + + if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { + return InteractionResult.FAIL; + } + + if (player.getCooldowns().isOnCooldown(stack.getItem())) { + return InteractionResult.PASS; + } + + this.firedInteract = true; + this.interactResult = false; + this.interactPosition = blockposition.immutable(); + this.interactHand = hand; + this.interactItemStack = stack.copy(); + + boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty(); + boolean flag1 = player.isSecondaryUseActive() && flag; + + if (!flag1) { + ItemInteractionResult iteminteractionresult = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); + + if (iteminteractionresult.consumesAction()) { + return iteminteractionresult.result(); + } + + if (iteminteractionresult == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION && hand == InteractionHand.MAIN_HAND) { + enuminteractionresult = iblockdata.useWithoutItem(world, player, hitResult); + if (enuminteractionresult.consumesAction()) { + return enuminteractionresult; + } + } + } + + if (!stack.isEmpty() && enuminteractionresult != InteractionResult.SUCCESS && !this.interactResult) { + UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult); + return stack.useOn(itemactioncontext); + } + return enuminteractionresult; + } + + @Override + public void setLevel(@NotNull ServerLevel world) { + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java b/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..c62f9258e4114ff686642b7f472d0e14151f37d5 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBotPacketListenerImpl.java @@ -0,0 +1,85 @@ +package org.leavesmc.leaves.bot; + +import net.minecraft.network.Connection; +import net.minecraft.network.DisconnectionDetails; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.bukkit.event.player.PlayerKickEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ServerBotPacketListenerImpl extends ServerGamePacketListenerImpl { + + public ServerBotPacketListenerImpl(MinecraftServer server, ServerBot bot) { + super(server, BotConnection.INSTANCE, bot, CommonListenerCookie.createInitial(bot.gameProfile, false)); + } + + @Override + public void sendPacket(@NotNull Packet packet) { + } + + @Override + public void send(@NotNull Packet packet) { + } + + @Override + public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks) { + } + + @Override + public void disconnect(@NotNull DisconnectionDetails disconnectionInfo, PlayerKickEvent.@NotNull Cause cause) { + } + + @Override + public boolean isAcceptingMessages() { + return true; + } + + @Override + public void tick() { + } + + public static class BotConnection extends Connection { + + private static final BotConnection INSTANCE = new BotConnection(); + + public BotConnection() { + super(PacketFlow.SERVERBOUND); + } + + @Override + public void tick() { + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public boolean isConnecting() { + return false; + } + + @Override + public boolean isMemoryConnection() { + return false; + } + + @Override + public void send(@NotNull Packet packet) { + } + + @Override + public void send(@NotNull Packet packet, @Nullable PacketSendListener packetsendlistener) { + } + + @Override + public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java b/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java new file mode 100644 index 0000000000000000000000000000000000000000..a37513e1ba8443c702ab0c01fbe5e052e5f0f2ab --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/Actions.java @@ -0,0 +1,67 @@ +package org.leavesmc.leaves.bot.agent; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.actions.*; + +import java.util.Collection; +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 UseItemAction()); + register(new UseItemOnAction()); + register(new UseItemToAction()); + register(new LookAction()); + register(new FishAction()); + register(new SwimAction()); + register(new UseItemOffHandAction()); + register(new UseItemOnOffhandAction()); + register(new UseItemToOffhandAction()); + register(new RotationAction()); + } + + 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(); + } + + @Nullable + public static BotAction getForName(String name) { + return actions.get(name); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java new file mode 100644 index 0000000000000000000000000000000000000000..3bd512b436b32f240466a406a101a051b4b07817 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotAction.java @@ -0,0 +1,163 @@ +package org.leavesmc.leaves.bot.agent; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.event.bot.BotActionExecuteEvent; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; + +import java.util.List; +import java.util.UUID; +import java.util.function.Supplier; + +public abstract class BotAction> { + + private final String name; + private final CommandArgument argument; + private final Supplier creator; + + private boolean cancel; + private int tickDelay; + private int number; + private UUID uuid; + + private int needWaitTick; + private int canDoNumber; + + public BotAction(String name, CommandArgument argument, Supplier creator) { + this.name = name; + this.argument = argument; + this.uuid = UUID.randomUUID(); + this.creator = creator; + + this.cancel = false; + this.tickDelay = 20; + this.number = -1; + } + + public void init() { + this.needWaitTick = 0; + this.canDoNumber = this.getNumber(); + this.setCancelled(false); + } + + public String getName() { + return this.name; + } + + public UUID getUUID() { + return uuid; + } + + public int getTickDelay() { + return this.tickDelay; + } + + @SuppressWarnings("unchecked") + public E setTickDelay(int tickDelay) { + this.tickDelay = Math.max(0, tickDelay); + return (E) this; + } + + public int getNumber() { + return this.number; + } + + @SuppressWarnings("unchecked") + public E setNumber(int number) { + this.number = Math.max(-1, number); + return (E) this; + } + + public int getCanDoNumber() { + return this.canDoNumber; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) { + new BotActionStopEvent(bot.getBukkitEntity(), this.name, this.uuid, reason, null).callEvent(); + this.setCancelled(true); + } + + public CommandArgument getArgument() { + return this.argument; + } + + @SuppressWarnings("unchecked") + public E setTabComplete(int index, List list) { + this.argument.setTabComplete(index, list); + return (E) this; + } + + public void tryTick(ServerBot bot) { + if (this.canDoNumber == 0) { + this.stop(bot, BotActionStopEvent.Reason.DONE); + return; + } + + if (this.needWaitTick <= 0) { + BotActionExecuteEvent event = new BotActionExecuteEvent(bot.getBukkitEntity(), name, uuid); + + event.callEvent(); + if (event.getResult() == BotActionExecuteEvent.Result.SOFT_CANCEL) { + this.needWaitTick = this.getTickDelay(); + return; + } else if (event.getResult() == BotActionExecuteEvent.Result.HARD_CANCEL) { + if (this.canDoNumber > 0) { + this.canDoNumber--; + } + this.needWaitTick = this.getTickDelay(); + return; + } + + if (this.doTick(bot)) { + if (this.canDoNumber > 0) { + this.canDoNumber--; + } + this.needWaitTick = this.getTickDelay(); + } + } else { + this.needWaitTick--; + } + } + + @NotNull + public E create() { + return this.creator.get(); + } + + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + if (!this.cancel) { + nbt.putString("actionName", this.name); + nbt.putUUID("actionUUID", this.uuid); + + nbt.putInt("canDoNumber", this.canDoNumber); + nbt.putInt("needWaitTick", this.needWaitTick); + nbt.putInt("tickDelay", this.tickDelay); + } + return nbt; + } + + public void load(@NotNull CompoundTag nbt) { + this.tickDelay = nbt.getInt("tickDelay"); + this.needWaitTick = nbt.getInt("needWaitTick"); + this.canDoNumber = nbt.getInt("canDoNumber"); + this.uuid = nbt.getUUID("actionUUID"); + } + + public abstract void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result); + + public abstract boolean doTick(@NotNull ServerBot bot); +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c889a2409d8b9f5979a10b61c98638054bd8f5bc --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/BotConfig.java @@ -0,0 +1,62 @@ +package org.leavesmc.leaves.bot.agent; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +import java.util.List; +import java.util.function.Supplier; + +public abstract class BotConfig { + + private final String name; + private final CommandArgument argument; + private final Supplier> creator; + protected ServerBot bot; + + public BotConfig(String name, CommandArgument argument, Supplier> creator) { + this.name = name; + this.argument = argument; + this.creator = creator; + } + + public BotConfig setBot(ServerBot bot) { + this.bot = bot; + return this; + } + + public abstract E getValue(); + + public abstract void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException; + + public List getMessage() { + return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue()); + } + + public List getChangeMessage() { + return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue()); + } + + public String getName() { + return name; + } + + public CommandArgument getArgument() { + return argument; + } + + @NotNull + public BotConfig create(ServerBot bot) { + return this.creator.get().setBot(bot); + } + + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + nbt.putString("configName", this.name); + return nbt; + } + + public abstract void load(@NotNull CompoundTag nbt); +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java b/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java new file mode 100644 index 0000000000000000000000000000000000000000..d99f459b2e323474174cfd5d892cb7573a32ca12 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/Configs.java @@ -0,0 +1,44 @@ +package org.leavesmc.leaves.bot.agent; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.configs.*; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class Configs { + + private static final Map> configs = new HashMap<>(); + + public static final Configs SKIP_SLEEP = register(new SkipSleepConfig()); + public static final Configs ALWAYS_SEND_DATA = register(new AlwaysSendDataConfig()); + public static final Configs SPAWN_PHANTOM = register(new SpawnPhantomConfig()); + public static final Configs SIMULATION_DISTANCE = register(new SimulationDistanceConfig()); + + public final BotConfig config; + + private Configs(BotConfig config) { + this.config = config; + } + + @NotNull + @Contract(pure = true) + public static Collection> getConfigs() { + return configs.values(); + } + + @Nullable + public static Configs getConfig(String name) { + return configs.get(name); + } + + @NotNull + private static Configs register(BotConfig botConfig) { + Configs config = new Configs<>(botConfig); + configs.put(botConfig.getName(), config); + return config; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java new file mode 100644 index 0000000000000000000000000000000000000000..be55a3085a53542c08e7f0209883a4f3f72602e7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AbstractTimerAction.java @@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; +import java.util.function.Supplier; + +public abstract class AbstractTimerAction> extends BotAction { + + public AbstractTimerAction(String name, Supplier creator) { + super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator); + this.setTabComplete(0, List.of("[TickDelay]")).setTabComplete(1, List.of("[DoNumber]")); + } + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(result.readInt(20)).setNumber(result.readInt(-1)); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java new file mode 100644 index 0000000000000000000000000000000000000000..03e9baf9b7c2da0fd1d7d9b0058b70daddedeeaa --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/AttackAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class AttackAction extends AbstractTimerAction { + + public AttackAction() { + super("attack", AttackAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Entity entity = bot.getTargetEntity(3, Entity::isAttackable); + if (entity != null) { + bot.attack(entity); + return true; + } + return false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java new file mode 100644 index 0000000000000000000000000000000000000000..bf7d20374cd7bff7cb7e09d209c6da5d297fe1f1 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/BreakBlockAction.java @@ -0,0 +1,75 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class BreakBlockAction extends AbstractTimerAction { + + public BreakBlockAction() { + super("break", BreakBlockAction::new); + } + + private BlockPos lastPos = null; + private int destroyProgressTime = 0; + private int lastSentState = -1; + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Block block = bot.getBukkitEntity().getTargetBlockExact(5); + if (block != null) { + BlockPos pos = ((CraftBlock) block).getPosition(); + + if (lastPos == null || !lastPos.equals(pos)) { + lastPos = pos; + destroyProgressTime = 0; + lastSentState = -1; + } + + BlockState iblockdata = bot.level().getBlockState(pos); + if (!iblockdata.isAir()) { + bot.swing(InteractionHand.MAIN_HAND); + + if (iblockdata.getDestroyProgress(bot, bot.level(), pos) >= 1.0F) { + bot.gameMode.destroyAndAck(pos, 0, "insta mine"); + bot.level().destroyBlockProgress(bot.getId(), pos, -1); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + 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.updateItemInHand(InteractionHand.MAIN_HAND); + finalBreak(); + return true; + } + } + } + return false; + } + + private void finalBreak() { + lastPos = null; + destroyProgressTime = 0; + lastSentState = -1; + } + + private float incrementDestroyProgress(ServerBot bot, @NotNull BlockState state, BlockPos pos) { + float f = state.getDestroyProgress(bot, bot.level(), pos) * (float) (++destroyProgressTime); + int k = (int) (f * 10.0F); + + if (k != lastSentState) { + bot.level().destroyBlockProgress(bot.getId(), pos, k); + lastSentState = k; + } + + return f; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java new file mode 100644 index 0000000000000000000000000000000000000000..d96fc7b97ff826efe1bd36988f2d1a9ea04654cb --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftBotAction.java @@ -0,0 +1,54 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.entity.botaction.BotActionType; +import org.leavesmc.leaves.entity.botaction.LeavesBotAction; + +public class CraftBotAction extends LeavesBotAction { + + private final BotAction handle; + + public CraftBotAction(@NotNull BotAction action) { + super(BotActionType.valueOf(action.getName()), action.getTickDelay(), action.getCanDoNumber()); + this.handle = action; + } + + @Contract("_ -> new") + @NotNull + public static LeavesBotAction asAPICopy(BotAction action) { + return new CraftBotAction(action); + } + + @NotNull + public static BotAction asInternalCopy(@NotNull LeavesBotAction action) { + BotAction act = Actions.getForName(action.getActionName()); + if (act == null) { + throw new IllegalArgumentException("Invalid action name!"); + } + + BotAction newAction = null; + String[] args = new String[]{String.valueOf(action.getExecuteInterval()), String.valueOf(action.getRemainingExecuteTime())}; + try { + if (act instanceof CraftCustomBotAction customBotAction) { + newAction = customBotAction.createCraft(action.getActionPlayer(), args); + } else { + newAction = act.create(); + newAction.loadCommand(action.getActionPlayer() == null ? null : ((CraftPlayer) action.getActionPlayer()).getHandle(), act.getArgument().parse(0, args)); + } + } catch (IllegalArgumentException ignore) { + } + + if (newAction == null) { + throw new IllegalArgumentException("Invalid action!"); // TODO look action + } + return newAction; + } + + public BotAction getHandle() { + return handle; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java new file mode 100644 index 0000000000000000000000000000000000000000..7b149243b08a44f1181e82217a8645ccab7732d7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/CraftCustomBotAction.java @@ -0,0 +1,49 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.entity.botaction.CustomBotAction; + +public class CraftCustomBotAction extends BotAction { + + private final CustomBotAction realAction; + + public CraftCustomBotAction(String name, @NotNull CustomBotAction realAction) { + super(name, CommandArgument.of().setAllTabComplete(realAction.getTabComplete()), null); + this.realAction = realAction; + } + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + throw new UnsupportedOperationException("Not supported."); + } + + public CraftCustomBotAction createCraft(@Nullable Player player, String[] args) { + CustomBotAction newRealAction = realAction.getNew(player, args); + if (newRealAction != null) { + return new CraftCustomBotAction(this.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.getBukkitEntity()); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java new file mode 100644 index 0000000000000000000000000000000000000000..c71e483e8938ef3b181c95d8e297e54203b5b914 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/DropAction.java @@ -0,0 +1,25 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.command.CommandArgumentResult; + +public class DropAction extends AbstractTimerAction { + + public DropAction() { + super("drop", DropAction::new); + } + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(result.readInt(100)).setNumber(result.readInt(1)); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.dropAll(); + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java new file mode 100644 index 0000000000000000000000000000000000000000..3a13f8ac73e042d939496fb5602e4aa4ea368e0d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/FishAction.java @@ -0,0 +1,73 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.FishingRodItem; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class FishAction extends AbstractTimerAction { + + public FishAction() { + super("fish", FishAction::new); + } + + private int delay = 0; + private int nowDelay = 0; + + @Override + public FishAction setTickDelay(int tickDelay) { + super.setTickDelay(0); + this.delay = tickDelay; + return this; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putInt("fishDelay", this.delay); + nbt.putInt("fishNowDelay", this.nowDelay); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.delay = nbt.getInt("fishDelay"); + this.nowDelay = nbt.getInt("fishNowDelay"); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (this.nowDelay > 0) { + this.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); + this.nowDelay = 20; + return false; + } + if (fishingHook.nibble > 0) { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); + this.nowDelay = this.delay; + return true; + } + } else { + mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND); + } + + return false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java new file mode 100644 index 0000000000000000000000000000000000000000..6fc9ba9bf94cb19ed32cfafa3a44fad0201b14a6 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/JumpAction.java @@ -0,0 +1,21 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class JumpAction extends AbstractTimerAction { + + public JumpAction() { + super("jump", JumpAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.onGround()) { + bot.jumpFromGround(); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java new file mode 100644 index 0000000000000000000000000000000000000000..8be962cf7dc273ccb6a6754684a9be8353865225 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/LookAction.java @@ -0,0 +1,63 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class LookAction extends BotAction { + + public LookAction() { + super("look", CommandArgument.of(CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), LookAction::new); + this.setTabComplete(0, List.of("")); + this.setTabComplete(1, List.of("")); + this.setTabComplete(2, List.of("")); + } + + private Vector pos; + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) throws IllegalArgumentException { + Vector pos = result.readVector(); + if (pos != null) { + this.setPos(pos).setTickDelay(0).setNumber(1); + } else { + throw new IllegalArgumentException("pos?"); + } + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putDouble("x", this.pos.getX()); + nbt.putDouble("y", this.pos.getY()); + nbt.putDouble("z", this.pos.getZ()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.setPos(new Vector(nbt.getDouble("x"), nbt.getDouble("y"), nbt.getDouble("z"))); + } + + public LookAction setPos(Vector pos) { + this.pos = pos; + return this; + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.look(pos.subtract(bot.getLocation().toVector()), false); + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java new file mode 100644 index 0000000000000000000000000000000000000000..84eb7bd727a0085d005a6ee518dfbb8b44fce991 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotateAction.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +public class RotateAction extends BotAction { + + public RotateAction() { + super("rotate", CommandArgument.EMPTY, RotateAction::new); + } + + private ServerPlayer player; + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setPlayer(player).setTickDelay(0).setNumber(1); + } + + public RotateAction setPlayer(ServerPlayer player) { + this.player = player; + return this; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putString("actionName", "look"); // to player loc + nbt.putDouble("x", player.getX()); + nbt.putDouble("y", player.getY()); + nbt.putDouble("z", player.getZ()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.faceLocation(player.getBukkitEntity().getLocation()); + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java new file mode 100644 index 0000000000000000000000000000000000000000..671d1aada7aa3cac0f3df8eec235b2f1ae389492 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/RotationAction.java @@ -0,0 +1,65 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class RotationAction extends BotAction { + + public RotationAction() { + super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), RotateAction::new); + this.setTabComplete(0, List.of("")); + this.setTabComplete(1, List.of("")); + } + + private float yaw; + private float pitch; + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + if (player == null) { + return; + } + + this.setYaw(result.readFloat(player.getYRot())).setPitch(result.readFloat(player.getXRot())).setTickDelay(0).setNumber(1); + } + + public RotationAction setYaw(float yaw) { + this.yaw = yaw; + return this; + } + + public RotationAction setPitch(float pitch) { + this.pitch = pitch; + return this; + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putFloat("yaw", this.yaw); + nbt.putFloat("pitch", this.pitch); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + super.load(nbt); + this.setYaw(nbt.getFloat("yaw")).setPitch(nbt.getFloat("pitch")); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.setRot(yaw, pitch); + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java new file mode 100644 index 0000000000000000000000000000000000000000..923cf55d81fce5cf9db9a1c7adc6f3aed5753b16 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SneakAction.java @@ -0,0 +1,27 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +public class SneakAction extends BotAction { + + public SneakAction() { + super("sneak", CommandArgument.EMPTY, SneakAction::new); + } + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(0).setNumber(1); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + bot.setShiftKeyDown(!bot.isShiftKeyDown()); + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java new file mode 100644 index 0000000000000000000000000000000000000000..b5ccedee17857bc955301512ee965d81fd12017f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/SwimAction.java @@ -0,0 +1,30 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; + +public class SwimAction extends BotAction { + + public SwimAction() { + super("swim", CommandArgument.EMPTY, SwimAction::new); + } + + @Override + public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) { + this.setTickDelay(0).setNumber(-1); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.isInWater()) { + bot.addDeltaMovement(new Vec3(0, 0.03, 0)); + } + return true; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java new file mode 100644 index 0000000000000000000000000000000000000000..1bdde4f8dc5e379d45fac19ba11aa07c4a1b735c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class UseItemAction extends AbstractTimerAction { + + public UseItemAction() { + super("use", UseItemAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.isUsingItem()) { + return false; + } + bot.swing(InteractionHand.MAIN_HAND); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + return bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.MAIN_HAND), InteractionHand.MAIN_HAND).consumesAction(); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java new file mode 100644 index 0000000000000000000000000000000000000000..f6de022b7177da0eb7c089f11ce039ab22c34903 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOffHandAction.java @@ -0,0 +1,22 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class UseItemOffHandAction extends AbstractTimerAction { + + public UseItemOffHandAction() { + super("use_offhand", UseItemOffHandAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + if (bot.isUsingItem()) { + return false; + } + bot.swing(InteractionHand.OFF_HAND); + bot.updateItemInHand(InteractionHand.OFF_HAND); + return bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.OFF_HAND), InteractionHand.OFF_HAND).consumesAction(); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java new file mode 100644 index 0000000000000000000000000000000000000000..f73cd841009117e4032f953a3e754305d97a68be --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnAction.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.TrappedChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +public class UseItemOnAction extends AbstractTimerAction { + + public UseItemOnAction() { + super("use_on", UseItemOnAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE); + if (result instanceof BlockHitResult blockHitResult) { + BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos()); + bot.swing(InteractionHand.MAIN_HAND); + if (state.getBlock() == Blocks.TRAPPED_CHEST) { + BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos()); + if (entity instanceof TrappedChestBlockEntity chestBlockEntity) { + chestBlockEntity.startOpen(bot); + Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1); + return true; + } + } else { + bot.updateItemInHand(InteractionHand.MAIN_HAND); + 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/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java new file mode 100644 index 0000000000000000000000000000000000000000..c0cd258151f690ccbf3df1ffd640b83d8f36aa7d --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemOnOffhandAction.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.TrappedChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.plugin.MinecraftInternalPlugin; + +public class UseItemOnOffhandAction extends AbstractTimerAction { + + public UseItemOnOffhandAction() { + super("use_on_offhand", UseItemOnOffhandAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE); + if (result instanceof BlockHitResult blockHitResult) { + BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos()); + bot.swing(InteractionHand.OFF_HAND); + if (state.getBlock() == Blocks.TRAPPED_CHEST) { + BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos()); + if (entity instanceof TrappedChestBlockEntity chestBlockEntity) { + chestBlockEntity.startOpen(bot); + Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1); + return true; + } + } else { + bot.updateItemInHand(InteractionHand.OFF_HAND); + 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/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java new file mode 100644 index 0000000000000000000000000000000000000000..2048024b754cce85d2bf4f2cbcb800a1f4727495 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToAction.java @@ -0,0 +1,24 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class UseItemToAction extends AbstractTimerAction { + + public UseItemToAction() { + super("use_to", UseItemToAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Entity entity = bot.getTargetEntity(3, null); + if (entity != null) { + bot.swing(InteractionHand.MAIN_HAND); + bot.updateItemInHand(InteractionHand.MAIN_HAND); + return bot.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction(); + } + return false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java new file mode 100644 index 0000000000000000000000000000000000000000..e42288a4f99f7de6655d49ee1a05d37b79652c22 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/actions/UseItemToOffhandAction.java @@ -0,0 +1,24 @@ +package org.leavesmc.leaves.bot.agent.actions; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.ServerBot; + +public class UseItemToOffhandAction extends AbstractTimerAction { + + public UseItemToOffhandAction() { + super("use_to_offhand", UseItemToOffhandAction::new); + } + + @Override + public boolean doTick(@NotNull ServerBot bot) { + Entity entity = bot.getTargetEntity(3, null); + if (entity != null) { + bot.swing(InteractionHand.OFF_HAND); + bot.updateItemInHand(InteractionHand.OFF_HAND); + return bot.interactOn(entity, InteractionHand.OFF_HAND).consumesAction(); + } + return false; + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..9a584603edbbe4ccd8a88c90ef3e9125480635f1 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/AlwaysSendDataConfig.java @@ -0,0 +1,45 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesConfig; + +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class AlwaysSendDataConfig extends BotConfig { + + private boolean value; + + public AlwaysSendDataConfig() { + super("always_send_data", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), AlwaysSendDataConfig::new); + this.value = LeavesConfig.alwaysSendFakeplayerData; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { + this.value = result.readBoolean(this.value); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean("always_send_data", this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.value = nbt.getBoolean("always_send_data"); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c8a2243361cd03e9c64b6a04b37725b549e5b87f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SimulationDistanceConfig.java @@ -0,0 +1,47 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.ArrayList; +import java.util.List; + +public class SimulationDistanceConfig extends BotConfig { + + public SimulationDistanceConfig() { + super("simulation_distance", CommandArgument.of(CommandArgumentType.INTEGER).setTabComplete(0, List.of("2", "10", "")), SimulationDistanceConfig::new); + } + + @Override + public Integer getValue() { + return this.bot.getBukkitEntity().getSimulationDistance(); + } + + @Override + public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { + int realValue = result.readInt(this.bot.getBukkitEntity().getSimulationDistance()); + if (realValue < 2 || realValue > 32) { + throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + result); + } + this.bot.getBukkitEntity().setSimulationDistance(realValue); + } + + @Override + @NotNull + public CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putInt("simulation_distance", this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(new CommandArgumentResult(new ArrayList<>(){{ + add(nbt.getInt("simulation_distance")); + }})); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..0d934910cff745ea9a53d651e20079635ea6781c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SkipSleepConfig.java @@ -0,0 +1,42 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.ArrayList; +import java.util.List; + +public class SkipSleepConfig extends BotConfig { + + public SkipSleepConfig() { + super("skip_sleep", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), SkipSleepConfig::new); + } + + @Override + public Boolean getValue() { + return bot.fauxSleeping; + } + + @Override + public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { + bot.fauxSleeping = result.readBoolean(bot.fauxSleeping); + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean("skip_sleep", this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.setValue(new CommandArgumentResult(new ArrayList<>() {{ + add(nbt.getBoolean("skip_sleep")); + }})); + } +} diff --git a/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..a3f978318a67c3c5e147a50eb2b6c01c3f549dc2 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/agent/configs/SpawnPhantomConfig.java @@ -0,0 +1,51 @@ +package org.leavesmc.leaves.bot.agent.configs; + +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.LeavesConfig; +import org.leavesmc.leaves.bot.agent.BotConfig; +import org.leavesmc.leaves.command.CommandArgument; +import org.leavesmc.leaves.command.CommandArgumentResult; +import org.leavesmc.leaves.command.CommandArgumentType; + +import java.util.List; + +public class SpawnPhantomConfig extends BotConfig { + + private boolean value; + + public SpawnPhantomConfig() { + super("spawn_phantom", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("ture", "false")), SpawnPhantomConfig::new); + this.value = LeavesConfig.fakeplayerSpawnPhantom; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException { + this.value = result.readBoolean(this.value); + } + + @Override + public List getMessage() { + return List.of( + bot.getScoreboardName() + "'s spawn_phantom: " + this.getValue(), + bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks + ); + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { + super.save(nbt); + nbt.putBoolean("spawn_phantom", this.getValue()); + return nbt; + } + + @Override + public void load(@NotNull CompoundTag nbt) { + this.value = nbt.getBoolean("spawn_phantom"); + } +} diff --git a/src/main/java/org/leavesmc/leaves/entity/CraftBot.java b/src/main/java/org/leavesmc/leaves/entity/CraftBot.java new file mode 100644 index 0000000000000000000000000000000000000000..46aec2f954919f487e22ab953062b6889fe3e58b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBot.java @@ -0,0 +1,84 @@ +package org.leavesmc.leaves.entity; + +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.actions.CraftBotAction; +import org.leavesmc.leaves.entity.botaction.LeavesBotAction; +import org.leavesmc.leaves.event.bot.BotActionStopEvent; +import org.leavesmc.leaves.event.bot.BotRemoveEvent; + +import java.util.UUID; + +public class CraftBot extends CraftPlayer implements Bot { + + public CraftBot(CraftServer server, ServerBot entity) { + super(server, entity); + } + + @Override + public String getSkinName() { + return this.getHandle().createState.skinName(); + } + + @Override + public @NotNull String getRealName() { + return this.getHandle().createState.realName(); + } + + @Override + public @Nullable UUID getCreatePlayerUUID() { + return this.getHandle().createPlayer; + } + + @Override + public void addAction(@NotNull LeavesBotAction action) { + this.getHandle().addBotAction(CraftBotAction.asInternalCopy(action), null); + } + + @Override + public LeavesBotAction getAction(int index) { + return CraftBotAction.asAPICopy(this.getHandle().getBotActions().get(index)); + } + + @Override + public int getActionSize() { + return this.getHandle().getBotActions().size(); + } + + @Override + public void stopAction(int index) { + this.getHandle().getBotActions().get(index).stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); + } + + @Override + public void stopAllActions() { + for (BotAction action : this.getHandle().getBotActions()) { + action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN); + } + } + + @Override + public boolean remove(boolean save) { + BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save); + return true; + } + + @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/org/leavesmc/leaves/entity/CraftBotManager.java b/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java new file mode 100644 index 0000000000000000000000000000000000000000..422640df346ccae612b2d3492780efa59d8b4d17 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/entity/CraftBotManager.java @@ -0,0 +1,80 @@ +package org.leavesmc.leaves.entity; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.leavesmc.leaves.bot.BotCreateState; +import org.leavesmc.leaves.bot.BotList; +import org.leavesmc.leaves.bot.ServerBot; +import org.leavesmc.leaves.bot.agent.Actions; +import org.leavesmc.leaves.bot.agent.BotAction; +import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction; +import org.leavesmc.leaves.entity.botaction.CustomBotAction; +import org.leavesmc.leaves.event.bot.BotCreateEvent; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +public class CraftBotManager implements BotManager { + + private final BotList botList; + private final Collection botViews; + + public CraftBotManager() { + this.botList = MinecraftServer.getServer().getBotList(); + this.botViews = Collections.unmodifiableList(Lists.transform(botList.bots, new Function() { + @Override + public CraftBot apply(ServerBot bot) { + return bot.getBukkitEntity(); + } + })); + } + + @Override + public @Nullable Bot getBot(@NotNull UUID uuid) { + ServerBot bot = botList.getBot(uuid); + if (bot != null) { + return bot.getBukkitEntity(); + } else { + return null; + } + } + + @Override + public @Nullable Bot getBot(@NotNull String name) { + ServerBot bot = botList.getBotByName(name); + if (bot != null) { + return bot.getBukkitEntity(); + } else { + return null; + } + } + + @Override + public Collection getBots() { + return botViews; + } + + @Override + public boolean registerCustomBotAction(String name, CustomBotAction action) { + return Actions.register(new CraftCustomBotAction(name, action)); + } + + @Override + public boolean unregisterCustomBotAction(String name) { + BotAction action = Actions.getForName(name); + if (action instanceof CraftCustomBotAction) { + return Actions.unregister(name); + } + return false; + } + + @Override + public BotCreator botCreator(@NotNull String realName, @NotNull Location location) { + return BotCreateState.builder(realName, location).createReason(BotCreateEvent.CreateReason.PLUGIN); + } +} diff --git a/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java b/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..de06c854a9a5242cf632b38806e8e710496b7e4e --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/plugin/MinecraftInternalPlugin.java @@ -0,0 +1,151 @@ +package org.leavesmc.leaves.plugin; + +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +public class MinecraftInternalPlugin extends PluginBase { + + public static final MinecraftInternalPlugin INSTANCE = new MinecraftInternalPlugin(); + + private boolean enabled = true; + + private final PluginDescriptionFile pdf; + + public MinecraftInternalPlugin() { + String pluginName = "Minecraft"; + pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public File getDataFolder() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginDescriptionFile getDescription() { + return pdf; + } + + @Override + public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { + return pdf; + } + + @Override + public FileConfiguration getConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public InputStream getResource(String filename) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveDefaultConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveResource(String resourcePath, boolean replace) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void reloadConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLogger getLogger() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLoader getPluginLoader() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public Server getServer() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void onDisable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onLoad() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onEnable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isNaggable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setNaggable(boolean canNag) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { + throw new UnsupportedOperationException("Not supported."); + } +}