[Anleitung] Spieler Nicken - Name und Skin ändern - 1.8.8

Hier könnt ihr anderen Leuten helfen, indem ihr Anleitungen oder praktische Codesegmente zur Verfügung stellt.

[Anleitung] Spieler Nicken - Name und Skin ändern - 1.8.8

Beitragvon Jofkos » Mo 5. Jan 2015, 23:09

Geupdatet:
  • 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
  1. private static Field modifiers = getField(Field.class, "modifiers");
  2. private static Field actionField = getField(PacketPlayOutPlayerInfo.class, "a");
  3. private static Field dataField = getField(PacketPlayOutPlayerInfo.class, "b");
  4. private static Field nameField = getField(GameProfile.class, "name");
  5. private static Field uuidField = getField(GameProfile.class, "id");
  6. private static Field getField(Class<?> clazz, String name) {
  7.    try {
  8.       Field field = clazz.getDeclaredField(name);
  9.       field.setAccessible(true);
  10.       
  11.       if (Modifier.isFinal(field.getModifiers())) {
  12.          modifiers.set(field, field.getModifiers() & ~Modifier.FINAL);
  13.       }
  14.       
  15.       return field;
  16.    } catch (Exception e) {
  17.       e.printStackTrace();
  18.    }
  19.    return null;
  20. }


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
  1. private static ExecutorService pool = Executors.newCachedThreadPool();
mit diesem können wir über pool.execute(Runnable runnable) Runnables ausführen, und er kümmert sich um die Threads.
So, jetzt brauchen wir eine Methode, welche bei mir so aussieht:
Code: Alles auswählen
  1.    public static void nick(Player player, String name) {
  2.       pool.execute(new Runnable() {
  3.          @Override
  4.          public void run() {
  5.             try {
  6.             } catch (Exception e) {
  7.                e.printStackTrace();
  8.             }
  9.          }
  10.       });
  11.    }
GameProfile-Fetchen und ChatColors handeln
Code: Alles auswählen
  1. GameProfile prof = GameProfileBuilder.fetch(UUIDFetcher.getUUID(ChatColor.stripColor(name)));
  2. nameField.set(prof, name);
Am einfachsten ist es, wenn das neue Profil die gleiche UUID wie der Spieler hat, da der Server in den Spawn Packets dem Spieler einfach die UUID mitteilt.
Code: Alles auswählen
  1. uuidField.set(prof, player.getUniqueId());
EntityPlayer vom Spieler in einer lokalen Variable speichern
Code: Alles auswählen
  1. EntityPlayer entity = ((CraftPlayer) p).getHandle();
Jetzt gehts los mit den Packets.
Wir müssen sie folgendermassen senden: EntityDestroy -> TabListRemove -> TabListAddNeu -> NamedEntitySpawn
DespawnPacket
Code: Alles auswählen
  1. PacketPlayOutEntityDestroy despawn = new PacketPlayOutEntityDestroy(entity.getId());
Für die Info-Packets habe ich mir eine extra Methode gemacht, da sie zweimal gebraucht wird. Die Methode setzt einfach die zwei Felder des Packets, die ich statisch gespeichert habe (siehe oben). (a -> EnumPlayerInfoAction, b -> List<PlayerInfoData>)
Code: Alles auswählen
  1. private static PacketPlayOutPlayerInfo setInfo(PacketPlayOutPlayerInfo packet, EnumPlayerInfoAction action, PlayerInfoData... data) {
  2.    try {
  3.       actionField.set(packet, action);
  4.       dataField.set(packet, Arrays.asList(data));
  5.    } catch (Exception e) {
  6.       e.printStackTrace();
  7.    }
  8.    return packet;
  9. }
Jetzt kommt das TabList Remove Packet. Hier reicht es das Packet und die UUID zu übergeben.
Code: Alles auswählen
  1. PacketPlayOutPlayerInfo removeProfile = new PacketPlayOutPlayerInfo();
  2. setInfo(removeProfile, EnumPlayerInfoAction.REMOVE_PLAYER, removeProfile.new PlayerInfoData(entity.getProfile(), -1, null, null));
Jetzt müssen wir das TabList Packet mit dem neuen GameProfile erstellen. Hier übergeben wir TabListen Informationen inklusive GameProfile (Skin)
Code: Alles auswählen
  1. PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo();
  2. setInfo(info, EnumPlayerInfoAction.ADD_PLAYER, info.new PlayerInfoData(prof, entity.ping, entity.playerInteractManager.getGameMode(), CraftChatMessage.fromString(name)[0]));
