Resource Advanced NPC Util. [1.17] (Packets)

Discussion in 'Spigot Plugin Development' started by DanielTheDev, Jun 24, 2021.

  1. So, will you make a maven dependency? :D
  2. Hereby, the maven dependency at the main page.
    • Winner Winner x 2
  3. <3
  4. Hey after calling teleportNPC the head rotation is broken. Its always the direction. The pitch is fine.
  5. First of all nice work, however I think I have a more suitable solution for the getByUsername and getByUUID methods.
    That's what we have Futures for. More specifically, since Java 8 CompletableFuture.

    A simplified usage would be
    Code (Java):
    NPC.SkinTextures.getByUsername("DanielTheDev").thenAccept(skinTextures -> {
        //skin texture loaded...
        NPC npc = new NPC(receiver.getLocation(), "Steve");
    }).exceptionally((e) -> {
        //skin texture not loaded...
        System.out.println("Error " + e.getMessage());
        return null;
    The following two methods just add them on the NPC class after the original two. I am writing here so that @DanielTheDev can tell me what he thinks. If so, I'll do a pr on github.
    Code (Java):
    public static CompletableFuture<SkinTextures> getByUsername(String username) {
        return CompletableFuture.supplyAsync(()->{
            JSONArray array = new JSONArray();
            UUID result = null;
            try {
                HttpRequest request = HttpRequest.newBuilder(new URI(UUID_URL))
                        .setHeader("Content-Type", "application/json")

                HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
                if (response.statusCode() == HttpURLConnection.HTTP_OK) {
                    JSONArray uuidArray = (JSONArray) new JSONParser().parse(response.body());
                    if (uuidArray.size() > 0) {
                        String uuidStr = (String) ((JSONObject) uuidArray.get(0)).get("id");
                        result = UUID.fromString(uuidStr.replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
            } catch (URISyntaxException | InterruptedException | IOException | ParseException e) {
            if(result == null)
                throw new IllegalArgumentException("SkinTextures not found by username");
            else {
                return getByUUID(result).join();

    public static CompletableFuture<SkinTextures> getByUUID(UUID uuid) {
        return CompletableFuture.supplyAsync(()->{
            SkinTextures result = null;
            try {
                HttpRequest request = HttpRequest.newBuilder(new URI(String.format(TEXTURE_URL, uuid.toString().replace("-", ""))))

                HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
                if (response.statusCode() == HttpURLConnection.HTTP_OK) {
                    JSONArray properties = (JSONArray) ((JSONObject) new JSONParser().parse(response.body())).get("properties");
                    for (int t = 0; t < properties.size(); t++) {
                        JSONObject obj = (JSONObject) properties.get(t);
                        if (obj.containsKey("name") && obj.get("name").equals("textures")) {
                            result = new SkinTextures((String) obj.get("value"), (String) obj.get("signature"));
            } catch (URISyntaxException | InterruptedException | IOException | ParseException e) {
                throw new IllegalArgumentException("SkinTextures not found by uuid");
                return result;
    • Like Like x 3
  6. Hey, I'm very grateful you took effort in providing this functionality.

    I don't see any reason why this is not a good idea, despite giving the developers access to sync/hold methods.
    But since I already came up with a safe solution by using Consumer callbacks, I can easily add them as an addition to the code.
    I find this a great solution, giving the more experienced developers a broader variety of options to handle async methods.

    Again, thanks for sharing this with me. It also keeps me sharp since I completely forgot about the CompletableFuture. ;)
    Many appreciations.
  7. Thanks for notifying me. I didn't realize you have to add a EntityHeadRotate packet after teleporting.
    I will update this together with the new SkinTexture supplier as soon as possible.
  8. Is there anyway to add functionality to have the NPC walk to a certain location? I would imagine that it is a combination of animation & moving but i am not sure...
  9. Yes, that is possible. It is not implement in this project and will not be since it is not inline with the main focus of the project.
    To do this yourself, you have to send out
    or PacketPlayOutEntityTeleport (including many others) combined with the animations/actions provided within this Utility.
    And in order to do so, you need to create a custom pathfinding mechanism that interacts with the world, since there is no implementation for custom NPCs.
  10. Yeah, i feared it would be a lot of packets and pathfinding. Thanks for your reply though!
  11. Is it possible to make the npc looks to the player?
  12. I quickly wrote you a method that allows you to set the NPC's looking direction at the targeted player.
    Just add these two methods to the NPC class:

    Code (Java):
    public void lookAtPoint(Player player, Location location) {
        Location eyeLocation = this.getEyeLocation();
        float yaw = (float) Math.toDegrees(Math.atan2(location.getZ() - eyeLocation.getZ(), location.getX()-eyeLocation.getX())) - 90;
        yaw = (float) (yaw + Math.ceil( -yaw / 360 ) * 360);

        float deltaXZ = (float) Math.sqrt(Math.pow(eyeLocation.getX()-location.getX(), 2) + Math.pow(eyeLocation.getZ()-location.getZ(), 2));
        float pitch = (float) Math.toDegrees(Math.atan2(deltaXZ, location.getY()-eyeLocation.getY())) - 90;

        pitch = (float) (pitch + Math.ceil( -pitch / 360 ) * 360);

        this.rotateHead(player, pitch, yaw);

    public Location getEyeLocation() {
        return this.location.clone().add(0, * 0.85F, 0);
    After that, you use this code to make an NPC look directly at you.

    Code (Java):
    Player player = (Player) sender;
    NPC npc = new NPC(player.getLocation(), "henk");
    npc.teleportNPC(player, player.getLocation(), true);
    npc.lookAtPoint(player, player.getEyeLocation());
    Note: I will include this in the next version (1.2) which will take a day to update.
    Or you can grab the code from the GitHub here.
    • Like Like x 1
  13. Oh nice it works in a runnable.
    1. Is it for each player individual?
    2. Is it possible that you could implement an onClick function, where I could handle own actions?

  14. 1. Yes, this entire utility is based on player individuality. Therefore, you can, with each function, select with the first parameter the receiver(s) that will eventually receive the packets.

    2. Yes, that is also possible but not implemented because it requires a Netty channel handler that intercepts the Interact_Entity packet.
    And since it has nothing to do with displaying and creating NPC's, there is no use in adding it.
    Also, in order to implement this, you need to make custom event handlers that catch this and distribute it over the registered listeners.
    But, nevertheless, ProtocolLib supports this and implemented it in its code.

    In case you want to do it yourself, I can send you the required code for 1.17 to do it yourself.
    If you do, then let me know.
  15. Ok, that's very cool.
    But I've found another solution for interacting stuff, but thanks anyway!

    Keep up the good work

    With kind regards
  16. Uhm heyho it's me again, i've got a little problem

    I have nms (not API) included in my project, but its always throwing this error
    - I have the newest version of spigot 1.17.1, I ran buildtools

    2. I had written a little code for the interact entity thing but it doesn't work could you send it to me maybe per discord, would be very helpful Gamedude_TV#0208
    #36 Gamedude_TV, Jul 26, 2021
    Last edited: Jul 26, 2021
  17. 1. The error cannot have something to do with the version switch (1.17 -> 1.17.1).

    Here are the changes:

    Code (Java):
    public PacketPlayOutEntityDestroy(int i) {
        this.entityId = i;

    public PacketPlayOutEntityDestroy(PacketDataSerializer packetdataserializer) {
        this.entityId = packetdataserializer.j();
    Code (Java):
    public PacketPlayOutEntityDestroy(IntList intlist) {
        this.entityIds = new IntArrayList(intlist);

    public PacketPlayOutEntityDestroy(int... aint) {
        this.entityIds = new IntArrayList(aint);
    And since my method
    Code (Java):
    return new PacketPlayOutEntityDestroy(this.entityID);
    Is both supportable by the single integer and the vararg integer, this problem is not spigot related.

    2. This is a sample code of entity interaction.
    Code (Java):
    Player player = (Player) sender;
    CraftPlayer cp = (CraftPlayer) player;
    cp.getHandle().b.a.k.pipeline().addBefore("packet_handler", "{pick any unique name for your handler}", new ChannelDuplexHandler() {
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            super.channelRead(ctx, msg);

            if(msg instanceof PacketPlayInUseEntity) {
                PacketPlayInUseEntity packet = (PacketPlayInUseEntity) msg;
                Field entityIdField = packet.getClass().getDeclaredField("a");
                Field actionField = packet.getClass().getDeclaredField("b");


                int entityId = entityIdField.getInt(packet);
                boolean usingSecondaryAction = packet.b();
                String actionName = switch (actionField.get(packet).getClass().getName()) {
                    case "$e": {
                        yield "INTERACT_AT";
                    case "$d": {
                        yield "INTERACT";
                    case "$1": {
                        yield "ATTACK";
                    default: {
                        yield null;

                if(entityId ==  npcId && actionName.equals("INTERACT")) {
                    Bukkit.broadcastMessage("have you clicked me?");
    Note: the switch yield is an available function in Java 13 and above.

    Update: I realized that you used the maven repo, which is already compiled to that specific contructor.
    So to fix this you can, for now, use the GitHub source code and copy it in a file manually.
    #37 DanielTheDev, Jul 26, 2021
    Last edited: Jul 26, 2021
  18. I try to build my project, but something went wrong. How i can fix that?[​IMG]
  19. Set the project SDK (Java version for coding) to 16.
    And also, my code is compiled for Spigot 1.17 which only runs on Java 16. See here
    See Java major versions
  20. Brilliant resource. Would it be possible to have the NPC walk to a location etc with this? Not sure how to do it with this packet system.