mirror of
https://github.com/LeavesMC/Leaves.git
synced 2025-12-19 14:59:32 +00:00
919 lines
33 KiB
Diff
919 lines
33 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: violetc <58360096+s-yh-china@users.noreply.github.com>
|
|
Date: Thu, 3 Feb 2022 12:28:15 +0800
|
|
Subject: [PATCH] Add fakeplayer support
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
|
|
index a0c19503aabab5378d672a30163d35a5ba05b6c1..8873e12cbd3d6f9071efedb35ea3c69c78033d78 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
|
|
@@ -48,6 +48,7 @@ import net.minecraft.server.players.PlayerList;
|
|
import net.minecraft.util.datafix.DataFixTypes;
|
|
import net.minecraft.world.level.GameRules;
|
|
import org.slf4j.Logger;
|
|
+import top.leavesmc.leaves.bot.ServerBot;
|
|
|
|
public class PlayerAdvancements {
|
|
|
|
@@ -272,6 +273,11 @@ public class PlayerAdvancements {
|
|
}
|
|
|
|
public boolean award(Advancement advancement, String criterionName) {
|
|
+ // Leaves start - bot can't get advancement
|
|
+ if (player instanceof ServerBot) {
|
|
+ return false;
|
|
+ }
|
|
+ // Leaves end - bot can't get advancement
|
|
boolean flag = false;
|
|
AdvancementProgress advancementprogress = this.getOrStartProgress(advancement);
|
|
boolean flag1 = advancementprogress.isDone();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index 6cbd839cc7529eb0095485c4ef4a0d81a3cd9b19..0a4edeed3e6c4813e7ef3cf847ccbbe1325d03c6 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -174,6 +174,7 @@ import org.bukkit.event.player.PlayerPortalEvent;
|
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
|
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
|
|
import org.bukkit.inventory.MainHand;
|
|
+import top.leavesmc.leaves.bot.ServerBot;
|
|
// CraftBukkit end
|
|
|
|
public class ServerPlayer extends Player {
|
|
@@ -1177,6 +1178,13 @@ public class ServerPlayer extends Player {
|
|
this.lastSentHealth = -1.0F;
|
|
this.lastSentFood = -1;
|
|
|
|
+ // Leaves start - bot support
|
|
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
|
|
+ ServerBot.getBots().forEach(bot1 ->
|
|
+ bot1.render(this.connection, true,this.getBukkitEntity().getWorld() == bot1.getBukkitEntity().getWorld())); // Leaves - render bot
|
|
+ }
|
|
+ // Leaves end - bot support
|
|
+
|
|
// CraftBukkit start
|
|
PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
|
|
this.level.getCraftServer().getPluginManager().callEvent(changeEvent);
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index d63bfd9d3194a2972a984ec3a817767a683a940d..f3c3c530d9ae974c9b35e1a12f60e5bad014b42a 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -96,6 +96,7 @@ import net.minecraft.world.scores.Objective;
|
|
import net.minecraft.world.scores.PlayerTeam;
|
|
import net.minecraft.world.scores.Scoreboard; // Paper
|
|
import net.minecraft.world.scores.Team;
|
|
+import top.leavesmc.leaves.bot.ServerBot;
|
|
import org.slf4j.Logger;
|
|
|
|
// CraftBukkit start
|
|
@@ -113,7 +114,6 @@ import org.bukkit.Location;
|
|
import org.bukkit.craftbukkit.CraftServer;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
-import org.bukkit.craftbukkit.util.CraftChatMessage;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
|
import org.bukkit.event.player.PlayerJoinEvent;
|
|
@@ -369,6 +369,18 @@ public abstract class PlayerList {
|
|
return;
|
|
}
|
|
|
|
+ // Leaves start - bot support
|
|
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
|
|
+ ServerBot bot = ServerBot.getBot(player.getName().getString());
|
|
+ if (bot != null) {
|
|
+ bot.kill(); // Leaves - remove bot with the same name
|
|
+ }
|
|
+
|
|
+ ServerBot.getBots().forEach(bot1 ->
|
|
+ bot1.render(playerconnection, true,player.getBukkitEntity().getWorld() == bot1.getBukkitEntity().getWorld())); // 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
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
index 6549ade8e19807c523e5a1dc68b66585aad438b1..b58a4fb6563554c8a5a10eada110125fb1e979b3 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
|
|
@@ -187,6 +187,8 @@ import org.bukkit.plugin.Plugin;
|
|
import org.bukkit.util.BoundingBox;
|
|
import org.bukkit.util.NumberConversions;
|
|
import org.bukkit.util.Vector;
|
|
+import top.leavesmc.leaves.bot.ServerBot;
|
|
+import top.leavesmc.leaves.entity.CraftBot;
|
|
|
|
public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
private static PermissibleBase perm;
|
|
@@ -211,7 +213,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
|
|
if (entity instanceof LivingEntity) {
|
|
// Players
|
|
if (entity instanceof Player) {
|
|
- if (entity instanceof ServerPlayer) { return new CraftPlayer(server, (ServerPlayer) entity); }
|
|
+ // Leaves start - add CraftBot
|
|
+ if (entity instanceof ServerPlayer) {
|
|
+ if (entity instanceof ServerBot) { return new CraftBot(server, (ServerBot) entity); }
|
|
+ else { return new CraftPlayer(server, (ServerPlayer) entity); }
|
|
+ }
|
|
+ // Leaves end - add CraftBot
|
|
else { return new CraftHumanEntity(server, (Player) entity); }
|
|
}
|
|
// Water Animals
|
|
diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java
|
|
index a85a5de7d85cf6c5e19c0245c40e6106e6623007..898c61c25675232e203ee2c872ca25804c41358c 100644
|
|
--- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java
|
|
+++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java
|
|
@@ -7,11 +7,13 @@ import org.bukkit.Bukkit;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.configuration.ConfigurationSection;
|
|
import org.bukkit.configuration.file.YamlConfiguration;
|
|
+import top.leavesmc.leaves.bot.BotCommand;
|
|
|
|
import java.io.File;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
+import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
@@ -63,6 +65,10 @@ public final class LeavesConfig {
|
|
LeavesConfig.load(config);
|
|
|
|
commands = new HashMap<>();
|
|
+
|
|
+ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) {
|
|
+ commands.put("bot", new BotCommand("bot"));
|
|
+ }
|
|
}
|
|
|
|
public static void load(final YamlConfiguration config) {
|
|
@@ -124,6 +130,12 @@ public final class LeavesConfig {
|
|
return LeavesConfig.config.getString(path, dfl);
|
|
}
|
|
|
|
+
|
|
+ static <T> List<T> getList(final String path, final List<T> def) {
|
|
+ LeavesConfig.config.addDefault(path, def);
|
|
+ return (List<T>) LeavesConfig.config.getList(path, config.getList(path));
|
|
+ }
|
|
+
|
|
public static boolean playerCanEditSign = true;
|
|
private static void playerCanEditSign() {
|
|
playerCanEditSign = getBoolean("settings.player-can-edit-sign", playerCanEditSign);
|
|
@@ -134,6 +146,20 @@ public final class LeavesConfig {
|
|
snowballAndEggCanKnockback = getBoolean("settings.snowball-and-egg-can-knockback-player", snowballAndEggCanKnockback);
|
|
}
|
|
|
|
+ public static boolean fakeplayerSupport = true;
|
|
+ private static void fakeplayerSupport() {
|
|
+ if (config.contains("settings.fakeplayer-support")) {
|
|
+ fakeplayerSupport = LeavesConfig.config.getBoolean("settings.fakeplayer-support", fakeplayerSupport);
|
|
+ LeavesConfig.config.set("settings.fakeplayer-support", null);
|
|
+ }
|
|
+ fakeplayerSupport = getBoolean("settings.fakeplayer.enable", fakeplayerSupport);
|
|
+ }
|
|
+
|
|
+ public static List<String> unableFakeplayerNames;
|
|
+ private static void unableFakeplayerNames() {
|
|
+ unableFakeplayerNames = getList("settings.fakeplayer.unable-fakeplayer-names", Arrays.asList("player-name"));
|
|
+ }
|
|
+
|
|
public static final class WorldConfig {
|
|
|
|
public final String worldName;
|
|
diff --git a/src/main/java/top/leavesmc/leaves/bot/BotCommand.java b/src/main/java/top/leavesmc/leaves/bot/BotCommand.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..28744e9065493c7764d0b7a8c5ec0883c21d8156
|
|
--- /dev/null
|
|
+++ b/src/main/java/top/leavesmc/leaves/bot/BotCommand.java
|
|
@@ -0,0 +1,106 @@
|
|
+package top.leavesmc.leaves.bot;
|
|
+
|
|
+import net.minecraft.world.damagesource.DamageSource;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.bukkit.entity.Player;
|
|
+
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+
|
|
+public class BotCommand extends Command {
|
|
+
|
|
+ public BotCommand(String name) {
|
|
+ super(name);
|
|
+ this.description = "FakePlayer Command";
|
|
+ this.usageMessage = "/bot [create | remove]";
|
|
+ this.setPermission("bukkit.command.bot");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
|
|
+ var list = new ArrayList<String>();
|
|
+
|
|
+ if (args.length <= 1) {
|
|
+ list.add("create");
|
|
+ list.add("remove");
|
|
+ } else {
|
|
+ switch (args[0]) {
|
|
+ case "create" -> list.add("[BotName]");
|
|
+ case "remove" -> list.addAll(ServerBot.getBots().stream().map(e -> e.getName().getString()).toList());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return list;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
|
+ if (!testPermission(sender)) return true;
|
|
+
|
|
+ if (args.length == 0) {
|
|
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ switch (args[0]) {
|
|
+ case "create" -> this.onCreate(sender, args);
|
|
+
|
|
+ case "remove" -> this.onRemove(sender, args);
|
|
+
|
|
+ default -> {
|
|
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ private void onCreate(CommandSender sender, String[] args) {
|
|
+ if (args.length < 2) {
|
|
+ sender.sendMessage(ChatColor.RED + "Use /bot create [name] to create a fakeplayer");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!(sender instanceof Player)) {
|
|
+ sender.sendMessage(ChatColor.RED + "This command only can use by player");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!args[1].matches("^[a-zA-Z0-9_]{4,16}$")) {
|
|
+ sender.sendMessage(ChatColor.RED + "This name is unable");
|
|
+ }
|
|
+
|
|
+ if (Bukkit.getPlayer(args[1]) != null || ServerBot.getBot(args[1]) != null) {
|
|
+ sender.sendMessage(ChatColor.RED + "This player is in server");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (top.leavesmc.leaves.LeavesConfig.unableFakeplayerNames.contains(args[1])) {
|
|
+ sender.sendMessage(ChatColor.RED + "This name is unable");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ServerBot.createBot(((Player) sender).getLocation(), args[1]);
|
|
+ }
|
|
+
|
|
+ private void onRemove(CommandSender sender, String[] args) {
|
|
+ if (args.length < 2) {
|
|
+ sender.sendMessage(ChatColor.RED + "Use /bot remove [name] to remove a fakeplayer");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ServerBot bot = ServerBot.getBot(args[1]);
|
|
+
|
|
+ if (bot == null) {
|
|
+ sender.sendMessage(ChatColor.RED + "This fakeplayer is null");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ bot.die(DamageSource.OUT_OF_WORLD);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java b/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..daaece30b2a3983f1cc9ee9a851e8f373974d5ec
|
|
--- /dev/null
|
|
+++ b/src/main/java/top/leavesmc/leaves/bot/MojangAPI.java
|
|
@@ -0,0 +1,41 @@
|
|
+package top.leavesmc.leaves.bot;
|
|
+
|
|
+import com.google.gson.JsonObject;
|
|
+import com.google.gson.JsonParser;
|
|
+
|
|
+import java.io.IOException;
|
|
+import java.io.InputStreamReader;
|
|
+import java.net.URL;
|
|
+import java.util.HashMap;
|
|
+import java.util.Map;
|
|
+
|
|
+public class MojangAPI {
|
|
+
|
|
+ private static final boolean CACHE_ENABLED = false;
|
|
+
|
|
+ private static final Map<String, String[]> CACHE = new HashMap<>();
|
|
+
|
|
+ public static String[] getSkin(String name) {
|
|
+ if (CACHE_ENABLED && CACHE.containsKey(name)) {
|
|
+ return CACHE.get(name);
|
|
+ }
|
|
+
|
|
+ String[] values = pullFromAPI(name);
|
|
+ CACHE.put(name, values);
|
|
+ return values;
|
|
+ }
|
|
+
|
|
+ // Laggggggggggggggggggggggggggggggggggggggggg
|
|
+ public static String[] pullFromAPI(String name) {
|
|
+ try {
|
|
+ String uuid = new JsonParser().parse(new InputStreamReader(new URL("https://api.mojang.com/users/profiles/minecraft/" + name)
|
|
+ .openStream())).getAsJsonObject().get("id").getAsString();
|
|
+ JsonObject property = new JsonParser()
|
|
+ .parse(new InputStreamReader(new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false")
|
|
+ .openStream())).getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject();
|
|
+ return new String[] {property.get("value").getAsString(), property.get("signature").getAsString()};
|
|
+ } catch (IOException | IllegalStateException e) {
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/top/leavesmc/leaves/bot/ServerBot.java b/src/main/java/top/leavesmc/leaves/bot/ServerBot.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..24d90f94da2d2ddea7e7134568b6bfcbe8c53ec3
|
|
--- /dev/null
|
|
+++ b/src/main/java/top/leavesmc/leaves/bot/ServerBot.java
|
|
@@ -0,0 +1,517 @@
|
|
+package top.leavesmc.leaves.bot;
|
|
+
|
|
+import com.mojang.authlib.GameProfile;
|
|
+import com.mojang.authlib.properties.Property;
|
|
+import io.netty.util.concurrent.Future;
|
|
+import io.netty.util.concurrent.GenericFutureListener;
|
|
+import net.minecraft.network.Connection;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.network.protocol.PacketFlow;
|
|
+import net.minecraft.network.protocol.game.ClientboundAddPlayerPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
|
+import net.minecraft.network.syncher.EntityDataAccessor;
|
|
+import net.minecraft.network.syncher.EntityDataSerializers;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.dedicated.DedicatedServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.TicketType;
|
|
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
|
+import net.minecraft.server.network.ServerPlayerConnection;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.damagesource.DamageSource;
|
|
+import net.minecraft.world.entity.Entity;
|
|
+import net.minecraft.world.entity.MoverType;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import net.minecraft.world.phys.AABB;
|
|
+import net.minecraft.world.phys.Vec3;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.Material;
|
|
+import org.bukkit.World;
|
|
+import org.bukkit.block.Block;
|
|
+import org.bukkit.craftbukkit.CraftWorld;
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
|
|
+import org.bukkit.entity.Player;
|
|
+import org.bukkit.event.entity.CreatureSpawnEvent;
|
|
+import org.bukkit.event.player.PlayerJoinEvent;
|
|
+import org.bukkit.event.player.PlayerTeleportEvent;
|
|
+import org.bukkit.plugin.Plugin;
|
|
+import org.bukkit.util.BoundingBox;
|
|
+import org.bukkit.util.Vector;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import top.leavesmc.leaves.entity.Bot;
|
|
+import top.leavesmc.leaves.entity.CraftBot;
|
|
+import top.leavesmc.leaves.event.bot.BotCreateEvent;
|
|
+import top.leavesmc.leaves.bot.agent.BotAction;
|
|
+import top.leavesmc.leaves.event.bot.BotJoinEvent;
|
|
+import top.leavesmc.leaves.util.MathUtils;
|
|
+
|
|
+import javax.annotation.Nullable;
|
|
+import java.util.HashSet;
|
|
+import java.util.Objects;
|
|
+import java.util.Set;
|
|
+import java.util.UUID;
|
|
+
|
|
+public class ServerBot extends ServerPlayer {
|
|
+
|
|
+ private Vector velocity;
|
|
+ private Vector oldVelocity;
|
|
+
|
|
+ private boolean removeOnDeath;
|
|
+ private byte fireTicks;
|
|
+ private byte groundTicks;
|
|
+ private byte jumpTicks;
|
|
+ private byte noFallTicks;
|
|
+
|
|
+ private static final Set<ServerBot> bots = new HashSet<>();
|
|
+ private static final Plugin MINECRAFT_PLUGIN = new MinecraftInternalPlugin();
|
|
+
|
|
+ public ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) {
|
|
+ super(server, world, profile, null);
|
|
+ this.entityData.set(new EntityDataAccessor<>(16, EntityDataSerializers.INT), 0xFF);
|
|
+
|
|
+ this.velocity = new Vector(0, 0, 0);
|
|
+ this.oldVelocity = velocity.clone();
|
|
+ this.noFallTicks = 60;
|
|
+ this.fireTicks = 0;
|
|
+ this.removeOnDeath = true;
|
|
+ }
|
|
+
|
|
+ public static void createBot(Location loc, String name) {
|
|
+ Bukkit.getScheduler().runTaskAsynchronously(MINECRAFT_PLUGIN, () -> {
|
|
+ String[] skin = MojangAPI.getSkin(name);
|
|
+ Bukkit.getScheduler().runTask(MINECRAFT_PLUGIN, () -> createBot(loc, name, skin));
|
|
+ });
|
|
+ }
|
|
+
|
|
+ @Nullable
|
|
+ public static ServerBot createBot(@NotNull Location loc, @NotNull String name, String[] skin) {
|
|
+ MinecraftServer server = MinecraftServer.getServer();
|
|
+
|
|
+ BotCreateEvent event = new BotCreateEvent(name, loc, ChatColor.YELLOW + name + " joined the game");
|
|
+ server.server.getPluginManager().callEvent(event);
|
|
+ if (!event.isCancelled()) {
|
|
+ loc = event.getCreateLocation();
|
|
+
|
|
+ ServerLevel world = ((CraftWorld) Objects.requireNonNull(loc.getWorld())).getHandle();
|
|
+ UUID uuid = UUID.randomUUID();
|
|
+ CustomGameProfile profile = new CustomGameProfile(uuid, name.length() > 16 ? name.substring(0, 16) : name, skin);
|
|
+
|
|
+ ServerBot bot = new ServerBot(server, world, profile);
|
|
+
|
|
+ bot.connection = new ServerGamePacketListenerImpl(server, new Connection(PacketFlow.CLIENTBOUND) {
|
|
+ @Override
|
|
+ public void send(@NotNull Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
|
|
+ }
|
|
+ }, bot);
|
|
+ bot.isRealPlayer = true;
|
|
+
|
|
+ if (event.getJoinMessage() != null) {
|
|
+ Bukkit.broadcastMessage(event.getJoinMessage());
|
|
+ }
|
|
+
|
|
+ bot.teleportTo(loc.getX(), loc.getY(), loc.getZ());
|
|
+ bot.setRot(loc.getYaw(), loc.getPitch());
|
|
+ world.addFreshEntity(bot, CreatureSpawnEvent.SpawnReason.COMMAND);
|
|
+
|
|
+ bot.renderAll();
|
|
+ bots.add(bot);
|
|
+
|
|
+ BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitPlayer());
|
|
+ server.server.getPluginManager().callEvent(event1);
|
|
+
|
|
+ return bot;
|
|
+ }
|
|
+
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ private void renderAll() {
|
|
+ Packet<?>[] packets = getRenderPackets();
|
|
+ Bukkit.getOnlinePlayers().forEach(p ->
|
|
+ render(((CraftPlayer) p).getHandle().connection, packets, false, p.getWorld() == getBukkitPlayer().getWorld()));
|
|
+ }
|
|
+
|
|
+ public void render(ServerPlayerConnection connection, boolean login, boolean all) {
|
|
+ render(connection, getRenderPackets(), login, all);
|
|
+ }
|
|
+
|
|
+ private void render(ServerPlayerConnection connection, Packet<?>[] packets, boolean login, boolean all) { // always use getRenderPackets() to get packets. replace it soon
|
|
+ connection.send(packets[0]);
|
|
+ if (all) {
|
|
+ connection.send(packets[1]);
|
|
+ connection.send(packets[2]);
|
|
+ if (login) {
|
|
+ connection.send(packets[3]); // This need delay 10 tick ? real ?
|
|
+ } else {
|
|
+ connection.send(packets[3]);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private Packet<?>[] getRenderPackets() {
|
|
+ return new Packet[]{
|
|
+ new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, this),
|
|
+ new ClientboundAddPlayerPacket(this),
|
|
+ new ClientboundSetEntityDataPacket(this.getId(), this.getEntityData(), true),
|
|
+ new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))
|
|
+ };
|
|
+ }
|
|
+
|
|
+ private void sendPacket(Packet<?> packet) {
|
|
+ Bukkit.getOnlinePlayers().forEach(p -> ((CraftPlayer) p).getHandle().connection.send(packet));
|
|
+ }
|
|
+
|
|
+ // die check start
|
|
+ @Override
|
|
+ public void die(DamageSource damageSource) {
|
|
+ super.die(damageSource);
|
|
+ this.dieCheck();
|
|
+ }
|
|
+
|
|
+ private void dieCheck() {
|
|
+ if (removeOnDeath) {
|
|
+ bots.remove(this);
|
|
+ remove(RemovalReason.KILLED);
|
|
+ this.setDead();
|
|
+ this.removeTab();
|
|
+ Bukkit.broadcastMessage(ChatColor.YELLOW + this.getName().getString() + " leaved the game"); // TODO i18n
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void setDead() {
|
|
+ sendPacket(new ClientboundRemoveEntitiesPacket(getId()));
|
|
+ this.dead = true;
|
|
+ this.inventoryMenu.removed(this);
|
|
+ this.containerMenu.removed(this);
|
|
+ }
|
|
+ // die check end
|
|
+
|
|
+ @Nullable
|
|
+ @Override
|
|
+ public Entity changeDimension(ServerLevel destination) {
|
|
+ return null; // disable dimension change
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isOnGround() {
|
|
+ return groundTicks != 0;
|
|
+ }
|
|
+
|
|
+ private void removeTab() {
|
|
+ sendPacket(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, this));
|
|
+ }
|
|
+
|
|
+ public static boolean solidAt(Location loc) {
|
|
+ Block block = loc.getBlock();
|
|
+ BoundingBox box = block.getBoundingBox();
|
|
+ Vector position = loc.toVector();
|
|
+
|
|
+ double x = position.getX();
|
|
+ double y = position.getY();
|
|
+ double z = position.getZ();
|
|
+
|
|
+ double minX = box.getMinX();
|
|
+ double minY = box.getMinY();
|
|
+ double minZ = box.getMinZ();
|
|
+
|
|
+ double maxX = box.getMaxX();
|
|
+ double maxY = box.getMaxY();
|
|
+ double maxZ = box.getMaxZ();
|
|
+
|
|
+ return x > minX && x < maxX && y > minY && y < maxY && z > minZ && z < maxZ;
|
|
+ }
|
|
+
|
|
+ private void loadChunks() {
|
|
+ ServerLevel world = (ServerLevel) level;
|
|
+ int view = ((DedicatedServer) server).settings.getProperties().viewDistance - 1;
|
|
+ for (int i = chunkPosition().x - view; i <= chunkPosition().x + view; i++) {
|
|
+ for (int j = chunkPosition().z - view; j <= chunkPosition().z + view; j++) {
|
|
+ LevelChunk chunk = world.getChunk(i, j);
|
|
+ ChunkPos chunkPos = chunk.getPos();
|
|
+ chunk.loaded = true;
|
|
+ world.chunkSource.chunkMap.distanceManager.addTicket(TicketType.PLAYER, chunkPos, 31, chunkPos);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean checkGround() {
|
|
+ double vy = velocity.getY();
|
|
+
|
|
+ if (vy > 0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ World world = getBukkitPlayer().getWorld();
|
|
+ AABB box = getBoundingBox();
|
|
+
|
|
+ double[] xVals = new double[]{
|
|
+ box.minX,
|
|
+ box.maxX
|
|
+ };
|
|
+
|
|
+ double[] zVals = new double[]{
|
|
+ box.minZ,
|
|
+ box.maxZ
|
|
+ };
|
|
+
|
|
+ for (double x : xVals) {
|
|
+ for (double z : zVals) {
|
|
+ Location loc = new Location(world, x, getY() - 0.01, z);
|
|
+ Block block = world.getBlockAt(loc);
|
|
+
|
|
+ if (block.getType().isSolid() && solidAt(loc)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public Bot getBukkitPlayer() {
|
|
+ return getBukkitEntity();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ @NotNull
|
|
+ public CraftBot getBukkitEntity() {
|
|
+ return (CraftBot) super.getBukkitEntity();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isInWater() {
|
|
+ Location loc = getLocation();
|
|
+ for (int i = 0; i <= 2; i++) {
|
|
+ Material type = loc.getBlock().getType();
|
|
+ if (type == Material.WATER || type == Material.LAVA) {
|
|
+ return true;
|
|
+ }
|
|
+ loc.add(0, 0.9, 0);
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void tick() {
|
|
+ // loadChunks(); // Load chunks
|
|
+ super.tick();
|
|
+
|
|
+ if (!isAlive()) return;
|
|
+
|
|
+ if (fireTicks > 0) --fireTicks;
|
|
+ if (jumpTicks > 0) --jumpTicks;
|
|
+ if (noFallTicks > 0) --noFallTicks;
|
|
+
|
|
+ if (checkGround()) {
|
|
+ if (groundTicks < 5) groundTicks++;
|
|
+ } else {
|
|
+ groundTicks = 0;
|
|
+ }
|
|
+
|
|
+ updateLocation();
|
|
+
|
|
+ float health = getHealth();
|
|
+ float maxHealth = getMaxHealth();
|
|
+ float regenAmount = 0.025f;
|
|
+ float amount;
|
|
+
|
|
+ if (health < maxHealth - regenAmount) {
|
|
+ amount = health + regenAmount;
|
|
+ } else {
|
|
+ amount = maxHealth;
|
|
+ }
|
|
+
|
|
+ this.setHealth(amount);
|
|
+
|
|
+ fireDamageCheck();
|
|
+ fallDamageCheck();
|
|
+
|
|
+ checkOutOfWorld();
|
|
+
|
|
+ oldVelocity = velocity.clone();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void push(Entity entity) {
|
|
+ if (!this.isPassengerOfSameVehicle(entity) && !entity.noPhysics && !this.noPhysics) {
|
|
+ double d0 = entity.getX() - this.getX();
|
|
+ double d1 = entity.getZ() - this.getZ();
|
|
+ double d2 = Mth.absMax(d0, d1);
|
|
+ if (d2 >= 0.009999999776482582D) {
|
|
+ d2 = Math.sqrt(d2);
|
|
+ d0 /= d2;
|
|
+ d1 /= d2;
|
|
+ double d3 = 1.0D / d2;
|
|
+ if (d3 > 1.0D) {
|
|
+ d3 = 1.0D;
|
|
+ }
|
|
+
|
|
+ d0 *= d3;
|
|
+ d1 *= d3;
|
|
+ d0 *= 0.05000000074505806D;
|
|
+ d1 *= 0.05000000074505806D;
|
|
+ if (!this.isVehicle()) {
|
|
+ velocity.add(new Vector(-d0 * 3, 0.0D, -d1 * 3));
|
|
+ }
|
|
+
|
|
+ if (!entity.isVehicle()) {
|
|
+ entity.push(d0, 0.0D, d1);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void doTick() {
|
|
+ if (this.hurtTime > 0) {
|
|
+ this.hurtTime -= 1;
|
|
+ }
|
|
+
|
|
+ baseTick();
|
|
+ tickEffects();
|
|
+
|
|
+ this.lerpSteps = (int) this.zza;
|
|
+ this.animStep = this.run;
|
|
+ this.yRotO = this.getYRot();
|
|
+ this.xRotO = this.getXRot();
|
|
+ }
|
|
+
|
|
+ public Location getLocation() {
|
|
+ return getBukkitPlayer().getLocation();
|
|
+ }
|
|
+
|
|
+ public void setOnFirePackets(boolean onFire) {
|
|
+ entityData.set(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), onFire ? (byte) 1 : (byte) 0);
|
|
+ sendPacket(new ClientboundSetEntityDataPacket(getId(), getEntityData(), false));
|
|
+ }
|
|
+
|
|
+ private void fallDamageCheck() {
|
|
+ if (groundTicks != 0 && noFallTicks == 0 && !(oldVelocity.getY() >= -0.8)) {
|
|
+ hurt(DamageSource.FALL, (float) Math.pow(3.6, -oldVelocity.getY()));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void fireDamageCheck() {
|
|
+ Material type = getLocation().getBlock().getType();
|
|
+
|
|
+ final boolean lava = type == Material.LAVA;
|
|
+ final boolean fire = type == Material.FIRE || type == Material.SOUL_FIRE;
|
|
+
|
|
+ if (!isAlive()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (type == Material.WATER) {
|
|
+ setOnFirePackets(false);
|
|
+ fireTicks = 0;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (lava || fire) {
|
|
+ ignite();
|
|
+ }
|
|
+
|
|
+ if (invulnerableTime == 0) {
|
|
+ if (lava) {
|
|
+ hurt(DamageSource.LAVA, 4);
|
|
+ invulnerableTime = 12;
|
|
+ } else if (fire) {
|
|
+ hurt(DamageSource.IN_FIRE, 2);
|
|
+ invulnerableTime = 12;
|
|
+ } else if (fireTicks > 1) {
|
|
+ hurt(DamageSource.ON_FIRE, 1);
|
|
+ invulnerableTime = 20;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (fireTicks == 1) {
|
|
+ setOnFirePackets(false);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void ignite() {
|
|
+ if (fireTicks <= 1) setOnFirePackets(true);
|
|
+ this.fireTicks = 100;
|
|
+ }
|
|
+
|
|
+ public void addFriction(double factor) {
|
|
+
|
|
+ double frictionMin = 0.01;
|
|
+ double x = velocity.getX();
|
|
+ double z = velocity.getZ();
|
|
+
|
|
+ velocity.setX(Math.abs(x) < frictionMin ? 0 : x * factor);
|
|
+ velocity.setZ(Math.abs(z) < frictionMin ? 0 : z * factor);
|
|
+ }
|
|
+
|
|
+ private void updateLocation() {
|
|
+ double y;
|
|
+
|
|
+ MathUtils.clean(velocity); // TODO lag
|
|
+
|
|
+ if (isInWater()) {
|
|
+ y = Math.min(velocity.getY() + 0.1, 0.1);
|
|
+ addFriction(0.8);
|
|
+ velocity.setY(y);
|
|
+ } else {
|
|
+ if (groundTicks != 0) {
|
|
+ velocity.setY(0);
|
|
+ addFriction(0.5);
|
|
+ y = 0;
|
|
+ } else {
|
|
+ y = velocity.getY();
|
|
+ velocity.setY(Math.max(y - 0.1, -3.5));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.move(MoverType.SELF, new Vec3(velocity.getX(), y, velocity.getZ()));
|
|
+ }
|
|
+
|
|
+ public static ServerBot getBot(ServerPlayer player) {
|
|
+ ServerBot bot = null;
|
|
+ for (ServerBot b : bots) {
|
|
+ if (b.getId() == player.getId()) {
|
|
+ bot = b;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ return bot;
|
|
+ }
|
|
+
|
|
+ public static ServerBot getBot(String name) {
|
|
+ ServerBot bot = null;
|
|
+ for (ServerBot b : bots) {
|
|
+ if (b.getName().getString().equals(name)) {
|
|
+ bot = b;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ return bot;
|
|
+ }
|
|
+
|
|
+ public static Set<ServerBot> getBots() { // It needs unmodifiable
|
|
+ return bots;
|
|
+ }
|
|
+
|
|
+ public static class CustomGameProfile extends GameProfile {
|
|
+
|
|
+ public CustomGameProfile(UUID uuid, String name, String[] skin) {
|
|
+ super(uuid, name);
|
|
+ setSkin(skin);
|
|
+ }
|
|
+
|
|
+ public void setSkin(String[] skin) {
|
|
+ if (skin != null) {
|
|
+ getProperties().put("textures", new Property("textures", skin[0], skin[1]));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/top/leavesmc/leaves/entity/CraftBot.java b/src/main/java/top/leavesmc/leaves/entity/CraftBot.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2d5044c4a09044364e818b7674908bf98932bf58
|
|
--- /dev/null
|
|
+++ b/src/main/java/top/leavesmc/leaves/entity/CraftBot.java
|
|
@@ -0,0 +1,26 @@
|
|
+package top.leavesmc.leaves.entity;
|
|
+
|
|
+import org.bukkit.craftbukkit.CraftServer;
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
+import top.leavesmc.leaves.bot.ServerBot;
|
|
+
|
|
+public class CraftBot extends CraftPlayer implements Bot {
|
|
+
|
|
+ public CraftBot(CraftServer server, ServerBot entity) {
|
|
+ super(server, entity);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ServerBot getHandle() {
|
|
+ return (ServerBot) entity;
|
|
+ }
|
|
+
|
|
+ public void setHandle(final ServerBot entity) {
|
|
+ super.setHandle(entity);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "CraftBot{" + "name=" + getName() + '}';
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/top/leavesmc/leaves/util/MathUtils.java b/src/main/java/top/leavesmc/leaves/util/MathUtils.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..acdef051d6d4eb4e0d957bfbd7f205827c2f23a9
|
|
--- /dev/null
|
|
+++ b/src/main/java/top/leavesmc/leaves/util/MathUtils.java
|
|
@@ -0,0 +1,13 @@
|
|
+package top.leavesmc.leaves.util;
|
|
+
|
|
+import org.bukkit.util.NumberConversions;
|
|
+import org.bukkit.util.Vector;
|
|
+
|
|
+public class MathUtils {
|
|
+ // Lag ?
|
|
+ public static void clean(Vector vector) {
|
|
+ if (!NumberConversions.isFinite(vector.getX())) vector.setX(0);
|
|
+ if (!NumberConversions.isFinite(vector.getY())) vector.setY(0);
|
|
+ if (!NumberConversions.isFinite(vector.getZ())) vector.setZ(0);
|
|
+ }
|
|
+}
|