So, nun müssen wir nur noch das Packet um den neuen Spieler zu spawnen erstellen
Code: Alles auswählen
  1. 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
  1. Set<PlayerConnection> players = Bukkit.getOnlinePlayers()
  2.       .stream()
  3.          .filter(Predicate.isEqual(player).negate())
  4.          .map(CraftPlayer.class::cast)
  5.          .map(CraftPlayer::getHandle)
  6.          .map(p -> p.playerConnection)
  7.          .collect(Collectors.toSet()
  8.       );
  9. players.forEach(c -> c.sendPacket(despawn));
  10. players.forEach(c -> c.sendPacket(removeProfile));
  11. synchronized (this) {
  12.    wait(125L);
  13. }
  14. players.forEach(c -> c.sendPacket(info));
  15. players.forEach(c -> c.sendPacket(spawn));

Das wars auch schon!

Hier nochmal die ganze Methode zur übersicht:
Code: Alles auswählen
  1. private static ExecutorService pool = Executors.newCachedThreadPool();
  2. private static Field modifiers = getField(Field.class, "modifiers");
  3. private static Field actionField = getField(PacketPlayOutPlayerInfo.class, "a");
  4. private static Field dataField = getField(PacketPlayOutPlayerInfo.class, "b");
  5. private static Field nameField = getField(GameProfile.class, "name");
  6. private static Field uuidField = getField(GameProfile.class, "id");
  7. public static void nick(Player player, String name) {
  8.    pool.execute(new Runnable() {
  9.       @Override
  10.       public void run() {
  11.          try {
  12.             GameProfile prof = GameProfileBuilder.fetch(UUIDFetcher.getUUID(ChatColor.stripColor(name)));
  13.             nameField.set(prof, name);
  14.             uuidField.set(prof, player.getUniqueId());
  15.             EntityPlayer entity = ((CraftPlayer) player).getHandle();
  16.             PacketPlayOutEntityDestroy despawn = new PacketPlayOutEntityDestroy(entity.getId());
  17.             PacketPlayOutPlayerInfo removeProfile = new PacketPlayOutPlayerInfo();
  18.             setInfo(removeProfile, EnumPlayerInfoAction.REMOVE_PLAYER, removeProfile.new PlayerInfoData(entity.getProfile(), -1, null, null));
  19.             PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo();
  20.             setInfo(info, EnumPlayerInfoAction.ADD_PLAYER, info.new PlayerInfoData(prof, entity.ping, entity.playerInteractManager.getGameMode(), CraftChatMessage.fromString(name)[0]));
  21.             PacketPlayOutNamedEntitySpawn spawn = new PacketPlayOutNamedEntitySpawn(entity);
  22.             Set<PlayerConnection> players = Bukkit.getOnlinePlayers()
  23.                   .stream()
  24.                      .filter(Predicate.isEqual(player).negate())
  25.                      .map(CraftPlayer.class::cast)
  26.                      .map(CraftPlayer::getHandle)
  27.                      .map(p -> p.playerConnection)
  28.                      .collect(Collectors.toSet()
  29.                   );
  30.             players.forEach(c -> c.sendPacket(despawn));
  31.             players.forEach(c -> c.sendPacket(removeProfile));
  32.             synchronized (this) {
  33.                wait(125L);
  34.             }
  35.             players.forEach(c -> c.sendPacket(info));
  36.             players.forEach(c -> c.sendPacket(spawn));
  37.          } catch (Exception e) {
  38.             e.printStackTrace();
  39.          }
  40.       }
  41.    });
  42. }
  43. private static PacketPlayOutPlayerInfo setInfo(PacketPlayOutPlayerInfo packet, EnumPlayerInfoAction action, PlayerInfoData... data) {
  44.    try {
  45.       actionField.set(packet, action);
  46.       dataField.set(packet, Arrays.asList(data));
  47.    } catch (Exception e) {
  48.       e.printStackTrace();
  49.    }
  50.    return packet;
  51. }
  52. private static Field getField(Class<?> clazz, String name) {
  53.    try {
  54.       Field field = clazz.getDeclaredField(name);
  55.       field.setAccessible(true);
  56.       if (Modifier.isFinal(field.getModifiers())) {
  57.          modifiers.set(field, field.getModifiers() & ~Modifier.FINAL);
  58.       }
  59.       return field;
  60.    } catch (Exception e) {
  61.       e.printStackTrace();
  62.    }
  63.    return null;
  64. }


Ich hoffe, dass sich nirgendwo Fehler eingeschlichen haben. Ich habe das ganze mit dem neusten Spigot-Build getestet.
Zuletzt geändert von Jofkos am Fr 13. Nov 2015, 21:16, insgesamt 7-mal geändert.
Jofkos

...........

..Bild
Benutzeravatar
Jofkos
 
Beiträge: 1537
Registriert: So 16. Jun 2013, 22:45

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon ilouHD » Mo 5. Jan 2015, 23:15

Dankeeee. Werde es sehr gut gebrauchen können.
Bild
Benutzeravatar
ilouHD
 
