Jump to content

Select Entities through Paint


yfoo

Recommended Posts

Posted (edited)

Painter classes that allows a user to select any entities through the paint. 
Capture.jpg

Capture-pickpocket.jpg

 

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 by yfoo
  • Like 1
Link to comment
Share on other sites

  • yfoo changed the title to Select Entities through Paint

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. 

Link to comment
Share on other sites

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. 

Link to comment
Share on other sites

  • 1 month later...

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...