diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index a8f7200568..40f4042cd0 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -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 * diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankCalculation.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankCalculation.java index a93c2c409c..13398e22f7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankCalculation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankCalculation.java @@ -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 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; } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index b926e9528f..98472f9571 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -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: (\\d+)"); private static final Pattern RAIDS_PATTERN = Pattern.compile("Your completed (.+) count is: (\\d+)"); private static final Pattern WINTERTODT_PATTERN = Pattern.compile("Your subdued Wintertodt count is: (\\d+)"); @@ -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 ") diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java index 66878516d4..7a11d73140 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java @@ -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 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) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLUtil.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLUtil.java index fd6094aea7..2629e81e4b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLUtil.java @@ -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(); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index f16275a13f..d795b80767 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -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; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java index 4d8c2b1801..31fb5b06bd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java @@ -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; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java index 445f6c60d1..cb7d283d69 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java @@ -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; } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java index 0ce04acb1e..eadaefa9e5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java @@ -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); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AnisotropicFilteringMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AnisotropicFilteringMode.java new file mode 100644 index 0000000000..a5fd8aa831 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AnisotropicFilteringMode.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, 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; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java index 7f40564e01..9c93d47b0f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItemPanel.java @@ -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); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java index 1f6158e6b2..04ec06249f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeItems.java @@ -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; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java index bae1c45648..957d99f57e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangeSearchPanel.java @@ -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)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightConfig.java new file mode 100644 index 0000000000..1cf5a673a1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightConfig.java @@ -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; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightInputListener.java new file mode 100644 index 0000000000..faaadb3dc6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightInputListener.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * 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; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightOverlay.java new file mode 100644 index 0000000000..6f262264de --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightOverlay.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * 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; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightPlugin.java new file mode 100644 index 0000000000..26c124169e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventoryhighlight/InventoryHighlightPlugin.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, Jeremy Plsek + * 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; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java index 7c748a7702..7d49f78fdf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java @@ -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()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java index 1b678bab4b..cf2adbfb4a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftConfig.java @@ -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; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ItemSortTypes.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ItemSortTypes.java new file mode 100644 index 0000000000..80ae04b20d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ItemSortTypes.java @@ -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; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerConfig.java new file mode 100644 index 0000000000..e20a9b3bab --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerConfig.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018, 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; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerPlugin.java new file mode 100644 index 0000000000..b7a1db30f6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/StonedLootTrackerPlugin.java @@ -0,0 +1,703 @@ +/* + * Copyright (c) 2018, Psikoi + * Copyright (c) 2018, Adam + * 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 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 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 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 lootRecordMultimap = ArrayListMultimap.create(); + private Multimap uniques = ArrayListMultimap.create(); + private Map 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 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 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 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 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 currentInventory = HashMultiset.create(); + Arrays.stream(inventoryContainer.getItems()) + .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); + + final Multiset diff = Multisets.difference(currentInventory, inventorySnapshot); + + List 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 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 getData() + { + return lootRecordMultimap.values(); + } + + public Collection getDataByName(String name) + { + return lootRecordMultimap.get(name); + } + + private void refreshData() + { + // Pull data from files + lootRecordMultimap.clear(); + Collection recs = writer.loadAllLootTrackerRecords(); + for (LootRecordCustom r : recs) + { + lootRecordMultimap.put(r.getName(), r); + } + } + + public void refreshDataByName(String name) + { + lootRecordMultimap.removeAll(name); + Collection recs = writer.loadLootTrackerRecords(name); + lootRecordMultimap.putAll(name, recs); + } + + public void clearStoredDataByName(String name) + { + lootRecordMultimap.removeAll(name); + writer.deleteLootTrackerRecords(name); + } + + public TreeSet 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 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 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; + } + }); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/BossTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/BossTab.java new file mode 100644 index 0000000000..b59752f5f7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/BossTab.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018, 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 byName = buildMap(); + public static BossTab getByName(String name) + { + return byName.get(name.toUpperCase()); + } + private static Map buildMap() + { + Map byName = new HashMap<>(); + for (BossTab tab : values()) + { + byName.put(tab.getName().toUpperCase(), tab); + } + + return byName; + } + + // By Category Name + private static final Map> byCategoryName = buildCategoryMap(); + public static ArrayList getByCategoryName(String name) + { + return byCategoryName.get(name.toUpperCase()); + } + private static Map> buildCategoryMap() + { + Map> 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 categories = getCategories(); + private static Set getCategories() + { + Set s = new TreeSet<>(); + for (BossTab tab : values()) + { + s.add(tab.getCategory()); + } + return s; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecord.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecord.java new file mode 100644 index 0000000000..5143e79591 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecord.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018, 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 drops; + + public LootRecord(int id, String name, int level, int kc, Collection drops) + { + this.id = id; + this.name = name; + this.level = level; + this.killCount = kc; + this.drops = (drops == null ? new ArrayList<>() : drops); + } + + LootRecordCustom toNewFormat() + { + List 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); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordCustom.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordCustom.java new file mode 100644 index 0000000000..ec208f8615 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordCustom.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018, 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 drops; + + public LootRecordCustom(int id, String name, int level, int kc, Collection 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 consolidateLootTrackerItemEntries(Collection records) + { + // Store LootTrackerItemEntry by ItemID + Map 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(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordWriter.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordWriter.java new file mode 100644 index 0000000000..3a03546606 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootRecordWriter.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018, 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 getKnownFileNames() + { + Set 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 loadLootTrackerRecords(String npcName) + { + String fileName = npcNameToFileName(npcName); + File file = new File(playerFolder, fileName); + Collection 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 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 loadAllLootTrackerRecords() + { + List 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 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()); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootTrackerItemEntry.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootTrackerItemEntry.java new file mode 100644 index 0000000000..c7215a4b5b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/LootTrackerItemEntry.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, 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 + ")"; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/Pet.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/Pet.java new file mode 100644 index 0000000000..354ef2bd7c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/Pet.java @@ -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 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 buildBossMap() + { + Map byName = new HashMap<>(); + for (Pet pet : values()) + { + String[] droppingBosses = pet.getBossNames(); + for (String bossName : droppingBosses) + { + byName.put(bossName.toUpperCase(), pet); + } + } + return byName; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItem.java new file mode 100644 index 0000000000..959327e293 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItem.java @@ -0,0 +1,929 @@ +/* + * Copyright (c) 2018, 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 byName = buildNameMap(); + public static UniqueItem getByName(String name) + { + return byName.get(name.toUpperCase()); + } + private static Map buildNameMap() + { + Map 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> byActivityName = buildActivityMap(); + public static ArrayList getByActivityName(String name) + { + return byActivityName.get(name.toUpperCase()); + } + private static Map> buildActivityMap() + { + Map> byName = new HashMap<>(); + for (UniqueItem item : values()) + { + String[] activities = item.getActivities(); + for (String activity : activities) + { + byName.computeIfAbsent(activity.toUpperCase(), e -> new ArrayList()).add(item); + } + + } + + return byName; + } + + // Return an array of UniqueItems by `setName` + // Unused ATM + private static final Map> bySetName = buildSetMap(); + public static ArrayList getBySetName(String name) + { + return bySetName.get(name.toUpperCase()); + } + private static Map> buildSetMap() + { + Map> 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> createPositionSetMap(Collection items) + { + Map> 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)); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItemPrepared.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItemPrepared.java new file mode 100644 index 0000000000..b82fd0936a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/UniqueItemPrepared.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, 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; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerNameChange.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerNameChange.java new file mode 100644 index 0000000000..679344dd6e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerNameChange.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018, 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 +{ +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerRecordStored.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerRecordStored.java new file mode 100644 index 0000000000..48b63b504f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/data/events/LootTrackerRecordStored.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, 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; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/ItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/ItemPanel.java new file mode 100644 index 0000000000..4199f3b44b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/ItemPanel.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018, 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); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootGrid.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootGrid.java new file mode 100644 index 0000000000..b5ee9a7449 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootGrid.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018, 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 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 "" + name + " x " + StackFormatter.formatNumber(quantity) + + "
Price: " + StackFormatter.quantityToStackSize(price) + + "
Total: " + StackFormatter.quantityToStackSize(quantity * price) + " + * 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 records; + private Map> uniqueMap; + private boolean hideUniques; + private ItemSortTypes sortType; + private boolean itemBreakdown; + private ItemManager itemManager; + // Consolidate LTItemEntries stored by ItemID + private Map consolidated; + + @Getter + private boolean playbackPlaying = false; + private boolean cancelPlayback = false; + + LootPanel(Collection records, Map> 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 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 records) + { + GridBagConstraints c = SelectionPanel.constrains(); + + // Create necessary helpers for the unique toggles + final Map uniqueQtys = new HashMap<>(); + final Set 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 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 itemsToDisplay = new ArrayList<>(); + ArrayList 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 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 recs) + { + this.removeAll(); + + this.createConsolidatedArray(recs); + this.createPanel(recs); + + this.revalidate(); + this.repaint(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerBox.java new file mode 100644 index 0000000000..1b6083505d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerBox.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2018, Psikoi + * Copyright (c) 2018, Tomas Slusny + * 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 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 allItems = new ArrayList<>(); + final List 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 dedupeClues(List items) + { + final ArrayList 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; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerPanel.java new file mode 100644 index 0000000000..50af60e653 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/LootTrackerPanel.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2018, 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 data = plugin.getDataByName(name); + + // Grab all Uniques for this NPC/Activity + Collection 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(), "Are you sure you want to clear all data for this tab?
There is no way to undo this action.", "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); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/SelectionPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/SelectionPanel.java new file mode 100644 index 0000000000..37acb992e0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/SelectionPanel.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018, 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 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 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 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 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; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/TextPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/TextPanel.java new file mode 100644 index 0000000000..38529cce86 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/TextPanel.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018, 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); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/UniqueItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/UniqueItemPanel.java new file mode 100644 index 0000000000..e5a4c38721 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stonedloottracker/ui/UniqueItemPanel.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018, 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 items, Map 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 = "" + name; + if (quantity > 1) + { + tooltipText += "
Price: " + StackFormatter.quantityToStackSize(price); + + if (haPrice > 0) + { + tooltipText += " (HA: " + StackFormatter.quantityToStackSize(haPrice) + ")"; + } + } + + if (quantity > 0) + { + tooltipText += "
Total: " + StackFormatter.quantityToStackSize(quantity * price); + + if (haPrice > 0) + { + tooltipText += " (HA: " + StackFormatter.quantityToStackSize(quantity * haPrice) + ")"; + } + } + tooltipText += " + * 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. + */ + +#include to_screen.glsl + +/* + * Rotate a vertex by a given orientation in JAU + */ +ivec4 rotate(ivec4 vertex, int orientation) { + ivec2 sinCos = sinCosTable[orientation]; + int s = sinCos.x; + int c = sinCos.y; + int x = vertex.z * s + vertex.x * c >> 16; + int z = vertex.z * c - vertex.x * s >> 16; + return ivec4(x, vertex.y, z, vertex.w); +} + +/* + * Calculate the distance to a vertex given the camera angle + */ +int distance(ivec4 vertex, int cameraYaw, int cameraPitch) { + int yawSin = int(65536.0f * sin(cameraYaw * UNIT)); + int yawCos = int(65536.0f * cos(cameraYaw * UNIT)); + + int pitchSin = int(65536.0f * sin(cameraPitch * UNIT)); + int pitchCos = int(65536.0f * cos(cameraPitch * UNIT)); + + int j = vertex.z * yawCos - vertex.x * yawSin >> 16; + int l = vertex.y * pitchSin + j * pitchCos >> 16; + + return l; +} + +/* + * Calculate the distance to a face + */ +int face_distance(ivec4 vA, ivec4 vB, ivec4 vC, int cameraYaw, int cameraPitch) { + int dvA = distance(vA, cameraYaw, cameraPitch); + int dvB = distance(vB, cameraYaw, cameraPitch); + int dvC = distance(vC, cameraYaw, cameraPitch); + int faceDistance = (dvA + dvB + dvC) / 3; + return faceDistance; +} + +/* + * Test if a face is visible (not backward facing) + */ +bool face_visible(ivec4 vA, ivec4 vB, ivec4 vC, ivec4 position, int cameraYaw, int cameraPitch, int centerX, int centerY, int zoom) { + // Move model to scene location, and account for camera offset + ivec4 cameraPos = ivec4(cameraX, cameraY, cameraZ, 0); + vA += position - cameraPos; + vB += position - cameraPos; + vC += position - cameraPos; + + ivec3 sA = toScreen(vA.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom); + ivec3 sB = toScreen(vB.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom); + ivec3 sC = toScreen(vC.xyz, cameraYaw, cameraPitch, centerX, centerY, zoom); + + return (sA.x - sB.x) * (sC.y - sB.y) - (sC.x - sB.x) * (sA.y - sB.y) > 0; +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl index f569bceed6..1045bed629 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.glsl @@ -37,7 +37,7 @@ shared int dfs[4096]; // packed face id and distance layout(local_size_x = 1024) in; -#include common.glsl +#include common_func.glsl #include priority_render.glsl void main() { @@ -100,4 +100,4 @@ void main() { sort_and_insert(localId + 1, minfo, prio2Adj, dis2, vB1, vB2, vB3); sort_and_insert(localId + 2, minfo, prio3Adj, dis3, vC1, vC2, vC3); sort_and_insert(localId + 3, minfo, prio4Adj, dis4, vD1, vD2, vD3); -} +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_small.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_small.glsl index edb8375900..462da4b45b 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_small.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_small.glsl @@ -37,7 +37,7 @@ shared int dfs[512]; // packed face id and distance layout(local_size_x = 512) in; -#include common.glsl +#include common_func.glsl #include priority_render.glsl void main() { @@ -78,4 +78,4 @@ void main() { barrier(); sort_and_insert(localId, minfo, prio1Adj, dis1, vA1, vA2, vA3); -} +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl index b4b526838b..ae3a8d26e0 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.glsl @@ -29,7 +29,7 @@ layout(local_size_x = 6) in; -#include common.glsl +#include common_func.glsl void main() { uint groupId = gl_WorkGroupID.x; @@ -86,4 +86,4 @@ void main() { uvout[outOffset + myOffset * 3 + 1] = uv[uvOffset + localId * 3 + 1]; uvout[outOffset + myOffset * 3 + 2] = uv[uvOffset + localId * 3 + 2]; } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl index 968f8aec00..865b32e645 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.glsl @@ -64,6 +64,7 @@ int priority_map(int p, int distance, int _min10, int avg1, int avg2, int avg3) default: return -1; } + return -1; } // calculate the number of faces with a lower adjusted priority than @@ -108,6 +109,7 @@ int count_prio_offset(int priority) { case 0: return total; } + return total; } void get_face(uint localId, modelinfo minfo, int cameraYaw, int cameraPitch, int centerX, int centerY, int zoom, @@ -289,4 +291,4 @@ void sort_and_insert(uint localId, modelinfo minfo, int thisPriority, int thisDi uvout[outOffset + myOffset * 3 + 2] = uv[uvOffset + localId * 3 + 2]; } } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl index bdce8dd2ab..9e61fc7cb0 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl @@ -47,7 +47,9 @@ layout(std140) uniform uniforms { uniform float brightness; uniform int useFog; -uniform int fogDepth; +uniform float fogDepth; +uniform float fogCornerRadius; +uniform float fogDensity; uniform int drawDistance; out ivec3 vPosition; @@ -58,8 +60,27 @@ out float vFogAmount; #include hsl_to_rgb.glsl -float fogFactorLinear(const float dist, const float start, const float end) { - return 1.0 - clamp((dist - start) / (end - start), 0.0, 1.0); +float fogFactorCurved(const float dist, const float start, const float end, const float accel) { + return 1.0 - pow(clamp((dist - start) / (end - start), 0.0, 1.0), accel); +} + +// Returns the distance to the closest edge +float minEdgeDistance(vec3 v1, vec4 bounds){ + return min(min(v1.x - bounds.x, bounds.y - v1.x), min(v1.z - bounds.z, bounds.w - v1.z)); +} + +float roundedRectangleFunction(vec3 v1, vec4 bounds, float cornerRadius){ + float minXDistance = min(v1.x - bounds.x, bounds.y - v1.x); + float minZDistance = min(v1.z - bounds.z, bounds.w - v1.z); + + if (minXDistance < cornerRadius && minZDistance < cornerRadius) + { + return cornerRadius - sqrt( pow(minXDistance - cornerRadius, 2) + pow(minZDistance - cornerRadius, 2) ); + } + else + { + return min(minXDistance, minZDistance); + } } void main() @@ -81,8 +102,6 @@ void main() int fogSouth = max(FOG_SCENE_EDGE_MIN, cameraZ - drawDistance); int fogNorth = min(FOG_SCENE_EDGE_MAX, cameraZ + drawDistance - TILE_SIZE); - // Calculate distance from the scene edge - float fogDistance = min(min(vertex.x - fogWest, fogEast - vertex.x), min(vertex.z - fogSouth, fogNorth - vertex.z)); - - vFogAmount = fogFactorLinear(fogDistance, 0, fogDepth * TILE_SIZE) * useFog; -} + float fogDistance = roundedRectangleFunction(vPosition, vec4(fogWest, fogEast, fogSouth, fogNorth), fogCornerRadius); + vFogAmount = fogFactorCurved(fogDistance, 0, fogDepth, fogDensity) * useFog; +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back-arrow-white.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back-arrow-white.png new file mode 100644 index 0000000000..39dddbb177 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back-arrow-white.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back_icon.png new file mode 100644 index 0000000000..96b9200f60 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/back_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/delete-white.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/delete-white.png new file mode 100644 index 0000000000..014cf20850 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/delete-white.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/grouped_loot_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/grouped_loot_icon.png new file mode 100644 index 0000000000..5827719316 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/grouped_loot_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/invisible_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/invisible_icon.png new file mode 100644 index 0000000000..a52cbfe0a7 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/invisible_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/panel_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/panel_icon.png new file mode 100644 index 0000000000..58a523016a Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/panel_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/refresh-white.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/refresh-white.png new file mode 100644 index 0000000000..cd26305f7a Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/refresh-white.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/replay-white.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/replay-white.png new file mode 100644 index 0000000000..c0d23e4297 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/replay-white.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/single_loot_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/single_loot_icon.png new file mode 100644 index 0000000000..6c2214da99 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/single_loot_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/visible_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/visible_icon.png new file mode 100644 index 0000000000..5c4232c808 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/stonedloottracker/visible_icon.png differ diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankCalculationTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankCalculationTest.java index 404336687b..6458d1bd55 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankCalculationTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankCalculationTest.java @@ -110,6 +110,6 @@ public class BankCalculationTest bankCalculation.calculate(); long value = bankCalculation.getHaPrice(); - assertTrue(value > Integer.MAX_VALUE); + assertTrue(value == Integer.MAX_VALUE); } }