Beiträge: 1733
Registriert: Do 9. Jan 2014, 14:49

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Twister_21 » Di 6. Jan 2015, 09:17

Auch danke.

Kann man das auch hinkriegen, sodass es für die 1.7 und die 1.8 funktioniert? Also sodass die 1.7 und die 1.8 User mit der Spigot-Protcolhack-Version die NameTags sehen?
Mit freundlichen Grüßen
Twister21
Benutzeravatar
Twister_21
 
Beiträge: 652
Registriert: Mi 11. Jun 2014, 05:51
Wohnort: Deutschland

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Jofkos » Di 6. Jan 2015, 16:04

Twister_21 hat geschrieben:Auch danke.

Kann man das auch hinkriegen, sodass es für die 1.7 und die 1.8 funktioniert? Also sodass die 1.7 und die 1.8 User mit der Spigot-Protcolhack-Version die NameTags sehen?

Die API ist in der Spigot Protocolhack-Version anders aufgebaut, aber ja, es funktioniertauch da schon.
Jofkos

...........

..Bild
Benutzeravatar
Jofkos
 
Beiträge: 1537
Registriert: So 16. Jun 2013, 22:45

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Twister_21 » Di 6. Jan 2015, 17:29

Also sodass die 1.7 und die 1.8 User den gesetzten String sehen? Dann können wir Mivcon doch auf 1.7 und 1.8 lassen. Ist ziemlich kompliziert, dass das Wichtigste für jede Version funktioniert.

Die EnumPlayerInfoAction kann man z.B nicht importieren.

Kann man denn die Methode so machen, dass sie für die 1.7 und 1.8 funktioniert?
Mit freundlichen Grüßen
Twister21
Benutzeravatar
Twister_21
 
Beiträge: 652
Registriert: Mi 11. Jun 2014, 05:51
Wohnort: Deutschland

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Tyubin » Di 6. Jan 2015, 23:56

Hey, kann ich den Spieler auch irgendwie direkt beim Login nicken, wegen den 50 Millisekunden ist das ja nicht möglich?
Benutzeravatar
Tyubin
 
Beiträge: 74
Registriert: Mo 11. Aug 2014, 02:56

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon ilouHD » Mi 7. Jan 2015, 06:52

coolirobin hat geschrieben:Hey, kann ich den Spieler auch irgendwie direkt beim Login nicken, wegen den 50 Millisekunden ist das ja nicht möglich?


PlayerLoginEvent sollte funktionieren

Oder

PlayerPreLoginEvent, oder wie das heist
Bild
Benutzeravatar
ilouHD
 
Beiträge: 1733
Registriert: Do 9. Jan 2014, 14:49

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Jofkos » Mi 7. Jan 2015, 12:17

coolirobin hat geschrieben:Hey, kann ich den Spieler auch irgendwie direkt beim Login nicken, wegen den 50 Millisekunden ist das ja nicht möglich?

Na klar! Es wird ja, mehr oder weniger, instant das destroyPacket gesendet. Wenn er joint, sieht man ihn halt etwas verzögert.
Jofkos

...........

..Bild
Benutzeravatar
Jofkos
 
Beiträge: 1537
Registriert: So 16. Jun 2013, 22:45

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon Twister_21 » Mi 7. Jan 2015, 15:02

#push
Es sind zwar erst 22 Stunden vorbei, aber ich hoffe das ist nicht zu schlimm. Wie oben hab ich die Frage, ob und wie man das für die 1.7 und 1.8 machen kann. Beim Spigot-Protocolhack können ja 1.7 Spieler und 1.8 Spieler joinen. Die sollen dann aber alle einen Tag sehen. Mit dieser Methode geht es ja für die 1.8, wie macht man das aber auch für die 1.7? Also sodass es für die 1.7 Spieler und die 1.8 Spieler möglich ist den gesetzten Tag zu sehen. Das ist so dringend, weil das ein wichtiger Punkt ist um den Server wiedereröffnen zu können.
Mit freundlichen Grüßen
Twister21
Benutzeravatar
Twister_21
 
Beiträge: 652
Registriert: Mi 11. Jun 2014, 05:51
Wohnort: Deutschland

Re: Spieler Nicken - Name und Skin ändern - 1.8+

Beitragvon IK_Raptor » Mi 7. Jan 2015, 16:37

1.8 Spielern musst du dann die neuen Packets senden während du für 1.7 Spieler die bereits existierenden Methoden verwenden kannst. Je nachdem wie gut du dich auskennst sehe ich es auf Dauer als sinnvoller an sowieso auf 1.8 upzudaten.
Benutzeravatar
IK_Raptor
 
Beiträge: 609
Registriert: Mo 12. Aug 2013, 15:37

Nächste

Zurück zu Anleitungen

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast

cron