* Mini GPU overhaul

* Alch value

* Stoned Loot Tracker (persistent loot tracker)

* Checkstyle

* Added back max values to GPU plugin

* Persistent loot tracker config options

* Zulrah plugin type

* Add inventory highlight plugin

When dragging an item in the inventory, a highlight will show up to help
the player place the item in the correct slot, instead of missing when
they drag the item to the "gutter"

* Fix inventory highlight plugin

* Inventory highlight plugin type

* Inventory highlight config options

* Fix ess pouch swapping

* Runecraftig config options

* Add plugin type to persistient loot tracker

* Fix panel repaint

* Disable this for now

* Revert "Fix ess pouch swapping" -> PR #351

This reverts commit c18f65069f5df3a1cafb983af5b3e162817a22fe.

* I blame intellij for this

* Fix test
This commit is contained in:
sdburns1998
2019-05-21 21:56:07 +02:00
committed by Kyleeld
parent e9e0c51bd8
commit c18f13e2ab
58 changed files with 5334 additions and 243 deletions

View File

@@ -187,6 +187,9 @@ public class ItemManager
private final Color outlineColor;
}
// Used when getting High Alchemy value - multiplied by general store price.
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private final Client client;
private final ScheduledExecutorService scheduledExecutorService;
private final ClientThread clientThread;
@@ -411,6 +414,27 @@ public class ItemManager
return price;
}
public int getAlchValue(int itemID)
{
if (itemID == ItemID.COINS_995)
{
return 1;
}
if (itemID == ItemID.PLATINUM_TOKEN)
{
return 1000;
}
float alchValue = getItemComposition(itemID).getPrice() * HIGH_ALCHEMY_CONSTANT;
/*
* If the alch value is below 1, do not round up, return 0 instead.
* For example bones have a base price of 1, multiplied by 0.6 will be 0.6,
* if rounded up, the alch price will be 1, when it should be 0.
*/
return alchValue < 1 ? 0 : Math.round(alchValue);
}
/**
* Look up an item's stats
*

View File

@@ -36,7 +36,6 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemComposition;
import net.runelite.api.ItemContainer;
import net.runelite.api.ItemID;
import static net.runelite.api.ItemID.COINS_995;
@@ -47,7 +46,6 @@ import net.runelite.client.game.ItemManager;
@Slf4j
class BankCalculation
{
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private static final ImmutableList<Varbits> TAB_VARBITS = ImmutableList.of(
Varbits.BANK_TAB_ONE_COUNT,
Varbits.BANK_TAB_TWO_COUNT,
@@ -144,8 +142,6 @@ class BankCalculation
continue;
}
final ItemComposition itemComposition = itemManager.getItemComposition(item.getId());
if (config.showGE())
{
itemIds.add(item.getId());
@@ -153,12 +149,11 @@ class BankCalculation
if (config.showHA())
{
int price = itemComposition.getPrice();
long alchValue = itemManager.getAlchValue(item.getId());
if (price > 0)
if (alchValue > 0)
{
haPrice += (long) Math.round(price * HIGH_ALCHEMY_CONSTANT) *
(long) quantity;
haPrice += alchValue * quantity;
}
}
}

View File

@@ -83,7 +83,6 @@ import org.apache.commons.text.WordUtils;
@Slf4j
public class ChatCommandsPlugin extends Plugin
{
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private static final Pattern KILLCOUNT_PATTERN = Pattern.compile("Your (.+) (?:kill|harvest) count is: <col=ff0000>(\\d+)</col>");
private static final Pattern RAIDS_PATTERN = Pattern.compile("Your completed (.+) count is: <col=ff0000>(\\d+)</col>");
private static final Pattern WINTERTODT_PATTERN = Pattern.compile("Your subdued Wintertodt count is: <col=ff0000>(\\d+)</col>");
@@ -686,7 +685,7 @@ public class ChatCommandsPlugin extends Plugin
}
int itemId = item.getId();
int itemPrice = item.getPrice();
int itemPrice = itemManager.getItemPrice(itemId);
final ChatMessageBuilder builder = new ChatMessageBuilder();
builder.append(ChatColorType.NORMAL);
@@ -704,7 +703,7 @@ public class ChatCommandsPlugin extends Plugin
ItemComposition itemComposition = itemManager.getItemComposition(itemId);
if (itemComposition != null)
{
int alchPrice = Math.round(itemComposition.getPrice() * HIGH_ALCHEMY_CONSTANT);
int alchPrice = itemManager.getAlchValue(itemId);
builder
.append(ChatColorType.NORMAL)
.append(" HA value ")

View File

@@ -71,7 +71,6 @@ import net.runelite.http.api.osbuddy.OSBGrandExchangeResult;
@Slf4j
public class ExaminePlugin extends Plugin
{
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private static final Pattern X_PATTERN = Pattern.compile("^\\d+ x ");
private final Deque<PendingExamine> pending = new ArrayDeque<>();
@@ -323,7 +322,7 @@ public class ExaminePlugin extends Plugin
quantity = Math.max(1, quantity);
int itemCompositionPrice = itemComposition.getPrice();
final int gePrice = itemManager.getItemPrice(id);
final int alchPrice = itemCompositionPrice <= 0 ? 0 : Math.round(itemCompositionPrice * HIGH_ALCHEMY_CONSTANT);
final int alchPrice = itemCompositionPrice <= 0 ? 0 : itemManager.getAlchValue(id);
if (gePrice > 0 || alchPrice > 0)
{

View File

@@ -35,6 +35,7 @@ class GLUtil
private static final int ERR_LEN = 1024;
private static final int[] buf = new int[1];
private static final float[] fbuf = new float[1];
static int glGetInteger(GL4 gl, int pname)
{
@@ -42,6 +43,12 @@ class GLUtil
return buf[0];
}
static float glGetFloat(GL4 gl, int pname)
{
gl.glGetFloatv(pname, fbuf, 0);
return fbuf[0];
}
static int glGetShader(GL4 gl, int shader, int pname)
{
gl.glGetShaderiv(shader, pname, buf, 0);
@@ -201,4 +208,4 @@ class GLUtil
Scanner scanner = new Scanner(in).useDelimiter("\\A");
return scanner.next();
}
}
}

View File

@@ -74,18 +74,8 @@ import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginInstantiationException;
import net.runelite.client.plugins.PluginManager;
import static net.runelite.client.plugins.gpu.GLUtil.glDeleteBuffer;
import static net.runelite.client.plugins.gpu.GLUtil.glDeleteFrameBuffer;
import static net.runelite.client.plugins.gpu.GLUtil.glDeleteRenderbuffers;
import static net.runelite.client.plugins.gpu.GLUtil.glDeleteTexture;
import static net.runelite.client.plugins.gpu.GLUtil.glDeleteVertexArrays;
import static net.runelite.client.plugins.gpu.GLUtil.glGenBuffers;
import static net.runelite.client.plugins.gpu.GLUtil.glGenFrameBuffer;
import static net.runelite.client.plugins.gpu.GLUtil.glGenRenderbuffer;
import static net.runelite.client.plugins.gpu.GLUtil.glGenTexture;
import static net.runelite.client.plugins.gpu.GLUtil.glGenVertexArrays;
import static net.runelite.client.plugins.gpu.GLUtil.glGetInteger;
import static net.runelite.client.plugins.gpu.GLUtil.inputStreamToString;
import static net.runelite.client.plugins.gpu.GLUtil.*;
import net.runelite.client.plugins.gpu.config.AnisotropicFilteringMode;
import net.runelite.client.plugins.gpu.config.AntiAliasingMode;
import net.runelite.client.plugins.gpu.template.Template;
import net.runelite.client.ui.DrawManager;
@@ -102,7 +92,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
{
// This is the maximum number of triangles the compute shaders support
private static final int MAX_TRIANGLE = 4096;
private static final int SMALL_TRIANGLE_COUNT = 512;
static final int SMALL_TRIANGLE_COUNT = 512;
private static final int FLAG_SCENE_BUFFER = Integer.MIN_VALUE;
static final int MAX_DISTANCE = 90;
static final int MAX_FOG_DEPTH = 100;
@@ -215,6 +205,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
private int lastStretchedCanvasWidth;
private int lastStretchedCanvasHeight;
private AntiAliasingMode lastAntiAliasingMode;
private AnisotropicFilteringMode lastAnisotropicFilteringMode;
private int centerX;
private int centerY;
@@ -223,6 +214,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
private int uniUseFog;
private int uniFogColor;
private int uniFogDepth;
private int uniFogCornerRadius;
private int uniFogDensity;
private int uniDrawDistance;
private int uniProjectionMatrix;
private int uniBrightness;
@@ -499,6 +492,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
uniUseFog = gl.glGetUniformLocation(glProgram, "useFog");
uniFogColor = gl.glGetUniformLocation(glProgram, "fogColor");
uniFogDepth = gl.glGetUniformLocation(glProgram, "fogDepth");
uniFogCornerRadius = gl.glGetUniformLocation(glProgram, "fogCornerRadius");
uniFogDensity = gl.glGetUniformLocation(glProgram, "fogDensity");
uniDrawDistance = gl.glGetUniformLocation(glProgram, "drawDistance");
uniTex = gl.glGetUniformLocation(glUiProgram, "tex");
@@ -524,8 +519,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
gl.glDeleteProgram(glProgram);
glProgram = -1;
///
gl.glDeleteShader(glComputeShader);
glComputeShader = -1;
@@ -544,8 +537,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
gl.glDeleteProgram(glUnorderedComputeProgram);
glUnorderedComputeProgram = -1;
///
gl.glDeleteShader(glUiVertexShader);
glUiVertexShader = -1;
@@ -635,7 +626,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
}
uniformBuffer.flip();
gl.glBufferData(gl.GL_UNIFORM_BUFFER, uniformBuffer.limit() * Integer.BYTES, uniformBuffer, gl.GL_STATIC_DRAW);
gl.glBufferData(gl.GL_UNIFORM_BUFFER, uniformBuffer.limit() * Integer.BYTES, uniformBuffer, gl.GL_DYNAMIC_DRAW);
gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, 0);
}
@@ -985,6 +976,40 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
int renderViewportHeight = viewportHeight;
int renderViewportWidth = viewportWidth;
// Setup anisotropic filtering
final AnisotropicFilteringMode anisotropicFilteringMode = config.anisotropicFilteringMode();
final boolean afEnabled = anisotropicFilteringMode != anisotropicFilteringMode.DISABLED;
if (lastAnisotropicFilteringMode != anisotropicFilteringMode)
{
if (afEnabled)
{
switch (anisotropicFilteringMode)
{
case BILINEAR:
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR_MIPMAP_NEAREST);
break;
case TRILINEAR:
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR_MIPMAP_LINEAR);
break;
default:
final float maxSamples = glGetFloat(gl, gl.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT);
final float samples = Math.min(anisotropicFilteringMode.getSamples(), maxSamples);
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR_MIPMAP_LINEAR);
gl.glTexParameterf(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MAX_ANISOTROPY_EXT, samples);
break;
}
gl.glGenerateMipmap(gl.GL_TEXTURE_2D_ARRAY);
}
else
{
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST);
}
}
lastAnisotropicFilteringMode = anisotropicFilteringMode;
if (client.isStretchedEnabled())
{
Dimension dim = client.getStretchedDimensions();
@@ -1011,9 +1036,12 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
final int drawDistance = Math.max(0, Math.min(MAX_DISTANCE, config.drawDistance()));
final int fogDepth = config.fogDepth();
float effectiveDrawDistance = Perspective.LOCAL_TILE_SIZE * Math.min(Constants.SCENE_SIZE / 2, drawDistance);
gl.glUniform1i(uniUseFog, fogDepth > 0 ? 1 : 0);
gl.glUniform4f(uniFogColor, (sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f);
gl.glUniform1i(uniFogDepth, fogDepth);
gl.glUniform1f(uniFogDepth, config.fogDepth() * 0.01f * effectiveDrawDistance);
gl.glUniform1f(uniFogCornerRadius, config.fogCircularity() * 0.01f * effectiveDrawDistance);
gl.glUniform1f(uniFogDensity, config.fogDensity() * 0.1f);
gl.glUniform1i(uniDrawDistance, drawDistance * Perspective.LOCAL_TILE_SIZE);
// Brightness happens to also be stored in the texture provider, so we use that
@@ -1306,7 +1334,10 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
{
int var21 = (pitchCos * modelHeight >> 16) + var19;
int var22 = (var18 - var21) * zoom;
return var22 / var14 < Rasterizer3D_clipMidY2;
if (var22 / var14 < Rasterizer3D_clipMidY2)
{
return true;
}
}
}
}
@@ -1348,6 +1379,35 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
int tc = Math.min(MAX_TRIANGLE, model.getTrianglesCount());
int uvOffset = model.getUvBufferOffset();
boolean hasUv = model.getFaceTextures() != null;
// Speed hack: the scene uploader splits up large models with no priorities
// based on face height, and then we sort each smaller set of faces
if (tc > SMALL_TRIANGLE_COUNT && model.getFaceRenderPriorities() == null)
{
int left = tc;
int off = 0;
while (left > 0)
{
tc = Math.min(SMALL_TRIANGLE_COUNT, left);
GpuIntBuffer b = bufferForTriangles(tc);
b.ensureCapacity(8);
IntBuffer buffer = b.getBuffer();
buffer.put(model.getBufferOffset() + off);
buffer.put(hasUv ? uvOffset + off : -1);
buffer.put(tc);
buffer.put(targetBufferOffset);
buffer.put(FLAG_SCENE_BUFFER | (model.getRadius() << 12) | orientation);
buffer.put(x + client.getCameraX2()).put(y + client.getCameraY2()).put(z + client.getCameraZ2());
targetBufferOffset += tc * 3;
off += tc * 3;
left -= tc;
}
return;
}
GpuIntBuffer b = bufferForTriangles(tc);
@@ -1422,7 +1482,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
*/
private GpuIntBuffer bufferForTriangles(int triangles)
{
if (triangles < SMALL_TRIANGLE_COUNT)
if (triangles <= SMALL_TRIANGLE_COUNT)
{
++smallModels;
return modelBufferSmall;

View File

@@ -28,21 +28,36 @@ import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Range;
import net.runelite.client.config.Stub;
import static net.runelite.client.plugins.gpu.GpuPlugin.MAX_DISTANCE;
import static net.runelite.client.plugins.gpu.GpuPlugin.MAX_FOG_DEPTH;
import net.runelite.client.plugins.gpu.config.AnisotropicFilteringMode;
import net.runelite.client.plugins.gpu.config.AntiAliasingMode;
@ConfigGroup("gpu")
public interface GpuPluginConfig extends Config
{
@ConfigItem(
keyName = "drawingStub",
name = "Drawing",
description = "",
position = 1
)
default Stub drawingStub()
{
return new Stub();
}
@Range(
min = 20,
max = MAX_DISTANCE
)
@ConfigItem(
keyName = "drawDistance",
name = "Draw Distance",
description = "Draw distance",
position = 1
position = 2,
parent = "drawingStub"
)
default int drawDistance()
{
@@ -53,35 +68,102 @@ public interface GpuPluginConfig extends Config
keyName = "smoothBanding",
name = "Remove Color Banding",
description = "Smooths out the color banding that is present in the CPU renderer",
position = 2
position = 3,
parent = "drawingStub"
)
default boolean smoothBanding()
{
return false;
}
@ConfigItem(
keyName = "ppStub",
name = "Post processing",
description = "",
position = 4
)
default Stub ppStub()
{
return new Stub();
}
@ConfigItem(
keyName = "antiAliasingMode",
name = "Anti Aliasing",
description = "Configures the anti-aliasing mode",
position = 3
position = 5,
parent = "ppStub"
)
default AntiAliasingMode antiAliasingMode()
{
return AntiAliasingMode.DISABLED;
}
@ConfigItem(
keyName = "anisotropicFilteringMode",
name = "Anisotropic Filtering",
description = "Configures the anisotropic filtering mode",
position = 6,
parent = "ppStub"
)
default AnisotropicFilteringMode anisotropicFilteringMode()
{
return AnisotropicFilteringMode.DISABLED;
}
@ConfigItem(
keyName = "fogStub",
name = "Fog",
description = "",
position = 7
)
default Stub fogStub()
{
return new Stub();
}
@Range(
max = MAX_FOG_DEPTH
)
@ConfigItem(
keyName = "fogDepth",
name = "Fog depth",
name = "Depth",
description = "Distance from the scene edge the fog starts",
position = 4
position = 8,
parent = "fogStub"
)
default int fogDepth()
{
return 0;
return 30;
}
}
@Range(
max = MAX_FOG_DEPTH
)
@ConfigItem(
keyName = "fogCircularity",
name = "Roundness",
description = "Fog circularity in %",
position = 9,
parent = "fogStub"
)
default int fogCircularity()
{
return 30;
}
@Range(
max = MAX_FOG_DEPTH
)
@ConfigItem(
keyName = "fogDensity",
name = "Density",
description = "Relative fog thickness",
position = 10,
parent = "fogStub"
)
default int fogDensity()
{
return 10;
}
}

View File

@@ -24,6 +24,7 @@
*/
package net.runelite.client.plugins.gpu;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
@@ -40,6 +41,7 @@ import net.runelite.api.SceneTileModel;
import net.runelite.api.SceneTilePaint;
import net.runelite.api.Tile;
import net.runelite.api.WallObject;
import static net.runelite.client.plugins.gpu.GpuPlugin.SMALL_TRIANGLE_COUNT;
@Singleton
class SceneUploader
@@ -90,7 +92,7 @@ class SceneUploader
}
}
private void reset(Tile tile)
private static void reset(Tile tile)
{
Tile bridge = tile.getBridge();
if (bridge != null)
@@ -337,7 +339,7 @@ class SceneUploader
return 6;
}
private int upload(SceneTileModel sceneTileModel, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
private static int upload(SceneTileModel sceneTileModel, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
{
final int[] faceX = sceneTileModel.getFaceX();
final int[] faceY = sceneTileModel.getFaceY();
@@ -413,6 +415,21 @@ class SceneUploader
return cnt;
}
private static int faceHeight(Model model, int face)
{
final int[] vertexY = model.getVerticesY();
final int[] trianglesX = model.getTrianglesX();
final int[] trianglesY = model.getTrianglesY();
final int[] trianglesZ = model.getTrianglesZ();
int triangleA = trianglesX[face];
int triangleB = trianglesY[face];
int triangleC = trianglesZ[face];
return (vertexY[triangleA] + vertexY[triangleB] + vertexY[triangleC]) / 3;
}
private void uploadModel(Model model, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer)
{
if (model.getBufferOffset() > 0)
@@ -436,9 +453,32 @@ class SceneUploader
final int triangleCount = model.getTrianglesCount();
int len = 0;
for (int i = 0; i < triangleCount; ++i)
if (triangleCount > SMALL_TRIANGLE_COUNT && model.getFaceRenderPriorities() == null)
{
len += pushFace(model, i, vertexBuffer, uvBuffer);
Integer[] faces = new Integer[triangleCount];
for (int i = 0; i < triangleCount; ++i)
{
faces[i] = i;
}
Arrays.sort(faces, (i1, i2) ->
{
int z1 = faceHeight(model, i1);
int z2 = faceHeight(model, i2);
return Integer.compare(z2, z1);
});
for (int i = 0; i < triangleCount; ++i)
{
len += pushFace(model, faces[i], vertexBuffer, uvBuffer);
}
}
else
{
for (int i = 0; i < triangleCount; ++i)
{
len += pushFace(model, i, vertexBuffer, uvBuffer);
}
}
offset += len;
@@ -546,4 +586,4 @@ class SceneUploader
return 3;
}
}
}

View File

