Guest User

Untitled

a guest
Jan 24th, 2026
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.80 KB | None | 0 0
  1.  
  2. /*
  3. * tabAdd (PLAYER_INFO ADD_PLAYER + skin textures)
  4. */
  5. @Override
  6. public void tabAdd(Player viewer, UUID npcUuid, String displayNameRaw, FakeNPCAPI.SkinData skin) {
  7. try {
  8. String displayName = ChatColor.translateAlternateColorCodes('&',
  9. displayNameRaw == null ? "" : displayNameRaw);
  10.  
  11. String profileName = profileNameFromDisplay(displayName, npcUuid);
  12. WrappedGameProfile gp = new WrappedGameProfile(npcUuid, profileName);
  13.  
  14. // Workaround because WrappedGameProfile.getProperties() was NPE on my setup:
  15. // I inject textures using reflection on the GameProfile handle.
  16. GameProfileTextures.apply(gp, skin);
  17.  
  18. PacketContainer packet = protocol.createPacket(PacketType.Play.Server.PLAYER_INFO);
  19. packet.getModifier().writeDefaults();
  20.  
  21. packet.getPlayerInfoActions().write(0, EnumSet.of(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
  22.  
  23. PlayerInfoData data = new PlayerInfoData(
  24. gp, 0, EnumWrappers.NativeGameMode.SURVIVAL,
  25. WrappedChatComponent.fromLegacyText(displayName)
  26. );
  27.  
  28. List<PlayerInfoData> list = Collections.singletonList(data);
  29. try { packet.getPlayerInfoDataLists().write(1, list); }
  30. catch (Throwable ignored) { packet.getPlayerInfoDataLists().write(0, list); }
  31.  
  32. protocol.sendServerPacket(viewer, packet);
  33. } catch (Exception e) {
  34. plugin.getLogger().warning("[NPC] tabAdd failed: " + e.getMessage());
  35. e.printStackTrace();
  36. }
  37. }
  38.  
  39. private static String profileNameFromDisplay(String displayName, UUID uuid) {
  40. String stripped = ChatColor.stripColor(displayName);
  41. if (stripped == null) stripped = "";
  42. String clean = stripped.replaceAll("[^A-Za-z0-9_]", "");
  43. if (clean.isBlank()) clean = "NPC" + uuid.toString().replace("-", "").substring(0, 8);
  44. if (clean.length() > 16) clean = clean.substring(0, 16);
  45. return clean;
  46. }
  47.  
  48. /*
  49. * spawn (SPAWN_ENTITY + forced rotation)
  50. */
  51. @Override
  52. public void spawnPlayer(Player viewer, int entityId, UUID npcUuid, Location loc) {
  53. try {
  54. PacketContainer spawn = protocol.createPacket(PacketType.Play.Server.SPAWN_ENTITY);
  55. spawn.getModifier().writeDefaults();
  56.  
  57. spawn.getIntegers().write(0, entityId);
  58. spawn.getUUIDs().write(0, npcUuid);
  59.  
  60. // NMS EntityType via reflection (avoids Bukkit EntityType ClassCast)
  61. spawn.getModifier().withType(NmsEntityTypes.ENTITY_TYPE_CLASS).write(0, NmsEntityTypes.PLAYER);
  62.  
  63. spawn.getDoubles().write(0, loc.getX());
  64. spawn.getDoubles().write(1, loc.getY());
  65. spawn.getDoubles().write(2, loc.getZ());
  66.  
  67. protocol.sendServerPacket(viewer, spawn);
  68.  
  69. // Force yaw/pitch
  70. forceRotation(viewer, entityId, loc.getYaw(), loc.getPitch());
  71.  
  72. } catch (Exception e) {
  73. plugin.getLogger().warning("[NPC] spawnPlayer failed: " + e.getMessage());
  74. e.printStackTrace();
  75. }
  76. }
  77.  
  78. private void forceRotation(Player viewer, int entityId, float yawDeg, float pitchDeg) {
  79. byte yaw = (byte) Math.floor(yawDeg * 256.0f / 360.0f);
  80. byte pitch = (byte) Math.floor(pitchDeg * 256.0f / 360.0f);
  81.  
  82. try {
  83. PacketContainer look = protocol.createPacket(PacketType.Play.Server.ENTITY_LOOK);
  84. look.getIntegers().write(0, entityId);
  85. look.getBytes().write(0, yaw);
  86. look.getBytes().write(1, pitch);
  87. if (look.getBooleans().size() > 0) look.getBooleans().write(0, true);
  88. protocol.sendServerPacket(viewer, look);
  89. } catch (Throwable ignored) {}
  90.  
  91. try {
  92. PacketContainer head = protocol.createPacket(PacketType.Play.Server.ENTITY_HEAD_ROTATION);
  93. head.getIntegers().write(0, entityId);
  94. head.getBytes().write(0, yaw);
  95. protocol.sendServerPacket(viewer, head);
  96. } catch (Throwable ignored) {}
  97. }
  98.  
  99. private static final class NmsEntityTypes {
  100. static final Class<?> ENTITY_TYPE_CLASS;
  101. static final Object PLAYER;
  102. static {
  103. try {
  104. ENTITY_TYPE_CLASS = Class.forName("net.minecraft.world.entity.EntityType");
  105. PLAYER = ENTITY_TYPE_CLASS.getField("PLAYER").get(null);
  106. } catch (Exception e) {
  107. throw new RuntimeException(e);
  108. }
  109. }
  110. }
  111.  
  112.  
  113. /*
  114. * metadata: custom name only (safe)
  115. */
  116. @Override
  117. public void metadata(Player viewer, int entityId, String displayName) {
  118. try {
  119. PacketContainer meta = protocol.createPacket(PacketType.Play.Server.ENTITY_METADATA);
  120. meta.getIntegers().write(0, entityId);
  121.  
  122. List<WrappedDataValue> values = new ArrayList<>();
  123.  
  124. Object chatHandle = WrappedChatComponent.fromLegacyText(displayName == null ? "" : displayName).getHandle();
  125.  
  126. values.add(new WrappedDataValue(
  127. 2,
  128. WrappedDataWatcher.Registry.getChatComponentSerializer(true),
  129. Optional.of(chatHandle)
  130. ));
  131.  
  132. values.add(new WrappedDataValue(
  133. 3,
  134. serializer(Boolean.class),
  135. true
  136. ));
  137.  
  138. meta.getDataValueCollectionModifier().write(0, values);
  139. protocol.sendServerPacket(viewer, meta);
  140. } catch (Exception ignored) {}
  141. }
  142.  
  143.  
  144.  
  145.  
  146. /*
  147. * I resolve the correct DataAccessor id by name (DATA_PLAYER_MODE_CUSTOMISATION / CUSTOMIZATION)
  148. * and then send a separate ENTITY_METADATA with value 0x7F.
  149. */
  150. public void modelParts(Player viewer, int entityId) {
  151. try {
  152. Integer partsId = PlayerModelPartsId.resolve();
  153. if (partsId == null) return;
  154.  
  155. PacketContainer meta = protocol.createPacket(PacketType.Play.Server.ENTITY_METADATA);
  156. meta.getIntegers().write(0, entityId);
  157.  
  158. List<WrappedDataValue> values = new ArrayList<>();
  159. values.add(new WrappedDataValue(
  160. partsId,
  161. serializer(Byte.class),
  162. (byte) 0x7F
  163. ));
  164.  
  165. meta.getDataValueCollectionModifier().write(0, values);
  166. protocol.sendServerPacket(viewer, meta);
  167.  
  168. plugin.getLogger().info("[NPC] sent modelParts partsId=" + partsId + " value=0x7F");
  169.  
  170. } catch (Throwable t) {
  171. plugin.getLogger().warning("[NPC] modelParts failed: " + t.getMessage());
  172. t.printStackTrace();
  173. }
  174. }
  175.  
  176. /*
  177. * Resolver
  178. */
  179. public final class PlayerModelPartsId {
  180. private static volatile Integer CACHED;
  181.  
  182. public static Integer resolve() {
  183. Integer id = CACHED;
  184. if (id != null) return id;
  185. synchronized (PlayerModelPartsId.class) {
  186. if (CACHED != null) return CACHED;
  187. CACHED = compute();
  188. return CACHED;
  189. }
  190. }
  191.  
  192. private static Integer compute() {
  193. try {
  194. Class<?> playerClass = Class.forName("net.minecraft.world.entity.player.Player");
  195. Field f;
  196. try { f = playerClass.getDeclaredField("DATA_PLAYER_MODE_CUSTOMISATION"); }
  197. catch (NoSuchFieldException ignored) { f = playerClass.getDeclaredField("DATA_PLAYER_MODE_CUSTOMIZATION"); }
  198. f.setAccessible(true);
  199. Object accessor = f.get(null);
  200. if (accessor == null) return null;
  201.  
  202. try {
  203. Method getId = accessor.getClass().getMethod("getId");
  204. return (Integer) getId.invoke(accessor);
  205. } catch (NoSuchMethodException ignored) {
  206. Field idField = accessor.getClass().getDeclaredField("id");
  207. idField.setAccessible(true);
  208. return (Integer) idField.get(accessor);
  209. }
  210. } catch (Throwable t) {
  211. return null;
  212. }
  213. }
  214. }
  215.  
  216.  
  217. // And I call modelParts twice after spawn:
  218. Bukkit.getScheduler().runTaskLater(plugin, () -> packetSender.modelParts(viewer, entityId), 2L);
  219. Bukkit.getScheduler().runTaskLater(plugin, () -> packetSender.modelParts(viewer, entityId), 20L);
  220.  
Advertisement
Add Comment
Please, Sign In to add comment