Jump to content

Find NPC closest to Position


SeanAvak

Recommended Posts

Hiya folks,

Didn't immediately see a way of implementing this (I'm fairly new to the API) so I went about implementing it myself. Here's a snippet that takes a Position to minimize the distance from, as well as a String carrying the name of the NPC using Java 8 Streams. It returns a list of all NPCs that occupy the position of the NPC you've searched for. Additionally I've included an offset, so for the sake of loops (if the closest NPC isn't suitable, say it is already in combat) then you can add 1 and it will bring back the next closest position. I would imagine it is quite easy to adjust the returned object type, such as for other Entities.

This is being used with my first script, a Tutorial Island implementation, so I make no guarantees for actual in-game use, due to the seemingly large differences between Tutorial Island and the actual main game. Hope it helps.

    public static List<NPC> closestToPosition(Position position, String entity){
        List<NPC> npcs = getNpcs().getAll();
        
        List<Position> npcPositions = npcs.stream().filter(c -> c.getName().equals(entity))
                .map(temp -> new Position(temp.getX(), temp.getY(), temp.getZ())
                ).sorted(Comparator.comparingInt(temp -> temp.distance(position))).collect(Collectors.toList());
        return getNpcs().get(npcPositions.get(0).getX(), npcPositions.get(0).getY());
    }
      
    public static List<NPC> closestToPosition(Position position, String entity, int offset){
        List<NPC> npcs = getNpcs().getAll();
        
        List<Position> npcPositions = npcs.stream().filter(c -> c.getName().equals(entity))
                .map(temp -> new Position(temp.getX(), temp.getY(), temp.getZ())
                ).sorted(Comparator.comparingInt(temp -> temp.distance(position))).collect(Collectors.toList());
        
        return getNpcs().get(npcPositions.get(offset).getX(), npcPositions.get(offset).getY());
    }

Edit: I realize this is open to IndexOutOfRange issues; shouldn't be too big of a deal to handle on your end, but if it comes back to bite me I'll update as needed.

Edited by SeanAvak
  • Like 1
Link to comment
Share on other sites

@Gunman Closest is based off (as far as I could tell) the player position, which is great for 99.9% of the cases. In my case though, for Tutorial Island, I was trying to attack the Giant rats using the bow from a specific position. I was having issues with movement, so I wanted to attack the closest rat relative to a particular Position away from the player itself; this is what I believe to be lacking in the closest method. Unless I'm blind and missing something, but it gave me a chance to mess around with Streams so hey.

  • Like 1
Link to comment
Share on other sites

Can you give me some use cases for this?

Also does this do the same thing?

    public  NPC closestToPosition(Position position, String entity){
        return   Scheme.scemeBot.getNpcs().getAll().stream().filter(s->s.getName().equals(entity)&&check(s)).reduce((a,b)->{


                    return (a.getPosition().distance(position)< b.getPosition().distance(position))?a:b;
                }

        ).get();
    }

    private  boolean check(NPC npc) {
        return !npc.isHitBarVisible() && !npc.isUnderAttack() && npc.getHealthPercent() > 0 && npc.getInteracting() == null;
    }

 

  • Like 1
Link to comment
Share on other sites

11 minutes ago, SeanAvak said:

@Gunman Closest is based off (as far as I could tell) the player position, which is great for 99.9% of the cases. In my case though, for Tutorial Island, I was trying to attack the Giant rats using the bow from a specific position. I was having issues with movement, so I wanted to attack the closest rat relative to a particular Position away from the player itself; this is what I believe to be lacking in the closest method. Unless I'm blind and missing something, but it gave me a chance to mess around with Streams so hey.

That should be able to be done using the closest method. But at the end of the day whatever works for you man. Some body might find it helpful in the future.

Link to comment
Share on other sites

@Nbacon I can at the very least give you the reason I created it in more detail, any other use cases are up to your imagination.

In Tutorial Island, the ranged section of the Giant Rats is relatively straightforward; shoot a rat, move on. I kept having issues with being unable to reach a rat, due to the stalagmites (stalacites?) being in the way, causing a dialog to pop up. I handled this, but because the rats kept being in the same general location, they were always closest to the player, and as such I'd be stuck in a loop unless I wanted to walk away.

My first attempt at rectifying this was just to walk away; by walking further along out of the way of the obstruction, I would guaranteed have a clear view of the rats, and therefore have an ideal way of attacking them. For some inexplicable reason though, I kept having issues with movement, yet didn't have any issues with interaction.

image.thumb.png.d11b328efd93e7f58575f87298188f9a.png

So my next approach was to try and attack the rats based off the smallest distance from a selected Position; a location I had previously decided upon as it was extremely close to the rats, with no obvious obstructions surrounding it. Upon looking for something that could accomplish this in closest, I didn't immediately see it. So I spent ~35 minutes working this up.

Now, instead of attacking the closest rat, this snippet takes the Position I would like to minimize distance from, and returns the rat closest to that Position. In my case, this was perfect, since I was then able to attack it, and my player would either be in range or move in such a way so that it would be.