@@ -85,7 +85,7 @@ class TextureManager
* @param textureProvider
* @return
*/
private boolean allTexturesLoaded(TextureProvider textureProvider)
private static boolean allTexturesLoaded(TextureProvider textureProvider)
{
Texture[] textures = textureProvider.getTextures();
if (textures == null || textures.length == 0)
@@ -109,7 +109,7 @@ class TextureManager
return true;
}
private void updateTextures(TextureProvider textureProvider, GL4 gl, int textureArrayId)
private static void updateTextures(TextureProvider textureProvider, GL4 gl, int textureArrayId)
{
Texture[] textures = textureProvider.getTextures();
@@ -232,4 +232,4 @@ class TextureManager
texture.setU(u);
texture.setV(v);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2018, pacf531 <https://github.com/pacf531>
* 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.gpu.config;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum AnisotropicFilteringMode
{
DISABLED("Disabled", 0f),
BILINEAR("Bilinear", 0.5f),
TRILINEAR("Trilinear", 1f),
AF_2("x2", 2f),
AF_4("x4", 4f),
AF_8("x8", 8f),
AF_16("x16", 16f);
private final String name;
private final float samples;
@Override
public String toString()
{
return name;
}
}

View File

@@ -52,7 +52,7 @@ class GrandExchangeItemPanel extends JPanel
{
private static final Dimension ICON_SIZE = new Dimension(32, 32);
GrandExchangeItemPanel(AsyncBufferedImage icon, String name, int itemID, int gePrice, Double
GrandExchangeItemPanel(AsyncBufferedImage icon, String name, int itemID, int gePrice, int
haPrice, int geItemLimit)
{
BorderLayout layout = new BorderLayout();
@@ -139,7 +139,7 @@ class GrandExchangeItemPanel extends JPanel
// Alch price
JLabel haPriceLabel = new JLabel();
haPriceLabel.setText(StackFormatter.formatNumber(haPrice.intValue()) + " alch");
haPriceLabel.setText(StackFormatter.formatNumber(haPrice) + " alch");
haPriceLabel.setForeground(ColorScheme.GRAND_EXCHANGE_ALCH);
alchAndLimitPanel.add(haPriceLabel, BorderLayout.WEST);

View File

@@ -34,6 +34,6 @@ class GrandExchangeItems
private final String name;
private final int itemId;
private final int gePrice;
private final double haPrice;
private final int haPrice;
private final int geItemLimit;
}

View File

@@ -210,7 +210,7 @@ class GrandExchangeSearchPanel extends JPanel
int itemLimit = itemGELimits.getOrDefault(itemId, 0);
AsyncBufferedImage itemImage = itemManager.getImage(itemId);
itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemComp.getPrice() * 0.6, itemLimit));
itemsList.add(new GrandExchangeItems(itemImage, item.getName(), itemId, itemPrice, itemManager.getAlchValue(itemId), itemLimit));
// If using hotkey to lookup item, stop after finding match.
if (exactMatch && item.getName().equalsIgnoreCase(lookup))

View File

@@ -0,0 +1,53 @@
package net.runelite.client.plugins.inventoryhighlight;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Stub;
@ConfigGroup("inventoryHighlight")
public interface InventoryHighlightConfig extends Config
{
@ConfigItem(
keyName = "showItem",
name = "Show the item",
description = "Show a preview of the item in the new slot"
)
default boolean showItem()
{
return true;
}
@ConfigItem(
keyName = "gridStub",
name = "Grid",
description = "",
position = 1
)
default Stub gridStub()
{
return new Stub();
}
@ConfigItem(
keyName = "showGrid",
name = "Show a grid",
description = "Show a grid on the inventory while dragging",
parent = "gridStub"
)
default boolean showGrid()
{
return false;
}
@ConfigItem(
keyName = "showHighlight",
name = "Show background highlight",
description = "Show a green background highlight in the new slot",
parent = "gridStub"
)
default boolean showHighlight()
{
return false;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
* 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.inventoryhighlight;
import com.google.inject.Inject;
import java.awt.event.MouseEvent;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.Point;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.input.MouseListener;
public class InventoryHighlightInputListener implements MouseListener
{
private final InventoryHighlightPlugin plugin;
private final Client client;
@Inject
public InventoryHighlightInputListener(InventoryHighlightPlugin plugin, Client client)
{
this.plugin = plugin;
this.client = client;
}
@Override
public MouseEvent mouseDragged(MouseEvent mouseEvent)
{
plugin.setDragging(true);
return mouseEvent;
}
@Override
public MouseEvent mouseMoved(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseClicked(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mousePressed(MouseEvent mouseEvent)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return mouseEvent;
}
final Widget inventoryWidget = client.getWidget(WidgetInfo.INVENTORY);
final Widget bankInventoryWidget = client.getWidget(WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER);
if ((inventoryWidget == null || inventoryWidget.isSelfHidden()) &&
(inventoryWidget == null || bankInventoryWidget == null || bankInventoryWidget.isSelfHidden()))
{
return mouseEvent;
}
final Point mouse = client.getMouseCanvasPosition();
for (WidgetItem item : inventoryWidget.getWidgetItems())
{
if (item.getCanvasBounds().contains(mouse.getX(), mouse.getY()))
{
plugin.setDraggingItem(item.getId());
break;
}
}
return mouseEvent;
}
@Override
public MouseEvent mouseReleased(MouseEvent mouseEvent)
{
plugin.setDragging(false);
plugin.setDraggingItem(-1);
return mouseEvent;
}
@Override
public MouseEvent mouseEntered(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseExited(MouseEvent mouseEvent)
{
return mouseEvent;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
* 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.inventoryhighlight;
import com.google.inject.Inject;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import net.runelite.api.Client;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
public class InventoryHighlightOverlay extends Overlay
{
private final InventoryHighlightPlugin plugin;
private final InventoryHighlightConfig config;
private final Client client;
private final ItemManager itemManager;
// the inventory widget location is slightly off
private static final int FIXED_MARGIN_X = -10;
private static final int FIXED_MARGIN_Y = -25;
private static final int RESIZEABLE_MARGIN_X = -6;
private static final int RESIZEABLE_MARGIN_Y = -21;
private static final int ITEM_WIDTH = 32;
private static final int ITEM_HEIGHT = 32;
private static final int ITEM_MARGIN_X = 10;
private static final int ITEM_MARGIN_Y = 4;
private static final Color HIGHLIGHT = new Color(0, 255, 0, 45);
private static final Color GRID = new Color(255, 255, 255, 45);
@Inject
public InventoryHighlightOverlay(InventoryHighlightPlugin plugin, InventoryHighlightConfig config, Client client, ItemManager itemManager)
{
this.plugin = plugin;
this.itemManager = itemManager;
this.client = client;
this.config = config;
setLayer(OverlayLayer.ABOVE_WIDGETS);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (plugin.getDraggingItem() == -1 || !plugin.isDragging())
{
return null;
}
int marginX, marginY;
if (client.isResized())
{
marginX = RESIZEABLE_MARGIN_X;
marginY = RESIZEABLE_MARGIN_Y;
}
else
{
marginX = FIXED_MARGIN_X;
marginY = FIXED_MARGIN_Y;
}
final net.runelite.api.Point mouse = client.getMouseCanvasPosition();
final Point updatedMouse = new Point(mouse.getX() + marginX, mouse.getY() + marginY);
// null checks for inventory are checked during dragging events
final Widget inventoryWidget = client.getWidget(WidgetInfo.INVENTORY);
final int inventoryX = inventoryWidget.getCanvasLocation().getX() + marginX;
final int inventoryY = inventoryWidget.getCanvasLocation().getY() + marginY;
final BufferedImage draggedItemImage = itemManager.getImage(plugin.getDraggingItem());
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 7; j++)
{
final int x = (ITEM_WIDTH + ITEM_MARGIN_X) * i + inventoryX;
final int y = (ITEM_HEIGHT + ITEM_MARGIN_Y) * j + inventoryY;
final Rectangle bounds = new Rectangle(x, y, ITEM_WIDTH, ITEM_HEIGHT);
if (config.showItem() && bounds.contains(updatedMouse))
{
graphics.setComposite(AlphaComposite.SrcOver.derive(0.3f));
graphics.drawImage(draggedItemImage, x, y, null);
graphics.setComposite(AlphaComposite.SrcOver);
}
if (config.showHighlight() && bounds.contains(updatedMouse))
{
graphics.setColor(HIGHLIGHT);
graphics.fill(bounds);
}
if (config.showGrid())
{
// don't set color on highlighted slot
if (!config.showHighlight() || !(config.showHighlight() && bounds.contains(updatedMouse)))
{
graphics.setColor(GRID);
graphics.fill(bounds);
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2018, Jeremy Plsek <https://github.com/jplsek>
* 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.inventoryhighlight;
import com.google.inject.Inject;
import com.google.inject.Provides;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.events.FocusChanged;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.input.MouseManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginType;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Inventory Highlight",
description = "Shows a preview of where items will be dragged",
tags = {"items", "overlay"},
enabledByDefault = false,
type = PluginType.UTILITY
)
public class InventoryHighlightPlugin extends Plugin
{
@Inject
private InventoryHighlightOverlay overlay;
@Inject
private InventoryHighlightInputListener inputListener;
@Inject
private MouseManager mouseManager;
@Inject
private OverlayManager overlayManager;
@Getter(AccessLevel.PACKAGE)
@Setter(AccessLevel.PACKAGE)
private int draggingItem = -1;
@Getter(AccessLevel.PACKAGE)
@Setter(AccessLevel.PACKAGE)
private boolean dragging = false;
@Override
public void startUp()
{
overlayManager.add(overlay);
mouseManager.registerMouseListener(inputListener);
}
@Override
public void shutDown()
{
mouseManager.unregisterMouseListener(inputListener);
overlayManager.remove(overlay);
}
@Provides
InventoryHighlightConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(InventoryHighlightConfig.class);
}
@Subscribe
public void onFocusChanged(FocusChanged focusChanged)
{
if (!focusChanged.isFocused())
{
dragging = false;
draggingItem = -1;
}
}
}

View File

@@ -48,9 +48,6 @@ import net.runelite.client.util.StackFormatter;
class ItemPricesOverlay extends Overlay
{
// Used when getting High Alchemy value - multiplied by general store price.
private static final float HIGH_ALCHEMY_CONSTANT = 0.6f;
private static final int INVENTORY_ITEM_WIDGETID = WidgetInfo.INVENTORY.getPackedId();
private static final int BANK_INVENTORY_ITEM_WIDGETID = WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER.getPackedId();
private static final int BANK_ITEM_WIDGETID = WidgetInfo.BANK_ITEM_CONTAINER.getPackedId();
@@ -204,7 +201,7 @@ class ItemPricesOverlay extends Overlay
}
if (config.showHAValue())
{
haPrice = Math.round(itemDef.getPrice() * HIGH_ALCHEMY_CONSTANT);
haPrice = itemManager.getAlchValue(id);
}
if (gePrice > 0 && haPrice > 0 && config.showAlchProfit())
{

View File

@@ -28,171 +28,42 @@ package net.runelite.client.plugins.runecraft;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Stub;
@ConfigGroup("runecraft")
public interface RunecraftConfig extends Config
{
@ConfigItem(
keyName = "showRifts",
name = "Show Rifts in Abyss",
description = "Configures whether the rifts in the abyss will be displayed",
keyName = "utilStub",
name = "Utility",
description = "",
position = 1
)
default Stub utilStub()
{
return new Stub();
}
@ConfigItem(
keyName = "Lavas",
name = "Lavas",
description = "Swaps Ring of dueling menu entry depending on location, requires fire tiara or RC cape to be worn.",
parent = "utilStub",
position = 2
)
default boolean Lavas()
{
return true;
}
@ConfigItem(
keyName = "essPouch",
name = "Swap essence pouch",
description = "Makes essence pouch left-click fill in bank",
parent = "utilStub",
position = 3
)
default boolean showRifts()
{
return true;
}
@ConfigItem(
keyName = "showAir",
name = "Show Air rift",
description = "Configures whether to display the air rift",
position = 4
)
default boolean showAir()
{
return true;
}
@ConfigItem(
keyName = "showBlood",
name = "Show Blood rift",
description = "Configures whether to display the Blood rift",
position = 5
)
default boolean showBlood()
{
return true;
}
@ConfigItem(
keyName = "showBody",
name = "Show Body rift",
description = "Configures whether to display the Body rift",
position = 6
)
default boolean showBody()
{
return true;
}
@ConfigItem(
keyName = "showChaos",
name = "Show Chaos rift",
description = "Configures whether to display the Chaos rift",
position = 7
)
default boolean showChaos()
{
return true;
}
@ConfigItem(
keyName = "showCosmic",
name = "Show Cosmic rift",
description = "Configures whether to display the Cosmic rift",
position = 8
)
default boolean showCosmic()
{
return true;
}
@ConfigItem(
keyName = "showDeath",
name = "Show Death rift",
description = "Configures whether to display the Death rift",
position = 9
)
default boolean showDeath()
{
return true;
}
@ConfigItem(
keyName = "showEarth",
name = "Show Earth rift",
description = "Configures whether to display the Earth rift",
position = 10
)
default boolean showEarth()
{
return true;
}
@ConfigItem(
keyName = "showFire",
name = "Show Fire rift",
description = "Configures whether to display the Fire rift",
position = 11
)
default boolean showFire()
{
return true;
}
@ConfigItem(
keyName = "showLaw",
name = "Show Law rift",
description = "Configures whether to display the Law rift",
position = 12
)
default boolean showLaw()
{
return true;
}
@ConfigItem(
keyName = "showMind",
name = "Show Mind rift",
description = "Configures whether to display the Mind rift",
position = 13
)
default boolean showMind()
{
return true;
}
@ConfigItem(
keyName = "showNature",
name = "Show Nature rift",
description = "Configures whether to display the Nature rift",
position = 14
)
default boolean showNature()
{
return true;
}
@ConfigItem(
keyName = "showSoul",
name = "Show Soul rift",
description = "Configures whether to display the Soul rift",
position = 15
)
default boolean showSoul()
{
return true;
}
@ConfigItem(
keyName = "showWater",
name = "Show Water rift",
description = "Configures whether to display the Water rift",
position = 16
)
default boolean showWater()
{
return true;
}
@ConfigItem(
keyName = "showClickBox",
name = "Show Rift click box",
description = "Configures whether to display the click box of the rift",
position = 17
)
default boolean showClickBox()
default boolean essPouch()
{
return true;
}
@@ -201,7 +72,8 @@ public interface RunecraftConfig extends Config
keyName = "hightlightDarkMage",
name = "Highlight Dark Mage NPC",
description = "Configures whether to highlight the Dark Mage when pouches are degraded",
position = 18
position = 4,
parent = "utilStub"
)
default boolean hightlightDarkMage()
{
@@ -212,29 +84,230 @@ public interface RunecraftConfig extends Config
keyName = "degradingNotification",
name = "Notify when pouch degrades",
description = "Send a notification when a pouch degrades",
position = 19
position = 5,
parent = "utilStub"
)
default boolean degradingNotification()
{
return true;
}
@ConfigItem(
keyName = "Lavas",
name = "Lavas",
description = "Swaps Ring of dueling menu entry depending on location, requires fire tiara or RC cape to be worn."
keyName = "riftsStub",
name = "Rifts",
description = "",
position = 6
)
default boolean Lavas()
default Stub riftsStub()
{
return new Stub();
}
@ConfigItem(
keyName = "showRifts",
name = "Show Rifts in Abyss",
description = "Configures whether the rifts in the abyss will be displayed",
position = 7,
parent = "riftsStub"
)
default boolean showRifts()
{
return true;
}
@ConfigItem(
keyName = "essPouch",
name = "Swap essence pouch",
description = "Makes essence pouch left-click fill in bank"
keyName = "showAir",
name = "Show Air rift",
description = "Configures whether to display the air rift",
position = 8,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean essPouch()
default boolean showAir()
{
return true;
}
@ConfigItem(
keyName = "showBlood",
name = "Show Blood rift",
description = "Configures whether to display the Blood rift",
position = 9,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showBlood()
{
return true;
}
@ConfigItem(
keyName = "showBody",
name = "Show Body rift",
description = "Configures whether to display the Body rift",
position = 10,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showBody()
{
return true;
}
@ConfigItem(
keyName = "showChaos",
name = "Show Chaos rift",
description = "Configures whether to display the Chaos rift",
position = 11,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showChaos()
{
return true;
}
@ConfigItem(
keyName = "showCosmic",
name = "Show Cosmic rift",
description = "Configures whether to display the Cosmic rift",
position = 12,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showCosmic()
{
return true;
}
@ConfigItem(
keyName = "showDeath",
name = "Show Death rift",
description = "Configures whether to display the Death rift",
position = 13,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showDeath()
{
return true;
}
@ConfigItem(
keyName = "showEarth",
name = "Show Earth rift",
description = "Configures whether to display the Earth rift",
position = 14,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showEarth()
{
return true;
}
@ConfigItem(
keyName = "showFire",
name = "Show Fire rift",
description = "Configures whether to display the Fire rift",
position = 15,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showFire()
{
return true;
}
@ConfigItem(
keyName = "showLaw",
name = "Show Law rift",
description = "Configures whether to display the Law rift",
position = 16,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showLaw()
{
return true;
}
@ConfigItem(
keyName = "showMind",
name = "Show Mind rift",
description = "Configures whether to display the Mind rift",
position = 17,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showMind()
{
return true;
}
@ConfigItem(
keyName = "showNature",
name = "Show Nature rift",
description = "Configures whether to display the Nature rift",
position = 18,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showNature()
{
return true;
}
@ConfigItem(
keyName = "showSoul",
name = "Show Soul rift",
description = "Configures whether to display the Soul rift",
position = 19,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showSoul()
{
return true;
}
@ConfigItem(
keyName = "showWater",
name = "Show Water rift",
description = "Configures whether to display the Water rift",
position = 20,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showWater()
{
return true;
}
@ConfigItem(
keyName = "showClickBox",
name = "Show Rift click box",
description = "Configures whether to display the click box of the rift",
position = 21,
parent = "riftsStub",
hidden = true,
unhide = "showRifts"
)
default boolean showClickBox()
{
return true;
}

View File

@@ -0,0 +1,23 @@
package net.runelite.client.plugins.stonedloottracker;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ItemSortTypes
{
ALPHABETICAL("Alphabetical"),
ITEM_ID("Item ID"),
VALUE("Value"),
PRICE("Price"),
HAPRICE("HA Price");
private final String name;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Stub;
@ConfigGroup("stonedloottracker")
public interface StonedLootTrackerConfig extends Config
{
@ConfigItem(
keyName = "mainStub",
name = "Main panel",
description = "",
position = 1
)
default Stub mainStub()
{
return new Stub();
}
@ConfigItem(
position = 2,
keyName = "bossButtons",
name = "Show boss icons",
description = "Toggles whether the selection screen will use the boss icons",
parent = "mainStub"
)
default boolean bossButtons()
{
return true;
}
@ConfigItem(
keyName = "itemsStub",
name = "Items",
description = "",
position = 3
)
default Stub itemsStub()
{
return new Stub();
}
// TODO Option is not working will look into it later
@ConfigItem(
position = 4,
keyName = "hideUniques",
name = "Hide uniques",
description = "Hides unique items from the item breakdown",
parent = "itemsStub",
hidden = true
)
default boolean hideUniques()
{
return true;
}
@ConfigItem(
position = 5,
keyName = "itemSortType",
name = "Sort Items by",
description = "Sorts items by the requested value inside the UI. (Doesn't effect session/box view)",
parent = "itemsStub"
)
default ItemSortTypes itemSortType()
{
return ItemSortTypes.ALPHABETICAL;
}
@ConfigItem(
position = 6,
keyName = "itemBreakdown",
name = "Breakdown individual items",
description = "Toggles whether the Individual item UI should be used inside npc-specific tabs",
parent = "itemsStub"
)
default boolean itemBreakdown()
{
return true;
}
}

View File

@@ -0,0 +1,703 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.stonedloottracker;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.InventoryID;
import net.runelite.api.ItemComposition;
import net.runelite.api.ItemContainer;
import net.runelite.api.NPC;
import net.runelite.api.NpcID;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.NpcLootReceived;
import net.runelite.client.events.PlayerLootReceived;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.ItemStack;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginType;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordCustom;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordWriter;
import net.runelite.client.plugins.stonedloottracker.data.LootTrackerItemEntry;
import net.runelite.client.plugins.stonedloottracker.data.Pet;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItem;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItemPrepared;
import net.runelite.client.plugins.stonedloottracker.data.events.LootTrackerNameChange;
import net.runelite.client.plugins.stonedloottracker.data.events.LootTrackerRecordStored;
import net.runelite.client.plugins.stonedloottracker.ui.LootTrackerPanel;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
@PluginDescriptor(
name = "Stoned Loot Tracker",
description = "Tracks loot from monsters and minigames",
tags = {"drops"},
enabledByDefault = false,
type = PluginType.UTILITY
)
@Slf4j
public class StonedLootTrackerPlugin extends Plugin
{
// Activity/Event loot handling
private static final Pattern CLUE_SCROLL_PATTERN = Pattern.compile("You have completed ([0-9]+) ([a-z]+) Treasure Trails.");
private static final Pattern BOSS_NAME_NUMBER_PATTERN = Pattern.compile("Your (.*) kill count is: ([0-9]*).");
private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)");
private static final ImmutableList<String> PET_MESSAGES = ImmutableList.of("You have a funny feeling like you're being followed",
"You feel something weird sneaking into your backpack",
"You have a funny feeling like you would have been followed");
private static final int THEATRE_OF_BLOOD_REGION = 12867;
// Herbiboar loot handling
private static final String HERBIBOAR_LOOTED_MESSAGE = "Your herbiboar harvest count is";
private static final String HERBIBOR_EVENT = "Herbiboar";
// Chest loot handling
private static final String CHEST_LOOTED_MESSAGE = "You find some treasure in the chest!";
private static final Map<Integer, String> CHEST_EVENT_TYPES = ImmutableMap.of(
5179, "Brimstone Chest",
11573, "Crystal Chest"
);
@Inject
private ClientToolbar clientToolbar;
@Inject
private ItemManager itemManager;
@Inject
private Client client;
@Inject
public StonedLootTrackerConfig config;
@Inject
private ClientThread clientThread;
@Inject
private LootRecordWriter writer;
private Multiset<Integer> inventorySnapshot;
@Provides
StonedLootTrackerConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(StonedLootTrackerConfig.class);
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("stonedloottracker"))
{
panel.refreshUI();
}
}
private LootTrackerPanel panel;
private NavigationButton navButton;
private String eventType;
private Multimap<String, LootRecordCustom> lootRecordMultimap = ArrayListMultimap.create();
private Multimap<String, UniqueItemPrepared> uniques = ArrayListMultimap.create();
private Map<String, Integer> killCountMap = new HashMap<>();
// key = name, value=current killCount
private boolean loaded = false;
private boolean gotPet = false;
@Override
protected void startUp() throws Exception
{
panel = new LootTrackerPanel(itemManager, this);
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "panel_icon.png");
navButton = NavigationButton.builder()
.tooltip("Stoned Loot Tracker")
.icon(icon)
.priority(5)
.panel(panel)
.build();
clientToolbar.addNavigation(navButton);
if (!loaded)
{
clientThread.invokeLater(() ->
{
switch (client.getGameState())
{
case UNKNOWN:
case STARTING:
return false;
}
prepareUniqueItems();
return true;
});
}
}
private void prepareUniqueItems()
{
loaded = true;
for (UniqueItem i : UniqueItem.values())
{
ItemComposition c = itemManager.getItemComposition(i.getItemID());
for (String s : i.getActivities())
{
uniques.put(s.toUpperCase(), new UniqueItemPrepared(c.getLinkedNoteId(), itemManager.getItemPrice(i.getItemID()), i));
}
}
}
public Collection<UniqueItemPrepared> getUniques(String name)
{
return uniques.get(name.toUpperCase());
}
@Override
protected void shutDown()
{
clientToolbar.removeNavigation(navButton);
}
@Subscribe
public void onLootTrackerRecordStored(LootTrackerRecordStored s)
{
SwingUtilities.invokeLater(() -> panel.addLog(s.getRecord()));
}
@Subscribe
public void onLootTrackerNameChange(LootTrackerNameChange c)
{
refreshData();
SwingUtilities.invokeLater(() -> panel.updateNames());
}
@Subscribe
public void onNpcLootReceived(final NpcLootReceived npcLootReceived)
{
final NPC npc = npcLootReceived.getNpc();
final Collection<ItemStack> items = npcLootReceived.getItems();
final String name = npc.getName().toLowerCase().contains("vet'ion reborn") ? "Vet'ion" : npc.getName();
final int combat = npc.getCombatLevel();
final int killCount = killCountMap.getOrDefault(name.toUpperCase(), -1);
final LootTrackerItemEntry[] entries = buildEntries(items);
if (gotPet)
{
ItemStack pet = handlePet(name);
if (pet == null)
{
log.warn("Error finding pet for npc name: {}", name);
}
else
{
items.add(pet);
}
}
LootRecordCustom rec = new LootRecordCustom(npc.getId(), name, combat, killCount, Arrays.asList(entries));
addLog(name, rec);
}
@Subscribe
public void onPlayerLootReceived(final PlayerLootReceived playerLootReceived)
{
final Player player = playerLootReceived.getPlayer();
final Collection<ItemStack> items = playerLootReceived.getItems();
final String name = player.getName();
final int combat = player.getCombatLevel();
final int killCount = killCountMap.getOrDefault(name.toUpperCase(), -1);
final LootTrackerItemEntry[] entries = buildEntries(items);
LootRecordCustom rec = new LootRecordCustom(-1, name, combat, killCount, Arrays.asList(entries));
addLog(name, rec);
}
@Subscribe
public void onWidgetLoaded(WidgetLoaded event)
{
final ItemContainer container;
switch (event.getGroupId())
{
case (WidgetID.BARROWS_REWARD_GROUP_ID):
eventType = "Barrows";
container = client.getItemContainer(InventoryID.BARROWS_REWARD);
break;
case (WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID):
eventType = "Chambers of Xeric";
container = client.getItemContainer(InventoryID.CHAMBERS_OF_XERIC_CHEST);
break;
case (WidgetID.THEATRE_OF_BLOOD_GROUP_ID):
int region = WorldPoint.fromLocalInstance(client, client.getLocalPlayer().getLocalLocation()).getRegionID();
if (region != THEATRE_OF_BLOOD_REGION)
{
return;
}
eventType = "Theatre of Blood";
container = client.getItemContainer(InventoryID.THEATRE_OF_BLOOD_CHEST);
break;
case (WidgetID.CLUE_SCROLL_REWARD_GROUP_ID):
// event type should be set via ChatMessage for clue scrolls.
// Clue Scrolls use same InventoryID as Barrows
container = client.getItemContainer(InventoryID.BARROWS_REWARD);
break;
// Unsired redemption tracking
case (WidgetID.DIALOG_SPRITE_GROUP_ID):
Widget text = client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT);
if ("you place the unsired into the font of consumption...".equals(text.getText().toLowerCase()))
{
checkUnsiredWidget();
}
return;
default:
return;
}
if (container == null)
{
return;
}
// Convert container items to array of ItemStack
final Collection<ItemStack> items = Arrays.stream(container.getItems())
.filter(item -> item.getId() > 0)
.map(item -> new ItemStack(item.getId(), item.getQuantity(), client.getLocalPlayer().getLocalLocation()))
.collect(Collectors.toList());
if (items.isEmpty())
{
log.debug("No items to find for Event: {} | Container: {}", eventType, container);
return;
}
final LootTrackerItemEntry[] entries = buildEntries(items);
final int killCount = killCountMap.getOrDefault(eventType.toUpperCase(), -1);
LootRecordCustom rec = new LootRecordCustom(-1, eventType, -1, killCount, Arrays.asList(entries));
addLog(eventType, rec);
}
private void addLog(String name, LootRecordCustom record)
{
lootRecordMultimap.put(name, record);
writer.addLootTrackerRecord(record);
}
@Subscribe
public void onChatMessage(ChatMessage event)
{
if (event.getType() != ChatMessageType.GAMEMESSAGE && event.getType() != ChatMessageType.SPAM)
{
return;
}
String chatMessage = Text.removeTags(event.getMessage());
// Check if message is for a clue scroll reward
final Matcher m = CLUE_SCROLL_PATTERN.matcher(chatMessage);
if (m.find())
{
final String type = m.group(2).toLowerCase();
switch (type)
{
case "beginner":
eventType = "Clue Scroll (Beginner)";
break;
case "easy":
eventType = "Clue Scroll (Easy)";
break;
case "medium":
eventType = "Clue Scroll (Medium)";
break;
case "hard":
eventType = "Clue Scroll (Hard)";
break;
case "elite":
eventType = "Clue Scroll (Elite)";
break;
case "master":
eventType = "Clue Scroll (Master)";
break;
}
int killCount = Integer.valueOf(m.group(1));
killCountMap.put(eventType.toUpperCase(), killCount);
return;
}
// TODO: Figure out better way to handle Barrows and Raids/Raids 2
// Barrows KC
if (chatMessage.startsWith("Your Barrows chest count is"))
{
Matcher n = NUMBER_PATTERN.matcher(chatMessage);
if (n.find())
{
killCountMap.put("BARROWS", Integer.valueOf(n.group()));
return;
}
}
// Raids KC
if (chatMessage.startsWith("Your completed Chambers of Xeric count is"))
{
Matcher n = NUMBER_PATTERN.matcher(chatMessage);
if (n.find())
{
killCountMap.put("RAIDS", Integer.valueOf(n.group()));
return;
}
}
// Raids KC
if (chatMessage.startsWith("Your completed Theatre of Blood count is"))
{
Matcher n = NUMBER_PATTERN.matcher(chatMessage);
if (n.find())
{
killCountMap.put("THEATRE OF BLOOD", Integer.valueOf(n.group()));
return;
}
}
// Handle all other boss
Matcher boss = BOSS_NAME_NUMBER_PATTERN.matcher(chatMessage);
if (boss.find())
{
String bossName = boss.group(1);
int killCount = Integer.valueOf(boss.group(2));
killCountMap.put(bossName.toUpperCase(), killCount);
}
if (PET_MESSAGES.stream().anyMatch(chatMessage::contains))
{
gotPet = true;
}
if (chatMessage.equals(CHEST_LOOTED_MESSAGE))
{
final int regionID = client.getLocalPlayer().getWorldLocation().getRegionID();
if (!CHEST_EVENT_TYPES.containsKey(regionID))
{
return;
}
eventType = CHEST_EVENT_TYPES.get(regionID);
takeInventorySnapshot();
Matcher n = NUMBER_PATTERN.matcher(chatMessage);
if (n.find())
{
killCountMap.put(eventType, Integer.valueOf(n.group()));
}
}
if (chatMessage.startsWith(HERBIBOAR_LOOTED_MESSAGE))
{
eventType = HERBIBOR_EVENT;
takeInventorySnapshot();
Matcher n = NUMBER_PATTERN.matcher(chatMessage);
if (n.find())
{
killCountMap.put(HERBIBOR_EVENT, Integer.valueOf(n.group()));
}
}
}
@Subscribe
public void onItemContainerChanged(ItemContainerChanged event)
{
if (eventType != null && (CHEST_EVENT_TYPES.containsValue(eventType) || HERBIBOR_EVENT.equals(eventType)))
{
if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY))
{
return;
}
processInventoryLoot(eventType, event.getItemContainer());
eventType = null;
}
}
private void takeInventorySnapshot()
{
final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY);
if (itemContainer != null)
{
inventorySnapshot = HashMultiset.create();
Arrays.stream(itemContainer.getItems())
.forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity()));
}
}
private void processInventoryLoot(String eventType, ItemContainer inventoryContainer)
{
if (inventorySnapshot != null)
{
Multiset<Integer> currentInventory = HashMultiset.create();
Arrays.stream(inventoryContainer.getItems())
.forEach(item -> currentInventory.add(item.getId(), item.getQuantity()));
final Multiset<Integer> diff = Multisets.difference(currentInventory, inventorySnapshot);
List<ItemStack> items = diff.entrySet().stream()
.map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation()))
.collect(Collectors.toList());
final LootTrackerItemEntry[] entries = buildEntries(items);
LootRecordCustom rec = new LootRecordCustom(-1, eventType, -1, killCountMap.getOrDefault(eventType, -1), Arrays.asList(entries));
addLog(eventType, rec);
inventorySnapshot = null;
}
}
private LootTrackerItemEntry[] buildEntries(final Collection<ItemStack> itemStacks)
{
return itemStacks.stream().map(itemStack ->
{
final ItemComposition itemComposition = itemManager.getItemComposition(itemStack.getId());
final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemStack.getId();
final long price = itemManager.getItemPrice(realItemId);
final long haPrice = itemManager.getAlchValue(realItemId);
return new LootTrackerItemEntry(
itemComposition.getName(),
itemStack.getId(),
itemStack.getQuantity(),
price,
haPrice,
itemComposition.isStackable());
}).toArray(LootTrackerItemEntry[]::new);
}
public Collection<LootRecordCustom> getData()
{
return lootRecordMultimap.values();
}
public Collection<LootRecordCustom> getDataByName(String name)
{
return lootRecordMultimap.get(name);
}
private void refreshData()
{
// Pull data from files
lootRecordMultimap.clear();
Collection<LootRecordCustom> recs = writer.loadAllLootTrackerRecords();
for (LootRecordCustom r : recs)
{
lootRecordMultimap.put(r.getName(), r);
}
}
public void refreshDataByName(String name)
{
lootRecordMultimap.removeAll(name);
Collection<LootRecordCustom> recs = writer.loadLootTrackerRecords(name);
lootRecordMultimap.putAll(name, recs);
}
public void clearStoredDataByName(String name)
{
lootRecordMultimap.removeAll(name);
writer.deleteLootTrackerRecords(name);
}
public TreeSet<String> getNames()
{
return new TreeSet<>(lootRecordMultimap.keySet());
}
// Pet Handling
private ItemStack handlePet(String name)
{
gotPet = false;
int petID = getPetId(name);
if (petID == -1)
{
return null;
}
return new ItemStack(petID, 1, client.getLocalPlayer().getLocalLocation());
}
private int getPetId(String name)
{
Pet pet = Pet.getByBossName(name);
if (pet != null)
{
return pet.getPetID();
}
return -1;
}
// Handles adding the unsired loot to the tracker
private void receivedUnsiredLoot(int itemID)
{
clientThread.invokeLater(() ->
{
Collection<LootRecordCustom> data = getDataByName("Abyssal sire");
ItemComposition c = itemManager.getItemComposition(itemID);
LootTrackerItemEntry itemEntry = new LootTrackerItemEntry(c.getName(), itemID, 1, 0, 0, false);
log.debug("Received Unsired item: {}", c.getName());
// Don't have data for sire, create a new record with just this data.
if (data == null)
{
log.debug("No previous Abyssal sire loot, creating new loot record");
LootRecordCustom r = new LootRecordCustom(NpcID.ABYSSAL_SIRE, "Abyssal sire", 350, -1, Collections.singletonList(itemEntry));
writer.addLootTrackerRecord(r);
return;
}
log.debug("Adding drop to last abyssal sire loot record");
// Add data to last kill count
List<LootRecordCustom> items = new ArrayList<>(data);
LootRecordCustom r = items.get(items.size() - 1);
r.addDropEntry(itemEntry);
writer.writeLootTrackerFile("Abyssal sire", items);
});
}
private boolean unsiredThreadRunning = false;
private int unsiredThreadTries = 0;
// Handles checking for unsired loot reclamation
private void checkUnsiredWidget()
{
if (unsiredThreadRunning)
{
return;
}
unsiredThreadRunning = true;
unsiredThreadTries = 0;
clientThread.invokeLater(() ->
{
log.debug("Checking for text widget change...");
Widget text = client.getWidget(WidgetInfo.DIALOG_SPRITE_TEXT);
if ("the font consumes the unsired and returns you a reward.".equals(text.getText().toLowerCase()))
{
Widget sprite = client.getWidget(WidgetInfo.DIALOG_SPRITE);
log.debug("Sprite: {}", sprite);
log.debug("Sprite Item ID: {}", sprite.getItemId());
log.debug("Sprite Model ID: {}", sprite.getModelId());
receivedUnsiredLoot(sprite.getItemId());
unsiredThreadRunning = false;
return true;
}
else
{
if (unsiredThreadTries >= 10)
{
log.debug("Tried 10 times, canceling...");
return true;
}
unsiredThreadTries++;
return false;
}
});
}
@Subscribe
public void onGameStateChanged(GameStateChanged c)
{
if (c.getGameState().equals(GameState.LOGGED_IN))
{
clientThread.invokeLater(() ->
{
switch (client.getGameState())
{
case LOGGED_IN:
break;
case LOGGING_IN:
case LOADING:
return false;
default:
// Quit running if any other state
return true;
}
String name = client.getLocalPlayer().getName();
if (name != null)
{
writer.setPlayerUsername(name);
return true;
}
else
{
return false;
}
});
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import lombok.Getter;
import net.runelite.api.ItemID;
@Getter
public enum BossTab
{
// Chest Rewards
BARROWS("Barrows", ItemID.BARROWS_TELEPORT, "Other"),
RAIDS("Chambers of Xeric", ItemID.OLMLET, "Other"),
RAIDS_2("Theatre of Blood", ItemID.LIL_ZIK, "Other"),
// Loot received on NPC death
ZULRAH("Zulrah", ItemID.PET_SNAKELING, "Other"),
VORKATH("Vorkath", ItemID.VORKI, "Other"),
// God wars dungeon
ARMADYL("Kree'arra", ItemID.PET_KREEARRA , "God Wars Dungeon"),
BANDOS("General Graardor", ItemID.PET_GENERAL_GRAARDOR , "God Wars Dungeon"),
SARADOMIN("Commander Zilyana", ItemID.PET_ZILYANA , "God Wars Dungeon"),
ZAMMY("K'ril Tsutsaroth", ItemID.PET_KRIL_TSUTSAROTH , "God Wars Dungeon"),
// Wildy Bosses
VETION("Vet'ion", ItemID.VETION_JR , "Wilderness"),
VENENATIS("Venenatis", ItemID.VENENATIS_SPIDERLING , "Wilderness"),
CALLISTO("Callisto", ItemID.CALLISTO_CUB , "Wilderness"),
CHAOS_ELEMENTAL("Chaos Elemental", ItemID.PET_CHAOS_ELEMENTAL , "Wilderness"),
// Wildy Demi-Bosses
SCORPIA("Scorpia", ItemID.SCORPIAS_OFFSPRING, "Wilderness"),
CHAOS_FANATIC("Chaos Fanatic", ItemID.ANCIENT_STAFF , "Wilderness"),
CRAZY_ARCHAEOLOGIST("Crazy Archaeologist", ItemID.FEDORA , "Wilderness"),
// Wildy Other
KING_BLACK_DRAGON("King Black Dragon", ItemID.PRINCE_BLACK_DRAGON , "Wilderness"),
// Slayer Bosses
KALPHITE_QUEEN("Kalphite Queen", ItemID.KALPHITE_PRINCESS, "Other"),
SKOTIZO("Skotizo", ItemID.SKOTOS, "Slayer"),
GROTESQUE_GUARDIANS("Dusk", ItemID.NOON, "Slayer"),
ABYSSAL_SIRE("Abyssal Sire", ItemID.ABYSSAL_ORPHAN, "Slayer"),
KRAKEN("Kraken", ItemID.PET_KRAKEN, "Slayer"),
CERBERUS("Cerberus", ItemID.HELLPUPPY, "Slayer"),
THERMONUCLEAR_SMOKE_DEVIL("Thermonuclear Smoke Devil", ItemID.PET_SMOKE_DEVIL, "Slayer"),
// Other Bosses
GIANT_MOLE("Giant Mole", ItemID.BABY_MOLE, "Other"),
CORPOREAL_BEAST("Corporeal Beast", ItemID.PET_CORPOREAL_CRITTER, "Other"),
// Dagannoth Kings
DAGANNOTH_REX("Dagannoth Rex", ItemID.PET_DAGANNOTH_REX, "Dagannoth Kings"),
DAGANNOTH_PRIME("Dagannoth Prime", ItemID.PET_DAGANNOTH_PRIME, "Dagannoth Kings"),
DAGANNOTH_SUPREME("Dagannoth Supreme", ItemID.PET_DAGANNOTH_SUPREME, "Dagannoth Kings"),
// Clue scrolls
CLUE_SCROLL_BEGINNER("Clue Scroll (Beginner)", ItemID.CLUE_SCROLL_BEGINNER, "Clue Scrolls"),
CLUE_SCROLL_EASY("Clue Scroll (Easy)", ItemID.CLUE_SCROLL_EASY, "Clue Scrolls"),
CLUE_SCROLL_MEDIUM("Clue Scroll (Medium)", ItemID.CLUE_SCROLL_MEDIUM, "Clue Scrolls"),
CLUE_SCROLL_HARD("Clue Scroll (Hard)", ItemID.CLUE_SCROLL_HARD, "Clue Scrolls"),
CLUE_SCROLL_ELITE("Clue Scroll (Elite)", ItemID.CLUE_SCROLL_ELITE, "Clue Scrolls"),
CLUE_SCROLL_MASTER("Clue Scroll (Master)", ItemID.CLUE_SCROLL_MASTER, "Clue Scrolls"),
// Hunter
HERBIBOAR("Herbiboar", ItemID.HERBI, "Hunter");
BossTab(String name, int iconItem, String category)
{
this.name = name;
this.itemID = iconItem;
this.category = category;
}
private final String name;
private final int itemID;
private final String category;
// By Boss Name
private static final Map<String, BossTab> byName = buildMap();
public static BossTab getByName(String name)
{
return byName.get(name.toUpperCase());
}
private static Map<String, BossTab> buildMap()
{
Map<String, BossTab> byName = new HashMap<>();
for (BossTab tab : values())
{
byName.put(tab.getName().toUpperCase(), tab);
}
return byName;
}
// By Category Name
private static final Map<String, ArrayList<BossTab>> byCategoryName = buildCategoryMap();
public static ArrayList<BossTab> getByCategoryName(String name)
{
return byCategoryName.get(name.toUpperCase());
}
private static Map<String, ArrayList<BossTab>> buildCategoryMap()
{
Map<String, ArrayList<BossTab>> map = new HashMap<>();
for (BossTab tab : values())
{
map.computeIfAbsent(tab.getCategory().toUpperCase(), e -> new ArrayList<>()).add(tab);
}
return map;
}
// All Categories
public static final Set<String> categories = getCategories();
private static Set<String> getCategories()
{
Set<String> s = new TreeSet<>();
for (BossTab tab : values())
{
s.add(tab.getCategory());
}
return s;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.Getter;
@Getter
@Deprecated
public class LootRecord
{
private final int id;
private final String name;
private final int level;
private final int killCount;
final Collection<LootTrackerItemEntry> drops;
public LootRecord(int id, String name, int level, int kc, Collection<LootTrackerItemEntry> drops)
{
this.id = id;
this.name = name;
this.level = level;
this.killCount = kc;
this.drops = (drops == null ? new ArrayList<>() : drops);
}
LootRecordCustom toNewFormat()
{
List<LootTrackerItemEntry> drops = new ArrayList<>();
for (LootTrackerItemEntry e : this.getDrops())
{
drops.add(new LootTrackerItemEntry(e.getName(), e.getId(), e.getQuantity(), e.getPrice(), e.getHaPrice(), e.isStackable()));
}
return new LootRecordCustom(this.getId(), this.getName(), this.getLevel(), this.getKillCount(), drops);
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
@Getter
public class LootRecordCustom
{
private final int id;
private final String name;
private final int level;
private final int killCount;
final Collection<LootTrackerItemEntry> drops;
public LootRecordCustom(int id, String name, int level, int kc, Collection<LootTrackerItemEntry> drops)
{
this.id = id;
this.name = name;
this.level = level;
this.killCount = kc;
this.drops = (drops == null ? new ArrayList<>() : drops);
}
/**
* Add the requested ItemStack to this LootRecord
* @param drop ItemStack to add
*/
public void addDropEntry(LootTrackerItemEntry drop)
{
drops.add(drop);
}
public static Map<Integer, LootTrackerItemEntry> consolidateLootTrackerItemEntries(Collection<LootRecordCustom> records)
{
// Store LootTrackerItemEntry by ItemID
Map<Integer, LootTrackerItemEntry> itemMap = new HashMap<>();
for (LootRecordCustom r : records)
{
for (LootTrackerItemEntry e : r.getDrops())
{
int old = 0;
if (itemMap.containsKey(e.getId()))
{
old = itemMap.get(e.getId()).getQuantity();
itemMap.remove(e.getId());
}
itemMap.put(e.getId(), new LootTrackerItemEntry(e.getName(), e.getId(), e.getQuantity() + old, e.getPrice(), e.getHaPrice(), e.isStackable()));
}
}
return itemMap;
}
@Override
public String toString()
{
StringBuilder m = new StringBuilder();
m.append("LootRecord{id=")
.append(id)
.append(",name=")
.append(name)
.append(",level=")
.append(level)
.append(",killCount=")
.append(killCount)
.append(",drops=[");
boolean addComma = false;
for (LootTrackerItemEntry d : drops)
{
if (addComma)
{
m.append(",");
}
m.append(d.toString());
addComma = true;
}
m.append("]}");
return m.toString();
}
}

View File

@@ -0,0 +1,264 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import static net.runelite.client.RuneLite.RUNELITE_DIR;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.plugins.stonedloottracker.data.events.LootTrackerNameChange;
import net.runelite.client.plugins.stonedloottracker.data.events.LootTrackerRecordStored;
import net.runelite.http.api.RuneLiteAPI;
@Slf4j
@Singleton
public class LootRecordWriter
{
private static final String FILE_EXTENSION = ".log";
private static final File LOOT_RECORD_DIR = new File(RUNELITE_DIR, "loots");
// Data is stored in a folder with the players in-game username
private File playerFolder = LOOT_RECORD_DIR;
private final EventBus bus;
@Inject
public LootRecordWriter(EventBus bus)
{
this.bus = bus;
playerFolder.mkdir();
}
private static String npcNameToFileName(String npcName)
{
return npcName.toLowerCase().trim() + FILE_EXTENSION;
}
public void setPlayerUsername(String username)
{
playerFolder = new File(LOOT_RECORD_DIR, username);
playerFolder.mkdir();
bus.post(new LootTrackerNameChange());
}
public Set<String> getKnownFileNames()
{
Set<String> fileNames = new HashSet<>();
File[] files = playerFolder.listFiles((dir, name) -> name.endsWith(FILE_EXTENSION));
if (files != null)
{
for (File f : files)
{
fileNames.add(f.getName().replace(FILE_EXTENSION, ""));
}
}
return fileNames;
}
public synchronized Collection<LootRecordCustom> loadLootTrackerRecords(String npcName)
{
String fileName = npcNameToFileName(npcName);
File file = new File(playerFolder, fileName);
Collection<LootRecordCustom> data = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file)))
{
String line;
while ((line = br.readLine()) != null)
{
// Skip empty line at end of file
if (line.length() > 0)
{
LootRecordCustom r = RuneLiteAPI.GSON.fromJson(line, LootRecordCustom.class);
data.add(r);
}
}
}
catch (FileNotFoundException e)
{
log.debug("File not found: {}", fileName);
}
catch (IOException e)
{
log.warn("IOException for file {}: {}", fileName, e.getMessage());
}
return data;
}
public synchronized boolean addLootTrackerRecord(LootRecordCustom rec)
{
// Grab file
String fileName = npcNameToFileName(rec.getName());
File lootFile = new File(playerFolder, fileName);
// Convert entry to JSON
String dataAsString = RuneLiteAPI.GSON.toJson(rec);
// Open File in append mode and write new data
try
{
BufferedWriter file = new BufferedWriter(new FileWriter(String.valueOf(lootFile), true));
file.append(dataAsString);
file.newLine();
file.close();
bus.post(new LootTrackerRecordStored(rec));
return true;
}
catch (IOException ioe)
{
log.warn("Error writing loot data to file {}: {}", fileName, ioe.getMessage());
return false;
}
}
// Mostly used to adjust previous loot entries such as adding pet drops/abyssal sire drops
public synchronized boolean writeLootTrackerFile(String npcName, Collection<LootRecordCustom> loots)
{
String fileName = npcNameToFileName(npcName);
File lootFile = new File(playerFolder, fileName);
try
{
BufferedWriter file = new BufferedWriter(new FileWriter(String.valueOf(lootFile), false));
for (LootRecordCustom rec : loots)
{
// Convert entry to JSON
String dataAsString = RuneLiteAPI.GSON.toJson(rec);
file.append(dataAsString);
file.newLine();
}
file.close();
return true;
}
catch (IOException ioe)
{
log.warn("Error rewriting loot data to file {}: {}", fileName, ioe.getMessage());
return false;
}
}
public synchronized boolean deleteLootTrackerRecords(String npcName)
{
String fileName = npcNameToFileName(npcName);
File lootFile = new File(playerFolder, fileName);
if (lootFile.delete())
{
log.debug("Deleted loot file: {}", fileName);
return true;
}
else
{
log.debug("Couldn't delete file: {}", fileName);
return false;
}
}
public Collection<LootRecordCustom> loadAllLootTrackerRecords()
{
List<LootRecordCustom> recs = new ArrayList<>();
for (String n : getKnownFileNames())
{
recs.addAll(loadLootTrackerRecords(n));
}
return recs;
}
public void convertFileFormats()
{
for (String f : getKnownFileNames())
{
updateFileFormat(npcNameToFileName(f));
}
log.info("Done converting");
}
private void updateFileFormat(String fileName)
{
File lootFile = new File(playerFolder, fileName);
Collection<LootRecordCustom> data = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(lootFile)))
{
String line;
while ((line = br.readLine()) != null)
{
// Skip empty line at end of file
if (line.length() > 0)
{
LootRecordCustom r = RuneLiteAPI.GSON.fromJson(line, LootRecord.class).toNewFormat();
data.add(r);
}
}
}
catch (FileNotFoundException e)
{
log.info("File not found: {}", fileName);
}
catch (IOException e)
{
log.warn("IOException for file {}: {}", fileName, e.getMessage());
}
try
{
BufferedWriter file = new BufferedWriter(new FileWriter(String.valueOf(lootFile), false));
for (LootRecordCustom rec : data)
{
// Convert entry to JSON
String dataAsString = RuneLiteAPI.GSON.toJson(rec);
file.append(dataAsString);
file.newLine();
}
file.close();
}
catch (IOException ioe)
{
log.warn("Error rewriting loot data to file {}: {}", fileName, ioe.getMessage());
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import lombok.Getter;
@Getter
public class LootTrackerItemEntry
{
private final String name;
private final int id;
private int quantity;
private final long price;
private final long haPrice;
private long total;
private long haTotal;
private final boolean stackable;
public LootTrackerItemEntry(String name, int id, int quantity, long price, long haPrice, boolean stackable)
{
this.name = name;
this.id = id;
this.quantity = quantity;
this.price = price;
this.haPrice = haPrice;
this.stackable = stackable;
this.total = price * quantity;
this.haTotal = haPrice * quantity;
}
@Override
public String toString()
{
return "LootTrackerItemEntry(name=" + name + ",id=" + id + ",quantity=" + quantity + ",price=" + price + ",haprice=" + haPrice + ",total=" + total + ",haTotal=" + haTotal + ",stackable=" + stackable + ")";
}
}

View File

@@ -0,0 +1,73 @@
package net.runelite.client.plugins.stonedloottracker.data;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.ItemID;
@Getter
public enum Pet
{
// GWD Pets
PET_GENERAL_GRAARDOR(ItemID.PET_GENERAL_GRAARDOR, "General graador"),
PET_KREEARRA(ItemID.PET_KREEARRA, "Kree'arra"),
PET_KRIL_TSUTSAROTH(ItemID.PET_KRIL_TSUTSAROTH, "K'ril tsutsaroth"),
PET_ZILYANA(ItemID.PET_ZILYANA, "Commander zilyana"),
// Wildy Pets
CALLISTO_CUB(ItemID.CALLISTO_CUB, "Callisto"),
PET_CHAOS_ELEMENTAL(ItemID.PET_CHAOS_ELEMENTAL, "Chaos Elemental", "Chaos Fanatic"),
SCORPIAS_OFFSPRING(ItemID.SCORPIAS_OFFSPRING, "Scorpia"),
VENENATIS_SPIDERLING(ItemID.VENENATIS_SPIDERLING, "Venenatis"),
VETION_JR(ItemID.VETION_JR, "Vet'ion"),
// KBD isn't really in wildy but meh
PRINCE_BLACK_DRAGON(ItemID.PRINCE_BLACK_DRAGON, "King Black Dragon"),
// Slayer Pets
ABYSSAL_ORPHAN(ItemID.ABYSSAL_ORPHAN, "Abyssal Sire"),
HELLPUPPY(ItemID.HELLPUPPY, "Cerberus"),
NOON(ItemID.NOON, "Noon", "Dusk"),
PET_KRAKEN(ItemID.PET_KRAKEN, "Kraken"),
PET_SMOKE_DEVIL(ItemID.PET_SMOKE_DEVIL, "Thermonuclear Smoke Devil"),
SKOTOS(ItemID.SKOTOS, "Skotizo"),
// Other Bosses
BABY_MOLE(ItemID.BABY_MOLE, "Giant Mole"),
KALPHITE_PRINCESS(ItemID.KALPHITE_PRINCESS, "Kalphite Queen"),
OLMLET(ItemID.OLMLET, "Chambers of Xeric"),
LIL_ZIK(ItemID.LIL_ZIK, "Theatre of Blood"),
PET_DARK_CORE(ItemID.PET_DARK_CORE, "Corporeal Beast"),
PET_SNAKELING(ItemID.PET_SNAKELING, "Zulrah"),
PET_DAGANNOTH_REX(ItemID.PET_DAGANNOTH_REX, "Dagannoth Rex"),
PET_DAGANNOTH_PRIME(ItemID.PET_DAGANNOTH_PRIME, "Dagannoth Prime"),
PET_DAGANNOTH_SUPREME(ItemID.PET_DAGANNOTH_SUPREME, "Dagannoth Supreme"),
VORKI(ItemID.VORKI, "Vorkath"),
BLOODHOUND(ItemID.BLOODHOUND, "Clue Scroll Master"),
HERBI(ItemID.HERBI, "Herbiboar");
private final int petID;
private final String[] bossNames;
private static final Map<String, Pet> byBossName = buildBossMap();
Pet(int id, String... bossNames)
{
this.petID = id;
this.bossNames = bossNames;
}
public static Pet getByBossName(String name)
{
return byBossName.get(name.toUpperCase());
}
private static Map<String, Pet> buildBossMap()
{
Map<String, Pet> byName = new HashMap<>();
for (Pet pet : values())
{
String[] droppingBosses = pet.getBossNames();
for (String bossName : droppingBosses)
{
byName.put(bossName.toUpperCase(), pet);
}
}
return byName;
}
}

View File

@@ -0,0 +1,929 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.ItemID;
@RequiredArgsConstructor
@Getter
public enum UniqueItem
{
// Shared "Uniques" (Dropped by multiple activities)
// God Wars
GODSWORD_SHARD_1("Godsword shard 1", ItemID.GODSWORD_SHARD_1, "Kree'arra", "General Graardor", "Commander Zilyana", "K'ril Tsutsaroth"),
GODSWORD_SHARD_2("Godsword shard 2", ItemID.GODSWORD_SHARD_2, "Kree'arra", "General Graardor", "Commander Zilyana", "K'ril Tsutsaroth"),
GODSWORD_SHARD_3("Godsword shard 3", ItemID.GODSWORD_SHARD_3, "Kree'arra", "General Graardor", "Commander Zilyana", "K'ril Tsutsaroth"),
// Wildy
PET_CHAOS_ELEMENTAL("Pet chaos elemental", ItemID.PET_CHAOS_ELEMENTAL, "Chaos Elemental", "Chaos Fanatic"),
// MYSTERIOUS_EMBLEM("Mysterious emblem", ItemID.MYSTERIOUS_EMBLEM , ""),
CURVED_BONE("Curved bone", ItemID.CURVED_BONE, "Callisto", "Vet'ion", "Venenatis", "Giant Mole"),
DRAGON_PICKAXE("Dragon pickaxe", ItemID.DRAGON_PICKAXE , "Callisto", "Vet'ion", "Venenatis", "King Black Dragon", "Chaos Elemental"),
DRAGON_2H_SWORD("Dragon 2h sword", ItemID.DRAGON_2H_SWORD , "Callisto", "Vet'ion", "Venenatis", "Kalphite Queen"),
// Other
DRAGON_CHAINBODY("Dragon chainbody", ItemID.DRAGON_CHAINBODY_3140, "Thermonuclear Smoke Devil", "Kalphite Queen"),
DRAGON_AXE("Dragon axe", ItemID.DRAGON_AXE, "Dagannoth Rex", "Dagannoth Prime", "Dagannoth Supreme"),
UNCUT_ONYX("Uncut onyx", ItemID.UNCUT_ONYX, "Zulrah", "Skotizo"),
DRACONIC_VISAGE("Draconic visage", ItemID.DRACONIC_VISAGE, "Vorkath", "King Black Dragon"),
// Unique Items
// Barrows Uniques
// Ahrim
AHRIMS_HOOD("Ahrim's hood", ItemID.AHRIMS_HOOD, "Barrows", "Ahrims", 0),
AHRIMS_ROBETOP("Ahrim's robetop", ItemID.AHRIMS_ROBETOP, "Barrows", "Ahrims", 0),
AHRIMS_ROBESKIRT("Ahrim's robeskirt", ItemID.AHRIMS_ROBESKIRT, "Barrows", "Ahrims", 0),
AHRIMS_STAFF("Ahrim's staff", ItemID.AHRIMS_STAFF, "Barrows", "Ahrims", 0),
// Dharok
DHAROKS_HELM("Dharok's helm", ItemID.DHAROKS_HELM, "Barrows", "Dharoks", 1),
DHAROKS_PLATEBODY("Dharok's platebody", ItemID.DHAROKS_PLATEBODY, "Barrows", "Dharoks", 1),
DHAROKS_PLATELEGS("Dharok's platelegs", ItemID.DHAROKS_PLATELEGS, "Barrows", "Dharoks", 1),
DHAROKS_GREATAXE("Dharok's greataxe", ItemID.DHAROKS_GREATAXE, "Barrows", "Dharoks", 1),
// Guthans
GUTHANS_HELM("Guthan's helm", ItemID.GUTHANS_HELM, "Barrows", "Guthans", 2),
GUTHANS_PLATEBODY("Guthan's platebody", ItemID.GUTHANS_PLATEBODY, "Barrows", "Guthans", 2),
GUTHANS_CHAINSKIRT("Guthan's Chainskirt", ItemID.GUTHANS_CHAINSKIRT, "Barrows", "Guthans", 2),
GUTHANS_WARSPEAR("Guthan's warspear", ItemID.GUTHANS_WARSPEAR, "Barrows", "Guthans", 2),
// Karils
KARILS_COIF("Karil's coif", ItemID.KARILS_COIF, "Barrows", "Karils", 3),
KARILS_LEATHERTOP("Karil's leathertop", ItemID.KARILS_LEATHERTOP, "Barrows", "Karils", 3),
KARILS_LEATHERSKIRT("Karil's leatherskirt", ItemID.KARILS_LEATHERSKIRT, "Barrows", "Karils", 3),
KARILS_CROSSBOW("Karil's crossbow ", ItemID.KARILS_CROSSBOW, "Barrows", "Karils", 3),
//Torag
TORAGS_HELM("Torag's helm", ItemID.TORAGS_HELM, "Barrows", "Torags", 4),
TORAGS_PLATEBODY("Torags's platebody", ItemID.TORAGS_PLATEBODY, "Barrows", "Torags", 4),
TORAGS_PLATELEGS("Torags's platelegs", ItemID.TORAGS_PLATELEGS, "Barrows", "Torags", 4),
TORAGS_HAMMERS("Torag's hammers", ItemID.TORAGS_HAMMERS, "Barrows", "Torags", 4),
// Veracs
VERACS_HELM("Verac's helm", ItemID.VERACS_HELM, "Barrows", "Veracs", 5),
VERACS_BRASSARD("Verac's brassard", ItemID.VERACS_BRASSARD, "Barrows", "Veracs", 5),
VERACS_PLATESKIRT("Verac's plateskirt", ItemID.VERACS_PLATESKIRT, "Barrows", "Veracs", 5),
VERACS_FLAIL("Verac's flail", ItemID.VERACS_FLAIL, "Barrows", "Veracs", 5),
// Raids Uniques
// Mage
KODAI_INSIGNIA("Kodai insignia", ItemID.KODAI_INSIGNIA, "Chambers of Xeric", "Mage", 0),
ANCESTRAL_HAT("Ancestral hat", ItemID.ANCESTRAL_HAT, "Chambers of Xeric", "Mage", 0),
ANCESTRAL_ROBE_TOP("Ancestral robe top", ItemID.ANCESTRAL_ROBE_TOP, "Chambers of Xeric", "Mage", 0),
ANCESTRAL_ROBE_BOTTOM("Ancestral robe bottom", ItemID.ANCESTRAL_ROBE_BOTTOM, "Chambers of Xeric", "Mage", 0),
// Range
DRAGON_THROWNAXE("Dragon thrownaxe", ItemID.DRAGON_THROWNAXE, "Chambers of Xeric", "Range", 1),
DRAGON_HUNTER_CROSSBOW("Dragon hunter crossbow", ItemID.DRAGON_HUNTER_CROSSBOW, "Chambers of Xeric", "Range", 1),
TWISTED_BUCKLER("Twisted buckler", ItemID.TWISTED_BUCKLER, "Chambers of Xeric", "Range", 1),
TWISTED_BOW("Twisted bow", ItemID.TWISTED_BOW, "Chambers of Xeric", "Range", 1),
// Melee
DRAGON_SWORD("Dragon sword", ItemID.DRAGON_SWORD, "Chambers of Xeric", "Melee", 2),
DRAGON_CLAWS("Dragon claws", ItemID.DRAGON_CLAWS, "Chambers of Xeric", "Melee", 2),
DINHS_BULWARK("Dinh's bulwark", ItemID.DINHS_BULWARK, "Chambers of Xeric", "Melee", 2),
ELDER_MAUL("Elder maul", ItemID.ELDER_MAUL, "Chambers of Xeric", "Melee", 2),
// Prayers
TORN_PRAYER_SCROLL("Torn prayer scroll", ItemID.TORN_PRAYER_SCROLL, "Chambers of Xeric", "Prayer", 3),
ARCANE_PRAYER_SCROLL("Arcane prayer scroll", ItemID.ARCANE_PRAYER_SCROLL, "Chambers of Xeric", "Prayer", 3),
DEXTEROUS_PRAYER_SCROLL("Dexterous prayer scroll", ItemID.DEXTEROUS_PRAYER_SCROLL, "Chambers of Xeric", "Prayer", 3),
// Other
DRAGON_HARPOON("Dragon harpoon", ItemID.DRAGON_HARPOON, "Chambers of Xeric", "Other", 4),
DARK_RELIC("Dark relic", ItemID.DARK_RELIC, "Chambers of Xeric", "Other", 4),
// Raids 2 Unqiues (Theater of Blood)
// Weapons / Pet
GHRAZI_RAPIER("Ghrazi rapier", ItemID.GHRAZI_RAPIER, "Theatre of Blood", "Weapons", 0),
SCYTHE_OF_VITUR_UNCHARGED("Scythe of vitur (uncharged)", ItemID.SCYTHE_OF_VITUR_UNCHARGED, "Theatre of Blood", "Weapons", 0),
SANGUINESTI_STAFF_UNCHAGRED("Sanguinesti staff (uncharged)", ItemID.SANGUINESTI_STAFF_UNCHARGED, "Theatre of Blood", "Weapons", 0),
LIL_ZIK("Lil' zik", ItemID.LIL_ZIK, "Theatre of Blood", "Weapons", 0),
// Armor
AVERNIC_DEFENDER_HILT("Avernic defender hilt", ItemID.AVERNIC_DEFENDER_HILT, "Theatre of Blood", "Armor", 1),
JUSTICIAR_FACEGUARD("Justiciar faceguard", ItemID.JUSTICIAR_FACEGUARD, "Theatre of Blood", "Armor", 1),
JUSTICIAR_CHESTGUARD("Justiciar chestguard", ItemID.JUSTICIAR_CHESTGUARD, "Theatre of Blood", "Armor", 1),
JUSTICIAR_LEGGUARDS("Justiciar legguards", ItemID.JUSTICIAR_LEGGUARDS, "Theatre of Blood", "Armor", 1),
// Zulrah
// Uniques
TANZANITE_FANG("Tanzanite fang", ItemID.TANZANITE_FANG, "Zulrah", "Uniques", -1),
MAGIC_FANG("Magic fang", ItemID.MAGIC_FANG, "Zulrah", "Uniques", -1),
SERPENTINE_VISAGE("Serpentine visage", ItemID.SERPENTINE_VISAGE, "Zulrah", "Uniques", -1),
// Rares
PET_SNAKELING("Pet snakeling", ItemID.PET_SNAKELING, "Zulrah", "Rares", 0),
TANZANITE_MUTAGEN("Tanzanite mutagen", ItemID.TANZANITE_MUTAGEN, "Zulrah", "Rares", 0),
MAGMA_MUTAGEN("Magma mutagen", ItemID.MAGMA_MUTAGEN, "Zulrah", "Rares", 0),
JAR_OF_SWAMP("Jar of swamp", ItemID.JAR_OF_SWAMP, "Zulrah", "Rares", 0),
// Vorkath
DRAGONBONE_NECKLACE("Dragonbone necklace", ItemID.DRAGONBONE_NECKLACE, "Vorkath", "Uniques", 0),
VORKATHS_HEAD("Vorkath's head", ItemID.VORKATHS_HEAD_21907, "Vorkath", "Uniques", 0),
JAR_OF_DECAY("Jar of decay", ItemID.JAR_OF_DECAY, "Vorkath", "Uniques", 0),
SKELETAL_VISAGE("Skeletal visage", ItemID.SKELETAL_VISAGE, "Vorkath", "Uniques", -1),
VORKI("Vorki", ItemID.VORKI, "Vorkath", "Uniques", -1),
// God Wars Dungeon
// Kree'arra (Armadyl)
ARMADYL_HELMET("Armadyl helmet", ItemID.ARMADYL_HELMET, "Kree'arra", "Uniques", 0),
ARMADYL_CHESTPLATE("Armadyl chestplate", ItemID.ARMADYL_CHESTPLATE, "Kree'arra", "Uniques", 0),
ARMADYL_CHAINSKIRT("Armadyl chainskirt", ItemID.ARMADYL_CHAINSKIRT, "Kree'arra", "Uniques", 0),
ARMADYL_HILT("Armadyl hilt", ItemID.ARMADYL_HILT, "Kree'arra", "Uniques", 0),
PET_KREEARRA("Pet Kree'arra", ItemID.PET_KREEARRA, "Kree'arra", "Pet", -1),
// General Graardor (Bandos)
BANDOS_CHESTPLATE("Bandos chestplate", ItemID.BANDOS_CHESTPLATE, "General Graardor", "Uniques", 0),
BANDOS_TASSETS("Bandos tassets", ItemID.BANDOS_TASSETS, "General Graardor", "Uniques", 0),
BANDOS_BOOTS("Bandos boots", ItemID.BANDOS_BOOTS, "General Graardor", "Uniques", 0),
BANDOS_HILT("Bandos hilt", ItemID.BANDOS_HILT, "General Graardor", "Uniques", 0),
PET_GENERAL_GRAARDOR("Pet general graardor", ItemID.PET_GENERAL_GRAARDOR, "General Graardor", "Pet", -1),
// Command Zilyana (Saradomin)
SARADOMIN_SWORD("Saradomin sword", ItemID.SARADOMIN_SWORD, "Commander Zilyana", "Uniques", 0),
ARMADYL_CROSSBOW("Armadyl crossbow", ItemID.ARMADYL_CROSSBOW, "Commander Zilyana", "Uniques", 0),
SARADOMINS_LIGHT("Saradomins light", ItemID.SARADOMINS_LIGHT, "Commander Zilyana", "Uniques", 0),
SARADOMIN_HILT("Saradomin hilt", ItemID.SARADOMIN_HILT, "Commander Zilyana", "Uniques", 0),
PET_ZILYANA("Pet zilyana", ItemID.PET_ZILYANA, "Commander Zilyana", "Pet", -1),
// K'ril Tsutsaroth (Zammy)
STEAM_BATTLESTAFF("Steam battlestaff", ItemID.STEAM_BATTLESTAFF, "K'ril Tsutsaroth", "Uniques", 0),
STAFF_OF_THE_DEAD("Staff of the dead", ItemID.STAFF_OF_THE_DEAD, "K'ril Tsutsaroth", "Uniques", 0),
ZAMORAKIAN_SPEAR("Zamorakian spear", ItemID.ZAMORAKIAN_SPEAR, "K'ril Tsutsaroth", "Uniques", 0),
ZAMORAK_HILT("Zamorak hilt", ItemID.ZAMORAK_HILT, "K'ril Tsutsaroth", "Uniques", 0),
PET_KRIL_TSUTSAROTH("Pet k'ril tsutsaroth", ItemID.PET_KRIL_TSUTSAROTH, "K'ril Tsutsaroth", "Pet", -1),
// Wildy Bosses
// Vet'ion
RING_OF_THE_GODS("Ring of the gods", ItemID.RING_OF_THE_GODS , "Vet'ion", "Uniques", 0),
VETION_JR("Vet'ion jr.", ItemID.VETION_JR , "Vet'ion", "Uniques", 0),
SKELETON_CHAMPION_SCROLL("Skeleton champion scroll", ItemID.SKELETON_CHAMPION_SCROLL , "Vet'ion", "Uniques", 0),
// Venenatis
TREASONOUS_RING("Treasonous ring", ItemID.TREASONOUS_RING , "Venenatis", "Uniques", -1),
VENENATIS_SPIDERLING("Venenatis spiderling", ItemID.VENENATIS_SPIDERLING , "Venenatis", "Uniques", -1),
// Callisto
TYRANNICAL_RING("Tyrannical ring", ItemID.TYRANNICAL_RING , "Callisto", "Uniques", -1),
CALLISTO_CUB("Callisto cub", ItemID.CALLISTO_CUB , "Callisto", "Uniques", -1),
// Chaos Elemental Uniques are all in Shared
// Chaos Fanatic
ODIUM_SHARD_1("Odium shard 1", ItemID.ODIUM_SHARD_1 , "Chaos Fanatic", "Uniques", -1),
MALEDICTION_SHARD_1("Malediction shard 1", ItemID.MALEDICTION_SHARD_1 , "Chaos Fanatic", "Uniques", -1),
// Crazy Archaeologist
ODIUM_SHARD_2("Odium shard 2", ItemID.ODIUM_SHARD_2 , "Crazy Archaeologist", "Uniques", 0),
MALEDICTION_SHARD_2("Malediction shard 2", ItemID.MALEDICTION_SHARD_2 , "Crazy Archaeologist", "Uniques", 0),
FEDORA("Fedora", ItemID.FEDORA , "Crazy Archaeologist", "Uniques", 0),
// Scorpia
ODIUM_SHARD_3("Odium shard 3", ItemID.ODIUM_SHARD_3 , "Scorpia", "Uniques", 0),
MALEDICTION_SHARD_3("Malediction shard 3", ItemID.MALEDICTION_SHARD_3 , "Scorpia", "Uniques", 0),
SCORPIAS_OFFSPRING("Scorpia's offspring", ItemID.SCORPIAS_OFFSPRING , "Scorpia", "Uniques", 0),
// King Black Dragon,
KBD_HEADS("Kbd heads", ItemID.KBD_HEADS , "King Black Dragon", "Uniques", -1),
PRINCE_BLACK_DRAGON("Prince black dragon", ItemID.PRINCE_BLACK_DRAGON , "King Black Dragon", "Uniques", -1),
// Slayer Bosses
// Skotizo
DARK_CLAW("Dark claw", ItemID.DARK_CLAW, "Skotizo", "Uniques", -1),
SKOTOS("Skotos", ItemID.SKOTOS, "Skotizo", "Uniques", -1),
JAR_OF_DARKNESS("Jar of darkness", ItemID.JAR_OF_DARKNESS, "Skotizo", "Uniques", -1),
// Grotesque Guardians
GRANITE_GLOVES("Granite gloves", ItemID.GRANITE_GLOVES, "Dusk", "Uniques", 0),
GRANITE_RING("Granite ring", ItemID.GRANITE_RING, "Dusk", "Uniques", 0),
GRANITE_HAMMER("Granite hammer", ItemID.GRANITE_HAMMER, "Dusk", "Uniques", 0),
BLACK_TOURMALINE_CORE("Black touramline core", ItemID.BLACK_TOURMALINE_CORE, "Dusk", "Uniques 2", 1),
NOON("Noon", ItemID.NOON, "Dusk", "Uniques 2", 1),
JAR_OF_STONE("Jar of stone", ItemID.JAR_OF_STONE, "Dusk", "Uniques 2", 1),
// Abyssal Sire
UNSIRED("Unsired", ItemID.UNSIRED, "Abyssal Sire", "Uniques", 0),
BLUDGEON_CLAW("Bludgeon claw", ItemID.BLUDGEON_CLAW, "Abyssal Sire", "Uniques", 0),
BLUDGEON_SPINE("Bludgeon spine", ItemID.BLUDGEON_SPINE, "Abyssal Sire", "Uniques", 0),
BLUDGEON_AXON("Bludgeon axon", ItemID.BLUDGEON_AXON, "Abyssal Sire", "Uniques", 0),
ABYSSAL_DAGGER("Abyssal dagger", ItemID.ABYSSAL_DAGGER, "Abyssal Sire", "Uniques 2", 1),
ABYSSAL_WHIP("Abyssal whip", ItemID.ABYSSAL_WHIP, "Abyssal Sire", "Uniques 2", 1),
ABYSSAL_ORPHAN("Abyssal orphan", ItemID.ABYSSAL_ORPHAN, "Abyssal Sire", "Uniques 2", 1),
JAR_OF_MIASMA("Jar of miasma", ItemID.JAR_OF_MIASMA, "Abyssal Sire", "Uniques 2", 1),
ABYSSAL_HEAD("Abyssal head", ItemID.ABYSSAL_HEAD, "Abyssal Sire", "Uniques 2", 1),
// Kraken
TRIDENT_OF_THE_SEAS_FULL("Trident of the seas (full)", ItemID.TRIDENT_OF_THE_SEAS_FULL, "Kraken", "Uniques", 0),
KRAKEN_TENTACLE("Kraken tentacle", ItemID.KRAKEN_TENTACLE, "Kraken", "Uniques", 0),
JAR_OF_DIRT("Jar of dirt", ItemID.JAR_OF_DIRT, "Kraken", "Uniques", 0),
PET_KRAKEN("Pet kraken", ItemID.PET_KRAKEN, "Kraken", "Uniques", 0),
// Cerberus
PRIMORDIAL_CRYSTAL("Primordial crystal", ItemID.PRIMORDIAL_CRYSTAL, "Cerberus", "Uniques", 0),
PEGASIAN_CRYSTAL("Pegasian crystal", ItemID.PEGASIAN_CRYSTAL, "Cerberus", "Uniques", 0),
ETERNAL_CRYSTAL("Eternal crystal", ItemID.ETERNAL_CRYSTAL, "Cerberus", "Uniques", 0),
SMOULDERING_STONE("Smouldering stone", ItemID.SMOULDERING_STONE, "Cerberus", "Uniques", 1),
JAR_OF_SOULS("Jar of souls", ItemID.JAR_OF_SOULS, "Cerberus", "Uniques", 1),
HELLPUPPY("Hellpuppy", ItemID.HELLPUPPY, "Cerberus", "Uniques", 1),
// Thermonuclear Smoke Devil
SMOKE_BATTLESTAFF("Smoke battlestaff", ItemID.SMOKE_BATTLESTAFF, "Thermonuclear Smoke Devil", "Uniques", -1),
OCCULT_NECKLACE("Occult necklace", ItemID.OCCULT_NECKLACE, "Thermonuclear Smoke Devil", "Uniques", -1),
PET_SMOKE_DEVIL("Pet smoke devil", ItemID.PET_SMOKE_DEVIL, "Thermonuclear Smoke Devil", "Uniques", -1),
// Other Bosses
// Giant Mole
BABY_MOLE("Baby Mole", ItemID.BABY_MOLE, "Giant Mole", "Uniques", -1),
// Kalphite Queen
KQ_HEAD("Kq head", ItemID.KQ_HEAD, "Kalphite Queen", "Uniques", -1),
JAR_OF_SAND("Jar of sand", ItemID.JAR_OF_SAND, "Kalphite Queen", "Uniques", -1),
KALPHITE_PRINCESS("Kalphite princess", ItemID.KALPHITE_PRINCESS, "Kalphite Queen", "Uniques", -1),
// Corporeal Beast
SPIRIT_SHIELD("Spirit shield", ItemID.SPIRIT_SHIELD, "Corporeal Beast", "Uniques", 0),
HOLY_ELIXIR("Holy elixir", ItemID.HOLY_ELIXIR, "Corporeal Beast", "Uniques", 0),
PET_DARK_CORE("Pet dark core", ItemID.PET_DARK_CORE, "Corporeal Beast", "Uniques", 0),
SPECTRAL_SIGIL("Spectral sigil", ItemID.SPECTRAL_SIGIL, "Corporeal Beast", "Sigil", 1),
ARCANE_SIGIL("Arcane sigil", ItemID.ARCANE_SIGIL, "Corporeal Beast", "Sigil", 1),
ELYSIAN_SIGIL("Elysian sigil", ItemID.ELYSIAN_SIGIL, "Corporeal Beast", "Sigil", 1),
// Dagannoth Rex
WARRIOR_RING("Warrior ring", ItemID.WARRIOR_RING, "Dagannoth Rex", "Uniques", -1),
BERSERKER_RING("Berserk ring", ItemID.BERSERKER_RING, "Dagannoth Rex", "Uniques", -1),
PET_DAGANNOTH_REX("Pet dagannoth rex", ItemID.PET_DAGANNOTH_REX, "Dagannoth Rex", "Uniques", -1),
// Dagannoth Prime
MUD_BATTLESTAFF("Mud battlestaff", ItemID.MUD_BATTLESTAFF, "Dagannoth Prime", "Uniques", -1),
SEERS_RING("Seers ring", ItemID.SEERS_RING, "Dagannoth Prime", "Uniques", -1),
PET_DAGANNOTH_PRIME("Pet dagannoth prime", ItemID.PET_DAGANNOTH_PRIME, "Dagannoth Prime", "Uniques", -1),
// Dagannoth Supreme
SEERCULL("Seercrull", ItemID.SEERCULL, "Dagannoth Supreme", "Uniques", -1),
ARCHERS_RING("Archers ring", ItemID.ARCHERS_RING, "Dagannoth Supreme", "Uniques", -1),
PET_DAGANNOTH_SUPREME("Pet dagannoth supreme", ItemID.PET_DAGANNOTH_SUPREME, "Dagannoth Supreme", "Uniques", -1),
// Beginner Clue Scrolls
MOLE_SLIPPERS("Mole slippers", ItemID.MOLE_SLIPPERS, "Clue Scroll (Beginner)", "Slippers", -1),
FROG_SLIPPERS("Frog slippers", ItemID.FROG_SLIPPERS, "Clue Scroll (Beginner)", "Slippers", -1),
BEAR_FEET("Bear slippers", ItemID.BEAR_FEET, "Clue Scroll (Beginner)", "Slippers", -1),
DEMON_FEET("Demon slippers", ItemID.DEMON_FEET, "Clue Scroll (Beginner)", "Slippers", -1),
SANDWICH_LADY_HAT("Sandwich lady hat", ItemID.SANDWICH_LADY_HAT, "Clue Scroll (Beginner)", "Sandwich Lady", 0),
SANDWICH_LADY_TOP("Sandwich lady top", ItemID.SANDWICH_LADY_TOP, "Clue Scroll (Beginner)", "Sandwich Lady", 0),
SANDWICH_LADY_BOTTOM("Sandwich lady bottom", ItemID.SANDWICH_LADY_BOTTOM, "Clue Scroll (Beginner)", "Sandwich Lady", 0),
MONKS_ROBE_TOP_T("Monk robe top (t)", ItemID.MONKS_ROBE_TOP_T, "Clue Scroll (Beginner)", "Monk Robes", 1),
MONKS_ROBE_T("Monk robe (t)", ItemID.MONKS_ROBE_T, "Clue Scroll (Beginner)", "Monk Robes", 1),
JESTER_CAPE("Jester cape", ItemID.JESTER_CAPE, "Clue Scroll (Beginner)", "Other", 2),
SHOULDER_PARROT("Shoulder parrot", ItemID.SHOULDER_PARROT, "Clue Scroll (Beginner)", "Other", 2),
AMULET_OF_DEFENCE_T("Amulet of Defence (t)", ItemID.AMULET_OF_DEFENCE_T, "Clue Scroll (Beginner)", "Other", 2),
RUNE_SCIMITAR_ORNAMENT_KIT_GUTHIX("Rune scimitar Guthix ornament kit", ItemID.RUNE_SCIMITAR_ORNAMENT_KIT_GUTHIX, "Clue Scroll (Beginner)", "God Ornament Kit", 3),
RUNE_SCIMITAR_ORNAMENT_KIT_SARADOMIN("Rune scimitar Saradomin ornament kit", ItemID.RUNE_SCIMITAR_ORNAMENT_KIT_SARADOMIN, "Clue Scroll (Beginner)", "God Ornament Kit", 3),
RUNE_SCIMITAR_ORNAMENT_KIT_ZAMORAK("Rune scimitar Zamorak ornament kit", ItemID.RUNE_SCIMITAR_ORNAMENT_KIT_ZAMORAK, "Clue Scroll (Beginner)", "God Ornament Kit", 3),
// Easy Clue Scrolls
BRONZE_FULL_HELM_T("Bronze full helm (t)", ItemID.BRONZE_FULL_HELM_T, "Clue Scroll (Easy)", "Armor", -1),
BRONZE_PLATEBODY_T("Bronze platebody (t)", ItemID.BRONZE_PLATEBODY_T, "Clue Scroll (Easy)", "Armor", -1),
BRONZE_PLATELEGS_T("Bronze platelegs (t)", ItemID.BRONZE_PLATELEGS_T, "Clue Scroll (Easy)", "Armor", -1),
BRONZE_PLATESKIRT_T("Bronze plateskirt (t)", ItemID.BRONZE_PLATESKIRT_T, "Clue Scroll (Easy)", "Armor", -1),
BRONZE_KITESHIELD_T("Bronze kiteshield (t)", ItemID.BRONZE_KITESHIELD_T, "Clue Scroll (Easy)", "Armor", -1),
BRONZE_FULL_HELM_G("Bronze full helm (g)", ItemID.BRONZE_FULL_HELM_G, "Clue Scroll (Easy)", "Armor", 0),
BRONZE_PLATEBODY_G("Bronze platebody (g)", ItemID.BRONZE_PLATEBODY_G, "Clue Scroll (Easy)", "Armor", 0),
BRONZE_PLATELEGS_G("Bronze platelegs (g)", ItemID.BRONZE_PLATELEGS_G, "Clue Scroll (Easy)", "Armor", 0),
BRONZE_PLATESKIRT_G("Bronze plateskirt (g)", ItemID.BRONZE_PLATESKIRT_G, "Clue Scroll (Easy)", "Armor", 0),
BRONZE_KITESHIELD_G("Bronze kiteshield (g)", ItemID.BRONZE_KITESHIELD_G, "Clue Scroll (Easy)", "Armor", 0),
IRON_FULL_HELM_T("Iron full helm (t)", ItemID.IRON_FULL_HELM_T, "Clue Scroll (Easy)", "Armor", 1),
IRON_PLATEBODY_T("Iron platebody (t)", ItemID.IRON_PLATEBODY_T, "Clue Scroll (Easy)", "Armor", 1),
IRON_PLATELEGS_T("Iron platelegs (t)", ItemID.IRON_PLATELEGS_T, "Clue Scroll (Easy)", "Armor", 1),
IRON_PLATESKIRT_T("Iron plateskirt (t)", ItemID.IRON_PLATESKIRT_T, "Clue Scroll (Easy)", "Armor", 1),
IRON_KITESHIELD_T("Iron kiteshield (t)", ItemID.IRON_KITESHIELD_T, "Clue Scroll (Easy)", "Armor", 1),
IRON_FULL_HELM_G("Iron full helm (g)", ItemID.IRON_FULL_HELM_G, "Clue Scroll (Easy)", "Armor", 2),
IRON_PLATEBODY_G("Iron platebody (g)", ItemID.IRON_PLATEBODY_G, "Clue Scroll (Easy)", "Armor", 2),
IRON_PLATELEGS_G("Iron platelegs (g)", ItemID.IRON_PLATELEGS_G, "Clue Scroll (Easy)", "Armor", 2),
IRON_PLATESKIRT_G("Iron plateskirt (g)", ItemID.IRON_PLATESKIRT_G, "Clue Scroll (Easy)", "Armor", 2),
IRON_KITESHIELD_G("Iron kiteshield (g)", ItemID.IRON_KITESHIELD_G, "Clue Scroll (Easy)", "Armor", 2),
STEEL_FULL_HELM_T("Steel full helm (t)", ItemID.STEEL_FULL_HELM_T, "Clue Scroll (Easy)", "Armor", 3),
STEEL_PLATEBODY_T("Steel platebody (t)", ItemID.STEEL_PLATEBODY_T, "Clue Scroll (Easy)", "Armor", 3),
STEEL_PLATELEGS_T("Steel platelegs (t)", ItemID.STEEL_PLATELEGS_T, "Clue Scroll (Easy)", "Armor", 3),
STEEL_PLATESKIRT_T("Steel plateskirt (t)", ItemID.STEEL_PLATESKIRT_T, "Clue Scroll (Easy)", "Armor", 3),
STEEL_KITESHIELD_T("Steel kiteshield (t)", ItemID.STEEL_KITESHIELD_T, "Clue Scroll (Easy)", "Armor", 3),
STEEL_FULL_HELM_G("Steel full helm (g)", ItemID.STEEL_FULL_HELM_G, "Clue Scroll (Easy)", "Armor", 4),
STEEL_PLATEBODY_G("Steel platebody (g)", ItemID.STEEL_PLATEBODY_G, "Clue Scroll (Easy)", "Armor", 4),
STEEL_PLATELEGS_G("Steel platelegs (g)", ItemID.STEEL_PLATELEGS_G, "Clue Scroll (Easy)", "Armor", 4),
STEEL_PLATESKIRT_G("Steel plateskirt (g)", ItemID.STEEL_PLATESKIRT_G, "Clue Scroll (Easy)", "Armor", 4),
STEEL_KITESHIELD_G("Steel kiteshield (g)", ItemID.STEEL_KITESHIELD_G, "Clue Scroll (Easy)", "Armor", 4),
BLACK_FULL_HELM_T("Black full helm (t)", ItemID.BLACK_FULL_HELM_T, "Clue Scroll (Easy)", "Armor", 5),
BLACK_PLATEBODY_T("Black platebody (t)", ItemID.BLACK_PLATEBODY_T, "Clue Scroll (Easy)", "Armor", 5),
BLACK_PLATELEGS_T("Black platelegs (t)", ItemID.BLACK_PLATELEGS_T, "Clue Scroll (Easy)", "Armor", 5),
BLACK_PLATESKIRT_T("Black plateskirt (t)", ItemID.BLACK_PLATESKIRT_T, "Clue Scroll (Easy)", "Armor", 5),
BLACK_KITESHIELD_T("Black kiteshield (t)", ItemID.BLACK_KITESHIELD_T, "Clue Scroll (Easy)", "Armor", 5),
BLACK_FULL_HELM_G("Black full helm (g)", ItemID.BLACK_FULL_HELM_G, "Clue Scroll (Easy)", "Armor", 6),
BLACK_PLATEBODY_G("Black platebody (g)", ItemID.BLACK_PLATEBODY_G, "Clue Scroll (Easy)", "Armor", 6),
BLACK_PLATELEGS_G("Black platelegs (g)", ItemID.BLACK_PLATELEGS_G, "Clue Scroll (Easy)", "Armor", 6),
BLACK_PLATESKIRT_G("Black plateskirt (g)", ItemID.BLACK_PLATESKIRT_G, "Clue Scroll (Easy)", "Armor", 6),
BLACK_KITESHIELD_G("Black kiteshield (g)", ItemID.BLACK_KITESHIELD_G, "Clue Scroll (Easy)", "Armor", 6),
BLUE_WIZARD_HAT_T("Blue wizard hat (t)", ItemID.BLUE_WIZARD_HAT_T, "Clue Scroll (Easy)", "Wizard Robes", 7),
BLUE_WIZARD_ROBE_T("Blue wizard robe (t)", ItemID.BLUE_WIZARD_ROBE_T, "Clue Scroll (Easy)", "Wizard Robes", 7),
BLUE_SKIRT_T("Blue skirt (t)", ItemID.BLUE_SKIRT_T, "Clue Scroll (Easy)", "Wizard Robes", 7),
BLUE_WIZARD_HAT_G("Blue wizard hat (g)", ItemID.BLUE_WIZARD_HAT_G, "Clue Scroll (Easy)", "Wizard Robes", 8),
BLUE_WIZARD_ROBE_G("Blue wizard robe (g)", ItemID.BLUE_WIZARD_ROBE_G, "Clue Scroll (Easy)", "Wizard Robes", 8),
BLUE_SKIRT_G("Blue skirt (g)", ItemID.BLUE_SKIRT_G, "Clue Scroll (Easy)", "Wizard Robes", 8),
BLACK_WIZARD_HAT_T("Black wizard hat (t)", ItemID.BLACK_WIZARD_HAT_T, "Clue Scroll (Easy)", "Wizard Robes", 9),
BLACK_WIZARD_ROBE_T("Black wizard robe (t)", ItemID.BLACK_WIZARD_ROBE_T, "Clue Scroll (Easy)", "Wizard Robes", 9),
BLACK_SKIRT_T("Black skirt (t)", ItemID.BLACK_SKIRT_T, "Clue Scroll (Easy)", "Wizard Robes", 9),
BLACK_WIZARD_HAT_G("Black wizard hat (g)", ItemID.BLACK_WIZARD_HAT_G, "Clue Scroll (Easy)", "Wizard Robes", 10),
BLACK_WIZARD_ROBE_G("Black wizard robe (g)", ItemID.BLACK_WIZARD_ROBE_G, "Clue Scroll (Easy)", "Wizard Robes", 10),
BLACK_SKIRT_G("Black skirt (g)", ItemID.BLACK_SKIRT_G, "Clue Scroll (Easy)", "Wizard Robes", 10),
STUDDED_BODY_T("Studded body (t)", ItemID.STUDDED_BODY_T, "Clue Scroll (Easy)", "Leather Armor", 11),
STUDDED_CHAPS_T("Studded chaps (t)", ItemID.STUDDED_CHAPS_T, "Clue Scroll (Easy)", "Leather Armor", 11),
STUDDED_BODY_G("Studded body (g)", ItemID.STUDDED_BODY_G, "Clue Scroll (Easy)", "Leather Armor", 11),
STUDDED_CHAPS_G("Studded chaps (g)", ItemID.STUDDED_CHAPS_G, "Clue Scroll (Easy)", "Leather Armor", 11),
LEATHER_CHAPS_G("Leather chaps (g)", ItemID.LEATHER_CHAPS_G, "Clue Scroll (Easy)", "Leather Armor", 12),
LEATHER_BODY_G("Leather chaps (g)", ItemID.LEATHER_BODY_G, "Clue Scroll (Easy)", "Leather Armor", 12),
BLACK_HELM_H1("Black helm (h1)", ItemID.BLACK_HELM_H1, "Clue Scroll (Easy)", "Heralid Helm", 13),
BLACK_HELM_H2("Black helm (h2)", ItemID.BLACK_HELM_H2, "Clue Scroll (Easy)", "Heralid Helm", 13),
BLACK_HELM_H3("Black helm (h3)", ItemID.BLACK_HELM_H3, "Clue Scroll (Easy)", "Heralid Helm", 13),
BLACK_HELM_H4("Black helm (h4)", ItemID.BLACK_HELM_H4, "Clue Scroll (Easy)", "Heralid Helm", 13),
BLACK_HELM_H5("Black helm (h5)", ItemID.BLACK_HELM_H5, "Clue Scroll (Easy)", "Heralid Helm", 13),
BLACK_PLATEBODY_H1("Black platebody (h1)", ItemID.BLACK_PLATEBODY_H1, "Clue Scroll (Easy)", "Heralid Platebody", 14),
BLACK_PLATEBODY_H2("Black platebody (h2)", ItemID.BLACK_PLATEBODY_H2, "Clue Scroll (Easy)", "Heralid Platebody", 14),
BLACK_PLATEBODY_H3("Black platebody (h3)", ItemID.BLACK_PLATEBODY_H3, "Clue Scroll (Easy)", "Heralid Platebody", 14),
BLACK_PLATEBODY_H4("Black platebody (h4)", ItemID.BLACK_PLATEBODY_H4, "Clue Scroll (Easy)", "Heralid Platebody", 14),
BLACK_PLATEBODY_H5("Black platebody (h5)", ItemID.BLACK_PLATEBODY_H5, "Clue Scroll (Easy)", "Heralid Platebody", 14),
BLACK_SHIELD_H1("Black shield (h1)", ItemID.BLACK_SHIELD_H1, "Clue Scroll (Easy)", "Heralid Shield", 15),
BLACK_SHIELD_H2("Black shield (h2)", ItemID.BLACK_SHIELD_H2, "Clue Scroll (Easy)", "Heralid Shield", 15),
BLACK_SHIELD_H3("Black shield (h3)", ItemID.BLACK_SHIELD_H3, "Clue Scroll (Easy)", "Heralid Shield", 15),
BLACK_SHIELD_H4("Black shield (h4)", ItemID.BLACK_SHIELD_H4, "Clue Scroll (Easy)", "Heralid Shield", 15),
BLACK_SHIELD_H5("Black shield (h5)", ItemID.BLACK_SHIELD_H5, "Clue Scroll (Easy)", "Heralid Shield", 15),
BLUE_ELEGANT_SHIRT("Blue elegant shirt", ItemID.BLUE_ELEGANT_SHIRT, "Clue Scroll (Easy)", "Elegant", 16),
BLUE_ELEGANT_LEGS("Blue elegant legs", ItemID.BLUE_ELEGANT_LEGS, "Clue Scroll (Easy)", "Elegant", 16),
BLUE_ELEGANT_BLOUSE("Blue elegant blouse", ItemID.BLUE_ELEGANT_BLOUSE, "Clue Scroll (Easy)", "Elegant", 16),
BLUE_ELEGANT_SKIRT("Blue elegant skirt", ItemID.BLUE_ELEGANT_SKIRT, "Clue Scroll (Easy)", "Elegant", 16),
GREEN_ELEGANT_SHIRT("Green elegant shirt", ItemID.GREEN_ELEGANT_SHIRT, "Clue Scroll (Easy)", "Elegant", 17),
GREEN_ELEGANT_LEGS("Green elegant legs", ItemID.GREEN_ELEGANT_LEGS, "Clue Scroll (Easy)", "Elegant", 17),
GREEN_ELEGANT_BLOUSE("Green elegant blouse", ItemID.GREEN_ELEGANT_BLOUSE, "Clue Scroll (Easy)", "Elegant", 17),
GREEN_ELEGANT_SKIRT("Green elegant skirt", ItemID.GREEN_ELEGANT_SKIRT, "Clue Scroll (Easy)", "Elegant", 17),
RED_ELEGANT_SHIRT("Red elegant shirt", ItemID.RED_ELEGANT_SHIRT, "Clue Scroll (Easy)", "Elegant", 18),
RED_ELEGANT_LEGS("Red elegant legs", ItemID.RED_ELEGANT_LEGS, "Clue Scroll (Easy)", "Elegant", 18),
RED_ELEGANT_BLOUSE("Red elegant blouse", ItemID.RED_ELEGANT_BLOUSE, "Clue Scroll (Easy)", "Elegant", 18),
RED_ELEGANT_SKIRT("Red elegant skirt", ItemID.RED_ELEGANT_SKIRT, "Clue Scroll (Easy)", "Elegant", 18),
BOBS_RED_SHIRT("Bob's red shirt", ItemID.BOBS_RED_SHIRT, "Clue Scroll (Easy)", "Bobs Shirt", 19),
BOBS_BLUE_SHIRT("Bob's blue shirt", ItemID.BOBS_BLUE_SHIRT, "Clue Scroll (Easy)", "Bobs Shirt", 19),
BOBS_GREEN_SHIRT("Bob's green shirt", ItemID.BOBS_GREEN_SHIRT, "Clue Scroll (Easy)", "Bobs Shirt", 19),
BOBS_BLACK_SHIRT("Bob's black shirt", ItemID.BOBS_BLACK_SHIRT, "Clue Scroll (Easy)", "Bobs Shirt", 19),
BOBS_PURPLE_SHIRT("Bob's purple shirt", ItemID.BOBS_PURPLE_SHIRT, "Clue Scroll (Easy)", "Bobs Shirt", 19),
A_POWDERED_WIG("A powdered wig", ItemID.A_POWDERED_WIG, "Clue Scroll (Easy)", "Emote Enhancers", 20),
FLARED_TROUSERS("Flared trousers", ItemID.FLARED_TROUSERS, "Clue Scroll (Easy)", "Emote Enhancers", 20),
PANTALOONS("Pantaloons", ItemID.PANTALOONS, "Clue Scroll (Easy)", "Emote Enhancers", 20),
SLEEPING_CAP("Sleeping cap", ItemID.SLEEPING_CAP, "Clue Scroll (Easy)", "Emote Enhancers", 20),
GUTHIX_ROBE_TOP("Guthix robe top", ItemID.GUTHIX_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 21),
GUTHIX_ROBE_LEGS("Guthix robe legs", ItemID.GUTHIX_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 21),
SARADOMIN_ROBE_TOP("Saradomin robe top", ItemID.SARADOMIN_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 21),
SARADOMIN_ROBE_LEGS("Saradomin robe legs", ItemID.SARADOMIN_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 21),
ZAMORAK_ROBE_TOP("Zamorak robe top", ItemID.ZAMORAK_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 21),
ZAMORAK_ROBE_LEGS("Zamorak robe legs", ItemID.ZAMORAK_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 21),
ANCIENT_ROBE_TOP("Ancient robe top", ItemID.ANCIENT_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 22),
ANCIENT_ROBE_LEGS("Ancient robe legs", ItemID.ANCIENT_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 22),
BANDOS_ROBE_TOP("Bandos robe top", ItemID.BANDOS_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 22),
BANDOS_ROBE_LEGS("Bandos robe legs", ItemID.BANDOS_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 22),
ARMADYL_ROBE_TOP("Armadyl robe top", ItemID.ARMADYL_ROBE_TOP, "Clue Scroll (Easy)", "Vestment Robes", 22),
ARMADYL_ROBE_LEGS("Armadyl robe legs", ItemID.ARMADYL_ROBE_LEGS, "Clue Scroll (Easy)", "Vestment Robes", 22),
BLACK_BERET("Black beret", ItemID.BLACK_BERET, "Clue Scroll (Easy)", "Berets", 23),
BLUE_BERET("Blue beret", ItemID.BLUE_BERET, "Clue Scroll (Easy)", "Berets", 23),
WHITE_BERET("White beret", ItemID.WHITE_BERET, "Clue Scroll (Easy)", "Berets", 23),
RED_BERET("Red beret", ItemID.RED_BERET, "Clue Scroll (Easy)", "Berets", 23),
HIGHWAYMAN_MASK("Highwayman mask", ItemID.HIGHWAYMAN_MASK, "Clue Scroll (Easy)", "Masks", 24),
IMP_MASK("Imp mask", ItemID.IMP_MASK, "Clue Scroll (Easy)", "Masks", 24),
GOBLIN_MASK("Goblin mask", ItemID.GOBLIN_MASK, "Clue Scroll (Easy)", "Masks", 24),
BEANIE("Beanie", ItemID.BEANIE, "Clue Scroll (Easy)", "Masks", 24),
TEAM_CAPE_I("Team cape i", ItemID.TEAM_CAPE_I, "Clue Scroll (Easy)", "Cape", 25),
TEAM_CAPE_X("Team cape x", ItemID.TEAM_CAPE_X, "Clue Scroll (Easy)", "Cape", 25),
TEAM_CAPE_ZERO("Team cape zero", ItemID.TEAM_CAPE_ZERO, "Clue Scroll (Easy)", "Cape", 25),
CAPE_OF_SKULLS("Cape of Skulls", ItemID.CAPE_OF_SKULLS, "Clue Scroll (Easy)", "CApe", 25),
AMULET_OF_MAGIC_T("Amulet of magic (t)", ItemID.AMULET_OF_MAGIC_T, "Clue Scroll (Easy)", "Other", 26),
AMULET_OF_POWER_T("Power Amulet (t)", ItemID.AMULET_OF_POWER_T, "Clue Scroll (Easy)", "Other", 26),
BLACK_CANE("Black cane", ItemID.BLACK_CANE, "Clue Scroll (Easy)", "Other", 26),
BLACK_PICKAXE("Black pickaxe", ItemID.BLACK_PICKAXE, "Clue Scroll (Easy)", "Other", 26),
LARGE_SPADE("Large spade", ItemID.LARGE_SPADE, "Clue Scroll (Easy)", "Other", 26),
WOODEN_SHIELD_G("Wooden shield (g)", ItemID.WOODEN_SHIELD_G, "Clue Scroll (Easy)", "Golden", 27),
GOLDEN_CHEFS_HAT("Golden chef's hat", ItemID.GOLDEN_CHEFS_HAT, "Clue Scroll (Easy)", "Golden", 27),
GOLDEN_APRON("Golden apron", ItemID.GOLDEN_APRON, "Clue Scroll (Easy)", "Golden", 27),
MONKS_ROBE_TOP_G("Monk robe top (g)", ItemID.MONKS_ROBE_TOP_G, "Clue Scroll (Easy)", "Golden", 27),
MONKS_ROBE_G("Monk robe (g)", ItemID.MONKS_ROBE_G, "Clue Scroll (Easy)", "Golden", 27),
RAIN_BOW("Rain bow", ItemID.RAIN_BOW, "Clue Scroll (Easy)", "Other", 28),
HAM_JOINT("Ham joint", ItemID.HAM_JOINT, "Clue Scroll (Easy)", "Other", 28),
STAFF_OF_BOB_THE_CAT("Staff of Bob the Cat", ItemID.STAFF_OF_BOB_THE_CAT, "Clue Scroll (Easy)", "Other", 28),
// Medium Clue Scrolls
MITHRIL_FULL_HELM_T("Mithril full helm (t)", ItemID.MITHRIL_FULL_HELM_T, "Clue Scroll (Medium)", "Armor", -1),
MITHRIL_PLATEBODY_T("Mithril platebody (t)", ItemID.MITHRIL_PLATEBODY_T, "Clue Scroll (Medium)", "Armor", -1),
MITHRIL_PLATELEGS_T("Mithril platelegs (t)", ItemID.MITHRIL_PLATELEGS_T, "Clue Scroll (Medium)", "Armor", -1),
MITHRIL_PLATESKIRT_T("Mithril plateskirt (t)", ItemID.MITHRIL_PLATESKIRT_T, "Clue Scroll (Medium)", "Armor", -1),
MITHRIL_KITESHIELD_T("Mithril kiteshield (t)", ItemID.MITHRIL_KITESHIELD_T, "Clue Scroll (Medium)", "Armor", -1),
MITHRIL_FULL_HELM_G("Mithril full helm (g)", ItemID.MITHRIL_FULL_HELM_G, "Clue Scroll (Medium)", "Armor", 0),
MITHRIL_PLATEBODY_G("Mithril platebody (g)", ItemID.MITHRIL_PLATEBODY_G, "Clue Scroll (Medium)", "Armor", 0),
MITHRIL_PLATELEGS_G("Mithril platelegs (g)", ItemID.MITHRIL_PLATELEGS_G, "Clue Scroll (Medium)", "Armor", 0),
MITHRIL_PLATESKIRT_G("Mithril plateskirt (g)", ItemID.MITHRIL_PLATESKIRT_G, "Clue Scroll (Medium)", "Armor", 0),
MITHRIL_KITESHIELD_G("Mithril kiteshield (g)", ItemID.MITHRIL_KITESHIELD_G, "Clue Scroll (Medium)", "Armor", 0),
ADAMANT_FULL_HELM_T("Adamant full helm (t)", ItemID.ADAMANT_FULL_HELM_T, "Clue Scroll (Medium)", "Armor", 1),
ADAMANT_PLATEBODY_T("Adamant platebody (t)", ItemID.ADAMANT_PLATEBODY_T, "Clue Scroll (Medium)", "Armor", 1),
ADAMANT_PLATELEGS_T("Adamant platelegs (t)", ItemID.ADAMANT_PLATELEGS_T, "Clue Scroll (Medium)", "Armor", 1),
ADAMANT_PLATESKIRT_T("Adamant plateskirt (t)", ItemID.ADAMANT_PLATESKIRT_T, "Clue Scroll (Medium)", "Armor", 1),
ADAMANT_KITESHIELD_T("Adamant kiteshield (t)", ItemID.ADAMANT_KITESHIELD_T, "Clue Scroll (Medium)", "Armor", 1),
ADAMANT_FULL_HELM_G("Adamant full helm (g)", ItemID.ADAMANT_FULL_HELM_G, "Clue Scroll (Medium)", "Armor", 2),
ADAMANT_PLATEBODY_G("Adamant platebody (g)", ItemID.ADAMANT_PLATEBODY_G, "Clue Scroll (Medium)", "Armor", 2),
ADAMANT_PLATELEGS_G("Adamant platelegs (g)", ItemID.ADAMANT_PLATELEGS_G, "Clue Scroll (Medium)", "Armor", 2),
ADAMANT_PLATESKIRT_G("Adamant plateskirt (g)", ItemID.ADAMANT_PLATESKIRT_G, "Clue Scroll (Medium)", "Armor", 2),
ADAMANT_KITESHIELD_G("Adamant kiteshield (g)", ItemID.ADAMANT_KITESHIELD_G, "Clue Scroll (Medium)", "Armor", 2),
RANGER_BOOTS("Ranger boots", ItemID.RANGER_BOOTS, "Clue Scroll (Medium)", "Boots", 3),
HOLY_SANDALS("Holy sandals", ItemID.HOLY_SANDALS, "Clue Scroll (Medium)", "Boots", 3),
WIZARD_BOOTS("Wizard boots", ItemID.WIZARD_BOOTS, "Clue Scroll (Medium)", "Boots", 3),
RED_HEADBAND("Red headband", ItemID.RED_HEADBAND, "Clue Scroll (Medium)", "Headbands", 4),
BLACK_HEADBAND("Black headband", ItemID.BLACK_HEADBAND, "Clue Scroll (Medium)", "Headbands", 4),
BROWN_HEADBAND("Brown headband", ItemID.BROWN_HEADBAND, "Clue Scroll (Medium)", "Headbands", 4),
PINK_HEADBAND("Pink headband", ItemID.PINK_HEADBAND, "Clue Scroll (Medium)", "Headbands", 4),
GREEN_HEADBAND("Green headband", ItemID.GREEN_HEADBAND, "Clue Scroll (Medium)", "Headbands", 5),
BLUE_HEADBAND("Blue headband", ItemID.BLUE_HEADBAND, "Clue Scroll (Medium)", "Headbands", 5),
GOLD_HEADBAND("Gold headband", ItemID.GOLD_HEADBAND, "Clue Scroll (Medium)", "Headbands", 5),
WHITE_HEADBAND("White headband", ItemID.WHITE_HEADBAND, "Clue Scroll (Medium)", "Headbands", 5),
RED_BOATER("Red boater", ItemID.RED_BOATER, "Clue Scroll (Medium)", "Boater", 6),
ORANGE_BOATER("Orange boater", ItemID.ORANGE_BOATER, "Clue Scroll (Medium)", "Boater", 6),
GREEN_BOATER("Green boater", ItemID.GREEN_BOATER, "Clue Scroll (Medium)", "Boater", 6),
BLUE_BOATER("Blue boater", ItemID.BLUE_BOATER, "Clue Scroll (Medium)", "Boater", 6),
BLACK_BOATER("Black boater", ItemID.BLACK_BOATER, "Clue Scroll (Medium)", "Boater", 7),
PINK_BOATER("Pink boater", ItemID.PINK_BOATER, "Clue Scroll (Medium)", "Boater", 7),
PURPLE_BOATER("Purple boater", ItemID.PURPLE_BOATER, "Clue Scroll (Medium)", "Boater", 7),
WHITE_BOATER("White boater", ItemID.WHITE_BOATER, "Clue Scroll (Medium)", "Boater", 7),
ADAMANT_HELM_H1("Adamant helm (h1)", ItemID.ADAMANT_HELM_H1, "Clue Scroll (Medium)", "Heraldic", 8),
ADAMANT_HELM_H2("Adamant helm (h2)", ItemID.ADAMANT_HELM_H2, "Clue Scroll (Medium)", "Heraldic", 8),
ADAMANT_HELM_H3("Adamant helm (h3)", ItemID.ADAMANT_HELM_H3, "Clue Scroll (Medium)", "Heraldic", 8),
ADAMANT_HELM_H4("Adamant helm (h4)", ItemID.ADAMANT_HELM_H4, "Clue Scroll (Medium)", "Heraldic", 8),
ADAMANT_HELM_H5("Adamant helm (h5)", ItemID.ADAMANT_HELM_H5, "Clue Scroll (Medium)", "Heraldic", 8),
ADAMANT_SHIELD_H1("Adamant shield (h1)", ItemID.ADAMANT_SHIELD_H1, "Clue Scroll (Medium)", "Heraldic", 9),
ADAMANT_SHIELD_H2("Adamant shield (h2)", ItemID.ADAMANT_SHIELD_H2, "Clue Scroll (Medium)", "Heraldic", 9),
ADAMANT_SHIELD_H3("Adamant shield (h3)", ItemID.ADAMANT_SHIELD_H3, "Clue Scroll (Medium)", "Heraldic", 9),
ADAMANT_SHIELD_H4("Adamant shield (h4)", ItemID.ADAMANT_SHIELD_H4, "Clue Scroll (Medium)", "Heraldic", 9),
ADAMANT_SHIELD_H5("Adamant shield (h5)", ItemID.ADAMANT_SHIELD_H5, "Clue Scroll (Medium)", "Heraldic", 9),
ADAMANT_PLATEBODY_H1("Adamant platebody (h1)", ItemID.ADAMANT_PLATEBODY_H1, "Clue Scroll (Medium)", "Armor", 10),
ADAMANT_PLATEBODY_H2("Adamant platebody (h2)", ItemID.ADAMANT_PLATEBODY_H2, "Clue Scroll (Medium)", "Armor", 10),
ADAMANT_PLATEBODY_H3("Adamant platebody (h3)", ItemID.ADAMANT_PLATEBODY_H3, "Clue Scroll (Medium)", "Armor", 10),
ADAMANT_PLATEBODY_H4("Adamant platebody (h4)", ItemID.ADAMANT_PLATEBODY_H4, "Clue Scroll (Medium)", "Armor", 10),
ADAMANT_PLATEBODY_H5("Adamant platebody (h5)", ItemID.ADAMANT_PLATEBODY_H5, "Clue Scroll (Medium)", "Armor", 10),
GREEN_DHIDE_BODY_T("Green dragonhide body (t)", ItemID.GREEN_DHIDE_BODY_T, "Clue Scroll (Medium)", "Dhide", 11),
GREEN_DHIDE_CHAPS_T("Green dragonhide chaps (t)", ItemID.GREEN_DHIDE_CHAPS_T, "Clue Scroll (Medium)", "Dhide", 11),
GREEN_DHIDE_BODY_G("Green dragonhide body (g)", ItemID.GREEN_DHIDE_BODY_G, "Clue Scroll (Medium)", "Dhide", 11),
GREEN_DHIDE_CHAPS_G("Green dragonhide chaps (g)", ItemID.GREEN_DHIDE_CHAPS_G, "Clue Scroll (Medium)", "Dhide", 11),
BLACK_ELEGANT_SHIRT("Black elegant shirt", ItemID.BLACK_ELEGANT_SHIRT, "Clue Scroll (Medium)", "Elegeant", 12),
BLACK_ELEGANT_LEGS("Black elegant legs", ItemID.BLACK_ELEGANT_LEGS, "Clue Scroll (Medium)", "Elegeant", 12),
WHITE_ELEGANT_BLOUSE("White elegant blouse", ItemID.WHITE_ELEGANT_BLOUSE, "Clue Scroll (Medium)", "Elegeant", 12),
WHITE_ELEGANT_SKIRT("White elegant skirt", ItemID.WHITE_ELEGANT_SKIRT, "Clue Scroll (Medium)", "Elegeant", 12),
PURPLE_ELEGANT_SHIRT("Purple elegant shirt", ItemID.PURPLE_ELEGANT_SHIRT, "Clue Scroll (Medium)", "Elegeant", 13),
PURPLE_ELEGANT_LEGS("Purple elegant legs", ItemID.PURPLE_ELEGANT_LEGS, "Clue Scroll (Medium)", "Elegeant", 13),
PURPLE_ELEGANT_BLOUSE("Purple elegant blouse", ItemID.PURPLE_ELEGANT_BLOUSE, "Clue Scroll (Medium)", "Elegeant", 13),
PURPLE_ELEGANT_SKIRT("Purple elegant skirt", ItemID.PURPLE_ELEGANT_SKIRT, "Clue Scroll (Medium)", "Elegeant", 13),
PINK_ELEGANT_SHIRT("Pink elegant shirt", ItemID.PINK_ELEGANT_SHIRT, "Clue Scroll (Medium)", "Elegeant", 14),
PINK_ELEGANT_LEGS("Pink elegant legs", ItemID.PINK_ELEGANT_LEGS, "Clue Scroll (Medium)", "Elegeant", 14),
PINK_ELEGANT_BLOUSE("Pink elegant blouse", ItemID.PINK_ELEGANT_BLOUSE, "Clue Scroll (Medium)", "Elegeant", 14),
PINK_ELEGANT_SKIRT("Pink elegant skirt", ItemID.PINK_ELEGANT_SKIRT, "Clue Scroll (Medium)", "Elegeant", 14),
GOLD_ELEGANT_SHIRT("Gold elegant shirt", ItemID.GOLD_ELEGANT_SHIRT, "Clue Scroll (Medium)", "Elegeant", 15),
GOLD_ELEGANT_LEGS("Gold elegant legs", ItemID.GOLD_ELEGANT_LEGS, "Clue Scroll (Medium)", "Elegeant", 15),
GOLD_ELEGANT_BLOUSE("Gold elegant blouse", ItemID.GOLD_ELEGANT_BLOUSE, "Clue Scroll (Medium)", "Elegeant", 15),
GOLD_ELEGANT_SKIRT("Gold elegant skirt", ItemID.GOLD_ELEGANT_SKIRT, "Clue Scroll (Medium)", "Elegeant", 15),
GUTHIX_MITRE("Guthix mitre", ItemID.GUTHIX_MITRE, "Clue Scroll (Medium)", "Vestment", 16),
GUTHIX_CLOAK("Guthix cloak", ItemID.GUTHIX_CLOAK, "Clue Scroll (Medium)", "Vestment", 16),
SARADOMIN_MITRE("Saradomin mitre", ItemID.SARADOMIN_MITRE, "Clue Scroll (Medium)", "Vestment", 16),
SARADOMIN_CLOAK("Saradomin cloak", ItemID.SARADOMIN_CLOAK, "Clue Scroll (Medium)", "Vestment", 16),
ZAMORAK_MITRE("Zamorak mitre", ItemID.ZAMORAK_MITRE, "Clue Scroll (Medium)", "Vestment", 16),
ZAMORAK_CLOAK("Zamorak cloak", ItemID.ZAMORAK_CLOAK, "Clue Scroll (Medium)", "Vestment", 16),
ANCIENT_MITRE("Anicent mitre", ItemID.ANCIENT_MITRE, "Clue Scroll (Medium)", "Vestment", 17),
ANCIENT_CLOAK("Anicent cloak", ItemID.ANCIENT_CLOAK, "Clue Scroll (Medium)", "Vestment", 17),
BANDOS_MITRE("Bandos mitre", ItemID.BANDOS_MITRE, "Clue Scroll (Medium)", "Vestment", 17),
BANDOS_CLOAK("Bandos cloak", ItemID.BANDOS_CLOAK, "Clue Scroll (Medium)", "Vestment", 17),
ARMADYL_MITRE("Armadyl mitre", ItemID.ARMADYL_MITRE, "Clue Scroll (Medium)", "Vestment", 17),
ARMADYL_CLOAK("Armadyl cloak", ItemID.ARMADYL_CLOAK, "Clue Scroll (Medium)", "Vestment", 17),
ARMADYL_STOLE("Armadyl stole", ItemID.ARMADYL_STOLE, "Clue Scroll (Medium)", "Vestment", 18),
ARMADYL_CROZIER("Armadyl crozier", ItemID.ARMADYL_CROZIER, "Clue Scroll (Medium)", "Vestment", 18),
ANCIENT_STOLE("Anicent stole", ItemID.ANCIENT_STOLE, "Clue Scroll (Medium)", "Vestment", 18),
ANCIENT_CROZIER("Anicent crozier", ItemID.ANCIENT_CROZIER, "Clue Scroll (Medium)", "Vestment", 18),
BANDOS_STOLE("Bandos stole", ItemID.BANDOS_STOLE, "Clue Scroll (Medium)", "Vestment", 18),
BANDOS_CROZIER("Bandos crozier", ItemID.BANDOS_CROZIER, "Clue Scroll (Medium)", "Vestment", 18),
CAT_MASK("Cat mask", ItemID.CAT_MASK, "Clue Scroll (Medium)", "Masks", 19),
PENGUIN_MASK("Penguin mask", ItemID.PENGUIN_MASK, "Clue Scroll (Medium)", "Masks", 19),
BLACK_UNICORN_MASK("Black unicorn mask", ItemID.BLACK_UNICORN_MASK, "Clue Scroll (Medium)", "Masks", 19),
WHITE_UNICORN_MASK("White unicorn mask", ItemID.WHITE_UNICORN_MASK, "Clue Scroll (Medium)", "Masks", 19),
LEPRECHAUN_HAT("Leprechaun hat", ItemID.LEPRECHAUN_HAT, "Clue Scroll (Medium)", "Masks", 19),
BLACK_LEPRECHAUN_HAT("Black leprechaun hat", ItemID.BLACK_LEPRECHAUN_HAT, "Clue Scroll (Medium)", "Masks", 19),
CRIER_HAT("Crier hat", ItemID.CRIER_HAT, "Clue Scroll (Medium)", "Crier", 20),
CRIER_BELL("Crier bell", ItemID.CRIER_BELL, "Clue Scroll (Medium)", "Crier", 20),
CRIER_COAT("Crier coat", ItemID.CRIER_COAT, "Clue Scroll (Medium)", "Crier", 20),
ARCEUUS_BANNER("Arceeus banner", ItemID.ARCEUUS_BANNER, "Clue Scroll (Medium)", "Zeah Banners", 21),
HOSIDIUS_BANNER("Hosidius banner", ItemID.HOSIDIUS_BANNER, "Clue Scroll (Medium)", "Zeah Banners", 21),
LOVAKENGJ_BANNER("Lovakengj banner", ItemID.LOVAKENGJ_BANNER, "Clue Scroll (Medium)", "Zeah Banners", 21),
PISCARILIUS_BANNER("Picarilius banner", ItemID.PISCARILIUS_BANNER, "Clue Scroll (Medium)", "Zeah Banners", 21),
SHAYZIEN_BANNER("Shayzien banner", ItemID.SHAYZIEN_BANNER, "Clue Scroll (Medium)", "Zeah Banners", 21),
STRENGTH_AMULET_T("Strength amulet (t)", ItemID.STRENGTH_AMULET_T, "Clue Scroll (Medium)", "Other", 22),
ADAMANT_CANE("Adamant cane", ItemID.ADAMANT_CANE, "Clue Scroll (Medium)", "Other", 22),
GNOMISH_FIRELIGHTER("Gnomish firelighter", ItemID.GNOMISH_FIRELIGHTER, "Clue Scroll (Medium)", "Other", 22),
CABBAGE_ROUND_SHIELD("Cabbage round shield", ItemID.CABBAGE_ROUND_SHIELD, "Clue Scroll (Medium)", "Other", 22),
CLUELESS_SCROLL("Clueless scroll", ItemID.CLUELESS_SCROLL, "Clue Scroll (Medium)", "Other", 22),
SPIKED_MANACLES("Spiked manacles", ItemID.SPIKED_MANACLES, "Clue Scroll (Medium)", "Other", 23),
WOLF_MASK("Wolf mask", ItemID.WOLF_MASK, "Clue Scroll (Medium)", "Other", 23),
WOLF_CLOAK("Wolf cloak", ItemID.WOLF_CLOAK, "Clue Scroll (Medium)", "Other", 23),
CLIMBING_BOOTS_G("Climbing boots (g)", ItemID.CLIMBING_BOOTS_G, "Clue Scroll (Medium)", "Other", 23),
// Hard Clue Scrolls
RUNE_FULL_HELM_T("Rune full helm (t)", ItemID.RUNE_FULL_HELM_T, "Clue Scroll (Hard)", "Armor", -1),
RUNE_PLATEBODY_T("Rune platebody (t)", ItemID.RUNE_PLATEBODY_T, "Clue Scroll (Hard)", "Armor", -1),
RUNE_PLATELEGS_T("Rune platelegs (t)", ItemID.RUNE_PLATELEGS_T, "Clue Scroll (Hard)", "Armor", -1),
RUNE_PLATESKIRT_T("Rune plateskirt (t)", ItemID.RUNE_PLATESKIRT_T, "Clue Scroll (Hard)", "Armor", -1),
RUNE_KITESHIELD_T("Rune kiteshield (t)", ItemID.RUNE_KITESHIELD_T, "Clue Scroll (Hard)", "Armor", -1),
RUNE_FULL_HELM_G("Rune full helm (g)", ItemID.RUNE_FULL_HELM_G, "Clue Scroll (Hard)", "Armor", 0),
RUNE_PLATEBODY_G("Rune platebody (g)", ItemID.RUNE_PLATEBODY_G, "Clue Scroll (Hard)", "Armor", 0),
RUNE_PLATELEGS_G("Rune platelegs (g)", ItemID.RUNE_PLATELEGS_G, "Clue Scroll (Hard)", "Armor", 0),
RUNE_PLATESKIRT_G("Rune plateskirt (g)", ItemID.RUNE_PLATESKIRT_G, "Clue Scroll (Hard)", "Armor", 0),
RUNE_KITESHIELD_G("Rune kiteshield (g)", ItemID.RUNE_KITESHIELD_G, "Clue Scroll (Hard)", "Armor", 0),
GUTHIX_FULL_HELM("Guthix full helm", ItemID.GUTHIX_FULL_HELM, "Clue Scroll (Hard)", "Armor", 1),
GUTHIX_PLATEBODY("Guthix platebody", ItemID.GUTHIX_PLATEBODY, "Clue Scroll (Hard)", "Armor", 1),
GUTHIX_PLATELEGS("Guthix platelegs", ItemID.GUTHIX_PLATELEGS, "Clue Scroll (Hard)", "Armor", 1),
GUTHIX_PLATESKIRT("Guthix plateskirt", ItemID.GUTHIX_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 1),
GUTHIX_KITESHIELD("Guthix kiteshield", ItemID.GUTHIX_KITESHIELD, "Clue Scroll (Hard)", "Armor", 1),
SARADOMIN_FULL_HELM("Saradomin full helm", ItemID.SARADOMIN_FULL_HELM, "Clue Scroll (Hard)", "Armor", 2),
SARADOMIN_PLATEBODY("Saradomin platebody", ItemID.SARADOMIN_PLATEBODY, "Clue Scroll (Hard)", "Armor", 2),
SARADOMIN_PLATELEGS("Saradomin platelegs", ItemID.SARADOMIN_PLATELEGS, "Clue Scroll (Hard)", "Armor", 2),
SARADOMIN_PLATESKIRT("Saradomin plateskirt", ItemID.SARADOMIN_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 2),
SARADOMIN_KITESHIELD("Saradomin kiteshield", ItemID.SARADOMIN_KITESHIELD, "Clue Scroll (Hard)", "Armor", 2),
ZAMORAK_FULL_HELM("Zamorak full helm", ItemID.ZAMORAK_FULL_HELM, "Clue Scroll (Hard)", "Armor", 3),
ZAMORAK_PLATEBODY("Zamorak platebody", ItemID.ZAMORAK_PLATEBODY, "Clue Scroll (Hard)", "Armor", 3),
ZAMORAK_PLATELEGS("Zamorak platelegs", ItemID.ZAMORAK_PLATELEGS, "Clue Scroll (Hard)", "Armor", 3),
ZAMORAK_PLATESKIRT("Zamorak plateskirt", ItemID.ZAMORAK_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 3),
ZAMORAK_KITESHIELD("Zamorak kiteshield", ItemID.ZAMORAK_KITESHIELD, "Clue Scroll (Hard)", "Armor", 3),
ANCIENT_FULL_HELM("Ancient full helm", ItemID.ANCIENT_FULL_HELM, "Clue Scroll (Hard)", "Armor", 4),
ANCIENT_PLATEBODY("Ancient platebody", ItemID.ANCIENT_PLATEBODY, "Clue Scroll (Hard)", "Armor", 4),
ANCIENT_PLATELEGS("Ancient platelegs", ItemID.ANCIENT_PLATELEGS, "Clue Scroll (Hard)", "Armor", 4),
ANCIENT_PLATESKIRT("Ancient plateskirt", ItemID.ANCIENT_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 4),
ANCIENT_KITESHIELD("Ancient kiteshield", ItemID.ANCIENT_KITESHIELD, "Clue Scroll (Hard)", "Armor", 4),
BANDOS_FULL_HELM("Bandos full helm", ItemID.BANDOS_FULL_HELM, "Clue Scroll (Hard)", "Armor", 5),
BANDOS_PLATEBODY("Bandos platebody", ItemID.BANDOS_PLATEBODY, "Clue Scroll (Hard)", "Armor", 5),
BANDOS_PLATELEGS("Bandos platelegs", ItemID.BANDOS_PLATELEGS, "Clue Scroll (Hard)", "Armor", 5),
BANDOS_PLATESKIRT("Bandos plateskirt", ItemID.BANDOS_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 5),
BANDOS_KITESHIELD("Bandos kiteshield", ItemID.BANDOS_KITESHIELD, "Clue Scroll (Hard)", "Armor", 5),
ARMADYL_FULL_HELM("Armadyl full helm", ItemID.ARMADYL_FULL_HELM, "Clue Scroll (Hard)", "Armor", 6),
ARMADYL_PLATEBODY("Armadyl platebody", ItemID.ARMADYL_PLATEBODY, "Clue Scroll (Hard)", "Armor", 6),
ARMADYL_PLATELEGS("Armadyl platelegs", ItemID.ARMADYL_PLATELEGS, "Clue Scroll (Hard)", "Armor", 6),
ARMADYL_PLATESKIRT("Armadyl plateskirt", ItemID.ARMADYL_PLATESKIRT, "Clue Scroll (Hard)", "Armor", 6),
ARMADYL_KITESHIELD("Armadyl kiteshield", ItemID.ARMADYL_KITESHIELD, "Clue Scroll (Hard)", "Armor", 6),
GILDED_FULL_HELM("Gilded full helm", ItemID.GILDED_FULL_HELM, "Clue Scroll (Hard)", "Gilded", 7),
GILDED_PLATEBODY("Gilded platebody", ItemID.GILDED_PLATEBODY, "Clue Scroll (Hard)", "Gilded", 7),
GILDED_PLATELEGS("Gilded platelegs", ItemID.GILDED_PLATELEGS, "Clue Scroll (Hard)", "Gilded", 7),
GILDED_PLATESKIRT("Gilded plateskirt", ItemID.GILDED_PLATESKIRT, "Clue Scroll (Hard)", "Gilded", 7),
GILDED_KITESHIELD("Gilded kiteshield", ItemID.GILDED_KITESHIELD, "Clue Scroll (Hard)", "Gilded", 7),
GILDED_MED_HELM("Gilded med helm", ItemID.GILDED_MED_HELM, "Clue Scroll (Hard)", "Gilded", 8),
GILDED_CHAINBODY("Gilded chainbody", ItemID.GILDED_CHAINBODY, "Clue Scroll (Hard)", "Gilded", 8),
GILDED_SQ_SHIELD("Gilded sq shield", ItemID.GILDED_SQ_SHIELD, "Clue Scroll (Hard)", "Gilded", 8),
GILDED_2H_SWORD("Gilded 2H sword", ItemID.GILDED_2H_SWORD, "Clue Scroll (Hard)", "Gilded", 8),
GILDED_SPEAR("Gilded spear", ItemID.GILDED_SPEAR, "Clue Scroll (Hard)", "Gilded", 8),
GILDED_HASTA("Gilded hasta", ItemID.GILDED_HASTA, "Clue Scroll (Hard)", "Gilded", 8),
RUNE_HELM_H1("Rune helm (h1)", ItemID.RUNE_HELM_H1, "Clue Scroll (Hard)", "Heraldic", 9),
RUNE_HELM_H2("Rune helm (h2)", ItemID.RUNE_HELM_H2, "Clue Scroll (Hard)", "Heraldic", 9),
RUNE_HELM_H3("Rune helm (h3)", ItemID.RUNE_HELM_H3, "Clue Scroll (Hard)", "Heraldic", 9),
RUNE_HELM_H4("Rune helm (h4)", ItemID.RUNE_HELM_H4, "Clue Scroll (Hard)", "Heraldic", 9),
RUNE_HELM_H5("Rune helm (h5)", ItemID.RUNE_HELM_H5, "Clue Scroll (Hard)", "Heraldic", 9),
RUNE_SHIELD_H1("Rune shield (h1)", ItemID.RUNE_SHIELD_H1, "Clue Scroll (Hard)", "Heraldic", 10),
RUNE_SHIELD_H2("Rune shield (h2)", ItemID.RUNE_SHIELD_H2, "Clue Scroll (Hard)", "Heraldic", 10),
RUNE_SHIELD_H3("Rune shield (h3)", ItemID.RUNE_SHIELD_H3, "Clue Scroll (Hard)", "Heraldic", 10),
RUNE_SHIELD_H4("Rune shield (h4)", ItemID.RUNE_SHIELD_H4, "Clue Scroll (Hard)", "Heraldic", 10),
RUNE_SHIELD_H5("Rune shield (h5)", ItemID.RUNE_SHIELD_H5, "Clue Scroll (Hard)", "Heraldic", 10),
RUNE_PLATEBODY_H1("Rune platebody (h1)", ItemID.RUNE_PLATEBODY_H1, "Clue Scroll (Hard)", "Heraldic", 11),
RUNE_PLATEBODY_H2("Rune platebody (h2)", ItemID.RUNE_PLATEBODY_H2, "Clue Scroll (Hard)", "Heraldic", 11),
RUNE_PLATEBODY_H3("Rune platebody (h3)", ItemID.RUNE_PLATEBODY_H3, "Clue Scroll (Hard)", "Heraldic", 11),
RUNE_PLATEBODY_H4("Rune platebody (h4)", ItemID.RUNE_PLATEBODY_H4, "Clue Scroll (Hard)", "Heraldic", 11),
RUNE_PLATEBODY_H5 ("Rune platebody (h5)", ItemID.RUNE_PLATEBODY_H5, "Clue Scroll (Hard)", "Heraldic", 11),
BLUE_DHIDE_BODY_T("Blue dhide body (t)", ItemID.BLUE_DHIDE_BODY_T, "Clue Scroll (Hard)", "Dhide", 12),
BLUE_DHIDE_CHAPS_T("Blue dhide chaps (t)", ItemID.BLUE_DHIDE_CHAPS_T, "Clue Scroll (Hard)", "Dhide", 12),
BLUE_DHIDE_BODY_G("Blue dhide body (g)", ItemID.BLUE_DHIDE_BODY_G, "Clue Scroll (Hard)", "Dhide", 12),
BLUE_DHIDE_CHAPS_G("Blue dhide chaps (g)", ItemID.BLUE_DHIDE_CHAPS_G, "Clue Scroll (Hard)", "Dhide", 12),
RED_DHIDE_BODY_T("Red dhide body (t)", ItemID.RED_DHIDE_BODY_T, "Clue Scroll (Hard)", "Dhide", 13),
RED_DHIDE_CHAPS_T("Red dhide chaps (t)", ItemID.RED_DHIDE_CHAPS_T, "Clue Scroll (Hard)", "Dhide", 13),
RED_DHIDE_BODY_G("Red dhide body (g)", ItemID.RED_DHIDE_BODY_G, "Clue Scroll (Hard)", "Dhide", 13),
RED_DHIDE_CHAPS_G("Red dhide chaps (g)", ItemID.RED_DHIDE_CHAPS_G, "Clue Scroll (Hard)", "Dhide", 13),
ENCHANTED_HAT("Enchanted hat", ItemID.ENCHANTED_HAT, "Clue Scroll (Hard)", "Enchanted", 14),
ENCHANTED_TOP("Enchanted top", ItemID.ENCHANTED_TOP, "Clue Scroll (Hard)", "Enchanted", 14),
ENCHANTED_ROBE("Enchanted robe", ItemID.ENCHANTED_ROBE, "Clue Scroll (Hard)", "Enchanted", 14),
AMULET_OF_GLORY_T("Amulet of glory (t)", ItemID.AMULET_OF_GLORY_T, "Clue Scroll (Hard)", "Enchanted", 14),
ROBIN_HOOD_HAT("Robin hood hat", ItemID.ROBIN_HOOD_HAT, "Clue Scroll (Hard)", "Hats", 15),
PIRATE_HAT("Pirate hat", ItemID.PIRATE_HAT, "Clue Scroll (Hard)", "Hats", 15),
PITH_HELMET("Pith helmet", ItemID.PITH_HELMET, "Clue Scroll (Hard)", "Hats", 15),
EXPLORER_BACKPACK("Explorer backpack", ItemID.EXPLORER_BACKPACK, "Clue Scroll (Hard)", "Hats", 15),
RED_CAVALIER("Red cavalier", ItemID.RED_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
TAN_CAVALIER("Tan cavalier", ItemID.TAN_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
DARK_CAVALIER("Dark cavalier", ItemID.DARK_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
BLACK_CAVALIER("Black cavalier", ItemID.BLACK_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
NAVY_CAVALIER("Navy cavalier", ItemID.NAVY_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
WHITE_CAVALIER("White cavalier", ItemID.WHITE_CAVALIER, "Clue Scroll (Hard)", "Cavalier", 16),
_3RD_AGE_FULL_HELMET("3rd age full helm", ItemID._3RD_AGE_FULL_HELMET, "Clue Scroll (Hard)", "Third age", 17),
_3RD_AGE_PLATEBODY("3rd age platebody", ItemID._3RD_AGE_PLATEBODY, "Clue Scroll (Hard)", "Third age", 17),
_3RD_AGE_PLATELEGS("3rd age platelegs", ItemID._3RD_AGE_PLATELEGS, "Clue Scroll (Hard)", "Third age", 17),
_3RD_AGE_PLATESKIRT("3rd age plateskirt", ItemID._3RD_AGE_PLATESKIRT, "Clue Scroll (Hard)", "Third age", 17),
_3RD_AGE_KITESHIELD("3rd age kiteshield", ItemID._3RD_AGE_KITESHIELD, "Clue Scroll (Hard)", "Third age", 17),
_3RD_AGE_RANGE_COIF("3rd age range coif", ItemID._3RD_AGE_RANGE_COIF, "Clue Scroll (Hard)", "Third age", 18),
_3RD_AGE_RANGE_TOP("3rd age range top", ItemID._3RD_AGE_RANGE_TOP, "Clue Scroll (Hard)", "Third age", 18),
_3RD_AGE_RANGE_LEGS("3rd age range legs", ItemID._3RD_AGE_RANGE_LEGS, "Clue Scroll (Hard)", "Third age", 18),
_3RD_AGE_VAMBRACES("3rd age vambraces", ItemID._3RD_AGE_VAMBRACES, "Clue Scroll (Hard)", "Third age", 18),
_3RD_AGE_MAGE_HAT("3rd age mage hat", ItemID._3RD_AGE_MAGE_HAT, "Clue Scroll (Hard)", "Third age", 19),
_3RD_AGE_ROBE_TOP("3rd age robe top", ItemID._3RD_AGE_ROBE_TOP, "Clue Scroll (Hard)", "Third age", 19),
_3RD_AGE_ROBE("3rd age robe", ItemID._3RD_AGE_ROBE, "Clue Scroll (Hard)", "Third age", 19),
_3RD_AGE_AMULET("3rd age amulet", ItemID._3RD_AGE_AMULET, "Clue Scroll (Hard)", "Third age", 19),
GUTHIX_COIF("Guthix coif", ItemID.GUTHIX_COIF, "Clue Scroll (Hard)", "God Dhide", 20),
GUTHIX_DHIDE("Guthix dragonhide", ItemID.GUTHIX_DRAGONHIDE, "Clue Scroll (Hard)", "God Dhide", 20),
GUTHIX_CHAPS("Guthix chaps", ItemID.GUTHIX_CHAPS, "Clue Scroll (Hard)", "God Dhide", 20),
GUTHIX_BRACERS("Guthix bracers", ItemID.GUTHIX_BRACERS, "Clue Scroll (Hard)", "God Dhide", 20),
GUTHIX_DHIDE_BOOTS("Guthix dhide boots", ItemID.GUTHIX_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 20),
GUTHIX_DHIDE_SHIELD("Guthix dhide shield", ItemID.GUTHIX_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 20),
SARADOMIN_COIF("Saradomin coif", ItemID.SARADOMIN_COIF, "Clue Scroll (Hard)", "God Dhide", 21),
SARADOMIN_DHIDE("Saradomin dragonhide", ItemID.SARADOMIN_DHIDE, "Clue Scroll (Hard)", "God Dhide", 21),
SARADOMIN_CHAPS("Saradomin chaps", ItemID.SARADOMIN_CHAPS, "Clue Scroll (Hard)", "God Dhide", 21),
SARADOMIN_BRACERS("Saradomin bracers", ItemID.SARADOMIN_BRACERS, "Clue Scroll (Hard)", "God Dhide", 21),
SARADOMIN_DHIDE_BOOTS("Saradomin dhide boots", ItemID.SARADOMIN_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 21),
SARADOMIN_DHIDE_SHIELD("Saradomin dhide shield", ItemID.SARADOMIN_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 21),
ZAMORAK_COIF("Zamorak coif", ItemID.ZAMORAK_COIF, "Clue Scroll (Hard)", "God Dhide", 22),
ZAMORAK_DHIDE("Zamorak dragonhide", ItemID.ZAMORAK_DHIDE, "Clue Scroll (Hard)", "God Dhide", 22),
ZAMORAK_CHAPS("Zamorak chaps", ItemID.ZAMORAK_CHAPS, "Clue Scroll (Hard)", "God Dhide", 22),
ZAMORAK_BRACERS("Zamorak bracers", ItemID.ZAMORAK_BRACERS, "Clue Scroll (Hard)", "God Dhide", 22),
ZAMORAK_DHIDE_BOOTS("Zamorak dhide boots", ItemID.ZAMORAK_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 22),
ZAMORAK_DHIDE_SHIELD("Zamorak dhide shield", ItemID.ZAMORAK_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 22),
ANCIENT_COIF("Ancient coif", ItemID.ANCIENT_COIF, "Clue Scroll (Hard)", "God Dhide", 23),
ANCIENT_DHIDE("Ancient dragonhide", ItemID.ANCIENT_DHIDE, "Clue Scroll (Hard)", "God Dhide", 23),
ANCIENT_CHAPS("Ancient chaps", ItemID.ANCIENT_CHAPS, "Clue Scroll (Hard)", "God Dhide", 23),
ANCIENT_BRACERS("Ancient bracers", ItemID.ANCIENT_BRACERS, "Clue Scroll (Hard)", "God Dhide", 23),
ANCIENT_DHIDE_BOOTS("Ancient dhide boots", ItemID.ANCIENT_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 23),
ANCIENT_DHIDE_SHIELD("Ancient dhide shield", ItemID.ANCIENT_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 23),
BANDOS_COIF("Bandos coif", ItemID.BANDOS_COIF, "Clue Scroll (Hard)", "God Dhide", 24),
BANDOS_DHIDE("Bandos dragonhide", ItemID.BANDOS_DHIDE, "Clue Scroll (Hard)", "God Dhide", 24),
BANDOS_CHAPS("Bandos chaps", ItemID.BANDOS_CHAPS, "Clue Scroll (Hard)", "God Dhide", 24),
BANDOS_BRACERS("Bandos bracers", ItemID.BANDOS_BRACERS, "Clue Scroll (Hard)", "God Dhide", 24),
BANDOS_DHIDE_BOOTS("Bandos dhide boots", ItemID.BANDOS_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 24),
BANDOS_DHIDE_SHIELD("Bandos dhide shield", ItemID.BANDOS_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 24),
ARMADYL_COIF("Armadyl coif", ItemID.ARMADYL_COIF, "Clue Scroll (Hard)", "God Dhide", 25),
ARMADYL_DHIDE("Armadyl dragonhide", ItemID.ARMADYL_DHIDE, "Clue Scroll (Hard)", "God Dhide", 25),
ARMADYL_CHAPS("Armadyl chaps", ItemID.ARMADYL_CHAPS, "Clue Scroll (Hard)", "God Dhide", 25),
ARMADYL_BRACERS("Armadyl bracers", ItemID.ARMADYL_BRACERS, "Clue Scroll (Hard)", "God Dhide", 25),
ARMADYL_DHIDE_BOOTS("Armadyl dhide boots", ItemID.ARMADYL_DHIDE_BOOTS, "Clue Scroll (Hard)", "God Dhide", 25),
ARMADYL_DHIDE_SHIELD("Armadyl dhide shield", ItemID.ARMADYL_DHIDE_SHIELD, "Clue Scroll (Hard)", "God Dhide", 25),
GUTHIX_STOLE("Guthix stole", ItemID.GUTHIX_STOLE, "Clue Scroll (Hard)", "Vestment", 26),
GUTHIX_CROZIER("Guthix crozier", ItemID.GUTHIX_CROZIER, "Clue Scroll (Hard)", "Vestment", 26),
SARADOMIN_STOLE("Saradomin stole", ItemID.SARADOMIN_STOLE, "Clue Scroll (Hard)", "Vestment", 26),
SARADOMIN_CROZIER("Saradomin crozier", ItemID.SARADOMIN_CROZIER, "Clue Scroll (Hard)", "Vestment", 26),
ZAMORAK_STOLE("Zamorak stole", ItemID.ZAMORAK_STOLE, "Clue Scroll (Hard)", "Vestment", 26),
ZAMORAK_CROZIER("Zamorak crozier", ItemID.ZAMORAK_CROZIER, "Clue Scroll (Hard)", "Vestment", 26),
GREEN_DRAGON_MASK("Green dragon mask", ItemID.GREEN_DRAGON_MASK, "Clue Scroll (Hard)", "Masks", 27),
RED_DRAGON_MASK("Red dragon mask", ItemID.RED_DRAGON_MASK, "Clue Scroll (Hard)", "Masks", 27),
BLUE_DRAGON_MASK("Blue dragon mask", ItemID.BLUE_DRAGON_MASK, "Clue Scroll (Hard)", "Masks", 27),
BLACK_DRAGON_MASK("Black dragon mask", ItemID.BLACK_DRAGON_MASK, "Clue Scroll (Hard)", "Masks", 27),
RUNE_CANE("Rune cane", ItemID.RUNE_CANE, "Clue Scroll (Hard)", "Other", 28),
ZOMBIE_HEAD("Zombie head", ItemID.ZOMBIE_HEAD, "Clue Scroll (Hard)", "Other", 28),
CYCLOPS_HEAD("Cyclops head", ItemID.CYCLOPS_HEAD, "Clue Scroll (Hard)", "Other", 28),
NUNCHAKU("Nunchaku", ItemID.NUNCHAKU, "Clue Scroll (Hard)", "Other", 29),
DUAL_SAI("Dual sai", ItemID.DUAL_SAI, "Clue Scroll (Hard)", "Other", 29),
THIEVING_BAG("Thieving bag", ItemID.THIEVING_BAG, "Clue Scroll (Hard)", "Other", 29),
DRAGON_BOOTS_ORNAMENT_KIT("Dragon boots ornament kit", ItemID.DRAGON_BOOTS_ORNAMENT_KIT, "Clue Scroll (Hard)", "Ornament Kit", 30),
RUNE_DEFENDER_ORNAMENT_KIT("Rune defender ornament kit", ItemID.RUNE_DEFENDER_ORNAMENT_KIT, "Clue Scroll (Hard)", "Ornament Kit", 30),
TZHAARKETOM_ORNAMENT_KIT("Tzhaar-ket-om ornament kit", ItemID.TZHAARKETOM_ORNAMENT_KIT, "Clue Scroll (Hard)", "Ornament Kit", 30),
BERSERKER_NECKLACE_ORNAMENT_KIT("Berserker necklace ornament kit", ItemID.BERSERKER_NECKLACE_ORNAMENT_KIT, "Clue Scroll (Hard)", "Ornament Kit", 30),
// Elite Clue Scrolls
DRAGON_FULL_HELM_ORNAMENT_KIT("Dragon full helm ornament kit", ItemID.DRAGON_FULL_HELM_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", -1),
DRAGON_CHAINBODY_ORNAMENT_KIT("Dragon chainbody ornament kit", ItemID.DRAGON_CHAINBODY_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", -1),
DRAGON_LEGSSKIRT_ORNAMENT_KIT("Dragon legs/skirt ornament kit", ItemID.DRAGON_LEGSSKIRT_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", -1),
DRAGON_SQ_SHIELD_ORNAMENT_KIT("Dragon sq shield ornament kit", ItemID.DRAGON_SQ_SHIELD_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", -1),
DRAGON_SCIMITAR_ORNAMENT_KIT("Dragon scimitar ornament kit", ItemID.DRAGON_SCIMITAR_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", -1),
LIGHT_INFINITY_COLOUR_KIT("Light infinity colour kit", ItemID.LIGHT_INFINITY_COLOUR_KIT, "Clue Scroll (Elite)", "Colour Kits", 0),
DARK_INFINITY_COLOUR_KIT("Dark infinity colour kit", ItemID.DARK_INFINITY_COLOUR_KIT, "Clue Scroll (Elite)", "Colour Kits", 0),
FURY_ORNAMENT_KIT("Fury ornament kit", ItemID.FURY_ORNAMENT_KIT, "Clue Scroll (Elite)", "Ornament Kits", 0),
MUSKETEER_HAT("Musketeer hat", ItemID.MUSKETEER_HAT, "Clue Scroll (Elite)", "Musketeer", 1),
MUSKETEER_TABARD("Musketeer tabard", ItemID.MUSKETEER_TABARD, "Clue Scroll (Elite)", "Musketeer", 1),
MUSKETEER_PANTS("Musketeer pants", ItemID.MUSKETEER_PANTS, "Clue Scroll (Elite)", "Musketeer", 1),
DRAGON_CANE("Dragon cane", ItemID.DRAGON_CANE, "Clue Scroll (Elite)", "Musketeer", 1),
TOP_HAT("Top hat", ItemID.TOP_HAT, "Clue Scroll (Elite)", "Fancy", 2),
MONOCLE("Monocle", ItemID.MONOCLE, "Clue Scroll (Elite)", "Fancy", 2),
BRIEFCASE("Briefcase", ItemID.BRIEFCASE, "Clue Scroll (Elite)", "Fancy", 2),
SAGACIOUS_SPECTACLES("Sagacious spectacles", ItemID.SAGACIOUS_SPECTACLES, "Clue Scroll (Elite)", "Fancy", 2),
BIG_PIRATE_HAT("Big pirate hat", ItemID.BIG_PIRATE_HAT, "Clue Scroll (Elite)", "Hats", 3),
DEERSTALKER("Deerstalker", ItemID.DEERSTALKER, "Clue Scroll (Elite)", "Hats", 3),
BLACKSMITHS_HELM("Blacksmith's helm", ItemID.BLACKSMITHS_HELM, "Clue Scroll (Elite)", "Hats", 3),
BUCKET_HELM("Bucket helm", ItemID.BUCKET_HELM, "Clue Scroll (Elite)", "Hats", 3),
AFRO("Afro", ItemID.AFRO, "Clue Scroll (Elite)", "Hats", 3),
GILDED_BOOTS("Gilded boots", ItemID.GILDED_BOOTS, "Clue Scroll (Elite)", "Rare", 4),
GILDED_SCIMITAR("Gilded scimitar", ItemID.GILDED_SCIMITAR, "Clue Scroll (Elite)", "Rare", 4),
RANGERS_TUNIC("Rangers' tunic", ItemID.RANGERS_TUNIC, "Clue Scroll (Elite)", "Rare", 4),
RANGERS_TIGHTS("Rangers' tights", ItemID.RANGERS_TIGHTS, "Clue Scroll (Elite)", "Rare", 4),
RANGER_GLOVES("Ranger gloves", ItemID.RANGER_GLOVES, "Clue Scroll (Elite)", "Rare", 4),
BRONZE_DRAGON_MASK("Bronze dragon mask", ItemID.BRONZE_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 5),
IRON_DRAGON_MASK("Iron dragon mask", ItemID.IRON_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 5),
STEEL_DRAGON_MASK("Steel dragon mask", ItemID.STEEL_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 5),
MITHRIL_DRAGON_MASK("Mithril dragon mask", ItemID.MITHRIL_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 5),
ADAMANT_DRAGON_MASK("Adamant dragon mask", ItemID.ADAMANT_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 6),
RUNE_DRAGON_MASK("Rune dragon mask", ItemID.RUNE_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 6),
LAVA_DRAGON_MASK("Lava dragon mask", ItemID.LAVA_DRAGON_MASK, "Clue Scroll (Elite)", "Masks", 6),
BLACK_DHIDE_BODY_T("Black dhide body (t)", ItemID.BLACK_DHIDE_BODY_T, "Clue Scroll (Elite)", "Dhide", 7),
BLACK_DHIDE_CHAPS_T("Black dhide chaps (t)", ItemID.BLACK_DHIDE_CHAPS_T, "Clue Scroll (Elite)", "Dhide", 7),
BLACK_DHIDE_BODY_G("Black dhide body (g)", ItemID.BLACK_DHIDE_BODY_G, "Clue Scroll (Elite)", "Dhide", 7),
BLACK_DHIDE_CHAPS_G("Black dhide chaps (g)", ItemID.BLACK_DHIDE_CHAPS_G, "Clue Scroll (Elite)", "Dhide", 7),
GILDED_COIF("Gilded coif", ItemID.GILDED_COIF, "Clue Scroll (Elite)", "Dhide", 8),
GILDED_DHIDE_BODY("Gilded dhide body", ItemID.GILDED_DHIDE_BODY, "Clue Scroll (Elite)", "Dhide", 8),
GILDED_DHIDE_CHAPS("Gilded dhide chaps", ItemID.GILDED_DHIDE_CHAPS, "Clue Scroll (Elite)", "Dhide", 8),
GILDED_DHIDE_VAMBS("Gilded dhide vambs", ItemID.GILDED_DHIDE_VAMBS, "Clue Scroll (Elite)", "Dhide", 8),
_3RD_AGE_CLOAK("3rd age cloak", ItemID._3RD_AGE_CLOAK, "Clue Scroll (Elite)", "Third Age", 9),
_3RD_AGE_WAND("3rd age wand", ItemID._3RD_AGE_WAND, "Clue Scroll (Elite)", "Third Age", 9),
_3RD_AGE_BOW("3rd age bow", ItemID._3RD_AGE_BOW, "Clue Scroll (Elite)", "Third Age", 9),
_3RD_AGE_LONGSWORD("3rd age longsword", ItemID._3RD_AGE_LONGSWORD, "Clue Scroll (Elite)", "Third Age", 9),
ROYAL_CROWN("Royal crown", ItemID.ROYAL_CROWN, "Clue Scroll (Elite)", "Royal", 10),
ROYAL_GOWN_TOP("Royal gown top", ItemID.ROYAL_GOWN_TOP, "Clue Scroll (Elite)", "Royal", 10),
ROYAL_GOWN_BOTTOM("Royal gown bottom", ItemID.ROYAL_GOWN_BOTTOM, "Clue Scroll (Elite)", "Royal", 10),
ROYAL_SCEPTRE("Royal sceptre", ItemID.ROYAL_SCEPTRE, "Clue Scroll (Elite)", "Royal", 10),
GILDED_PICKAXE("Gilded pickaxe", ItemID.GILDED_PICKAXE, "Clue Scroll (Elite)", "Gilded", 11),
GILDED_AXE("Gilded axe", ItemID.GILDED_AXE, "Clue Scroll (Elite)", "Gilded", 11),
GILDED_SPADE("Gilded spade", ItemID.GILDED_SPADE, "Clue Scroll (Elite)", "Gilded", 11),
ARCEUUS_SCARF("Arceuus house scarf", ItemID.ARCEUUS_SCARF, "Clue Scroll (Elite)", "Zeah Scarfs", 12),
HOSIDIUS_SCARF("Hosidius house scarf", ItemID.HOSIDIUS_SCARF, "Clue Scroll (Elite)", "Zeah Scarfs", 12),
LOVAKENGJ_SCARF("Lovakengj house scarf", ItemID.LOVAKENGJ_SCARF, "Clue Scroll (Elite)", "Zeah Scarfs", 12),
PISCARILIUS_SCARF("Piscarilius house scarf", ItemID.PISCARILIUS_SCARF, "Clue Scroll (Elite)", "Zeah Scarfs", 12),
SHAYZIEN_SCARF("Shayzien house scarf", ItemID.SHAYZIEN_SCARF, "Clue Scroll (Elite)", "Zeah Scarfs", 12),
DARK_BOW_TIE("Dark bow tie", ItemID.DARK_BOW_TIE, "Clue Scroll (Elite)", "Dark Tuxedo", 13),
DARK_TUXEDO_JACKET("Dark tuxedo jacket", ItemID.DARK_TUXEDO_JACKET, "Clue Scroll (Elite)", "Dark Tuxedo", 13),
DARK_TROUSERS("Dark trousers", ItemID.DARK_TROUSERS, "Clue Scroll (Elite)", "Dark Tuxedo", 13),
DARK_TUXEDO_CUFFS("Dark tuxedo cuffs", ItemID.DARK_TUXEDO_CUFFS, "Clue Scroll (Elite)", "Dark Tuxedo", 13),
DARK_TUXEDO_SHOES("Dark tuxedo shoes", ItemID.DARK_TUXEDO_SHOES, "Clue Scroll (Elite)", "Dark Tuxedo", 13),
LIGHT_BOW_TIE("Light bow tie", ItemID.LIGHT_BOW_TIE, "Clue Scroll (Elite)", "Light Tuxedo", 14),
LIGHT_TUXEDO_JACKET("Light tuxedo jacket", ItemID.LIGHT_TUXEDO_JACKET, "Clue Scroll (Elite)", "Light Tuxedo", 14),
LIGHT_TROUSERS("Light trousers", ItemID.LIGHT_TROUSERS, "Clue Scroll (Elite)", "Light Tuxedo", 14),
LIGHT_TUXEDO_CUFFS("Light tuxedo cuffs", ItemID.LIGHT_TUXEDO_CUFFS, "Clue Scroll (Elite)", "Light Tuxedo", 14),
LIGHT_TUXEDO_SHOES("Light tuxedo shoes", ItemID.LIGHT_TUXEDO_SHOES, "Clue Scroll (Elite)", "Light Tuxedo", 14),
HOLY_WRAPS("Holy wraps", ItemID.HOLY_WRAPS, "Clue Scroll (Elite)", "Other", 15),
RING_OF_NATURE("Ring of nature", ItemID.RING_OF_NATURE, "Clue Scroll (Elite)", "Other", 15),
HEAVY_CASKET("Heavy casket", ItemID.HEAVY_CASKET, "Clue Scroll (Elite)", "Other", 15),
KATANA("Katana", ItemID.KATANA, "Clue Scroll (Elite)", "Other", 16),
URIS_HAT("Uri's hat", ItemID.URIS_HAT, "Clue Scroll (Elite)", "Other", 16),
FREMENNIK_KILT("Fremennik Kilt", ItemID.FREMENNIK_KILT, "Clue Scroll (Elite)", "Other", 16),
// Master Clue Scrolls
DRAGON_PLATEBODY_ORNAMENT_KIT("Dragon platebody ornament kit", ItemID.DRAGON_PLATEBODY_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
DRAGON_KITESHIELD_ORNAMENT_KIT("Dragon kiteshield ornament kit", ItemID.DRAGON_KITESHIELD_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
DRAGON_DEFENDER_ORNAMENT_KIT("Dragon defender ornament kit", ItemID.DRAGON_DEFENDER_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
TORMENTED_ORNAMENT_KIT("Tormented bracelet ornament kit", ItemID.TORMENTED_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
ANGUISH_ORNAMENT_KIT("Anguish ornament kit", ItemID.ANGUISH_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
TORTURE_ORNAMENT_KIT("Torture ornament kit", ItemID.TORTURE_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", -1),
OCCULT_ORNAMENT_KIT("Occult ornament kit", ItemID.OCCULT_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", 0),
ARMADYL_GODSWORD_ORNAMENT_KIT("Armadyl godsword ornament kit", ItemID.ARMADYL_GODSWORD_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", 0),
BANDOS_GODSWORD_ORNAMENT_KIT("Bandos godsword ornament kit", ItemID.BANDOS_GODSWORD_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", 0),
SARADOMIN_GODSWORD_ORNAMENT_KIT("Saradomin godsword ornament kit", ItemID.SARADOMIN_GODSWORD_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", 0),
ZAMORAK_GODSWORD_ORNAMENT_KIT("Zamorak godsword ornament kit", ItemID.ZAMORAK_GODSWORD_ORNAMENT_KIT, "Clue Scroll (Master)", "Ornament Kits", 0),
_3RD_AGE_AXE("3rd age axe", ItemID._3RD_AGE_AXE, "Clue Scroll (Master)", "3rd age", 1),
_3RD_AGE_PICKAXE("3rd age pickaxe", ItemID._3RD_AGE_PICKAXE, "Clue Scroll (Master)", "3rd age", 1),
_3RD_AGE_DRUIDIC_STAFF("3rd age druidic staff", ItemID._3RD_AGE_DRUIDIC_STAFF, "Clue Scroll (Master)", "3rd age", 2),
_3RD_AGE_DRUIDIC_ROBE_TOP("3rd age druidic robe top", ItemID._3RD_AGE_DRUIDIC_ROBE_TOP, "Clue Scroll (Master)", "3rd age", 2),
_3RD_AGE_DRUIDIC_ROBE_BOTTOMS("3rd age druidic robe bottom", ItemID._3RD_AGE_DRUIDIC_ROBE_BOTTOMS, "Clue Scroll (Master)", "3rd age", 2),
_3RD_AGE_DRUIDIC_CLOAK("3rd age druidic cloak", ItemID._3RD_AGE_DRUIDIC_CLOAK, "Clue Scroll (Master)", "3rd age", 2),
LESSER_DEMON_MASK("Lesser demon mask", ItemID.LESSER_DEMON_MASK, "Clue Scroll (Master)", "Demon Masks", 3),
GREATER_DEMON_MASK("Greater demon mask", ItemID.GREATER_DEMON_MASK, "Clue Scroll (Master)", "Demon Masks", 3),
BLACK_DEMON_MASK("Black demon mask", ItemID.BLACK_DEMON_MASK, "Clue Scroll (Master)", "Demon Masks", 3),
JUNGLE_DEMON_MASK("Jungle demon mask", ItemID.JUNGLE_DEMON_MASK, "Clue Scroll (Master)", "Demon Masks", 3),
OLD_DEMON_MASK("Old demon mask", ItemID.OLD_DEMON_MASK, "Clue Scroll (Master)", "Demon Masks", 3),
ARCEUUS_HOOD("Arceuus house hood", ItemID.ARCEUUS_HOOD, "Clue Scroll (Master)", "House Hoods", 4),
HOSIDIUS_HOOD("Hosidius house hood", ItemID.HOSIDIUS_HOOD, "Clue Scroll (Master)", "House Hoods", 4),
LOVAKENGJ_HOOD("Lovakengj house hood", ItemID.LOVAKENGJ_HOOD, "Clue Scroll (Master)", "House Hoods", 4),
PISCARILIUS_HOOD("Piscarilius house hood", ItemID.PISCARILIUS_HOOD, "Clue Scroll (Master)", "House Hoods", 4),
SHAYZIEN_HOUSE_HOOD("Shayzien house hood", ItemID.SHAYZIEN_HOOD, "Clue Scroll (Master)", "House Hoods", 4),
SAMURAI_KASA("Samurai kasa", ItemID.SAMURAI_KASA, "Clue Scroll (Master)", "Samurai Outfit", 5),
SAMURAI_SHIRT("Samurai shirt", ItemID.SAMURAI_SHIRT, "Clue Scroll (Master)", "Samurai Outfit", 5),
SAMURAI_GREAVES("Samurai greaves", ItemID.SAMURAI_GREAVES, "Clue Scroll (Master)", "Samurai Outfit", 5),
SAMURAI_GLOVES("Samurai gloves", ItemID.SAMURAI_GLOVES, "Clue Scroll (Master)", "Samurai Outfit", 5),
SAMURAI_BOOTS("Samurai boots", ItemID.SAMURAI_BOOTS, "Clue Scroll (Master)", "Samurai Outfit", 5),
MUMMYS_HEAD("Mummy's head", ItemID.MUMMYS_HEAD, "Clue Scroll (Master)", "Mummy Outfit", 6),
MUMMYS_BODY("Mummy's body", ItemID.MUMMYS_BODY, "Clue Scroll (Master)", "Mummy Outfit", 6),
MUMMYS_LEGS("Mummy's legs", ItemID.MUMMYS_LEGS, "Clue Scroll (Master)", "Mummy Outfit", 6),
MUMMYS_HANDS("Mummy's hands", ItemID.MUMMYS_HANDS, "Clue Scroll (Master)", "Mummy Outfit", 6),
MUMMYS_FEET("Mummy's feet", ItemID.MUMMYS_FEET, "Clue Scroll (Master)", "Mummy Outfit", 6),
ANKOU_MASK("Ankou mask", ItemID.ANKOU_MASK, "Clue Scroll (Master)", "Ankou Outfit", 7),
ANKOU_TOP("Ankou top", ItemID.ANKOU_TOP, "Clue Scroll (Master)", "Ankou Outfit", 7),
ANKOUS_LEGGINGS("Ankou's leggings", ItemID.ANKOUS_LEGGINGS, "Clue Scroll (Master)", "Ankou Outfit", 7),
ANKOU_GLOVES("Ankou gloves", ItemID.ANKOU_GLOVES, "Clue Scroll (Master)", "Ankou Outfit", 7),
ANKOU_SOCKS("Ankou socks", ItemID.ANKOU_SOCKS, "Clue Scroll (Master)", "Ankou Outfit", 7),
HOOD_OF_DARKNESS("Hood of darkness", ItemID.HOOD_OF_DARKNESS, "Clue Scroll (Master)", "Robes of Darkness", 8),
ROBE_TOP_OF_DARKNESS("Robe top of darkness", ItemID.ROBE_TOP_OF_DARKNESS, "Clue Scroll (Master)", "Robes of Darkness", 8),
ROBE_BOTTOM_OF_DARKNESS("Robe bottom of darkness", ItemID.ROBE_BOTTOM_OF_DARKNESS, "Clue Scroll (Master)", "Robes of Darkness", 8),
BOOTS_OF_DARKNESS("Boots of darkness", ItemID.BOOTS_OF_DARKNESS, "Clue Scroll (Master)", "Robes of Darkness", 8),
GLOVES_OF_DARKNESS("Gloves of darkness", ItemID.GLOVES_OF_DARKNESS, "Clue Scroll (Master)", "Robes of Darkness", 8),
RING_OF_COINS("Ring of coins", ItemID.RING_OF_COINS, "Clue Scroll (Master)", "Other", 9),
LEFT_EYE_PATCH("Left eye patch", ItemID.LEFT_EYE_PATCH, "Clue Scroll (Master)", "Other", 9),
OBSIDIAN_CAPE_R("Obsidian cape (r)", ItemID.OBSIDIAN_CAPE_R, "Clue Scroll (Master)", "Other", 9),
FANCY_TIARA("Fancy tiara", ItemID.FANCY_TIARA, "Clue Scroll (Master)", "Other", 9),
HALF_MOON_SPECTACLES("Half moon spectacles", ItemID.HALF_MOON_SPECTACLES, "Clue Scroll (Master)", "Other", 9),
ALE_OF_THE_GODS("Ale of the gods", ItemID.ALE_OF_THE_GODS, "Clue Scroll (Master)", "Other 2", 10),
BUCKET_HELM_G("Bucket helm (g)", ItemID.BUCKET_HELM_G, "Clue Scroll (Master)", "Other 2", 10),
BOWL_WIG("Bowl wig", ItemID.BOWL_WIG, "Clue Scroll (Master)", "Other 2", 10),
BLOODHOUND("Bloodhound", ItemID.BLOODHOUND, "Clue Scroll (Master)", "Other 2", 10),
HERBI("Herbi", ItemID.HERBI, "Herbiboar", "Pet", -1);
// Shared Unique Items
UniqueItem(String n, int id, String... activities)
{
this.name = n;
this.itemID = id;
this.activities = activities;
this.setName = "Shared";
this.position = -1;
}
// Non-Shared Unique Items
UniqueItem(String n, int id, String a, String set, int position)
{
this.name = n;
this.itemID = id;
this.activities = new String[]{a};
this.setName = set;
this.position = position;
}
private final String name;
private final int itemID;
private final String[] activities;
private final String setName;
private final int position;
// Get a specific UniqueItem by Name
// Unused ATM
private static final Map<String, UniqueItem> byName = buildNameMap();
public static UniqueItem getByName(String name)
{
return byName.get(name.toUpperCase());
}
private static Map<String, UniqueItem> buildNameMap()
{
Map<String, UniqueItem> byName = new HashMap<>();
for (UniqueItem item : values())
{
byName.put(item.getName().toUpperCase(), item);
}
return byName;
}
// Returns an array of UniqueItems by an individual `activities` name
private static final Map<String, ArrayList<UniqueItem>> byActivityName = buildActivityMap();
public static ArrayList<UniqueItem> getByActivityName(String name)
{
return byActivityName.get(name.toUpperCase());
}
private static Map<String, ArrayList<UniqueItem>> buildActivityMap()
{
Map<String, ArrayList<UniqueItem>> byName = new HashMap<>();
for (UniqueItem item : values())
{
String[] activities = item.getActivities();
for (String activity : activities)
{
byName.computeIfAbsent(activity.toUpperCase(), e -> new ArrayList<UniqueItem>()).add(item);
}
}
return byName;
}
// Return an array of UniqueItems by `setName`
// Unused ATM
private static final Map<String, ArrayList<UniqueItem>> bySetName = buildSetMap();
public static ArrayList<UniqueItem> getBySetName(String name)
{
return bySetName.get(name.toUpperCase());
}
private static Map<String, ArrayList<UniqueItem>> buildSetMap()
{
Map<String, ArrayList<UniqueItem>> byName = new HashMap<>();
for (UniqueItem item : values())
{
byName.computeIfAbsent(item.getSetName().toUpperCase(), e -> new ArrayList<>()).add(item);
}
return byName;
}
// Takes a list of UniqueItems and maps them by Position to ensure adding in predefined order
public static Map<Integer, Collection<UniqueItemPrepared>> createPositionSetMap(Collection<UniqueItemPrepared> items)
{
Map<Integer, Collection<UniqueItemPrepared>> setNames = new HashMap<>();
for (UniqueItemPrepared item : items)
{
setNames.computeIfAbsent(item.getUniqueItem().getPosition(), e -> new ArrayList<>()).add(item);
}
// Sort them by Key
return setNames.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.data;
import lombok.Value;
@Value
public class UniqueItemPrepared
{
private int linkedID;
private int price;
private UniqueItem uniqueItem;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.data.events;
public class LootTrackerNameChange
{
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.data.events;
import lombok.Data;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordCustom;
@Data
public class LootTrackerRecordStored
{
private final LootRecordCustom record;
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.data.LootTrackerItemEntry;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
import net.runelite.client.util.StackFormatter;
@Getter
class ItemPanel extends JPanel
{
private static final GridBagLayout LAYOUT = new GridBagLayout();
private static final Dimension PANEL_SIZE = new Dimension(215, 50);
private static final Border PANEL_BORDER = BorderFactory.createMatteBorder(3, 0, 3, 0, ColorScheme.DARK_GRAY_COLOR);
private static final Color PANEL_BACKGROUND_COLOR = ColorScheme.DARKER_GRAY_COLOR;
private static final Border CONTAINER_BORDER = BorderFactory.createMatteBorder(4, 15, 4, 15, PANEL_BACKGROUND_COLOR);
private LootTrackerItemEntry record;
ItemPanel(LootTrackerItemEntry record, ItemManager itemManager)
{
this.record = record;
this.setLayout(LAYOUT);
this.setBorder(PANEL_BORDER);
this.setBackground(PANEL_BACKGROUND_COLOR);
this.setPreferredSize(PANEL_SIZE);
// Item Image Icon
JLabel icon = new JLabel();
itemManager.getImage(record.getId(), record.getQuantity(), (record.isStackable() || record.getQuantity() > 1)).addTo(icon);
icon.setHorizontalAlignment(JLabel.CENTER);
// Container for Info
JPanel uiInfo = new JPanel(new GridLayout(2, 1));
uiInfo.setBorder(new EmptyBorder(0, 5, 0, 0));
uiInfo.setBackground(PANEL_BACKGROUND_COLOR);
JShadowedLabel labelName = new JShadowedLabel(this.record.getName());
labelName.setForeground(Color.WHITE);
colorLabel(labelName, this.record.getPrice());
labelName.setVerticalAlignment(SwingUtilities.BOTTOM);
String gpLabel = StackFormatter.quantityToStackSize(this.record.getTotal()) + " gp";
if (this.record.getHaTotal() > 0 && this.record.getHaPrice() > 1)
{
gpLabel += " (HA: " + StackFormatter.quantityToStackSize(this.record.getHaTotal()) + " gp)";
}
JShadowedLabel labelValue = new JShadowedLabel(gpLabel);
labelValue.setFont(FontManager.getRunescapeSmallFont());
colorLabel(labelValue, this.record.getTotal());
labelValue.setVerticalAlignment(SwingUtilities.TOP);
uiInfo.add(labelName);
uiInfo.add(labelValue);
// Create and append elements to container panel
JPanel panel = createPanel();
panel.add(icon, BorderLayout.LINE_START);
panel.add(uiInfo, BorderLayout.CENTER);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
this.add(panel, c);
this.setToolTipText(UniqueItemPanel.tooltipText(this.record.getPrice(), this.record.getHaPrice(), this.record.getQuantity(), this.record.getName()));
}
static JPanel createPanel()
{
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBorder(CONTAINER_BORDER);
panel.setBackground(PANEL_BACKGROUND_COLOR);
return panel;
}
// Color label to match RuneScape coloring
private void colorLabel(JLabel label, long val)
{
Color labelColor = (val >= 10000000) ? Color.GREEN : (val >= 100000) ? Color.WHITE : Color.YELLOW;
label.setForeground(labelColor);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.data.LootTrackerItemEntry;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.StackFormatter;
class LootGrid extends JPanel
{
private static final int ITEMS_PER_ROW = 5;
private static final Dimension ITEM_SIZE = new Dimension(40, 40);
private final LootTrackerItemEntry[] itemsToDisplay;
private ItemManager itemManager;
LootGrid(LootTrackerItemEntry[] itemsToDisplay, ItemManager itemManager)
{
this.itemsToDisplay = itemsToDisplay;
this.itemManager = itemManager;
setBorder(new EmptyBorder(5, 0, 5, 0));
buildItems();
}
/**
* This method creates stacked items from the item list, calculates total price and then
* displays all the items in the UI.
*/
private void buildItems()
{
ArrayList<LootTrackerItemEntry> items = LootTrackerBox.dedupeClues(Arrays.asList(itemsToDisplay));
// Calculates how many rows need to be display to fit all items
final int rowSize = LootTrackerBox.rowSize(items.size());
removeAll();
setLayout(new GridLayout(rowSize, ITEMS_PER_ROW, 1, 1));
for (int i = 0; i < rowSize * ITEMS_PER_ROW; i++)
{
final JPanel slot = new JPanel();
slot.setLayout(new GridLayout(1, 1, 0, 0));
slot.setBackground(ColorScheme.DARKER_GRAY_COLOR);
slot.setPreferredSize(ITEM_SIZE);
if (i < items.size())
{
final LootTrackerItemEntry item = items.get(i);
if (item == null)
{
continue;
}
final JLabel itemLabel = new JLabel();
itemLabel.setToolTipText(buildToolTip(item));
itemLabel.setVerticalAlignment(SwingConstants.CENTER);
itemLabel.setHorizontalAlignment(SwingConstants.CENTER);
itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1).addTo(itemLabel);
slot.add(itemLabel);
}
add(slot);
}
repaint();
}
private static String buildToolTip(LootTrackerItemEntry item)
{
final String name = item.getName();
final int quantity = item.getQuantity();
final long price = item.getPrice();
return "<html>" + name + " x " + StackFormatter.formatNumber(quantity)
+ "<br/>Price: " + StackFormatter.quantityToStackSize(price)
+ "<br/>Total: " + StackFormatter.quantityToStackSize(quantity * price) + "</html";
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import com.google.common.collect.Iterators;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.ItemSortTypes;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordCustom;
import net.runelite.client.plugins.stonedloottracker.data.LootTrackerItemEntry;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItemPrepared;
import net.runelite.client.ui.ColorScheme;
@Slf4j
class LootPanel extends JPanel
{
private Collection<LootRecordCustom> records;
private Map<Integer, Collection<UniqueItemPrepared>> uniqueMap;
private boolean hideUniques;
private ItemSortTypes sortType;
private boolean itemBreakdown;
private ItemManager itemManager;
// Consolidate LTItemEntries stored by ItemID
private Map<Integer, LootTrackerItemEntry> consolidated;
@Getter
private boolean playbackPlaying = false;
private boolean cancelPlayback = false;
LootPanel(Collection<LootRecordCustom> records, Map<Integer, Collection<UniqueItemPrepared>> uniqueMap, boolean hideUnqiues, ItemSortTypes sort, boolean itemBreakdown, ItemManager itemManager)
{
this.records = (records == null ? new ArrayList<>() : records);
this.uniqueMap = (uniqueMap == null ? new HashMap<>() : uniqueMap);
this.hideUniques = hideUnqiues;
this.sortType = sort;
this.itemBreakdown = itemBreakdown;
this.itemManager = itemManager;
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(0, 10, 0, 10));
setBackground(ColorScheme.DARK_GRAY_COLOR);
createConsolidatedArray(this.records);
createPanel(this.records);
}
private void createConsolidatedArray(Collection<LootRecordCustom> records)
{
// Consolidate all LootTrackerItemEntrys from each record for combined loot totals
// Sort them based on the config setting.
this.consolidated = LootRecordCustom.consolidateLootTrackerItemEntries(records)
.entrySet().stream()
.sorted((lt1, lt2) ->
{
LootTrackerItemEntry o1 = lt1.getValue();
LootTrackerItemEntry o2 = lt2.getValue();
switch (sortType)
{
case ITEM_ID:
return o1.getId() - o2.getId();
case PRICE:
if (o1.getPrice() != o2.getPrice())
{
return o1.getPrice() > o2.getPrice() ? -1 : 1;
}
break;
case VALUE:
if (o1.getTotal() != o2.getTotal())
{
return o1.getTotal() > o2.getTotal() ? -1 : 1;
}
break;
case ALPHABETICAL:
// Handled below
break;
default:
log.warn("Sort Type not being handled correctly, defaulting to alphabetical.");
break;
}
// Default to alphabetical
return o1.getName().compareTo(o2.getName());
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
private void createPanel(Collection<LootRecordCustom> records)
{
GridBagConstraints c = SelectionPanel.constrains();
// Create necessary helpers for the unique toggles
final Map<UniqueItemPrepared, Integer> uniqueQtys = new HashMap<>();
final Set<Integer> uniqueIds = new HashSet<>();
long totalValue = 0;
long totalHaValue = 0;
// Loop over all UniqueItems and check how many the player has received as a drop for each
// Also add all Item IDs for uniques to a Set for easy hiding later on.
for (Collection<UniqueItemPrepared> items : this.uniqueMap.values())
{
for (UniqueItemPrepared item : items)
{
int id = item.getUniqueItem().getItemID();
int linkedId = item.getLinkedID();
uniqueIds.add(id);
uniqueIds.add(linkedId);
LootTrackerItemEntry entry = this.consolidated.get(id);
LootTrackerItemEntry notedEntry = this.consolidated.get(linkedId);
int qty = (entry == null ? 0 : entry.getQuantity()) + (notedEntry == null ? 0 : notedEntry.getQuantity());
if (qty > 0)
{
totalValue += itemManager.getItemPrice(id);
totalHaValue += itemManager.getAlchValue(id);
uniqueQtys.put(item, qty);
}
}
}
// Attach all the Unique Items first
this.uniqueMap.forEach((setPosition, set) ->
{
UniqueItemPanel p = new UniqueItemPanel(set, uniqueQtys, this.itemManager);
this.add(p, c);
c.gridy++;
});
// Attach Kill Count Panel(s)
if (records.size() > 0)
{
int amount = records.size();
LootRecordCustom entry = Iterators.get(records.iterator(), (amount - 1));
if (entry.getKillCount() != -1)
{
TextPanel p = new TextPanel("Current Killcount:", entry.getKillCount());
this.add(p, c);
c.gridy++;
}
TextPanel p2 = new TextPanel("Kills Logged:", amount);
this.add(p2, c);
c.gridy++;
}
int totalValueIndex = c.gridy;
c.gridy += 2;
Collection<LootTrackerItemEntry> itemsToDisplay = new ArrayList<>();
ArrayList<LootTrackerItemEntry> items = LootTrackerBox.dedupeClues(new ArrayList<>(consolidated.values()));
for (LootTrackerItemEntry item : items)
{
totalValue += item.getTotal();
totalHaValue += item.getHaTotal();
if (hideUniques && uniqueIds.contains(item.getId()))
{
continue;
}
if (itemBreakdown)
{
ItemPanel p = new ItemPanel(item, itemManager);
this.add(p, c);
c.gridy++;
}
else
{
itemsToDisplay.add(item);
}
}
if (itemsToDisplay.size() > 0)
{
LootGrid grid = new LootGrid(itemsToDisplay.toArray(new LootTrackerItemEntry[0]), itemManager);
this.add(grid, c);
c.gridy++;
}
// Only add the total value element if it has something useful to display
if (totalValue > 0)
{
c.gridy = totalValueIndex;
TextPanel totalPanel = new TextPanel("Total Value:", totalValue);
this.add(totalPanel, c);
}
if (totalHaValue > 0)
{
c.gridy = totalValueIndex + 1;
TextPanel totalHaPanel = new TextPanel("Total HA Value:", totalHaValue);
this.add(totalHaPanel, c);
}
}
//void addedRecord(LootRecordCustom record)
void addedRecord()
{
//records.add(record);
// TODO: Smarter update system so it only repaints necessary Item and Text Panels
this.removeAll();
this.createConsolidatedArray(this.records);
this.createPanel(this.records);
this.revalidate();
this.repaint();
}
void playback()
{
playbackPlaying = true;
if (this.records.size() > 0)
{
Collection<LootRecordCustom> recs = new ArrayList<>();
for (LootRecordCustom r : this.records)
{
recs.add(r);
SwingUtilities.invokeLater(() -> refreshPlayback(recs));
try
{
if (cancelPlayback)
{
playbackPlaying = false;
cancelPlayback = false;
SwingUtilities.invokeLater(() -> refreshPlayback(this.records));
break;
}
Thread.sleep(250);
}
catch (InterruptedException e)
{
System.out.println(e.getMessage());
}
}
}
playbackPlaying = false;
}
void cancelPlayback()
{
// Only cancel if actually playing
cancelPlayback = playbackPlaying;
}
private void refreshPlayback(Collection<LootRecordCustom> recs)
{
this.removeAll();
this.createConsolidatedArray(recs);
this.createPanel(recs);
this.revalidate();
this.repaint();
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Tomas Slusny <slusnucky@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.stonedloottracker.ui;
import com.google.common.base.Strings;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordCustom;
import net.runelite.client.plugins.stonedloottracker.data.LootTrackerItemEntry;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.util.StackFormatter;
import net.runelite.client.util.Text;
@Getter
class LootTrackerBox extends JPanel
{
private static final int ITEMS_PER_ROW = 5;
private final JPanel itemContainer = new JPanel();
private final JLabel priceLabel = new JLabel();
private final JLabel subTitleLabel = new JLabel();
private final ItemManager itemManager;
@Getter(AccessLevel.PACKAGE)
private final String id;
@Getter
private final List<LootRecordCustom> records = new ArrayList<>();
private long totalPrice;
private long totalHaPrice;
LootTrackerBox(final ItemManager itemManager, final String id, @Nullable final String subtitle, LootRecordCustom record)
{
this.id = id;
this.itemManager = itemManager;
this.records.add(record);
setLayout(new BorderLayout(0, 1));
setBorder(new EmptyBorder(5, 0, 0, 0));
final JPanel logTitle = new JPanel(new BorderLayout(5, 0));
logTitle.setBorder(new EmptyBorder(7, 7, 7, 7));
logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker());
final JLabel titleLabel = new JLabel(Text.removeTags(id));
titleLabel.setFont(FontManager.getRunescapeSmallFont());
titleLabel.setForeground(Color.WHITE);
logTitle.add(titleLabel, BorderLayout.WEST);
subTitleLabel.setFont(FontManager.getRunescapeSmallFont());
subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
logTitle.add(subTitleLabel, BorderLayout.CENTER);
if (!Strings.isNullOrEmpty(subtitle))
{
subTitleLabel.setText(subtitle);
}
priceLabel.setFont(FontManager.getRunescapeSmallFont());
priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
logTitle.add(priceLabel, BorderLayout.EAST);
add(logTitle, BorderLayout.NORTH);
add(itemContainer, BorderLayout.CENTER);
buildItems();
}
/**
* This method creates stacked items from the item list, calculates total price and then
* displays all the items in the UI.
*/
private void buildItems()
{
final List<LootTrackerItemEntry> allItems = new ArrayList<>();
final List<LootTrackerItemEntry> items = new ArrayList<>();
totalPrice = 0;
totalHaPrice = 0;
for (LootRecordCustom record : records)
{
allItems.addAll(record.getDrops());
}
for (final LootTrackerItemEntry entry : dedupeClues(allItems))
{
totalPrice += (entry.getPrice() * entry.getQuantity());
totalHaPrice += (entry.getHaPrice() * entry.getQuantity());
int quantity = 0;
for (final LootTrackerItemEntry i : items)
{
if (i.getId() == entry.getId())
{
quantity = i.getQuantity();
items.remove(i);
break;
}
}
if (quantity > 0)
{
int newQuantity = entry.getQuantity() + quantity;
long pricePerItem = entry.getPrice();
long haPricePerItem = entry.getHaPrice();
items.add(new LootTrackerItemEntry(entry.getName(), entry.getId(), newQuantity, pricePerItem, haPricePerItem, false));
}
else
{
items.add(entry);
}
}
items.sort((i1, i2) -> Long.compare(i2.getPrice(), i1.getPrice()));
// Calculates how many rows need to be display to fit all items
final int rowSize = rowSize(items.size());
resetContainer(itemContainer, rowSize);
for (int i = 0; i < rowSize * ITEMS_PER_ROW; i++)
{
final JPanel slotContainer = new JPanel();
slotContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
if (i < items.size())
{
final LootTrackerItemEntry item = items.get(i);
final JLabel imageLabel = buildimageLabel(itemManager, item);
slotContainer.add(imageLabel);
}
itemContainer.add(slotContainer);
}
itemContainer.repaint();
priceLabel.setText(StackFormatter.quantityToStackSize(totalPrice) + " gp (HA: " + StackFormatter.quantityToStackSize(totalHaPrice) + ")");
priceLabel.setToolTipText(StackFormatter.formatNumber(totalPrice) + " gp (HA: " + StackFormatter.formatNumber(totalHaPrice) + ")");
if (records.size() > 1)
{
subTitleLabel.setText("x " + records.size());
}
repaint();
}
static ArrayList<LootTrackerItemEntry> dedupeClues(List<LootTrackerItemEntry> items)
{
final ArrayList<LootTrackerItemEntry> newItems = new ArrayList<>();
int eliteClues = 0;
int hardClues = 0;
int mediumClues = 0;
int easyClues = 0;
int beginnerClues = 0;
LootTrackerItemEntry eliteClue = null;
LootTrackerItemEntry hardClue = null;
LootTrackerItemEntry mediumClue = null;
LootTrackerItemEntry easyClue = null;
LootTrackerItemEntry beginnerClue = null;
for (LootTrackerItemEntry lootEntry : items)
{
if (lootEntry == null)
{
continue;
}
String name = lootEntry.getName();
if (lootEntry.getName().contains("Clue scroll ("))
{
if (name.contains("elite"))
{
eliteClues += lootEntry.getQuantity();
eliteClue = lootEntry;
}
else if (name.contains("hard"))
{
hardClues += lootEntry.getQuantity();
hardClue = lootEntry;
}
else if (name.contains("medium"))
{
mediumClues += lootEntry.getQuantity();
mediumClue = lootEntry;
}
else if (name.contains("easy"))
{
easyClues += lootEntry.getQuantity();
easyClue = lootEntry;
}
else if (name.contains("beginner"))
{
beginnerClues += lootEntry.getQuantity();
beginnerClue = lootEntry;
}
continue;
}
newItems.add(lootEntry);
}
LootTrackerItemEntry[] clueArray = {beginnerClue, easyClue, mediumClue, hardClue, eliteClue};
int[] clueAmountArray = {beginnerClues, easyClues, mediumClues, hardClues, eliteClues};
for (int i = 0; i < 4; i++)
{
if (clueAmountArray[i] > 0)
{
newItems.add(createNewLootEntry(Objects.requireNonNull(clueArray[i]), clueAmountArray[i]));
}
}
return newItems;
}
private static LootTrackerItemEntry createNewLootEntry(LootTrackerItemEntry item, int amount)
{
return new LootTrackerItemEntry(
item.getName(),
item.getId(),
amount,
item.getPrice(),
item.getHaPrice(),
item.isStackable()
);
}
static int rowSize(int items)
{
return ((items % ITEMS_PER_ROW == 0) ? 0 : 1) + items / ITEMS_PER_ROW;
}
private static void resetContainer(JPanel itemContainer, int rowSize)
{
itemContainer.removeAll();
itemContainer.setLayout(new GridLayout(rowSize, ITEMS_PER_ROW, 1, 1));
}
private static JLabel buildimageLabel(ItemManager itemManager, LootTrackerItemEntry item)
{
final JLabel imageLabel = new JLabel();
imageLabel.setToolTipText(UniqueItemPanel.tooltipText(item.getPrice(), item.getHaPrice(), item.getQuantity(), item.getName()));
imageLabel.setVerticalAlignment(SwingConstants.CENTER);
imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1).addTo(imageLabel);
return imageLabel;
}
}

View File

@@ -0,0 +1,365 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.StonedLootTrackerPlugin;
import net.runelite.client.plugins.stonedloottracker.data.LootRecordCustom;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItem;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItemPrepared;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.PluginErrorPanel;
@Slf4j
public class LootTrackerPanel extends PluginPanel
{
private static final BufferedImage ICON_DELETE;
private static final BufferedImage ICON_REFRESH;
private static final BufferedImage ICON_BACK;
private static final BufferedImage ICON_REPLAY;
private final static Color BACKGROUND_COLOR = ColorScheme.DARK_GRAY_COLOR;
private final static Color BUTTON_HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR;
static
{
BufferedImage i1;
BufferedImage i2;
BufferedImage i3;
BufferedImage i4;
try
{
synchronized (ImageIO.class)
{
i1 = ImageIO.read(StonedLootTrackerPlugin.class.getResourceAsStream("delete-white.png"));
i2 = ImageIO.read(StonedLootTrackerPlugin.class.getResourceAsStream("refresh-white.png"));
i3 = ImageIO.read(StonedLootTrackerPlugin.class.getResourceAsStream("back-arrow-white.png"));
i4 = ImageIO.read(StonedLootTrackerPlugin.class.getResourceAsStream("replay-white.png"));
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
ICON_DELETE = i1;
ICON_REFRESH = i2;
ICON_BACK = i3;
ICON_REPLAY = i4;
}
// NPC name for current view or null if on selection screen
private String currentView = null;
private LootPanel lootPanel;
private final ItemManager itemManager;
private final StonedLootTrackerPlugin plugin;
public LootTrackerPanel(final ItemManager itemManager, StonedLootTrackerPlugin plugin)
{
super(false);
this.itemManager = itemManager;
this.plugin = plugin;
this.setBackground(ColorScheme.DARK_GRAY_COLOR);
this.setLayout(new BorderLayout());
showSelectionView();
}
// Loot Selection view
private void showSelectionView()
{
this.removeAll();
currentView = null;
lootPanel = null;
PluginErrorPanel errorPanel = new PluginErrorPanel();
errorPanel.setBorder(new EmptyBorder(10, 25, 10, 25));
errorPanel.setContent("Stoned Loot Tracker", "Please select the Activity, Player, or NPC you wish to view loot for");
SelectionPanel selection = new SelectionPanel(plugin.config.bossButtons(), plugin.getNames(), this, itemManager);
this.add(errorPanel, BorderLayout.NORTH);
this.add(wrapContainer(selection), BorderLayout.CENTER);
this.revalidate();
this.repaint();
}
// Loot breakdown view
void showLootView(String name)
{
this.removeAll();
currentView = name;
Collection<LootRecordCustom> data = plugin.getDataByName(name);
// Grab all Uniques for this NPC/Activity
Collection<UniqueItemPrepared> uniques = plugin.getUniques(name);
if (uniques == null)
{
uniques = new ArrayList<>();
}
JPanel title = createLootViewTitle(name);
lootPanel = new LootPanel(data, UniqueItem.createPositionSetMap(uniques), plugin.config.hideUniques(), plugin.config.itemSortType(), plugin.config.itemBreakdown(), itemManager);
this.add(title, BorderLayout.NORTH);
this.add(wrapContainer(lootPanel), BorderLayout.CENTER);
this.revalidate();
this.repaint();
}
private LootTrackerBox createLootTrackerBox(LootRecordCustom r)
{
final String subTitle = r.getLevel() > -1 ? "(lvl-" + r.getLevel() + ")" : "";
return new LootTrackerBox(itemManager, r.getName(), subTitle, r);
}
// Title element for Loot breakdown view
private JPanel createLootViewTitle(String name)
{
JPanel title = new JPanel();
title.setBorder(new CompoundBorder(
new EmptyBorder(10, 8, 8, 8),
new MatteBorder(0, 0, 1, 0, Color.GRAY)
));
title.setLayout(new BorderLayout());
title.setBackground(BACKGROUND_COLOR);
JPanel first = new JPanel();
first.setBackground(BACKGROUND_COLOR);
// Back Button
JLabel back = createIconLabel(ICON_BACK);
back.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
showSelectionView();
}
});
// Plugin Name
JLabel text = new JLabel(name);
text.setForeground(Color.WHITE);
first.add(back);
first.add(text);
JPanel second = new JPanel();
second.setBackground(BACKGROUND_COLOR);
// Refresh Data button
JLabel refresh = createIconLabel(ICON_REFRESH);
refresh.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
refreshLootView(name);
}
});
refresh.setToolTipText("Refresh panel");
// Clear data button
JLabel clear = createIconLabel(ICON_DELETE);
clear.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
clearData(name);
}
});
clear.setToolTipText("Clear stored data");
// Clear data button
JLabel replay = createIconLabel(ICON_REPLAY);
replay.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
playbackLoot();
}
});
replay.setToolTipText("Replay Loot");
second.add(refresh);
second.add(clear);
second.add(replay);
title.add(first, BorderLayout.WEST);
title.add(second, BorderLayout.EAST);
return title;
}
private JLabel createIconLabel(BufferedImage icon)
{
JLabel label = new JLabel();
label.setIcon(new ImageIcon(icon));
label.setOpaque(true);
label.setBackground(BACKGROUND_COLOR);
label.addMouseListener(new MouseAdapter()
{
@Override
public void mouseEntered(MouseEvent e)
{
label.setBackground(BUTTON_HOVER_COLOR);
}
@Override
public void mouseExited(MouseEvent e)
{
label.setBackground(BACKGROUND_COLOR);
}
});
return label;
}
// Wrap the panel inside a scroll pane
private JScrollPane wrapContainer(JPanel container)
{
JPanel wrapped = new JPanel(new BorderLayout());
wrapped.add(container, BorderLayout.NORTH);
wrapped.setBackground(BACKGROUND_COLOR);
JScrollPane scroller = new JScrollPane(wrapped);
scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroller.getVerticalScrollBar().setPreferredSize(new Dimension(8, 0));
scroller.setBackground(BACKGROUND_COLOR);
return scroller;
}
// Clear stored data and return to selection screen
private void clearData(String name)
{
// Confirm delete action
int delete = JOptionPane.showConfirmDialog(this.getRootPane(), "<html>Are you sure you want to clear all data for this tab?<br/>There is no way to undo this action.</html>", "Warning", JOptionPane.YES_NO_OPTION);
if (delete == JOptionPane.YES_OPTION)
{
plugin.clearStoredDataByName(name);
// Return to selection screen
showSelectionView();
}
}
public void addLog(LootRecordCustom r)
{
if (currentView == null)
{
showLootView(r.getName());
}
else if (currentView.equals(r.getName()))
{
//lootPanel.addedRecord(r);
lootPanel.addedRecord();
}
}
// Refresh panel when writer playerFolder is updated
public void updateNames()
{
if (currentView == null)
{
showSelectionView();
}
else
{
showLootView(currentView);
}
}
// Refresh panel when config options are changed
public void refreshUI()
{
log.debug("Refreshing UI");
if (currentView == null)
{
showSelectionView();
}
else
{
showLootView(currentView);
}
}
private void refreshLootView(String name)
{
plugin.refreshDataByName(name);
showLootView(name); // Recreate the entire panel
}
private void playbackLoot()
{
if (lootPanel == null)
{
return;
}
if (lootPanel.isPlaybackPlaying())
{
lootPanel.cancelPlayback();
return;
}
ScheduledExecutorService ex = Executors.newSingleThreadScheduledExecutor();
if (currentView != null)
{
ex.schedule(lootPanel::playback, 0, TimeUnit.SECONDS);
}
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
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 net.runelite.client.game.AsyncBufferedImage;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.data.BossTab;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.components.materialtabs.MaterialTab;
import net.runelite.client.ui.components.materialtabs.MaterialTabGroup;
public class SelectionPanel extends JPanel
{
private TreeSet<String> names;
private LootTrackerPanel parent;
private ItemManager itemManager;
private final static Color BACKGROUND_COLOR = ColorScheme.DARK_GRAY_COLOR;
private final static Color BUTTON_COLOR = ColorScheme.DARKER_GRAY_COLOR;
private final static Color BUTTON_HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR;
private boolean configToggle;
SelectionPanel(boolean configToggle, TreeSet<String> names, LootTrackerPanel parent, ItemManager itemManager)
{
this.names = names == null ? new TreeSet<>() : names;
this.parent = parent;
this.itemManager = itemManager;
this.configToggle = configToggle;
this.setLayout(new GridBagLayout());
this.setBackground(BACKGROUND_COLOR);
createPanel();
}
private void createPanel()
{
GridBagConstraints c = constrains();
c.insets = new Insets(5, 0, 0, 0);
// Add the bosses tabs, by category, to tabGroup
if (configToggle)
{
Set<String> categories = BossTab.categories;
JPanel container = new JPanel(new GridBagLayout());
container.setBorder(new EmptyBorder(0, 0, 10, 0));
int oldc = c.gridy;
c.gridy = 0;
for (String categoryName : categories)
{
container.add(createTabCategory(categoryName), c);
c.gridy++;
}
c.gridy = oldc;
this.add(container, c);
c.gridy++;
}
// Add all other names
for (String name : this.names)
{
if (!configToggle || BossTab.getByName(name) == null)
{
this.add(createNamePanel(name), c);
c.gridy++;
}
}
}
private JPanel createNamePanel(String name)
{
JPanel p = new JPanel();
p.add(new JLabel(name));
p.setBackground(BUTTON_COLOR);
p.addMouseListener(new MouseAdapter()
{
@Override
public void mouseEntered(MouseEvent e)
{
p.setBackground(BUTTON_HOVER_COLOR);
}
@Override
public void mouseExited(MouseEvent e)
{
p.setBackground(BUTTON_COLOR);
}
@Override
public void mouseClicked(MouseEvent e)
{
SwingUtilities.invokeLater(() -> parent.showLootView(name));
}
});
return p;
}
static GridBagConstraints constrains()
{
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
return c;
}
// Creates all tabs for a specific category
private JPanel createTabCategory(String categoryName)
{
JPanel container = new JPanel();
container.setLayout(new GridBagLayout());
container.setBorder(new EmptyBorder(0, 5, 0, 5));
GridBagConstraints c = constrains();
MaterialTabGroup thisTabGroup = new MaterialTabGroup();
thisTabGroup.setLayout(new GridLayout(0, 4, 7, 7));
thisTabGroup.setBorder(new EmptyBorder(4, 0, 0, 0));
JLabel name = new JLabel(categoryName);
name.setBorder(new EmptyBorder(8, 0, 0, 0));
name.setForeground(Color.WHITE);
name.setVerticalAlignment(SwingConstants.CENTER);
ArrayList<BossTab> categoryTabs = BossTab.getByCategoryName(categoryName);
for (BossTab tab : categoryTabs)
{
// Create tab (with hover effects/text)
MaterialTab materialTab = new MaterialTab("", thisTabGroup, null);
materialTab.setName(tab.getName());
materialTab.setToolTipText(tab.getName());
//materialTab.setSelectedBorder(new EmptyBorder(0, 0, 0, 0));
materialTab.addMouseListener(new MouseAdapter()
{
@Override
public void mouseEntered(MouseEvent e)
{
materialTab.setBackground(BUTTON_HOVER_COLOR);
}
@Override
public void mouseExited(MouseEvent e)
{
materialTab.setBackground(BUTTON_COLOR);
}
@Override
public void mouseClicked(MouseEvent e)
{
SwingUtilities.invokeLater(() -> parent.showLootView(tab.getName()));
}
});
// Attach Icon to the Tab
AsyncBufferedImage image = itemManager.getImage(tab.getItemID());
Runnable resize = () ->
{
materialTab.setIcon(new ImageIcon(image.getScaledInstance(35, 35, Image.SCALE_SMOOTH)));
materialTab.setOpaque(true);
materialTab.setBackground(BUTTON_COLOR);
materialTab.setHorizontalAlignment(SwingConstants.CENTER);
materialTab.setVerticalAlignment(SwingConstants.CENTER);
materialTab.setPreferredSize(new Dimension(35, 35));
};
image.onChanged(resize);
resize.run();
thisTabGroup.addTab(materialTab);
}
container.add(name, c);
c.gridy++;
container.add(thisTabGroup, c);
return container;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.StackFormatter;
@Getter
class TextPanel extends JPanel
{
private static final GridBagLayout LAYOUT = new GridBagLayout();
private static final Border PANEL_BORDER = BorderFactory.createMatteBorder(3, 0, 3, 0, ColorScheme.DARK_GRAY_COLOR);
private static final Color PANEL_BACKGROUND_COLOR = ColorScheme.DARKER_GRAY_COLOR;
private static final Border CONTAINER_BORDER = BorderFactory.createMatteBorder(0, 15, 0, 15, PANEL_BACKGROUND_COLOR);
// Long value should be for Total Value
TextPanel(String text, long totalValue)
{
this.setLayout(LAYOUT);
this.setBorder(PANEL_BORDER);
this.setBackground(PANEL_BACKGROUND_COLOR);
JLabel totalText = new JLabel(text, SwingConstants.LEFT);
totalText.setForeground(Color.WHITE);
// Item Values (Colored off Total Value of item)
JLabel total = new JLabel(StackFormatter.quantityToStackSize(totalValue) + " gp", SwingConstants.LEFT);
total.setBorder(new EmptyBorder(0, 5, 0, 0));
colorLabel(total, totalValue);
JPanel panel = ItemPanel.createPanel();
panel.add(totalText, BorderLayout.LINE_START);
panel.add(total, BorderLayout.CENTER);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
c.ipady = 20;
panel.setToolTipText(StackFormatter.formatNumber(totalValue));
this.add(panel, c);
}
TextPanel(String text, int value)
{
this.setLayout(LAYOUT);
this.setBorder(PANEL_BORDER);
this.setBackground(PANEL_BACKGROUND_COLOR);
JLabel textLabel = new JLabel(text, SwingConstants.LEFT);
textLabel.setForeground(Color.WHITE);
JLabel valueLabel = new JLabel(String.valueOf(value), SwingConstants.LEFT);
valueLabel.setBorder(new EmptyBorder(0, 5, 0, 0));
JPanel panel = ItemPanel.createPanel();
panel.add(textLabel, BorderLayout.LINE_START);
panel.add(valueLabel, BorderLayout.CENTER);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
c.ipady = 20;
this.add(panel, c);
}
// Color label to match RuneScape coloring
private void colorLabel(JLabel label, long val)
{
Color labelColor = (val >= 10000000) ? Color.GREEN : (val >= 100000) ? Color.WHITE : Color.YELLOW;
label.setForeground(labelColor);
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2018, TheStonedTurtle <www.github.com/TheStonedTurtle>
* 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.stonedloottracker.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import net.runelite.client.game.AsyncBufferedImage;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.stonedloottracker.data.UniqueItemPrepared;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.StackFormatter;
@Getter
class UniqueItemPanel extends JPanel
{
private ItemManager itemManager;
private final float alphaMissing = 0.35f;
private final float alphaHas = 1.0f;
private static final Dimension panelSize = new Dimension(215, 50);
private static final Border panelBorder = new EmptyBorder(3, 0, 3, 0);
private static final Color panelBackgroundColor = ColorScheme.DARK_GRAY_COLOR;
UniqueItemPanel(Collection<UniqueItemPrepared> items, Map<UniqueItemPrepared, Integer> uniqueMap, ItemManager itemManager)
{
this.itemManager = itemManager;
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
panel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
panel.setBorder(new EmptyBorder(3, 0, 3, 0));
this.setLayout(new BorderLayout());
this.setBorder(panelBorder);
this.setBackground(panelBackgroundColor);
this.setPreferredSize(panelSize);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
c.ipady = 5;
// Add each Unique Item icon to the panel
for (UniqueItemPrepared l : items)
{
final int id = l.getUniqueItem().getItemID();
final int quantity = uniqueMap.getOrDefault(l, 0);
final float alpha = (quantity > 0 ? alphaHas : alphaMissing);
AsyncBufferedImage image = itemManager.getImage(l.getUniqueItem().getItemID(), quantity, quantity > 1);
BufferedImage opaque = ImageUtil.alphaOffset(image, alpha);
final long price = itemManager.getItemPrice(id);
final long haPrice = itemManager.getAlchValue(id);
// Attach Image to Label and append label to Panel
ImageIcon o = new ImageIcon(opaque);
JLabel icon = new JLabel(o);
panel.add(icon, c);
c.gridx++;
// in case the image is blank we will refresh it upon load
// Should only trigger if image hasn't been added
image.onChanged(() -> SwingUtilities.invokeLater(() -> refreshImage(icon, image, alpha)));
icon.setToolTipText(tooltipText(price, haPrice, quantity, l.getUniqueItem().getName()));
}
this.add(panel, BorderLayout.NORTH);
}
static String tooltipText(long price, long haPrice, int quantity, String name)
{
String tooltipText = "<html>" + name;
if (quantity > 1)
{
tooltipText += "<br/>Price: " + StackFormatter.quantityToStackSize(price);
if (haPrice > 0)
{
tooltipText += " (HA: " + StackFormatter.quantityToStackSize(haPrice) + ")";
}
}
if (quantity > 0)
{
tooltipText += "<br/>Total: " + StackFormatter.quantityToStackSize(quantity * price);
if (haPrice > 0)
{
tooltipText += " (HA: " + StackFormatter.quantityToStackSize(quantity * haPrice) + ")";
}
}
tooltipText += "</html";
return tooltipText;
}
// Used to refresh the item icon if the image was still loading when attempting to create it earlier
private void refreshImage(JLabel label, AsyncBufferedImage image, float finalAlpha)
{
BufferedImage opaque = ImageUtil.alphaOffset(image, finalAlpha);
ImageIcon o = new ImageIcon(opaque);
label.setIcon(o);
label.revalidate();
label.repaint();
}
}

View File

@@ -40,6 +40,7 @@ import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginType;
import net.runelite.client.plugins.zulrah.overlays.ZulrahCurrentPhaseOverlay;
import net.runelite.client.plugins.zulrah.overlays.ZulrahNextPhaseOverlay;
import net.runelite.client.plugins.zulrah.overlays.ZulrahOverlay;
@@ -55,7 +56,8 @@ import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Zulrah",
description = "Overlays to assist with killing Zulrah",
tags = {"zulrah", "boss", "helper"}
tags = {"zulrah", "boss", "helper"},
type = PluginType.PVM
)
@Slf4j