Slayer update (#161)
* Slayer update Stripped model outliner Slayer points varbit Seperate superior color Slayer estimate task completion Slayer kc fix NPCID vhere needed * Tests * Ignore AWT-EventQueue-0 in tests * Fix componnent creation threading error
This commit is contained in:
committed by
Tyler Bochard
parent
92f677727d
commit
fb307cf235
@@ -433,6 +433,11 @@ public enum Varbits
|
||||
*/
|
||||
ACCOUNT_TYPE(1777),
|
||||
|
||||
/**
|
||||
* Varbit used for Slayer reward points
|
||||
*/
|
||||
SLAYER_REWARD_POINTS(4068),
|
||||
|
||||
/**
|
||||
* The varbit that stores the oxygen percentage for player
|
||||
*/
|
||||
|
||||
@@ -54,12 +54,14 @@ import net.runelite.client.game.ClanManager;
|
||||
import net.runelite.client.game.ItemManager;
|
||||
import net.runelite.client.game.LootManager;
|
||||
import net.runelite.client.game.chatbox.ChatboxPanelManager;
|
||||
import net.runelite.client.graphics.ModelOutlineRenderer;
|
||||
import net.runelite.client.menus.MenuManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginInstantiationException;
|
||||
import net.runelite.client.plugins.PluginManager;
|
||||
import net.runelite.client.plugins.config.ConfigPanel;
|
||||
import net.runelite.client.rs.ClientUpdateCheckMode;
|
||||
import net.runelite.client.task.Scheduler;
|
||||
import net.runelite.client.ui.ClientUI;
|
||||
import net.runelite.client.ui.DrawManager;
|
||||
import net.runelite.client.ui.RuneLiteSplashScreen;
|
||||
@@ -166,6 +168,12 @@ public class RuneLite
|
||||
@Nullable
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private Provider<ModelOutlineRenderer> modelOutlineRenderer;
|
||||
|
||||
@Inject
|
||||
private Scheduler scheduler;
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
@@ -337,6 +345,12 @@ public class RuneLite
|
||||
|
||||
// Start plugins
|
||||
pluginManager.startCorePlugins();
|
||||
|
||||
// Register additional schedulers
|
||||
if (this.client != null)
|
||||
{
|
||||
scheduler.registerObject(modelOutlineRenderer.get());
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
|
||||
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* 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.graphics;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MainBufferProvider;
|
||||
import net.runelite.api.Model;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NPCComposition;
|
||||
import net.runelite.api.Perspective;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.client.task.Schedule;
|
||||
|
||||
@Singleton
|
||||
public class ModelOutlineRenderer
|
||||
{
|
||||
/*
|
||||
* This class doesn't really "need" static variables, but they are
|
||||
* static for performance reasons. Arrays are kept outside methods
|
||||
* to avoid frequent big allocations. Arrays should mostly be seen
|
||||
* as ArrayLists. The size of them is increased whenever they need
|
||||
* to become bigger.
|
||||
*/
|
||||
|
||||
private final Client client;
|
||||
|
||||
private boolean isReset;
|
||||
private boolean usedSinceLastCheck;
|
||||
|
||||
// Dimensions of the underlying image
|
||||
private int imageWidth;
|
||||
private int imageHeight;
|
||||
|
||||
// Boundaries for the current rasterization
|
||||
private int clipX1;
|
||||
private int clipY1;
|
||||
private int clipX2;
|
||||
private int clipY2;
|
||||
|
||||
// Pixel points that would be rendered to
|
||||
private int[] visited;
|
||||
private int currentVisitedNumber = 0;
|
||||
|
||||
// Transformed vertex positions
|
||||
private int[] projectedVerticesX;
|
||||
private int[] projectedVerticesY;
|
||||
private boolean[] projectedVerticesRenderable;
|
||||
|
||||
// An array of pixel points to raster onto the image. These are checked against
|
||||
// clip boundaries and the visited array to prevent drawing on top of the model
|
||||
// and outside the scene area. They are grouped per distance to the closest pixel
|
||||
// drawn on the model.
|
||||
private int[][] outlinePixels;
|
||||
private int[] outlinePixelsLengths; // outlinePixelsLength[i] is the used length of outlinePixels[i]
|
||||
private int outlineArrayWidth;
|
||||
|
||||
// A list of pixel distances ordered from shortest to longest distance for
|
||||
// each outline width. These are calculated once upon first usage and then
|
||||
// stored here to prevent reevaluation.
|
||||
private List<List<PixelDistanceAlpha>> precomputedDistancePriorities;
|
||||
|
||||
@Inject
|
||||
private ModelOutlineRenderer(Client client)
|
||||
{
|
||||
this.client = client;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
@Schedule(period = 5, unit = ChronoUnit.SECONDS)
|
||||
public void checkUsage()
|
||||
{
|
||||
if (!isReset && !usedSinceLastCheck)
|
||||
{
|
||||
// Reset memory allocated when the rasterizer becomes inactive
|
||||
reset();
|
||||
}
|
||||
usedSinceLastCheck = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset memory used by the rasterizer
|
||||
*/
|
||||
private void reset()
|
||||
{
|
||||
visited = new int[0];
|
||||
projectedVerticesX = new int[0];
|
||||
projectedVerticesY = new int[0];
|
||||
projectedVerticesRenderable = new boolean[0];
|
||||
outlinePixels = new int[0][];
|
||||
outlinePixelsLengths = new int[0];
|
||||
precomputedDistancePriorities = new ArrayList<>(0);
|
||||
isReset = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next power of two of a value
|
||||
*
|
||||
* @param value The value to find the next power of two of
|
||||
* @return Returns the next power of two
|
||||
*/
|
||||
private static int nextPowerOfTwo(int value)
|
||||
{
|
||||
value--;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value++;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a triangle goes counter clockwise
|
||||
*
|
||||
* @return Returns true if the triangle goes counter clockwise and should be culled, otherwise false
|
||||
*/
|
||||
private static boolean cullFace(int x1, int y1, int x2, int y2, int x3, int y3)
|
||||
{
|
||||
return
|
||||
(y2 - y1) * (x3 - x2) -
|
||||
(x2 - x1) * (y3 - y2) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of pixel distances ordered by distance from closest pixel for a specific outline width.
|
||||
*
|
||||
* @param outlineWidth The outline width
|
||||
* @return Returns the list of pixel distances
|
||||
*/
|
||||
private List<PixelDistanceAlpha> getPriorityList(int outlineWidth)
|
||||
{
|
||||
while (precomputedDistancePriorities.size() <= outlineWidth)
|
||||
{
|
||||
precomputedDistancePriorities.add(null);
|
||||
}
|
||||
|
||||
// Grab the cached outline width if we have one
|
||||
if (precomputedDistancePriorities.get(outlineWidth) != null)
|
||||
{
|
||||
return precomputedDistancePriorities.get(outlineWidth);
|
||||
}
|
||||
|
||||
List<PixelDistanceAlpha> ps = new ArrayList<>();
|
||||
for (int x = 0; x <= outlineWidth; x++)
|
||||
{
|
||||
for (int y = 0; y <= outlineWidth; y++)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double dist = Math.sqrt(x * x + y * y);
|
||||
if (dist > outlineWidth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int outerAlpha = outlineWidth == 1 ? 255 // For preventing division by 0
|
||||
: (int) (255 * (dist - 1) / (outlineWidth - 1));
|
||||
ps.add(new PixelDistanceAlpha(outerAlpha, x + y * outlineArrayWidth));
|
||||
}
|
||||
}
|
||||
ps.sort(Comparator.comparingDouble(PixelDistanceAlpha::getOuterAlpha));
|
||||
precomputedDistancePriorities.set(outlineWidth, ps);
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the size of outlinePixels is big enough to hold a specific
|
||||
* amount of elements. This is used to reduce the amount of if checks needed
|
||||
* when adding elements to outlinePixels.
|
||||
*
|
||||
* @param distArrayPos The position in the array
|
||||
* @param additionalMinimumSize The additional minimum size required
|
||||
*/
|
||||
private void ensureMinimumOutlineQueueSize(int distArrayPos, int additionalMinimumSize)
|
||||
{
|
||||
int minimumSize = outlinePixelsLengths[distArrayPos] + additionalMinimumSize;
|
||||
while (outlinePixels[distArrayPos].length < minimumSize)
|
||||
{
|
||||
int[] newArr = new int[nextPowerOfTwo(minimumSize)];
|
||||
System.arraycopy(outlinePixels[distArrayPos], 0, newArr, 0,
|
||||
outlinePixels[distArrayPos].length);
|
||||
outlinePixels[distArrayPos] = newArr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the visited flag for a specific amount of pixels
|
||||
*
|
||||
* @param pixelAmount The amount of pixels to reset
|
||||
*/
|
||||
private void resetVisited(int pixelAmount)
|
||||
{
|
||||
// The visited array is essentially a boolean array, but by
|
||||
// making it an int array and checking if visited[i] == currentVisitedNumber
|
||||
// and changing currentVisitedNumber for every new outline, we can essentially
|
||||
// reset the whole array without having to iterate over every element
|
||||
|
||||
if (visited.length < pixelAmount)
|
||||
{
|
||||
visited = new int[nextPowerOfTwo(pixelAmount)];
|
||||
currentVisitedNumber = 0;
|
||||
}
|
||||
|
||||
currentVisitedNumber++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the pixels that are queued for outlining
|
||||
*
|
||||
* @param outlineWidth The width of the outline to reset pixels for
|
||||
*/
|
||||
private void resetOutline(int outlineWidth)
|
||||
{
|
||||
outlineArrayWidth = outlineWidth + 2;
|
||||
|
||||
int arraySizes = outlineArrayWidth * outlineArrayWidth;
|
||||
if (outlinePixels.length < arraySizes)
|
||||
{
|
||||
outlinePixels = new int[arraySizes][];
|
||||
outlinePixelsLengths = new int[arraySizes];
|
||||
for (int i = 0; i < arraySizes; i++)
|
||||
{
|
||||
outlinePixels[i] = new int[4];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < arraySizes; i++)
|
||||
{
|
||||
outlinePixelsLengths[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a horizontal line rasterization and adds the pixels to the left
|
||||
* and to the right to the outline queue if they are within the clip area.
|
||||
*
|
||||
* @param pixelPos The pixel position in the line where x == 0
|
||||
* @param x1 The starting x position
|
||||
* @param x2 The ending x position
|
||||
*/
|
||||
private void simulateHorizontalLineRasterizationForOutline(
|
||||
int pixelPos, int x1, int x2)
|
||||
{
|
||||
if (x2 > clipX2)
|
||||
{
|
||||
x2 = clipX2;
|
||||
}
|
||||
if (x1 < clipX1)
|
||||
{
|
||||
x1 = clipX1;
|
||||
}
|
||||
if (x1 >= x2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the pixel positions to the left and to the right of the line
|
||||
ensureMinimumOutlineQueueSize(1, 2);
|
||||
if (x2 < clipX2)
|
||||
{
|
||||
outlinePixels[1][outlinePixelsLengths[1]++] = pixelPos + x2;
|
||||
}
|
||||
if (x1 > clipX1)
|
||||
{
|
||||
outlinePixels[1][outlinePixelsLengths[1]++] = pixelPos + x1 - 1;
|
||||
}
|
||||
|
||||
// Divide by 4 to account for loop unrolling
|
||||
int xDist = x2 - x1 >> 2;
|
||||
pixelPos += x1;
|
||||
|
||||
// This loop could run over 100m times per second without loop unrolling in some cases,
|
||||
// so unrolling it can give a noticeable performance boost.
|
||||
while (xDist-- > 0)
|
||||
{
|
||||
visited[pixelPos++] = currentVisitedNumber;
|
||||
visited[pixelPos++] = currentVisitedNumber;
|
||||
visited[pixelPos++] = currentVisitedNumber;
|
||||
visited[pixelPos++] = currentVisitedNumber;
|
||||
}
|
||||
|
||||
// Draw up to 3 more pixels if there were any left
|
||||
xDist = (x2 - x1) & 3;
|
||||
while (xDist-- > 0)
|
||||
{
|
||||
visited[pixelPos++] = currentVisitedNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the pixel positions above and below two horizontal lines, excluding those
|
||||
* where the x positions of the lines intersect.
|
||||
*
|
||||
* @param pixelPos The pixel position at x == 0 of the second line
|
||||
* @param x1 The starting x position of the first line
|
||||
* @param x2 The ending x position of the first line
|
||||
* @param x3 The starting x position of the second line
|
||||
* @param x4 The ending x position of the second line
|
||||
*/
|
||||
private void outlineAroundHorizontalLine(
|
||||
int pixelPos, int x1, int x2, int x3, int x4)
|
||||
{
|
||||
if (x1 < clipX1)
|
||||
{
|
||||
x1 = clipX1;
|
||||
}
|
||||
if (x2 < clipX1)
|
||||
{
|
||||
x2 = clipX1;
|
||||
}
|
||||
if (x3 < clipX1)
|
||||
{
|
||||
x3 = clipX1;
|
||||
}
|
||||
if (x4 < clipX1)
|
||||
{
|
||||
x4 = clipX1;
|
||||
}
|
||||
|
||||
if (x1 > clipX2)
|
||||
{
|
||||
x1 = clipX2;
|
||||
}
|
||||
if (x2 > clipX2)
|
||||
{
|
||||
x2 = clipX2;
|
||||
}
|
||||
if (x3 > clipX2)
|
||||
{
|
||||
x3 = clipX2;
|
||||
}
|
||||
if (x4 > clipX2)
|
||||
{
|
||||
x4 = clipX2;
|
||||
}
|
||||
|
||||
if (x1 < x3)
|
||||
{
|
||||
ensureMinimumOutlineQueueSize(outlineArrayWidth, x3 - x1);
|
||||
for (int x = x1; x < x3; x++)
|
||||
{
|
||||
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos - imageWidth + x;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureMinimumOutlineQueueSize(outlineArrayWidth, x1 - x3);
|
||||
for (int x = x3; x < x1; x++)
|
||||
{
|
||||
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos + x;
|
||||
}
|
||||
}
|
||||
|
||||
if (x2 < x4)
|
||||
{
|
||||
ensureMinimumOutlineQueueSize(outlineArrayWidth, x4 - x2);
|
||||
for (int x = x2; x < x4; x++)
|
||||
{
|
||||
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos + x;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ensureMinimumOutlineQueueSize(outlineArrayWidth, x2 - x4);
|
||||
for (int x = x4; x < x2; x++)
|
||||
{
|
||||
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos - imageWidth + x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates rasterization of a triangle and adds every pixel outside the triangle
|
||||
* to the outline queue.
|
||||
*
|
||||
* @param x1 The x position of the first vertex in the triangle
|
||||
* @param y1 The y position of the first vertex in the triangle
|
||||
* @param x2 The x position of the second vertex in the triangle
|
||||
* @param y2 The y position of the second vertex in the triangle
|
||||
* @param x3 The x position of the third vertex in the triangle
|
||||
* @param y3 The y position of the third vertex in the triangle
|
||||
*/
|
||||
private void simulateTriangleRasterizationForOutline(
|
||||
int x1, int y1, int x2, int y2, int x3, int y3)
|
||||
{
|
||||
// Swap vertices so y1 <= y2 <= y3 using bubble sort
|
||||
if (y1 > y2)
|
||||
{
|
||||
int yp = y1;
|
||||
int xp = x1;
|
||||
y1 = y2;
|
||||
y2 = yp;
|
||||
x1 = x2;
|
||||
x2 = xp;
|
||||
}
|
||||
if (y2 > y3)
|
||||
{
|
||||
int yp = y2;
|
||||
int xp = x2;
|
||||
y2 = y3;
|
||||
y3 = yp;
|
||||
x2 = x3;
|
||||
x3 = xp;
|
||||
}
|
||||
if (y1 > y2)
|
||||
{
|
||||
int yp = y1;
|
||||
int xp = x1;
|
||||
y1 = y2;
|
||||
y2 = yp;
|
||||
x1 = x2;
|
||||
x2 = xp;
|
||||
}
|
||||
|
||||
if (y1 > clipY2)
|
||||
{
|
||||
// All points are outside clip boundaries
|
||||
return;
|
||||
}
|
||||
|
||||
int slope1 = 0;
|
||||
if (y1 != y2)
|
||||
{
|
||||
slope1 = (x2 - x1 << 14) / (y2 - y1);
|
||||
}
|
||||
|
||||
int slope2 = 0;
|
||||
if (y3 != y2)
|
||||
{
|
||||
slope2 = (x3 - x2 << 14) / (y3 - y2);
|
||||
}
|
||||
|
||||
int slope3 = 0;
|
||||
if (y1 != y3)
|
||||
{
|
||||
slope3 = (x1 - x3 << 14) / (y1 - y3);
|
||||
}
|
||||
|
||||
if (y2 > clipY2)
|
||||
{
|
||||
y2 = clipY2;
|
||||
}
|
||||
if (y3 > clipY2)
|
||||
{
|
||||
y3 = clipY2;
|
||||
}
|
||||
if (y1 == y3 || y3 < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
x1 <<= 14;
|
||||
x2 <<= 14;
|
||||
x3 = x1;
|
||||
|
||||
if (y1 < 0)
|
||||
{
|
||||
x3 -= y1 * slope3;
|
||||
x1 -= y1 * slope1;
|
||||
y1 = 0;
|
||||
}
|
||||
if (y2 < 0)
|
||||
{
|
||||
x2 -= slope2 * y2;
|
||||
y2 = 0;
|
||||
}
|
||||
|
||||
int pixelPos = y1 * imageWidth;
|
||||
int currX1;
|
||||
int currX2;
|
||||
if (y1 != y2 && slope3 < slope1 || y1 == y2 && slope3 > slope2)
|
||||
{
|
||||
int height1 = y2 - y1;
|
||||
int height2 = y3 - y2;
|
||||
|
||||
int prevX1;
|
||||
int prevX2;
|
||||
if (height1 <= 0)
|
||||
{
|
||||
prevX1 = x3 >> 14;
|
||||
prevX2 = x2 >> 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevX1 = x3 >> 14;
|
||||
prevX2 = x1 >> 14;
|
||||
}
|
||||
|
||||
outlineAroundHorizontalLine(pixelPos, prevX1, prevX2, prevX2, prevX2);
|
||||
|
||||
while (height1-- > 0)
|
||||
{
|
||||
currX1 = x3 >> 14;
|
||||
currX2 = x1 >> 14;
|
||||
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
|
||||
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
|
||||
x3 += slope3;
|
||||
x1 += slope1;
|
||||
pixelPos += imageWidth;
|
||||
prevX1 = currX1;
|
||||
prevX2 = currX2;
|
||||
}
|
||||
|
||||
while (height2-- > 0)
|
||||
{
|
||||
currX1 = x3 >> 14;
|
||||
currX2 = x2 >> 14;
|
||||
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
|
||||
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
|
||||
x3 += slope3;
|
||||
x2 += slope2;
|
||||
pixelPos += imageWidth;
|
||||
prevX1 = currX1;
|
||||
prevX2 = currX2;
|
||||
}
|
||||
|
||||
outlineAroundHorizontalLine(pixelPos, prevX1, prevX1, prevX1, prevX2);
|
||||
}
|
||||
else
|
||||
{
|
||||
int height1 = y2 - y1;
|
||||
int height2 = y3 - y2;
|
||||
|
||||
int prevX1;
|
||||
int prevX2;
|
||||
if (height1 <= 0)
|
||||
{
|
||||
prevX1 = x2 >> 14;
|
||||
prevX2 = x3 >> 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevX1 = x1 >> 14;
|
||||
prevX2 = x3 >> 14;
|
||||
}
|
||||
|
||||
outlineAroundHorizontalLine(pixelPos, prevX1, prevX2, prevX2, prevX2);
|
||||
|
||||
while (height1-- > 0)
|
||||
{
|
||||
currX1 = x1 >> 14;
|
||||
currX2 = x3 >> 14;
|
||||
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
|
||||
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
|
||||
x1 += slope1;
|
||||
x3 += slope3;
|
||||
pixelPos += imageWidth;
|
||||
prevX1 = currX1;
|
||||
prevX2 = currX2;
|
||||
}
|
||||
|
||||
while (height2-- > 0)
|
||||
{
|
||||
currX1 = x2 >> 14;
|
||||
currX2 = x3 >> 14;
|
||||
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
|
||||
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
|
||||
x3 += slope3;
|
||||
x2 += slope2;
|
||||
pixelPos += imageWidth;
|
||||
prevX1 = currX1;
|
||||
prevX2 = currX2;
|
||||
}
|
||||
|
||||
outlineAroundHorizontalLine(pixelPos, prevX1, prevX1, prevX1, prevX2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the vertices 3D points to the screen canvas 2D points
|
||||
*
|
||||
* @param localX The local x position of the vertices
|
||||
* @param localY The local y position of the vertices
|
||||
* @param localZ The local z position of the vertices
|
||||
* @param vertexOrientation The orientation of the vertices
|
||||
* @return Returns true if any of them are inside the clip area, otherwise false
|
||||
*/
|
||||
private boolean projectVertices(Model model,
|
||||
final int localX, final int localY, final int localZ, final int vertexOrientation)
|
||||
{
|
||||
final int cameraX = client.getCameraX();
|
||||
final int cameraY = client.getCameraY();
|
||||
final int cameraZ = client.getCameraZ();
|
||||
final int cameraYaw = client.getCameraYaw();
|
||||
final int cameraPitch = client.getCameraPitch();
|
||||
final int scale = client.getScale();
|
||||
final int orientationSin = Perspective.SINE[vertexOrientation];
|
||||
final int orientationCos = Perspective.COSINE[vertexOrientation];
|
||||
final int pitchSin = Perspective.SINE[cameraPitch];
|
||||
final int pitchCos = Perspective.COSINE[cameraPitch];
|
||||
final int yawSin = Perspective.SINE[cameraYaw];
|
||||
final int yawCos = Perspective.COSINE[cameraYaw];
|
||||
final int vertexCount = model.getVerticesCount();
|
||||
final int[] verticesX = model.getVerticesX();
|
||||
final int[] verticesY = model.getVerticesY();
|
||||
final int[] verticesZ = model.getVerticesZ();
|
||||
|
||||
boolean anyVisible = false;
|
||||
|
||||
// Make sure the arrays are big enough
|
||||
while (projectedVerticesX.length < vertexCount)
|
||||
{
|
||||
int newSize = nextPowerOfTwo(vertexCount);
|
||||
projectedVerticesX = new int[newSize];
|
||||
projectedVerticesY = new int[newSize];
|
||||
projectedVerticesRenderable = new boolean[newSize];
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
int vx = verticesX[i];
|
||||
int vy = verticesZ[i];
|
||||
int vz = verticesY[i];
|
||||
int vh; // Value holder
|
||||
|
||||
// Rotate based on orientation
|
||||
vh = vx * orientationCos + vy * orientationSin >> 16;
|
||||
vy = vy * orientationCos - vx * orientationSin >> 16;
|
||||
vx = vh;
|
||||
|
||||
// Translate to local coords
|
||||
vx += localX;
|
||||
vy += localY;
|
||||
vz += localZ;
|
||||
|
||||
// Translate to camera
|
||||
vx -= cameraX;
|
||||
vy -= cameraY;
|
||||
vz -= cameraZ;
|
||||
|
||||
// Transform to canvas
|
||||
vh = vx * yawCos + vy * yawSin >> 16;
|
||||
vy = vy * yawCos - vx * yawSin >> 16;
|
||||
vx = vh;
|
||||
vh = vz * pitchCos - vy * pitchSin >> 16;
|
||||
vz = vz * pitchSin + vy * pitchCos >> 16;
|
||||
vy = vh;
|
||||
|
||||
if (vz >= 50)
|
||||
{
|
||||
projectedVerticesX[i] = (clipX1 + clipX2) / 2 + vx * scale / vz;
|
||||
projectedVerticesY[i] = (clipY1 + clipY2) / 2 + vy * scale / vz;
|
||||
|
||||
projectedVerticesRenderable[i] = true;
|
||||
anyVisible |=
|
||||
projectedVerticesX[i] >= clipX1 && projectedVerticesX[i] < clipX2 &&
|
||||
projectedVerticesY[i] >= clipY1 && projectedVerticesY[i] < clipY2;
|
||||
}
|
||||
else
|
||||
{
|
||||
projectedVerticesRenderable[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return anyVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate rendering of the model and puts every pixel of the wireframe of
|
||||
* the non-culled and non-transparent faces into the outline pixel queue.
|
||||
*/
|
||||
private void simulateModelRasterizationForOutline(Model model)
|
||||
{
|
||||
final int triangleCount = model.getTrianglesCount();
|
||||
final int[] indices1 = model.getTrianglesX();
|
||||
final int[] indices2 = model.getTrianglesY();
|
||||
final int[] indices3 = model.getTrianglesZ();
|
||||
final byte[] triangleTransparencies = model.getTriangleTransparencies();
|
||||
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
if (projectedVerticesRenderable[indices1[i]] &&
|
||||
projectedVerticesRenderable[indices2[i]] &&
|
||||
projectedVerticesRenderable[indices3[i]] &&
|
||||
// 254 and 255 counts as fully transparent
|
||||
(triangleTransparencies == null || (triangleTransparencies[i] & 255) < 254))
|
||||
{
|
||||
final int index1 = indices1[i];
|
||||
final int index2 = indices2[i];
|
||||
final int index3 = indices3[i];
|
||||
final int v1x = projectedVerticesX[index1];
|
||||
final int v1y = projectedVerticesY[index1];
|
||||
final int v2x = projectedVerticesX[index2];
|
||||
final int v2y = projectedVerticesY[index2];
|
||||
final int v3x = projectedVerticesX[index3];
|
||||
final int v3y = projectedVerticesY[index3];
|
||||
|
||||
if (!cullFace(v1x, v1y, v2x, v2y, v3x, v3y))
|
||||
{
|
||||
simulateTriangleRasterizationForOutline(
|
||||
v1x, v1y, v2x, v2y, v3x, v3y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an outline of the pixels in the outline queue to an image
|
||||
*
|
||||
* @param image The image to draw the outline to
|
||||
* @param outlineWidth The width of the outline
|
||||
* @param innerColor The color of the pixels of the outline closest to the model
|
||||
* @param outerColor The color of the pixels of the outline furthest away from the model
|
||||
*/
|
||||
private void renderOutline(BufferedImage image, int outlineWidth,
|
||||
Color innerColor, Color outerColor)
|
||||
{
|
||||
int[] imageData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
List<PixelDistanceAlpha> ps = getPriorityList(outlineWidth);
|
||||
|
||||
for (PixelDistanceAlpha p : ps)
|
||||
{
|
||||
int color;
|
||||
int alpha;
|
||||
if (outlineWidth == 1)
|
||||
{
|
||||
color =
|
||||
((innerColor.getRed() + outerColor.getRed()) << 15) |
|
||||
((innerColor.getGreen() + outerColor.getGreen() << 7)) |
|
||||
((innerColor.getBlue() + outerColor.getBlue() >> 1));
|
||||
alpha = (innerColor.getAlpha() + outerColor.getAlpha()) >> 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
int outerAlpha = p.getOuterAlpha();
|
||||
int innerAlpha = 255 - outerAlpha;
|
||||
int innerAlphaFraction = (innerAlpha * innerColor.getAlpha()) / 255;
|
||||
int outerAlphaFraction = (outerAlpha * outerColor.getAlpha()) / 255;
|
||||
alpha = innerAlphaFraction + outerAlphaFraction;
|
||||
if (alpha != 0)
|
||||
{
|
||||
color =
|
||||
((innerColor.getRed() * innerAlphaFraction +
|
||||
outerColor.getRed() * outerAlphaFraction) / alpha << 16) |
|
||||
((innerColor.getGreen() * innerAlphaFraction +
|
||||
outerColor.getGreen() * outerAlphaFraction) / alpha << 8) |
|
||||
((innerColor.getBlue() * innerAlphaFraction +
|
||||
outerColor.getBlue() * outerAlphaFraction) / alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = 0;
|
||||
}
|
||||
}
|
||||
|
||||
final int distArrayPos = p.getDistArrayPos();
|
||||
final int nextDistArrayPosY = distArrayPos + outlineArrayWidth;
|
||||
final int nextDistArrayPosX = distArrayPos + 1;
|
||||
ensureMinimumOutlineQueueSize(nextDistArrayPosX, outlinePixelsLengths[distArrayPos] * 2);
|
||||
ensureMinimumOutlineQueueSize(nextDistArrayPosY, outlinePixelsLengths[distArrayPos] * 2);
|
||||
|
||||
// The following 3 branches do the same thing, but when the requirements are simple,
|
||||
// there are less checks needed which can give a performance boost.
|
||||
if (alpha == 255)
|
||||
{
|
||||
if (outlineWidth == 1)
|
||||
{
|
||||
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
|
||||
{
|
||||
int pixelPos = outlinePixels[distArrayPos][i2];
|
||||
int x = pixelPos % imageWidth;
|
||||
int y = pixelPos / imageWidth;
|
||||
if (x < clipX1 || x >= clipX2 ||
|
||||
y < clipY1 || y >= clipY2 ||
|
||||
visited[pixelPos] == currentVisitedNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
imageData[pixelPos] = color;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
|
||||
{
|
||||
int pixelPos = outlinePixels[distArrayPos][i2];
|
||||
int x = pixelPos % imageWidth;
|
||||
int y = pixelPos / imageWidth;
|
||||
if (x < clipX1 || x >= clipX2 ||
|
||||
y < clipY1 || y >= clipY2 ||
|
||||
visited[pixelPos] == currentVisitedNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
visited[pixelPos] = currentVisitedNumber;
|
||||
|
||||
imageData[pixelPos] = color;
|
||||
|
||||
if (pixelPos % imageWidth != 0)
|
||||
{
|
||||
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos - 1;
|
||||
}
|
||||
if ((pixelPos + 1) % imageWidth != 0)
|
||||
{
|
||||
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos + 1;
|
||||
}
|
||||
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos - imageWidth;
|
||||
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos + imageWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
|
||||
{
|
||||
int pixelPos = outlinePixels[distArrayPos][i2];
|
||||
int x = pixelPos % imageWidth;
|
||||
int y = pixelPos / imageWidth;
|
||||
if (x < clipX1 || x >= clipX2 ||
|
||||
y < clipY1 || y >= clipY2 ||
|
||||
visited[pixelPos] == currentVisitedNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
visited[pixelPos] = currentVisitedNumber;
|
||||
|
||||
imageData[pixelPos] =
|
||||
((((color & 0xFF0000) * alpha + (imageData[pixelPos] & 0xFF0000) * (255 - alpha)) / 255) & 0xFF0000) +
|
||||
((((color & 0xFF00) * alpha + (imageData[pixelPos] & 0xFF00) * (255 - alpha)) / 255) & 0xFF00) +
|
||||
((((color & 0xFF) * alpha + (imageData[pixelPos] & 0xFF) * (255 - alpha)) / 255) & 0xFF);
|
||||
|
||||
if (pixelPos % imageWidth != 0)
|
||||
{
|
||||
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos - 1;
|
||||
}
|
||||
if ((pixelPos + 1) % imageWidth != 0)
|
||||
{
|
||||
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos + 1;
|
||||
}
|
||||
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos - imageWidth;
|
||||
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos + imageWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an outline around a model to an image
|
||||
*
|
||||
* @param localX The local x position of the model
|
||||
* @param localY The local y position of the model
|
||||
* @param localZ The local z position of the model
|
||||
* @param orientation The orientation of the model
|
||||
* @param outlineWidth The width of the outline
|
||||
* @param innerColor The color of the pixels of the outline closest to the model
|
||||
* @param outerColor The color of the pixels of the outline furthest away from the model
|
||||
*/
|
||||
private void drawModelOutline(Model model,
|
||||
int localX, int localY, int localZ, int orientation,
|
||||
int outlineWidth, Color innerColor, Color outerColor)
|
||||
{
|
||||
if (outlineWidth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isReset = false;
|
||||
usedSinceLastCheck = true;
|
||||
|
||||
MainBufferProvider bufferProvider = (MainBufferProvider) client.getBufferProvider();
|
||||
BufferedImage image = (BufferedImage) bufferProvider.getImage();
|
||||
|
||||
clipX1 = client.getViewportXOffset();
|
||||
clipY1 = client.getViewportYOffset();
|
||||
clipX2 = client.getViewportWidth() + clipX1;
|
||||
clipY2 = client.getViewportHeight() + clipY1;
|
||||
imageWidth = image.getWidth();
|
||||
imageHeight = image.getHeight();
|
||||
final int pixelAmount = imageWidth * imageHeight;
|
||||
|
||||
resetVisited(pixelAmount);
|
||||
resetOutline(outlineWidth);
|
||||
|
||||
if (!projectVertices(model,
|
||||
localX, localY, localZ, orientation))
|
||||
{
|
||||
// No vertex of the model is visible on the screen, so we can
|
||||
// assume there are no parts of the model to outline.
|
||||
return;
|
||||
}
|
||||
|
||||
simulateModelRasterizationForOutline(model);
|
||||
|
||||
renderOutline(image, outlineWidth, innerColor, outerColor);
|
||||
}
|
||||
|
||||
public void drawOutline(NPC npc, int outlineWidth, Color color)
|
||||
{
|
||||
drawOutline(npc, outlineWidth, color, color);
|
||||
}
|
||||
|
||||
public void drawOutline(NPC npc, int outlineWidth,
|
||||
Color innerColor, Color outerColor)
|
||||
{
|
||||
int size = 1;
|
||||
NPCComposition composition = npc.getTransformedComposition();
|
||||
if (composition != null)
|
||||
{
|
||||
size = composition.getSize();
|
||||
}
|
||||
|
||||
LocalPoint lp = npc.getLocalLocation();
|
||||
if (lp != null)
|
||||
{
|
||||
// NPCs z position are calculated based on the tile height of the northeastern tile
|
||||
final int northEastX = lp.getX() + Perspective.LOCAL_TILE_SIZE * (size - 1) / 2;
|
||||
final int northEastY = lp.getY() + Perspective.LOCAL_TILE_SIZE * (size - 1) / 2;
|
||||
final LocalPoint northEastLp = new LocalPoint(northEastX, northEastY);
|
||||
|
||||
drawModelOutline(npc.getModel(), lp.getX(), lp.getY(),
|
||||
Perspective.getTileHeight(client, northEastLp, client.getPlane()),
|
||||
npc.getOrientation(), outlineWidth, innerColor, outerColor);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawOutline(Player player, int outlineWidth, Color color)
|
||||
{
|
||||
drawOutline(player, outlineWidth, color, color);
|
||||
}
|
||||
|
||||
public void drawOutline(Player player, int outlineWidth,
|
||||
Color innerColor, Color outerColor)
|
||||
{
|
||||
LocalPoint lp = player.getLocalLocation();
|
||||
if (lp != null)
|
||||
{
|
||||
drawModelOutline(player.getModel(), lp.getX(), lp.getY(),
|
||||
Perspective.getTileHeight(client, lp, client.getPlane()),
|
||||
player.getOrientation(), outlineWidth, innerColor, outerColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.graphics;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
@RequiredArgsConstructor
|
||||
class PixelDistanceAlpha
|
||||
{
|
||||
private final int outerAlpha;
|
||||
private final int distArrayPos;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public interface NpcIndicatorsConfig extends Config
|
||||
)
|
||||
default RenderStyle renderStyle()
|
||||
{
|
||||
return RenderStyle.HULL;
|
||||
return RenderStyle.THIN_OUTLINE;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
|
||||
@@ -42,6 +42,7 @@ import net.runelite.api.Perspective;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.client.graphics.ModelOutlineRenderer;
|
||||
import net.runelite.client.ui.overlay.Overlay;
|
||||
import net.runelite.client.ui.overlay.OverlayLayer;
|
||||
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||
@@ -50,6 +51,8 @@ import net.runelite.client.util.Text;
|
||||
|
||||
public class NpcSceneOverlay extends Overlay
|
||||
{
|
||||
private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
|
||||
|
||||
// Anything but white text is quite hard to see since it is drawn on
|
||||
// a dark background
|
||||
private static final Color TEXT_COLOR = Color.WHITE;
|
||||
@@ -67,13 +70,15 @@ public class NpcSceneOverlay extends Overlay
|
||||
private final Client client;
|
||||
private final NpcIndicatorsConfig config;
|
||||
private final NpcIndicatorsPlugin plugin;
|
||||
private final ModelOutlineRenderer modelOutliner;
|
||||
|
||||
@Inject
|
||||
NpcSceneOverlay(Client client, NpcIndicatorsConfig config, NpcIndicatorsPlugin plugin)
|
||||
NpcSceneOverlay(Client client, NpcIndicatorsConfig config, NpcIndicatorsPlugin plugin, ModelOutlineRenderer modelOutliner)
|
||||
{
|
||||
this.client = client;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.modelOutliner = modelOutliner;
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
}
|
||||
@@ -173,6 +178,22 @@ public class NpcSceneOverlay extends Overlay
|
||||
|
||||
renderPoly(graphics, color, objectClickbox);
|
||||
break;
|
||||
|
||||
case THIN_OUTLINE:
|
||||
modelOutliner.drawOutline(actor, 1, color);
|
||||
break;
|
||||
|
||||
case OUTLINE:
|
||||
modelOutliner.drawOutline(actor, 2, color);
|
||||
break;
|
||||
|
||||
case THIN_GLOW:
|
||||
modelOutliner.drawOutline(actor, 4, color, TRANSPARENT);
|
||||
break;
|
||||
|
||||
case GLOW:
|
||||
modelOutliner.drawOutline(actor, 8, color, TRANSPARENT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.drawNames())
|
||||
|
||||
@@ -29,7 +29,11 @@ public enum RenderStyle
|
||||
OFF("Off"),
|
||||
TILE("Tile"),
|
||||
HULL("Hull"),
|
||||
SOUTH_WEST_TILE("South West Tile");
|
||||
SOUTH_WEST_TILE("South West Tile"),
|
||||
THIN_OUTLINE("Thin outline"),
|
||||
OUTLINE("Outline"),
|
||||
THIN_GLOW("Thin glow"),
|
||||
GLOW("Glow");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Davis Cook <daviscook447@gmail.com>
|
||||
* 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.slayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class KnapsackSolver
|
||||
{
|
||||
|
||||
private List<Integer> reconstructItemsInSack(int [] [] sackMatrix, List<Integer> items, int i, int w)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
if (sackMatrix[i][w] > sackMatrix[i - 1][w])
|
||||
{
|
||||
List<Integer> list = reconstructItemsInSack(sackMatrix, items,
|
||||
i - 1, w - items.get(i - 1));
|
||||
list.add(items.get(i - 1));
|
||||
return list;
|
||||
}
|
||||
else
|
||||
{
|
||||
return reconstructItemsInSack(sackMatrix, items, i - 1, w);
|
||||
}
|
||||
}
|
||||
|
||||
public int howMuchFitsInSack(List<Integer> items, int maxWeight)
|
||||
{
|
||||
int itemCount = items.size();
|
||||
|
||||
int[] [] sackMatrix = new int[itemCount + 1] [maxWeight + 1];
|
||||
for (int i = 1; i <= itemCount; i++)
|
||||
{
|
||||
for (int j = 0; j <= maxWeight; j++)
|
||||
{
|
||||
if (items.get(i - 1) > j)
|
||||
{
|
||||
sackMatrix[i] [j] = sackMatrix[i - 1] [j];
|
||||
}
|
||||
else
|
||||
{
|
||||
sackMatrix[i] [j] = Math.max(
|
||||
sackMatrix[i - 1] [j],
|
||||
sackMatrix[i - 1] [j - items.get(i - 1)] + items.get(i - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reconstructItemsInSack(sackMatrix, items, itemCount, maxWeight).size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.NPC;
|
||||
|
||||
public class NPCPresence
|
||||
{
|
||||
|
||||
private static final int FADE_TIMER_START = 20;
|
||||
|
||||
@Getter
|
||||
private String name;
|
||||
|
||||
@Getter
|
||||
private int combatLevel;
|
||||
|
||||
private int fadeTimer;
|
||||
|
||||
private NPCPresence(String name, int combatLevel)
|
||||
{
|
||||
this.name = name;
|
||||
this.combatLevel = combatLevel;
|
||||
this.fadeTimer = FADE_TIMER_START;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return name + "[" + combatLevel + "]";
|
||||
}
|
||||
|
||||
public boolean shouldExist()
|
||||
{
|
||||
return fadeTimer > 0;
|
||||
}
|
||||
|
||||
public void tickExistence()
|
||||
{
|
||||
fadeTimer--;
|
||||
}
|
||||
|
||||
public static NPCPresence buildPresence(NPC npc)
|
||||
{
|
||||
return new NPCPresence(npc.getName(), npc.getCombatLevel());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
public enum RenderStyle
|
||||
{
|
||||
TILE("Tile"),
|
||||
HULL("Hull"),
|
||||
SOUTH_WEST_TILE("South West Tile"),
|
||||
THIN_OUTLINE("Thin outline"),
|
||||
OUTLINE("Outline"),
|
||||
THIN_GLOW("Thin glow"),
|
||||
GLOW("Glow"),
|
||||
TRUE_LOCATIONS("True Location");
|
||||
|
||||
private final String name;
|
||||
|
||||
RenderStyle(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,18 @@ public interface SlayerConfig extends Config
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 6,
|
||||
position = 7,
|
||||
keyName = "highlightStyle",
|
||||
name = "Highlight Style",
|
||||
description = "Highlight setting"
|
||||
)
|
||||
default RenderStyle renderStyle()
|
||||
{
|
||||
return RenderStyle.THIN_OUTLINE;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
keyName = "targetColor",
|
||||
name = "Target Color",
|
||||
description = "Color of the highlighted targets"
|
||||
@@ -101,7 +112,7 @@ public interface SlayerConfig extends Config
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 7,
|
||||
position = 8,
|
||||
keyName = "superiorColor",
|
||||
name = "Superior Color",
|
||||
description = "Color of the highlighted superior slayer creatures"
|
||||
@@ -112,7 +123,29 @@ public interface SlayerConfig extends Config
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
position = 9,
|
||||
keyName = "drawNames",
|
||||
name = "Draw names above NPC",
|
||||
description = "Configures whether or not NPC names should be drawn above the NPC"
|
||||
)
|
||||
default boolean drawNames()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 10,
|
||||
keyName = "drawMinimapNames",
|
||||
name = "Draw names on minimap",
|
||||
description = "Configures whether or not NPC names should be drawn on the minimap"
|
||||
)
|
||||
default boolean drawMinimapNames()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 12,
|
||||
keyName = "weaknessPrompt",
|
||||
name = "Show Monster Weakness",
|
||||
description = "Show an overlay on a monster when it is weak enough to finish off (Only Lizards, Gargoyles & Rockslugs)"
|
||||
@@ -123,7 +156,7 @@ public interface SlayerConfig extends Config
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
position = 8,
|
||||
position = 13,
|
||||
keyName = "taskCommand",
|
||||
name = "Task Command",
|
||||
description = "Configures whether the slayer task command is enabled<br> !task"
|
||||
@@ -180,6 +213,7 @@ public interface SlayerConfig extends Config
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "initialAmount",
|
||||
name = "",
|
||||
@@ -205,6 +239,24 @@ public interface SlayerConfig extends Config
|
||||
)
|
||||
void taskLocation(String key);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lastCertainAmount",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default int lastCertainAmount()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "lastCertainAmount",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void lastCertainAmount(int lastCertainAmount);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "streak",
|
||||
name = "",
|
||||
@@ -222,58 +274,4 @@ public interface SlayerConfig extends Config
|
||||
description = ""
|
||||
)
|
||||
void streak(int streak);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "points",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default int points()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "points",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void points(int points);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "expeditious",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default int expeditious()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "expeditious",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void expeditious(int expeditious);
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "slaughter",
|
||||
name = "",
|
||||
description = "",
|
||||
hidden = true
|
||||
)
|
||||
default int slaughter()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "slaughter",
|
||||
name = "",
|
||||
description = ""
|
||||
)
|
||||
void slaughter(int slaughter);
|
||||
}
|
||||
|
||||
@@ -104,32 +104,23 @@ class SlayerOverlay extends WidgetItemOverlay
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = plugin.getAmount();
|
||||
if (amount <= 0)
|
||||
if (plugin.getCurrentTask() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int slaughterCount = plugin.getSlaughterChargeCount();
|
||||
int expeditiousCount = plugin.getExpeditiousChargeCount();
|
||||
int amount = plugin.getCurrentTask().getAmount();
|
||||
if (amount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
graphics.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
final Rectangle bounds = itemWidget.getCanvasBounds();
|
||||
final TextComponent textComponent = new TextComponent();
|
||||
|
||||
switch (itemId)
|
||||
{
|
||||
case ItemID.EXPEDITIOUS_BRACELET:
|
||||
textComponent.setText(String.valueOf(expeditiousCount));
|
||||
break;
|
||||
case ItemID.BRACELET_OF_SLAUGHTER:
|
||||
textComponent.setText(String.valueOf(slaughterCount));
|
||||
break;
|
||||
default:
|
||||
textComponent.setText(String.valueOf(amount));
|
||||
break;
|
||||
}
|
||||
textComponent.setText(String.valueOf(amount));
|
||||
|
||||
// Draw the counter in the bottom left for equipment, and top left for jewelry
|
||||
textComponent.setPosition(new Point(bounds.x, bounds.y + (SLAYER_JEWELRY.contains(itemId)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,368 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.PluginPanel;
|
||||
import net.runelite.client.ui.components.PluginErrorPanel;
|
||||
import net.runelite.client.util.ColorUtil;
|
||||
import net.runelite.client.util.ImageUtil;
|
||||
import net.runelite.client.util.StackFormatter;
|
||||
|
||||
public class SlayerTaskPanel extends PluginPanel
|
||||
{
|
||||
private static final long MILLIS_PER_SECOND = 1000;
|
||||
private static final long SECONDS_PER_MINUTE = 60;
|
||||
private static final long MINUTES_PER_HOUR = 60;
|
||||
|
||||
// Templates
|
||||
private static final String HTML_LABEL_TEMPLATE =
|
||||
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
|
||||
private static final String HTML_TIME_LABEL_TEMPLATE =
|
||||
"<html><body style='color:%s'>%s<span style='color:white'>%02d:%02d:%02d</span></body></html>";
|
||||
|
||||
|
||||
private static final ImageIcon PAUSE, PAUSE_FADED, PAUSE_HOVER;
|
||||
private static final ImageIcon PLAY, PLAY_FADED, PLAY_HOVER;
|
||||
|
||||
// TODO: set some kind of maximum for the amount of tasks to be tracked in a session
|
||||
private static final int MAX_TASK_BOXES = 50;
|
||||
|
||||
// When there are no tasks, display this
|
||||
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
|
||||
|
||||
// Handle task boxes
|
||||
private final JPanel tasksContainer = new JPanel();
|
||||
|
||||
// Handle overall slayer session data
|
||||
private final JPanel overallPanel = new JPanel();
|
||||
private final JLabel overallKillsLabel = new JLabel();
|
||||
private final JLabel overallTimeLabel = new JLabel();
|
||||
private final JLabel overallIcon = new JLabel();
|
||||
|
||||
// Actions
|
||||
private final JPanel actionsContainer = new JPanel();
|
||||
private final JLabel playBtn = new JLabel();
|
||||
private final JLabel pauseBtn = new JLabel();
|
||||
|
||||
// Log tasks
|
||||
private final List<TaskBox> tasks = new ArrayList<>();
|
||||
|
||||
private SlayerPlugin slayerPlugin;
|
||||
|
||||
static
|
||||
{
|
||||
final BufferedImage pauseImg = ImageUtil.getResourceStreamFromClass(SlayerPlugin.class, "pause_icon.png");
|
||||
final BufferedImage playImg = ImageUtil.getResourceStreamFromClass(SlayerPlugin.class, "play_icon.png");
|
||||
|
||||
PAUSE = new ImageIcon(pauseImg);
|
||||
PAUSE_FADED = new ImageIcon(ImageUtil.alphaOffset(pauseImg, -180));
|
||||
PAUSE_HOVER = new ImageIcon(ImageUtil.alphaOffset(pauseImg, -220));
|
||||
|
||||
PLAY = new ImageIcon(playImg);
|
||||
PLAY_FADED = new ImageIcon(ImageUtil.alphaOffset(playImg, -180));
|
||||
PLAY_HOVER = new ImageIcon(ImageUtil.alphaOffset(playImg, -220));
|
||||
}
|
||||
|
||||
public SlayerTaskPanel(SlayerPlugin slayerPlugin)
|
||||
{
|
||||
this.slayerPlugin = slayerPlugin;
|
||||
|
||||
setBorder(new EmptyBorder(6, 6, 6, 6));
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
// Create layout panel for wrapping
|
||||
final JPanel layoutPanel = new JPanel();
|
||||
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
|
||||
add(layoutPanel, BorderLayout.NORTH);
|
||||
|
||||
actionsContainer.setLayout(new BorderLayout());
|
||||
actionsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
actionsContainer.setPreferredSize(new Dimension(0, 30));
|
||||
actionsContainer.setBorder(new EmptyBorder(5, 5, 5, 10));
|
||||
actionsContainer.setVisible(false);
|
||||
|
||||
final JPanel controlsPanel = new JPanel(new GridLayout(1, 2, 10, 0));
|
||||
controlsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
playBtn.setIcon(PLAY);
|
||||
playBtn.setToolTipText("Resume the current slayer task");
|
||||
playBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
slayerPlugin.setPaused(false);
|
||||
changePauseState(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
boolean paused = true;
|
||||
TaskData currentTask = slayerPlugin.getCurrentTask();
|
||||
if (currentTask != null)
|
||||
{
|
||||
paused = currentTask.isPaused();
|
||||
}
|
||||
playBtn.setIcon(paused ? PLAY_FADED : PLAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
boolean paused = true;
|
||||
TaskData currentTask = slayerPlugin.getCurrentTask();
|
||||
if (currentTask != null)
|
||||
{
|
||||
paused = currentTask.isPaused();
|
||||
}
|
||||
playBtn.setIcon(paused ? PLAY_HOVER : PLAY);
|
||||
}
|
||||
});
|
||||
|
||||
pauseBtn.setIcon(PAUSE);
|
||||
pauseBtn.setToolTipText("Pause the current slayer task");
|
||||
pauseBtn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent)
|
||||
{
|
||||
slayerPlugin.setPaused(true);
|
||||
changePauseState(true);
|
||||
}
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent)
|
||||
{
|
||||
boolean paused = true;
|
||||
TaskData currentTask = slayerPlugin.getCurrentTask();
|
||||
if (currentTask != null)
|
||||
{
|
||||
paused = currentTask.isPaused();
|
||||
}
|
||||
pauseBtn.setIcon(paused ? PAUSE : PAUSE_FADED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent)
|
||||
{
|
||||
boolean paused = true;
|
||||
TaskData currentTask = slayerPlugin.getCurrentTask();
|
||||
if (currentTask != null)
|
||||
{
|
||||
paused = currentTask.isPaused();
|
||||
}
|
||||
pauseBtn.setIcon(paused ? PAUSE : PAUSE_HOVER);
|
||||
}
|
||||
});
|
||||
|
||||
controlsPanel.add(playBtn);
|
||||
controlsPanel.add(pauseBtn);
|
||||
|
||||
actionsContainer.add(controlsPanel, BorderLayout.EAST);
|
||||
changePauseState(true);
|
||||
if (slayerPlugin.getCurrentTask() != null)
|
||||
{
|
||||
changePauseState(slayerPlugin.getCurrentTask().isPaused());
|
||||
}
|
||||
|
||||
// Create panel that will contain overall data
|
||||
overallPanel.setBorder(new EmptyBorder(8, 10, 8, 10));
|
||||
overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
overallPanel.setLayout(new BorderLayout());
|
||||
overallPanel.setVisible(false);
|
||||
|
||||
// Add contents
|
||||
final JPanel overallInfo = new JPanel();
|
||||
overallInfo.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
overallInfo.setLayout(new GridLayout(2, 1));
|
||||
overallInfo.setBorder(new EmptyBorder(2, 10, 2, 0));
|
||||
overallKillsLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
overallTimeLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
overallInfo.add(overallKillsLabel);
|
||||
overallInfo.add(overallTimeLabel);
|
||||
overallPanel.add(overallIcon, BorderLayout.WEST);
|
||||
overallPanel.add(overallInfo, BorderLayout.CENTER);
|
||||
|
||||
tasksContainer.setLayout(new BoxLayout(tasksContainer, BoxLayout.Y_AXIS));
|
||||
layoutPanel.add(actionsContainer);
|
||||
layoutPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
layoutPanel.add(overallPanel);
|
||||
layoutPanel.add(tasksContainer);
|
||||
|
||||
// Add error pane
|
||||
errorPanel.setContent("Task trackers", "You have not received any slayer tasks yet.");
|
||||
add(errorPanel);
|
||||
}
|
||||
|
||||
void loadHeaderIcon(BufferedImage img)
|
||||
{
|
||||
overallIcon.setIcon(new ImageIcon(img));
|
||||
}
|
||||
|
||||
private void changePauseState(boolean paused)
|
||||
{
|
||||
playBtn.setIcon(paused ? PLAY_FADED : PLAY);
|
||||
pauseBtn.setIcon(paused ? PAUSE : PAUSE_FADED);
|
||||
}
|
||||
|
||||
private void updateOverall()
|
||||
{
|
||||
int overallKills = 0;
|
||||
long overallTime = 0;
|
||||
for (TaskBox box : tasks)
|
||||
{
|
||||
overallKills += box.getTaskData().getElapsedKills();
|
||||
overallTime += box.getTaskData().getElapsedTime();
|
||||
}
|
||||
|
||||
overallKillsLabel.setText(htmlLabel("Total kills: ", overallKills));
|
||||
overallTimeLabel.setText(htmlLabel("Total time: ", overallTime));
|
||||
}
|
||||
|
||||
private static boolean isEmptyTask(TaskData taskData)
|
||||
{
|
||||
return (taskData.getTaskName() == null || taskData.getTaskName().equals("")) && taskData.getAmount() == 0 && taskData.getInitialAmount() == 0;
|
||||
}
|
||||
|
||||
private void showMainView()
|
||||
{
|
||||
remove(errorPanel);
|
||||
actionsContainer.setVisible(true);
|
||||
overallPanel.setVisible(true);
|
||||
}
|
||||
|
||||
private TaskBox buildBox(SlayerPlugin plugin, JPanel container, TaskData data)
|
||||
{
|
||||
TaskBox newBox = new TaskBox(plugin, container, data.toBuilder().build());
|
||||
tasks.add(0, newBox);
|
||||
showMainView();
|
||||
return newBox;
|
||||
}
|
||||
|
||||
private boolean stringsEqualIncludeNull(String str0, String str1)
|
||||
{
|
||||
if (str0 == null && str1 == null)
|
||||
{
|
||||
return true; // both are null
|
||||
}
|
||||
else if (str0 == null || str1 == null)
|
||||
{
|
||||
return false; // only 1 is null
|
||||
}
|
||||
else
|
||||
{
|
||||
// none are null so equals check is safe
|
||||
return str0.equals(str1);
|
||||
}
|
||||
}
|
||||
|
||||
void updateCurrentTask(boolean updated, boolean paused, TaskData newData, boolean isNewAssignment)
|
||||
{
|
||||
// important case for if the current task is completed so the update will show the empty task
|
||||
if (isEmptyTask(newData))
|
||||
{
|
||||
if (tasks.isEmpty()) // if there is no current task an empty task doesn't do anything
|
||||
{
|
||||
return;
|
||||
}
|
||||
TaskBox current = tasks.get(0);
|
||||
// current task has ended even though it should still have 1 amount remaining b/c the ending chat message
|
||||
// pops before the slayer xp drop so we need to force the remaining kc to zero and add the last kc to
|
||||
// the elapsed kc
|
||||
if (current.getTaskData().getAmount() != 0) // must check not equal zero b/c otherwise this would constantly tick
|
||||
{
|
||||
int amountDelta = current.getTaskData().getAmount();
|
||||
current.getTaskData().setAmount(0);
|
||||
current.getTaskData().setElapsedKills(current.getTaskData().getElapsedKills() + amountDelta);
|
||||
// current task has ended so it should be paused
|
||||
current.update(true, true, current.getTaskData());
|
||||
updateOverall();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (tasks.isEmpty() || isNewAssignment)
|
||||
{
|
||||
// new task so append it to the front of the list
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TaskBox newBox = buildBox(slayerPlugin, tasksContainer, newData);
|
||||
newBox.update(true, newData.isPaused(), newData);
|
||||
});
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if here there is a current task so check if the current task matches
|
||||
// the update being sent
|
||||
TaskBox current = tasks.get(0);
|
||||
if (!stringsEqualIncludeNull(current.getTaskData().getTaskName(), newData.getTaskName()) ||
|
||||
!stringsEqualIncludeNull(current.getTaskData().getTaskLocation(), newData.getTaskLocation()) ||
|
||||
current.getTaskData().getInitialAmount() != newData.getInitialAmount())
|
||||
{
|
||||
// current task does not match the update being sent so the current task
|
||||
// must have been outdated - this is necessarily true because if a true
|
||||
// new task was sent it would have set the isNewAssignment flag
|
||||
|
||||
// so this previous task is invalid so delete it then add in the new actually
|
||||
// correct task
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
tasksContainer.remove(tasks.get(0));
|
||||
tasks.remove(0);
|
||||
TaskBox newBox = buildBox(slayerPlugin, tasksContainer, newData);
|
||||
newBox.update(true, newData.isPaused(), newData);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// not an empty assignment or a new assignment so just update the current assignment
|
||||
TaskBox current = tasks.get(0);
|
||||
current.update(updated, paused, newData);
|
||||
|
||||
// update the overall stats once this task stats are updated
|
||||
updateOverall();
|
||||
changePauseState(paused);
|
||||
}
|
||||
|
||||
static String htmlLabel(String key, long timeMillis)
|
||||
{
|
||||
if (timeMillis == Long.MAX_VALUE)
|
||||
{
|
||||
String valueStr = "N/A";
|
||||
return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR),
|
||||
key, valueStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
long seconds = timeMillis / MILLIS_PER_SECOND;
|
||||
long minutes = seconds / SECONDS_PER_MINUTE;
|
||||
seconds %= 60;
|
||||
long hours = minutes / MINUTES_PER_HOUR;
|
||||
minutes %= 60;
|
||||
return String.format(HTML_TIME_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR),
|
||||
key, (int) hours, (int) minutes, (int) seconds);
|
||||
}
|
||||
}
|
||||
|
||||
static String htmlLabel(String key, int value)
|
||||
{
|
||||
String valueStr = StackFormatter.quantityToRSDecimalStack(value);
|
||||
return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR),
|
||||
key, valueStr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Davis Cook <daviscook447@gmail.com>
|
||||
* 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.slayer;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SlayerXpDropLookup
|
||||
{
|
||||
private Map<String, List<Double>> xpMap;
|
||||
|
||||
// floating point math equality
|
||||
private static final double EPSILON = 1e-6;
|
||||
|
||||
void loadXpJson()
|
||||
{
|
||||
final InputStream xpFile = getClass().getResourceAsStream("/slayer_xp.json");
|
||||
Gson gson = new Gson();
|
||||
xpMap = gson.fromJson(new InputStreamReader(xpFile), new TypeToken<Map<String, List<Double>>>()
|
||||
{
|
||||
|
||||
}.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the xp for a given npc using the xp + combat level data provided
|
||||
* from the JSON - since scrapping from the wiki isn't perfectly accurate
|
||||
* we make some estimations
|
||||
*
|
||||
* precondition is that xpCombatLevel array is non-null - if it is null
|
||||
* we can simply return -1 to indicate no slayer xp because this npc
|
||||
* has no associated xpCombatLevel array
|
||||
*
|
||||
* 1. first check to see if anywhere in the xp + combat level data this
|
||||
* creature name give slayer xp - if it doesn't just return -1 and
|
||||
* be done with this - if it does give slayer xp then continue
|
||||
* 2. now check to see if we can find the xp for this combat level where
|
||||
* that xp is greater than 0. note that we don't just find the xp for
|
||||
* this combat level - this is because for some monsters the wiki
|
||||
* only has slayer xp data for some combat levels and has it unknown
|
||||
* for the other combat levels. this way we only return the combat level
|
||||
* related xp data for a monster if it is know
|
||||
* 3. finally if the slayer xp data for the monster was unknown for the given
|
||||
* level we estimate the slayer xp by using one of the slayer xps for a level
|
||||
* that does have xp given
|
||||
* 4. note that if a monster gives no slayer xp for any level it will return
|
||||
* -1 so we don't accidentally misscount non-slayer targets dying as giving
|
||||
* slayer xp
|
||||
*
|
||||
* @param npc the npc we are estimating slayer xp for
|
||||
* @return our best guess for the slayer xp for this npc
|
||||
*/
|
||||
public double findXpForNpc(NPCPresence npc)
|
||||
{
|
||||
List<Double> xpCombatLevel = xpMap.get(npc.getName());
|
||||
if (xpCombatLevel == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
boolean givesSlayerXp = false;
|
||||
for (int i = 0; i < xpCombatLevel.size() - 1; i += 2)
|
||||
{
|
||||
if (xpCombatLevel.get(i) > 0)
|
||||
{
|
||||
givesSlayerXp = true;
|
||||
}
|
||||
}
|
||||
if (!givesSlayerXp)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
boolean foundCombatLevel = false;
|
||||
for (int i = 0; i < xpCombatLevel.size() - 1; i += 2)
|
||||
{
|
||||
if (Math.abs(xpCombatLevel.get(i + 1) - npc.getCombatLevel()) < EPSILON
|
||||
&& xpCombatLevel.get(i) > 0)
|
||||
{
|
||||
foundCombatLevel = true;
|
||||
}
|
||||
}
|
||||
if (foundCombatLevel)
|
||||
{
|
||||
for (int i = 0; i < xpCombatLevel.size() - 1; i += 2)
|
||||
{
|
||||
if (Math.abs(xpCombatLevel.get(i + 1) - npc.getCombatLevel()) < EPSILON)
|
||||
{
|
||||
return xpCombatLevel.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < xpCombatLevel.size() - 1; i += 2)
|
||||
{
|
||||
if (xpCombatLevel.get(i) > 0)
|
||||
{
|
||||
return xpCombatLevel.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public SlayerXpDropLookup()
|
||||
{
|
||||
loadXpJson();
|
||||
}
|
||||
}
|
||||
@@ -33,21 +33,37 @@ import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NPCComposition;
|
||||
import net.runelite.api.Perspective;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.client.graphics.ModelOutlineRenderer;
|
||||
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.OverlayUtil;
|
||||
import net.runelite.client.util.Text;
|
||||
|
||||
public class TargetClickboxOverlay extends Overlay
|
||||
{
|
||||
private static final Color TRANSPARENT = new Color(0, 0, 0, 0);
|
||||
|
||||
private final Client client;
|
||||
private final SlayerConfig config;
|
||||
private final SlayerPlugin plugin;
|
||||
private final ModelOutlineRenderer modelOutliner;
|
||||
|
||||
@Inject
|
||||
TargetClickboxOverlay(SlayerConfig config, SlayerPlugin plugin)
|
||||
TargetClickboxOverlay(Client client, SlayerConfig config, SlayerPlugin plugin,
|
||||
ModelOutlineRenderer modelOutlineRenderer)
|
||||
{
|
||||
this.client = client;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.modelOutliner = modelOutlineRenderer;
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
}
|
||||
@@ -55,36 +71,104 @@ public class TargetClickboxOverlay extends Overlay
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
if (!config.highlightTargets())
|
||||
if (config.highlightTargets())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<NPC> targets = plugin.getHighlightedTargets();
|
||||
for (NPC target : targets)
|
||||
{
|
||||
Color coloration = config.getTargetColor();
|
||||
if (plugin.isSuperior(target.getName()))
|
||||
List<NPC> targets = plugin.getHighlightedTargets();
|
||||
for (NPC target : targets)
|
||||
{
|
||||
coloration = config.getSuperiorColor();
|
||||
}
|
||||
Color coloration = config.getTargetColor();
|
||||
if (plugin.isSuperior(target.getName()))
|
||||
{
|
||||
coloration = config.getSuperiorColor();
|
||||
}
|
||||
|
||||
renderTargetOverlay(graphics, target, coloration);
|
||||
renderNpcOverlay(graphics, target, coloration);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void renderTargetOverlay(Graphics2D graphics, NPC actor, Color color)
|
||||
private void renderNpcOverlay(Graphics2D graphics, NPC actor, Color color)
|
||||
{
|
||||
Polygon objectClickbox = actor.getConvexHull();
|
||||
if (objectClickbox != null)
|
||||
switch (config.renderStyle())
|
||||
{
|
||||
case SOUTH_WEST_TILE:
|
||||
LocalPoint lp1 = LocalPoint.fromWorld(client, actor.getWorldLocation());
|
||||
Polygon tilePoly1 = Perspective.getCanvasTilePoly(client, lp1);
|
||||
|
||||
renderPoly(graphics, color, tilePoly1);
|
||||
break;
|
||||
|
||||
case TILE:
|
||||
int size = 1;
|
||||
NPCComposition composition = actor.getTransformedComposition();
|
||||
if (composition != null)
|
||||
{
|
||||
size = composition.getSize();
|
||||
}
|
||||
LocalPoint lp = actor.getLocalLocation();
|
||||
Polygon tilePoly = Perspective.getCanvasTileAreaPoly(client, lp, size);
|
||||
|
||||
renderPoly(graphics, color, tilePoly);
|
||||
break;
|
||||
|
||||
case HULL:
|
||||
Polygon objectClickbox = actor.getConvexHull();
|
||||
|
||||
renderPoly(graphics, color, objectClickbox);
|
||||
break;
|
||||
case THIN_OUTLINE:
|
||||
modelOutliner.drawOutline(actor, 1, color);
|
||||
break;
|
||||
|
||||
case OUTLINE:
|
||||
modelOutliner.drawOutline(actor, 2, color);
|
||||
break;
|
||||
|
||||
case THIN_GLOW:
|
||||
modelOutliner.drawOutline(actor, 4, color, TRANSPARENT);
|
||||
break;
|
||||
|
||||
case GLOW:
|
||||
modelOutliner.drawOutline(actor, 8, color, TRANSPARENT);
|
||||
break;
|
||||
case TRUE_LOCATIONS:
|
||||
size = 1;
|
||||
composition = actor.getTransformedComposition();
|
||||
if (composition != null)
|
||||
{
|
||||
size = composition.getSize();
|
||||
}
|
||||
WorldPoint wp = actor.getWorldLocation();
|
||||
lp = LocalPoint.fromWorld(client, wp);
|
||||
tilePoly = Perspective.getCanvasTileAreaPoly(client, lp, size);
|
||||
|
||||
renderPoly(graphics, color, tilePoly);
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.drawNames())
|
||||
{
|
||||
String npcName = Text.removeTags(actor.getName());
|
||||
Point textLocation = actor.getCanvasTextLocation(graphics, npcName, actor.getLogicalHeight() + 40);
|
||||
|
||||
if (textLocation != null)
|
||||
{
|
||||
OverlayUtil.renderTextLocation(graphics, textLocation, npcName, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderPoly(Graphics2D graphics, Color color, Polygon polygon)
|
||||
{
|
||||
if (polygon != null)
|
||||
{
|
||||
graphics.setColor(color);
|
||||
graphics.setStroke(new BasicStroke(2));
|
||||
graphics.draw(objectClickbox);
|
||||
graphics.draw(polygon);
|
||||
graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
|
||||
graphics.fill(objectClickbox);
|
||||
graphics.fill(polygon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.client.ui.overlay.Overlay;
|
||||
@@ -40,12 +41,15 @@ import net.runelite.client.ui.overlay.OverlayUtil;
|
||||
|
||||
public class TargetMinimapOverlay extends Overlay
|
||||
{
|
||||
|
||||
private final Client client;
|
||||
private final SlayerConfig config;
|
||||
private final SlayerPlugin plugin;
|
||||
|
||||
@Inject
|
||||
TargetMinimapOverlay(SlayerConfig config, SlayerPlugin plugin)
|
||||
TargetMinimapOverlay(Client client, SlayerConfig config, SlayerPlugin plugin)
|
||||
{
|
||||
this.client = client;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
@@ -63,18 +67,29 @@ public class TargetMinimapOverlay extends Overlay
|
||||
List<NPC> targets = plugin.getHighlightedTargets();
|
||||
for (NPC target : targets)
|
||||
{
|
||||
renderTargetOverlay(graphics, target, config.getTargetColor());
|
||||
Color coloration = config.getTargetColor();
|
||||
if (plugin.isSuperior(target.getName()))
|
||||
{
|
||||
coloration = config.getSuperiorColor();
|
||||
}
|
||||
|
||||
renderTargetOverlay(graphics, target, target.getName(), coloration);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void renderTargetOverlay(Graphics2D graphics, NPC actor, Color color)
|
||||
private void renderTargetOverlay(Graphics2D graphics, NPC actor, String name, Color color)
|
||||
{
|
||||
Point minimapLocation = actor.getMinimapLocation();
|
||||
if (minimapLocation != null)
|
||||
{
|
||||
OverlayUtil.renderMinimapLocation(graphics, minimapLocation, color);
|
||||
|
||||
if (config.drawMinimapNames())
|
||||
{
|
||||
OverlayUtil.renderTextLocation(graphics, minimapLocation, name, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ package net.runelite.client.plugins.slayer;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.NPC;
|
||||
@@ -65,35 +64,26 @@ class TargetWeaknessOverlay extends Overlay
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
final List<NPC> targets = plugin.getHighlightedTargets();
|
||||
|
||||
if (targets.isEmpty() || !config.weaknessPrompt())
|
||||
if (!config.weaknessPrompt())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final Task curTask = Task.getTask(plugin.getTaskName());
|
||||
if (curTask == null || curTask.getWeaknessThreshold() < 0 || curTask.getWeaknessItem() < 0)
|
||||
final Task npcTask = plugin.getWeaknessTask();
|
||||
|
||||
if (npcTask == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final int threshold = curTask.getWeaknessThreshold();
|
||||
final BufferedImage image = itemManager.getImage(curTask.getWeaknessItem());
|
||||
final NPC npc = (NPC) client.getLocalPlayer().getInteracting();
|
||||
final int threshold = npcTask.getWeaknessThreshold();
|
||||
final BufferedImage image = itemManager.getImage(npcTask.getWeaknessItem());
|
||||
final int currentHealth = calculateHealth(npc);
|
||||
|
||||
if (image == null)
|
||||
if (currentHealth >= 0 && currentHealth <= threshold)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (NPC target : targets)
|
||||
{
|
||||
final int currentHealth = calculateHealth(target);
|
||||
|
||||
if (currentHealth >= 0 && currentHealth <= threshold)
|
||||
{
|
||||
renderTargetItem(graphics, target, image);
|
||||
}
|
||||
renderTargetItem(graphics, npc, image);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -26,131 +26,191 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.ArrayList;
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.NpcID;
|
||||
|
||||
@Getter
|
||||
enum Task
|
||||
{
|
||||
|
||||
/*
|
||||
* format for enum is that the name of the task is first
|
||||
* second is the item id image we use to represent the task graphically
|
||||
* third is the list of names of monsters that can be killed on task (note that he task name is already handled
|
||||
* so that is why for a task like ankou there is no need to include this list)
|
||||
* fourth is the list of ids of monsters that can be killed on task (main reason for this is weird cases like baby dragons and the elf mourner)
|
||||
*/
|
||||
|
||||
//<editor-fold desc="Enums">
|
||||
ABERRANT_SPECTRES("Aberrant spectres", ItemID.ABERRANT_SPECTRE, "Spectre"),
|
||||
ABYSSAL_DEMONS("Abyssal demons", ItemID.ABYSSAL_DEMON),
|
||||
ABERRANT_SPECTRES("Aberrant spectres", ItemID.ABERRANT_SPECTRE,
|
||||
asList("Abhorrent spectre", "Deviant spectre", "Repugnant spectre"), Collections.emptyList()),
|
||||
ABYSSAL_DEMONS("Abyssal demons", ItemID.ABYSSAL_DEMON,
|
||||
asList("Ayssal Sire"), Collections.emptyList()),
|
||||
ABYSSAL_SIRE("Abyssal Sire", ItemID.ABYSSAL_ORPHAN),
|
||||
ADAMANT_DRAGONS("Adamant dragons", ItemID.ADAMANT_DRAGON_MASK),
|
||||
ALCHEMICAL_HYDRA("Alchemical Hydra", ItemID.IKKLE_HYDRA),
|
||||
ANKOU("Ankou", ItemID.ANKOU_MASK),
|
||||
AVIANSIES("Aviansies", ItemID.ENSOULED_AVIANSIE_HEAD),
|
||||
AVIANSIES("Aviansies", ItemID.ENSOULED_AVIANSIE_HEAD,
|
||||
asList("Kree'arra"), Collections.emptyList()),
|
||||
BANSHEES("Banshees", ItemID.BANSHEE),
|
||||
BARROWS_BROTHERS("Barrows Brothers", ItemID.KARILS_COIF),
|
||||
BARROWS_BROTHERS("Barrows Brothers", ItemID.KARILS_COIF,
|
||||
asList("Ahrim the blighted", "Dharok the wretched", "Guthan the infested", "Karil the tainted", "Torag the corrupted", "Verac the defiled"), Collections.emptyList()),
|
||||
BASILISKS("Basilisks", ItemID.BASILISK),
|
||||
BATS("Bats", ItemID.GIRAL_BAT_2),
|
||||
BEARS("Bears", ItemID.ENSOULED_BEAR_HEAD),
|
||||
ENTS("Ents", ItemID.NICE_TREE, "Ent"),
|
||||
LAVA_DRAGONS("Lava Dragons", ItemID.LAVA_SCALE, "Lava dragon"),
|
||||
BIRDS("Birds", ItemID.FEATHER, "Chicken", "Rooster", "Terrorbird", "Seagull"),
|
||||
BLACK_DEMONS("Black demons", ItemID.BLACK_DEMON_MASK),
|
||||
BLACK_DRAGONS("Black dragons", ItemID.BLACK_DRAGON_MASK),
|
||||
BEARS("Bears", ItemID.ENSOULED_BEAR_HEAD,
|
||||
asList("Callisto"), Collections.emptyList()),
|
||||
ENTS("Ents", ItemID.ENTS_ROOTS),
|
||||
LAVA_DRAGONS("Lava Dragons", ItemID.LAVA_SCALE),
|
||||
BANDITS("Bandits", ItemID.BANDIT),
|
||||
BIRDS("Birds", ItemID.FEATHER,
|
||||
asList("Chicken", "Rooster", "Terrorbird", "Seagull", "Chompy bird", "Jubbly bird", "Oomlie bird", "Vulture"), Collections.emptyList()),
|
||||
BLACK_DEMONS("Black demons", ItemID.BLACK_DEMON_MASK,
|
||||
asList("Demonic gorilla", "Balfrug kreeyath", "Skotizo"), Collections.emptyList()),
|
||||
BLACK_DRAGONS("Black dragons", ItemID.BLACK_DRAGON_MASK,
|
||||
Collections.emptyList(), asList(NpcID.BABY_DRAGON_1871, NpcID.BABY_DRAGON_1872, NpcID.BABY_DRAGON_7955)),
|
||||
BLOODVELD("Bloodveld", ItemID.BLOODVELD),
|
||||
BLUE_DRAGONS("Blue dragons", ItemID.BLUE_DRAGON_MASK),
|
||||
BLUE_DRAGONS("Blue dragons", ItemID.BLUE_DRAGON_MASK,
|
||||
asList("Vorkath"), asList(NpcID.BABY_DRAGON, NpcID.BABY_DRAGON_242, NpcID.BABY_DRAGON_243)),
|
||||
BRINE_RATS("Brine rats", ItemID.BRINE_RAT),
|
||||
BRONZE_DRAGONS("Bronze dragons", ItemID.BRONZE_DRAGON_MASK),
|
||||
CALLISTO("Callisto", ItemID.CALLISTO_CUB),
|
||||
CATABLEPON("Catablepon", ItemID.LEFT_SKULL_HALF),
|
||||
CAVE_BUGS("Cave bugs", ItemID.SWAMP_CAVE_BUG),
|
||||
CAVE_CRAWLERS("Cave crawlers", ItemID.CAVE_CRAWLER, "Chasm crawler"),
|
||||
CAVE_HORRORS("Cave horrors", ItemID.CAVE_HORROR, "Cave abomination"),
|
||||
CAVE_KRAKEN("Cave kraken", ItemID.CAVE_KRAKEN),
|
||||
CAVE_CRAWLERS("Cave crawlers", ItemID.CAVE_CRAWLER,
|
||||
asList("Chasm crawler"), Collections.emptyList()),
|
||||
CAVE_HORRORS("Cave horrors", ItemID.CAVE_HORROR,
|
||||
asList("Cave abomination"), Collections.emptyList()),
|
||||
CAVE_KRAKEN("Cave kraken", ItemID.CAVE_KRAKEN,
|
||||
asList("Kraken"), Collections.emptyList()),
|
||||
CAVE_SLIMES("Cave slimes", ItemID.SWAMP_CAVE_SLIME),
|
||||
CERBERUS("Cerberus", ItemID.HELLPUPPY),
|
||||
CHAOS_DRUIDS("Chaos druids", ItemID.ELDER_CHAOS_HOOD),
|
||||
CHAOS_ELEMENTAL("Chaos Elemental", ItemID.PET_CHAOS_ELEMENTAL),
|
||||
CHAOS_FANATIC("Chaos Fanatic", ItemID.ANCIENT_STAFF),
|
||||
COCKATRICE("Cockatrice", ItemID.COCKATRICE, "Cockathrice"),
|
||||
COCKATRICE("Cockatrice", ItemID.COCKATRICE,
|
||||
asList("Cockathrice"), Collections.emptyList()),
|
||||
COWS("Cows", ItemID.COW_MASK),
|
||||
CRAWLING_HANDS("Crawling hands", ItemID.CRAWLING_HAND, "Crushing hand"),
|
||||
CRAZY_ARCHAEOLOGIST("Crazy Archaeologists", ItemID.FEDORA, "Crazy Archaeologist"),
|
||||
CRAWLING_HANDS("Crawling hands", ItemID.CRAWLING_HAND,
|
||||
asList("Crushing hand"), Collections.emptyList()),
|
||||
CRAZY_ARCHAEOLOGIST("Crazy Archaeologist", ItemID.FEDORA),
|
||||
CROCODILES("Crocodiles", ItemID.SWAMP_LIZARD),
|
||||
DAGANNOTH("Dagannoth", ItemID.DAGANNOTH),
|
||||
DAGANNOTH_KINGS("Dagannoth Kings", ItemID.PET_DAGANNOTH_PRIME),
|
||||
DARK_BEASTS("Dark beasts", ItemID.DARK_BEAST, "Night beast"),
|
||||
DARK_WARRIORS("Dark warriors", ItemID.BLACK_MED_HELM, "Dark warrior"),
|
||||
DAGANNOTH("Dagannoth", ItemID.DAGANNOTH,
|
||||
asList("Dagannoth Rex", "Dagannoth Prime", "Dagannoth Supreme"), Collections.emptyList()),
|
||||
DAGANNOTH_KINGS("Dagannoth Kings", ItemID.PET_DAGANNOTH_PRIME,
|
||||
asList("Dagannoth Rex", "Dagannoth Prime", "Dagannoth Supreme"), Collections.emptyList()),
|
||||
DARK_BEASTS("Dark beasts", ItemID.DARK_BEAST,
|
||||
asList("Night beast"), Collections.emptyList()),
|
||||
DARK_WARRIORS("Dark warriors", ItemID.BLACK_MED_HELM),
|
||||
DERANGED_ARCHAEOLOGIST("Deranged Archaeologist", ItemID.ARCHAEOLOGISTS_DIARY),
|
||||
DESERT_LIZARDS("Desert lizards", ItemID.DESERT_LIZARD, 4, ItemID.ICE_COOLER, "Small lizard", "Lizard"),
|
||||
DOGS("Dogs", ItemID.GUARD_DOG, "Jackal"),
|
||||
DESERT_LIZARDS("Desert lizards", ItemID.DESERT_LIZARD,
|
||||
asList("Small lizard", "Lizard"), Collections.emptyList(), 4, ItemID.ICE_COOLER),
|
||||
DOGS("Dogs", ItemID.GUARD_DOG, asList("Jackal"), Collections.emptyList()),
|
||||
DRAKES("Drakes", ItemID.DRAKE),
|
||||
DUST_DEVILS("Dust devils", ItemID.DUST_DEVIL, "Choke devil"),
|
||||
DWARVES("Dwarves", ItemID.DWARVEN_HELMET, "Dwarf"),
|
||||
DUST_DEVILS("Dust devils", ItemID.DUST_DEVIL,
|
||||
asList("Choke devil"), Collections.emptyList()),
|
||||
DWARVES("Dwarves", ItemID.DWARVEN_HELMET,
|
||||
asList("Dwarf", "Black guard"), Collections.emptyList()),
|
||||
EARTH_WARRIORS("Earth warriors", ItemID.BRONZE_FULL_HELM_T),
|
||||
ELVES("Elves", ItemID.ELF, "Elf"),
|
||||
ELVES("Elves", ItemID.ELF,
|
||||
asList("Elf"), asList(NpcID.MOURNER_5311)),
|
||||
FEVER_SPIDERS("Fever spiders", ItemID.FEVER_SPIDER),
|
||||
FIRE_GIANTS("Fire giants", ItemID.FIRE_BATTLESTAFF),
|
||||
REVENANTS("Revenants", ItemID.BRACELET_OF_ETHEREUM, "Revenant imp", "Revenant goblin", "Revenant pyrefiend", "Revenant hobgoblin", "Revenant cyclops", "Revenant hellhound", "Revenant demon", "Revenant ork", "Revenant dark beast", "Revenant knight", "Revenant dragon"),
|
||||
FLESH_CRAWLERS("Fleshcrawlers", ItemID.ENSOULED_SCORPION_HEAD, "Flesh crawler"),
|
||||
FOSSIL_ISLAND_WYVERNS("Fossil island wyverns", ItemID.FOSSIL_ISLAND_WYVERN, "Ancient wyvern", "Long-tailed wyvern", "Spitting wyvern", "Taloned wyvern"),
|
||||
GARGOYLES("Gargoyles", ItemID.GARGOYLE, 9, ItemID.ROCK_HAMMER),
|
||||
REVENANTS("Revenants", ItemID.REVENANT_ETHER,
|
||||
asList("Revenant imp", "Revenant goblin", "Revenant pyrefiend", "Revenant hobgoblin", "Revenant cyclops", "Revenant hellhound", "Revenant demon", "Revenant ork", "Revenant dark beast", "Revenant knight", "Revenant dragon"), Collections.emptyList()),
|
||||
FLESH_CRAWLERS("Flesh crawlers", ItemID.ENSOULED_SCORPION_HEAD),
|
||||
FOSSIL_ISLAND_WYVERNS("Fossil island wyverns", ItemID.FOSSIL_ISLAND_WYVERN,
|
||||
asList("Ancient wyvern", "Long-tailed wyvern", "Spitting wyvern", "Taloned wyvern"), Collections.emptyList()),
|
||||
GARGOYLES("Gargoyles", ItemID.GARGOYLE,
|
||||
asList("Dusk", "Dawn"), Collections.emptyList(), 9, ItemID.ROCK_HAMMER),
|
||||
GENERAL_GRAARDOR("General Graardor", ItemID.PET_GENERAL_GRAARDOR),
|
||||
GHOSTS("Ghosts", ItemID.GHOSTSPEAK_AMULET, "Tortured soul"),
|
||||
GHOSTS("Ghosts", ItemID.GHOSTSPEAK_AMULET,
|
||||
asList("Tortured soul"), Collections.emptyList()),
|
||||
GIANT_MOLE("Giant Mole", ItemID.BABY_MOLE),
|
||||
GHOULS("Ghouls", ItemID.ZOMBIE_HEAD),
|
||||
GOBLINS("Goblins", ItemID.ENSOULED_GOBLIN_HEAD),
|
||||
GREATER_DEMONS("Greater demons", ItemID.GREATER_DEMON_MASK),
|
||||
GREEN_DRAGONS("Green dragons", ItemID.GREEN_DRAGON_MASK),
|
||||
GROTESQUE_GUARDIANS("Grotesque Guardians", ItemID.MIDNIGHT, 0, ItemID.ROCK_HAMMER, "Dusk", "Dawn"),
|
||||
GREATER_DEMONS("Greater demons", ItemID.GREATER_DEMON_MASK,
|
||||
asList("K'ril Tsutsaroth", "Tstanon Karlak", "Skotizo"), Collections.emptyList()),
|
||||
GREEN_DRAGONS("Green dragons", ItemID.GREEN_DRAGON_MASK,
|
||||
Collections.emptyList(), asList(NpcID.BABY_DRAGON_5194, NpcID.BABY_DRAGON_5872, NpcID.BABY_DRAGON_5873)),
|
||||
GROTESQUE_GUARDIANS("Grotesque Guardians", ItemID.MIDNIGHT,
|
||||
asList("Dusk", "Dawn"), Collections.emptyList(), 0, ItemID.ROCK_HAMMER),
|
||||
HARPIE_BUG_SWARMS("Harpie bug swarms", ItemID.SWARM),
|
||||
HELLHOUNDS("Hellhounds", ItemID.HELLHOUND),
|
||||
HILL_GIANTS("Hill giants", ItemID.ENSOULED_GIANT_HEAD),
|
||||
HELLHOUNDS("Hellhounds", ItemID.HELLHOUND,
|
||||
asList("Cerberus"), Collections.emptyList()),
|
||||
HILL_GIANTS("Hill giants", ItemID.ENSOULED_GIANT_HEAD,
|
||||
asList("Cyclops"), Collections.emptyList()),
|
||||
HOBGOBLINS("Hobgoblins", ItemID.HOBGOBLIN_GUARD),
|
||||
HYDRAS("Hydras", ItemID.HYDRA),
|
||||
ICEFIENDS("Icefiends", ItemID.ICE_DIAMOND),
|
||||
ICE_GIANTS("Ice giants", ItemID.ICE_DIAMOND),
|
||||
ICE_WARRIORS("Ice warriors", ItemID.MITHRIL_FULL_HELM_T),
|
||||
ICEFIENDS("Icefiends", ItemID.ICE_DIAMOND),
|
||||
INFERNAL_MAGES("Infernal mages", ItemID.INFERNAL_MAGE, "Malevolent mage"),
|
||||
INFERNAL_MAGES("Infernal mages", ItemID.INFERNAL_MAGE,
|
||||
asList("Malevolent mage"), Collections.emptyList()),
|
||||
IRON_DRAGONS("Iron dragons", ItemID.IRON_DRAGON_MASK),
|
||||
JAD("TzTok-Jad", ItemID.TZREKJAD),
|
||||
JELLIES("Jellies", ItemID.JELLY, "Jelly"),
|
||||
JELLIES("Jellies", ItemID.JELLY,
|
||||
asList("Jelly"), Collections.emptyList()),
|
||||
JUNGLE_HORROR("Jungle horrors", ItemID.ENSOULED_HORROR_HEAD),
|
||||
KALPHITE("Kalphite", ItemID.KALPHITE_SOLDIER),
|
||||
MAMMOTHS("Mammoths", ItemID.ATTACKER_HORN, "Mammoth"),
|
||||
KALPHITE_QUEEN("Kalphite Queen", ItemID.KALPHITE_PRINCESS),
|
||||
KILLERWATTS("Killerwatts", ItemID.KILLERWATT),
|
||||
KING_BLACK_DRAGON("King Black Dragon", ItemID.PRINCE_BLACK_DRAGON),
|
||||
KRAKEN("Cave Kraken Boss", ItemID.PET_KRAKEN, "Kraken"),
|
||||
KRAKEN("Cave Kraken Boss", ItemID.PET_KRAKEN,
|
||||
asList("Kraken"), Collections.emptyList()),
|
||||
KREEARRA("Kree'arra", ItemID.PET_KREEARRA),
|
||||
KRIL_TSUTSAROTH("K'ril Tsutsaroth", ItemID.PET_KRIL_TSUTSAROTH),
|
||||
KURASK("Kurask", ItemID.KURASK),
|
||||
ROGUES("Rogues", ItemID.ROGUE_MASK, "Rogue"),
|
||||
ROGUES("Rogues", ItemID.ROGUE_MASK, asList("Rogue"), Collections.emptyList()),
|
||||
LESSER_DEMONS("Lesser demons", ItemID.LESSER_DEMON_MASK),
|
||||
LIZARDMEN("Lizardmen", ItemID.LIZARDMAN_FANG, "Lizardman"),
|
||||
MINIONS_OF_SCABARAS("Minions of scabaras", ItemID.GOLDEN_SCARAB, "Scarab swarm", "Locust rider", "Scarab mage"),
|
||||
LIZARDMEN("Lizardmen", ItemID.LIZARDMAN_FANG,
|
||||
asList("Lizardman"), Collections.emptyList()),
|
||||
MAGIC_AXES("Magic axes", ItemID.IRON_BATTLEAXE),
|
||||
MAMMOTHS("Mammoths", ItemID.ATTACKER_HORN,
|
||||
asList("Mammoth"), Collections.emptyList()),
|
||||
MINIONS_OF_SCABARAS("Minions of scabaras", ItemID.GOLDEN_SCARAB,
|
||||
asList("Scarab swarm", "Locust rider", "Scarab mage"), Collections.emptyList()),
|
||||
MINOTAURS("Minotaurs", ItemID.ENSOULED_MINOTAUR_HEAD),
|
||||
MITHRIL_DRAGONS("Mithril dragons", ItemID.MITHRIL_DRAGON_MASK),
|
||||
MOGRES("Mogres", ItemID.MOGRE),
|
||||
MOLANISKS("Molanisks", ItemID.MOLANISK),
|
||||
MONKEYS("Monkeys", ItemID.ENSOULED_MONKEY_HEAD),
|
||||
MOSS_GIANTS("Moss giants", ItemID.HILL_GIANT_CLUB),
|
||||
MUTATED_ZYGOMITES("Mutated zygomites", ItemID.MUTATED_ZYGOMITE, 7, ItemID.FUNGICIDE_SPRAY_0, "Zygomite", "Fungi"),
|
||||
NECHRYAEL("Nechryael", ItemID.NECHRYAEL, "Nechryarch"),
|
||||
OGRES("Ogres", ItemID.ENSOULED_OGRE_HEAD),
|
||||
MOSS_GIANTS("Moss giants", ItemID.HILL_GIANT_CLUB,
|
||||
asList("Bryophyta"), Collections.emptyList()),
|
||||
MUTATED_ZYGOMITES("Mutated zygomites", ItemID.MUTATED_ZYGOMITE,
|
||||
asList("Zygomite"), Collections.emptyList(), 7, ItemID.FUNGICIDE_SPRAY_0),
|
||||
NECHRYAEL("Nechryael", ItemID.NECHRYAEL,
|
||||
asList("Nechryarch"), Collections.emptyList()),
|
||||
OGRES("Ogres", ItemID.ENSOULED_OGRE_HEAD,
|
||||
asList("Enclave guard"), Collections.emptyList()),
|
||||
OTHERWORLDLY_BEING("Otherworldly beings", ItemID.GHOSTLY_HOOD),
|
||||
PYREFIENDS("Pyrefiends", ItemID.PYREFIEND, "Flaming pyrelord"),
|
||||
PYREFIENDS("Pyrefiends", ItemID.PYREFIEND,
|
||||
asList("Flaming pyrelord"), Collections.emptyList()),
|
||||
RATS("Rats", ItemID.RATS_TAIL),
|
||||
RED_DRAGONS("Red dragons", ItemID.BABY_RED_DRAGON),
|
||||
RED_DRAGONS("Red dragons", ItemID.BABY_RED_DRAGON,
|
||||
Collections.emptyList(), asList(NpcID.BABY_DRAGON_244, NpcID.BABY_DRAGON_245, NpcID.BABY_DRAGON_246)),
|
||||
ROCKSLUGS("Rockslugs", ItemID.ROCKSLUG, 4, ItemID.BAG_OF_SALT),
|
||||
RUNE_DRAGONS("Rune dragons", ItemID.RUNE_DRAGON_MASK),
|
||||
SCORPIA("Scorpia", ItemID.SCORPIAS_OFFSPRING),
|
||||
CHAOS_DRUIDS("Chaos druids", ItemID.ELDER_CHAOS_HOOD, "Elder Chaos druid", "Chaos druid"),
|
||||
BANDITS("Bandits", ItemID.BANDIT, "Bandit"),
|
||||
MAGIC_AXES("Magic axes", ItemID.IRON_BATTLEAXE, "Magic axe"),
|
||||
SCORPIONS("Scorpions", ItemID.ENSOULED_SCORPION_HEAD),
|
||||
SCORPIONS("Scorpions", ItemID.ENSOULED_SCORPION_HEAD,
|
||||
asList("Scorpia"), Collections.emptyList()),
|
||||
SEA_SNAKES("Sea snakes", ItemID.SNAKE_CORPSE),
|
||||
SHADES("Shades", ItemID.SHADE_ROBE_TOP, "Loar Shadow", "Loar Shade", "Phrin Shadow", "Phrin Shade", "Riyl Shadow", "Riyl Shade", "Asyn Shadow", "Asyn Shade", "Fiyr Shadow", "Fiyr Shade"),
|
||||
SHADES("Shades", ItemID.SHADE_ROBE_TOP,
|
||||
asList("Loar Shadow", "Loar Shade", "Phrin Shadow", "Phrin Shade", "Riyl Shadow", "Riyl Shade", "Asyn Shadow", "Asyn Shade", "Fiyr Shadow", "Fiyr Shade"), Collections.emptyList()),
|
||||
SHADOW_WARRIORS("Shadow warriors", ItemID.BLACK_FULL_HELM),
|
||||
SKELETAL_WYVERNS("Skeletal wyverns", ItemID.SKELETAL_WYVERN),
|
||||
SKELETONS("Skeletons", ItemID.SKELETON_GUARD),
|
||||
SMOKE_DEVILS("Smoke devils", ItemID.SMOKE_DEVIL),
|
||||
SPIDERS("Spiders", ItemID.HUGE_SPIDER),
|
||||
SPIRITUAL_CREATURES("Spiritual creatures", ItemID.DRAGON_BOOTS, "Spiritual ranger", "Spiritual mage", "Spiritual warrior"),
|
||||
SPIDERS("Spiders", ItemID.HUGE_SPIDER,
|
||||
asList("Venenatis"), Collections.emptyList()),
|
||||
SPIRITUAL_CREATURES("Spiritual creatures", ItemID.DRAGON_BOOTS,
|
||||
asList("Spiritual ranger", "Spiritual mage", "Spiritual warrior"), Collections.emptyList()),
|
||||
STEEL_DRAGONS("Steel dragons", ItemID.STEEL_DRAGON),
|
||||
SULPHUR_LIZARDS("Sulphur Lizards", ItemID.SULPHUR_LIZARD),
|
||||
SUQAHS("Suqahs", ItemID.SUQAH_TOOTH),
|
||||
@@ -158,18 +218,23 @@ enum Task
|
||||
THERMONUCLEAR_SMOKE_DEVIL("Thermonuclear Smoke Devil", ItemID.PET_SMOKE_DEVIL),
|
||||
TROLLS("Trolls", ItemID.TROLL_GUARD),
|
||||
TUROTH("Turoth", ItemID.TUROTH),
|
||||
TZHAAR("Tzhaar", ItemID.ENSOULED_TZHAAR_HEAD),
|
||||
VAMPIRES("Vampires", ItemID.STAKE),
|
||||
TZHAAR("Tzhaar", ItemID.ENSOULED_TZHAAR_HEAD,
|
||||
asList("Tz-"), Collections.emptyList(), false),
|
||||
VAMPIRES("Vampires", ItemID.STAKE,
|
||||
asList("Vampyre"), Collections.emptyList()),
|
||||
VENENATIS("Venenatis", ItemID.VENENATIS_SPIDERLING),
|
||||
VETION("Vet'ion", ItemID.VETION_JR),
|
||||
VORKATH("Vorkath", ItemID.VORKI),
|
||||
WALL_BEASTS("Wall beasts", ItemID.SWAMP_WALLBEAST),
|
||||
WATERFIENDS("Waterfiends", ItemID.WATER_ORB),
|
||||
WEREWOLVES("Werewolves", ItemID.WOLFBANE, "Werewolf"),
|
||||
WOLVES("Wolves", ItemID.GREY_WOLF_FUR, "Wolf"),
|
||||
WEREWOLVES("Werewolves", ItemID.WOLFBANE,
|
||||
asList("Werewolf"), Collections.emptyList()),
|
||||
WOLVES("Wolves", ItemID.GREY_WOLF_FUR,
|
||||
asList("Wolf"), Collections.emptyList()),
|
||||
WYRMS("Wyrms", ItemID.WYRM),
|
||||
ZILYANA("Commander Zilyana", ItemID.PET_ZILYANA),
|
||||
ZOMBIES("Zombies", ItemID.ZOMBIE_HEAD, "Undead"),
|
||||
ZILYANA("Zilyana", ItemID.PET_ZILYANA),
|
||||
ZOMBIES("Zombies", ItemID.ZOMBIE_HEAD,
|
||||
asList("Undead"), Collections.emptyList()),
|
||||
ZULRAH("Zulrah", ItemID.PET_SNAKELING),
|
||||
ZUK("TzKal-Zuk", ItemID.TZREKZUK);
|
||||
//</editor-fold>
|
||||
@@ -178,7 +243,10 @@ enum Task
|
||||
|
||||
private final String name;
|
||||
private final int itemSpriteId;
|
||||
private final String[] targetNames;
|
||||
|
||||
private final List<String> targetNames;
|
||||
private final List<Integer> npcIds;
|
||||
private final boolean checkAsTokens;
|
||||
private final int weaknessThreshold;
|
||||
private final int weaknessItem;
|
||||
|
||||
@@ -190,7 +258,43 @@ enum Task
|
||||
}
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, String... targetNames)
|
||||
Task(String name, int itemSpriteId)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
this.itemSpriteId = itemSpriteId;
|
||||
this.weaknessThreshold = -1;
|
||||
this.weaknessItem = -1;
|
||||
this.targetNames = new ArrayList<>();
|
||||
this.npcIds = new ArrayList<>();
|
||||
this.checkAsTokens = true;
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, int weaknessThreshold, int weaknessItem)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
this.itemSpriteId = itemSpriteId;
|
||||
this.weaknessThreshold = weaknessThreshold;
|
||||
this.weaknessItem = weaknessItem;
|
||||
this.targetNames = new ArrayList<>();
|
||||
this.npcIds = new ArrayList<>();
|
||||
this.checkAsTokens = true;
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, boolean checkAsTokens)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
this.itemSpriteId = itemSpriteId;
|
||||
this.weaknessThreshold = -1;
|
||||
this.weaknessItem = -1;
|
||||
this.targetNames = new ArrayList<>();
|
||||
this.npcIds = new ArrayList<>();
|
||||
this.checkAsTokens = checkAsTokens;
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, List<String> targetNames, List<Integer> npcIds)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
@@ -198,9 +302,11 @@ enum Task
|
||||
this.weaknessThreshold = -1;
|
||||
this.weaknessItem = -1;
|
||||
this.targetNames = targetNames;
|
||||
this.npcIds = npcIds;
|
||||
this.checkAsTokens = true;
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, int weaknessThreshold, int weaknessItem, String... targetNames)
|
||||
Task(String name, int itemSpriteId, List<String> targetNames, List<Integer> npcIds, int weaknessThreshold, int weaknessItem)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
@@ -208,6 +314,20 @@ enum Task
|
||||
this.weaknessThreshold = weaknessThreshold;
|
||||
this.weaknessItem = weaknessItem;
|
||||
this.targetNames = targetNames;
|
||||
this.npcIds = npcIds;
|
||||
this.checkAsTokens = true;
|
||||
}
|
||||
|
||||
Task(String name, int itemSpriteId, List<String> targetNames, List<Integer> npcIds, boolean checkAsTokens)
|
||||
{
|
||||
Preconditions.checkArgument(itemSpriteId >= 0);
|
||||
this.name = name;
|
||||
this.itemSpriteId = itemSpriteId;
|
||||
this.weaknessThreshold = -1;
|
||||
this.weaknessItem = -1;
|
||||
this.targetNames = targetNames;
|
||||
this.npcIds = npcIds;
|
||||
this.checkAsTokens = checkAsTokens;
|
||||
}
|
||||
|
||||
static Task getTask(String taskName)
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Collections;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.runelite.client.ui.ColorScheme;
|
||||
import net.runelite.client.ui.DynamicGridLayout;
|
||||
import net.runelite.client.ui.FontManager;
|
||||
import net.runelite.client.ui.components.ProgressBar;
|
||||
import net.runelite.client.util.StackFormatter;
|
||||
|
||||
class TaskBox extends JPanel
|
||||
{
|
||||
private static final long MILLIS_PER_SECOND = 1000;
|
||||
private static final long SECONDS_PER_MINUTE = 60;
|
||||
private static final long MINUTES_PER_HOUR = 60;
|
||||
private static final long MILLIS_PER_HOUR = MILLIS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
|
||||
|
||||
private static final DecimalFormat TWO_DECIMAL_FORMAT = new DecimalFormat("0.00");
|
||||
|
||||
|
||||
// Templates
|
||||
private static final String HTML_TOOL_TIP_TEMPLATE =
|
||||
"<html>%.1f Kills/hr<br/>" +
|
||||
"%02d:%02d:%02d per kill</html>";
|
||||
private static final String HTML_LABEL_TEMPLATE =
|
||||
"<html><body style='color:white'>%s</body></html>";
|
||||
private static final String HTML_TIME_LABEL_TEMPLATE =
|
||||
"<html><body style='color:white'>%02d:%02d:%02d</span></body></html>";
|
||||
|
||||
// Instance members
|
||||
private final JPanel panel;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final TaskData taskData;
|
||||
|
||||
/* Contains the task icon and the stats panel */
|
||||
private final JPanel headerPanel = new JPanel();
|
||||
|
||||
/* Contains the overall stats of the slayer task */
|
||||
private final JPanel statsPanel = new JPanel();
|
||||
|
||||
private final ProgressBar progressBar = new ProgressBar();
|
||||
|
||||
private final JLabel elapsed = new JLabel("Elapsed:");
|
||||
private final JLabel remaining = new JLabel("Remaining:");
|
||||
private final JLabel duration = new JLabel("Time:");
|
||||
private final JLabel currentDuration = new JLabel();
|
||||
private final JLabel remainingDuration = new JLabel();
|
||||
private final JLabel kills = new JLabel("Kills:");
|
||||
private final JLabel currentKills = new JLabel();
|
||||
private final JLabel remainingKills = new JLabel();
|
||||
private final JLabel xp = new JLabel("XP:");
|
||||
private final JLabel currentXp = new JLabel();
|
||||
private final JLabel remainingXp = new JLabel();
|
||||
|
||||
private boolean paused = false;
|
||||
|
||||
TaskBox(SlayerPlugin slayerPlugin, JPanel panel, TaskData taskData)
|
||||
{
|
||||
this.panel = panel;
|
||||
this.taskData = taskData;
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||
|
||||
/* This task's wrapping container */
|
||||
JPanel container = new JPanel();
|
||||
container.setLayout(new BorderLayout());
|
||||
container.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
BufferedImage taskImg = slayerPlugin.getImageForTask(Task.getTask(taskData.getTaskName()));
|
||||
JLabel taskIcon = new JLabel(new ImageIcon(taskImg));
|
||||
taskIcon.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
taskIcon.setVerticalAlignment(SwingConstants.CENTER);
|
||||
taskIcon.setPreferredSize(new Dimension(35, 35));
|
||||
|
||||
statsPanel.setLayout(new DynamicGridLayout(4, 3));
|
||||
statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
statsPanel.setBorder(new EmptyBorder(9, 2, 9, 2));
|
||||
|
||||
elapsed.setFont(FontManager.getRunescapeSmallFont());
|
||||
remaining.setFont(FontManager.getRunescapeSmallFont());
|
||||
duration.setFont(FontManager.getRunescapeSmallFont());
|
||||
currentDuration.setFont(FontManager.getRunescapeSmallFont());
|
||||
remainingDuration.setFont(FontManager.getRunescapeSmallFont());
|
||||
kills.setFont(FontManager.getRunescapeSmallFont());
|
||||
currentKills.setFont(FontManager.getRunescapeSmallFont());
|
||||
remainingKills.setFont(FontManager.getRunescapeSmallFont());
|
||||
xp.setFont(FontManager.getRunescapeSmallFont());
|
||||
currentXp.setFont(FontManager.getRunescapeSmallFont());
|
||||
remainingXp.setFont(FontManager.getRunescapeSmallFont());
|
||||
|
||||
statsPanel.add(new JLabel());
|
||||
statsPanel.add(elapsed);
|
||||
statsPanel.add(remaining);
|
||||
statsPanel.add(duration);
|
||||
statsPanel.add(currentDuration);
|
||||
statsPanel.add(remainingDuration);
|
||||
statsPanel.add(kills);
|
||||
statsPanel.add(currentKills);
|
||||
statsPanel.add(remainingKills);
|
||||
statsPanel.add(xp);
|
||||
statsPanel.add(currentXp);
|
||||
statsPanel.add(remainingXp);
|
||||
|
||||
headerPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
headerPanel.setLayout(new BorderLayout());
|
||||
|
||||
headerPanel.add(statsPanel, BorderLayout.CENTER);
|
||||
headerPanel.add(taskIcon, BorderLayout.WEST);
|
||||
});
|
||||
|
||||
JPanel progressWrapper = new JPanel();
|
||||
progressWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||
progressWrapper.setLayout(new BorderLayout());
|
||||
progressWrapper.setBorder(new EmptyBorder(0, 7, 7, 7));
|
||||
|
||||
progressBar.setMaximumValue(100);
|
||||
progressBar.setBackground(new Color(61, 56, 49));
|
||||
|
||||
progressWrapper.add(progressBar, BorderLayout.NORTH);
|
||||
|
||||
final JPanel logTitle = new JPanel(new BorderLayout(5, 0));
|
||||
logTitle.setBorder(new EmptyBorder(7, 7, 7, 7));
|
||||
logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker());
|
||||
|
||||
String taskName = taskData.getTaskName();
|
||||
taskName = taskName.substring(0, 1).toUpperCase() + taskName.substring(1);
|
||||
final JLabel titleLabel = new JLabel(taskName);
|
||||
titleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
titleLabel.setForeground(Color.WHITE);
|
||||
|
||||
logTitle.add(titleLabel, BorderLayout.WEST);
|
||||
|
||||
final JLabel subTitleLabel = new JLabel("x " + taskData.getInitialAmount());
|
||||
subTitleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||
|
||||
logTitle.add(subTitleLabel, BorderLayout.CENTER);
|
||||
|
||||
if (taskData.getTaskLocation() != null && !taskData.getTaskLocation().equals(""))
|
||||
{
|
||||
final JLabel locationLabel = new JLabel(taskData.getTaskLocation());
|
||||
locationLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||
locationLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||
|
||||
logTitle.add(locationLabel, BorderLayout.EAST);
|
||||
}
|
||||
|
||||
container.add(logTitle, BorderLayout.NORTH);
|
||||
container.add(headerPanel, BorderLayout.CENTER);
|
||||
container.add(progressWrapper, BorderLayout.SOUTH);
|
||||
|
||||
add(container, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
void update(boolean updated, boolean paused, TaskData newData)
|
||||
{
|
||||
SwingUtilities.invokeLater(() -> rebuildAsync(updated, paused, newData));
|
||||
}
|
||||
|
||||
private void rebuildAsync(boolean updated, boolean taskPaused, TaskData newData)
|
||||
{
|
||||
if (updated)
|
||||
{
|
||||
if (getParent() != panel)
|
||||
{
|
||||
panel.add(this, 0);
|
||||
panel.revalidate();
|
||||
}
|
||||
|
||||
// Update data
|
||||
taskData.setElapsedKills(newData.getElapsedKills());
|
||||
taskData.setAmount(newData.getAmount());
|
||||
taskData.setElapsedXp(newData.getElapsedXp());
|
||||
|
||||
// Update information labels
|
||||
int kills = taskData.getInitialAmount() - taskData.getAmount();
|
||||
currentKills.setText(htmlLabel(taskData.getElapsedKills()));
|
||||
remainingKills.setText(htmlLabel(taskData.getAmount()));
|
||||
|
||||
currentXp.setText(htmlLabel(taskData.getElapsedXp()));
|
||||
double xpPerKill = ((double) taskData.getElapsedXp()) / ((double) taskData.getElapsedKills());
|
||||
double xpLeft = xpPerKill * taskData.getAmount();
|
||||
remainingXp.setText(htmlLabel((int) xpLeft));
|
||||
|
||||
// Update progress bar
|
||||
double percentComplete = ((double) kills) / ((double) taskData.getInitialAmount());
|
||||
progressBar.setForeground(new Color(98, 70, 70));
|
||||
progressBar.setValue((int) (percentComplete * 100));
|
||||
progressBar.setCenterLabel(TWO_DECIMAL_FORMAT.format(percentComplete * 100) + "%");
|
||||
progressBar.setLeftLabel("0 Kc");
|
||||
progressBar.setRightLabel(taskData.getInitialAmount() + " Kc");
|
||||
progressBar.setPositions(Collections.emptyList());
|
||||
|
||||
double killsPerMillis = ((double) taskData.getElapsedKills() - 1) / ((double) taskData.getElapsedTime());
|
||||
if (killsPerMillis > 0)
|
||||
{
|
||||
double killsPerHour = killsPerMillis * MILLIS_PER_HOUR;
|
||||
double millisPerKill = 1.0 / killsPerMillis;
|
||||
long seconds = ((long) millisPerKill) / MILLIS_PER_SECOND;
|
||||
long minutes = seconds / SECONDS_PER_MINUTE;
|
||||
seconds %= 60;
|
||||
long hours = minutes / MINUTES_PER_HOUR;
|
||||
minutes %= 60;
|
||||
progressBar.setToolTipText(String.format(
|
||||
HTML_TOOL_TIP_TEMPLATE,
|
||||
killsPerHour,
|
||||
hours,
|
||||
minutes,
|
||||
seconds
|
||||
));
|
||||
}
|
||||
|
||||
if (taskData.getAmount() == 0 && taskData.getElapsedXp() == 0)
|
||||
{
|
||||
progressBar.setDimmedText("Skipped");
|
||||
progressBar.setForeground(new Color(128, 0, 0));
|
||||
}
|
||||
else if ((taskData.getAmount() == 0 && taskData.getElapsedXp() > 0) || (int) (percentComplete * 100) >= 100)
|
||||
{
|
||||
progressBar.setDimmedText("Finished");
|
||||
progressBar.setForeground(new Color(0, 128, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
progressBar.setDimmedText("Paused");
|
||||
}
|
||||
|
||||
progressBar.setDimmed(taskPaused);
|
||||
progressBar.repaint();
|
||||
}
|
||||
else if (!paused && taskPaused)
|
||||
{
|
||||
progressBar.setDimmedText("Paused");
|
||||
progressBar.setDimmed(true);
|
||||
progressBar.repaint();
|
||||
paused = true;
|
||||
}
|
||||
else if (paused && !taskPaused)
|
||||
{
|
||||
progressBar.setDimmed(false);
|
||||
progressBar.repaint();
|
||||
paused = false;
|
||||
}
|
||||
|
||||
// Update duration separately, every time (not only when there's an update)
|
||||
taskData.setElapsedTime(newData.getElapsedTime());
|
||||
currentDuration.setText(htmlLabel(taskData.getElapsedTime()));
|
||||
remainingDuration.setText(htmlLabel(estimateRemainingTime(taskData)));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
private static long estimateRemainingTime(TaskData taskData)
|
||||
{
|
||||
int kills = taskData.getElapsedKills();
|
||||
int killsInElapsedTime = kills - 1; // b/c time only elapses after 1st slayer drop
|
||||
if (killsInElapsedTime < 1)
|
||||
{
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
double timePerKill = ((double) taskData.getElapsedTime()) / ((double) killsInElapsedTime);
|
||||
double remainingTime = timePerKill * taskData.getAmount();
|
||||
return (long) remainingTime;
|
||||
}
|
||||
|
||||
private static String htmlLabel(long timeMillis)
|
||||
{
|
||||
if (timeMillis == Long.MAX_VALUE)
|
||||
{
|
||||
String valueStr = "N/A";
|
||||
return String.format(HTML_LABEL_TEMPLATE, valueStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
long seconds = timeMillis / MILLIS_PER_SECOND;
|
||||
long minutes = seconds / SECONDS_PER_MINUTE;
|
||||
seconds %= 60;
|
||||
long hours = minutes / MINUTES_PER_HOUR;
|
||||
minutes %= 60;
|
||||
return String.format(HTML_TIME_LABEL_TEMPLATE, (int) hours, (int) minutes, (int) seconds);
|
||||
}
|
||||
}
|
||||
|
||||
private static String htmlLabel(int value)
|
||||
{
|
||||
String valueStr = StackFormatter.quantityToRSDecimalStack(value);
|
||||
return String.format(HTML_LABEL_TEMPLATE, valueStr);
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,10 @@
|
||||
*/
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.ui.overlay.infobox.Counter;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
class TaskCounter extends Counter
|
||||
{
|
||||
TaskCounter(BufferedImage img, Plugin plugin, int amount)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.runelite.client.plugins.slayer;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder(toBuilder=true)
|
||||
public class TaskData
|
||||
{
|
||||
private long elapsedTime;
|
||||
private int elapsedKills;
|
||||
private int elapsedXp;
|
||||
private int amount, initialAmount, lastCertainAmount;
|
||||
private String taskLocation;
|
||||
private String taskName;
|
||||
private boolean paused;
|
||||
|
||||
public void tick(long delta)
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
elapsedTime += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,33 @@ public class Scheduler
|
||||
return Collections.unmodifiableList(scheduledMethods);
|
||||
}
|
||||
|
||||
public void registerObject(Object obj)
|
||||
{
|
||||
for (Method method : obj.getClass().getMethods())
|
||||
{
|
||||
Schedule schedule = method.getAnnotation(Schedule.class);
|
||||
if (schedule == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ScheduledMethod scheduledMethod = new ScheduledMethod(schedule, method, obj);
|
||||
addScheduledMethod(scheduledMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterObject(Object obj)
|
||||
{
|
||||
for (ScheduledMethod sm : scheduledMethods)
|
||||
{
|
||||
if (sm.getObject() == obj)
|
||||
{
|
||||
removeScheduledMethod(sm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void tick()
|
||||
{
|
||||
Instant now = Instant.now();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 559 B |
Binary file not shown.
|
After Width: | Height: | Size: 189 B |
Binary file not shown.
|
After Width: | Height: | Size: 217 B |
1970
runelite-client/src/main/resources/slayer_xp.json
Normal file
1970
runelite-client/src/main/resources/slayer_xp.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -35,9 +35,11 @@ import static net.runelite.api.ChatMessageType.GAMEMESSAGE;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Varbits;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.VarbitChanged;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
import net.runelite.client.Notifier;
|
||||
@@ -73,7 +75,6 @@ public class SlayerPluginTest
|
||||
private static final String TASK_CHECKSLAYERGEM = "You're assigned to kill Suqahs; only 211 more to go.";
|
||||
private static final String TASK_CHECKSLAYERGEM_WILDERNESS = "You're assigned to kill Suqahs in the Wilderness; only 211 more to go.";
|
||||
private static final String TASK_CHECKSLAYERGEM_KONAR = "You're assigned to kill Blue dragons in the Ogre Enclave; only 122 more to go.";
|
||||
private static final String TASK_UPDATE_COMBAT_BRACELET = "You still need to kill 30 monsters to complete your current Slayer assignment";
|
||||
|
||||
private static final String TASK_BOSS_NEW = "Excellent. You're now assigned to kill Vet'ion 3 times.<br>Your reward point tally is 914.";
|
||||
private static final String TASK_BOSS_NEW_THE = "Excellent. You're now assigned to kill the Chaos <br>Elemental 3 times. Your reward point tally is 914.";
|
||||
@@ -92,24 +93,6 @@ public class SlayerPluginTest
|
||||
|
||||
private static final String SUPERIOR_MESSAGE = "A superior foe has appeared...";
|
||||
|
||||
private static final String BRACLET_SLAUGHTER = "Your bracelet of slaughter prevents your slayer count decreasing. It has 9 charges left.";
|
||||
private static final String BRACLET_EXPEDITIOUS = "Your expeditious bracelet helps you progress your slayer task faster. It has 9 charges left.";
|
||||
|
||||
private static final String BRACLET_SLAUGHTER_V2 = "Your bracelet of slaughter prevents your slayer count decreasing. It has 1 charge left.";
|
||||
private static final String BRACLET_EXPEDITIOUS_V2 = "Your expeditious bracelet helps you progress your slayer faster. It has 1 charge left.";
|
||||
|
||||
private static final String BRACLET_SLAUGHTER_V3 = "Your bracelet of slaughter prevents your slayer count decreasing. It then crumbles to dust.";
|
||||
private static final String BRACLET_EXPEDITIOUS_V3 = "Your expeditious bracelet helps you progress your slayer faster. It then crumbles to dust.";
|
||||
|
||||
private static final String CHAT_BRACELET_SLAUGHTER_CHARGE = "Your bracelet of slaughter has 12 charges left.";
|
||||
private static final String CHAT_BRACELET_EXPEDITIOUS_CHARGE = "Your expeditious bracelet has 12 charges left.";
|
||||
|
||||
private static final String CHAT_BRACELET_SLAUGHTER_CHARGE_ONE = "Your bracelet of slaughter has 1 charge left.";
|
||||
private static final String CHAT_BRACELET_EXPEDITIOUS_CHARGE_ONE = "Your expeditious bracelet has 1 charge left.";
|
||||
|
||||
private static final String BREAK_SLAUGHTER = "The bracelet shatters. Your next bracelet of slaughter<br>will start afresh from 30 charges.";
|
||||
private static final String BREAK_EXPEDITIOUS = "The bracelet shatters. Your next expeditious bracelet<br>will start afresh from 30 charges.";
|
||||
|
||||
@Mock
|
||||
@Bind
|
||||
Client client;
|
||||
@@ -157,6 +140,10 @@ public class SlayerPluginTest
|
||||
@Inject
|
||||
SlayerPlugin slayerPlugin;
|
||||
|
||||
@Mock
|
||||
@Bind
|
||||
SlayerTaskPanel panel;
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
@@ -171,8 +158,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(231, slayerPlugin.getAmount());
|
||||
assertEquals("Suqahs", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(231, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,9 +170,9 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Wyrms", slayerPlugin.getTaskName());
|
||||
assertEquals(147, slayerPlugin.getAmount());
|
||||
assertEquals("Karuulm Slayer Dungeon", slayerPlugin.getTaskLocation());
|
||||
assertEquals("Wyrms", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(147, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals("Karuulm Slayer Dungeon", slayerPlugin.getCurrentTask().getTaskLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -196,9 +183,9 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Hellhounds", slayerPlugin.getTaskName());
|
||||
assertEquals(142, slayerPlugin.getAmount());
|
||||
assertEquals("Witchhaven Dungeon", slayerPlugin.getTaskLocation());
|
||||
assertEquals("Hellhounds", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(142, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals("Witchhaven Dungeon", slayerPlugin.getCurrentTask().getTaskLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,8 +196,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("goblins", slayerPlugin.getTaskName());
|
||||
assertEquals(17, slayerPlugin.getAmount());
|
||||
assertEquals("goblins", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(17, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -221,8 +208,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(211, slayerPlugin.getAmount());
|
||||
assertEquals("Suqahs", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(211, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -233,8 +220,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Vet'ion", slayerPlugin.getTaskName());
|
||||
assertEquals(3, slayerPlugin.getAmount());
|
||||
assertEquals("Vet'ion", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(3, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals(914, slayerPlugin.getPoints());
|
||||
}
|
||||
|
||||
@@ -246,8 +233,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("Chaos Elemental", slayerPlugin.getTaskName());
|
||||
assertEquals(3, slayerPlugin.getAmount());
|
||||
assertEquals("Chaos Elemental", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(3, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals(914, slayerPlugin.getPoints());
|
||||
}
|
||||
|
||||
@@ -257,8 +244,8 @@ public class SlayerPluginTest
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", TASK_NEW_FROM_PARTNER, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals("Dust Devils", slayerPlugin.getTaskName());
|
||||
assertEquals(377, slayerPlugin.getAmount());
|
||||
assertEquals("Dust Devils", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(377, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -266,8 +253,8 @@ public class SlayerPluginTest
|
||||
{
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", TASK_CHECKSLAYERGEM, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(211, slayerPlugin.getAmount());
|
||||
assertEquals("Suqahs", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(211, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -275,9 +262,9 @@ public class SlayerPluginTest
|
||||
{
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", TASK_CHECKSLAYERGEM_WILDERNESS, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(211, slayerPlugin.getAmount());
|
||||
assertEquals("Wilderness", slayerPlugin.getTaskLocation());
|
||||
assertEquals("Suqahs", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(211, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals("Wilderness", slayerPlugin.getCurrentTask().getTaskLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -286,9 +273,9 @@ public class SlayerPluginTest
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", TASK_CHECKSLAYERGEM_KONAR, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals("Blue dragons", slayerPlugin.getTaskName());
|
||||
assertEquals(122, slayerPlugin.getAmount());
|
||||
assertEquals("Ogre Enclave", slayerPlugin.getTaskLocation());
|
||||
assertEquals("Blue dragons", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(122, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals("Ogre Enclave", slayerPlugin.getCurrentTask().getTaskLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -299,23 +286,8 @@ public class SlayerPluginTest
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals("suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(222, slayerPlugin.getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewardPointsWidget()
|
||||
{
|
||||
Widget rewardBar = mock(Widget.class);
|
||||
Widget rewardBarText = mock(Widget.class);
|
||||
Widget[] rewardBarChildren = new Widget[]{rewardBarText};
|
||||
|
||||
when(rewardBar.getDynamicChildren()).thenReturn(rewardBarChildren);
|
||||
when(rewardBarText.getText()).thenReturn(REWARD_POINTS);
|
||||
when(client.getWidget(WidgetInfo.SLAYER_REWARDS_TOPBAR)).thenReturn(rewardBar);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
assertEquals(17566, slayerPlugin.getPoints());
|
||||
assertEquals("suqahs", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(222, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -325,8 +297,8 @@ public class SlayerPluginTest
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(1, slayerPlugin.getStreak());
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -336,58 +308,66 @@ public class SlayerPluginTest
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(3, slayerPlugin.getStreak());
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPoints()
|
||||
{
|
||||
when(client.getVar(Varbits.SLAYER_REWARD_POINTS)).thenReturn(18_000);
|
||||
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "Perterter", TASK_POINTS, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
VarbitChanged varbitChanged = new VarbitChanged();
|
||||
slayerPlugin.onVarbitChanged(varbitChanged);
|
||||
|
||||
assertEquals(9, slayerPlugin.getStreak());
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals(18_000, slayerPlugin.getPoints());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeStreak()
|
||||
{
|
||||
when(client.getVar(Varbits.SLAYER_REWARD_POINTS)).thenReturn(17_566_000);
|
||||
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "Perterter", TASK_LARGE_STREAK, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
VarbitChanged varbitChanged = new VarbitChanged();
|
||||
slayerPlugin.onVarbitChanged(varbitChanged);
|
||||
|
||||
assertEquals(2465, slayerPlugin.getStreak());
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
assertEquals(17_566_000, slayerPlugin.getPoints());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplete()
|
||||
{
|
||||
slayerPlugin.setTaskName("cows");
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.getCurrentTask().setTaskName("cows");
|
||||
slayerPlugin.getCurrentTask().setAmount(42);
|
||||
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "Perterter", TASK_COMPLETE, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelled()
|
||||
{
|
||||
slayerPlugin.setTaskName("cows");
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.getCurrentTask().setTaskName("cows");
|
||||
slayerPlugin.getCurrentTask().setAmount(42);
|
||||
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "Perterter", TASK_CANCELED, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals("", slayerPlugin.getTaskName());
|
||||
assertEquals(0, slayerPlugin.getAmount());
|
||||
assertEquals("", slayerPlugin.getCurrentTask().getTaskName());
|
||||
assertEquals(0, slayerPlugin.getCurrentTask().getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -404,120 +384,6 @@ public class SlayerPluginTest
|
||||
verifyNoMoreInteractions(notifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBraceletSlaughter()
|
||||
{
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_SLAUGHTER, null, 0);
|
||||
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.setSlaughterChargeCount(10);
|
||||
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(9, slayerPlugin.getSlaughterChargeCount());
|
||||
assertEquals(43, slayerPlugin.getAmount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", CHAT_BRACELET_SLAUGHTER_CHARGE, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(12, slayerPlugin.getSlaughterChargeCount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", CHAT_BRACELET_SLAUGHTER_CHARGE_ONE, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(1, slayerPlugin.getSlaughterChargeCount());
|
||||
|
||||
slayerPlugin.setSlaughterChargeCount(1);
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_SLAUGHTER_V3, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(30, slayerPlugin.getSlaughterChargeCount());
|
||||
|
||||
Widget braceletBreakWidget = mock(Widget.class);
|
||||
when(braceletBreakWidget.getText()).thenReturn(BREAK_SLAUGHTER);
|
||||
when(client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT)).thenReturn(braceletBreakWidget);
|
||||
|
||||
slayerPlugin.setSlaughterChargeCount(-1);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
assertEquals(30, slayerPlugin.getSlaughterChargeCount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_SLAUGHTER_V2, null, 0);
|
||||
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.setSlaughterChargeCount(2);
|
||||
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(1, slayerPlugin.getSlaughterChargeCount());
|
||||
assertEquals(43, slayerPlugin.getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBraceletExpeditious()
|
||||
{
|
||||
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_EXPEDITIOUS, null, 0);
|
||||
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.setExpeditiousChargeCount(10);
|
||||
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(41, slayerPlugin.getAmount());
|
||||
assertEquals(9, slayerPlugin.getExpeditiousChargeCount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", CHAT_BRACELET_EXPEDITIOUS_CHARGE, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(12, slayerPlugin.getExpeditiousChargeCount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", CHAT_BRACELET_EXPEDITIOUS_CHARGE_ONE, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(1, slayerPlugin.getExpeditiousChargeCount());
|
||||
|
||||
slayerPlugin.setExpeditiousChargeCount(1);
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_EXPEDITIOUS_V3, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(30, slayerPlugin.getExpeditiousChargeCount());
|
||||
|
||||
Widget braceletBreakWidget = mock(Widget.class);
|
||||
when(braceletBreakWidget.getText()).thenReturn(BREAK_EXPEDITIOUS);
|
||||
when(client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT)).thenReturn(braceletBreakWidget);
|
||||
|
||||
slayerPlugin.setExpeditiousChargeCount(-1);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
assertEquals(30, slayerPlugin.getExpeditiousChargeCount());
|
||||
|
||||
chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", BRACLET_EXPEDITIOUS_V2, null, 0);
|
||||
|
||||
slayerPlugin.setAmount(42);
|
||||
slayerPlugin.setExpeditiousChargeCount(2);
|
||||
|
||||
slayerPlugin.onChatMessage(chatMessageEvent);
|
||||
|
||||
assertEquals(41, slayerPlugin.getAmount());
|
||||
assertEquals(1, slayerPlugin.getExpeditiousChargeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCombatBraceletUpdate()
|
||||
{
|
||||
final Player player = mock(Player.class);
|
||||
when(player.getLocalLocation()).thenReturn(new LocalPoint(0, 0));
|
||||
when(client.getLocalPlayer()).thenReturn(player);
|
||||
|
||||
slayerPlugin.setTaskName("Suqahs");
|
||||
slayerPlugin.setAmount(231);
|
||||
|
||||
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", TASK_UPDATE_COMBAT_BRACELET, null, 0);
|
||||
slayerPlugin.onChatMessage(chatMessage);
|
||||
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
slayerPlugin.killedOne();
|
||||
assertEquals(30, slayerPlugin.getAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskLookup() throws IOException
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user