Add NPC unaggression timer
This commit is contained in:
@@ -114,8 +114,7 @@ public class WorldArea
|
|||||||
return Integer.MAX_VALUE;
|
return Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point distances = getAxisDistances(other);
|
return distanceTo2D(other);
|
||||||
return Math.max(distances.getX(), distances.getY());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +128,29 @@ public class WorldArea
|
|||||||
return distanceTo(new WorldArea(other, 1, 1));
|
return distanceTo(new WorldArea(other, 1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the shortest distance to another area while ignoring the plane.
|
||||||
|
*
|
||||||
|
* @param other the passed area
|
||||||
|
* @return the distance
|
||||||
|
*/
|
||||||
|
public int distanceTo2D(WorldArea other)
|
||||||
|
{
|
||||||
|
Point distances = getAxisDistances(other);
|
||||||
|
return Math.max(distances.getX(), distances.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the shortest distance to a world coordinate.
|
||||||
|
*
|
||||||
|
* @param other the passed coordinate
|
||||||
|
* @return the distance
|
||||||
|
*/
|
||||||
|
public int distanceTo2D(WorldPoint other)
|
||||||
|
{
|
||||||
|
return distanceTo2D(new WorldArea(other, 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether this area is within melee distance of another.
|
* Checks whether this area is within melee distance of another.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -0,0 +1,454 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.api.geometry;
|
||||||
|
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.GeneralPath;
|
||||||
|
import java.awt.geom.PathIterator;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class Geometry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find the point where two lines intersect.
|
||||||
|
*
|
||||||
|
* @param x1 X coordinate of the first endpoint of the first line.
|
||||||
|
* @param y1 Y coordinate of the first endpoint of the first line.
|
||||||
|
* @param x2 X coordinate of the second endpoint of the first line.
|
||||||
|
* @param y2 Y coordinate of the second endpoint of the first line.
|
||||||
|
* @param x3 X coordinate of the first endpoint of the second line.
|
||||||
|
* @param y3 Y coordinate of the first endpoint of the second line.
|
||||||
|
* @param x4 X coordinate of the second endpoint of the second line.
|
||||||
|
* @param y4 Y coordinate of the second endpoint of the second line.
|
||||||
|
* @return The intersection point of the lines, or null if the lines don't intersect.
|
||||||
|
*/
|
||||||
|
public static Point2D.Float lineIntersectionPoint(
|
||||||
|
float x1, float y1, float x2, float y2,
|
||||||
|
float x3, float y3, float x4, float y4)
|
||||||
|
{
|
||||||
|
// https://stackoverflow.com/a/1968345
|
||||||
|
|
||||||
|
float p1x = x2 - x1;
|
||||||
|
float p1y = y2 - y1;
|
||||||
|
float p2x = x4 - x3;
|
||||||
|
float p2y = y4 - y3;
|
||||||
|
|
||||||
|
float s = (-p1y * (x1 - x3) + p1x * (y1 - y3)) / (-p2x * p1y + p1x * p2y);
|
||||||
|
float t = ( p2x * (y1 - y3) - p2y * (x1 - x3)) / (-p2x * p1y + p1x * p2y);
|
||||||
|
|
||||||
|
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
|
||||||
|
{
|
||||||
|
float x = x1 + (t * p1x);
|
||||||
|
float y = y1 + (t * p1y);
|
||||||
|
return new Point2D.Float(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No intersection
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the intersection points between a Shape and a line.
|
||||||
|
*
|
||||||
|
* @param shape The shape.
|
||||||
|
* @param x1 X coordinate of the first endpoint of the line.
|
||||||
|
* @param y1 Y coordinate of the first endpoint of the line.
|
||||||
|
* @param x2 X coordinate of the second endpoint of the line.
|
||||||
|
* @param y2 Y coordinate of the second endpoint of the line.
|
||||||
|
* @return A list with the intersection points.
|
||||||
|
*/
|
||||||
|
public static List<Point2D.Float> intersectionPoints(Shape shape, float x1, float y1, float x2, float y2)
|
||||||
|
{
|
||||||
|
List<Point2D.Float> intersections = new LinkedList<>();
|
||||||
|
|
||||||
|
PathIterator it = shape.getPathIterator(new AffineTransform());
|
||||||
|
float[] coords = new float[2];
|
||||||
|
float[] prevCoords = new float[2];
|
||||||
|
float[] start = new float[2];
|
||||||
|
while (!it.isDone())
|
||||||
|
{
|
||||||
|
int type = it.currentSegment(coords);
|
||||||
|
if (type == PathIterator.SEG_MOVETO)
|
||||||
|
{
|
||||||
|
start[0] = coords[0];
|
||||||
|
start[1] = coords[1];
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_LINETO)
|
||||||
|
{
|
||||||
|
Point2D.Float intersection = lineIntersectionPoint(
|
||||||
|
prevCoords[0], prevCoords[1], coords[0], coords[1], x1, y1, x2, y2);
|
||||||
|
if (intersection != null)
|
||||||
|
{
|
||||||
|
intersections.add(intersection);
|
||||||
|
}
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
Point2D.Float intersection = lineIntersectionPoint(
|
||||||
|
coords[0], coords[1], start[0], start[1], x1, y1, x2, y2);
|
||||||
|
if (intersection != null)
|
||||||
|
{
|
||||||
|
intersections.add(intersection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the points in a path according to a method.
|
||||||
|
*
|
||||||
|
* @param it The iterator of the path to change the points on.
|
||||||
|
* @param method The method to use to transform the points. Takes a float[2] array with x and y coordinates as parameter.
|
||||||
|
* @return The transformed path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath transformPath(PathIterator it, Consumer<float[]> method)
|
||||||
|
{
|
||||||
|
GeneralPath path = new GeneralPath();
|
||||||
|
float[] coords = new float[2];
|
||||||
|
while (!it.isDone())
|
||||||
|
{
|
||||||
|
int type = it.currentSegment(coords);
|
||||||
|
if (type == PathIterator.SEG_MOVETO)
|
||||||
|
{
|
||||||
|
method.accept(coords);
|
||||||
|
path.moveTo(coords[0], coords[1]);
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_LINETO)
|
||||||
|
{
|
||||||
|
method.accept(coords);
|
||||||
|
path.lineTo(coords[0], coords[1]);
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
path.closePath();
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the points in a path according to a method.
|
||||||
|
*
|
||||||
|
* @param path The path to change the points on.
|
||||||
|
* @param method The method to use to transform the points. Takes a float[2] array with x and y coordinates as parameter.
|
||||||
|
* @return The transformed path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath transformPath(GeneralPath path, Consumer<float[]> method)
|
||||||
|
{
|
||||||
|
return transformPath(path.getPathIterator(new AffineTransform()), method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a line into smaller segments and appends the segments to a path.
|
||||||
|
*
|
||||||
|
* @param path The path to append lines to.
|
||||||
|
* @param segmentLength The desired length to use for the segmented lines.
|
||||||
|
* @param x1 X coordinate of the first endpoint of the line.
|
||||||
|
* @param y1 Y coordinate of the first endpoint of the line.
|
||||||
|
* @param x2 X coordinate of the second endpoint of the line.
|
||||||
|
* @param y2 Y coordinate of the second endpoint of the line.
|
||||||
|
*/
|
||||||
|
private static void appendSegmentLines(GeneralPath path, float segmentLength,
|
||||||
|
float x1, float y1, float x2, float y2)
|
||||||
|
{
|
||||||
|
float x = x1;
|
||||||
|
float y = y1;
|
||||||
|
float angle = (float)Math.atan2(y2 - y1, x2 - x1);
|
||||||
|
float dx = (float)Math.cos(angle) * segmentLength;
|
||||||
|
float dy = (float)Math.sin(angle) * segmentLength;
|
||||||
|
float length = (float)Math.hypot(x2 - x1, y2 - y1);
|
||||||
|
int steps = (int)((length - 1e-4) / segmentLength);
|
||||||
|
for (int i = 0; i < steps; i++)
|
||||||
|
{
|
||||||
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a path into smaller segments.
|
||||||
|
* For example, calling this on a path with a line of length 6, with desired
|
||||||
|
* segment length of 2, would split the path into 3 consecutive lines of length 2.
|
||||||
|
*
|
||||||
|
* @param it The iterator of the path to modify.
|
||||||
|
* @param segmentLength The desired length to use for the segments.
|
||||||
|
* @return The modified path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath splitIntoSegments(PathIterator it, float segmentLength)
|
||||||
|
{
|
||||||
|
GeneralPath newPath = new GeneralPath();
|
||||||
|
float[] prevCoords = new float[2];
|
||||||
|
float[] coords = new float[2];
|
||||||
|
float[] startCoords = new float[2];
|
||||||
|
while (!it.isDone())
|
||||||
|
{
|
||||||
|
int type = it.currentSegment(coords);
|
||||||
|
if (type == PathIterator.SEG_MOVETO)
|
||||||
|
{
|
||||||
|
startCoords[0] = coords[0];
|
||||||
|
startCoords[1] = coords[1];
|
||||||
|
newPath.moveTo(coords[0], coords[1]);
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_LINETO)
|
||||||
|
{
|
||||||
|
appendSegmentLines(newPath, segmentLength, prevCoords[0], prevCoords[1], coords[0], coords[1]);
|
||||||
|
newPath.lineTo(coords[0], coords[1]);
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
appendSegmentLines(newPath, segmentLength, coords[0], coords[1], startCoords[0], startCoords[1]);
|
||||||
|
newPath.closePath();
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a path into smaller segments.
|
||||||
|
* For example, calling this on a path with a line of length 6, with desired
|
||||||
|
* segment length of 2, would split the path into 3 consecutive lines of length 2.
|
||||||
|
*
|
||||||
|
* @param path The path to modify.
|
||||||
|
* @param segmentLength The desired length to use for the segments.
|
||||||
|
* @return The modified path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath splitIntoSegments(GeneralPath path, float segmentLength)
|
||||||
|
{
|
||||||
|
return splitIntoSegments(path.getPathIterator(new AffineTransform()), segmentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes lines from a path according to a method.
|
||||||
|
*
|
||||||
|
* @param it The iterator of the path to filter.
|
||||||
|
* @param method The method to use to decide which lines to remove. Takes two float[2] arrays with x and y coordinates of the endpoints of the line. Lines for which the predicate returns false are removed.
|
||||||
|
* @return The filtered path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath filterPath(PathIterator it, BiPredicate<float[], float[]> method)
|
||||||
|
{
|
||||||
|
GeneralPath newPath = new GeneralPath();
|
||||||
|
float[] prevCoords = new float[2];
|
||||||
|
float[] coords = new float[2];
|
||||||
|
float[] start = new float[2];
|
||||||
|
boolean shouldMoveNext = false;
|
||||||
|
while (!it.isDone())
|
||||||
|
{
|
||||||
|
int type = it.currentSegment(coords);
|
||||||
|
if (type == PathIterator.SEG_MOVETO)
|
||||||
|
{
|
||||||
|
start[0] = coords[0];
|
||||||
|
start[1] = coords[1];
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
shouldMoveNext = true;
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_LINETO)
|
||||||
|
{
|
||||||
|
if (method.test(prevCoords, coords))
|
||||||
|
{
|
||||||
|
if (shouldMoveNext)
|
||||||
|
{
|
||||||
|
newPath.moveTo(prevCoords[0], prevCoords[1]);
|
||||||
|
shouldMoveNext = false;
|
||||||
|
}
|
||||||
|
newPath.lineTo(coords[0], coords[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shouldMoveNext = true;
|
||||||
|
}
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
if (shouldMoveNext)
|
||||||
|
{
|
||||||
|
newPath.moveTo(prevCoords[0], prevCoords[1]);
|
||||||
|
}
|
||||||
|
if (method.test(prevCoords, start))
|
||||||
|
{
|
||||||
|
newPath.lineTo(start[0], start[1]);
|
||||||
|
}
|
||||||
|
shouldMoveNext = false;
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes lines from a path according to a method.
|
||||||
|
*
|
||||||
|
* @param path The path to filter.
|
||||||
|
* @param method The method to use to decide which lines to remove. Takes two float[2] arrays with x and y coordinates of the endpoints of the line. Lines for which the predicate returns false are removed.
|
||||||
|
* @return The filtered path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath filterPath(GeneralPath path, BiPredicate<float[], float[]> method)
|
||||||
|
{
|
||||||
|
return filterPath(path.getPathIterator(new AffineTransform()), method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes lines from a path that lie outside the clipping area and cuts
|
||||||
|
* lines intersecting with the clipping area so the resulting lines
|
||||||
|
* lie within the clipping area.
|
||||||
|
*
|
||||||
|
* @param it The iterator of the path to clip.
|
||||||
|
* @param shape The clipping area to clip with.
|
||||||
|
* @return The clipped path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath clipPath(PathIterator it, Shape shape)
|
||||||
|
{
|
||||||
|
GeneralPath newPath = new GeneralPath();
|
||||||
|
float[] prevCoords = new float[2];
|
||||||
|
float[] coords = new float[2];
|
||||||
|
float[] start = new float[2];
|
||||||
|
float[] nextMove = new float[2];
|
||||||
|
boolean shouldMove = false;
|
||||||
|
boolean wasInside = false;
|
||||||
|
while (!it.isDone())
|
||||||
|
{
|
||||||
|
int type = it.currentSegment(coords);
|
||||||
|
if (type == PathIterator.SEG_MOVETO)
|
||||||
|
{
|
||||||
|
start[0] = coords[0];
|
||||||
|
start[1] = coords[1];
|
||||||
|
wasInside = shape.contains(coords[0], coords[1]);
|
||||||
|
if (wasInside)
|
||||||
|
{
|
||||||
|
nextMove[0] = coords[0];
|
||||||
|
nextMove[1] = coords[1];
|
||||||
|
shouldMove = true;
|
||||||
|
}
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
else if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
if (type == PathIterator.SEG_CLOSE)
|
||||||
|
{
|
||||||
|
coords[0] = start[0];
|
||||||
|
coords[1] = start[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Point2D.Float> intersections = intersectionPoints(shape, prevCoords[0], prevCoords[1], coords[0], coords[1]);
|
||||||
|
intersections.sort((a, b) ->
|
||||||
|
{
|
||||||
|
double diff = a.distance(prevCoords[0], prevCoords[1]) - b.distance(prevCoords[0], prevCoords[1]);
|
||||||
|
if (diff < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (diff > 0)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (Point2D.Float intersection : intersections)
|
||||||
|
{
|
||||||
|
if (wasInside)
|
||||||
|
{
|
||||||
|
if (shouldMove)
|
||||||
|
{
|
||||||
|
newPath.moveTo(nextMove[0], nextMove[1]);
|
||||||
|
shouldMove = false;
|
||||||
|
}
|
||||||
|
newPath.lineTo(intersection.getX(), intersection.getY());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextMove[0] = intersection.x;
|
||||||
|
nextMove[1] = intersection.y;
|
||||||
|
shouldMove = true;
|
||||||
|
}
|
||||||
|
wasInside = !wasInside;
|
||||||
|
prevCoords[0] = intersection.x;
|
||||||
|
prevCoords[1] = intersection.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasInside = shape.contains(coords[0], coords[1]);
|
||||||
|
if (wasInside)
|
||||||
|
{
|
||||||
|
if (shouldMove)
|
||||||
|
{
|
||||||
|
newPath.moveTo(nextMove[0], nextMove[1]);
|
||||||
|
shouldMove = false;
|
||||||
|
}
|
||||||
|
newPath.lineTo(coords[0], coords[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextMove[0] = coords[0];
|
||||||
|
nextMove[1] = coords[1];
|
||||||
|
shouldMove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevCoords[0] = coords[0];
|
||||||
|
prevCoords[1] = coords[1];
|
||||||
|
}
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes lines from a path that lie outside the clipping area and cuts
|
||||||
|
* lines intersecting with the clipping area so the resulting lines
|
||||||
|
* lie within the clipping area.
|
||||||
|
*
|
||||||
|
* @param path The path to clip.
|
||||||
|
* @param shape The clipping area to clip with.
|
||||||
|
* @return The clipped path.
|
||||||
|
*/
|
||||||
|
public static GeneralPath clipPath(GeneralPath path, Shape shape)
|
||||||
|
{
|
||||||
|
return clipPath(path.getPathIterator(new AffineTransform()), shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.npcunaggroarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.ui.overlay.infobox.Timer;
|
||||||
|
|
||||||
|
class AggressionTimer extends Timer
|
||||||
|
{
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean visible;
|
||||||
|
|
||||||
|
AggressionTimer(Duration duration, BufferedImage image, Plugin plugin, boolean visible)
|
||||||
|
{
|
||||||
|
super(duration.toMillis(), ChronoUnit.MILLIS, image, plugin);
|
||||||
|
setTooltip("Time until NPCs become unaggressive");
|
||||||
|
this.visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getTextColor()
|
||||||
|
{
|
||||||
|
Duration timeLeft = Duration.between(Instant.now(), getEndTime());
|
||||||
|
|
||||||
|
if (timeLeft.getSeconds() < 60)
|
||||||
|
{
|
||||||
|
return Color.RED.brighter();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean render()
|
||||||
|
{
|
||||||
|
return visible && super.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.npcunaggroarea;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigGroup;
|
||||||
|
import net.runelite.client.config.ConfigItem;
|
||||||
|
|
||||||
|
@ConfigGroup("npcUnaggroArea")
|
||||||
|
public interface NpcAggroAreaConfig extends Config
|
||||||
|
{
|
||||||
|
String CONFIG_GROUP = "npcUnaggroArea";
|
||||||
|
String CONFIG_CENTER1 = "center1";
|
||||||
|
String CONFIG_CENTER2 = "center2";
|
||||||
|
String CONFIG_LOCATION = "location";
|
||||||
|
String CONFIG_DURATION = "duration";
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "npcUnaggroAlwaysActive",
|
||||||
|
name = "Always active",
|
||||||
|
description = "Always show this plugins overlays<br>Otherwise, they will only be shown when any NPC name matches the list",
|
||||||
|
position = 1
|
||||||
|
)
|
||||||
|
default boolean alwaysActive()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "npcUnaggroNames",
|
||||||
|
name = "NPC names",
|
||||||
|
description = "Enter names of NPCs where you wish to use this plugin",
|
||||||
|
position = 2
|
||||||
|
)
|
||||||
|
default String npcNamePatterns()
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "npcUnaggroShowTimer",
|
||||||
|
name = "Show timer",
|
||||||
|
description = "Display a timer until NPCs become unaggressive",
|
||||||
|
position = 3
|
||||||
|
)
|
||||||
|
default boolean showTimer()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "npcUnaggroShowAreaLines",
|
||||||
|
name = "Show area lines",
|
||||||
|
description = "Display lines, when walked past, the unaggressive timer resets",
|
||||||
|
position = 4
|
||||||
|
)
|
||||||
|
default boolean showAreaLines()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "npcUnaggroAreaColor",
|
||||||
|
name = "Area lines colour",
|
||||||
|
description = "Choose colour to use for marking NPC unaggressive area",
|
||||||
|
position = 5
|
||||||
|
)
|
||||||
|
default Color aggroAreaColor()
|
||||||
|
{
|
||||||
|
return Color.YELLOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.npcunaggroarea;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import net.runelite.client.ui.overlay.Overlay;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayPriority;
|
||||||
|
import net.runelite.client.ui.overlay.components.LineComponent;
|
||||||
|
import net.runelite.client.ui.overlay.components.PanelComponent;
|
||||||
|
|
||||||
|
class NpcAggroAreaNotWorkingOverlay extends Overlay
|
||||||
|
{
|
||||||
|
private final NpcAggroAreaPlugin plugin;
|
||||||
|
private final PanelComponent panelComponent;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private NpcAggroAreaNotWorkingOverlay(NpcAggroAreaPlugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
panelComponent = new PanelComponent();
|
||||||
|
panelComponent.setPreferredSize(new Dimension(150, 0));
|
||||||
|
panelComponent.getChildren().add(LineComponent.builder()
|
||||||
|
.left("Unaggressive NPC timers will start working when you teleport far away or enter a dungeon.")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
setPriority(OverlayPriority.LOW);
|
||||||
|
setPosition(OverlayPosition.TOP_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension render(Graphics2D graphics)
|
||||||
|
{
|
||||||
|
if (!plugin.isActive() || plugin.getSafeCenters()[1] != null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return panelComponent.render(graphics);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.npcunaggroarea;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.geom.GeneralPath;
|
||||||
|
import java.time.Instant;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.Perspective;
|
||||||
|
import net.runelite.api.Point;
|
||||||
|
import net.runelite.api.coords.LocalPoint;
|
||||||
|
import net.runelite.api.geometry.Geometry;
|
||||||
|
import net.runelite.client.ui.overlay.Overlay;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayLayer;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayPriority;
|
||||||
|
|
||||||
|
class NpcAggroAreaOverlay extends Overlay
|
||||||
|
{
|
||||||
|
private static final int MAX_LOCAL_DRAW_LENGTH = 20 * Perspective.LOCAL_TILE_SIZE;
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final NpcAggroAreaConfig config;
|
||||||
|
private final NpcAggroAreaPlugin plugin;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private NpcAggroAreaOverlay(Client client, NpcAggroAreaConfig config, NpcAggroAreaPlugin plugin)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.config = config;
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||||
|
setPriority(OverlayPriority.LOW);
|
||||||
|
setPosition(OverlayPosition.DYNAMIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension render(Graphics2D graphics)
|
||||||
|
{
|
||||||
|
if (!plugin.isActive() || plugin.getSafeCenters()[1] == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneralPath lines = plugin.getLinesToDisplay()[client.getPlane()];
|
||||||
|
if (lines == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color outlineColor = config.aggroAreaColor();
|
||||||
|
AggressionTimer timer = plugin.getCurrentTimer();
|
||||||
|
if (timer == null || Instant.now().compareTo(timer.getEndTime()) < 0)
|
||||||
|
{
|
||||||
|
outlineColor = new Color(
|
||||||
|
outlineColor.getRed(),
|
||||||
|
outlineColor.getGreen(),
|
||||||
|
outlineColor.getBlue(),
|
||||||
|
100);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPath(graphics, lines, outlineColor);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderPath(Graphics2D graphics, GeneralPath path, Color color)
|
||||||
|
{
|
||||||
|
LocalPoint playerLp = client.getLocalPlayer().getLocalLocation();
|
||||||
|
Rectangle viewArea = new Rectangle(
|
||||||
|
playerLp.getX() - MAX_LOCAL_DRAW_LENGTH,
|
||||||
|
playerLp.getY() - MAX_LOCAL_DRAW_LENGTH,
|
||||||
|
MAX_LOCAL_DRAW_LENGTH * 2,
|
||||||
|
MAX_LOCAL_DRAW_LENGTH * 2);
|
||||||
|
|
||||||
|
graphics.setColor(color);
|
||||||
|
graphics.setStroke(new BasicStroke(1));
|
||||||
|
|
||||||
|
path = Geometry.clipPath(path, viewArea);
|
||||||
|
path = Geometry.filterPath(path, (p1, p2) ->
|
||||||
|
Perspective.localToCanvas(client, new LocalPoint((int)p1[0], (int)p1[1]), client.getPlane()) != null &&
|
||||||
|
Perspective.localToCanvas(client, new LocalPoint((int)p2[0], (int)p2[1]), client.getPlane()) != null);
|
||||||
|
path = Geometry.transformPath(path, coords ->
|
||||||
|
{
|
||||||
|
Point point = Perspective.localToCanvas(client, new LocalPoint((int)coords[0], (int)coords[1]), client.getPlane());
|
||||||
|
coords[0] = point.getX();
|
||||||
|
coords[1] = point.getY();
|
||||||
|
});
|
||||||
|
|
||||||
|
graphics.draw(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,474 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.npcunaggroarea;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import java.awt.Polygon;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.geom.Area;
|
||||||
|
import java.awt.geom.GeneralPath;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.Constants;
|
||||||
|
import net.runelite.api.ItemID;
|
||||||
|
import net.runelite.api.NPC;
|
||||||
|
import net.runelite.api.NPCComposition;
|
||||||
|
import net.runelite.api.Perspective;
|
||||||
|
import net.runelite.api.coords.LocalPoint;
|
||||||
|
import net.runelite.api.coords.WorldArea;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.api.events.ConfigChanged;
|
||||||
|
import net.runelite.api.events.GameStateChanged;
|
||||||
|
import net.runelite.api.events.GameTick;
|
||||||
|
import net.runelite.api.events.NpcSpawned;
|
||||||
|
import net.runelite.api.geometry.Geometry;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import net.runelite.client.ui.overlay.OverlayManager;
|
||||||
|
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
|
||||||
|
import net.runelite.client.util.WildcardMatcher;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@PluginDescriptor(
|
||||||
|
name = "Unaggressive NPC timer",
|
||||||
|
description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active",
|
||||||
|
tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "timer", "slayer"},
|
||||||
|
enabledByDefault = false
|
||||||
|
)
|
||||||
|
public class NpcAggroAreaPlugin extends Plugin
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
How it works: The game remembers 2 tiles. When the player goes >10 steps
|
||||||
|
away from both tiles, the oldest one is moved to under the player and the
|
||||||
|
NPC aggression timer resets.
|
||||||
|
So to first figure out where the 2 tiles are, we wait until the player teleports
|
||||||
|
a long enough distance. At that point it's very likely that the player
|
||||||
|
moved out of the radius of both tiles, which resets one of them. The other
|
||||||
|
should reset shortly after as the player starts moving around.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final int SAFE_AREA_RADIUS = 10;
|
||||||
|
private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2;
|
||||||
|
private static final int AGGRESSIVE_TIME_SECONDS = 600;
|
||||||
|
private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
|
||||||
|
private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0);
|
||||||
|
private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private NpcAggroAreaConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private NpcAggroAreaOverlay overlay;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private NpcAggroAreaNotWorkingOverlay notWorkingOverlay;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private OverlayManager overlayManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ItemManager itemManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private InfoBoxManager infoBoxManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ConfigManager configManager;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final WorldPoint[] safeCenters = new WorldPoint[2];
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z];
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private AggressionTimer currentTimer;
|
||||||
|
|
||||||
|
private WorldPoint lastPlayerLocation;
|
||||||
|
private WorldPoint previousUnknownCenter;
|
||||||
|
private boolean loggingIn;
|
||||||
|
private List<String> npcNamePatterns;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
NpcAggroAreaConfig provideConfig(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
return configManager.getConfig(NpcAggroAreaConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startUp() throws Exception
|
||||||
|
{
|
||||||
|
overlayManager.add(overlay);
|
||||||
|
overlayManager.add(notWorkingOverlay);
|
||||||
|
npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutDown() throws Exception
|
||||||
|
{
|
||||||
|
removeTimer();
|
||||||
|
overlayManager.remove(overlay);
|
||||||
|
overlayManager.remove(notWorkingOverlay);
|
||||||
|
Arrays.fill(safeCenters, null);
|
||||||
|
lastPlayerLocation = null;
|
||||||
|
currentTimer = null;
|
||||||
|
loggingIn = false;
|
||||||
|
npcNamePatterns = null;
|
||||||
|
active = false;
|
||||||
|
|
||||||
|
Arrays.fill(linesToDisplay, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Area generateSafeArea()
|
||||||
|
{
|
||||||
|
final Area area = new Area();
|
||||||
|
|
||||||
|
for (WorldPoint wp : safeCenters)
|
||||||
|
{
|
||||||
|
if (wp == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygon poly = new Polygon();
|
||||||
|
poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS);
|
||||||
|
poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1);
|
||||||
|
poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1);
|
||||||
|
poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS);
|
||||||
|
area.add(new Area(poly));
|
||||||
|
}
|
||||||
|
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformWorldToLocal(float[] coords)
|
||||||
|
{
|
||||||
|
final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]);
|
||||||
|
coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f;
|
||||||
|
coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reevaluateActive()
|
||||||
|
{
|
||||||
|
if (currentTimer != null)
|
||||||
|
{
|
||||||
|
currentTimer.setVisible(active && config.showTimer());
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateLinesToDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateLinesToDisplay()
|
||||||
|
{
|
||||||
|
if (!active || !config.showAreaLines())
|
||||||
|
{
|
||||||
|
Arrays.fill(linesToDisplay, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle sceneRect = new Rectangle(
|
||||||
|
client.getBaseX() + 1, client.getBaseY() + 1,
|
||||||
|
Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < linesToDisplay.length; i++)
|
||||||
|
{
|
||||||
|
GeneralPath lines = new GeneralPath(generateSafeArea());
|
||||||
|
lines = Geometry.clipPath(lines, sceneRect);
|
||||||
|
lines = Geometry.splitIntoSegments(lines, 1);
|
||||||
|
lines = Geometry.transformPath(lines, this::transformWorldToLocal);
|
||||||
|
linesToDisplay[i] = lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeTimer()
|
||||||
|
{
|
||||||
|
infoBoxManager.removeInfoBox(currentTimer);
|
||||||
|
currentTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTimer(Duration duration)
|
||||||
|
{
|
||||||
|
removeTimer();
|
||||||
|
BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD);
|
||||||
|
currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer());
|
||||||
|
infoBoxManager.addInfoBox(currentTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetTimer()
|
||||||
|
{
|
||||||
|
createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInWilderness(WorldPoint location)
|
||||||
|
{
|
||||||
|
return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNpcMatch(NPC npc)
|
||||||
|
{
|
||||||
|
NPCComposition composition = npc.getTransformedComposition();
|
||||||
|
if (composition == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Strings.isNullOrEmpty(composition.getName()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most NPCs stop aggroing when the player has more than double
|
||||||
|
// its combat level.
|
||||||
|
int playerLvl = client.getLocalPlayer().getCombatLevel();
|
||||||
|
int npcLvl = composition.getCombatLevel();
|
||||||
|
String npcName = composition.getName().toLowerCase();
|
||||||
|
if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String pattern : npcNamePatterns)
|
||||||
|
{
|
||||||
|
if (WildcardMatcher.matches(pattern, npcName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAreaNpcs(final NPC... npcs)
|
||||||
|
{
|
||||||
|
for (NPC npc : npcs)
|
||||||
|
{
|
||||||
|
if (npc == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNpcMatch(npc))
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reevaluateActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recheckActive()
|
||||||
|
{
|
||||||
|
active = config.alwaysActive();
|
||||||
|
checkAreaNpcs(client.getCachedNPCs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onNpcSpawned(NpcSpawned event)
|
||||||
|
{
|
||||||
|
if (config.alwaysActive())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAreaNpcs(event.getNpc());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onGameTick(GameTick event)
|
||||||
|
{
|
||||||
|
WorldPoint newLocation = client.getLocalPlayer().getWorldLocation();
|
||||||
|
if (lastPlayerLocation != null)
|
||||||
|
{
|
||||||
|
if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4)
|
||||||
|
{
|
||||||
|
safeCenters[0] = null;
|
||||||
|
safeCenters[1] = newLocation;
|
||||||
|
resetTimer();
|
||||||
|
calculateLinesToDisplay();
|
||||||
|
|
||||||
|
// We don't know where the previous area was, so if the player e.g.
|
||||||
|
// entered a dungeon and then goes back out, he/she may enter the previous
|
||||||
|
// area which is unknown and would make the plugin inaccurate
|
||||||
|
previousUnknownCenter = lastPlayerLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (safeCenters[0] == null && previousUnknownCenter != null &&
|
||||||
|
previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS)
|
||||||
|
{
|
||||||
|
// Player went back to their previous unknown area before the 2nd
|
||||||
|
// center point was found, which means we don't know where it is again.
|
||||||
|
safeCenters[1] = null;
|
||||||
|
removeTimer();
|
||||||
|
calculateLinesToDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (safeCenters[1] != null)
|
||||||
|
{
|
||||||
|
if (Arrays.stream(safeCenters).noneMatch(
|
||||||
|
x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS))
|
||||||
|
{
|
||||||
|
safeCenters[0] = safeCenters[1];
|
||||||
|
safeCenters[1] = newLocation;
|
||||||
|
resetTimer();
|
||||||
|
calculateLinesToDisplay();
|
||||||
|
previousUnknownCenter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPlayerLocation = newLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onConfigChanged(ConfigChanged event)
|
||||||
|
{
|
||||||
|
String key = event.getKey();
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "npcUnaggroAlwaysActive":
|
||||||
|
recheckActive();
|
||||||
|
break;
|
||||||
|
case "npcUnaggroShowTimer":
|
||||||
|
if (currentTimer != null)
|
||||||
|
{
|
||||||
|
currentTimer.setVisible(active && config.showTimer());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "npcUnaggroCollisionDetection":
|
||||||
|
case "npcUnaggroShowAreaLines":
|
||||||
|
calculateLinesToDisplay();
|
||||||
|
break;
|
||||||
|
case "npcUnaggroNames":
|
||||||
|
npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns());
|
||||||
|
recheckActive();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadConfig()
|
||||||
|
{
|
||||||
|
safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class);
|
||||||
|
safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class);
|
||||||
|
lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class);
|
||||||
|
|
||||||
|
Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class);
|
||||||
|
if (timeLeft != null)
|
||||||
|
{
|
||||||
|
createTimer(timeLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetConfig()
|
||||||
|
{
|
||||||
|
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1);
|
||||||
|
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2);
|
||||||
|
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION);
|
||||||
|
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveConfig()
|
||||||
|
{
|
||||||
|
if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null)
|
||||||
|
{
|
||||||
|
resetConfig();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]);
|
||||||
|
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]);
|
||||||
|
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation);
|
||||||
|
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLogin()
|
||||||
|
{
|
||||||
|
loadConfig();
|
||||||
|
resetConfig();
|
||||||
|
|
||||||
|
WorldPoint newLocation = client.getLocalPlayer().getWorldLocation();
|
||||||
|
assert newLocation != null;
|
||||||
|
|
||||||
|
// If the player isn't at the location he/she logged out at,
|
||||||
|
// the safe unaggro area probably changed, and should be disposed.
|
||||||
|
if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0)
|
||||||
|
{
|
||||||
|
safeCenters[0] = null;
|
||||||
|
safeCenters[1] = null;
|
||||||
|
lastPlayerLocation = newLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onGameStateChanged(GameStateChanged event)
|
||||||
|
{
|
||||||
|
switch (event.getGameState())
|
||||||
|
{
|
||||||
|
case LOGGED_IN:
|
||||||
|
if (loggingIn)
|
||||||
|
{
|
||||||
|
loggingIn = false;
|
||||||
|
onLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
recheckActive();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOGGING_IN:
|
||||||
|
loggingIn = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOGIN_SCREEN:
|
||||||
|
if (lastPlayerLocation != null)
|
||||||
|
{
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
safeCenters[0] = null;
|
||||||
|
safeCenters[1] = null;
|
||||||
|
lastPlayerLocation = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user