SeanAvak Posted July 22, 2020 Share Posted July 22, 2020 (edited) 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 July 22, 2020 by SeanAvak 1 Quote Link to comment Share on other sites More sharing options...
Gunman Posted July 22, 2020 Share Posted July 22, 2020 @SeanAvak Did you not know of the api method closest? Or is there some other use for this I am not seeing? Quote Link to comment Share on other sites More sharing options...
SeanAvak Posted July 22, 2020 Author Share Posted July 22, 2020 @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. 1 Quote Link to comment Share on other sites More sharing options...
Nbacon Posted July 22, 2020 Share Posted July 22, 2020 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; } 1 Quote Link to comment Share on other sites More sharing options...
Gunman Posted July 22, 2020 Share Posted July 22, 2020 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. Quote Link to comment Share on other sites More sharing options...
SeanAvak Posted July 22, 2020 Author Share Posted July 22, 2020 @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. 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). Quote Link to comment Share on other sites More sharing options...
Nbacon Posted July 22, 2020 Share Posted July 22, 2020 30 minutes ago, SeanAvak said: . Thank for your reply. [Super informative and also odd flex with those semicolons] 30 minutes ago, SeanAvak said: So I spent ~35 minutes working this up Programing with higher order funtions gets easier the more you use them. So keep it up =). 1 Quote Link to comment Share on other sites More sharing options...
Explv Posted July 22, 2020 Share Posted July 22, 2020 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")); 7 1 Quote Link to comment Share on other sites More sharing options...