diff --git a/src/main/java/io/github/hello09x/fakeplayer/Main.java b/src/main/java/io/github/hello09x/fakeplayer/Main.java index c1c5fa3..71c60a4 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/Main.java +++ b/src/main/java/io/github/hello09x/fakeplayer/Main.java @@ -3,7 +3,7 @@ package io.github.hello09x.fakeplayer; import io.github.hello09x.fakeplayer.command.FakePlayerCommand; import io.github.hello09x.fakeplayer.listener.PlayerDeathListener; import io.github.hello09x.fakeplayer.listener.PlayerQuitListener; -import io.github.hello09x.fakeplayer.listener.PlayerTeleportListener; +import io.github.hello09x.fakeplayer.manager.FakePlayerManager; import lombok.Getter; import org.bukkit.plugin.java.JavaPlugin; @@ -22,15 +22,14 @@ public final class Main extends JavaPlugin { } { -// getServer().getPluginManager().registerEvents(PlayerQuitListener.instance, Main.getInstance()); + getServer().getPluginManager().registerEvents(PlayerQuitListener.instance, Main.getInstance()); getServer().getPluginManager().registerEvents(PlayerDeathListener.instance, Main.getInstance()); -// getServer().getPluginManager().registerEvents(PlayerTeleportListener.instance, Main.getInstance()); } } @Override public void onDisable() { - // Plugin shutdown logic + FakePlayerManager.instance.removeFakePlayers(); } } diff --git a/src/main/java/io/github/hello09x/fakeplayer/command/player/AbstractTeleportCommand.java b/src/main/java/io/github/hello09x/fakeplayer/command/player/AbstractTeleportCommand.java index 91f3dff..1a00c7c 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/command/player/AbstractTeleportCommand.java +++ b/src/main/java/io/github/hello09x/fakeplayer/command/player/AbstractTeleportCommand.java @@ -7,6 +7,7 @@ import net.kyori.adventure.sound.Sound; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -90,18 +91,7 @@ public abstract class AbstractTeleportCommand extends ExecutableCommand { } protected void teleport(@NotNull Player from, @NotNull Player to) { - from.teleport(to.getLocation()); - from.playSound(Sound.sound( - ENTITY_ENDERMAN_TELEPORT.key(), - Sound.Source.PLAYER, - 1.0F, - 1.0F - )); - to.playSound(Sound.sound( - ENTITY_ENDERMAN_TELEPORT.key(), - Sound.Source.PLAYER, - 1.0F, - 1.0F - )); + from.teleport(to.getLocation(), PlayerTeleportEvent.TeleportCause.COMMAND); + to.getLocation().getWorld().playSound(to.getLocation(), ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F); } } diff --git a/src/main/java/io/github/hello09x/fakeplayer/command/player/RemoveCommand.java b/src/main/java/io/github/hello09x/fakeplayer/command/player/RemoveCommand.java index 4c6534f..6f89e2e 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/command/player/RemoveCommand.java +++ b/src/main/java/io/github/hello09x/fakeplayer/command/player/RemoveCommand.java @@ -91,8 +91,7 @@ public class RemoveCommand extends ExecutableCommand { return fakes .stream() .map(Player::getName) - .filter(name -> args[0].isBlank() - || name.toLowerCase().contains(args[0].toLowerCase())) + .filter(name -> args[0].isBlank() || name.toLowerCase().contains(args[0].toLowerCase())) .toList(); } } diff --git a/src/main/java/io/github/hello09x/fakeplayer/command/player/TpHereCommand.java b/src/main/java/io/github/hello09x/fakeplayer/command/player/TpHereCommand.java index 782524d..cfa5382 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/command/player/TpHereCommand.java +++ b/src/main/java/io/github/hello09x/fakeplayer/command/player/TpHereCommand.java @@ -46,11 +46,6 @@ public class TpHereCommand extends AbstractTeleportCommand { return true; } - if (!fake.getWorld().equals(creator.getWorld())) { - sender.sendMessage(text("暂不支持跨世界传送...", RED)); - return true; - } - teleport(fake, creator); return true; } diff --git a/src/main/java/io/github/hello09x/fakeplayer/core/EmptyAdvancements.java b/src/main/java/io/github/hello09x/fakeplayer/core/EmptyAdvancements.java new file mode 100644 index 0000000..c8d7427 --- /dev/null +++ b/src/main/java/io/github/hello09x/fakeplayer/core/EmptyAdvancements.java @@ -0,0 +1,67 @@ +package io.github.hello09x.fakeplayer.core; + +import com.mojang.datafixers.DataFixer; +import io.github.hello09x.fakeplayer.util.ReflectionUtils; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.server.PlayerAdvancements; +import net.minecraft.server.ServerAdvancementManager; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Map; + +public class EmptyAdvancements extends PlayerAdvancements { + + public final static Field PROGRESS = ReflectionUtils.getFirstFieldByType(PlayerAdvancements.class, Map.class, false); + + public EmptyAdvancements( + DataFixer datafixer, + PlayerList playerlist, + ServerAdvancementManager manager, + Path path, + ServerPlayer player + ) { + super(datafixer, playerlist, manager, path, player); + this.save(); + } + + @Override + public boolean award(Advancement advancement, String s) { + return false; + } + + @Override + public void flushDirty(ServerPlayer player) { + } + + @Override + public AdvancementProgress getOrStartProgress(Advancement advancement) { + return new AdvancementProgress(); + } + + @Override + public boolean revoke(Advancement advancement, String s) { + return false; + } + + @Override + public void save() { + } + + @Override + public void setPlayer(ServerPlayer player) { + } + + @Override + public void setSelectedTab(Advancement advancement) { + } + + @Override + public void stopListening() { + } + + +} diff --git a/src/main/java/io/github/hello09x/fakeplayer/entity/FakePlayer.java b/src/main/java/io/github/hello09x/fakeplayer/entity/FakePlayer.java index e4c913d..7f741d4 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/entity/FakePlayer.java +++ b/src/main/java/io/github/hello09x/fakeplayer/entity/FakePlayer.java @@ -2,12 +2,16 @@ package io.github.hello09x.fakeplayer.entity; import com.mojang.authlib.GameProfile; import io.github.hello09x.fakeplayer.Main; +import io.github.hello09x.fakeplayer.core.EmptyAdvancements; import io.github.hello09x.fakeplayer.core.EmptyConnection; import io.github.hello09x.fakeplayer.core.EmptyNetworkManager; +import io.github.hello09x.fakeplayer.util.ReflectionUtils; +import lombok.Getter; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; import net.minecraft.network.protocol.game.ServerboundClientInformationPacket; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -15,6 +19,7 @@ import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.entity.player.ChatVisiblity; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Sound; import org.bukkit.craftbukkit.v1_20_R1.CraftServer; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; @@ -22,31 +27,33 @@ import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Objects; import java.util.UUID; +import static org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT; + public class FakePlayer extends ServerPlayer { - private final UUID uniqueId; + public final static Field advancements = ReflectionUtils.getFirstFieldByType(ServerPlayer.class, PlayerAdvancements.class, false); - private final String name; - - private final Location spawnLocation; + @Getter + private @NotNull + final Location spawnLocation; public FakePlayer( - MinecraftServer server, - ServerLevel world, - UUID uniqueId, - String name, - Location at + @NotNull MinecraftServer server, + @NotNull ServerLevel world, + @NotNull UUID uniqueId, + @NotNull String name, + @NotNull Location at ) { super(server, world, new GameProfile(uniqueId, name)); - this.uniqueId = uniqueId; - this.name = name; this.spawnLocation = at; try { @@ -56,9 +63,25 @@ public class FakePlayer extends ServerPlayer { } catch (IOException e) { throw new RuntimeException(e); } + + if (advancements != null) { + try { + advancements.set( + this, + new EmptyAdvancements( + server.getFixerUpper(), + server.getPlayerList(), + server.getAdvancements(), + Main.getInstance().getDataFolder().getParentFile().toPath(), + this + )); + } catch (IllegalAccessException ignored) { + } + } + } - private static boolean isChunkLoaded(Location at) { + private static boolean isChunkLoaded(@NotNull Location at) { if (at.getWorld() == null) { return false; } @@ -67,17 +90,21 @@ public class FakePlayer extends ServerPlayer { return at.getWorld().isChunkLoaded(x, z); } - public Player prepare() { - addToPlayerList(); - spawn(); + public @NotNull Player spawn() { + this.boardcast(); + this.addEntityToWorld(); var p = Objects.requireNonNull(Bukkit.getPlayer(this.uuid)); p.setSleepingIgnored(true); p.setPersistent(false); + p.setInvulnerable(true); new BukkitRunnable() { @Override public void run() { + if (!p.isOnline()) { + cancel(); + } doTick(); tickCount++; } @@ -86,9 +113,9 @@ public class FakePlayer extends ServerPlayer { } @SuppressWarnings("all") - public void addToPlayerList() { - var packet = new ServerboundClientInformationPacket( - "EN", + public void boardcast() { + this.updateOptions(new ServerboundClientInformationPacket( + "en_us", 10, ChatVisiblity.FULL, false, @@ -96,14 +123,14 @@ public class FakePlayer extends ServerPlayer { HumanoidArm.LEFT, false, true - ); - this.updateOptions(packet); + )); var entity = this.getBukkitEntity(); var handle = (ServerPlayer) ((CraftEntity) entity).getHandle(); if (handle.level() == null) { return; } + var playerList = handle.level().players(); if (!playerList.contains(handle)) { ((List) playerList).add(handle); @@ -132,15 +159,15 @@ public class FakePlayer extends ServerPlayer { } - public void spawn() { + public void addEntityToWorld() { var entity = this.getBukkitEntity(); var handle = (ServerPlayer) ((CraftEntity) entity).getHandle(); if (!isChunkLoaded(spawnLocation)) { spawnLocation.getChunk().load(); } - handle.level().addFreshEntity(handle, CreatureSpawnEvent.SpawnReason.CUSTOM); + handle.level().addFreshEntity(handle, CreatureSpawnEvent.SpawnReason.CUSTOM); ((CraftServer) Bukkit.getServer()).getHandle().respawn( this, true, @@ -149,6 +176,7 @@ public class FakePlayer extends ServerPlayer { // move directly getBukkitEntity().teleport(spawnLocation); + spawnLocation.getWorld().playSound(spawnLocation, Sound.ENTITY_ENDERMAN_TELEPORT, 1.0F, 1.0F); } diff --git a/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerDeathListener.java b/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerDeathListener.java index b649e21..eb51baa 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerDeathListener.java +++ b/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerDeathListener.java @@ -19,6 +19,7 @@ public class PlayerDeathListener implements Listener { return; } + // 假人不会复活, 死掉了就踢掉 player.kick(); } diff --git a/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerTeleportListener.java b/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerTeleportListener.java deleted file mode 100644 index c2dc477..0000000 --- a/src/main/java/io/github/hello09x/fakeplayer/listener/PlayerTeleportListener.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.github.hello09x.fakeplayer.listener; - -import io.github.hello09x.fakeplayer.manager.FakePlayerManager; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.jetbrains.annotations.NotNull; - -public class PlayerTeleportListener implements Listener { - - public final static PlayerTeleportListener instance = new PlayerTeleportListener(); - private final FakePlayerManager manager = FakePlayerManager.instance; - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void handlePlayerTeleport(@NotNull PlayerTeleportEvent event) { - if (!manager.isFakePlayer(event.getPlayer())) { - return; - } - - // 创建玩家时会使用这个传送原因 - if (event.getCause() == PlayerTeleportEvent.TeleportCause.SPECTATE) { - return; - } - - var from = event.getFrom().getWorld().getUID(); - var to = event.getTo().getWorld().getUID(); - if (from.equals(to)) { - return; - } - - event.setCancelled(true); - } - -} diff --git a/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerManager.java b/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerManager.java index 170e9cc..54d741b 100644 --- a/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerManager.java +++ b/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerManager.java @@ -1,6 +1,5 @@ package io.github.hello09x.fakeplayer.manager; -import com.mojang.authlib.GameProfile; import io.github.hello09x.fakeplayer.Main; import io.github.hello09x.fakeplayer.entity.FakePlayer; import org.bukkit.Bukkit; @@ -25,8 +24,16 @@ public class FakePlayerManager { public final static FakePlayerManager instance = new FakePlayerManager(); + private final static String META_KEY_CREATOR = "fakeplayer:creator"; + private final FakeplayerProperties properties = FakeplayerProperties.instance; + /** + * 创建一个假人 + * + * @param creator 创建者 + * @param at 生成地点 + */ public synchronized void spawnFakePlayer( @NotNull CommandSender creator, @NotNull Location at @@ -50,9 +57,8 @@ public class FakePlayerManager { UUID.randomUUID(), name, at - ); - var p = player.prepare(); - p.setMetadata(FakePlayerSpawner.META_KEY_CREATOR, new FixedMetadataValue(Main.getInstance(), creator.getName())); + ).spawn(); + player.setMetadata(META_KEY_CREATOR, new FixedMetadataValue(Main.getInstance(), creator.getName())); } public @Nullable Player getFakePlayer(@NotNull CommandSender creator, @NotNull String name) { @@ -69,6 +75,12 @@ public class FakePlayerManager { return fake; } + /** + * 根据名称获取假人 + * + * @param name 名称 + * @return 假人 + */ public @Nullable Player getFakePlayer(@NotNull String name) { var player = Bukkit.getServer().getPlayer(name); if (player == null) { @@ -82,6 +94,12 @@ public class FakePlayerManager { return player; } + /** + * 移除指定创建者创建的假人 + * + * @param creator 创建者 + * @return 移除假人的数量 + */ public int removeFakePlayers(@NotNull Player creator) { var fakes = getFakePlayers(creator); for (var f : fakes) { @@ -90,8 +108,14 @@ public class FakePlayerManager { return fakes.size(); } + /** + * 获取一个假人的创建者, 如果这个玩家不是假人, 则为 {@code null} + * + * @param fakePlayer 假人 + * @return 假人的创建者 + */ public @Nullable String getCreator(@NotNull Player fakePlayer) { - var meta = fakePlayer.getMetadata(FakePlayerSpawner.META_KEY_CREATOR); + var meta = fakePlayer.getMetadata(META_KEY_CREATOR); if (meta.isEmpty()) { return null; } @@ -99,6 +123,11 @@ public class FakePlayerManager { return meta.get(0).asString(); } + /** + * 移除所有假人 + * + * @return 移除的假人数量 + */ public int removeFakePlayers() { var fakes = getFakePlayers(); for (var f : fakes) { @@ -107,29 +136,44 @@ public class FakePlayerManager { return fakes.size(); } + /** + * @return 获取所有假人 + */ public @NotNull List getFakePlayers() { return Bukkit .getServer() .getOnlinePlayers() .stream() - .filter(p -> !p.getMetadata(FakePlayerSpawner.META_KEY_CREATOR).isEmpty()) + .filter(p -> !p.getMetadata(META_KEY_CREATOR).isEmpty()) .collect(Collectors.toList()); } + /** + * 获取创建者创建的所有假人 + * + * @param creator 创建者 + * @return 创建者创建的假人 + */ public @NotNull List getFakePlayers(@NotNull CommandSender creator) { var name = creator.getName(); return Bukkit .getServer() .getOnlinePlayers() .stream() - .filter(p -> p.getMetadata(FakePlayerSpawner.META_KEY_CREATOR) + .filter(p -> p.getMetadata(META_KEY_CREATOR) .stream() .anyMatch(meta -> meta.asString().equals(name))) .collect(Collectors.toList()); } + /** + * 判断一名玩家是否是假人 + * + * @param player 玩家 + * @return 是否是假人 + */ public boolean isFakePlayer(@NotNull Player player) { - return !player.getMetadata(FakePlayerSpawner.META_KEY_CREATOR).isEmpty(); + return !player.getMetadata(META_KEY_CREATOR).isEmpty(); } diff --git a/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerSpawner.java b/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerSpawner.java deleted file mode 100644 index 1f560fc..0000000 --- a/src/main/java/io/github/hello09x/fakeplayer/manager/FakePlayerSpawner.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.github.hello09x.fakeplayer.manager; - -import com.mojang.authlib.GameProfile; -import io.github.hello09x.fakeplayer.Main; -import io.github.hello09x.fakeplayer.core.EmptyNetworkManager; -import net.kyori.adventure.sound.Sound; -import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R1.CraftServer; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerRespawnEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.metadata.FixedMetadataValue; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -import static org.bukkit.Sound.ENTITY_ENDERMAN_TELEPORT; - -public class FakePlayerSpawner { - - public final static String META_KEY_CREATOR = "fakeplayer::creator"; - -// public static @NotNull Player spawn( -// @NotNull UUID uniqueId, -// @NotNull String name, -// @NotNull Player creator, -// @NotNull Location at -// ) { -// var craftServer = (CraftServer) Bukkit.getServer(); -// var server = craftServer.getServer(); -// var gameProfile = new GameProfile(uniqueId, name); -// var world = ((CraftWorld) at.getWorld()).getHandle(); -// -// var entityPlayer = new EntityPlayer(server, world, gameProfile); -// var craftPlayer = entityPlayer.getBukkitEntity(); -// -// -// entityPlayer.f = 0; // ping -// entityPlayer.c = new PlayerConnection( -// server, -// new EmptyNetworkManager(EnumProtocolDirection.a), -// entityPlayer -// ); -// -// craftPlayer.setFirstPlayed(System.currentTimeMillis()); -// craftServer.getHandle().respawn( -// entityPlayer, -// true, -// PlayerRespawnEvent.RespawnReason.PLUGIN -// ); -// refreshTabList(entityPlayer); -// -// var holder = Objects.requireNonNull(Bukkit.getPlayer(uniqueId)); -// holder.setViewDistance(9); -// holder.setAffectsSpawning(true); -// holder.setSleepingIgnored(true); -// holder.setPersistent(false); -// holder.setGameMode(GameMode.CREATIVE); -// holder.setAllowFlight(true); -// holder.setMetadata(META_KEY_CREATOR, new FixedMetadataValue(Main.getInstance(), creator.getUniqueId().toString())); -// holder.teleport(at, PlayerTeleportEvent.TeleportCause.SPECTATE); -// creator.playSound(Sound.sound( -// ENTITY_ENDERMAN_TELEPORT.key(), -// Sound.Source.PLAYER, -// 1.0F, -// 1.0F -// )); -// -// CompletableFuture.runAsync(() -> { -// System.out.println(holder); -// System.out.println(craftPlayer); -// }); -// return holder; -// } -// -// private static void refreshTabList(EntityPlayer player) { -// try { -// var field = EntityPlayer.class.getDeclaredField("cU"); -// field.setAccessible(true); -// field.set(player, true); -// } catch (NoSuchFieldException | IllegalAccessException e) { -// throw new RuntimeException(e); -// } -// -// for (var online : Bukkit.getOnlinePlayers()) { -// ((CraftPlayer) online).getHandle().c.a(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.a.a, player)); //ADD_PLAYER -// ((CraftPlayer) online).getHandle().c.a(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.a.d, player)); //UPDATE_LISTED -// } -// } - - -} diff --git a/src/main/java/io/github/hello09x/fakeplayer/util/ReflectionUtils.java b/src/main/java/io/github/hello09x/fakeplayer/util/ReflectionUtils.java new file mode 100644 index 0000000..5213ce7 --- /dev/null +++ b/src/main/java/io/github/hello09x/fakeplayer/util/ReflectionUtils.java @@ -0,0 +1,26 @@ +package io.github.hello09x.fakeplayer.util; + +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class ReflectionUtils { + + private final static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + public static @Nullable Field getFirstFieldByType(Class clazz, Class fieldType, boolean includeStatic) { + for (var field : clazz.getDeclaredFields()) { + if (includeStatic ^ Modifier.isStatic(field.getModifiers())) { + continue; + } + if (field.getType() == fieldType) { + field.setAccessible(true); + return field; + } + } + return null; + } + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 84eb6c7..232c99e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,6 +12,9 @@ commands: permissions: fakeplayer: - default: true - fakeplayer.admin: + description: '假人基础权限' + default: op + + fakeplayer.admin: + description: '假人管理员权限' default: op