Solved NPC-Change Skin doesn't work

Discussion in 'Spigot Plugin Development' started by Kiwanga, Sep 14, 2018 at 12:20 PM.

  1. So I try to create a Fakeplayer with a certain skin. But it is not working. I looked up if the server has a problem but all other player have skins... This is my code:
    Code (Text):
    //    private String value = "eyJ0aW1lc3RhbXAiOjE1MzY5MjA4NDQ2MjUsInByb2ZpbGVJZCI6Ijc1OTBlYjcyNDJlOTQyZWViM2RmODY2YjE4NDlkYjk5IiwicHJvZmlsZU5hbWUiOiJCbGVhY2hfXyIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZWMzZTRiNTUzMGExZmMzYjdkYjRkOGU2NjMxMzY4MTViYzIxMDg5OTU5NDUxMmZkMTkyMmUwYjI2YTc0ZDJhMSJ9fX0=";
    //    private String signature = "Zr6/3CJXsQ9F/wGbgZQD2DEPoyy1JqosJ0wsqBhMVhtOlBhNQZR8HZiaVWUoAkkSp4XOHyUFillEw/tGNL6PHe5lFuXY7Jzfp+smGCoRdzkBTrKmNyRqayfJlRKGF4M9f2jPwf+4AnPeuCPLGGfnK9penb8OPXQk0tz8nTZiakpZDyEk4spWbSbsvBb6OlKJpesDSIJn5xg1VdSuVnUY1AlxKPIromiB015TikSinYtx9ungacufZCtS8cFto5uD6Lfz2bXwVs6zZLVrFWdm5d6ch2of805kYlqBUHQudZ9T794ZYV3KsGuWherqco6y2+WiEtsk/M9+BgP4v16U6zBBnO5WHhNjEd26IActp5qK/nz+93l4eVf/1igN7Eotz3cClEExUNFmWFbXy+Q5lCjGvzsGcToh48mr4vHlBy+GkdhmWBz4erBBTGZ285iak8/jpsW+JhorpcWUx1RPWKkdOfzrgDdjZnnnqWCEObqqJ1CSeBfUwph2EbUL4E4idnZeeWTX0BD4qAaqCPIvvSnT9bI0NNv/IONQNaqGzlmKbJoAS/zf/R03zTeNYULHgTbXBVFM9KlzVJP1X8/9bkuqyAyxvTseeGRVo18eizOcQElPz7XRz5yYaPXGWWZW7sPPLyAa9p67lElS3JP2Ks5VkV0Vsq7KhbERO4yX/tg=";
     
        public void createNPC(Player player, String npcName) {
            Location location = player.getLocation();
            MinecraftServer nmsServer = ((CraftServer) Bukkit.getServer()).getServer();
            WorldServer nmsWorld = ((CraftWorld) player.getWorld()).getHandle();
            GameProfile gameProfile = new GameProfile(UUID.randomUUID(), npcName);
         
            String[] skin = getFromName("Bleach__");
            gameProfile.getProperties().put("textures", new Property("textures", skin[0], skin[1]));
    //        gameProfile.getProperties().put("textures", new Property("textures", value, signature));

            EntityPlayer npc = new EntityPlayer(nmsServer, nmsWorld, gameProfile, new PlayerInteractManager(nmsWorld));
            Player npcPlayer = npc.getBukkitEntity().getPlayer();
            npcPlayer.setPlayerListName("");

            npc.setLocation(location.getX(), location.getY(), location.getZ(), player.getLocation().getYaw(), player.getLocation().getPitch());

            PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
            connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, npc));
            connection.sendPacket(new PacketPlayOutNamedEntitySpawn(npc));
            connection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, npc));
        }
     
        public String[] getFromName(String name) {
            try {
                URL url_0 = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
                InputStreamReader reader_0 = new InputStreamReader(url_0.openStream());
                String uuid = new JsonParser().parse(reader_0).getAsJsonObject().get("id").getAsString();
     
                URL url_1 = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
                InputStreamReader reader_1 = new InputStreamReader(url_1.openStream());
                JsonObject textureProperty = new JsonParser().parse(reader_1).getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject();
                String texture = textureProperty.get("value").getAsString();
                String signature = textureProperty.get("signature").getAsString();
     
                return new String[] {texture, signature};
            } catch (IOException e) {
                System.err.println("Could not get skin data from session servers!");
                e.printStackTrace();
                return null;
            }
        }
    }
    I tried it with the commented and the current methode, but both don't change the skin. The NPC with his name spawns.
    ~Kiwanga
     
  2. what exactly isn't working?
    Please describe your problem a bit more in detail so people here can help you easier, for example if the npc comes up in the tablist for a short time, then the 2 info packets can't be wrong, is the entity self not spawning? keep in mind that you have to add that npc aswell to the world chunk otherwise it won't be visible cause its then only an invalid object, so your npc needs also an unique id
     
  3. Okay my problem ist just, that the NPC has no skin but it should have one, because I set it. So this part is not working:
    Code (Text):
    String[] skin = getFromName("Bleach__");
    gameProfile.getProperties().put("textures", new Property("textures", skin[0], skin[1]));
    The NPC spawns normally and has the selected displayname. It just has no skin and I don't know why. Is method old or is the an other way? So my question is how to change the skin of a fakeplayer.
     
  4. I've been working lately with NPCs, if the player doesn't load (The NPC is not directly inside the player FOV) before the player info with the remove_player the skin is not loaded. So, you need to do two things:
    1. Delay the PacketPlayOutPlayerInfo with the remove_player info.
    2. What I've been doing is teleport the NPC in front of the player with a teleport packet, then create a new metadata packet modifying the DataWatcher.Item list inside it to set the player invisible, then send both packets and finally teleport the NPC few ticks later at the normal position. This is how i do it, hopefully is useful enough. (I've not tested this in a full working enviroment, so no clue if has some flaws. The method sendWrappedPacket is just to create a ProtocolLib Packet and send it).
      Code (Java):

      private static DataWatcherObject<Byte> ENTITY_META_OBJECT;

      private static DataWatcherObject<Byte> getEntityMetaObject()
      {
          if(ENTITY_META_OBJECT == null)
          {
              ENTITY_META_OBJECT = (DataWatcherObject<Byte>) Validator.validateField(ReflectionUtil.getField(Entity.class, "Z"), null);
              Validate.notNull("Something went wrong during reflectioning an npc hidder needed value.");
          }
          return ENTITY_META_OBJECT;
      }
      //This are the player location and his pitch and yaw. Don't question why i've done this way :P
      private void sendSkinLoadingTeleport(Player player, double x, double y, double z, float pitch, float yaw)
      {
          var metaPacket = new PacketPlayOutEntityMetadata(entityPlayer.getId(), entityPlayer.getDataWatcher(), true);

          var itemList = NPCPacketUtil.getDataWatcherItemList(metaPacket);
          itemList.stream().filter(item -> item.a() == getEntityMetaObject()).findFirst().map(item -> (DataWatcher.Item<Byte>) item).ifPresent(item ->
          {
              var data = (byte) (item.b() | 1 << 5); //Add the invisible tag.
              data = (byte) (data & ~(1 << 6)); //Remove the glowing tag, just in case it has it.

              item.a(data);
          });

          this.sendWrappedPacket(player, PacketType.Play.Server.ENTITY_METADATA, metaPacket);

          var yd = -Math.sin(Math.toRadians(pitch));

          double xz = Math.cos(Math.toRadians(pitch));
          var xd = -xz * Math.sin(Math.toRadians(yaw));
          var zd = xz * Math.cos(Math.toRadians(yaw));

          //Send the teleport packet, to allow the player to charge the skin.
          var teleportPacket = NPCPacketUtil.getTeleportPacket(this, x + (xd * 2), y + (yd * 2), z + (zd * 2), false);
          this.sendWrappedPacket(player, PacketType.Play.Server.ENTITY_TELEPORT, teleportPacket);
      }
       
      NPC Packet Util
      Code (Java):
      package me.parozzz.core.nms.npc.util;

      import me.parozzz.core.nms.npc.NPC;
      import me.parozzz.core.tools.Validator;
      import me.parozzz.core.utilities.ReflectionUtil;
      import net.minecraft.server.v1_12_R1.*;
      import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
      import org.bukkit.entity.Player;

      import java.lang.reflect.Field;
      import java.util.ArrayList;
      import java.util.Collection;
      import java.util.List;
      import java.util.function.Supplier;
      import java.util.stream.Stream;

      public final class NPCPacketUtil
      {
          private NPCPacketUtil() {}

          private static final Field TELEPORT_ENTITYID = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "a");
          private static final Field TELEPORT_LOCX = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "b");
          private static final Field TELEPORT_LOCY = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "c");
          private static final Field TELEPORT_LOCZ = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "d");
          //private static final Field TELEPORT_YAW = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "e");
          //private static final Field TELEPORT_PITCH = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "f");
          private static final Field TELEPORT_ONGROUND = ReflectionUtil.getField(PacketPlayOutEntityTeleport.class, "g");

          public static Packet getTeleportPacket(NPC npc, double x, double y, double z, boolean onGround)
          {
              var teleportPacket = new PacketPlayOutEntityTeleport();

              Validator.setField(TELEPORT_ENTITYID, teleportPacket, npc.getId());
              Validator.setField(TELEPORT_LOCX, teleportPacket, x);
              Validator.setField(TELEPORT_LOCY, teleportPacket, y);
              Validator.setField(TELEPORT_LOCZ, teleportPacket, z);
              Validator.setField(TELEPORT_ONGROUND, teleportPacket, onGround);

              return teleportPacket;
          }

          private static final Field ITEM_LIST = ReflectionUtil.getField(PacketPlayOutEntityMetadata.class, "b");
          public static List<DataWatcher.Item<?>> getDataWatcherItemList(PacketPlayOutEntityMetadata metaPacket)
          {
              return (List<DataWatcher.Item<?>>) Validator.validateField(ITEM_LIST, metaPacket);
          }
      }
       
     
    • Useful Useful x 1
  5. Thank you very much I will give it a try!
     
  6. Thank you so much! You are a hero! Solved my problem!!!

    EDIT: I think you don't have to teleport the player. You just have to delay the infopacket with REMOVE_PLAYER, Cause I tried it without teleporting
     
    #6 Kiwanga, Sep 16, 2018 at 5:16 PM
    Last edited: Sep 16, 2018 at 5:23 PM

Share This Page