Packet Question

Discussion in 'Plugin Development' started by xWatermelon, Jun 16, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    xWatermelon

    I am trying to make it so a player will receive a loading bar using the boss health bar. I saw in another thread that you can use packets (http://wiki.vg/Protocol#Spawn_Mob_.280x18.29) to fake a mob spawn, but I do not know how to change the entity's custom name to say "Reloading..". I also don't know how to make the bar slowly fill up (would changing the entities health update it on the boss health bar? How can I get the entity?). Any help appreciated!

    Bump.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 2, 2016
  2. Offline

    Comphenix

    This is fairly easy if you use ProtocolLib and copy in the following classes from PacketWrapper into your project:
    Then you can do this (download):
    Code:java
    1. public class SpawnFakeWither extends JavaPlugin {
    2. private static final int TICKS_PER_SECOND = 20;
    3.  
    4. // You could also use a full-fledged API like RemoteEntities
    5. private static class FakeWither {
    6. public static final byte INVISIBLE = 0x20;
    7. // Just a guess
    8. private static final int HEALTH_RANGE = 80 * 80;
    9. // Next entity ID
    10. private static int NEXT_ID = 6000;
    11.  
    12. // Unique ID
    13. private int id = NEXT_ID++;
    14. // Default health
    15. private int health = 300;
    16.  
    17. private boolean visible;
    18. private String customName;
    19. private boolean created;
    20.  
    21. private Location location;
    22. private ProtocolManager manager;
    23.  
    24. public FakeWither(Location location, ProtocolManager manager) {
    25. this.location = location;
    26. this.manager = manager;
    27. }
    28.  
    29. public int getHealth() {
    30. return health;
    31. }
    32.  
    33. public void setHealth(int health) {
    34. // Update the health of the entity
    35. if (created) {
    36. WrappedDataWatcher watcher = new WrappedDataWatcher();
    37.  
    38. watcher.setObject(16, health);
    39. sendMetadata(watcher);
    40. }
    41. this.health = health;
    42. }
    43.  
    44. public void setVisible(boolean visible) {
    45. // Make visible or invisible
    46. if (created) {
    47. WrappedDataWatcher watcher = new WrappedDataWatcher();
    48.  
    49. watcher.setObject(0, visible ? (byte)0 : INVISIBLE);
    50. sendMetadata(watcher);
    51. }
    52. this.visible = visible;
    53. }
    54.  
    55. public void setCustomName(String name) {
    56. if (created) {
    57. WrappedDataWatcher watcher = new WrappedDataWatcher();
    58.  
    59. if (name != null) {
    60. watcher.setObject(5, name);
    61. watcher.setObject(6, (byte) 1);
    62. } else {
    63. // Hide custom name
    64. watcher.setObject(6, (byte) 0);
    65. }
    66.  
    67. // Only players nearby when this is sent will see this name
    68. sendMetadata(watcher);
    69. }
    70. this.customName = name;
    71. }
    72.  
    73. private void sendMetadata(WrappedDataWatcher watcher) {
    74. Packet28EntityMetadata update = new Packet28EntityMetadata();
    75.  
    76. update.setEntityId(id);
    77. update.setEntityMetadata(watcher.getWatchableObjects());
    78. broadcastPacket(update.getHandle(), true);
    79. }
    80.  
    81. public int getId() {
    82. return id;
    83. }
    84.  
    85. public void create() {
    86. Packet18SpawnMob spawnMob = new Packet18SpawnMob();
    87. WrappedDataWatcher watcher = new WrappedDataWatcher();
    88.  
    89. watcher.setObject(0, visible ? (byte)0 : INVISIBLE);
    90. watcher.setObject(16, health);
    91.  
    92. if (customName != null) {
    93. watcher.setObject(5, customName);
    94. watcher.setObject(6, (byte) 1);
    95. }
    96.  
    97. spawnMob.setEntityID(id);
    98. spawnMob.setType(EntityType.WITHER);
    99. spawnMob.setX(location.getX());
    100. spawnMob.setY(location.getY());
    101. spawnMob.setZ(location.getZ());
    102. spawnMob.setMetadata(watcher);
    103.  
    104. broadcastPacket(spawnMob.getHandle(), true);
    105. created = true;
    106. }
    107.  
    108. public void destroy() {
    109. if (!created)
    110. throw new IllegalStateException("Cannot kill a killed entity.");
    111.  
    112. Packet1DDestroyEntity destroyMe = new Packet1DDestroyEntity();
    113. destroyMe.setEntities(new int[] { id });
    114.  
    115. broadcastPacket(destroyMe.getHandle(), false);
    116. created = false;
    117. }
    118.  
    119. private void broadcastPacket(PacketContainer packet, boolean onlyNearby) {
    120. for (Player player : Bukkit.getServer().getOnlinePlayers()) {
    121. // Must be within the range
    122. if (!onlyNearby || player.getLocation().distanceSquared(location) < HEALTH_RANGE) {
    123. try {
    124. manager.sendServerPacket(player, packet);
    125. Bukkit.getLogger().log(Level.WARNING, "Cannot send " + packet + " to " + player, e);
    126. }
    127. }
    128. }
    129. }
    130. }
    131.  
    132. private FakeWither wither;
    133. private BukkitTask task;
    134.  
    135. @Override
    136. public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    137. if (sender instanceof Player) {
    138. Player player = (Player) sender;
    139.  
    140. if (wither != null) {
    141. wither.destroy();
    142. task.cancel();
    143. }
    144.  
    145. // Initialize and create the wither
    146. wither = new FakeWither(player.getLocation(), ProtocolLibrary.getProtocolManager());
    147. wither.setCustomName("Countdown Man");
    148. wither.setVisible(false);
    149. wither.create();
    150.  
    151. // Count down
    152. task = getServer().getScheduler().runTaskTimer(this, new Runnable() {
    153. @Override
    154. public void run() {
    155. // Count down
    156. wither.setHealth(wither.getHealth() - 1);
    157.  
    158. if (wither.getHealth() <= 0) {
    159. wither.destroy();
    160. task.cancel();
    161. }
    162. }
    163. }, TICKS_PER_SECOND / 4, TICKS_PER_SECOND / 4);
    164. }
    165. return true;
    166. }
    167. }
     
  3. Offline

    xWatermelon

    Comphenix thank you! Is there any way I can do this without using ProtocolLib? I don't really want to have to use any external libraries apart from Bukkit/CraftBukkit.
     
  4. Offline

    Comphenix

    Sure, you can modify the packets directly (Packet24MobSpawn, etc.) and send them with playerConnection:
    Code:
    ((CraftPlayer) yourBukkitPlayer).getHandle().playerConnection.sendPacket(Packet);
    Without most of the wrappers in ProtocolLib, you will have to tie your plugin to a specific Minecraft version due to CraftBukkit versioning.
     
  5. Offline

    MCForger

  6. Offline

    xWatermelon

    Comphenix ok. I looked at the documentation and saw that the field "l" is Metadata, but when using packet.l, it comes up as an IntHashMap. What do I do for the last field?
     
  7. Offline

    Comphenix

    Field "l"? Are you talking about Packet24MobSpawn?

    In any case, for writing data out, you set the DataWatcher field (named t I believe) to a new DataWatcher with at least one watchable (index 0 with byte value 0 for instance).

    Maybe you see now why I wrote my example with ProtocolLib. Much easier to both read and write. :p
     
  8. Offline

    xWatermelon

    It says that there is no "t" variable. Anyways, here is my code:
    Code:java
    1. Packet24MobSpawn packet = new Packet24MobSpawn();
    2.  
    3. packet.a = 123; //Entity ID
    4. packet.b = EntityType.WITHER.getTypeId(); //Mob type
    5. packet.c = loc.getBlockX(); //X position
    6. packet.d = loc.getBlockY(); //Y position
    7. packet.e = loc.getBlockZ(); //Z position
    8. packet.f = 0; //Pitch
    9. packet.g = 0; //Head Pitch
    10. packet.h = 0; //Yaw
    11. packet.i = 0; //X velocity
    12. packet.j = 0; //Y velocity
    13. packet.k = 0; //Z velocity
    14. packet.l = ?; //Metadata

    I got this from the protocol documentation. The type for "packet.l" is an IntHashMap. I looked at the entity metadata documentation and saw that I want it to be invisible (0x20, index 0) and have a custom name (index 5). How can I do this?
     
  9. Offline

    Comphenix

    That "l" is a static variable containing a map for packet IDs to packet classes. It's not something you should modify.

    You can't just count up from a to find the fields you need - you have to consult the source code, and essentially "map" the field names against the order they are written to the network. The order (and data type) on the wire is what is described in the documentation, not the field names.

    Unfortunately, the DataWatcher field (named t) is private, so you will have to use reflection to write to it.
     
    xWatermelon likes this.
  10. Offline

    xWatermelon

    Comphenix thank you for all your help so far! I have never used reflection, so I am probably doing this wrong. This is my code:
    Code:java
    1. Packet24MobSpawn mobPacket = new Packet24MobSpawn();
    2.  
    3. mobPacket.a = 1234; //Entity ID
    4. mobPacket.b = EntityType.WITHER.getTypeId(); //Mob type
    5. mobPacket.c = loc.getBlockX(); //X position
    6. mobPacket.d = loc.getBlockY(); //Y position
    7. mobPacket.e = loc.getBlockZ(); //Z position
    8. mobPacket.f = 0; //Pitch
    9. mobPacket.g = 0; //Head Pitch
    10. mobPacket.h = 0; //Yaw
    11. mobPacket.i = 0; //X velocity
    12. mobPacket.j = 0; //Y velocity
    13. mobPacket.k = 0; //Z velocity
    14.  
    15. DataWatcher watcher = new DataWatcher();
    16. watcher.a(0, 0x20); //Flags, 0x20 = invisible
    17. watcher.a(5, ChatColor.AQUA + "Test"); //Entity name
    18. watcher.a(6, 0); //Snow name, 1 = show, 0 = don't show
    19. watcher.a(8, 0); //Firework effects, 0 = none
    20. watcher.a(16, 150); //Wither health, 300 = full health
    21.  
    22. try {
    23. Field t = Packet24MobSpawn.class.getDeclaredField("t");
    24.  
    25. t.setAccessible(true);
    26. t.set(t, watcher);
    27. } catch(SecurityException e){
    28. e.printStackTrace();
    29. e.printStackTrace();
    30. e.printStackTrace();
    31. e.printStackTrace();
    32. }
    33.  
    34. if(player != null){
    35. sendPacket(player, mobPacket);
    36. }

    I am getting this error (when I am using t.set(t, watcher)):
    Code:
    java.lang.IllegalArgumentException: Can not set net.minecraft.server.v1_5_R3.DataWatcher field net.minecraft.server.v1_5_R3.Packet24MobSpawn.t to java.lang.reflect.Field
    Can you show me the correct way to use reflection to set private fields?
     
  11. Offline

    Burnett1

    When you broadcast what packet do you use?
     
  12. Offline

    xWatermelon

  13. Offline

    Burnett1

    Sorry i meant a packet container not a packet. Any ideas?
     
  14. Offline

    Comphenix

    You have to pass in the packet object:
    Code:java
    1.  
    2. Field t = Packet24MobSpawn.class.getDeclaredField("t");
    3.  
    4. t.setAccessible(true);
    5. t.set(mobPacket , watcher);
     
    xWatermelon likes this.
  15. Offline

    microgeek

    This is more-or-less pseudo-code, considering you don't have the required methods, but it should point you in the correct direction.
    Code:
      public static void spawnFakeMobForPlayer(byte type, Player player, Location location, int id, HashMap<Integer, Object> meta) {
            try {
                FauxPacket packet = PacketManager.getNewPacketByName("Packet24MobSpawn");
                packet.setVariable("a", id);
                packet.setVariable("b", type);
                packet.setVariable("c", (int) Math.floor(location.getX() * 32D));
                packet.setVariable("d", (int) Math.floor(location.getY() * 32D));
                packet.setVariable("e", (int) Math.floor(location.getZ() * 32D));
                packet.setVariable("f", (int) 0);
                packet.setVariable("g", (byte) 0);
                packet.setVariable("h", (byte) 0);
                packet.setVariable("i", (byte) 0);
                packet.setVariable("j", (byte) 0);
                packet.setVariable("k", (byte) 0);
                Object metadata = PacketManager.newDataWatcher();
                if(meta == null || meta.isEmpty()) {
                    meta = new HashMap<Integer, Object>();
                    meta.put(0, 0);
                }
                for(Entry<Integer, Object> e : meta.entrySet()) {
                    ReflectionUtil.getMethod(metadata, "a",  new Class<?>[] {int.class, Object.class}).invoke(metadata, e.getKey(), e.getValue());
                }
                ReflectionUtil.setValue(packet.getPacket(), "t", metadata);
                packet.sendPacketToPlayer(player);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
     
  16. Offline

    xWatermelon

    Comphenix
    Code:java
    1. Packet24MobSpawn mobPacket = new Packet24MobSpawn();
    2.  
    3. mobPacket.a = 1234; //Entity ID
    4. mobPacket.b = EntityType.WITHER.getTypeId(); //Mob type
    5. mobPacket.c = loc.getBlockX(); //X position
    6. mobPacket.d = loc.getBlockY(); //Y position
    7. mobPacket.e = loc.getBlockZ(); //Z position
    8. mobPacket.f = 0; //Pitch
    9. mobPacket.g = 0; //Head Pitch
    10. mobPacket.h = 0; //Yaw
    11. mobPacket.i = 0; //X velocity
    12. mobPacket.j = 0; //Y velocity
    13. mobPacket.k = 0; //Z velocity
    14.  
    15. DataWatcher watcher = new DataWatcher();
    16. watcher.a(0, 0x20); //Flags, 0x20 = invisible
    17. watcher.a(5, ChatColor.AQUA + "Test"); //Entity name
    18. watcher.a(6, 0); //Snow name, 1 = show, 0 = don't show
    19. watcher.a(8, 0); //Firework effects, 0 = none
    20. watcher.a(16, 150); //Wither health, 300 = full health
    21.  
    22. try {
    23. Field t = Packet24MobSpawn.class.getDeclaredField("t");
    24.  
    25. t.setAccessible(true);
    26. t.set(mobPacket, watcher);
    27. } catch(SecurityException e){
    28. e.printStackTrace();
    29. e.printStackTrace();
    30. e.printStackTrace();
    31. e.printStackTrace();
    32. }
    33.  
    34. if(player != null){
    35. sendPacket(player, mobPacket);
    36. }

    Get disconnected for "End of stream", no errors in console. Happen to know why?
     
  17. Offline

    Comphenix

    The type you pass in to the DataWatcher matters. Number literals are typed as integers by default, so you will have to cast them to the appropriate type.

    For instance, in my original example, I made sure to cast the first value (index 0) to a byte:
    Code:java
    1. watcher.setObject(0, visible ? (byte)0 : INVISIBLE); // here
    2. watcher.setObject(16, health);
    3.  
    4. if (customName != null) {
    5. watcher.setObject(5, customName);
    6. watcher.setObject(6, (byte) 1);
    7. }

    Give that a try.

    Also, your coordinates are off by 32 (they're fixed point reals, essentially);
    Code:java
    1. mobPacket.c = (int) Math.floor(loc.getBlockX() * 32.0D); //X position
    2. mobPacket.d = (int) Math.floor(loc.getBlockY() * 32.0D); //Y position
    3. mobPacket.e = (int) Math.floor(loc.getBlockZ() * 32.0D)(); //Z position
     
    xWatermelon likes this.
  18. Offline

    xWatermelon

    Comphenix I have done that by looking at the documentation and the source, but it still doesn't seem to be working :( any ideas why?
    Code:java
    1. Packet24MobSpawn mobPacket = new Packet24MobSpawn();
    2.  
    3. mobPacket.a = (int) 123; //Entity ID
    4. mobPacket.b = (int) EntityType.WITHER.getTypeId(); //Mob type (ID: 64)
    5. mobPacket.c = (int) Math.floor(loc.getBlockX() * 32.0D); //X position
    6. mobPacket.d = (int) Math.floor(loc.getBlockY() * 32.0D); //Y position
    7. mobPacket.e = (int) Math.floor(loc.getBlockZ() * 32.0D); //Z position
    8. mobPacket.f = (int) 0; //Pitch
    9. mobPacket.g = (int) 0; //Head Pitch
    10. mobPacket.h = (int) 0; //Yaw
    11. mobPacket.i = (byte) 0; //X velocity
    12. mobPacket.j = (byte) 0; //Y velocity
    13. mobPacket.k = (byte) 0; //Z velocity
    14.  
    15. DataWatcher watcher = new DataWatcher();
    16. watcher.a(0, (byte) 0x20); //Flags, 0x20 = invisible
    17. watcher.a(5, "Test"); //Entity name
    18. watcher.a(6, (byte) 1); //Show name, 1 = show, 0 = don't show
    19. watcher.a(16, (int) 150); //Wither health, 300 = full health
    20.  
    21. try {
    22. Field t = Packet24MobSpawn.class.getDeclaredField("t");
    23.  
    24. t.setAccessible(true);
    25. t.set(mobPacket, watcher);
    26. } catch(SecurityException e){
    27. e.printStackTrace();
    28. e.printStackTrace();
    29. e.printStackTrace();
    30. e.printStackTrace();
    31. }
    32.  
    33. if(player != null){
    34. sendPacket(player, mobPacket);
    35. }
     
  19. Offline

    Comphenix

    Well, you've messed up the order of the variables (look at this constructor!), but that's not the big game-crashing problem.

    The big problem is that DataWather is on of the more poorly designed classes in Minecraft, in addition to having little to none deobfuscation in CraftBukkit. One mistake, and it will happily corrupt the network stream for you, as it does very little type checking. In particular, its addObject method a(int, Object) relies in the runtime type of Object, which is bad enough, but worse the deobfuscation overloads this with addObjectByDataType or a(int, int). This latter method is what you've been calling, instead of addObject.

    These kind of traps is what you have to expect when dealing with NMS, and why I initially recommended ProtocolLib. You have to be able to read the obfuscated source code, or use MCP and match its deobfuscation up against CraftBukkit.

    As for the corruption problem, the solution is to cast each primitive integer to its wrapper counterpart:
    Code:java
    1. watcher.a(0, (Byte) (byte) 0x20); //Flags, 0x20 = invisible
    2. watcher.a(5, "Test"); //Entity name
    3. watcher.a(6, (Byte) (byte) 1); //Show name, 1 = show, 0 = don't show
    4. watcher.a(16, (Integer) (int) 150); //Wither health, 300 = full health
    5.  


    You should also correct the field order, and perhaps broadcast the packet instead of sending it to one player:
    Code:java
    1. Player initiator = (Player) sender;
    2.  
    3. Packet24MobSpawn mobPacket = new Packet24MobSpawn();
    4. Location loc = initiator.getLocation();
    5.  
    6. mobPacket.a = (int) 6000; //Entity ID
    7. mobPacket.b = (int) EntityType.WITHER.getTypeId(); //Mob type (ID: 64)
    8. mobPacket.c = (int) Math.floor(loc.getBlockX() * 32.0D); //X position
    9. mobPacket.d = (int) Math.floor(loc.getBlockY() * 32.0D); //Y position
    10. mobPacket.e = (int) Math.floor(loc.getBlockZ() * 32.0D); //Z position
    11. mobPacket.f = (int) 0; //X velocity
    12. mobPacket.g = (int) 0; //Y velocity
    13. mobPacket.h = (int) 0; //Z velocity
    14. mobPacket.i = (byte) 0; //Pitch
    15. mobPacket.j = (byte) 0; //Head Pitch
    16. mobPacket.k = (byte) 0; //Yaw
    17.  
    18. DataWatcher watcher = new DataWatcher();
    19. watcher.a(0, (Byte) (byte) 0x20); //Flags, 0x20 = invisible
    20. watcher.a(5, "Test"); //Entity name
    21. watcher.a(6, (Byte) (byte) 1); //Show name, 1 = show, 0 = don't show
    22. watcher.a(16, (Integer) (int) 150); //Wither health, 300 = full health
    23.  
    24. try {
    25. Field t = Packet24MobSpawn.class.getDeclaredField("t");
    26.  
    27. t.setAccessible(true);
    28. t.set(mobPacket, watcher);
    29. } catch(Exception e){
    30. e.printStackTrace();
    31. }
    32.  
    33. for (Player player : getServer().getOnlinePlayers()) {
    34. ((CraftPlayer) player).getHandle().playerConnection.sendPacket(mobPacket);
    35. }
     
Thread Status:
Not open for further replies.

Share This Page