- Java 8
- Spigot 1.8.8
- "kleiner" Logik Fehler: die UUID sollte im GameProfile bzw. Tablist Packet gesetz werden und nicht im Spawn Packet (Respawn, Death, etc)
Ich erkläre hier extra alles, also wäre es nett sich das auch durchzulesen, und nicht einfach alles zu kopieren.
Also zum start: Ich werde hier meinen GameProfileBuilder und meinen UUIDFetcher auf der neusten Version nutzen. Die habe ich gerade nochmals geupdatet.
Ihr könnt natürlich nutzen was ihr wollt, auch wenn ich euch meine empfehle.
Genutzte Methoden/Variablen:
- Code: Alles auswählen
- private static Field modifiers = getField(Field.class, "modifiers");
- private static Field actionField = getField(PacketPlayOutPlayerInfo.class, "a");
- private static Field dataField = getField(PacketPlayOutPlayerInfo.class, "b");
- private static Field nameField = getField(GameProfile.class, "name");
- private static Field uuidField = getField(GameProfile.class, "id");
- private static Field getField(Class<?> clazz, String name) {
- try {
- Field field = clazz.getDeclaredField(name);
- field.setAccessible(true);
- if (Modifier.isFinal(field.getModifiers())) {
- modifiers.set(field, field.getModifiers() & ~Modifier.FINAL);
- }
- return field;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
Zum Anfang sei noch gesagt: Ich werde die Klasse statisch machen, da es, so wie ich sie aufgebaut habe, keine Notwendigkeit gibt irgendwelche instanzspezifischen Variablen zu speichern.
Das ist euch natürlich selbst überlassen. Also, los!
Da wir das alles Asynchron Ausführen, werde ich das mit einem ThreadPool tun
- Code: Alles auswählen
- private static ExecutorService pool = Executors.newCachedThreadPool();
So, jetzt brauchen wir eine Methode, welche bei mir so aussieht:
- Code: Alles auswählen
- public static void nick(Player player, String name) {
- pool.execute(new Runnable() {
- @Override
- public void run() {
- try {
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- Code: Alles auswählen
- GameProfile prof = GameProfileBuilder.fetch(UUIDFetcher.getUUID(ChatColor.stripColor(name)));
- nameField.set(prof, name);
- Code: Alles auswählen
- uuidField.set(prof, player.getUniqueId());
- Code: Alles auswählen
- EntityPlayer entity = ((CraftPlayer) p).getHandle();
Wir müssen sie folgendermassen senden: EntityDestroy -> TabListRemove -> TabListAddNeu -> NamedEntitySpawn
DespawnPacket
- Code: Alles auswählen
- PacketPlayOutEntityDestroy despawn = new PacketPlayOutEntityDestroy(entity.getId());
- Code: Alles auswählen
- private static PacketPlayOutPlayerInfo setInfo(PacketPlayOutPlayerInfo packet, EnumPlayerInfoAction action, PlayerInfoData... data) {
- try {
- actionField.set(packet, action);
- dataField.set(packet, Arrays.asList(data));
- } catch (Exception e) {
- e.printStackTrace();
- }
- return packet;
- }
- Code: Alles auswählen
- PacketPlayOutPlayerInfo removeProfile = new PacketPlayOutPlayerInfo();
- setInfo(removeProfile, EnumPlayerInfoAction.REMOVE_PLAYER, removeProfile.new PlayerInfoData(entity.getProfile(), -1, null, null));
- Code: Alles auswählen
- PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo();
- setInfo(info, EnumPlayerInfoAction.ADD_PLAYER, info.new PlayerInfoData(prof, entity.ping, entity.playerInteractManager.getGameMode(), CraftChatMessage.fromString(name)[0]));
- Code: Alles auswählen
- PacketPlayOutNamedEntitySpawn spawn = new PacketPlayOutNamedEntitySpawn(entity);
Jetzt wo wir alle haben, müssen wir sie nur noch allen Spielern senden. Bei mir hat es allerdings nur geklappt, wenn ich ~100 Millisekunden warte zwischen dem Senden der De-Spawn (Destroy und TabList) und Respawn-Packete (TabList und NamedEntitySpawn).
Um an alle PlayerConnections zu kommen habe ich den Java-8 Stream verwendet
- Code: Alles auswählen
- Set<PlayerConnection> players = Bukkit.getOnlinePlayers()
- .stream()
- .filter(Predicate.isEqual(player).negate())
- .map(CraftPlayer.class::cast)
- .map(CraftPlayer::getHandle)
- .map(p -> p.playerConnection)
- .collect(Collectors.toSet()
- );
- players.forEach(c -> c.sendPacket(despawn));
- players.forEach(c -> c.sendPacket(removeProfile));
- synchronized (this) {
- wait(125L);
- }
- players.forEach(c -> c.sendPacket(info));
- players.forEach(c -> c.sendPacket(spawn));
Das wars auch schon!
Hier nochmal die ganze Methode zur übersicht:
- Code: Alles auswählen
- private static ExecutorService pool = Executors.newCachedThreadPool();
- private static Field modifiers = getField(Field.class, "modifiers");
- private static Field actionField = getField(PacketPlayOutPlayerInfo.class, "a");
- private static Field dataField = getField(PacketPlayOutPlayerInfo.class, "b");
- private static Field nameField = getField(GameProfile.class, "name");
- private static Field uuidField = getField(GameProfile.class, "id");
- public static void nick(Player player, String name) {
- pool.execute(new Runnable() {
- @Override
- public void run() {
- try {
- GameProfile prof = GameProfileBuilder.fetch(UUIDFetcher.getUUID(ChatColor.stripColor(name)));
- nameField.set(prof, name);
- uuidField.set(prof, player.getUniqueId());
- EntityPlayer entity = ((CraftPlayer) player).getHandle();
- PacketPlayOutEntityDestroy despawn = new PacketPlayOutEntityDestroy(entity.getId());
- PacketPlayOutPlayerInfo removeProfile = new PacketPlayOutPlayerInfo();
- setInfo(removeProfile, EnumPlayerInfoAction.REMOVE_PLAYER, removeProfile.new PlayerInfoData(entity.getProfile(), -1, null, null));
- PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo();
- setInfo(info, EnumPlayerInfoAction.ADD_PLAYER, info.new PlayerInfoData(prof, entity.ping, entity.playerInteractManager.getGameMode(), CraftChatMessage.fromString(name)[0]));
- PacketPlayOutNamedEntitySpawn spawn = new PacketPlayOutNamedEntitySpawn(entity);
- Set<PlayerConnection> players = Bukkit.getOnlinePlayers()
- .stream()
- .filter(Predicate.isEqual(player).negate())
- .map(CraftPlayer.class::cast)
- .map(CraftPlayer::getHandle)
- .map(p -> p.playerConnection)
- .collect(Collectors.toSet()
- );
- players.forEach(c -> c.sendPacket(despawn));
- players.forEach(c -> c.sendPacket(removeProfile));
- synchronized (this) {
- wait(125L);
- }
- players.forEach(c -> c.sendPacket(info));
- players.forEach(c -> c.sendPacket(spawn));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- private static PacketPlayOutPlayerInfo setInfo(PacketPlayOutPlayerInfo packet, EnumPlayerInfoAction action, PlayerInfoData... data) {
- try {
- actionField.set(packet, action);
- dataField.set(packet, Arrays.asList(data));
- } catch (Exception e) {
- e.printStackTrace();
- }
- return packet;
- }
- private static Field getField(Class<?> clazz, String name) {
- try {
- Field field = clazz.getDeclaredField(name);
- field.setAccessible(true);
- if (Modifier.isFinal(field.getModifiers())) {
- modifiers.set(field, field.getModifiers() & ~Modifier.FINAL);
- }
- return field;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
Ich hoffe, dass sich nirgendwo Fehler eingeschlichen haben. Ich habe das ganze mit dem neusten Spigot-Build getestet.