yfoo Posted May 15 Share Posted May 15 (edited) Painter classes that allows a user to select any entities through the paint. 1. Copy this class into your project, Adjust the package if needed. package Paint; import org.osbot.rs07.api.EntityAPI; import org.osbot.rs07.api.filter.Filter; import org.osbot.rs07.api.map.Position; import org.osbot.rs07.api.model.Entity; import org.osbot.rs07.canvas.paint.Painter; import org.osbot.rs07.input.mouse.BotMouseListener; import org.osbot.rs07.script.MethodProvider; import org.osbot.rs07.script.Script; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.geom.Area; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; public abstract class EntitySelectionPainter<T extends Entity> extends BotMouseListener implements Painter { private final EntityAPI<T> entityAPI; private final Color FINISH_SELECTION_BG = new Color(25, 240, 25, 156); private final Color ALPHA_GREEN = new Color(25, 240, 25, 20); private final Color ALPHA_RED = new Color(250, 25, 25, 20); private final Filter<T> validEntityFilter; private final Script script; private List<T> visibleEntities; private final HashSet<T> selectedEntities; private final boolean isSingleSelection; private int frameCounter = 0; private Rectangle finishSelectionRect; private boolean isSelectionComplete = false; private final static String CONFIRM_SELECTION = "Confirm Selections"; protected EntitySelectionPainter(Script script, Filter<T> validEntityFilter, EntityAPI<T> entityAPI, boolean isSingleSelection) { this.entityAPI = entityAPI; this.script = script; this.validEntityFilter = validEntityFilter; this.selectedEntities = new HashSet<>(); this.isSingleSelection = isSingleSelection; script.getBot().addPainter(this); script.getBot().addMouseListener(this); } @Override public void onPaint(Graphics2D g2d) { setFinishSelectionRectangle(g2d); frameCounter += 1; if (frameCounter % 100 == 0) { queryValidEntities(); script.log("Found " + visibleEntities.size()); } for (T entity : visibleEntities) { if(entity == null) continue; Area entityOutline = getEntityOutline(entity); if(entityOutline != null) { g2d.setColor(selectedEntities.contains(entity) ? Color.GREEN : Color.RED); g2d.draw(entityOutline); g2d.setColor(selectedEntities.contains(entity) ? ALPHA_GREEN : ALPHA_RED); g2d.fill(entityOutline); continue; } Shape positionOutline = getEntityPositionShape(entity); if(positionOutline != null) { g2d.setColor(selectedEntities.contains(entity) ? Color.GREEN : Color.RED); g2d.draw(positionOutline); } } drawFinishSelectionRectangle(g2d); } @Override public void checkMouseEvent(MouseEvent mouseEvent) { if (mouseEvent.getID() != MouseEvent.MOUSE_PRESSED || mouseEvent.getButton() != MouseEvent.BUTTON1) { return; } Point clickPt = mouseEvent.getPoint(); if (finishSelectionRect != null && finishSelectionRect.contains(clickPt)) { isSelectionComplete = true; mouseEvent.consume(); return; } for (T entity : visibleEntities) { Rectangle entityBoundingBox = getEntityBoundingBox(entity); // Draw the outline of the tile (position) for an NPC. If too many players interact with a NPC at any point // It will disappear. ex: splashing host @ Ardy knights. boolean cannotGetBB = entityBoundingBox == null; if (cannotGetBB) script.warn(String.format("Entity bounding box is null (%s @ %s). Attempting to use Position Shape.", entity.getName(), entity.getPosition())); if (!cannotGetBB && entityBoundingBox.contains(clickPt)) { mouseEvent.consume(); userClickedEntityHelper(entity); continue; } Shape npcPositionOutline = getEntityPositionShape(entity); if(npcPositionOutline == null) { script.warn(String.format("Unable to get the Entity Position Shape for %s @ %s", entity.getName(), entity.getPosition())); continue; } if(npcPositionOutline.contains(clickPt)) { mouseEvent.consume(); userClickedEntityHelper(entity); } } } public ArrayList<T> awaitSelectedEntities() throws InterruptedException { while (!isSelectionComplete) { MethodProvider.sleep(1000); } if (selectedEntities.isEmpty()) { script.warn("Nothing was selected!"); } cleanup(); return new ArrayList<>(selectedEntities); } private void userClickedEntityHelper(T entity) { if(isSingleSelection) { boolean isDeselect = selectedEntities.contains(entity); selectedEntities.clear(); if(!isDeselect) selectedEntities.add(entity); return; } if (selectedEntities.contains(entity)) { script.log(String.format("Removing entity (id: %d)", entity.getId())); selectedEntities.remove(entity); } else { script.log(String.format("Added entity (id: %d)", entity.getId())); selectedEntities.add(entity); } } private void setFinishSelectionRectangle(Graphics2D g2d) { if(finishSelectionRect != null) { return; } FontMetrics metrics = g2d.getFontMetrics(); int width = metrics.stringWidth(CONFIRM_SELECTION) + 30; int numLines = CONFIRM_SELECTION.split("\n").length; int height = metrics.getHeight() * numLines + 30; finishSelectionRect = new Rectangle(0, 0, width, height); } private void drawFinishSelectionRectangle(Graphics2D g2d) { g2d.setColor(FINISH_SELECTION_BG); g2d.fill(finishSelectionRect); FontMetrics metrics = g2d.getFontMetrics(); int textX = finishSelectionRect.x + 15; int textY = finishSelectionRect.y + 15 + metrics.getAscent(); g2d.setColor(Color.WHITE); g2d.drawString(CONFIRM_SELECTION, textX, textY); } private void queryValidEntities() { //noinspection unchecked visibleEntities = entityAPI.filter(validEntityFilter).stream().distinct().collect(Collectors.toList()); if (visibleEntities.isEmpty()) { script.log("Found no NPCs with supplied filter"); } } private Rectangle getEntityBoundingBox(T entity) { return entity.getModel().getBoundingBox(entity.getGridX(), entity.getGridY(), entity.getZ()); } private Shape getEntityPositionShape(T entity) { Position position = entity.getPosition(); if (position != null) { return position.getPolygon(script.getBot()); } return null; } private Area getEntityOutline(T entity) { return entity.getModel().getArea(entity.getGridX(), entity.getGridY(), entity.getZ()); } public void cleanup() { script.getBot().removePainter(this); script.getBot().removeMouseListener(this); } } 2. Extend EntitySelectionPainter. ex: For RS2Objects (and its implementations GroundDecoration, InteractableObject, WallDecoration, WallObject) package Paint; import org.osbot.rs07.api.Objects; import org.osbot.rs07.api.filter.Filter; import org.osbot.rs07.api.model.RS2Object; import org.osbot.rs07.script.Script; public class Rs2ObjectSelectionPainter extends EntitySelectionPainter<RS2Object> { public Rs2ObjectSelectionPainter(Script script, Filter<RS2Object> validEntityFilter, Objects objects, boolean isSingleSelection) { super(script, validEntityFilter, objects, isSingleSelection); } } ex: For NPCs package Paint; import org.osbot.rs07.api.NPCS; import org.osbot.rs07.api.filter.Filter; import org.osbot.rs07.api.model.NPC; import org.osbot.rs07.script.Script; public class NPCSelectionPainter extends EntitySelectionPainter<NPC> { public NPCSelectionPainter(Script script, Filter<NPC> validEntityFilter, NPCS npcs, boolean isSingleSelection) { super(script, validEntityFilter, npcs, isSingleSelection); } } 3. Use in your project. I put this in onStart(). You will need a filter to only highlight applicable Entities for your script. This below example is from a construction script, therefore only objects that have the "Build" action will be highlighted. The isSingleSelection boolean controls if the user can only select 1 entity at a time. ex: A construction script may only want to select 1 larder build space. Rs2ObjectSelectionPainter buildSpotSelectionPainter; @Override public void onStart() throws InterruptedException { super.onStart(); buildSpotSelectionPainter = new Rs2ObjectSelectionPainter(this, object -> object.hasAction("Build"), objects, false); ArrayList<RS2Object> selectedObjects = buildSpotSelectionPainter.awaitSelectedEntities(); log("# objects: " + selectedObjects.size()); for (RS2Object e: selectedObjects) { log(e.getName() + " " + e.getPosition()); } } Edited May 15 by yfoo 1 Quote Link to comment Share on other sites More sharing options...
yfoo Posted May 15 Author Share Posted May 15 Don't forget to call cleanup() in onStop(), this function removes the painter as both a mouse listener and a painter from osbot. @Override public void onStop() throws InterruptedException { super.onStop(); if(buildSpotSelectionPainter != null) buildSpotSelectionPainter.cleanup(); Instantiation of the painter object should be at the top of your onStart(). There can be a race condition where script.stop() is called before painter construction. script.stop() then triggers onStop before onStart creates that painter. The result is that the painter is never removed from the canvas. Quote Link to comment Share on other sites More sharing options...
Czar Posted May 15 Share Posted May 15 Amazing, great work. I did mine a different way but I appreciate how you queried the entities in onPaint, I was always wary of placing code in there since it's run on a different thread Quote Link to comment Share on other sites More sharing options...
yfoo Posted May 15 Author Share Posted May 15 4 hours ago, Czar said: Amazing, great work. I did mine a different way but I appreciate how you queried the entities in onPaint, I was always wary of placing code in there since it's run on a different thread That's a good idea, Thanks. I never really liked having to preform queries every 100 frames as it was a hack to prevent lag. Probably going to change it to use a periodic executor service in the constructor that does the query. Quote Link to comment Share on other sites More sharing options...
Wacky Jacky Posted June 21 Share Posted June 21 Awesome! I have to take a look at my shopper how I handled it, probably very wacky... nice going Quote Link to comment Share on other sites More sharing options...