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());
}
}