To answer whether your snippet does the same thing, yep! Just tested it. Only differences are the offset (for which I just overloaded a method for) and that I return a List of NPCs instead of a singular one (To account for rats stacking on top of each other, but really that doesn't matter in this use case).

Link to comment
Share on other sites

10 hours ago, SeanAvak said:

Hiya folks,

Didn't immediately see a way of implementing this (I'm fairly new to the API) so I went about implementing it myself. Here's a snippet that takes a Position to minimize the distance from, as well as a String carrying the name of the NPC using Java 8 Streams. It returns a list of all NPCs that occupy the position of the NPC you've searched for. Additionally I've included an offset, so for the sake of loops (if the closest NPC isn't suitable, say it is already in combat) then you can add 1 and it will bring back the next closest position. I would imagine it is quite easy to adjust the returned object type, such as for other Entities.

This is being used with my first script, a Tutorial Island implementation, so I make no guarantees for actual in-game use, due to the seemingly large differences between Tutorial Island and the actual main game. Hope it helps.


    public static List<NPC> closestToPosition(Position position, String entity){
        List<NPC> npcs = getNpcs().getAll();
        
        List<Position> npcPositions = npcs.stream().filter(c -> c.getName().equals(entity))
                .map(temp -> new Position(temp.getX(), temp.getY(), temp.getZ())
                ).sorted(Comparator.comparingInt(temp -> temp.distance(position))).collect(Collectors.toList());
        return getNpcs().get(npcPositions.get(0).getX(), npcPositions.get(0).getY());
    }
      
    public static List<NPC> closestToPosition(Position position, String entity, int offset){
        List<NPC> npcs = getNpcs().getAll();
        
        List<Position> npcPositions = npcs.stream().filter(c -> c.getName().equals(entity))
                .map(temp -> new Position(temp.getX(), temp.getY(), temp.getZ())
                ).sorted(Comparator.comparingInt(temp -> temp.distance(position))).collect(Collectors.toList());
        
        return getNpcs().get(npcPositions.get(offset).getX(), npcPositions.get(offset).getY());
    }

Edit: I realize this is open to IndexOutOfRange issues; shouldn't be too big of a deal to handle on your end, but if it comes back to bite me I'll update as needed.

 

9 hours ago, Nbacon said:

Can you give me some use cases for this?

Also does this do the same thing?


    public  NPC closestToPosition(Position position, String entity){
        return   Scheme.scemeBot.getNpcs().getAll().stream().filter(s->s.getName().equals(entity)&&check(s)).reduce((a,b)->{


                    return (a.getPosition().distance(position)< b.getPosition().distance(position))?a:b;
                }

        ).get();
    }

    private  boolean check(NPC npc) {
        return !npc.isHitBarVisible() && !npc.isUnderAttack() && npc.getHealthPercent() > 0 && npc.getInteracting() == null;
    }

 



This would be a bit cleaner, allowing for types other than just NPC, and filters other than just entity name:

 

public <T extends Entity> Optional<T> closestToPosition(final Position position, final List<T> entities) {
    return entities.stream().min(Comparator.comparingInt(e -> position.distance(e.getPosition())));
}

 

Example Usage:

Position pos = new Position(1, 2, 3);

Optional<NPC> rat = closestToPosition(pos, getNpcs().filter(new NameFilter<>("Rat")));

 

Note that `position.distance()` is the *straight line* distance, it does not take into account any obstacles. This doesn't matter in your specific use-case, but if you wanted to account for it, then you should use getMap().realDistance(). For example:

 

public <T extends Entity> Optional<T> closestToPosition(final Position position, final List<T> entities) {
    return entities.stream().min(Comparator.comparingInt(e -> getMap().realDistance(position, e.getPosition())));
}

 

Or you can support both options using:

 

public <T extends Entity> Optional<T> closestToPosition(final Position position, final List<T> entities, final boolean realDistance) {
    final ToIntFunction<T> distanceFunc = (T e) -> (
            realDistance ? getMap().realDistance(position, e.getPosition())
                         : position.distance(e.getPosition())
    );

    return entities.stream().min(Comparator.comparingInt(distanceFunc));
}

 

If you want to include further filtering abilities within the helper function itself, that can also be achieved using:

 

public <T extends Entity> Optional<T> closestToPosition(final Position position, final List<T> entities, final boolean realDistance, final Filter<T>... entityFilter) {
    final ToIntFunction<T> distanceFunc = (T e) -> (
            realDistance ? getMap().realDistance(position, e.getPosition())
                         : position.distance(e.getPosition())
    );
        
    final Predicate<T> aggregatedEntityFilter = e -> Stream.of(entityFilter).allMatch(filter -> filter.match(e));

    return entities.stream().filter(aggregatedEntityFilter).min(Comparator.comparingInt(distanceFunc));
}

 

Example usage:

 

Position pos = new Position(1, 2, 3);

Optional<NPC> rat = closestToPosition(pos, getNpcs().getAll(), true, new NameFilter<>("Rat"));

 

  • Like 7
  • Heart 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...