Util Reflection: Change PlayerNameTag with 24 lines of code. *Without Scoreboards*

Discussion in 'Resources' started by Orange Tabby, Dec 27, 2016.

?

Was this helpful?

  1. Yes!

    3 vote(s)
    50.0%
  2. No.

    0 vote(s)
    0.0%
  3. I <3 Kitties!

    3 vote(s)
    50.0%
Thread Status:
Not open for further replies.
  1. Offline

    Orange Tabby

    Hello, I've been trying to figure out an easy way to change the player's name tag without an API all day. And I've finally figured out a simple way to do so. Since I couldn't find anything like this on google, I thought I should share it.

    Sorry if my reflections are bad, I'm new to reflections.

    Note: Currently only works in 1.8.8, will fix :D

    Code:
    public static void changeName(String name, Player player) {
            try {
                Method getHandle = player.getClass().getMethod("getHandle", (Class<?>[]) null);
                Object entityPlayer = getHandle.invoke(player);
                Class<?> entityHuman = entityPlayer.getClass().getSuperclass();
                Field bH = entityHuman.getDeclaredField("bH");
                bH.setAccessible(true);
                bH.set(entityPlayer, new GameProfile(player.getUniqueId(), name));
                for (Player players : Bukkit.getOnlinePlayers()) {
                    players.hidePlayer(player);
                    players.showPlayer(player);
                }
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    Some awesome people submitted more "nameChanging" methods for other Minecraft versions below in the comments:
     
    Last edited: Dec 29, 2016
    ChipDev likes this.
  2. Offline

    PumpMelon

    Nice, maybe you can turn it into an API?
     
  3. Online

    timtower Administrator Administrator Moderator

    An API for 24 lines of code?
     
    VinexAx789 and DoggyCodeâ„¢ like this.
  4. Offline

    PumpMelon

    Well it most likely doesn't work for other versions of minecraft, as I've seen what it took to make it for 1.10 and other versions.
     
  5. Online

    timtower Administrator Administrator Moderator

    @PumpMelon That is why it is reflection and only 1 method.
    If you want it multi version then you are talking about abstraction.
     
  6. @PumpMelon @timtower
    This is actually really easy to do in later versions. Only thing you need to take into consideration is that the bH field has been renamed bS in 1.9 and above. So the following code works from 1.8.8 to 1.11.2 (tested versions, might work with earlier ones too):
    Code:java
    1. public static void changeName(String name, Player player) {
    2. try {
    3. Method getHandle = player.getClass().getMethod("getHandle");
    4. Object entityPlayer = getHandle.invoke(player);
    5. Class<?> entityHuman = entityPlayer.getClass().getSuperclass();
    6. Field gameProfileField;
    7. int majVersion = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3].replaceAll("(v|R[0-9]+)", "").split("_")[0]);
    8. int minVersion = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3].replaceAll("(v|R[0-9]+)", "").split("_")[1]);
    9. if (majVersion >= 1 && minVersion >= 9) {
    10. gameProfileField = entityHuman.getDeclaredField("bS");
    11. } else {
    12. gameProfileField = entityHuman.getDeclaredField("bH");
    13. }
    14. gameProfileField.setAccessible(true);
    15. gameProfileField.set(entityPlayer, new GameProfile(player.getUniqueId(), name));
    16. for (Player players : Bukkit.getOnlinePlayers()) {
    17. players.hidePlayer(player);
    18. players.showPlayer(player);
    19. }
    20. e.printStackTrace();
    21. }
    22. }
    One thing to be mindful of with this technique is that it has some perhaps unwanted sideeffects..
    [​IMG]
    [​IMG]
    The reason this method is not very popular is that it's not just the name above the player's head that gets changed, which is why most people used TagAPI (which is sadly discontinued). TagAPI instead modified outgoing packets to edit names, which required a lot more work, but had the bonus of only changing the name above the player's head.
     
    ChipDev likes this.
  7. Offline

    Zombie_Striker

    @AlvinB
    Although that could work, and solved the multi-version issue, there is an easier way of getting the gameprofile instance.

    @Orange Tabby
    Instead of accessing the field, I just used the method CraftPlayer#getProfile() which will work for all 1.7.8+ (?) servers (though 1.7 servers will not be affected since they still rely on the old CraftPlayer.name field)

    Code:java
    1. /**
    2.   * Only works in 1.8+.
    3.   *
    4.   * @param name
    5.   * @param player
    6.   */
    7. public static void changeName(String name, Player player) {
    8. try {
    9. Method getHandle = player.getClass().getMethod("getHandle",
    10. (Class<?>[]) null);
    11. // Object entityPlayer = getHandle.invoke(player);
    12. // Class<?> entityHuman = entityPlayer.getClass().getSuperclass();
    13. /**
    14.   * These methods are no longer needed, as we can just access the
    15.   * profile using handle.getProfile. Also, because we can just use
    16.   * the method, which will not change, we don't have to do any
    17.   * field-name look-ups.
    18.   */
    19. try {
    20. Class.forName("com.mojang.authlib.GameProfile");
    21. // By having the line above, only 1.8+ servers will run this.
    22. } catch (ClassNotFoundException e) {
    23. /**
    24.   * Currently, there is no field that can be easily modified for
    25.   * lower versions. The "name" field is final, and cannot be
    26.   * modified in runtime. The only workaround for this that I can
    27.   * think of would be if the server creates a "dummy" entity that
    28.   * takes in the player's input and plays the player's animations
    29.   * (which will be a lot more lines)
    30.   */
    31. Bukkit.broadcastMessage("CHANGE NAME METHOD DOES NOT WORK IN 1.7 OR LOWER!");
    32. return;
    33. }
    34. Object profile = getHandle.invoke(player).getClass()
    35. .getMethod("getProfile")
    36. .invoke(getHandle.invoke(player));
    37. Field ff = profile.getClass().getDeclaredField("name");
    38. ff.setAccessible(true);
    39. ff.set(profile, name);
    40. for (Player players : Bukkit.getOnlinePlayers()) {
    41. players.hidePlayer(player);
    42. players.showPlayer(player);
    43. }
    44. /**
    45.   * Merged all the exceptions. Less lines
    46.   */
    47. e.printStackTrace();
    48. }
    49. }
    21 lines (if you do not include formatted lines and comments)
     
    Last edited: Dec 27, 2016
    ChipDev likes this.
  8. @Zombie_Striker
    You can change final fields. Field#setAccessible(true) does precisely that (unless the field is static, then it gets a bit more hacky..). I would do it, but I don't have any bukkit/spigot releases since before 1.8 (BuildTools doesn't let you download them).
     
  9. Offline

    Zombie_Striker

    @AlvinB
    Okay, tried it with this:
    Code:
      Field f = entityHuman.getDeclaredField("name");
               f.setAccessible(true);
               f.set(entityHuman, name);
    
    But it spits out an exception at the "f.set" line:
     
  10. @Zombie_Striker
    Well, entityHuman is the class object, entityPlayer is the actual instance of the object.
     
  11. Offline

    VinexAx789

    @Zombie_Striker
    I did check much of the code over because my phone is acting up, will do when I get home though. Anyway I'm wondering if you can use this to essentially hide the nametag completely?
     
  12. Offline

    Zombie_Striker

    @VinexAx789
    For 1.8+, yes. Just input a space (don't know if it can take a string with length 0) to 'remove' it.
     
  13. Offline

    VinexAx789

  14. @Zombie_Striker
    Managed to dig up some old craftbukkit releases from a couple older computers, and after a bit of tinkering, I came up with this code:
    Code:java
    1. /*
    2.   * Works from 1.0+.
    3.   *
    4.   * @param name new name of the player
    5.   * @param player player to change the name of
    6.   */
    7. @SuppressWarnings("unchecked")
    8. public static void changeName(String name, Player player) {
    9. try {
    10. Method getHandle = player.getClass().getMethod("getHandle");
    11. Object entityPlayer = getHandle.invoke(player);
    12. /*
    13.   * These methods are no longer needed, as we can just access the
    14.   * profile using handle.getProfile. Also, because we can just use
    15.   * the method, which will not change, we don't have to do any
    16.   * field-name look-ups.
    17.   */
    18. boolean gameProfileExists = false;
    19. // Some 1.7 versions had the GameProfile class in a different package
    20. try {
    21. Class.forName("net.minecraft.util.com.mojang.authlib.GameProfile");
    22. gameProfileExists = true;
    23. } catch (ClassNotFoundException ignored) {
    24.  
    25. }
    26. try {
    27. Class.forName("com.mojang.authlib.GameProfile");
    28. gameProfileExists = true;
    29. } catch (ClassNotFoundException ignored) {
    30.  
    31. }
    32. if (!gameProfileExists) {
    33. /*
    34.   * Only 1.6 and lower servers will run this code.
    35.   *
    36.   * In these versions, the name wasn't stored in a GameProfile object,
    37.   * but as a String in the (final) name field of the EntityHuman class.
    38.   * Final (non-static) fields can actually be modified by using
    39.   * {@link java.lang.reflect.Field#setAccessible(boolean)}
    40.   */
    41. Field nameField = entityPlayer.getClass().getSuperclass().getDeclaredField("name");
    42. nameField.setAccessible(true);
    43. nameField.set(entityPlayer, name);
    44. } else {
    45. // Only 1.7+ servers will run this code
    46. Object profile = entityPlayer.getClass().getMethod("getProfile").invoke(entityPlayer);
    47. Field ff = profile.getClass().getDeclaredField("name");
    48. ff.setAccessible(true);
    49. ff.set(profile, name);
    50. }
    51. // In older versions, Bukkit.getOnlinePlayers() returned an Array instead of a Collection.
    52. if (Bukkit.class.getMethod("getOnlinePlayers", new Class<?>[0]).getReturnType() == Collection.class) {
    53. Collection<? extends Player> players = (Collection<? extends Player>) Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
    54. for (Player p : players) {
    55. p.hidePlayer(player);
    56. p.showPlayer(player);
    57. }
    58. } else {
    59. Player[] players = ((Player[]) Bukkit.class.getMethod("getOnlinePlayers").invoke(null));
    60. for (Player p : players) {
    61. p.hidePlayer(player);
    62. p.showPlayer(player);
    63. }
    64. }
    65. /*
    66.   * Merged all the exceptions. Less lines
    67.   */
    68. e.printStackTrace();
    69. }
    70. }
    Not quite 24 lines anymore, but it works all the way from 1.0 to 1.11.2 (maybe even earlier, but the earliest craftbukkit jar I had was 1.0)
     
Thread Status:
Not open for further replies.

Share This Page