Merge pull request #6133 from WooxSolo/npc-unaggro-timer
Add NPC unaggression timer
This commit is contained in:
@@ -114,8 +114,7 @@ public class WorldArea
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
Point distances = getAxisDistances(other);
|
||||
return Math.max(distances.getX(), distances.getY());
|
||||
return distanceTo2D(other);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +128,29 @@ public class WorldArea
|
||||
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.
|
||||
* <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);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import java.nio.channels.FileLock;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@@ -59,6 +60,7 @@ import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ConfigChanged;
|
||||
import net.runelite.client.RuneLite;
|
||||
import net.runelite.client.account.AccountSession;
|
||||
@@ -588,6 +590,18 @@ public class ConfigManager
|
||||
}
|
||||
return new Keybind(code, mods);
|
||||
}
|
||||
if (type == WorldPoint.class)
|
||||
{
|
||||
String[] splitStr = str.split(":");
|
||||
int x = Integer.parseInt(splitStr[0]);
|
||||
int y = Integer.parseInt(splitStr[1]);
|
||||
int plane = Integer.parseInt(splitStr[2]);
|
||||
return new WorldPoint(x, y, plane);
|
||||
}
|
||||
if (type == Duration.class)
|
||||
{
|
||||
return Duration.ofMillis(Long.parseLong(str));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -625,6 +639,15 @@ public class ConfigManager
|
||||
Keybind k = (Keybind) object;
|
||||
return k.getKeyCode() + ":" + k.getModifiers();
|
||||
}
|
||||
if (object instanceof WorldPoint)
|
||||
{
|
||||
WorldPoint wp = (WorldPoint) object;
|
||||
return wp.getX() + ":" + wp.getY() + ":" + wp.getPlane();
|
||||
}
|
||||
if (object instanceof Duration)
|
||||
{
|
||||
return Long.toString(((Duration) object).toMillis());
|
||||
}
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -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