Add Fakeplayer

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