Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * tabAdd (PLAYER_INFO ADD_PLAYER + skin textures)
- */
- @Override
- public void tabAdd(Player viewer, UUID npcUuid, String displayNameRaw, FakeNPCAPI.SkinData skin) {
- try {
- String displayName = ChatColor.translateAlternateColorCodes('&',
- displayNameRaw == null ? "" : displayNameRaw);
- String profileName = profileNameFromDisplay(displayName, npcUuid);
- WrappedGameProfile gp = new WrappedGameProfile(npcUuid, profileName);
- // Workaround because WrappedGameProfile.getProperties() was NPE on my setup:
- // I inject textures using reflection on the GameProfile handle.
- GameProfileTextures.apply(gp, skin);
- PacketContainer packet = protocol.createPacket(PacketType.Play.Server.PLAYER_INFO);
- packet.getModifier().writeDefaults();
- packet.getPlayerInfoActions().write(0, EnumSet.of(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
- PlayerInfoData data = new PlayerInfoData(
- gp, 0, EnumWrappers.NativeGameMode.SURVIVAL,
- WrappedChatComponent.fromLegacyText(displayName)
- );
- List<PlayerInfoData> list = Collections.singletonList(data);
- try { packet.getPlayerInfoDataLists().write(1, list); }
- catch (Throwable ignored) { packet.getPlayerInfoDataLists().write(0, list); }
- protocol.sendServerPacket(viewer, packet);
- } catch (Exception e) {
- plugin.getLogger().warning("[NPC] tabAdd failed: " + e.getMessage());
- e.printStackTrace();
- }
- }
- private static String profileNameFromDisplay(String displayName, UUID uuid) {
- String stripped = ChatColor.stripColor(displayName);
- if (stripped == null) stripped = "";
- String clean = stripped.replaceAll("[^A-Za-z0-9_]", "");
- if (clean.isBlank()) clean = "NPC" + uuid.toString().replace("-", "").substring(0, 8);
- if (clean.length() > 16) clean = clean.substring(0, 16);
- return clean;
- }
- /*
- * spawn (SPAWN_ENTITY + forced rotation)
- */
- @Override
- public void spawnPlayer(Player viewer, int entityId, UUID npcUuid, Location loc) {
- try {
- PacketContainer spawn = protocol.createPacket(PacketType.Play.Server.SPAWN_ENTITY);
- spawn.getModifier().writeDefaults();
- spawn.getIntegers().write(0, entityId);
- spawn.getUUIDs().write(0, npcUuid);
- // NMS EntityType via reflection (avoids Bukkit EntityType ClassCast)
- spawn.getModifier().withType(NmsEntityTypes.ENTITY_TYPE_CLASS).write(0, NmsEntityTypes.PLAYER);
- spawn.getDoubles().write(0, loc.getX());
- spawn.getDoubles().write(1, loc.getY());
- spawn.getDoubles().write(2, loc.getZ());
- protocol.sendServerPacket(viewer, spawn);
- // Force yaw/pitch
- forceRotation(viewer, entityId, loc.getYaw(), loc.getPitch());
- } catch (Exception e) {
- plugin.getLogger().warning("[NPC] spawnPlayer failed: " + e.getMessage());
- e.printStackTrace();
- }
- }
- private void forceRotation(Player viewer, int entityId, float yawDeg, float pitchDeg) {
- byte yaw = (byte) Math.floor(yawDeg * 256.0f / 360.0f);
- byte pitch = (byte) Math.floor(pitchDeg * 256.0f / 360.0f);
- try {
- PacketContainer look = protocol.createPacket(PacketType.Play.Server.ENTITY_LOOK);
- look.getIntegers().write(0, entityId);
- look.getBytes().write(0, yaw);
- look.getBytes().write(1, pitch);
- if (look.getBooleans().size() > 0) look.getBooleans().write(0, true);
- protocol.sendServerPacket(viewer, look);
- } catch (Throwable ignored) {}
- try {
- PacketContainer head = protocol.createPacket(PacketType.Play.Server.ENTITY_HEAD_ROTATION);
- head.getIntegers().write(0, entityId);
- head.getBytes().write(0, yaw);
- protocol.sendServerPacket(viewer, head);
- } catch (Throwable ignored) {}
- }
- private static final class NmsEntityTypes {
- static final Class<?> ENTITY_TYPE_CLASS;
- static final Object PLAYER;
- static {
- try {
- ENTITY_TYPE_CLASS = Class.forName("net.minecraft.world.entity.EntityType");
- PLAYER = ENTITY_TYPE_CLASS.getField("PLAYER").get(null);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
- /*
- * metadata: custom name only (safe)
- */
- @Override
- public void metadata(Player viewer, int entityId, String displayName) {
- try {
- PacketContainer meta = protocol.createPacket(PacketType.Play.Server.ENTITY_METADATA);
- meta.getIntegers().write(0, entityId);
- List<WrappedDataValue> values = new ArrayList<>();
- Object chatHandle = WrappedChatComponent.fromLegacyText(displayName == null ? "" : displayName).getHandle();
- values.add(new WrappedDataValue(
- 2,
- WrappedDataWatcher.Registry.getChatComponentSerializer(true),
- Optional.of(chatHandle)
- ));
- values.add(new WrappedDataValue(
- 3,
- serializer(Boolean.class),
- true
- ));
- meta.getDataValueCollectionModifier().write(0, values);
- protocol.sendServerPacket(viewer, meta);
- } catch (Exception ignored) {}
- }
- /*
- * I resolve the correct DataAccessor id by name (DATA_PLAYER_MODE_CUSTOMISATION / CUSTOMIZATION)
- * and then send a separate ENTITY_METADATA with value 0x7F.
- */
- public void modelParts(Player viewer, int entityId) {
- try {
- Integer partsId = PlayerModelPartsId.resolve();
- if (partsId == null) return;
- PacketContainer meta = protocol.createPacket(PacketType.Play.Server.ENTITY_METADATA);
- meta.getIntegers().write(0, entityId);
- List<WrappedDataValue> values = new ArrayList<>();
- values.add(new WrappedDataValue(
- partsId,
- serializer(Byte.class),
- (byte) 0x7F
- ));
- meta.getDataValueCollectionModifier().write(0, values);
- protocol.sendServerPacket(viewer, meta);
- plugin.getLogger().info("[NPC] sent modelParts partsId=" + partsId + " value=0x7F");
- } catch (Throwable t) {
- plugin.getLogger().warning("[NPC] modelParts failed: " + t.getMessage());
- t.printStackTrace();
- }
- }
- /*
- * Resolver
- */
- public final class PlayerModelPartsId {
- private static volatile Integer CACHED;
- public static Integer resolve() {
- Integer id = CACHED;
- if (id != null) return id;
- synchronized (PlayerModelPartsId.class) {
- if (CACHED != null) return CACHED;
- CACHED = compute();
- return CACHED;
- }
- }
- private static Integer compute() {
- try {
- Class<?> playerClass = Class.forName("net.minecraft.world.entity.player.Player");
- Field f;
- try { f = playerClass.getDeclaredField("DATA_PLAYER_MODE_CUSTOMISATION"); }
- catch (NoSuchFieldException ignored) { f = playerClass.getDeclaredField("DATA_PLAYER_MODE_CUSTOMIZATION"); }
- f.setAccessible(true);
- Object accessor = f.get(null);
- if (accessor == null) return null;
- try {
- Method getId = accessor.getClass().getMethod("getId");
- return (Integer) getId.invoke(accessor);
- } catch (NoSuchMethodException ignored) {
- Field idField = accessor.getClass().getDeclaredField("id");
- idField.setAccessible(true);
- return (Integer) idField.get(accessor);
- }
- } catch (Throwable t) {
- return null;
- }
- }
- }
- // And I call modelParts twice after spawn:
- Bukkit.getScheduler().runTaskLater(plugin, () -> packetSender.modelParts(viewer, entityId), 2L);
- Bukkit.getScheduler().runTaskLater(plugin, () -> packetSender.modelParts(viewer, entityId), 20L);
Advertisement
Add Comment
Please, Sign In to add comment