diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index 8b5e1275d8..e8057cb516 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -36,7 +36,7 @@ import net.runelite.api.coords.WorldPoint; /** * Represents a RuneScape actor/entity. */ -public interface Actor extends Entity, Locatable +public interface Actor extends Renderable, Locatable { /** * Gets the combat level of the actor. diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 7e29fc9ec3..49a5827226 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -932,7 +932,7 @@ public interface Client extends GameShell * @return the corresponding object composition * @see ObjectID */ - ObjectDefinition getObjectComposition(int objectId); + ObjectComposition getObjectDefinition(int objectId); /** * Gets the NPC composition corresponding to an NPCs ID. diff --git a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java index 3686f8590d..246ed2bfd5 100644 --- a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java @@ -40,8 +40,8 @@ public interface DecorativeObject extends TileObject Shape getConvexHull(); Shape getConvexHull2(); - Entity getEntity1(); - Entity getEntity2(); + Renderable getRenderable(); + Renderable getRenderable2(); Model getModel1(); diff --git a/runelite-api/src/main/java/net/runelite/api/DynamicObject.java b/runelite-api/src/main/java/net/runelite/api/DynamicObject.java index 3fefde5d17..a1914605e3 100644 --- a/runelite-api/src/main/java/net/runelite/api/DynamicObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DynamicObject.java @@ -1,6 +1,6 @@ package net.runelite.api; -public interface DynamicObject extends Entity +public interface DynamicObject extends Renderable { int getAnimationID(); } diff --git a/runelite-api/src/main/java/net/runelite/api/GameObject.java b/runelite-api/src/main/java/net/runelite/api/GameObject.java index 46cfbc3411..23a6948d69 100644 --- a/runelite-api/src/main/java/net/runelite/api/GameObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GameObject.java @@ -68,7 +68,7 @@ public interface GameObject extends TileObject */ Angle getOrientation(); - Entity getEntity(); + Renderable getRenderable(); int getRsOrientation(); diff --git a/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java b/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java index 9aa6218ccc..da3f944bfc 100644 --- a/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java @@ -29,7 +29,7 @@ import net.runelite.api.coords.LocalPoint; /** * Represents a graphics object/spotanim. */ -public interface GraphicsObject extends Entity +public interface GraphicsObject extends Renderable { /** * The graphics object ID. diff --git a/runelite-api/src/main/java/net/runelite/api/GroundObject.java b/runelite-api/src/main/java/net/runelite/api/GroundObject.java index 39f6bce225..8732dfbf92 100644 --- a/runelite-api/src/main/java/net/runelite/api/GroundObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GroundObject.java @@ -31,7 +31,7 @@ import java.awt.Shape; */ public interface GroundObject extends TileObject { - Entity getEntity(); + Renderable getRenderable(); Model getModel(); diff --git a/runelite-api/src/main/java/net/runelite/api/MenuEntry.java b/runelite-api/src/main/java/net/runelite/api/MenuEntry.java index 4ffa7bd792..d0bddc7086 100644 --- a/runelite-api/src/main/java/net/runelite/api/MenuEntry.java +++ b/runelite-api/src/main/java/net/runelite/api/MenuEntry.java @@ -57,7 +57,7 @@ public class MenuEntry implements Cloneable /** * An additional parameter for the action. */ - private int actionParam0; + private int actionParam; /** * A second additional parameter for the action. */ @@ -70,13 +70,13 @@ public class MenuEntry implements Cloneable */ private boolean forceLeftClick; - public MenuEntry(String option, String target, int type, int opcode, int actionParam0, int actionParam1, boolean forceLeftClick) + public MenuEntry(String option, String target, int type, int opcode, int actionParam, int actionParam1, boolean forceLeftClick) { this.option = option; this.target = target; this.identifier = type; this.opcode = opcode; - this.actionParam0 = actionParam0; + this.actionParam = actionParam; this.actionParam1 = actionParam1; this.forceLeftClick = forceLeftClick; } @@ -94,9 +94,19 @@ public class MenuEntry implements Cloneable } } + public void setActionParam0(int i) + { + this.actionParam = i; + } + + public int getActionParam0() + { + return this.actionParam; + } + public void setParam0(int i) { - this.actionParam0 = i; + this.actionParam = i; } public void setParam1(int i) @@ -114,6 +124,16 @@ public class MenuEntry implements Cloneable return this.opcode; } + public void setId(int i) + { + this.identifier = i; + } + + public int getId() + { + return this.identifier; + } + /** * Get opcode, but as it's enum counterpart */ diff --git a/runelite-api/src/main/java/net/runelite/api/Model.java b/runelite-api/src/main/java/net/runelite/api/Model.java index 6dc42e10dd..dd3c8eb3b0 100644 --- a/runelite-api/src/main/java/net/runelite/api/Model.java +++ b/runelite-api/src/main/java/net/runelite/api/Model.java @@ -31,7 +31,7 @@ import java.util.List; /** * Represents the model of an object. */ -public interface Model extends Entity +public interface Model extends Renderable { /** * Gets a list of all vertices of the model. diff --git a/runelite-api/src/main/java/net/runelite/api/ObjectDefinition.java b/runelite-api/src/main/java/net/runelite/api/ObjectComposition.java similarity index 97% rename from runelite-api/src/main/java/net/runelite/api/ObjectDefinition.java rename to runelite-api/src/main/java/net/runelite/api/ObjectComposition.java index 4dd57e5578..43fa5aa88a 100644 --- a/runelite-api/src/main/java/net/runelite/api/ObjectDefinition.java +++ b/runelite-api/src/main/java/net/runelite/api/ObjectComposition.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Information about a specific {@link ObjectID} */ -public interface ObjectDefinition +public interface ObjectComposition { /** * Gets ID for the object. @@ -72,5 +72,5 @@ public interface ObjectDefinition * * @throws NullPointerException if {@link #getImpostorIds()} is null */ - ObjectDefinition getImpostor(); + ObjectComposition getImpostor(); } diff --git a/runelite-api/src/main/java/net/runelite/api/Projectile.java b/runelite-api/src/main/java/net/runelite/api/Projectile.java index f12839f43b..d298135af9 100644 --- a/runelite-api/src/main/java/net/runelite/api/Projectile.java +++ b/runelite-api/src/main/java/net/runelite/api/Projectile.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Represents a projectile entity (ie. cannonball, arrow). */ -public interface Projectile extends Entity +public interface Projectile extends Renderable { /** * Gets the ID of the projectile. diff --git a/runelite-api/src/main/java/net/runelite/api/Entity.java b/runelite-api/src/main/java/net/runelite/api/Renderable.java similarity index 97% rename from runelite-api/src/main/java/net/runelite/api/Entity.java rename to runelite-api/src/main/java/net/runelite/api/Renderable.java index 035d373641..d8d2114a8d 100644 --- a/runelite-api/src/main/java/net/runelite/api/Entity.java +++ b/runelite-api/src/main/java/net/runelite/api/Renderable.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Represents an object that can be rendered. */ -public interface Entity extends Node +public interface Renderable extends Node { /** * Gets the model of the object. diff --git a/runelite-api/src/main/java/net/runelite/api/TileModel.java b/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java similarity index 98% rename from runelite-api/src/main/java/net/runelite/api/TileModel.java rename to runelite-api/src/main/java/net/runelite/api/SceneTileModel.java index c6f7fd2876..03899d218d 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileModel.java +++ b/runelite-api/src/main/java/net/runelite/api/SceneTileModel.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Represents the model of a tile in the current scene. */ -public interface TileModel +public interface SceneTileModel { /** * Gets the underlay color of the tile. diff --git a/runelite-api/src/main/java/net/runelite/api/TilePaint.java b/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java similarity index 98% rename from runelite-api/src/main/java/net/runelite/api/TilePaint.java rename to runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java index 491af71d61..0247c17e8f 100644 --- a/runelite-api/src/main/java/net/runelite/api/TilePaint.java +++ b/runelite-api/src/main/java/net/runelite/api/SceneTilePaint.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Represents the paint of a tile in the current scene. */ -public interface TilePaint +public interface SceneTilePaint { /** * Gets the RGB value of the paint. diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index ebf2d14767..d064ea5900 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -347,4 +347,7 @@ public final class ScriptID @ScriptArguments() public static final int BANKMAIN_SEARCHING = 514; + + @ScriptArguments(integer = 7) + public static final int SETTINGS_SLIDER_CHOOSE_ONOP = 3885; } diff --git a/runelite-api/src/main/java/net/runelite/api/SettingID.java b/runelite-api/src/main/java/net/runelite/api/SettingID.java new file mode 100644 index 0000000000..fd1b2859db --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/SettingID.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api; + +public class SettingID +{ + public static final int CAMERA_ZOOM = 14; + + public static final int MUSIC_VOLUME = 30; + public static final int EFFECT_VOLUME = 31; + public static final int AREA_VOLUME = 32; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/Tile.java b/runelite-api/src/main/java/net/runelite/api/Tile.java index 4c2213aff5..0c2dd25daa 100644 --- a/runelite-api/src/main/java/net/runelite/api/Tile.java +++ b/runelite-api/src/main/java/net/runelite/api/Tile.java @@ -71,14 +71,14 @@ public interface Tile extends TileObject * * @return the paint */ - TilePaint getTilePaint(); + SceneTilePaint getSceneTilePaint(); /** * Gets the model of the tile in the scene. * * @return the tile model */ - TileModel getTileModel(); + SceneTileModel getSceneTileModel(); /** * Gets the location coordinate of the tile in scene coords diff --git a/runelite-api/src/main/java/net/runelite/api/TileItem.java b/runelite-api/src/main/java/net/runelite/api/TileItem.java index 51d9bbc713..bd472b71b7 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileItem.java +++ b/runelite-api/src/main/java/net/runelite/api/TileItem.java @@ -27,7 +27,7 @@ package net.runelite.api; /** * Represents an item inside an {@link TileItemPile}. */ -public interface TileItem extends Entity +public interface TileItem extends Renderable { /** * @return the ID of the item diff --git a/runelite-api/src/main/java/net/runelite/api/TileItemPile.java b/runelite-api/src/main/java/net/runelite/api/TileItemPile.java index 21cf5ee98b..04c27ed8cc 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileItemPile.java +++ b/runelite-api/src/main/java/net/runelite/api/TileItemPile.java @@ -41,21 +41,21 @@ public interface TileItemPile extends TileObject * * @return the bottom item */ - Entity getBottom(); + Renderable getBottom(); /** * Gets the item at the middle of the pile. * * @return the middle item */ - Entity getMiddle(); + Renderable getMiddle(); /** * Gets the item at the top of the pile. * * @return the top item */ - Entity getTop(); + Renderable getTop(); Model getModelBottom(); Model getModelMiddle(); diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index 328db7d878..0fbc6f7016 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -52,8 +52,8 @@ public interface WallObject extends TileObject */ int getConfig(); - Entity getEntity1(); - Entity getEntity2(); + Renderable getRenderable(); + Renderable getRenderable2(); Model getModelA(); Model getModelB(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java b/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java index aeb69b9309..0ed4fb0df1 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java +++ b/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java @@ -90,7 +90,7 @@ public class MenuOptionClicked extends MenuEntry implements Event setTarget(e.getTarget()); setIdentifier(e.getIdentifier()); setOpcode(e.getOpcode()); - setActionParam0(e.getActionParam0()); + setActionParam(e.getActionParam()); setActionParam1(e.getActionParam1()); setForceLeftClick(e.isForceLeftClick()); } diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java index 681b9f8621..6452500d48 100644 --- a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java +++ b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java @@ -25,22 +25,22 @@ package net.runelite.api.hooks; import net.runelite.api.Model; -import net.runelite.api.Entity; -import net.runelite.api.TileModel; -import net.runelite.api.TilePaint; +import net.runelite.api.Renderable; +import net.runelite.api.SceneTileModel; +import net.runelite.api.SceneTilePaint; import net.runelite.api.Texture; public interface DrawCallbacks { - void draw(Entity entity, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash); + void draw(Renderable renderable, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash); void drawScenePaint(int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, - TilePaint paint, int tileZ, int tileX, int tileY, + SceneTilePaint paint, int tileZ, int tileX, int tileY, int zoom, int centerX, int centerY); void drawSceneModel(int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, - TileModel model, int tileZ, int tileX, int tileY, + SceneTileModel model, int tileZ, int tileX, int tileY, int zoom, int centerX, int centerY); void draw(); diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java index 43f8d6bf27..94b49bb08e 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java @@ -758,7 +758,7 @@ public interface Widget */ Object[] getOnLoadListener(); - Object[] getOnInvTransmit(); + Object[] getOnInvTransmitListener(); /** * Returns the archive id of the font used diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index a3eb2001b3..a6a02c17a7 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -181,6 +181,22 @@ public class WidgetID public static final int DUEL_INVENTORY_OTHER_GROUP_ID = 481; public static final int TRAILBLAZER_AREAS_GROUP_ID = 512; + public static final int SETTINGS_SIDE_GROUP_ID = 116; + public static final int SETTINGS_GROUP_ID = 134; + + static class SettingsSide + { + static final int CAMERA_ZOOM_SLIDER_TRACK = 59; + static final int MUSIC_SLIDER = 13; + static final int SOUND_EFFECT_SLIDER = 17; + static final int AREA_SOUND_SLIDER = 21; + } + + static class Settings + { + static final int INIT = 1; + } + static class WorldMap { static final int MAPVIEW = 7; @@ -766,6 +782,7 @@ public class WidgetID static final int BASE_POINTS = 33; static final int HONOUR_POINTS_REWARD = 49; } + static final int CORRECT_STYLE = 3; static final int GAME_WIDGET = 3; static final int CURRENT_WAVE_WIDGET = 4; static final int CURRENT_WAVE = 5; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index a6e713edee..3269a12bbd 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -480,6 +480,7 @@ public enum WidgetInfo BA_COLL_ROLE_TEXT(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE), BA_COLL_ROLE_SPRITE(WidgetID.BA_COLLECTOR_GROUP_ID, WidgetID.BarbarianAssault.ROLE_SPRITE), + BA_ATK_LISTEN_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.CORRECT_STYLE), BA_ATK_WAVE_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.CURRENT_WAVE), BA_ATK_CALL_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.TO_CALL), BA_ATK_LISTEN_TOP_TEXT(WidgetID.BA_ATTACKER_GROUP_ID, WidgetID.BarbarianAssault.ATK.LISTEN_TOP), @@ -919,7 +920,8 @@ public enum WidgetInfo HALLOWED_SEPULCHRE_TIMER_CONTAINER(WidgetID.HALLOWED_SEPULCHRE_TIMER_GROUP_ID, WidgetID.HallowedSepulchreTimer.CONTAINER), HEALTH_OVERLAY_BAR(WidgetID.HEALTH_OVERLAY_BAR_GROUP_ID, WidgetID.EncounterHealthBar.CONTAINER), - + SETTINGS_INIT(WidgetID.SETTINGS_GROUP_ID, WidgetID.Settings.INIT), + SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK(WidgetID.SETTINGS_SIDE_GROUP_ID, WidgetID.SettingsSide.CAMERA_ZOOM_SLIDER_TRACK), TRAILBLAZER_AREA_TELEPORT(WidgetID.TRAILBLAZER_AREAS_GROUP_ID, WidgetID.TrailblazerAreas.TELEPORT), ; diff --git a/runelite-client/runelite-client.gradle.kts b/runelite-client/runelite-client.gradle.kts index 3d48a2c786..b7e4b59f80 100644 --- a/runelite-client/runelite-client.gradle.kts +++ b/runelite-client/runelite-client.gradle.kts @@ -32,6 +32,11 @@ plugins { java } +repositories { + maven { + url = uri("https://repo.runelite.net") + } +} apply() @@ -76,10 +81,20 @@ dependencies { implementation(group = "org.pf4j", name = "pf4j-update", version = "2.3.0") implementation(group = "com.google.archivepatcher", name = "archive-patch-applier", version= "1.0.4") implementation(project(":http-api")) + implementation(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429") + implementation(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429") runtimeOnly(group = "org.pushing-pixels", name = "radiance-trident", version = "2.5.1") runtimeOnly(project(":injected-client")) runtimeOnly(project(":runescape-api")) + runtimeOnly(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429", classifier = "natives-linux-amd64") + runtimeOnly(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429", classifier = "natives-windows-amd64") + runtimeOnly(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429", classifier = "natives-windows-i586") + runtimeOnly(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429", classifier = "natives-macosx-universal") + runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-linux-amd64") + runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-windows-amd64") + runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-windows-i586") + runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-macosx-universal") testAnnotationProcessor(group = "org.projectlombok", name = "lombok", version = "1.18.16") diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index 0329cbb229..9165d9cdc7 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -42,7 +42,7 @@ import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; import net.runelite.api.BufferProvider; import net.runelite.api.Client; -import net.runelite.api.Entity; +import net.runelite.api.Renderable; import net.runelite.api.MainBufferProvider; import net.runelite.api.NullItemID; import net.runelite.api.RenderOverview; @@ -561,16 +561,16 @@ public class Hooks implements Callbacks } } - public static void renderDraw(Entity entity, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash) + public static void renderDraw(Renderable renderable, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash) { DrawCallbacks drawCallbacks = client.getDrawCallbacks(); if (drawCallbacks != null) { - drawCallbacks.draw(entity, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + drawCallbacks.draw(renderable, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); } else { - entity.draw(orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + renderable.draw(orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java new file mode 100644 index 0000000000..9260e9ef56 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingConfig.java @@ -0,0 +1,68 @@ +/* + * 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.animsmoothing; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(AnimationSmoothingPlugin.CONFIG_GROUP) +public interface AnimationSmoothingConfig extends Config +{ + + @ConfigItem( + keyName = "smoothPlayerAnimations", + name = "Smooth Player Animations", + description = "Configures whether the player animations are smooth or not", + position = 1 + ) + default boolean smoothPlayerAnimations() + { + return true; + } + + @ConfigItem( + keyName = "smoothNpcAnimations", + name = "Smooth NPC Animations", + description = "Configures whether the npc animations are smooth or not", + position = 2 + ) + default boolean smoothNpcAnimations() + { + return true; + } + + @ConfigItem( + keyName = "smoothObjectAnimations", + name = "Smooth Object Animations", + description = "Configures whether the object animations are smooth or not", + position = 3 + ) + default boolean smoothObjectAnimations() + { + return true; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java new file mode 100644 index 0000000000..725a0c7c1b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java @@ -0,0 +1,87 @@ +/* + * 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.animsmoothing; + +import com.google.inject.Provides; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Animation Smoothing", + description = "Show smoother player, NPC, and object animations", + tags = {"npcs", "objects", "players"}, + enabledByDefault = false +) +public class AnimationSmoothingPlugin extends Plugin +{ + static final String CONFIG_GROUP = "animationSmoothing"; + + @Inject + private Client client; + + @Inject + private AnimationSmoothingConfig config; + + @Provides + AnimationSmoothingConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(AnimationSmoothingConfig.class); + } + + @Override + protected void startUp() throws Exception + { + update(); + } + + @Override + protected void shutDown() throws Exception + { + client.setInterpolatePlayerAnimations(false); + client.setInterpolateNpcAnimations(false); + client.setInterpolateObjectAnimations(false); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_GROUP)) + { + update(); + } + } + + private void update() + { + client.setInterpolatePlayerAnimations(config.smoothPlayerAnimations()); + client.setInterpolateNpcAnimations(config.smoothNpcAnimations()); + client.setInterpolateObjectAnimations(config.smoothObjectAnimations()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java new file mode 100644 index 0000000000..6fa7c3871a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, DennisDeV + * 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.antidrag; + +import net.runelite.api.Constants; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(AntiDragPlugin.CONFIG_GROUP) +public interface AntiDragConfig extends Config +{ + @ConfigItem( + keyName = "dragDelay", + name = "Drag Delay", + description = "Configures the inventory drag delay in client ticks (20ms)", + position = 1 + ) + default int dragDelay() + { + return Constants.GAME_TICK_LENGTH / Constants.CLIENT_TICK_LENGTH; // one game tick + } + + @ConfigItem( + keyName = "onShiftOnly", + name = "On Shift Only", + description = "Configures whether to only adjust the delay while holding shift in non-PvP scenarios. Shift is required in PvP regardless of this config setting", + position = 2 + ) + default boolean onShiftOnly() + { + return true; + } + + @ConfigItem( + keyName = "disableOnCtrl", + name = "Disable On Control Pressed", + description = "Configures whether to ignore the delay while holding control.", + position = 3 + ) + default boolean disableOnCtrl() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java new file mode 100644 index 0000000000..1c3952737e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/antidrag/AntiDragPlugin.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2018, DennisDeV + * 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.antidrag; + +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Varbits; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.VarbitChanged; +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.ConfigChanged; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Anti Drag", + description = "Prevent dragging an item for a specified delay", + tags = {"antidrag", "delay", "inventory", "items"}, + enabledByDefault = false +) +public class AntiDragPlugin extends Plugin implements KeyListener +{ + static final String CONFIG_GROUP = "antiDrag"; + + private static final int DEFAULT_DELAY = 5; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private AntiDragConfig config; + + @Inject + private KeyManager keyManager; + + private boolean inPvp; + private boolean shiftHeld; + private boolean ctrlHeld; + + @Provides + AntiDragConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(AntiDragConfig.class); + } + + @Override + protected void startUp() throws Exception + { + if (client.getGameState() == GameState.LOGGED_IN) + { + clientThread.invokeLater(() -> + { + inPvp = client.getVar(Varbits.PVP_SPEC_ORB) == 1; + if (!config.onShiftOnly() && !inPvp) + { + setDragDelay(); + } + }); + } + + keyManager.registerKeyListener(this); + } + + @Override + protected void shutDown() throws Exception + { + clientThread.invoke(this::resetDragDelay); + keyManager.unregisterKeyListener(this); + } + + @Override + public void keyTyped(KeyEvent e) + { + + } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_CONTROL && config.disableOnCtrl() && !(inPvp || config.onShiftOnly())) + { + resetDragDelay(); + ctrlHeld = true; + } + else if (e.getKeyCode() == KeyEvent.VK_SHIFT && (inPvp || config.onShiftOnly())) + { + setDragDelay(); + shiftHeld = true; + } + } + + @Override + public void keyReleased(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_CONTROL && config.disableOnCtrl() && !(inPvp || config.onShiftOnly())) + { + setDragDelay(); + ctrlHeld = false; + } + else if (e.getKeyCode() == KeyEvent.VK_SHIFT && (inPvp || config.onShiftOnly())) + { + resetDragDelay(); + shiftHeld = false; + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_GROUP)) + { + if (!config.disableOnCtrl()) + { + ctrlHeld = false; + } + + if (config.onShiftOnly() || inPvp) + { + shiftHeld = false; + clientThread.invoke(this::resetDragDelay); + } + else + { + clientThread.invoke(this::setDragDelay); + } + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged varbitChanged) + { + boolean currentStatus = client.getVar(Varbits.PVP_SPEC_ORB) == 1; + + if (currentStatus != inPvp) + { + inPvp = currentStatus; + + if (!inPvp && !config.onShiftOnly()) + { + setDragDelay(); + } + else + { + resetDragDelay(); + } + } + + } + + @Subscribe + public void onFocusChanged(FocusChanged focusChanged) + { + if (!focusChanged.isFocused()) + { + shiftHeld = false; + ctrlHeld = false; + clientThread.invoke(this::resetDragDelay); + } + else if (!inPvp && !config.onShiftOnly()) + { + clientThread.invoke(this::setDragDelay); + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded widgetLoaded) + { + if ((widgetLoaded.getGroupId() == WidgetID.BANK_GROUP_ID || + widgetLoaded.getGroupId() == WidgetID.BANK_INVENTORY_GROUP_ID || + widgetLoaded.getGroupId() == WidgetID.DEPOSIT_BOX_GROUP_ID) && (!config.onShiftOnly() || shiftHeld) && !ctrlHeld) + { + setBankDragDelay(config.dragDelay()); + } + } + + private void setBankDragDelay(int delay) + { + final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + final Widget bankInventoryItemsContainer = client.getWidget(WidgetInfo.BANK_INVENTORY_ITEMS_CONTAINER); + final Widget bankDepositContainer = client.getWidget(WidgetInfo.DEPOSIT_BOX_INVENTORY_ITEMS_CONTAINER); + if (bankItemContainer != null) + { + Widget[] items = bankItemContainer.getDynamicChildren(); + for (Widget item : items) + { + item.setDragDeadTime(delay); + } + } + if (bankInventoryItemsContainer != null) + { + Widget[] items = bankInventoryItemsContainer.getDynamicChildren(); + for (Widget item : items) + { + item.setDragDeadTime(delay); + } + } + if (bankDepositContainer != null) + { + Widget[] items = bankDepositContainer.getDynamicChildren(); + for (Widget item : items) + { + item.setDragDeadTime(delay); + } + } + } + + private void setDragDelay() + { + client.setInventoryDragDelay(config.dragDelay()); + setBankDragDelay(config.dragDelay()); + } + + private void resetDragDelay() + { + client.setInventoryDragDelay(DEFAULT_DELAY); + setBankDragDelay(DEFAULT_DELAY); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStyle.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStyle.java new file mode 100644 index 0000000000..047363e1e4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStyle.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017, honeyhoney + * 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.attackstyles; + +import net.runelite.api.Skill; + +enum AttackStyle +{ + ACCURATE("Accurate", Skill.ATTACK), + AGGRESSIVE("Aggressive", Skill.STRENGTH), + DEFENSIVE("Defensive", Skill.DEFENCE), + CONTROLLED("Controlled", Skill.ATTACK, Skill.STRENGTH, Skill.DEFENCE), + RANGING("Ranging", Skill.RANGED), + LONGRANGE("Longrange", Skill.RANGED, Skill.DEFENCE), + CASTING("Casting", Skill.MAGIC), + DEFENSIVE_CASTING("Defensive Casting", Skill.MAGIC, Skill.DEFENCE), + OTHER("Other"); + + private final String name; + private final Skill[] skills; + + AttackStyle(String name, Skill... skills) + { + this.name = name; + this.skills = skills; + } + + public String getName() + { + return name; + } + + public Skill[] getSkills() + { + return skills; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java new file mode 100644 index 0000000000..c3715481d1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesConfig.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017, honeyhoney + * 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.attackstyles; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("attackIndicator") +public interface AttackStylesConfig extends Config +{ + @ConfigItem( + keyName = "alwaysShowStyle", + name = "Always show style", + description = "Show attack style indicator at all times", + position = 1 + ) + default boolean alwaysShowStyle() + { + return true; + } + + @ConfigItem( + keyName = "warnForDefensive", + name = "Warn for defence", + description = "Show warning when a Defence skill combat option is selected", + position = 2 + ) + default boolean warnForDefence() + { + return false; + } + + @ConfigItem( + keyName = "warnForAttack", + name = "Warn for attack", + description = "Show warning when an Attack skill combat option is selected", + position = 3 + ) + default boolean warnForAttack() + { + return false; + } + + @ConfigItem( + keyName = "warnForStrength", + name = "Warn for strength", + description = "Show warning when a Strength skill combat option is selected", + position = 4 + ) + default boolean warnForStrength() + { + return false; + } + + @ConfigItem( + keyName = "warnForRanged", + name = "Warn for ranged", + description = "Show warning when a Ranged skill combat option is selected", + position = 5 + ) + default boolean warnForRanged() + { + return false; + } + + @ConfigItem( + keyName = "warnForMagic", + name = "Warn for magic", + description = "Show warning when a Magic skill combat option is selected", + position = 6 + ) + default boolean warnForMagic() + { + return false; + } + + @ConfigItem( + keyName = "hideAutoRetaliate", + name = "Hide auto retaliate", + description = "Hide auto retaliate from the combat options tab", + position = 7 + ) + default boolean hideAutoRetaliate() + { + return false; + } + + @ConfigItem( + keyName = "removeWarnedStyles", + name = "Remove warned styles", + description = "Remove warned styles from the combat options tab", + position = 8 + ) + default boolean removeWarnedStyles() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesOverlay.java new file mode 100644 index 0000000000..9ef2cfe762 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesOverlay.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2017, honeyhoney + * 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.attackstyles; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.TitleComponent; + +class AttackStylesOverlay extends OverlayPanel +{ + private final AttackStylesPlugin plugin; + private final AttackStylesConfig config; + + @Inject + private AttackStylesOverlay(AttackStylesPlugin plugin, AttackStylesConfig config) + { + super(plugin); + setPosition(OverlayPosition.ABOVE_CHATBOX_RIGHT); + this.plugin = plugin; + this.config = config; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Attack style overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + boolean warnedSkillSelected = plugin.isWarnedSkillSelected(); + + if (warnedSkillSelected || config.alwaysShowStyle()) + { + final AttackStyle attackStyle = plugin.getAttackStyle(); + + if (attackStyle == null) + { + return null; + } + + final String attackStyleString = attackStyle.getName(); + + panelComponent.getChildren().add(TitleComponent.builder() + .text(attackStyleString) + .color(warnedSkillSelected ? Color.RED : Color.WHITE) + .build()); + + panelComponent.setPreferredSize(new Dimension( + graphics.getFontMetrics().stringWidth(attackStyleString) + 10, + 0)); + + return super.render(graphics); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java new file mode 100644 index 0000000000..eecaa99843 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2017, honeyhoney + * 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.attackstyles; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.inject.Provides; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.ScriptID; +import net.runelite.api.Skill; +import net.runelite.api.VarPlayer; +import net.runelite.api.Varbits; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.widgets.Widget; +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.ConfigChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import static net.runelite.client.plugins.attackstyles.AttackStyle.CASTING; +import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE_CASTING; +import static net.runelite.client.plugins.attackstyles.AttackStyle.OTHER; +import net.runelite.client.ui.overlay.OverlayManager; + +@PluginDescriptor( + name = "Attack Styles", + description = "Show your current attack style as an overlay", + tags = {"combat", "defence", "magic", "overlay", "ranged", "strength", "warn", "pure"} +) +public class AttackStylesPlugin extends Plugin +{ + private int attackStyleVarbit = -1; + private int equippedWeaponTypeVarbit = -1; + private int castingModeVarbit = -1; + private AttackStyle attackStyle; + private final Set warnedSkills = new HashSet<>(); + private boolean warnedSkillSelected = false; + private final Table widgetsToHide = HashBasedTable.create(); + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private AttackStylesConfig config; + + @Inject + private OverlayManager overlayManager; + + @Inject + private AttackStylesOverlay overlay; + + @Provides + AttackStylesConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(AttackStylesConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + + if (client.getGameState() == GameState.LOGGED_IN) + { + clientThread.invoke(this::start); + } + } + + private void start() + { + resetWarnings(); + attackStyleVarbit = client.getVar(VarPlayer.ATTACK_STYLE); + equippedWeaponTypeVarbit = client.getVar(Varbits.EQUIPPED_WEAPON_TYPE); + castingModeVarbit = client.getVar(Varbits.DEFENSIVE_CASTING_MODE); + updateAttackStyle( + equippedWeaponTypeVarbit, + attackStyleVarbit, + castingModeVarbit); + updateWarning(false); + processWidgets(); + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + hideWarnedStyles(false); + processWidgets(); + hideWidget(client.getWidget(WidgetInfo.COMBAT_AUTO_RETALIATE), false); + } + + @Nullable + public AttackStyle getAttackStyle() + { + return attackStyle; + } + + public boolean isWarnedSkillSelected() + { + return warnedSkillSelected; + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired scriptPostFired) + { + if (scriptPostFired.getScriptId() == ScriptID.COMBAT_INTERFACE_SETUP) + { + processWidgets(); + } + } + + /** + * Hide or unhide widgets depending on widgetsToHide + */ + private void processWidgets() + { + WeaponType equippedWeaponType = WeaponType.getWeaponType(equippedWeaponTypeVarbit); + + if (widgetsToHide.containsRow(equippedWeaponType)) + { + for (WidgetInfo widgetKey : widgetsToHide.row(equippedWeaponType).keySet()) + { + hideWidget(client.getWidget(widgetKey), widgetsToHide.get(equippedWeaponType, widgetKey)); + } + } + hideWidget(client.getWidget(WidgetInfo.COMBAT_AUTO_RETALIATE), config.hideAutoRetaliate()); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + resetWarnings(); + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + int currentAttackStyleVarbit = client.getVar(VarPlayer.ATTACK_STYLE); + int currentEquippedWeaponTypeVarbit = client.getVar(Varbits.EQUIPPED_WEAPON_TYPE); + int currentCastingModeVarbit = client.getVar(Varbits.DEFENSIVE_CASTING_MODE); + + if (attackStyleVarbit != currentAttackStyleVarbit || equippedWeaponTypeVarbit != currentEquippedWeaponTypeVarbit || castingModeVarbit != currentCastingModeVarbit) + { + boolean weaponSwitch = currentEquippedWeaponTypeVarbit != equippedWeaponTypeVarbit; + + attackStyleVarbit = currentAttackStyleVarbit; + equippedWeaponTypeVarbit = currentEquippedWeaponTypeVarbit; + castingModeVarbit = currentCastingModeVarbit; + + updateAttackStyle(equippedWeaponTypeVarbit, attackStyleVarbit, + castingModeVarbit); + updateWarning(weaponSwitch); + + // this isn't required, but will hide styles 1 tick earlier than the script event, which fires + // 1 tick after the combat options is unhidden + if (weaponSwitch) + { + processWidgets(); + } + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("attackIndicator")) + { + boolean enabled = Boolean.TRUE.toString().equals(event.getNewValue()); + switch (event.getKey()) + { + case "warnForDefensive": + updateWarnedSkills(enabled, Skill.DEFENCE); + break; + case "warnForAttack": + updateWarnedSkills(enabled, Skill.ATTACK); + break; + case "warnForStrength": + updateWarnedSkills(enabled, Skill.STRENGTH); + break; + case "warnForRanged": + updateWarnedSkills(enabled, Skill.RANGED); + break; + case "warnForMagic": + updateWarnedSkills(enabled, Skill.MAGIC); + break; + case "removeWarnedStyles": + hideWarnedStyles(enabled); + break; + } + processWidgets(); + } + } + + private void resetWarnings() + { + updateWarnedSkills(config.warnForAttack(), Skill.ATTACK); + updateWarnedSkills(config.warnForStrength(), Skill.STRENGTH); + updateWarnedSkills(config.warnForDefence(), Skill.DEFENCE); + updateWarnedSkills(config.warnForRanged(), Skill.RANGED); + updateWarnedSkills(config.warnForMagic(), Skill.MAGIC); + } + + private void updateAttackStyle(int equippedWeaponType, int attackStyleIndex, int castingMode) + { + AttackStyle[] attackStyles = WeaponType.getWeaponType(equippedWeaponType).getAttackStyles(); + if (attackStyleIndex < attackStyles.length) + { + attackStyle = attackStyles[attackStyleIndex]; + if (attackStyle == null) + { + attackStyle = OTHER; + } + else if ((attackStyle == CASTING) && (castingMode == 1)) + { + attackStyle = DEFENSIVE_CASTING; + } + } + } + + private void updateWarnedSkills(boolean enabled, Skill skill) + { + if (enabled) + { + warnedSkills.add(skill); + } + else + { + warnedSkills.remove(skill); + } + updateWarning(false); + } + + private void updateWarning(boolean weaponSwitch) + { + warnedSkillSelected = false; + if (attackStyle != null) + { + for (Skill skill : attackStyle.getSkills()) + { + if (warnedSkills.contains(skill)) + { + if (weaponSwitch) + { // NOPMD EmptyIfStmt + // TODO : chat message to warn players that their weapon switch also caused an unwanted attack style change + } + warnedSkillSelected = true; + break; + } + } + } + hideWarnedStyles(config.removeWarnedStyles()); + } + + private void hideWarnedStyles(boolean enabled) + { + WeaponType equippedWeaponType = WeaponType.getWeaponType(equippedWeaponTypeVarbit); + if (equippedWeaponType == null) + { + return; + } + + AttackStyle[] attackStyles = equippedWeaponType.getAttackStyles(); + + // Iterate over attack styles + for (int i = 0; i < attackStyles.length; i++) + { + AttackStyle attackStyle = attackStyles[i]; + if (attackStyle == null) + { + continue; + } + + boolean warnedSkill = false; + for (Skill skill : attackStyle.getSkills()) + { + if (warnedSkills.contains(skill)) + { + warnedSkill = true; + break; + } + } + + // Magic staves defensive casting mode + if (attackStyle == DEFENSIVE_CASTING || !enabled) + { + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_DEFENSIVE_SPELL_BOX, enabled && warnedSkill); + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_DEFENSIVE_SPELL_ICON, enabled && warnedSkill); + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_DEFENSIVE_SPELL_SHIELD, enabled && warnedSkill); + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_DEFENSIVE_SPELL_TEXT, enabled && warnedSkill); + } + + // Remove appropriate combat option + switch (i) + { + case 0: + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_STYLE_ONE, enabled && warnedSkill); + break; + case 1: + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_STYLE_TWO, enabled && warnedSkill); + break; + case 2: + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_STYLE_THREE, enabled && warnedSkill); + break; + case 3: + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_STYLE_FOUR, enabled && warnedSkill); + break; + case 4: + widgetsToHide.put(equippedWeaponType, WidgetInfo.COMBAT_SPELLS, enabled && warnedSkill); + break; + default: + // 5 can be defensive casting + } + } + } + + private void hideWidget(Widget widget, boolean hidden) + { + if (widget != null) + { + widget.setHidden(hidden); + } + } + + @VisibleForTesting + Set getWarnedSkills() + { + return warnedSkills; + } + + @VisibleForTesting + Table getHiddenWidgets() + { + return widgetsToHide; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/WeaponType.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/WeaponType.java new file mode 100644 index 0000000000..de71de2424 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/WeaponType.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017, honeyhoney + * 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.attackstyles; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import static net.runelite.client.plugins.attackstyles.AttackStyle.ACCURATE; +import static net.runelite.client.plugins.attackstyles.AttackStyle.AGGRESSIVE; +import static net.runelite.client.plugins.attackstyles.AttackStyle.CASTING; +import static net.runelite.client.plugins.attackstyles.AttackStyle.CONTROLLED; +import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE; +import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE_CASTING; +import static net.runelite.client.plugins.attackstyles.AttackStyle.LONGRANGE; +import static net.runelite.client.plugins.attackstyles.AttackStyle.OTHER; +import static net.runelite.client.plugins.attackstyles.AttackStyle.RANGING; + +enum WeaponType +{ + TYPE_0(ACCURATE, AGGRESSIVE, null, DEFENSIVE), + TYPE_1(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_2(ACCURATE, AGGRESSIVE, null, DEFENSIVE), + TYPE_3(RANGING, RANGING, null, LONGRANGE), + TYPE_4(ACCURATE, AGGRESSIVE, CONTROLLED, DEFENSIVE), + TYPE_5(RANGING, RANGING, null, LONGRANGE), + TYPE_6(AGGRESSIVE, RANGING, CASTING, null), + TYPE_7(RANGING, RANGING, null, LONGRANGE), + TYPE_8(OTHER, AGGRESSIVE, null, null), + TYPE_9(ACCURATE, AGGRESSIVE, CONTROLLED, DEFENSIVE), + TYPE_10(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_11(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_12(CONTROLLED, AGGRESSIVE, null, DEFENSIVE), + TYPE_13(ACCURATE, AGGRESSIVE, null, DEFENSIVE), + TYPE_14(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_15(CONTROLLED, CONTROLLED, CONTROLLED, DEFENSIVE), + TYPE_16(ACCURATE, AGGRESSIVE, CONTROLLED, DEFENSIVE), + TYPE_17(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_18(ACCURATE, AGGRESSIVE, null, DEFENSIVE, CASTING, DEFENSIVE_CASTING), + TYPE_19(RANGING, RANGING, null, LONGRANGE), + TYPE_20(ACCURATE, CONTROLLED, null, DEFENSIVE), + TYPE_21(ACCURATE, AGGRESSIVE, null, DEFENSIVE, CASTING, DEFENSIVE_CASTING), + TYPE_22(ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE), + TYPE_23(CASTING, CASTING, null, DEFENSIVE_CASTING), + TYPE_24(ACCURATE, AGGRESSIVE, CONTROLLED, DEFENSIVE), + TYPE_25(CONTROLLED, AGGRESSIVE, null, DEFENSIVE), + TYPE_26(AGGRESSIVE, AGGRESSIVE, null, AGGRESSIVE), + TYPE_27(ACCURATE, null, null, OTHER); + + private final AttackStyle[] attackStyles; + + private static final Map weaponTypes; + + static + { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + + for (WeaponType weaponType : values()) + { + builder.put(weaponType.ordinal(), weaponType); + } + + weaponTypes = builder.build(); + } + + WeaponType(AttackStyle... attackStyles) + { + this.attackStyles = attackStyles; + } + + public AttackStyle[] getAttackStyles() + { + return attackStyles; + } + + public static WeaponType getWeaponType(int id) + { + return weaponTypes.get(id); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java new file mode 100644 index 0000000000..002e01a3d4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankConfig.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * 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.bank; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Keybind; + +@ConfigGroup("bank") +public interface BankConfig extends Config +{ + @ConfigItem( + keyName = "showGE", + name = "Show Grand Exchange price", + description = "Show grand exchange price total (GE)", + position = 1 + ) + default boolean showGE() + { + return true; + } + + @ConfigItem( + keyName = "showHA", + name = "Show high alchemy price", + description = "Show high alchemy price total (HA)", + position = 2 + ) + default boolean showHA() + { + return false; + } + + @ConfigItem( + keyName = "showExact", + name = "Show exact bank value", + description = "Show exact bank value", + position = 3 + ) + default boolean showExact() + { + return false; + } + + @ConfigItem( + keyName = "rightClickBankInventory", + name = "Disable left click bank inventory", + description = "Configures whether the bank inventory button will bank your inventory on left click", + position = 4 + ) + default boolean rightClickBankInventory() + { + return false; + } + + @ConfigItem( + keyName = "rightClickBankEquip", + name = "Disable left click bank equipment", + description = "Configures whether the bank equipment button will bank your equipment on left click", + position = 5 + ) + default boolean rightClickBankEquip() + { + return false; + } + + @ConfigItem( + keyName = "rightClickBankLoot", + name = "Disable left click bank looting bag", + description = "Configures whether the bank looting bag button will bank your looting bag contents on left click", + position = 6 + ) + default boolean rightClickBankLoot() + { + return false; + } + + @ConfigItem( + keyName = "seedVaultValue", + name = "Show seed vault value", + description = "Adds the total value of all seeds inside the seed vault to the title", + position = 7 + ) + default boolean seedVaultValue() + { + return true; + } + + @ConfigItem( + keyName = "bankPinKeyboard", + name = "Keyboard Bankpin", + description = "Allows using the keyboard keys for bank pin input", + position = 8 + ) + default boolean bankPinKeyboard() + { + return false; + } + + @ConfigItem( + keyName = "searchKeybind", + name = "Search Shortcut", + description = "Keyboard shortcut for initiating a bank search", + position = 9 + ) + default Keybind searchKeybind() + { + return new Keybind(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java new file mode 100644 index 0000000000..1741a87015 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2018, TheLonelyDev + * Copyright (c) 2018, Jeremy Plsek + * Copyright (c) 2019, Hydrox6 + * 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.bank; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import java.text.ParseException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import javax.inject.Inject; +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 net.runelite.api.MenuEntry; +import net.runelite.api.ScriptID; +import net.runelite.api.VarClientStr; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuShouldLeftClick; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.JavaScriptCallback; +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.config.Keybind; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.QuantityFormatter; + +@PluginDescriptor( + name = "Bank", + description = "Modifications to the banking interface", + tags = {"grand", "exchange", "high", "alchemy", "prices", "deposit"} +) +@Slf4j +public class BankPlugin extends Plugin +{ + private static final String DEPOSIT_WORN = "Deposit worn items"; + private static final String DEPOSIT_INVENTORY = "Deposit inventory"; + private static final String DEPOSIT_LOOT = "Deposit loot"; + private static final String SEED_VAULT_TITLE = "Seed Vault"; + + private static final String NUMBER_REGEX = "[0-9]+(\\.[0-9]+)?[kmb]?"; + private static final Pattern VALUE_SEARCH_PATTERN = Pattern.compile("^(?ge|ha|alch)?" + + " *(((?[<>=]|>=|<=) *(?" + NUMBER_REGEX + "))|" + + "((?" + NUMBER_REGEX + ") *- *(?" + NUMBER_REGEX + ")))$", Pattern.CASE_INSENSITIVE); + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ItemManager itemManager; + + @Inject + private BankConfig config; + + @Inject + private BankSearch bankSearch; + + @Inject + private KeyManager keyManager; + + private boolean forceRightClickFlag; + private Multiset itemQuantities; // bank item quantities for bank value search + private String searchString; + + private final KeyListener searchHotkeyListener = new KeyListener() + { + @Override + public void keyTyped(KeyEvent e) + { + } + + @Override + public void keyPressed(KeyEvent e) + { + Keybind keybind = config.searchKeybind(); + if (keybind.matches(e)) + { + Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (bankContainer == null || bankContainer.isSelfHidden()) + { + return; + } + + log.debug("Search hotkey pressed"); + + bankSearch.initSearch(); + e.consume(); + } + } + + @Override + public void keyReleased(KeyEvent e) + { + } + }; + + @Provides + BankConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(BankConfig.class); + } + + @Override + protected void startUp() + { + keyManager.registerKeyListener(searchHotkeyListener); + } + + @Override + protected void shutDown() + { + keyManager.unregisterKeyListener(searchHotkeyListener); + clientThread.invokeLater(() -> bankSearch.reset(false)); + forceRightClickFlag = false; + itemQuantities = null; + searchString = null; + } + + @Subscribe + public void onMenuShouldLeftClick(MenuShouldLeftClick event) + { + if (!forceRightClickFlag) + { + return; + } + + forceRightClickFlag = false; + MenuEntry[] menuEntries = client.getMenuEntries(); + for (MenuEntry entry : menuEntries) + { + if ((entry.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip()) + || (entry.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory()) + || (entry.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot())) + { + event.setForceRightClick(true); + return; + } + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + if ((event.getOption().equals(DEPOSIT_WORN) && config.rightClickBankEquip()) + || (event.getOption().equals(DEPOSIT_INVENTORY) && config.rightClickBankInventory()) + || (event.getOption().equals(DEPOSIT_LOOT) && config.rightClickBankLoot())) + { + forceRightClickFlag = true; + } + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + int[] intStack = client.getIntStack(); + String[] stringStack = client.getStringStack(); + int intStackSize = client.getIntStackSize(); + int stringStackSize = client.getStringStackSize(); + + switch (event.getEventName()) + { + case "bankSearchFilter": + int itemId = intStack[intStackSize - 1]; + String search = stringStack[stringStackSize - 1]; + + if (valueSearch(itemId, search)) + { + // return true + intStack[intStackSize - 2] = 1; + } + + break; + case "bankpinButtonSetup": + { + if (!config.bankPinKeyboard()) + { + return; + } + + final int compId = intStack[intStackSize - 2]; + final int buttonId = intStack[intStackSize - 1]; + //TODO Implement client.getWidget(compId) + Widget button = null; + Widget buttonRect = button.getChild(0); + + final Object[] onOpListener = buttonRect.getOnOpListener(); + buttonRect.setOnKeyListener((JavaScriptCallback) e -> + { + int typedChar = e.getTypedKeyChar() - '0'; + if (typedChar != buttonId) + { + return; + } + + log.debug("Bank pin keypress"); + + final String input = client.getVar(VarClientStr.CHATBOX_TYPED_TEXT); + clientThread.invokeLater(() -> + { + // reset chatbox input to avoid pin going to chatbox.. + client.setVar(VarClientStr.CHATBOX_TYPED_TEXT, input); + client.runScript(ScriptID.CHAT_PROMPT_INIT); + + client.runScript(onOpListener); + }); + }); + break; + } + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() != WidgetID.SEED_VAULT_GROUP_ID || !config.seedVaultValue()) + { + return; + } + + updateSeedVaultTotal(); + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (event.getScriptId() == ScriptID.BANKMAIN_BUILD) + { + // Compute bank prices using only the shown items so that we can show bank value during searches + final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + final ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + final Widget[] children = bankItemContainer.getChildren(); + long geTotal = 0, haTotal = 0; + + if (children != null) + { + log.debug("Computing bank price of {} items", bankContainer.size()); + + // The first components are the bank items, followed by tabs etc. There are always 816 components regardless + // of bank size, but we only need to check up to the bank size. + for (int i = 0; i < bankContainer.size(); ++i) + { + Widget child = children[i]; + if (child != null && !child.isSelfHidden() && child.getItemId() > -1) + { + final int alchPrice = getHaPrice(child.getItemId()); + geTotal += (long) itemManager.getItemPrice(child.getItemId()) * child.getItemQuantity(); + haTotal += (long) alchPrice * child.getItemQuantity(); + } + } + + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + bankTitle.setText(bankTitle.getText() + createValueText(geTotal, haTotal)); + } + } + else if (event.getScriptId() == ScriptID.BANKMAIN_SEARCH_REFRESH) + { + // vanilla only lays out the bank every 40 client ticks, so if the search input has changed, + // and the bank wasn't laid out this tick, lay it out early + final String inputText = client.getVar(VarClientStr.INPUT_TEXT); + if (searchString != inputText && client.getGameCycle() % 40 != 0) + { + clientThread.invokeLater(bankSearch::layoutBank); + searchString = inputText; + } + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + int containerId = event.getContainerId(); + + if (containerId == InventoryID.BANK.getId()) + { + itemQuantities = null; + } + else if (containerId == InventoryID.SEED_VAULT.getId() && config.seedVaultValue()) + { + updateSeedVaultTotal(); + } + } + + private String createValueText(long gePrice, long haPrice) + { + StringBuilder stringBuilder = new StringBuilder(); + if (config.showGE() && gePrice != 0) + { + stringBuilder.append(" ("); + + if (config.showHA()) + { + stringBuilder.append("GE: "); + } + + if (config.showExact()) + { + stringBuilder.append(QuantityFormatter.formatNumber(gePrice)); + } + else + { + stringBuilder.append(QuantityFormatter.quantityToStackSize(gePrice)); + } + stringBuilder.append(')'); + } + + if (config.showHA() && haPrice != 0) + { + stringBuilder.append(" ("); + + if (config.showGE()) + { + stringBuilder.append("HA: "); + } + + if (config.showExact()) + { + stringBuilder.append(QuantityFormatter.formatNumber(haPrice)); + } + else + { + stringBuilder.append(QuantityFormatter.quantityToStackSize(haPrice)); + } + stringBuilder.append(')'); + } + + return stringBuilder.toString(); + } + + private void updateSeedVaultTotal() + { + final Widget titleContainer = client.getWidget(WidgetInfo.SEED_VAULT_TITLE_CONTAINER); + if (titleContainer == null) + { + return; + } + + final Widget title = titleContainer.getChild(1); + if (title == null) + { + return; + } + + final ContainerPrices prices = calculate(getSeedVaultItems()); + if (prices == null) + { + return; + } + + final String titleText = createValueText(prices.getGePrice(), prices.getHighAlchPrice()); + title.setText(SEED_VAULT_TITLE + titleText); + } + + private Item[] getSeedVaultItems() + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.SEED_VAULT); + if (itemContainer == null) + { + return null; + } + + return itemContainer.getItems(); + } + + + @VisibleForTesting + boolean valueSearch(final int itemId, final String str) + { + final Matcher matcher = VALUE_SEARCH_PATTERN.matcher(str); + if (!matcher.matches()) + { + return false; + } + + // Count bank items and remember it for determining item quantity + if (itemQuantities == null) + { + itemQuantities = getBankItemSet(); + } + + final ItemComposition itemComposition = itemManager.getItemComposition(itemId); + final int qty = itemQuantities.count(itemId); + final long gePrice = (long) itemManager.getItemPrice(itemId) * qty; + final long haPrice = (long) itemComposition.getHaPrice() * qty; + + long value = Math.max(gePrice, haPrice); + + final String mode = matcher.group("mode"); + if (mode != null) + { + value = mode.toLowerCase().equals("ge") ? gePrice : haPrice; + } + + final String op = matcher.group("op"); + if (op != null) + { + long compare; + try + { + compare = QuantityFormatter.parseQuantity(matcher.group("num")); + } + catch (ParseException e) + { + return false; + } + + switch (op) + { + case ">": + return value > compare; + case "<": + return value < compare; + case "=": + return value == compare; + case ">=": + return value >= compare; + case "<=": + return value <= compare; + } + } + + final String num1 = matcher.group("num1"); + final String num2 = matcher.group("num2"); + if (num1 != null && num2 != null) + { + long compare1, compare2; + try + { + compare1 = QuantityFormatter.parseQuantity(num1); + compare2 = QuantityFormatter.parseQuantity(num2); + } + catch (ParseException e) + { + return false; + } + + return compare1 <= value && compare2 >= value; + } + + return false; + } + + private Multiset getBankItemSet() + { + ItemContainer itemContainer = client.getItemContainer(InventoryID.BANK); + if (itemContainer == null) + { + return HashMultiset.create(); + } + + Multiset set = HashMultiset.create(); + for (Item item : itemContainer.getItems()) + { + if (item.getId() != ItemID.BANK_FILLER) + { + set.add(item.getId(), item.getQuantity()); + } + } + return set; + } + + @Nullable + ContainerPrices calculate(@Nullable Item[] items) + { + if (items == null) + { + return null; + } + + long ge = 0; + long alch = 0; + + for (final Item item : items) + { + final int qty = item.getQuantity(); + final int id = item.getId(); + + if (id <= 0 || qty == 0) + { + continue; + } + + alch += (long) getHaPrice(id) * qty; + ge += (long) itemManager.getItemPrice(id) * qty; + } + + return new ContainerPrices(ge, alch); + } + + private int getHaPrice(int itemId) + { + switch (itemId) + { + case ItemID.COINS_995: + return 1; + case ItemID.PLATINUM_TOKEN: + return 1000; + default: + return itemManager.getItemComposition(itemId).getHaPrice(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java new file mode 100644 index 0000000000..73e00b436e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankSearch.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.bank; + +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.api.Client; +import net.runelite.api.ScriptID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; +import net.runelite.api.vars.InputType; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; +import org.apache.commons.lang3.ArrayUtils; + +@Singleton +public class BankSearch +{ + private final Client client; + private final ClientThread clientThread; + + @Inject + private BankSearch( + final Client client, + final ClientThread clientThread + ) + { + this.client = client; + this.clientThread = clientThread; + } + + public void layoutBank() + { + Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (bankContainer == null || bankContainer.isHidden()) + { + return; + } + + Object[] scriptArgs = bankContainer.getOnInvTransmitListener(); + if (scriptArgs == null) + { + return; + } + + client.runScript(scriptArgs); + } + + public void initSearch() + { + clientThread.invoke(() -> + { + Widget bankContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (bankContainer == null || bankContainer.isHidden()) + { + return; + } + + Object[] bankBuildArgs = bankContainer.getOnInvTransmitListener(); + if (bankBuildArgs == null) + { + return; + } + + // the search toggle script requires 1 as its first argument + Object[] searchToggleArgs = ArrayUtils.insert(1, bankBuildArgs, 1); + searchToggleArgs[0] = ScriptID.BANKMAIN_SEARCH_TOGGLE; + + // reset search to clear tab tags and also allow us to initiate a new search while searching + reset(true); + client.runScript(searchToggleArgs); + }); + } + + public void reset(boolean closeChat) + { + clientThread.invoke(() -> + { + // This ensures that any chatbox input (e.g from search) will not remain visible when + // selecting/changing tab + if (closeChat) + { + // this clears the input text and type, and resets the chatbox to allow input + client.runScript(ScriptID.MESSAGE_LAYER_CLOSE, 1, 1); + } + else + { + client.setVar(VarClientInt.INPUT_TYPE, InputType.NONE.getType()); + client.setVar(VarClientStr.INPUT_TEXT, ""); + } + + layoutBank(); + }); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java new file mode 100644 index 0000000000..5762218e18 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/ContainerPrices.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, 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.bank; + +import lombok.Value; + +@Value +class ContainerPrices +{ + private long gePrice; + private long highAlchPrice; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java new file mode 100644 index 0000000000..6efc69b613 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("banktags") +public interface BankTagsConfig extends Config +{ + @ConfigItem( + keyName = "useTabs", + name = "Use Tag Tabs", + description = "Enable the ability to add tabs to your bank which allow fast access to tags.", + position = 1 + ) + default boolean tabs() + { + return true; + } + + @ConfigItem( + keyName = "rememberTab", + name = "Remember last Tag Tab", + description = "Enable the ability to remember last Tag Tab when closing/opening the bank.", + position = 2 + ) + default boolean rememberTab() + { + return true; + } + + @ConfigItem( + keyName = "removeTabSeparators", + name = "Remove tab separators", + description = "Remove the tab separators normally present in tag tabs", + position = 3 + ) + default boolean removeSeparators() + { + return false; + } + + @ConfigItem( + keyName = "preventTagTabDrags", + name = "Prevent tag tab item dragging", + description = "Ignore dragged items to prevent unwanted bank item reordering", + position = 4 + ) + default boolean preventTagTabDrags() + { + return false; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "", + hidden = true + ) + default int position() + { + return 0; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "" + ) + void position(int idx); + + @ConfigItem( + keyName = "tab", + name = "", + description = "", + hidden = true + ) + default String tab() + { + return ""; + } + + @ConfigItem( + keyName = "tab", + name = "", + description = "" + ) + void tab(String tab); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java new file mode 100644 index 0000000000..f03f730e6a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2018, Adam + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import com.google.common.collect.Lists; +import com.google.common.primitives.Shorts; +import com.google.inject.Provides; +import java.awt.event.MouseWheelEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.inject.Inject; +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.KeyCode; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.ScriptID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientStr; +import net.runelite.api.events.DraggingWidgetChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.GrandExchangeSearched; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.ScriptPreFired; +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.ConfigChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemVariationMapping; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.input.MouseWheelListener; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDependency; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.banktags.tabs.TabInterface; +import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS; +import net.runelite.client.plugins.banktags.tabs.TabSprites; +import net.runelite.client.plugins.banktags.tabs.TagTab; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Bank Tags", + description = "Enable tagging of bank items and searching of bank tags", + tags = {"searching", "tagging"} +) +@PluginDependency(ClueScrollPlugin.class) +public class BankTagsPlugin extends Plugin implements MouseWheelListener +{ + public static final String CONFIG_GROUP = "banktags"; + public static final String TAG_SEARCH = "tag:"; + private static final String EDIT_TAGS_MENU_OPTION = "Edit-tags"; + public static final String ICON_SEARCH = "icon_"; + public static final String TAG_TABS_CONFIG = "tagtabs"; + public static final String VAR_TAG_SUFFIX = "*"; + private static final int ITEMS_PER_ROW = 8; + private static final int ITEM_VERTICAL_SPACING = 36; + private static final int ITEM_HORIZONTAL_SPACING = 48; + private static final int ITEM_ROW_START = 51; + + private static final int MAX_RESULT_COUNT = 250; + + private static final String SEARCH_BANK_INPUT_TEXT = + "Show items whose names or tags contain the following text:
" + + "(To show only tagged items, start your search with 'tag:')"; + private static final String SEARCH_BANK_INPUT_TEXT_FOUND = + "Show items whose names or tags contain the following text: (%d found)
" + + "(To show only tagged items, start your search with 'tag:')"; + + @Inject + private ItemManager itemManager; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private MouseManager mouseManager; + + @Inject + private BankTagsConfig config; + + @Inject + private TagManager tagManager; + + @Inject + private TabInterface tabInterface; + + @Inject + private SpriteManager spriteManager; + + @Inject + private ConfigManager configManager; + + @Provides + BankTagsConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(BankTagsConfig.class); + } + + @Override + public void resetConfiguration() + { + List extraKeys = Lists.newArrayList( + CONFIG_GROUP + "." + TagManager.ITEM_KEY_PREFIX, + CONFIG_GROUP + "." + ICON_SEARCH, + CONFIG_GROUP + "." + TAG_TABS_CONFIG + ); + + for (String prefix : extraKeys) + { + List keys = configManager.getConfigurationKeys(prefix); + for (String key : keys) + { + String[] str = key.split("\\.", 2); + if (str.length == 2) + { + configManager.unsetConfiguration(str[0], str[1]); + } + } + } + + clientThread.invokeLater(() -> + { + tabInterface.destroy(); + tabInterface.init(); + }); + } + + + @Override + public void startUp() + { + cleanConfig(); + mouseManager.registerMouseWheelListener(this); + clientThread.invokeLater(tabInterface::init); + spriteManager.addSpriteOverrides(TabSprites.values()); + } + + @Deprecated + private void cleanConfig() + { + removeInvalidTags("tagtabs"); + + List tags = configManager.getConfigurationKeys(CONFIG_GROUP + ".item_"); + tags.forEach(s -> + { + String[] split = s.split("\\.", 2); + removeInvalidTags(split[1]); + }); + + List icons = configManager.getConfigurationKeys(CONFIG_GROUP + ".icon_"); + icons.forEach(s -> + { + String[] split = s.split("\\.", 2); + String replaced = split[1].replaceAll("[<>/]", ""); + if (!split[1].equals(replaced)) + { + String value = configManager.getConfiguration(CONFIG_GROUP, split[1]); + configManager.unsetConfiguration(CONFIG_GROUP, split[1]); + if (replaced.length() > "icon_".length()) + { + configManager.setConfiguration(CONFIG_GROUP, replaced, value); + } + } + }); + } + + @Deprecated + private void removeInvalidTags(final String key) + { + final String value = configManager.getConfiguration(CONFIG_GROUP, key); + if (value == null) + { + return; + } + + String replaced = value.replaceAll("[<>:/]", ""); + if (!value.equals(replaced)) + { + replaced = Text.toCSV(Text.fromCSV(replaced)); + if (replaced.isEmpty()) + { + configManager.unsetConfiguration(CONFIG_GROUP, key); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, key, replaced); + } + } + } + + @Override + public void shutDown() + { + mouseManager.unregisterMouseWheelListener(this); + clientThread.invokeLater(tabInterface::destroy); + spriteManager.removeSpriteOverrides(TabSprites.values()); + } + + @Subscribe + public void onGrandExchangeSearched(GrandExchangeSearched event) + { + final String input = client.getVar(VarClientStr.INPUT_TEXT); + if (!input.startsWith(TAG_SEARCH)) + { + return; + } + + event.consume(); + + final String tag = input.substring(TAG_SEARCH.length()).trim(); + final Set ids = tagManager.getItemsForTag(tag) + .stream() + .mapToInt(Math::abs) + .mapToObj(ItemVariationMapping::getVariations) + .flatMap(Collection::stream) + .distinct() + .filter(i -> itemManager.getItemComposition(i).isTradeable()) + .limit(MAX_RESULT_COUNT) + .collect(Collectors.toCollection(TreeSet::new)); + + client.setGeSearchResultIndex(0); + client.setGeSearchResultCount(ids.size()); + client.setGeSearchResultIds(Shorts.toArray(ids)); + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + String eventName = event.getEventName(); + + int[] intStack = client.getIntStack(); + String[] stringStack = client.getStringStack(); + int intStackSize = client.getIntStackSize(); + int stringStackSize = client.getStringStackSize(); + + tabInterface.handleScriptEvent(event); + + switch (eventName) + { + case "setSearchBankInputText": + stringStack[stringStackSize - 1] = SEARCH_BANK_INPUT_TEXT; + break; + case "setSearchBankInputTextFound": + { + int matches = intStack[intStackSize - 1]; + stringStack[stringStackSize - 1] = String.format(SEARCH_BANK_INPUT_TEXT_FOUND, matches); + break; + } + case "bankSearchFilter": + final int itemId = intStack[intStackSize - 1]; + final String searchfilter = stringStack[stringStackSize - 1]; + + // This event only fires when the bank is in search mode. It will fire even if there is no search + // input. We prevent having a tag tab open while also performing a normal search, so if a tag tab + // is active here it must mean we have placed the bank into search mode. See onScriptPostFired(). + TagTab activeTab = tabInterface.getActiveTab(); + String search = activeTab != null ? TAG_SEARCH + activeTab.getTag() : searchfilter; + + if (search.isEmpty()) + { + return; + } + + boolean tagSearch = search.startsWith(TAG_SEARCH); + if (tagSearch) + { + search = search.substring(TAG_SEARCH.length()).trim(); + } + + if (tagManager.findTag(itemId, search)) + { + // return true + intStack[intStackSize - 2] = 1; + } + else if (tagSearch) + { + // if the item isn't tagged we return false to prevent the item matching if the item name happens + // to contain the tag name. + intStack[intStackSize - 2] = 0; + } + break; + case "getSearchingTagTab": + intStack[intStackSize - 1] = tabInterface.isActive() ? 1 : 0; + break; + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + MenuEntry[] entries = client.getMenuEntries(); + + if (event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + Widget item = container.getChild(event.getActionParam()); + int itemID = item.getItemId(); + String text = EDIT_TAGS_MENU_OPTION; + int tagCount = tagManager.getTags(itemID, false).size() + tagManager.getTags(itemID, true).size(); + + if (tagCount > 0) + { + text += " (" + tagCount + ")"; + } + + MenuEntry editTags = new MenuEntry(); + editTags.setActionParam(event.getActionParam()); + editTags.setParam1(event.getActionParam1()); + editTags.setTarget(event.getTarget()); + editTags.setOption(text); + editTags.setType(MenuAction.RUNELITE.getId()); + editTags.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = editTags; + client.setMenuEntries(entries); + } + + tabInterface.handleAdd(event); + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getMenuAction() == MenuAction.RUNELITE + && event.getMenuOption().startsWith(EDIT_TAGS_MENU_OPTION)) + { + event.consume(); + int inventoryIndex = event.getActionParam(); + ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + if (bankContainer == null) + { + return; + } + Item[] items = bankContainer.getItems(); + if (inventoryIndex < 0 || inventoryIndex >= items.length) + { + return; + } + Item item = bankContainer.getItems()[inventoryIndex]; + if (item == null) + { + return; + } + + int itemId = item.getId(); + ItemComposition itemComposition = itemManager.getItemComposition(itemId); + String name = itemComposition.getName(); + + // Get both tags and vartags and append * to end of vartags name + Collection tags = tagManager.getTags(itemId, false); + tagManager.getTags(itemId, true).stream() + .map(i -> i + "*") + .forEach(tags::add); + + String initialValue = Text.toCSV(tags); + + chatboxPanelManager.openTextInput(name + " tags:
(append " + VAR_TAG_SUFFIX + " for variation tag)") + .addCharValidator(FILTERED_CHARS) + .value(initialValue) + .onDone((Consumer) (newValue) -> + clientThread.invoke(() -> + { + // Split inputted tags to vartags (ending with *) and regular tags + final Collection newTags = new ArrayList<>(Text.fromCSV(newValue.toLowerCase())); + final Collection newVarTags = new ArrayList<>(newTags).stream().filter(s -> s.endsWith(VAR_TAG_SUFFIX)).map(s -> + { + newTags.remove(s); + return s.substring(0, s.length() - VAR_TAG_SUFFIX.length()); + }).collect(Collectors.toList()); + + // And save them + tagManager.setTagString(itemId, Text.toCSV(newTags), false); + tagManager.setTagString(itemId, Text.toCSV(newVarTags), true); + + // Check both previous and current tags in case the tag got removed in new tags or in case + // the tag got added in new tags + tabInterface.updateTabIfActive(Text.fromCSV(initialValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); + tabInterface.updateTabIfActive(Text.fromCSV(newValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); + })) + .build(); + } + else + { + tabInterface.handleClick(event); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (configChanged.getGroup().equals(CONFIG_GROUP) && configChanged.getKey().equals("useTabs")) + { + if (config.tabs()) + { + clientThread.invokeLater(tabInterface::init); + } + else + { + clientThread.invokeLater(tabInterface::destroy); + } + } + } + + @Subscribe + public void onScriptPreFired(ScriptPreFired event) + { + int scriptId = event.getScriptId(); + if (scriptId == ScriptID.BANKMAIN_FINISHBUILDING) + { + // Since we apply tag tab search filters even when the bank is not in search mode, + // bankkmain_build will reset the bank title to "The Bank of Gielinor". So apply our + // own title. + TagTab activeTab = tabInterface.getActiveTab(); + if (tabInterface.isTagTabActive()) + { + // Tag tab tab has its own title since it isn't a real tag + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + bankTitle.setText("Tag tab tab"); + } + else if (activeTab != null) + { + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + bankTitle.setText("Tag tab " + activeTab.getTag() + ""); + } + } + else if (scriptId == ScriptID.BANKMAIN_SEARCH_TOGGLE) + { + tabInterface.handleSearch(); + } + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (event.getScriptId() == ScriptID.BANKMAIN_SEARCHING) + { + // The return value of bankmain_searching is on the stack. If we have a tag tab active + // make it return true to put the bank in a searching state. + if (tabInterface.getActiveTab() != null || tabInterface.isTagTabActive()) + { + client.getIntStack()[client.getIntStackSize() - 1] = 1; // true + } + return; + } + + if (event.getScriptId() != ScriptID.BANKMAIN_BUILD || !config.removeSeparators()) + { + return; + } + + if (!tabInterface.isActive()) + { + return; + } + + Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (itemContainer == null) + { + return; + } + + int items = 0; + + Widget[] containerChildren = itemContainer.getDynamicChildren(); + + // sort the child array as the items are not in the displayed order + Arrays.sort(containerChildren, Comparator.comparing(Widget::getOriginalY) + .thenComparing(Widget::getOriginalX)); + + for (Widget child : containerChildren) + { + if (child.getItemId() != -1 && !child.isHidden()) + { + // calculate correct item position as if this was a normal tab + int adjYOffset = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING; + int adjXOffset = (items % ITEMS_PER_ROW) * ITEM_HORIZONTAL_SPACING + ITEM_ROW_START; + + if (child.getOriginalY() != adjYOffset) + { + child.setOriginalY(adjYOffset); + child.revalidate(); + } + + if (child.getOriginalX() != adjXOffset) + { + child.setOriginalX(adjXOffset); + child.revalidate(); + } + + items++; + } + + // separator line or tab text + if (child.getSpriteId() == SpriteID.RESIZEABLE_MODE_SIDE_PANEL_BACKGROUND + || child.getText().contains("Tab")) + { + child.setHidden(true); + } + } + + final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + int itemContainerHeight = bankItemContainer.getHeight(); + // add a second row of height here to allow users to scroll down when the last row is partially visible + int adjustedScrollHeight = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING + ITEM_VERTICAL_SPACING; + itemContainer.setScrollHeight(Math.max(adjustedScrollHeight, itemContainerHeight)); + + final int itemContainerScroll = bankItemContainer.getScrollY(); + clientThread.invokeLater(() -> + client.runScript(ScriptID.UPDATE_SCROLLBAR, + WidgetInfo.BANK_SCROLLBAR.getId(), + WidgetInfo.BANK_ITEM_CONTAINER.getId(), + itemContainerScroll)); + + } + + @Subscribe + public void onGameTick(GameTick event) + { + tabInterface.update(); + } + + @Subscribe + public void onDraggingWidgetChanged(DraggingWidgetChanged event) + { + final boolean shiftPressed = client.isKeyPressed(KeyCode.KC_SHIFT); + tabInterface.handleDrag(event.isDraggingWidget(), shiftPressed); + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() == WidgetID.BANK_GROUP_ID) + { + tabInterface.init(); + } + } + + @Override + public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event) + { + tabInterface.handleWheel(event); + return event; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java new file mode 100644 index 0000000000..fe697034d0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import com.google.common.base.Strings; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemVariationMapping; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import net.runelite.client.plugins.cluescrolls.ClueScrollService; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue; +import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; +import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue; +import net.runelite.client.plugins.cluescrolls.clues.HotColdClue; +import net.runelite.client.plugins.cluescrolls.clues.MapClue; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import net.runelite.client.util.Text; + +@Singleton +public class TagManager +{ + static final String ITEM_KEY_PREFIX = "item_"; + private final ConfigManager configManager; + private final ItemManager itemManager; + private final ClueScrollService clueScrollService; + + @Inject + private TagManager( + final ItemManager itemManager, + final ConfigManager configManager, + final ClueScrollService clueScrollService) + { + this.itemManager = itemManager; + this.configManager = configManager; + this.clueScrollService = clueScrollService; + } + + String getTagString(int itemId, boolean variation) + { + itemId = getItemId(itemId, variation); + + String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + if (config == null) + { + return ""; + } + + return config; + } + + Collection getTags(int itemId, boolean variation) + { + return new LinkedHashSet<>(Text.fromCSV(getTagString(itemId, variation).toLowerCase())); + } + + void setTagString(int itemId, String tags, boolean variation) + { + itemId = getItemId(itemId, variation); + + if (Strings.isNullOrEmpty(tags)) + { + configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags); + } + } + + public void addTags(int itemId, final Collection t, boolean variation) + { + final Collection tags = getTags(itemId, variation); + if (tags.addAll(t)) + { + setTags(itemId, tags, variation); + } + } + + public void addTag(int itemId, String tag, boolean variation) + { + final Collection tags = getTags(itemId, variation); + if (tags.add(Text.standardize(tag))) + { + setTags(itemId, tags, variation); + } + } + + private void setTags(int itemId, Collection tags, boolean variation) + { + setTagString(itemId, Text.toCSV(tags), variation); + } + + boolean findTag(int itemId, String search) + { + if (search.equals("clue") && testClue(itemId)) + { + return true; + } + + Collection tags = getTags(itemId, false); + tags.addAll(getTags(itemId, true)); + return tags.stream().anyMatch(tag -> tag.startsWith(Text.standardize(search))); + } + + public List getItemsForTag(String tag) + { + final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; + return configManager.getConfigurationKeys(prefix).stream() + .map(item -> Integer.parseInt(item.replace(prefix, ""))) + .filter(item -> getTags(item, false).contains(tag) || getTags(item, true).contains(tag)) + .collect(Collectors.toList()); + } + + public void removeTag(String tag) + { + final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; + configManager.getConfigurationKeys(prefix).forEach(item -> + { + int id = Integer.parseInt(item.replace(prefix, "")); + removeTag(id, tag); + }); + } + + public void removeTag(int itemId, String tag) + { + Collection tags = getTags(itemId, false); + if (tags.remove(Text.standardize(tag))) + { + setTags(itemId, tags, false); + } + + tags = getTags(itemId, true); + if (tags.remove(Text.standardize(tag))) + { + setTags(itemId, tags, true); + } + } + + public void renameTag(String oldTag, String newTag) + { + List items = getItemsForTag(Text.standardize(oldTag)); + items.forEach(id -> + { + Collection tags = getTags(id, id < 0); + + tags.remove(Text.standardize(oldTag)); + tags.add(Text.standardize(newTag)); + + setTags(id, tags, id < 0); + }); + } + + private int getItemId(int itemId, boolean variation) + { + itemId = Math.abs(itemId); + itemId = itemManager.canonicalize(itemId); + + if (variation) + { + itemId = ItemVariationMapping.map(itemId) * -1; + } + + return itemId; + } + + private boolean testClue(int itemId) + { + ClueScroll c = clueScrollService.getClue(); + + if (c == null) + { + return false; + } + + if (c instanceof EmoteClue) + { + EmoteClue emote = (EmoteClue) c; + + for (ItemRequirement ir : emote.getItemRequirements()) + { + if (ir.fulfilledBy(itemId)) + { + return true; + } + } + } + else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue) + { + return itemId == ItemID.SPADE; + } + else if (c instanceof MapClue) + { + MapClue mapClue = (MapClue) c; + + return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE; + } + + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java new file mode 100644 index 0000000000..bcf03fd585 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +class MenuIndexes +{ + static class NewTab + { + static final int NEW_TAB = 2; + static final int IMPORT_TAB = 3; + static final int OPEN_TAB_MENU = 4; + } + + static class Tab + { + static final int OPEN_TAG = 2; + static final int CHANGE_ICON = 3; + static final int DELETE_TAB = 4; + static final int EXPORT_TAB = 5; + static final int RENAME_TAB = 6; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java new file mode 100644 index 0000000000..d24ef55605 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java @@ -0,0 +1,1198 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Runnables; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.MouseWheelEvent; +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.IntPredicate; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Point; +import net.runelite.api.ScriptEvent; +import net.runelite.api.ScriptID; +import net.runelite.api.SoundEffectID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; +import net.runelite.api.Varbits; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.widgets.ItemQuantityMode; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.Notifier; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.chatbox.ChatboxItemSearch; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.plugins.bank.BankSearch; +import net.runelite.client.plugins.banktags.BankTagsConfig; +import net.runelite.client.plugins.banktags.BankTagsPlugin; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX; +import net.runelite.client.plugins.banktags.TagManager; +import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.NewTab; +import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.Tab; +import net.runelite.client.ui.JagexColors; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; + +@Singleton +public class TabInterface +{ + public static final IntPredicate FILTERED_CHARS = c -> ":".indexOf(c) == -1; + + private static final Color HILIGHT_COLOR = JagexColors.MENU_TARGET; + private static final String SCROLL_UP = "Scroll up"; + private static final String SCROLL_DOWN = "Scroll down"; + private static final String NEW_TAB = "New tag tab"; + private static final String REMOVE_TAB = "Delete tag tab"; + private static final String EXPORT_TAB = "Export tag tab"; + private static final String IMPORT_TAB = "Import tag tab"; + private static final String VIEW_TAB = "View tag tab"; + private static final String RENAME_TAB = "Rename tag tab"; + private static final String CHANGE_ICON = "Change icon"; + private static final String REMOVE_TAG = "Remove-tag"; + private static final String TAG_GEAR = "Tag-equipment"; + private static final String TAG_INVENTORY = "Tag-inventory"; + private static final String TAB_MENU_KEY = "tagtabs"; + private static final String OPEN_TAB_MENU = "View tag tabs"; + private static final String SHOW_WORN = "Show worn items"; + private static final String SHOW_SETTINGS = "Show menu"; + private static final String SHOW_TUTORIAL = "Show tutorial"; + private static final int TAB_HEIGHT = 40; + private static final int TAB_WIDTH = 39; + private static final int BUTTON_HEIGHT = 20; + private static final int MARGIN = 1; + private static final int SCROLL_TICK = 500; + private static final int INCINERATOR_WIDTH = 48; + private static final int INCINERATOR_HEIGHT = 39; + private static final int BANK_ITEM_WIDTH = 36; + private static final int BANK_ITEM_HEIGHT = 32; + private static final int BANK_ITEM_X_PADDING = 12; + private static final int BANK_ITEM_Y_PADDING = 4; + private static final int BANK_ITEMS_PER_ROW = 8; + private static final int BANK_ITEM_START_X = 51; + private static final int BANK_ITEM_START_Y = 0; + + private final Client client; + private final ClientThread clientThread; + private final ItemManager itemManager; + private final TagManager tagManager; + private final TabManager tabManager; + private final ChatboxPanelManager chatboxPanelManager; + private final BankTagsConfig config; + private final Notifier notifier; + private final BankSearch bankSearch; + private final ChatboxItemSearch searchProvider; + private final Rectangle bounds = new Rectangle(); + private final Rectangle canvasBounds = new Rectangle(); + + @Getter + private TagTab activeTab; + @Getter + private boolean tagTabActive; + private int maxTabs; + private int currentTabIndex; + private Instant startScroll = Instant.now(); + + @Getter + private Widget upButton; + + @Getter + private Widget downButton; + + @Getter + private Widget newTab; + + @Getter + private Widget parent; + + @Inject + private TabInterface( + final Client client, + final ClientThread clientThread, + final ItemManager itemManager, + final TagManager tagManager, + final TabManager tabManager, + final ChatboxPanelManager chatboxPanelManager, + final BankTagsConfig config, + final Notifier notifier, + final BankSearch bankSearch, + final ChatboxItemSearch searchProvider) + { + this.client = client; + this.clientThread = clientThread; + this.itemManager = itemManager; + this.tagManager = tagManager; + this.tabManager = tabManager; + this.chatboxPanelManager = chatboxPanelManager; + this.config = config; + this.notifier = notifier; + this.bankSearch = bankSearch; + this.searchProvider = searchProvider; + } + + public boolean isActive() + { + return activeTab != null; + } + + public void init() + { + if (isHidden()) + { + return; + } + + currentTabIndex = config.position(); + parent = client.getWidget(WidgetInfo.BANK_CONTENT_CONTAINER); + + updateBounds(); + + upButton = createGraphic("", TabSprites.UP_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + upButton.setAction(1, SCROLL_UP); + int clickmask = upButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + upButton.setClickMask(clickmask); + upButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(-1)); + + downButton = createGraphic("", TabSprites.DOWN_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + downButton.setAction(1, SCROLL_DOWN); + clickmask = downButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + downButton.setClickMask(clickmask); + downButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(1)); + + newTab = createGraphic("", TabSprites.NEW_TAB.getSpriteId(), -1, TAB_WIDTH, 39, bounds.x, 0, true); + newTab.setAction(1, NEW_TAB); + newTab.setAction(2, IMPORT_TAB); + newTab.setAction(3, OPEN_TAB_MENU); + newTab.setOnOpListener((JavaScriptCallback) this::handleNewTab); + + tabManager.clear(); + tabManager.getAllTabs().forEach(this::loadTab); + activateTab(null); + scrollTab(0); + + if (config.rememberTab() && !Strings.isNullOrEmpty(config.tab())) + { + // the server will resync the last opened vanilla tab when the bank is opened + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + openTag(config.tab()); + } + + Widget equipmentButton = client.getWidget(WidgetInfo.BANK_EQUIPMENT_BUTTON); + Widget titleBar = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + if (equipmentButton == null || titleBar == null || titleBar.getOriginalX() > 0) + { + // don't keep moving widgets if they have already been moved + return; + } + + equipmentButton.setOriginalX(6); + equipmentButton.setOriginalY(4); + equipmentButton.revalidate(); + + // the bank item count is 3 widgets + for (int child = WidgetInfo.BANK_ITEM_COUNT_TOP.getChildId(); child <= WidgetInfo.BANK_ITEM_COUNT_BOTTOM.getChildId(); child++) + { + Widget widget = client.getWidget(WidgetID.BANK_GROUP_ID, child); + if (widget == null) + { + return; + } + + widget.setOriginalX(widget.getOriginalX() + equipmentButton.getWidth()); + widget.revalidate(); + } + + titleBar.setOriginalX(equipmentButton.getWidth() / 2); + titleBar.setOriginalWidth(titleBar.getWidth() - equipmentButton.getWidth()); + titleBar.revalidate(); + } + + private void handleDeposit(MenuOptionClicked event, Boolean inventory) + { + ItemContainer container = client.getItemContainer(inventory ? InventoryID.INVENTORY : InventoryID.EQUIPMENT); + + if (container == null) + { + return; + } + + List items = Arrays.stream(container.getItems()) + .filter(Objects::nonNull) + .map(Item::getId) + .filter(id -> id != -1) + .collect(Collectors.toList()); + + if (!Strings.isNullOrEmpty(event.getMenuTarget())) + { + if (activeTab != null && Text.removeTags(event.getMenuTarget()).equals(activeTab.getTag())) + { + for (Integer item : items) + { + tagManager.addTag(item, activeTab.getTag(), false); + } + + openTag(activeTab.getTag()); + } + + return; + } + + chatboxPanelManager.openTextInput((inventory ? "Inventory " : "Equipment ") + " tags:") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (newTags) -> + clientThread.invoke(() -> + { + final List tags = Text.fromCSV(newTags.toLowerCase()); + + for (Integer item : items) + { + tagManager.addTags(item, tags, false); + } + + updateTabIfActive(tags); + })) + .build(); + } + + private void handleNewTab(ScriptEvent event) + { + switch (event.getOp()) + { + case NewTab.NEW_TAB: + chatboxPanelManager.openTextInput("Tag name") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (tagName) -> clientThread.invoke(() -> + { + if (!Strings.isNullOrEmpty(tagName)) + { + loadTab(tagName); + tabManager.save(); + scrollTab(0); + } + })) + .build(); + break; + case NewTab.IMPORT_TAB: + try + { + final String dataString = Toolkit + .getDefaultToolkit() + .getSystemClipboard() + .getData(DataFlavor.stringFlavor) + .toString() + .trim(); + + final Iterator dataIter = Text.fromCSV(dataString).iterator(); + String name = dataIter.next(); + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) + { + if (FILTERED_CHARS.test(c)) + { + sb.append(c); + } + } + + if (sb.length() == 0) + { + notifier.notify("Failed to import tag tab from clipboard, invalid format."); + return; + } + + name = sb.toString(); + + final String icon = dataIter.next(); + tabManager.setIcon(name, icon); + + while (dataIter.hasNext()) + { + final int itemId = Integer.parseInt(dataIter.next()); + tagManager.addTag(itemId, name, itemId < 0); + } + + loadTab(name); + tabManager.save(); + scrollTab(0); + + if (activeTab != null && name.equals(activeTab.getTag())) + { + openTag(activeTab.getTag()); + } + + notifier.notify("Tag tab " + name + " has been imported from your clipboard!"); + } + catch (UnsupportedFlavorException | NoSuchElementException | IOException | NumberFormatException ex) + { + notifier.notify("Failed to import tag tab from clipboard, invalid format."); + } + break; + case NewTab.OPEN_TAB_MENU: + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + openTag(TAB_MENU_KEY); + break; + } + } + + private void handleTagTab(ScriptEvent event) + { + switch (event.getOp()) + { + case Tab.OPEN_TAG: + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + Widget clicked = event.getSource(); + + TagTab tab = tabManager.find(Text.removeTags(clicked.getName())); + + if (tab.equals(activeTab)) + { + activateTab(null); + bankSearch.reset(true); + } + else + { + openTag(Text.removeTags(clicked.getName())); + // openTag will reset and relayout + } + + client.playSoundEffect(SoundEffectID.UI_BOOP); + break; + case Tab.CHANGE_ICON: + final String tag = Text.removeTags(event.getOpbase()); + searchProvider + .tooltipText(CHANGE_ICON + " (" + tag + ")") + .onItemSelected((itemId) -> + { + TagTab iconToSet = tabManager.find(tag); + if (iconToSet != null) + { + iconToSet.setIconItemId(itemId); + iconToSet.getIcon().setItemId(itemId); + iconToSet.getMenu().setItemId(itemId); + tabManager.setIcon(iconToSet.getTag(), itemId + ""); + } + }) + .build(); + break; + case Tab.DELETE_TAB: + String target = Text.standardize(event.getOpbase()); + chatboxPanelManager.openTextMenuInput("Delete " + target) + .option("1. Tab and tag from all items", () -> + clientThread.invoke(() -> + { + tagManager.removeTag(target); + deleteTab(target); + }) + ) + .option("2. Only tab", () -> clientThread.invoke(() -> deleteTab(target))) + .option("3. Cancel", Runnables::doNothing) + .build(); + break; + case Tab.EXPORT_TAB: + final List data = new ArrayList<>(); + final TagTab tagTab = tabManager.find(Text.removeTags(event.getOpbase())); + data.add(tagTab.getTag()); + data.add(String.valueOf(tagTab.getIconItemId())); + + for (Integer item : tagManager.getItemsForTag(tagTab.getTag())) + { + data.add(String.valueOf(item)); + } + + final StringSelection stringSelection = new StringSelection(Text.toCSV(data)); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!"); + break; + case Tab.RENAME_TAB: + String renameTarget = Text.standardize(event.getOpbase()); + renameTab(renameTarget); + break; + } + } + + public void destroy() + { + activeTab = null; + currentTabIndex = 0; + maxTabs = 0; + parent = null; + + if (upButton != null) + { + upButton.setHidden(true); + downButton.setHidden(true); + newTab.setHidden(true); + } + + tabManager.clear(); + } + + public void update() + { + if (isHidden()) + { + parent = null; + + saveTab(); + return; + } + + // Don't continue ticking if equipment menu or bank menu is open + if (parent.isSelfHidden()) + { + return; + } + + updateBounds(); + scrollTab(0); + } + + private void saveTab() + { + // If bank window was just hidden, update last active tab position + if (currentTabIndex != config.position()) + { + config.position(currentTabIndex); + } + + // Do the same for last active tab + if (config.rememberTab()) + { + if (activeTab == null && !Strings.isNullOrEmpty(config.tab())) + { + config.tab(""); + } + else if (activeTab != null && !activeTab.getTag().equals(config.tab())) + { + config.tab(activeTab.getTag()); + } + } + else if (!Strings.isNullOrEmpty(config.tab())) + { + config.tab(""); + } + } + + private void setTabMenuVisible(boolean visible) + { + for (TagTab t : tabManager.getTabs()) + { + t.getMenu().setHidden(!visible); + } + } + + private boolean isTabMenuActive() + { + return tagTabActive; + } + + public void handleScriptEvent(final ScriptCallbackEvent event) + { + String eventName = event.getEventName(); + + int[] intStack = client.getIntStack(); + int intStackSize = client.getIntStackSize(); + + switch (eventName) + { + case "setBankScroll": + if (!isTabMenuActive()) + { + setTabMenuVisible(false); + return; + } + + setTabMenuVisible(true); + + // scroll height + intStack[intStackSize - 3] = (((tabManager.getTabs().size() - 1) / BANK_ITEMS_PER_ROW) + 1) * (BANK_ITEM_HEIGHT + BANK_ITEM_Y_PADDING); + + // skip normal bank layout + intStack[intStackSize - 2] = 1; + break; + case "beforeBankLayout": + setTabMenuVisible(false); + break; + } + } + + public void handleWheel(final MouseWheelEvent event) + { + if (parent == null || !canvasBounds.contains(event.getPoint())) + { + return; + } + + event.consume(); + + clientThread.invoke(() -> + { + if (isHidden()) + { + return; + } + + scrollTab(event.getWheelRotation()); + }); + } + + public void handleAdd(MenuEntryAdded event) + { + if (isHidden()) + { + return; + } + + MenuEntry[] entries = client.getMenuEntries(); + + if (activeTab != null + && event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + entries = createMenuEntry(event, REMOVE_TAG + " (" + activeTab.getTag() + ")", event.getTarget(), entries); + client.setMenuEntries(entries); + } + else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() + && event.getOption().equals("Deposit inventory")) + { + entries = createMenuEntry(event, TAG_INVENTORY, event.getTarget(), entries); + + if (activeTab != null) + { + entries = createMenuEntry(event, TAG_INVENTORY, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); + } + + client.setMenuEntries(entries); + } + else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() + && event.getOption().equals("Deposit worn items")) + { + entries = createMenuEntry(event, TAG_GEAR, event.getTarget(), entries); + + if (activeTab != null) + { + entries = createMenuEntry(event, TAG_GEAR, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); + } + + client.setMenuEntries(entries); + } + } + + public void handleClick(MenuOptionClicked event) + { + if (isHidden()) + { + return; + } + + if (chatboxPanelManager.getCurrentInput() != null + && event.getMenuAction() != MenuAction.CANCEL + && !event.getMenuOption().equals(SCROLL_UP) + && !event.getMenuOption().equals(SCROLL_DOWN)) + { + chatboxPanelManager.close(); + } + + if (activeTab != null + && (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items"))) + { + activateTab(null); + } + else if (activeTab != null + && event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getMenuAction() == MenuAction.RUNELITE + && event.getMenuOption().startsWith(REMOVE_TAG)) + { + // Add "remove" menu entry to all items in bank while tab is selected + event.consume(); + final ItemComposition item = getItem(event.getActionParam()); + final int itemId = item.getId(); + tagManager.removeTag(itemId, activeTab.getTag()); + bankSearch.layoutBank(); // re-layout to filter the removed item out + } + else if (event.getMenuAction() == MenuAction.RUNELITE + && ((event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() && event.getMenuOption().equals(TAG_INVENTORY)) + || (event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() && event.getMenuOption().equals(TAG_GEAR)))) + { + handleDeposit(event, event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId()); + } + else if (activeTab != null && ((event.getWidgetId() == WidgetInfo.BANK_EQUIPMENT_BUTTON.getId() && event.getMenuOption().equals(SHOW_WORN)) + || (event.getWidgetId() == WidgetInfo.BANK_SETTINGS_BUTTON.getId() && event.getMenuOption().equals(SHOW_SETTINGS)) + || (event.getWidgetId() == WidgetInfo.BANK_TUTORIAL_BUTTON.getId() && event.getMenuOption().equals(SHOW_TUTORIAL)))) + { + saveTab(); + } + } + + public void handleSearch() + { + if (activeTab != null) + { + activateTab(null); + // This ensures that when clicking Search when tab is selected, the search input is opened rather + // than client trying to close it first + client.setVar(VarClientStr.INPUT_TEXT, ""); + client.setVar(VarClientInt.INPUT_TYPE, 0); + } + } + + public void updateTabIfActive(final Collection tags) + { + if (activeTab != null && tags.contains(activeTab.getTag())) + { + openTag(activeTab.getTag()); + } + } + + public void handleDrag(boolean isDragging, boolean shiftDown) + { + if (isHidden()) + { + return; + } + + Widget draggedOn = client.getDraggedOnWidget(); + Widget draggedWidget = client.getDraggedWidget(); + + // Returning early or nulling the drag release listener has no effect. Hence, we need to + // null the draggedOnWidget instead. + if (draggedWidget.getId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() && isActive() + && config.preventTagTabDrags()) + { + client.setDraggedOnWidget(null); + } + + if (!isDragging || draggedOn == null) + { + return; + } + + // is dragging widget and mouse button released + if (client.getMouseCurrentButton() == 0) + { + if (!isTabMenuActive() && draggedWidget.getItemId() > 0 && draggedWidget.getId() != parent.getId()) + { + // Tag an item dragged on a tag tab + if (draggedOn.getId() == parent.getId()) + { + tagManager.addTag(draggedWidget.getItemId(), draggedOn.getName(), shiftDown); + updateTabIfActive(Lists.newArrayList(Text.standardize(draggedOn.getName()))); + } + } + else if ((isTabMenuActive() && draggedWidget.getId() == draggedOn.getId() && draggedOn.getId() != parent.getId()) + || (parent.getId() == draggedOn.getId() && parent.getId() == draggedWidget.getId())) + { + // Reorder tag tabs + moveTagTab(draggedWidget, draggedOn); + } + } + else if (draggedWidget.getItemId() > 0) + { + MenuEntry[] entries = client.getMenuEntries(); + + if (entries.length > 0) + { + MenuEntry entry = entries[entries.length - 1]; + + if (draggedWidget.getItemId() > 0 && entry.getOption().equals(VIEW_TAB) && draggedOn.getId() != draggedWidget.getId()) + { + entry.setOption(TAG_SEARCH + Text.removeTags(entry.getTarget()) + (shiftDown ? VAR_TAG_SUFFIX : "")); + entry.setTarget(draggedWidget.getName()); + client.setMenuEntries(entries); + } + + if (entry.getOption().equals(SCROLL_UP)) + { + scrollTick(-1); + } + else if (entry.getOption().equals(SCROLL_DOWN)) + { + scrollTick(1); + } + } + } + } + + private void moveTagTab(final Widget source, final Widget dest) + { + if (Strings.isNullOrEmpty(dest.getName())) + { + return; + } + + if (client.getVar(Varbits.BANK_REARRANGE_MODE) == 0) + { + tabManager.swap(source.getName(), dest.getName()); + } + else + { + tabManager.insert(source.getName(), dest.getName()); + } + + tabManager.save(); + updateTabs(); + } + + private boolean isHidden() + { + Widget widget = client.getWidget(WidgetInfo.BANK_CONTAINER); + return !config.tabs() || widget == null || widget.isHidden(); + } + + private void addTabActions(Widget w) + { + w.setAction(1, VIEW_TAB); + w.setAction(2, CHANGE_ICON); + w.setAction(3, REMOVE_TAB); + w.setAction(4, EXPORT_TAB); + w.setAction(5, RENAME_TAB); + w.setOnOpListener((JavaScriptCallback) this::handleTagTab); + } + + private void addTabOptions(Widget w) + { + int clickmask = w.getClickMask(); + clickmask |= WidgetConfig.DRAG; + clickmask |= WidgetConfig.DRAG_ON; + w.setClickMask(clickmask); + w.setDragDeadTime(5); + w.setDragDeadZone(5); + w.setItemQuantity(10000); + w.setItemQuantityMode(ItemQuantityMode.NEVER); + } + + private void loadTab(String tag) + { + TagTab tagTab = tabManager.load(tag); + + if (tagTab.getBackground() == null) + { + Widget btn = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), TabSprites.TAB_BACKGROUND.getSpriteId(), -1, TAB_WIDTH, TAB_HEIGHT, bounds.x, 1, true); + addTabActions(btn); + tagTab.setBackground(btn); + } + + if (tagTab.getIcon() == null) + { + Widget icon = createGraphic( + ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), + -1, + tagTab.getIconItemId(), + Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, + bounds.x + 3, 1, + false); + addTabOptions(icon); + tagTab.setIcon(icon); + } + + if (tagTab.getMenu() == null) + { + Widget menu = createGraphic( + client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER), + ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), + -1, + tagTab.getIconItemId(), + BANK_ITEM_WIDTH, BANK_ITEM_HEIGHT, + BANK_ITEM_START_X, BANK_ITEM_START_Y, + true); + addTabActions(menu); + addTabOptions(menu); + if (activeTab != null && activeTab.getTag().equals(TAB_MENU_KEY)) + { + menu.setHidden(false); + } + else + { + menu.setHidden(true); + } + tagTab.setMenu(menu); + } + + tabManager.add(tagTab); + } + + private void deleteTab(String tag) + { + if (activeTab != null && activeTab.getTag().equals(tag)) + { + activateTab(null); + bankSearch.reset(true); + } + + tabManager.remove(tag); + tabManager.save(); + + updateBounds(); + scrollTab(0); + } + + private void renameTab(String oldTag) + { + chatboxPanelManager.openTextInput("Enter new tag name for tag \"" + oldTag + "\":") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (newTag) -> clientThread.invoke(() -> + { + if (!Strings.isNullOrEmpty(newTag) && !newTag.equalsIgnoreCase(oldTag)) + { + if (tabManager.find(newTag) == null) + { + TagTab tagTab = tabManager.find(oldTag); + tagTab.setTag(newTag); + + final String coloredName = ColorUtil.wrapWithColorTag(newTag, HILIGHT_COLOR); + tagTab.getIcon().setName(coloredName); + tagTab.getBackground().setName(coloredName); + tagTab.getMenu().setName(coloredName); + + tabManager.removeIcon(oldTag); + tabManager.setIcon(newTag, tagTab.getIconItemId() + ""); + + tabManager.save(); + tagManager.renameTag(oldTag, newTag); + + if (activeTab != null && activeTab.equals(tagTab)) + { + openTag(newTag); + } + } + else + { + chatboxPanelManager.openTextMenuInput("The specified bank tag already exists.") + .option("1. Merge into existing tag \"" + newTag + "\".", () -> + clientThread.invoke(() -> + { + tagManager.renameTag(oldTag, newTag); + final String activeTag = activeTab != null ? activeTab.getTag() : ""; + deleteTab(oldTag); + + if (activeTag.equals(oldTag)) + { + openTag(newTag); + } + }) + ) + .option("2. Choose a different name.", () -> + clientThread.invoke(() -> + renameTab(oldTag)) + ) + .build(); + } + } + })) + .build(); + } + + private void scrollTick(int direction) + { + // This ensures that dragging on scroll buttons do not scrolls too fast + if (startScroll.until(Instant.now(), ChronoUnit.MILLIS) >= SCROLL_TICK) + { + startScroll = Instant.now(); + scrollTab(direction); + } + } + + private void scrollTab(int direction) + { + maxTabs = (bounds.height - BUTTON_HEIGHT * 2 - MARGIN * 2) / TAB_HEIGHT; + + // prevent running into the incinerator + while (bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT * 2 + MARGIN > bounds.y + bounds.height) + { + --maxTabs; + } + + int proposedIndex = currentTabIndex + direction; + int numTabs = tabManager.size(); + + if (proposedIndex >= numTabs || proposedIndex < 0) + { + currentTabIndex = 0; + } + else if (numTabs - proposedIndex >= maxTabs) + { + currentTabIndex = proposedIndex; + } + else if (maxTabs < numTabs && numTabs - proposedIndex < maxTabs) + { + // Edge case when only 1 tab displays instead of up to maxTabs when one is deleted at the end of the list + currentTabIndex = proposedIndex; + scrollTab(-1); + } + + updateTabs(); + } + + private void activateTab(TagTab tagTab) + { + if (activeTab != null && activeTab.equals(tagTab)) + { + return; + } + + if (activeTab != null) + { + Widget tab = activeTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND.getSpriteId()); + tab.revalidate(); + activeTab = null; + } + + if (tagTab != null) + { + Widget tab = tagTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND_ACTIVE.getSpriteId()); + tab.revalidate(); + activeTab = tagTab; + } + + tagTabActive = false; + } + + private void updateBounds() + { + Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (itemContainer == null) + { + return; + } + + int height = itemContainer.getHeight(); + + // If player isn't using normal bank tabs + if (itemContainer.getRelativeY() == 0) + { + height -= (TAB_HEIGHT + MARGIN); + } + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height); + bounds.setLocation(MARGIN, TAB_HEIGHT + MARGIN); + + Widget incinerator = client.getWidget(WidgetInfo.BANK_INCINERATOR); + + if (incinerator != null && !incinerator.isHidden()) + { + incinerator.setOriginalHeight(INCINERATOR_HEIGHT); + incinerator.setOriginalWidth(INCINERATOR_WIDTH); + incinerator.setOriginalY(INCINERATOR_HEIGHT); + + Widget child = incinerator.getChild(0); + child.setOriginalHeight(INCINERATOR_HEIGHT); + child.setOriginalWidth(INCINERATOR_WIDTH); + child.setWidthMode(WidgetSizeMode.ABSOLUTE); + child.setHeightMode(WidgetSizeMode.ABSOLUTE); + child.setType(WidgetType.GRAPHIC); + child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); + incinerator.revalidate(); + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); + } + + if (upButton != null) + { + Point p = upButton.getCanvasLocation(); + canvasBounds.setBounds(p.getX(), p.getY() + BUTTON_HEIGHT, bounds.width, maxTabs * TAB_HEIGHT + maxTabs * MARGIN); + } + } + + private void updateTabs() + { + int y = bounds.y + MARGIN + BUTTON_HEIGHT; + + if (maxTabs >= tabManager.size()) + { + currentTabIndex = 0; + } + else + { + y -= (currentTabIndex * TAB_HEIGHT + currentTabIndex * MARGIN); + } + + int itemX = BANK_ITEM_START_X; + int itemY = BANK_ITEM_START_Y; + int rowIndex = 0; + + for (TagTab tab : tabManager.getTabs()) + { + updateWidget(tab.getBackground(), y); + updateWidget(tab.getIcon(), y + 4); + + // Edge case where item icon is 1 pixel out of bounds + tab.getIcon().setHidden(tab.getBackground().isHidden()); + + // Keep item widget shown while drag scrolling + if (client.getDraggedWidget() == tab.getIcon()) + { + tab.getIcon().setHidden(false); + } + + y += TAB_HEIGHT + MARGIN; + + Widget item = tab.getMenu(); + item.setOriginalX(itemX); + item.setOriginalY(itemY); + item.revalidate(); + + rowIndex++; + if (rowIndex == BANK_ITEMS_PER_ROW) + { + itemX = BANK_ITEM_START_X; + itemY += BANK_ITEM_Y_PADDING + BANK_ITEM_HEIGHT; + rowIndex = 0; + } + else + { + itemX += BANK_ITEM_X_PADDING + BANK_ITEM_WIDTH; + } + } + + boolean hidden = !(tabManager.size() > 0); + + upButton.setHidden(hidden); + upButton.setOriginalY(bounds.y); + upButton.revalidate(); + + downButton.setHidden(hidden); + downButton.setOriginalY(bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT + MARGIN); + downButton.revalidate(); + } + + private Widget createGraphic(Widget container, String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) + { + Widget widget = container.createChild(-1, WidgetType.GRAPHIC); + widget.setOriginalWidth(width); + widget.setOriginalHeight(height); + widget.setOriginalX(x); + widget.setOriginalY(y); + + widget.setSpriteId(spriteId); + + if (itemId > -1) + { + widget.setItemId(itemId); + widget.setItemQuantity(-1); + widget.setBorderType(1); + } + + if (hasListener) + { + widget.setOnOpListener(ScriptID.NULL); + widget.setHasListener(true); + } + + widget.setName(name); + widget.revalidate(); + + return widget; + } + + private Widget createGraphic(String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) + { + return createGraphic(parent, name, spriteId, itemId, width, height, x, y, hasListener); + } + + private void updateWidget(Widget t, int y) + { + t.setOriginalY(y); + t.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT)); + t.revalidate(); + } + + private ItemComposition getItem(int idx) + { + ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + Item item = bankContainer.getItem(idx); + return itemManager.getItemComposition(item.getId()); + } + + private void openTag(final String tag) + { + activateTab(tabManager.find(tag)); + tagTabActive = BankTagsPlugin.TAG_TABS_CONFIG.equals(tag); + bankSearch.reset(true); // clear search dialog & relayout bank for new tab. + + // When searching the button has a script on timer to detect search end, that will set the background back + // and remove the timer. However since we are going from a bank search to our fake search this will not remove + // the timer but instead re-add it and reset the background. So remove the timer and the background. This is the + // same as bankmain_search_setbutton. + Widget searchButtonBackground = client.getWidget(WidgetInfo.BANK_SEARCH_BUTTON_BACKGROUND); + searchButtonBackground.setOnTimerListener((Object[]) null); + searchButtonBackground.setSpriteId(SpriteID.EQUIPMENT_SLOT_TILE); + } + + private static MenuEntry[] createMenuEntry(MenuEntryAdded event, String option, String target, MenuEntry[] entries) + { + final MenuEntry entry = new MenuEntry(); + entry.setActionParam(event.getActionParam()); + entry.setParam1(event.getActionParam1()); + entry.setTarget(target); + entry.setOption(option); + entry.setType(MenuAction.RUNELITE.getId()); + entry.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = entry; + return entries; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java new file mode 100644 index 0000000000..f3feed8511 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.MoreObjects; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_TABS_CONFIG; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.math.NumberUtils; + +@Singleton +class TabManager +{ + @Getter + private final List tabs = new ArrayList<>(); + private final ConfigManager configManager; + + @Inject + private TabManager(ConfigManager configManager) + { + this.configManager = configManager; + } + + void add(TagTab tagTab) + { + if (!contains(tagTab.getTag())) + { + tabs.add(tagTab); + } + } + + void clear() + { + tabs.forEach(t -> t.setHidden(true)); + tabs.clear(); + } + + TagTab find(String tag) + { + Optional first = tabs.stream().filter(t -> t.getTag().equals(Text.standardize(tag))).findAny(); + return first.orElse(null); + } + + List getAllTabs() + { + return Text.fromCSV(MoreObjects.firstNonNull(configManager.getConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG), "")); + } + + TagTab load(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab == null) + { + tag = Text.standardize(tag); + String item = configManager.getConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); + int itemid = NumberUtils.toInt(item, ItemID.SPADE); + tagTab = new TagTab(itemid, tag); + } + + return tagTab; + } + + void swap(String tagToMove, String tagDestination) + { + tagToMove = Text.standardize(tagToMove); + tagDestination = Text.standardize(tagDestination); + + if (contains(tagToMove) && contains(tagDestination)) + { + Collections.swap(tabs, indexOf(tagToMove), indexOf(tagDestination)); + } + } + + void insert(String tagToMove, String tagDestination) + { + tagToMove = Text.standardize(tagToMove); + tagDestination = Text.standardize(tagDestination); + + if (contains(tagToMove) && contains(tagDestination)) + { + tabs.add(indexOf(tagDestination), tabs.remove(indexOf(tagToMove))); + } + } + + void remove(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab != null) + { + tagTab.setHidden(true); + tabs.remove(tagTab); + removeIcon(tag); + } + } + + void save() + { + String tags = Text.toCSV(tabs.stream().map(TagTab::getTag).collect(Collectors.toList())); + configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); + } + + void removeIcon(final String tag) + { + configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag)); + } + + void setIcon(final String tag, final String icon) + { + configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon); + } + + int size() + { + return tabs.size(); + } + + private boolean contains(String tag) + { + return tabs.stream().anyMatch(t -> t.getTag().equals(tag)); + } + + private int indexOf(TagTab tagTab) + { + return tabs.indexOf(tagTab); + } + + private int indexOf(String tag) + { + return indexOf(find(tag)); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java new file mode 100644 index 0000000000..20f9d0dfb6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.client.game.SpriteOverride; + +@RequiredArgsConstructor +public enum TabSprites implements SpriteOverride +{ + INCINERATOR(-200, "incinerator.png"), + TAB_BACKGROUND(-201, "tag-tab.png"), + TAB_BACKGROUND_ACTIVE(-202, "tag-tab-active.png"), + UP_ARROW(-203, "up-arrow.png"), + DOWN_ARROW(-204, "down-arrow.png"), + NEW_TAB(-205, "new-tab.png"); + + @Getter + private final int spriteId; + + @Getter + private final String fileName; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java new file mode 100644 index 0000000000..88ef36537b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.runelite.api.widgets.Widget; + +@Data +@EqualsAndHashCode(of = "tag") +public class TagTab +{ + private String tag; + private int iconItemId; + private Widget background; + private Widget icon; + private Widget menu; + + TagTab(int iconItemId, String tag) + { + this.iconItemId = iconItemId; + this.tag = tag; + } + + void setHidden(boolean hide) + { + if (background != null) + { + background.setHidden(hide); + } + + if (icon != null) + { + icon.setHidden(hide); + } + + if (menu != null) + { + menu.setHidden(hide); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java new file mode 100644 index 0000000000..a2353866f4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultConfig.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, Cameron + * Copyright (c) 2018, Jacob M + * 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.barbarianassault; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("barbarianAssault") +public interface BarbarianAssaultConfig extends Config +{ + @ConfigItem( + keyName = "showTimer", + name = "Show call change timer", + description = "Show time to next call change" + ) + default boolean showTimer() + { + return true; + } + + @ConfigItem( + keyName = "showHealerBars", + name = "Show health bars for teammates when healer", + description = "Displays team health for healer" + ) + default boolean showHealerBars() + { + return true; + } + + @ConfigItem( + keyName = "waveTimes", + name = "Show wave and game duration", + description = "Displays wave and game duration" + ) + default boolean waveTimes() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java new file mode 100644 index 0000000000..73eb79037d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/BarbarianAssaultPlugin.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2018, Cameron + * Copyright (c) 2018, Jacob M + * 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.barbarianassault; + +import com.google.inject.Provides; +import java.awt.Image; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Varbits; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.VarbitChanged; +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.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ImageUtil; + +@PluginDescriptor( + name = "Barbarian Assault", + description = "Show a timer to the next call change and game/wave duration in chat.", + tags = {"minigame", "overlay", "timer"} +) +public class BarbarianAssaultPlugin extends Plugin +{ + private static final int BA_WAVE_NUM_INDEX = 2; + private static final String START_WAVE = "1"; + private static final String ENDGAME_REWARD_NEEDLE_TEXT = "
5"; + + @Getter(AccessLevel.PACKAGE) + private Image clockImage; + private int inGameBit = 0; + private String currentWave = START_WAVE; + private GameTimer gameTime; + + @Getter + private Round currentRound; + + @Inject + private Client client; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private BarbarianAssaultConfig config; + + @Inject + private TimerOverlay timerOverlay; + + @Inject + private HealerOverlay healerOverlay; + + @Provides + BarbarianAssaultConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(BarbarianAssaultConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(timerOverlay); + overlayManager.add(healerOverlay); + + clockImage = ImageUtil.getResourceStreamFromClass(getClass(), "clock.png"); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(timerOverlay); + overlayManager.remove(healerOverlay); + gameTime = null; + currentWave = START_WAVE; + inGameBit = 0; + clockImage = null; + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + switch (event.getGroupId()) + { + case WidgetID.BA_REWARD_GROUP_ID: + { + Widget rewardWidget = client.getWidget(WidgetInfo.BA_REWARD_TEXT); + + if (config.waveTimes() && rewardWidget != null && rewardWidget.getText().contains(ENDGAME_REWARD_NEEDLE_TEXT) && gameTime != null) + { + announceTime("Game finished, duration: ", gameTime.getTime(false)); + gameTime = null; + } + + break; + } + case WidgetID.BA_ATTACKER_GROUP_ID: + { + setRound(Role.ATTACKER); + break; + } + case WidgetID.BA_DEFENDER_GROUP_ID: + { + setRound(Role.DEFENDER); + break; + } + case WidgetID.BA_HEALER_GROUP_ID: + { + setRound(Role.HEALER); + break; + } + case WidgetID.BA_COLLECTOR_GROUP_ID: + { + setRound(Role.COLLECTOR); + break; + } + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() == ChatMessageType.GAMEMESSAGE + && event.getMessage().startsWith("---- Wave:")) + { + String[] message = event.getMessage().split(" "); + currentWave = message[BA_WAVE_NUM_INDEX]; + + if (currentWave.equals(START_WAVE)) + { + gameTime = new GameTimer(); + } + else if (gameTime != null) + { + gameTime.setWaveStartTime(); + } + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + int inGame = client.getVar(Varbits.IN_GAME_BA); + + if (inGameBit != inGame) + { + if (inGameBit == 1) + { + currentRound = null; + + // Use an instance check to determine if this is exiting a game or a tutorial + // After exiting tutorials there is a small delay before changing IN_GAME_BA back to + // 0 whereas when in a real wave it changes while still in the instance. + if (config.waveTimes() && gameTime != null && client.isInInstancedRegion()) + { + announceTime("Wave " + currentWave + " duration: ", gameTime.getTime(true)); + } + } + + inGameBit = inGame; + } + } + + private void setRound(Role role) + { + // Prevent changing rounds when a round is already set, as widgets can be + // loaded multiple times in game from eg. opening and closing the horn + // of glory. + if (currentRound == null) + { + currentRound = new Round(role); + } + } + + private void announceTime(String preText, String time) + { + final String chatMessage = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append(preText) + .append(ChatColorType.HIGHLIGHT) + .append(time) + .build(); + + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.CONSOLE) + .runeLiteFormattedMessage(chatMessage) + .build()); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/GameTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/GameTimer.java new file mode 100644 index 0000000000..fbf33bae14 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/GameTimer.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, Jacob M + * 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.barbarianassault; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import static net.runelite.client.util.RSTimeUnit.GAME_TICKS; + +class GameTimer +{ + final private Instant startTime = Instant.now(); + private Instant prevWave = startTime; + + String getTime(boolean waveTime) + { + final Instant now = Instant.now(); + final Duration elapsed; + + if (waveTime) + { + elapsed = Duration.between(prevWave, now); + } + else + { + elapsed = Duration.between(startTime, now).minus(Duration.of(1, GAME_TICKS)); + } + + return formatTime(LocalTime.ofSecondOfDay(elapsed.getSeconds())); + } + + void setWaveStartTime() + { + prevWave = Instant.now(); + } + + private static String formatTime(LocalTime time) + { + if (time.getHour() > 0) + { + return time.format(DateTimeFormatter.ofPattern("HH:mm")); + } + else if (time.getMinute() > 9) + { + return time.format(DateTimeFormatter.ofPattern("mm:ss")); + } + else + { + return time.format(DateTimeFormatter.ofPattern("m:ss")); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/HealerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/HealerOverlay.java new file mode 100644 index 0000000000..47b410c431 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/HealerOverlay.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2019, whartd + * 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.barbarianassault; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPosition; + +class HealerOverlay extends Overlay +{ + @Getter + @AllArgsConstructor + private enum HealerTeam + { + TEAMMATE1(WidgetInfo.BA_HEAL_TEAMMATE1, 28, 2, 115), + TEAMMATE2(WidgetInfo.BA_HEAL_TEAMMATE2, 26, 2, 115), + TEAMMATE3(WidgetInfo.BA_HEAL_TEAMMATE3, 26, 2, 115), + TEAMMATE4(WidgetInfo.BA_HEAL_TEAMMATE4, 25, 2, 115); + + private WidgetInfo teammate; + private int offsetX; + private int offsetY; + private int width; + } + + private static final Color HP_HIGH = new Color(10, 146, 5, 125); + private static final Color HP_MID = new Color(146, 146, 0, 230); + private static final Color HP_LOW = new Color(225, 35, 0, 125); + + private final Client client; + private final BarbarianAssaultPlugin plugin; + private final BarbarianAssaultConfig config; + + @Inject + private HealerOverlay(Client client, BarbarianAssaultPlugin plugin, BarbarianAssaultConfig config) + { + super(plugin); + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.UNDER_WIDGETS); + this.client = client; + this.plugin = plugin; + this.config = config; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "B.A. overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + Round round = plugin.getCurrentRound(); + if (round == null) + { + return null; + } + + Role role = round.getRoundRole(); + if (config.showHealerBars() && role == Role.HEALER) + { + for (HealerTeam teammate : HealerTeam.values()) + { + Widget widget = client.getWidget(teammate.getTeammate()); + if (widget == null) + { + continue; + } + + String[] teammateHealth = widget.getText().split(" / "); + int curHealth = Integer.parseInt(teammateHealth[0]); + int maxHealth = Integer.parseInt(teammateHealth[1]); + + int width = teammate.getWidth(); + double hpRatio = (double) curHealth / maxHealth; + int filledWidth = getBarWidth(hpRatio, width); + Color barColor = getBarColor(hpRatio); + + int offsetX = teammate.getOffsetX(); + int offsetY = teammate.getOffsetY(); + int x = widget.getCanvasLocation().getX() - offsetX; + int y = widget.getCanvasLocation().getY() - offsetY; + + graphics.setColor(barColor); + graphics.fillRect(x, y, filledWidth, 20); + } + } + + return null; + } + + private int getBarWidth(double ratio, int size) + { + if (ratio >= 1) + { + return size; + } + + return (int) Math.round(ratio * size); + } + + private Color getBarColor(double ratio) + { + if (ratio <= 0.33) + { + return HP_LOW; + } + + if (ratio <= 0.66) + { + return HP_MID; + } + + return HP_HIGH; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Role.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Role.java new file mode 100644 index 0000000000..a8fd8b656c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Role.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018, Cameron + * 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.barbarianassault; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.widgets.WidgetInfo; + +@AllArgsConstructor +@Getter +enum Role +{ + ATTACKER(WidgetInfo.BA_ATK_LISTEN_TEXT, WidgetInfo.BA_ATK_CALL_TEXT, WidgetInfo.BA_ATK_ROLE_TEXT, WidgetInfo.BA_ATK_ROLE_SPRITE), + DEFENDER(WidgetInfo.BA_DEF_LISTEN_TEXT, WidgetInfo.BA_DEF_CALL_TEXT, WidgetInfo.BA_DEF_ROLE_TEXT, WidgetInfo.BA_DEF_ROLE_SPRITE), + COLLECTOR(WidgetInfo.BA_COLL_LISTEN_TEXT, WidgetInfo.BA_COLL_CALL_TEXT, WidgetInfo.BA_COLL_ROLE_TEXT, WidgetInfo.BA_COLL_ROLE_SPRITE), + HEALER(WidgetInfo.BA_HEAL_LISTEN_TEXT, WidgetInfo.BA_HEAL_CALL_TEXT, WidgetInfo.BA_HEAL_ROLE_TEXT, WidgetInfo.BA_HEAL_ROLE_SPRITE); + + private final WidgetInfo listen; + private final WidgetInfo call; + private final WidgetInfo roleText; + private final WidgetInfo roleSprite; + + @Override + public String toString() + { + return name(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Round.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Round.java new file mode 100644 index 0000000000..3ed8932a0a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/Round.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, Cameron + * 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.barbarianassault; + +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import static net.runelite.client.util.RSTimeUnit.GAME_TICKS; + +class Round +{ + private final Instant roundStartTime; + @Getter + private final Role roundRole; + @Getter + @Setter + private boolean runnersKilled; + @Getter + @Setter + private boolean rangersKilled; + @Getter + @Setter + private boolean healersKilled; + @Getter + @Setter + private boolean fightersKilled; + + @Inject + public Round(@NonNull Role role) + { + this.roundRole = role; + this.roundStartTime = Instant.now().plus(Duration.of(2, GAME_TICKS)); + } + + public int getTimeToChange() + { + return 30 + ((int) Duration.between(Instant.now(), roundStartTime).getSeconds() % 30); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/TimerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/TimerOverlay.java new file mode 100644 index 0000000000..59a0aa6cd2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barbarianassault/TimerOverlay.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018, Cameron + * 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.barbarianassault; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import javax.inject.Inject; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.api.widgets.Widget; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPosition; + +class TimerOverlay extends Overlay +{ + private final Client client; + private final BarbarianAssaultPlugin plugin; + private final BarbarianAssaultConfig config; + + @Inject + private TimerOverlay(Client client, BarbarianAssaultPlugin plugin, BarbarianAssaultConfig config) + { + super(plugin); + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.client = client; + this.plugin = plugin; + this.config = config; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "B.A. overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + Round round = plugin.getCurrentRound(); + if (round == null) + { + return null; + } + + Role role = round.getRoundRole(); + Widget roleText = client.getWidget(role.getRoleText()); + Widget roleSprite = client.getWidget(role.getRoleSprite()); + + if (config.showTimer() && roleText != null && roleSprite != null) + { + roleText.setText(String.format("00:%02d", round.getTimeToChange())); + Rectangle spriteBounds = roleSprite.getBounds(); + roleSprite.setHidden(true); + graphics.drawImage(plugin.getClockImage(), spriteBounds.x, spriteBounds.y, null); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java new file mode 100644 index 0000000000..4c406af095 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraConfig.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2018 Abex + * 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.camera; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; + +@ConfigGroup("zoom") // using the old plugin's group name +public interface CameraConfig extends Config +{ + int OUTER_LIMIT_MIN = -400; + int OUTER_LIMIT_MAX = 400; + /** + * The largest (most zoomed in) value that can be used without the client crashing. + * + * Larger values trigger an overflow in the engine's fov to scale code. + */ + int INNER_ZOOM_LIMIT = 1004; + + @ConfigItem( + keyName = "inner", + name = "Expand inner zoom limit", + description = "Configures whether or not the inner zoom limit is reduced", + position = 1 + ) + default boolean innerLimit() + { + return false; + } + + @Range( + min = OUTER_LIMIT_MIN, + max = OUTER_LIMIT_MAX + ) + @ConfigItem( + keyName = "outerLimit", + name = "Expand outer zoom limit", + description = "Configures how much the outer zoom limit is adjusted", + position = 2 + ) + default int outerLimit() + { + return 0; + } + + @ConfigItem( + keyName = "relaxCameraPitch", + name = "Vertical camera", + description = "Relax the camera's upper pitch limit", + position = 3 + ) + default boolean relaxCameraPitch() + { + return false; + } + + @ConfigItem( + keyName = "controlFunction", + name = "Control Function", + description = "Configures the zoom function when control is pressed", + position = 4 + ) + default ControlFunction controlFunction() + { + return ControlFunction.NONE; + } + + @ConfigItem( + keyName = "ctrlZoomValue", + name = "Reset zoom position", + description = "Position of zoom when it is reset", + position = 5 + ) + @Range( + min = OUTER_LIMIT_MIN, + max = INNER_ZOOM_LIMIT + ) + default int ctrlZoomValue() + { + return 512; + } + + @ConfigItem( + keyName = "zoomIncrement", + name = "Zoom Speed", + description = "Speed of zoom", + position = 6 + ) + default int zoomIncrement() + { + return 25; + } + + @ConfigItem( + keyName = "rightClickMovesCamera", + name = "Right click moves camera", + description = "Remaps right click to middle mouse click if there are no menu options", + position = 7 + ) + default boolean rightClickMovesCamera() + { + return false; + } + + @ConfigItem( + keyName = "ignoreExamine", + name = "Ignore Examine", + description = "Ignore the Examine menu entry", + position = 8 + ) + default boolean ignoreExamine() + { + return false; + } + + @ConfigItem( + keyName = "middleClickMenu", + name = "Middle-button opens menu", + description = "Middle-mouse button always opens the menu", + position = 9 + ) + default boolean middleClickMenu() + { + return false; + } + + @ConfigItem( + keyName = "compassLook", + name = "Compass options", + description = "Adds Look South, East, and West options to the compass", + position = 10 + ) + default boolean compassLook() + { + return true; + } + + @ConfigItem( + keyName = "invertYaw", + name = "Invert Yaw", + description = "Makes moving the camera horizontally with the mouse backwards", + position = 11 + ) + default boolean invertYaw() + { + return false; + } + + @ConfigItem( + keyName = "invertPitch", + name = "Invert Pitch", + description = "Makes moving the camera vertically with the mouse backwards", + position = 12 + ) + default boolean invertPitch() + { + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java new file mode 100644 index 0000000000..91249c197c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/camera/CameraPlugin.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Adam + * Copyright (c) 2019, Wynadorn + * 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.camera; + +import com.google.common.primitives.Ints; +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.Arrays; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.ScriptID; +import net.runelite.api.SettingID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarPlayer; +import net.runelite.api.events.BeforeRender; +import net.runelite.api.events.ClientTick; +import net.runelite.api.events.FocusChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPreFired; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.JavaScriptCallback; +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.ConfigChanged; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseListener; +import net.runelite.client.input.MouseManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; + +@PluginDescriptor( + name = "Camera", + description = "Expands zoom limit, provides vertical camera, and remaps mouse input keys", + tags = {"zoom", "limit", "vertical", "click", "mouse"}, + enabledByDefault = false +) +public class CameraPlugin extends Plugin implements KeyListener, MouseListener +{ + private static final int DEFAULT_ZOOM_INCREMENT = 25; + private static final int DEFAULT_OUTER_ZOOM_LIMIT = 128; + static final int DEFAULT_INNER_ZOOM_LIMIT = 896; + + private static final String LOOK_NORTH = "Look North"; + private static final String LOOK_SOUTH = "Look South"; + private static final String LOOK_EAST = "Look East"; + private static final String LOOK_WEST = "Look West"; + + private boolean controlDown; + // flags used to store the mousedown states + private boolean rightClick; + private boolean middleClick; + /** + * Whether or not the current menu has any non-ignored menu entries + */ + private boolean menuHasEntries; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private CameraConfig config; + + @Inject + private KeyManager keyManager; + + @Inject + private MouseManager mouseManager; + + @Inject + private TooltipManager tooltipManager; + + private Tooltip sliderTooltip; + + @Provides + CameraConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(CameraConfig.class); + } + + @Override + protected void startUp() + { + rightClick = false; + middleClick = false; + menuHasEntries = false; + copyConfigs(); + keyManager.registerKeyListener(this); + mouseManager.registerMouseListener(this); + clientThread.invoke(() -> + { + Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK); + if (sideSlider != null) + { + addZoomTooltip(sideSlider); + } + + Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT); + if (settingsInit != null) + { + //TODO: Implement client.createScriptEvent + //client.createScriptEvent(settingsInit.getOnLoadListener()) + //.setSource(settingsInit) + //.run(); + } + }); + } + + @Override + protected void shutDown() + { + client.setCameraPitchRelaxerEnabled(false); + client.setInvertYaw(false); + client.setInvertPitch(false); + keyManager.unregisterKeyListener(this); + mouseManager.unregisterMouseListener(this); + controlDown = false; + + clientThread.invoke(() -> + { + Widget sideSlider = client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK); + if (sideSlider != null) + { + sideSlider.setOnMouseRepeatListener((Object[]) null); + } + + Widget settingsInit = client.getWidget(WidgetInfo.SETTINGS_INIT); + if (settingsInit != null) + { + //TODO: Implement client.createScriptEvent + //client.createScriptEvent(settingsInit.getOnLoadListener()) + //.setSource(settingsInit) + //.run(); + } + }); + } + + void copyConfigs() + { + client.setCameraPitchRelaxerEnabled(config.relaxCameraPitch()); + client.setInvertYaw(config.invertYaw()); + client.setInvertPitch(config.invertPitch()); + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded) + { + if (menuEntryAdded.getType() == MenuAction.CC_OP.getId() && menuEntryAdded.getOption().equals(LOOK_NORTH) && config.compassLook()) + { + MenuEntry[] menuEntries = client.getMenuEntries(); + int len = menuEntries.length; + MenuEntry north = menuEntries[len - 1]; + + menuEntries = Arrays.copyOf(menuEntries, len + 3); + + // The handling for these entries is done in ToplevelCompassOp.rs2asm + menuEntries[--len] = createCameraLookEntry(menuEntryAdded, 4, LOOK_WEST); + menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 3, LOOK_EAST); + menuEntries[++len] = createCameraLookEntry(menuEntryAdded, 2, LOOK_SOUTH); + menuEntries[++len] = north; + + client.setMenuEntries(menuEntries); + } + } + + private MenuEntry createCameraLookEntry(MenuEntryAdded lookNorth, int identifier, String option) + { + MenuEntry m = new MenuEntry(); + m.setOption(option); + m.setTarget(lookNorth.getTarget()); + m.setIdentifier(identifier); + m.setType(MenuAction.CC_OP.getId()); + m.setParam0(lookNorth.getActionParam0()); + m.setParam1(lookNorth.getActionParam1()); + return m; + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if (client.getIndexScripts().isOverlayOutdated()) + { + // if any cache overlay fails to load then assume at least one of the zoom scripts is outdated + // and prevent zoom extending entirely. + return; + } + + int[] intStack = client.getIntStack(); + int intStackSize = client.getIntStackSize(); + + if (!controlDown && "scrollWheelZoom".equals(event.getEventName()) && config.controlFunction() == ControlFunction.CONTROL_TO_ZOOM) + { + intStack[intStackSize - 1] = 1; + } + + if ("innerZoomLimit".equals(event.getEventName()) && config.innerLimit()) + { + intStack[intStackSize - 1] = CameraConfig.INNER_ZOOM_LIMIT; + return; + } + + if ("outerZoomLimit".equals(event.getEventName())) + { + int outerLimit = Ints.constrainToRange(config.outerLimit(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.OUTER_LIMIT_MAX); + int outerZoomLimit = DEFAULT_OUTER_ZOOM_LIMIT - outerLimit; + intStack[intStackSize - 1] = outerZoomLimit; + return; + } + + if ("scrollWheelZoomIncrement".equals(event.getEventName()) && config.zoomIncrement() != DEFAULT_ZOOM_INCREMENT) + { + intStack[intStackSize - 1] = config.zoomIncrement(); + return; + } + + if (config.innerLimit()) + { + // This lets the options panel's slider have an exponential rate + final double exponent = 2.d; + switch (event.getEventName()) + { + case "zoomLinToExp": + { + double range = intStack[intStackSize - 1]; + double value = intStack[intStackSize - 2]; + value = Math.pow(value / range, exponent) * range; + intStack[intStackSize - 2] = (int) value; + break; + } + case "zoomExpToLin": + { + double range = intStack[intStackSize - 1]; + double value = intStack[intStackSize - 2]; + value = Math.pow(value / range, 1.d / exponent) * range; + intStack[intStackSize - 2] = (int) value; + break; + } + } + } + } + + @Subscribe + public void onFocusChanged(FocusChanged event) + { + if (!event.isFocused()) + { + controlDown = false; + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged ev) + { + copyConfigs(); + } + + @Override + public void keyTyped(KeyEvent e) + { + } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_CONTROL) + { + controlDown = true; + } + } + + @Override + public void keyReleased(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_CONTROL) + { + controlDown = false; + + if (config.controlFunction() == ControlFunction.CONTROL_TO_RESET) + { + final int zoomValue = Ints.constrainToRange(config.ctrlZoomValue(), CameraConfig.OUTER_LIMIT_MIN, CameraConfig.INNER_ZOOM_LIMIT); + clientThread.invokeLater(() -> client.runScript(ScriptID.CAMERA_DO_ZOOM, zoomValue, zoomValue)); + } + } + } + + /** + * Checks if the menu has any non-ignored entries + */ + private boolean hasMenuEntries(MenuEntry[] menuEntries) + { + for (MenuEntry menuEntry : menuEntries) + { + MenuAction action = MenuAction.of(menuEntry.getType()); + switch (action) + { + case CANCEL: + case WALK: + break; + case EXAMINE_OBJECT: + case EXAMINE_NPC: + case EXAMINE_ITEM_GROUND: + case EXAMINE_ITEM: + case CC_OP_LOW_PRIORITY: + if (config.ignoreExamine()) + { + break; + } + default: + return true; + } + } + return false; + } + + /** + * Checks if the menu has any options, because menu entries are built each + * tick and the MouseListener runs on the awt thread + */ + @Subscribe + public void onClientTick(ClientTick event) + { + menuHasEntries = hasMenuEntries(client.getMenuEntries()); + } + + @Subscribe + private void onScriptPreFired(ScriptPreFired ev) + { + if (ev.getScriptId() == ScriptID.SETTINGS_SLIDER_CHOOSE_ONOP) + { + int arg = client.getIntStackSize() - 7; + int[] is = client.getIntStack(); + + if (is[arg] == SettingID.CAMERA_ZOOM) + { + //TODO: Implement client.createScriptEvent + //addZoomTooltip(client.getScriptActiveWidget()); + } + } + } + + @Subscribe + private void onWidgetLoaded(WidgetLoaded ev) + { + if (ev.getGroupId() == WidgetID.SETTINGS_SIDE_GROUP_ID) + { + addZoomTooltip(client.getWidget(WidgetInfo.SETTINGS_SIDE_CAMERA_ZOOM_SLIDER_TRACK)); + } + } + + private void addZoomTooltip(Widget w) + { + w.setOnMouseRepeatListener((JavaScriptCallback) ev -> + { + int value = client.getVar(VarClientInt.CAMERA_ZOOM_RESIZABLE_VIEWPORT); + int max = config.innerLimit() ? config.INNER_ZOOM_LIMIT : CameraPlugin.DEFAULT_INNER_ZOOM_LIMIT; + sliderTooltip = new Tooltip("Camera Zoom: " + value + " / " + max); + }); + } + + @Subscribe + private void onBeforeRender(BeforeRender ev) + { + if (sliderTooltip != null) + { + tooltipManager.add(sliderTooltip); + sliderTooltip = null; + } + } + + /** + * The event that is triggered when a mouse button is pressed + * In this method the right click is changed to a middle-click to enable rotating the camera + *

+ * This method also provides the config option to enable the middle-mouse button to always open the right click menu + */ + @Override + public MouseEvent mousePressed(MouseEvent mouseEvent) + { + if (SwingUtilities.isRightMouseButton(mouseEvent) && config.rightClickMovesCamera()) + { + boolean oneButton = client.getVar(VarPlayer.MOUSE_BUTTONS) == 1; + // Only move the camera if there is nothing at the menu, or if + // in one-button mode. In one-button mode, left and right click always do the same thing, + // so always treat it as the menu is empty + if (!menuHasEntries || oneButton) + { + // Set the rightClick flag to true so we can release the button in mouseReleased() later + rightClick = true; + // Change the mousePressed() MouseEvent to the middle mouse button + mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), + mouseEvent.getID(), + mouseEvent.getWhen(), + mouseEvent.getModifiersEx(), + mouseEvent.getX(), + mouseEvent.getY(), + mouseEvent.getClickCount(), + mouseEvent.isPopupTrigger(), + MouseEvent.BUTTON2); + } + } + else if (SwingUtilities.isMiddleMouseButton((mouseEvent)) && config.middleClickMenu()) + { + // Set the middleClick flag to true so we can release it later in mouseReleased() + middleClick = true; + // Chance the middle mouse button MouseEvent to a right-click + mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), + mouseEvent.getID(), + mouseEvent.getWhen(), + mouseEvent.getModifiersEx(), + mouseEvent.getX(), + mouseEvent.getY(), + mouseEvent.getClickCount(), + mouseEvent.isPopupTrigger(), + MouseEvent.BUTTON3); + } + return mouseEvent; + } + + /** + * Correct the MouseEvent to release the correct button + */ + @Override + public MouseEvent mouseReleased(MouseEvent mouseEvent) + { + if (rightClick) + { + rightClick = false; + // Change the MouseEvent to button 2 so the middle mouse button will be released + mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), + mouseEvent.getID(), + mouseEvent.getWhen(), + mouseEvent.getModifiersEx(), + mouseEvent.getX(), + mouseEvent.getY(), + mouseEvent.getClickCount(), + mouseEvent.isPopupTrigger(), + MouseEvent.BUTTON2); + + } + if (middleClick) + { + middleClick = false; + // Change the MouseEvent ot button 3 so the right mouse button will be released + mouseEvent = new MouseEvent((java.awt.Component) mouseEvent.getSource(), + mouseEvent.getID(), + mouseEvent.getWhen(), + mouseEvent.getModifiersEx(), + mouseEvent.getX(), + mouseEvent.getY(), + mouseEvent.getClickCount(), + mouseEvent.isPopupTrigger(), + MouseEvent.BUTTON3); + } + return mouseEvent; + } + + /* + * These methods are unused but required to be present in a MouseListener implementation + */ + // region Unused MouseListener methods + @Override + public MouseEvent mouseDragged(MouseEvent mouseEvent) + { + return mouseEvent; + } + + @Override + public MouseEvent mouseMoved(MouseEvent mouseEvent) + { + return mouseEvent; + } + + @Override + public MouseEvent mouseClicked(MouseEvent mouseEvent) + { + return mouseEvent; + } + + @Override + public MouseEvent mouseEntered(MouseEvent mouseEvent) + { + return mouseEvent; + } + + @Override + public MouseEvent mouseExited(MouseEvent mouseEvent) + { + return mouseEvent; + } + // endregion +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java b/runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java new file mode 100644 index 0000000000..07fecfd5ba --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/camera/ControlFunction.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019, Jacob M + * 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 HOLDER 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.camera; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ControlFunction +{ + NONE("None"), + CONTROL_TO_ZOOM("Hold to zoom"), + CONTROL_TO_RESET("Reset zoom"); + + private final String name; + + @Override + public String toString() + { + return getName(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollConfig.java new file mode 100644 index 0000000000..f95341e50d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018, Seth + * 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.cluescrolls; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("cluescroll") +public interface ClueScrollConfig extends Config +{ + @ConfigItem( + keyName = "displayHintArrows", + name = "Display hint arrows", + description = "Configures whether or not to display hint arrows for clues" + ) + default boolean displayHintArrows() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollEmoteOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollEmoteOverlay.java new file mode 100644 index 0000000000..ecaa85b8ba --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollEmoteOverlay.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +class ClueScrollEmoteOverlay extends Overlay +{ + private final ClueScrollPlugin plugin; + private final Client client; + + private boolean hasScrolled; + + @Inject + private ClueScrollEmoteOverlay(ClueScrollPlugin plugin, Client client) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.plugin = plugin; + this.client = client; + } + + @Override + public Dimension render(Graphics2D graphics) + { + ClueScroll clue = plugin.getClue(); + + if (!(clue instanceof EmoteClue)) + { + hasScrolled = false; + return null; + } + + EmoteClue emoteClue = (EmoteClue) clue; + + if (!emoteClue.getFirstEmote().hasSprite()) + { + return null; + } + + Widget emoteContainer = client.getWidget(WidgetInfo.EMOTE_CONTAINER); + + if (emoteContainer == null || emoteContainer.isHidden()) + { + return null; + } + + Widget emoteWindow = client.getWidget(WidgetInfo.EMOTE_WINDOW); + + if (emoteWindow == null) + { + return null; + } + + Widget firstEmoteWidget = null; + Widget secondEmoteWidget = null; + + for (Widget emoteWidget : emoteContainer.getDynamicChildren()) + { + if (emoteWidget.getSpriteId() == emoteClue.getFirstEmote().getSpriteId()) + { + firstEmoteWidget = emoteWidget; + plugin.highlightWidget(graphics, emoteWidget, emoteWindow, null, + emoteClue.getSecondEmote() != null ? "1st" : null); + } + else if (emoteClue.getSecondEmote() != null + && emoteWidget.getSpriteId() == emoteClue.getSecondEmote().getSpriteId()) + { + secondEmoteWidget = emoteWidget; + plugin.highlightWidget(graphics, emoteWidget, emoteWindow, null, "2nd"); + } + } + if (!hasScrolled) + { + hasScrolled = true; + plugin.scrollToWidget(WidgetInfo.EMOTE_CONTAINER, WidgetInfo.EMOTE_SCROLLBAR, firstEmoteWidget, secondEmoteWidget); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollMusicOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollMusicOverlay.java new file mode 100644 index 0000000000..2b44290fb5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollMusicOverlay.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019, Hydrox6 + * 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.cluescrolls; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.MusicClue; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +class ClueScrollMusicOverlay extends Overlay +{ + private static final Rectangle PADDING = new Rectangle(2, 1, 0, 1); + + private final ClueScrollPlugin plugin; + private final Client client; + + private boolean hasScrolled; + + @Inject + private ClueScrollMusicOverlay(ClueScrollPlugin plugin, Client client) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.plugin = plugin; + this.client = client; + } + + @Override + public Dimension render(Graphics2D graphics) + { + ClueScroll clue = plugin.getClue(); + + if (!(clue instanceof MusicClue)) + { + hasScrolled = false; + return null; + } + + MusicClue musicClue = (MusicClue) clue; + + Widget musicContainer = client.getWidget(WidgetInfo.MUSIC_WINDOW); + + if (musicContainer == null || musicContainer.isHidden()) + { + return null; + } + + Widget trackList = client.getWidget(WidgetInfo.MUSIC_TRACK_LIST); + String trackToFind = musicClue.getSong(); + Widget found = null; + + if (trackList == null) + { + return null; + } + + for (Widget track : trackList.getDynamicChildren()) + { + if (track.getText().equals(trackToFind)) + { + found = track; + break; + } + } + + if (found == null) + { + return null; + } + + if (!hasScrolled) + { + hasScrolled = true; + plugin.scrollToWidget(WidgetInfo.MUSIC_TRACK_LIST, WidgetInfo.MUSIC_TRACK_SCROLLBAR, found); + } + plugin.highlightWidget(graphics, found, trackList, PADDING, null); + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java new file mode 100644 index 0000000000..382f77aa52 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016-2017, Seth + * Copyright (c) 2018, Lotto + * Copyright (c) 2019, David + * 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 HOLDER 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.cluescrolls; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Item; +import static net.runelite.api.ItemID.*; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollection; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.item; +import net.runelite.client.plugins.cluescrolls.clues.item.SingleItemRequirement; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.LineComponent; + +public class ClueScrollOverlay extends OverlayPanel +{ + private static final ItemRequirement HAS_SPADE = new SingleItemRequirement(SPADE); + private static final ItemRequirement HAS_LIGHT = new AnyRequirementCollection("Light Source", + item(LIT_TORCH), + item(LIT_CANDLE), + item(LIT_BLACK_CANDLE), + item(CANDLE_LANTERN_4531), + item(CANDLE_LANTERN_4534), // lit black candle lantern + item(OIL_LAMP_4524), + item(OIL_LANTERN_4539), + item(BULLSEYE_LANTERN_4550), + item(SAPPHIRE_LANTERN_4702), + item(EMERALD_LANTERN_9065), + item(MINING_HELMET), + item(FIREMAKING_CAPE), + item(FIREMAKING_CAPET), + item(KANDARIN_HEADGEAR_1), + item(KANDARIN_HEADGEAR_2), + item(KANDARIN_HEADGEAR_3), + item(KANDARIN_HEADGEAR_4), + item(BRUMA_TORCH), + item(MAX_CAPE), + item(MAX_CAPE_13342)); + + public static final Color TITLED_CONTENT_COLOR = new Color(190, 190, 190); + + private final ClueScrollPlugin plugin; + private final Client client; + + @Inject + private ClueScrollOverlay(ClueScrollPlugin plugin, Client client) + { + super(plugin); + this.plugin = plugin; + this.client = client; + setPriority(OverlayPriority.LOW); + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Clue Scroll overlay")); + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, "Reset", "Clue Scroll overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + ClueScroll clue = plugin.getClue(); + + if (clue == null) + { + return null; + } + + clue.makeOverlayHint(panelComponent, plugin); + + final Item[] inventoryItems = plugin.getInventoryItems(); + final Item[] equippedItems = plugin.getEquippedItems(); + + if (clue.isRequiresSpade() && inventoryItems != null) + { + if (!HAS_SPADE.fulfilledBy(inventoryItems)) + { + panelComponent.getChildren().add(LineComponent.builder().left("").build()); + panelComponent.getChildren().add(LineComponent.builder().left("Requires Spade!").leftColor(Color.RED).build()); + } + } + + if (clue.isRequiresLight() + && ((clue.getHasFirePit() == null || client.getVar(clue.getHasFirePit()) != 1) + && (inventoryItems == null || !HAS_LIGHT.fulfilledBy(inventoryItems)) + && (equippedItems == null || !HAS_LIGHT.fulfilledBy(equippedItems)))) + { + panelComponent.getChildren().add(LineComponent.builder().left("").build()); + panelComponent.getChildren().add(LineComponent.builder().left("Requires Light Source!").leftColor(Color.RED).build()); + } + + if (clue.getEnemy() != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(clue.getEnemy().getText()) + .leftColor(Color.YELLOW) + .build()); + } + + return super.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java new file mode 100644 index 0000000000..1cca0adfb3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java @@ -0,0 +1,1112 @@ +/* + * Copyright (c) 2016-2017, Seth + * Copyright (c) 2018, Lotto + * 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 HOLDER 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.cluescrolls; + +import com.google.common.base.MoreObjects; +import com.google.inject.Binder; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Named; +import joptsimple.internal.Strings; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.GameState; +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 net.runelite.api.MenuAction; +import net.runelite.api.NPC; +import net.runelite.api.ObjectComposition; +import net.runelite.api.Point; +import net.runelite.api.Scene; +import net.runelite.api.ScriptID; +import net.runelite.api.Tile; +import net.runelite.api.TileObject; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.CommandExecuted; +import net.runelite.api.events.DecorativeObjectChanged; +import net.runelite.api.events.DecorativeObjectDespawned; +import net.runelite.api.events.DecorativeObjectSpawned; +import net.runelite.api.events.GameObjectChanged; +import net.runelite.api.events.GameObjectDespawned; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.GroundObjectChanged; +import net.runelite.api.events.GroundObjectDespawned; +import net.runelite.api.events.GroundObjectSpawned; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.WallObjectChanged; +import net.runelite.api.events.WallObjectDespawned; +import net.runelite.api.events.WallObjectSpawned; +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.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.cluescrolls.clues.AnagramClue; +import net.runelite.client.plugins.cluescrolls.clues.BeginnerMapClue; +import net.runelite.client.plugins.cluescrolls.clues.CipherClue; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue; +import net.runelite.client.plugins.cluescrolls.clues.CrypticClue; +import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; +import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue; +import net.runelite.client.plugins.cluescrolls.clues.FaloTheBardClue; +import net.runelite.client.plugins.cluescrolls.clues.HotColdClue; +import net.runelite.client.plugins.cluescrolls.clues.LocationClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.LocationsClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.MapClue; +import net.runelite.client.plugins.cluescrolls.clues.MusicClue; +import net.runelite.client.plugins.cluescrolls.clues.NamedObjectClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.NpcClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.ObjectClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.SkillChallengeClue; +import net.runelite.client.plugins.cluescrolls.clues.TextClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.ThreeStepCrypticClue; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.TextComponent; +import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; + +@PluginDescriptor( + name = "Clue Scroll", + description = "Show answers to clue scroll riddles, anagrams, ciphers, and cryptic clues", + tags = {"arrow", "hints", "world", "map", "coordinates", "emotes"} +) +@Slf4j +public class ClueScrollPlugin extends Plugin +{ + private static final Color HIGHLIGHT_BORDER_COLOR = Color.ORANGE; + private static final Color HIGHLIGHT_HOVER_BORDER_COLOR = HIGHLIGHT_BORDER_COLOR.darker(); + private static final Color HIGHLIGHT_FILL_COLOR = new Color(0, 255, 0, 20); + private static final int[] REGION_MIRRORS = { + // Prifddinas + 12894, 8755, + 12895, 8756, + 13150, 9011, + 13151, 9012 + }; + + @Getter + private ClueScroll clue; + + @Getter + private final List npcsToMark = new ArrayList<>(); + + @Getter + private final List objectsToMark = new ArrayList<>(); + + @Getter + private final Set namedObjectsToMark = new HashSet<>(); + + @Getter + private Item[] equippedItems; + + @Getter + private Item[] inventoryItems; + + @Inject + @Getter + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ClueScrollOverlay clueScrollOverlay; + + @Inject + private ClueScrollEmoteOverlay clueScrollEmoteOverlay; + + @Inject + private ClueScrollMusicOverlay clueScrollMusicOverlay; + + @Inject + private ClueScrollWorldOverlay clueScrollWorldOverlay; + + @Inject + private ClueScrollConfig config; + + @Inject + private WorldMapPointManager worldMapPointManager; + + @Inject + @Named("developerMode") + boolean developerMode; + + private BufferedImage emoteImage; + private BufferedImage mapArrow; + private Integer clueItemId; + private boolean worldMapPointsSet = false; + + // Some objects will only update to their "active" state when changing to their plane after varbit changes, + // which take one extra tick to fire after the plane change. These fields are used to track those changes and delay + // scans of the current plane's tiles accordingly. + private int currentPlane = -1; + private boolean namedObjectCheckThisTick; + + private final TextComponent textComponent = new TextComponent(); + + @Provides + ClueScrollConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ClueScrollConfig.class); + } + + @Override + public void configure(Binder binder) + { + binder.bind(ClueScrollService.class).to(ClueScrollServiceImpl.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(clueScrollOverlay); + overlayManager.add(clueScrollEmoteOverlay); + overlayManager.add(clueScrollWorldOverlay); + overlayManager.add(clueScrollMusicOverlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(clueScrollOverlay); + overlayManager.remove(clueScrollEmoteOverlay); + overlayManager.remove(clueScrollWorldOverlay); + overlayManager.remove(clueScrollMusicOverlay); + npcsToMark.clear(); + namedObjectsToMark.clear(); + inventoryItems = null; + equippedItems = null; + currentPlane = -1; + namedObjectCheckThisTick = false; + resetClue(true); + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.GAMEMESSAGE && event.getType() != ChatMessageType.SPAM) + { + return; + } + + if (clue instanceof HotColdClue) + { + if (((HotColdClue) clue).update(event.getMessage(), this)) + { + worldMapPointsSet = false; + } + } + + if (clue instanceof SkillChallengeClue) + { + String text = Text.removeTags(event.getMessage()); + if (text.equals("Skill challenge completed.") || + text.equals("You have completed your master level challenge!") || + text.startsWith("You have completed Charlie's task,") || + text.equals("You have completed this challenge scroll.")) + { + ((SkillChallengeClue) clue).setChallengeCompleted(true); + } + } + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked) + { + OverlayMenuEntry overlayMenuEntry = overlayMenuClicked.getEntry(); + if (overlayMenuEntry.getMenuAction() == MenuAction.RUNELITE_OVERLAY + && overlayMenuClicked.getEntry().getOption().equals("Reset") + && overlayMenuClicked.getOverlay() == clueScrollOverlay) + { + resetClue(true); + } + } + + @Subscribe + public void onMenuOptionClicked(final MenuOptionClicked event) + { + if (event.getMenuOption() != null && event.getMenuOption().equals("Read")) + { + final ItemComposition itemComposition = itemManager.getItemComposition(event.getId()); + + if (itemComposition != null && (itemComposition.getName().startsWith("Clue scroll") || itemComposition.getName().startsWith("Challenge scroll"))) + { + clueItemId = itemComposition.getId(); + updateClue(MapClue.forItemId(clueItemId)); + } + } + } + + @Subscribe + public void onItemContainerChanged(final ItemContainerChanged event) + { + if (event.getItemContainer() == client.getItemContainer(InventoryID.EQUIPMENT)) + { + equippedItems = event.getItemContainer().getItems(); + return; + } + + if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY)) + { + return; + } + + inventoryItems = event.getItemContainer().getItems(); + + // Check if item was removed from inventory + if (clue != null && clueItemId != null) + { + ItemContainer itemContainer = event.getItemContainer(); + + // Check if clue was removed from inventory + if (!itemContainer.contains(clueItemId)) + { + resetClue(true); + } + } + + // if three step clue check for clue scroll pieces + if (clue instanceof ThreeStepCrypticClue) + { + if (((ThreeStepCrypticClue) clue).update(event.getContainerId(), event.getItemContainer())) + { + worldMapPointsSet = false; + npcsToMark.clear(); + + if (config.displayHintArrows()) + { + client.clearHintArrow(); + } + + checkClueNPCs(clue, client.getCachedNPCs()); + } + } + } + + @Subscribe + public void onNpcSpawned(final NpcSpawned event) + { + final NPC npc = event.getNpc(); + checkClueNPCs(clue, npc); + } + + @Subscribe + public void onNpcDespawned(final NpcDespawned event) + { + final boolean removed = npcsToMark.remove(event.getNpc()); + + if (removed) + { + if (npcsToMark.isEmpty()) + { + client.clearHintArrow(); + } + else + { + // Always set hint arrow to first seen NPC + client.setHintArrow(npcsToMark.get(0)); + } + } + } + + @Subscribe + public void onDecorativeObjectChanged(final DecorativeObjectChanged event) + { + tileObjectChangedHandler(event.getPrevious(), event.getDecorativeObject()); + } + + @Subscribe + public void onDecorativeObjectDespawned(final DecorativeObjectDespawned event) + { + tileObjectDespawnedHandler(event.getDecorativeObject()); + } + + @Subscribe + public void onDecorativeObjectSpawned(final DecorativeObjectSpawned event) + { + tileObjectSpawnedHandler(event.getDecorativeObject()); + } + + @Subscribe + public void onGameObjectChanged(final GameObjectChanged event) + { + tileObjectChangedHandler(event.getPrevious(), event.getGameObject()); + } + + @Subscribe + public void onGameObjectDespawned(final GameObjectDespawned event) + { + tileObjectDespawnedHandler(event.getGameObject()); + } + + @Subscribe + public void onGameObjectSpawned(final GameObjectSpawned event) + { + tileObjectSpawnedHandler(event.getGameObject()); + } + + @Subscribe + public void onGroundObjectChanged(final GroundObjectChanged event) + { + tileObjectChangedHandler(event.getPrevious(), event.getGroundObject()); + } + + @Subscribe + public void onGroundObjectDespawned(final GroundObjectDespawned event) + { + tileObjectDespawnedHandler(event.getGroundObject()); + } + + @Subscribe + public void onGroundObjectSpawned(final GroundObjectSpawned event) + { + tileObjectSpawnedHandler(event.getGroundObject()); + } + + @Subscribe + public void onWallObjectChanged(final WallObjectChanged event) + { + tileObjectChangedHandler(event.getPrevious(), event.getWallObject()); + } + + @Subscribe + public void onWallObjectDespawned(final WallObjectDespawned event) + { + tileObjectDespawnedHandler(event.getWallObject()); + } + + @Subscribe + public void onWallObjectSpawned(final WallObjectSpawned event) + { + tileObjectSpawnedHandler(event.getWallObject()); + } + + private void tileObjectChangedHandler(final TileObject prev, final TileObject changedTo) + { + tileObjectDespawnedHandler(prev); + tileObjectSpawnedHandler(changedTo); + } + + private void tileObjectDespawnedHandler(final TileObject despawned) + { + namedObjectsToMark.remove(despawned); + } + + private void tileObjectSpawnedHandler(final TileObject spawned) + { + checkClueNamedObject(clue, spawned); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("cluescroll") && !config.displayHintArrows()) + { + client.clearHintArrow(); + } + } + + @Subscribe + public void onGameStateChanged(final GameStateChanged event) + { + final GameState state = event.getGameState(); + + if (state != GameState.LOGGED_IN) + { + namedObjectsToMark.clear(); + } + + if (state == GameState.LOGIN_SCREEN) + { + resetClue(true); + } + } + + @Subscribe + public void onGameTick(final GameTick event) + { + objectsToMark.clear(); + + if (clue instanceof LocationsClueScroll) + { + final WorldPoint[] locations = ((LocationsClueScroll) clue).getLocations(); + + if (locations.length > 0) + { + addMapPoints(locations); + } + + if (clue instanceof ObjectClueScroll) + { + int[] objectIds = ((ObjectClueScroll) clue).getObjectIds(); + + if (objectIds.length > 0) + { + for (WorldPoint location : locations) + { + if (location != null) + { + highlightObjectsForLocation(location, objectIds); + } + } + } + } + } + + if (clue instanceof LocationClueScroll) + { + final WorldPoint[] locations = ((LocationClueScroll) clue).getLocations(); + final boolean npcHintArrowMarked = client.getHintArrowNpc() != null && npcsToMark.contains(client.getHintArrowNpc()); + + if (!npcHintArrowMarked) + { + client.clearHintArrow(); + } + + for (WorldPoint location : locations) + { + // Only set the location hint arrow if we do not already have more accurate location + if (location.isInScene(client) && config.displayHintArrows() && !npcHintArrowMarked) + { + client.setHintArrow(location); + } + + addMapPoints(location); + + if (clue instanceof ObjectClueScroll) + { + int[] objectIds = ((ObjectClueScroll) clue).getObjectIds(); + + if (objectIds.length > 0) + { + highlightObjectsForLocation(location, objectIds); + } + } + } + } + + // Load the current plane's tiles if a tick has elapsed since the player has changed planes + if (namedObjectCheckThisTick) + { + namedObjectCheckThisTick = false; + checkClueNamedObjects(clue); + } + + // Delay one tick when changing planes before scanning for new named objects on the new plane + if (currentPlane != client.getPlane()) + { + currentPlane = client.getPlane(); + namedObjectCheckThisTick = true; + } + + // Reset clue when receiving a new beginner or master clue + // These clues use a single item ID, so we cannot detect step changes based on the item ID changing + final Widget chatDialogClueItem = client.getWidget(WidgetInfo.DIALOG_SPRITE_SPRITE); + if (chatDialogClueItem != null + && (chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_BEGINNER || chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_MASTER)) + { + resetClue(true); + } + + final Widget clueScrollText = client.getWidget(WidgetInfo.CLUE_SCROLL_TEXT); + + if (clueScrollText != null) + { + ClueScroll clueScroll = findClueScroll(clueScrollText.getText()); + updateClue(clueScroll); + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() < WidgetID.BEGINNER_CLUE_MAP_CHAMPIONS_GUILD + || event.getGroupId() > WidgetID.BEGINNER_CLUE_MAP_WIZARDS_TOWER) + { + return; + } + + updateClue(BeginnerMapClue.forWidgetID(event.getGroupId())); + } + + @Subscribe + public void onCommandExecuted(CommandExecuted commandExecuted) + { + if (developerMode && commandExecuted.getCommand().equals("clue")) + { + String text = Strings.join(commandExecuted.getArguments(), " "); + + if (text.isEmpty()) + { + resetClue(true); + } + else + { + ClueScroll clueScroll = findClueScroll(text); + log.debug("Found clue scroll for '{}': {}", text, clueScroll); + updateClue(clueScroll); + } + } + } + + public BufferedImage getClueScrollImage() + { + return itemManager.getImage(ItemID.CLUE_SCROLL_MASTER); + } + + public BufferedImage getEmoteImage() + { + if (emoteImage != null) + { + return emoteImage; + } + + emoteImage = ImageUtil.getResourceStreamFromClass(getClass(), "emote.png"); + + return emoteImage; + } + + public BufferedImage getSpadeImage() + { + return itemManager.getImage(ItemID.SPADE); + } + + BufferedImage getMapArrow() + { + if (mapArrow != null) + { + return mapArrow; + } + + mapArrow = ImageUtil.getResourceStreamFromClass(getClass(), "/util/clue_arrow.png"); + + return mapArrow; + } + + private void resetClue(boolean withItemId) + { + if (clue instanceof LocationsClueScroll) + { + ((LocationsClueScroll) clue).reset(); + } + + if (withItemId) + { + clueItemId = null; + } + + clue = null; + worldMapPointManager.removeIf(ClueScrollWorldMapPoint.class::isInstance); + worldMapPointsSet = false; + npcsToMark.clear(); + namedObjectsToMark.clear(); + + if (config.displayHintArrows()) + { + client.clearHintArrow(); + } + } + + private ClueScroll findClueScroll(String rawText) + { + // Remove line breaks and also the rare occasion where there are double line breaks + final String text = Text.sanitizeMultilineText(rawText).toLowerCase(); + + // Early return if this is same clue as already existing one + if (clue instanceof TextClueScroll) + { + if (((TextClueScroll) clue).getText().equalsIgnoreCase(text)) + { + return clue; + } + } + + if (text.startsWith("i'd like to hear some music.")) + { + return MusicClue.forText(rawText); + } + + if (text.contains("degrees") && text.contains("minutes")) + { + return coordinatesToWorldPoint(text); + } + + final AnagramClue anagramClue = AnagramClue.forText(text); + if (anagramClue != null) + { + return anagramClue; + } + + final CipherClue cipherClue = CipherClue.forText(text); + if (cipherClue != null) + { + return cipherClue; + } + + final CrypticClue crypticClue = CrypticClue.forText(text); + + if (crypticClue != null) + { + return crypticClue; + } + + final EmoteClue emoteClue = EmoteClue.forText(text); + + if (emoteClue != null) + { + return emoteClue; + } + + final FairyRingClue fairyRingClue = FairyRingClue.forText(text); + + if (fairyRingClue != null) + { + return fairyRingClue; + } + + final FaloTheBardClue faloTheBardClue = FaloTheBardClue.forText(text); + + if (faloTheBardClue != null) + { + return faloTheBardClue; + } + + final HotColdClue hotColdClue = HotColdClue.forText(text); + + if (hotColdClue != null) + { + return hotColdClue; + } + + final SkillChallengeClue skillChallengeClue = SkillChallengeClue.forText(text, rawText); + + if (skillChallengeClue != null) + { + return skillChallengeClue; + } + + // three step cryptic clues need unedited text to check which steps are already done + final ThreeStepCrypticClue threeStepCrypticClue = ThreeStepCrypticClue.forText(text, rawText); + + if (threeStepCrypticClue != null) + { + return threeStepCrypticClue; + } + + // We have unknown clue, reset + log.warn("Encountered unhandled clue text: {}", rawText); + resetClue(true); + return null; + } + + /** + * Example input: "00 degrees 00 minutes north 07 degrees 13 minutes west" + * Note: some clues use "1 degree" instead of "01 degrees" + */ + private CoordinateClue coordinatesToWorldPoint(String text) + { + String[] splitText = text.split(" "); + + if (splitText.length != 10) + { + log.warn("Splitting \"" + text + "\" did not result in an array of 10 cells"); + return null; + } + + if (!splitText[1].startsWith("degree") || !splitText[3].startsWith("minute")) + { + log.warn("\"" + text + "\" is not a well formed coordinate string"); + return null; + } + + int degY = Integer.parseInt(splitText[0]); + int minY = Integer.parseInt(splitText[2]); + + if (splitText[4].equals("south")) + { + degY *= -1; + minY *= -1; + } + + int degX = Integer.parseInt(splitText[5]); + int minX = Integer.parseInt(splitText[7]); + + if (splitText[9].equals("west")) + { + degX *= -1; + minX *= -1; + } + + WorldPoint coordinate = coordinatesToWorldPoint(degX, minX, degY, minY); + // Convert from overworld to real + WorldPoint mirrorPoint = getMirrorPoint(coordinate, false); + // Use mirror point as mirrorLocation if there is one + return new CoordinateClue(text, coordinate, coordinate == mirrorPoint ? null : mirrorPoint); + } + + /** + * This conversion is explained on + * https://oldschool.runescape.wiki/w/Treasure_Trails/Guide/Coordinates + */ + private WorldPoint coordinatesToWorldPoint(int degX, int minX, int degY, int minY) + { + // Center of the Observatory + int x2 = 2440; + int y2 = 3161; + + x2 += degX * 32 + Math.round(minX / 1.875); + y2 += degY * 32 + Math.round(minY / 1.875); + + return new WorldPoint(x2, y2, 0); + } + + private void addMapPoints(WorldPoint... points) + { + if (worldMapPointsSet) + { + return; + } + + worldMapPointsSet = true; + worldMapPointManager.removeIf(ClueScrollWorldMapPoint.class::isInstance); + + for (final WorldPoint point : points) + { + worldMapPointManager.add(new ClueScrollWorldMapPoint(point, this)); + } + } + + private void highlightObjectsForLocation(final WorldPoint location, final int... objectIds) + { + final LocalPoint localLocation = LocalPoint.fromWorld(client, location); + + if (localLocation == null) + { + return; + } + + final Scene scene = client.getScene(); + final Tile[][][] tiles = scene.getTiles(); + final Tile tile = tiles[client.getPlane()][localLocation.getSceneX()][localLocation.getSceneY()]; + + for (GameObject object : tile.getGameObjects()) + { + if (object == null) + { + continue; + } + + for (int id : objectIds) + { + if (object.getId() == id) + { + objectsToMark.add(object); + continue; + } + + // Check impostors + final ObjectComposition comp = client.getObjectDefinition(object.getId()); + final ObjectComposition impostor = comp.getImpostorIds() != null ? comp.getImpostor() : comp; + + if (impostor != null && impostor.getId() == id) + { + objectsToMark.add(object); + } + } + } + } + + private void checkClueNPCs(ClueScroll clue, final NPC... npcs) + { + if (!(clue instanceof NpcClueScroll)) + { + return; + } + + final NpcClueScroll npcClueScroll = (NpcClueScroll) clue; + + if (npcClueScroll.getNpcs() == null || npcClueScroll.getNpcs().length == 0) + { + return; + } + + for (NPC npc : npcs) + { + if (npc == null || npc.getName() == null) + { + continue; + } + + for (String npcName : npcClueScroll.getNpcs()) + { + if (!Objects.equals(npc.getName(), npcName)) + { + continue; + } + + npcsToMark.add(npc); + } + } + + if (!npcsToMark.isEmpty() && config.displayHintArrows()) + { + // Always set hint arrow to first seen NPC + client.setHintArrow(npcsToMark.get(0)); + } + } + + /** + * Scans all of the current plane's loaded tiles for {@link TileObject}s and passes any found objects to + * {@link ClueScrollPlugin#checkClueNamedObject(ClueScroll, TileObject)} for storing in the cache of discovered + * named objects. + * + * @param clue The active clue scroll + */ + private void checkClueNamedObjects(@Nullable ClueScroll clue) + { + if (!(clue instanceof NamedObjectClueScroll)) + { + return; + } + + // Search loaded tiles for objects + for (final Tile[] tiles : client.getScene().getTiles()[client.getPlane()]) + { + for (final Tile tile : tiles) + { + if (tile == null) + { + continue; + } + + for (final GameObject object : tile.getGameObjects()) + { + if (object == null) + { + continue; + } + + checkClueNamedObject(clue, object); + } + } + } + } + + /** + * Checks passed objects against the active clue's object names and regions. If the clue is a + * {@link NamedObjectClueScroll} and the object matches its allowable object names and is within its regions, the + * object will be stored in the cache of discovered named objects. + * + * @param clue The active clue scroll + * @param object The spawned or scanned object + */ + private void checkClueNamedObject(@Nullable final ClueScroll clue, @Nonnull final TileObject object) + { + if (!(clue instanceof NamedObjectClueScroll)) + { + return; + } + + final NamedObjectClueScroll namedObjectClue = (NamedObjectClueScroll) clue; + + final String[] objectNames = namedObjectClue.getObjectNames(); + final int[] regionIds = namedObjectClue.getObjectRegions(); + + if (objectNames == null || objectNames.length == 0 + || regionIds != null && !ArrayUtils.contains(regionIds, object.getWorldLocation().getRegionID())) + { + return; + } + + final ObjectComposition comp = client.getObjectDefinition(object.getId()); + final ObjectComposition impostor = comp.getImpostorIds() != null ? comp.getImpostor() : comp; + + for (final String name : objectNames) + { + if (comp.getName().equals(name) || impostor.getName().equals(name)) + { + namedObjectsToMark.add(object); + } + } + } + + private void updateClue(final ClueScroll clue) + { + if (clue == null || clue == this.clue) + { + return; + } + + resetClue(false); + checkClueNPCs(clue, client.getCachedNPCs()); + checkClueNamedObjects(clue); + // If we have a clue, save that knowledge + // so the clue window doesn't have to be open. + this.clue = clue; + } + + void highlightWidget(Graphics2D graphics, Widget toHighlight, Widget container, Rectangle padding, String text) + { + padding = MoreObjects.firstNonNull(padding, new Rectangle()); + + Point canvasLocation = toHighlight.getCanvasLocation(); + + if (canvasLocation == null) + { + return; + } + + Point windowLocation = container.getCanvasLocation(); + + if (windowLocation.getY() > canvasLocation.getY() + toHighlight.getHeight() + || windowLocation.getY() + container.getHeight() < canvasLocation.getY()) + { + return; + } + + // Visible area of widget + Area widgetArea = new Area( + new Rectangle( + canvasLocation.getX() - padding.x, + Math.max(canvasLocation.getY(), windowLocation.getY()) - padding.y, + toHighlight.getWidth() + padding.x + padding.width, + Math.min( + Math.min(windowLocation.getY() + container.getHeight() - canvasLocation.getY(), toHighlight.getHeight()), + Math.min(canvasLocation.getY() + toHighlight.getHeight() - windowLocation.getY(), toHighlight.getHeight())) + padding.y + padding.height + )); + + OverlayUtil.renderHoverableArea(graphics, widgetArea, client.getMouseCanvasPosition(), + HIGHLIGHT_FILL_COLOR, HIGHLIGHT_BORDER_COLOR, HIGHLIGHT_HOVER_BORDER_COLOR); + + if (text == null) + { + return; + } + + FontMetrics fontMetrics = graphics.getFontMetrics(); + + textComponent.setPosition(new java.awt.Point( + canvasLocation.getX() + toHighlight.getWidth() / 2 - fontMetrics.stringWidth(text) / 2, + canvasLocation.getY() + fontMetrics.getHeight())); + textComponent.setText(text); + textComponent.render(graphics); + } + + void scrollToWidget(WidgetInfo list, WidgetInfo scrollbar, Widget ... toHighlight) + { + final Widget parent = client.getWidget(list); + int averageCentralY = 0; + int nonnullCount = 0; + for (Widget widget : toHighlight) + { + if (widget != null) + { + averageCentralY += widget.getRelativeY() + widget.getHeight() / 2; + nonnullCount += 1; + } + } + if (nonnullCount == 0) + { + return; + } + averageCentralY /= nonnullCount; + final int newScroll = Math.max(0, Math.min(parent.getScrollHeight(), + averageCentralY - parent.getHeight() / 2)); + + client.runScript( + ScriptID.UPDATE_SCROLLBAR, + scrollbar.getId(), + list.getId(), + newScroll + ); + } + + /** + * Translate a coordinate either between overworld and real, or real and overworld + * + * @param worldPoint + * @param toOverworld whether to convert to overworld coordinates, or to real coordinates + * @return + */ + public static WorldPoint getMirrorPoint(WorldPoint worldPoint, boolean toOverworld) + { + int region = worldPoint.getRegionID(); + for (int i = 0; i < REGION_MIRRORS.length; i += 2) + { + int real = REGION_MIRRORS[i]; + int overworld = REGION_MIRRORS[i + 1]; + + // Test against what we are converting from + if (region == (toOverworld ? real : overworld)) + { + return WorldPoint.fromRegion(toOverworld ? overworld : real, + worldPoint.getRegionX(), worldPoint.getRegionY(), worldPoint.getPlane()); + } + } + return worldPoint; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollService.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollService.java new file mode 100644 index 0000000000..e84c77808f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollService.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.cluescrolls; + +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; + +public interface ClueScrollService +{ + /** + * Get the clue scroll + * + * @return ClueScroll + */ + ClueScroll getClue(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollServiceImpl.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollServiceImpl.java new file mode 100644 index 0000000000..3dd2d55a33 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollServiceImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.cluescrolls; + +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; + +@Singleton +class ClueScrollServiceImpl implements ClueScrollService +{ + private final ClueScrollPlugin plugin; + + @Inject + private ClueScrollServiceImpl(ClueScrollPlugin plugin) + { + this.plugin = plugin; + } + + @Override + public ClueScroll getClue() + { + return plugin.getClue(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldMapPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldMapPoint.java new file mode 100644 index 0000000000..45fa1f874c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldMapPoint.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018, Morgan Lewis + * 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.cluescrolls; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import net.runelite.api.Point; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; + +class ClueScrollWorldMapPoint extends WorldMapPoint +{ + private final ClueScrollPlugin plugin; + private final BufferedImage clueScrollWorldImage; + private final Point clueScrollWorldImagePoint; + + ClueScrollWorldMapPoint(final WorldPoint worldPoint, ClueScrollPlugin plugin) + { + super(worldPoint, null); + + clueScrollWorldImage = new BufferedImage(plugin.getMapArrow().getWidth(), plugin.getMapArrow().getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics graphics = clueScrollWorldImage.getGraphics(); + graphics.drawImage(plugin.getMapArrow(), 0, 0, null); + graphics.drawImage(plugin.getClueScrollImage(), 0, 0, null); + clueScrollWorldImagePoint = new Point( + clueScrollWorldImage.getWidth() / 2, + clueScrollWorldImage.getHeight()); + + this.plugin = plugin; + this.setSnapToEdge(true); + this.setJumpOnClick(true); + this.setImage(clueScrollWorldImage); + this.setImagePoint(clueScrollWorldImagePoint); + } + + @Override + public void onEdgeSnap() + { + this.setImage(plugin.getClueScrollImage()); + this.setImagePoint(null); + } + + @Override + public void onEdgeUnsnap() + { + this.setImage(clueScrollWorldImage); + this.setImagePoint(clueScrollWorldImagePoint); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldOverlay.java new file mode 100644 index 0000000000..d68f06a691 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollWorldOverlay.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; + +public class ClueScrollWorldOverlay extends Overlay +{ + public static final int IMAGE_Z_OFFSET = 30; + + public static final Color CLICKBOX_BORDER_COLOR = Color.ORANGE; + public static final Color CLICKBOX_HOVER_BORDER_COLOR = CLICKBOX_BORDER_COLOR.darker(); + public static final Color CLICKBOX_FILL_COLOR = new Color(0, 255, 0, 20); + + private final ClueScrollPlugin plugin; + + @Inject + private ClueScrollWorldOverlay(ClueScrollPlugin plugin) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.plugin = plugin; + } + + @Override + public Dimension render(Graphics2D graphics) + { + ClueScroll clue = plugin.getClue(); + + if (clue != null) + { + clue.makeWorldOverlayHint(graphics, plugin); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java new file mode 100644 index 0000000000..28d848a5b3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Set; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.NPC; +import net.runelite.api.ObjectID; +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_FILL_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_HOVER_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueScroll, ObjectClueScroll +{ + private static final String ANAGRAM_TEXT = "This anagram reveals who to speak to next: "; + private static final String ANAGRAM_TEXT_BEGINNER = "The anagram reveals who to speak to next: "; + + private static final Set CLUES = ImmutableSet.of( + new AnagramClue("A BAKER", "Baraek", new WorldPoint(3217, 3434, 0), "Varrock square", "How many stalls are there in Varrock Square?", "5"), + new AnagramClue("A BASIC ANTI POT", "Captain Tobias", new WorldPoint(3026, 3216, 0), "Port Sarim", "How many ships are there docked at Port Sarim currently?", "6"), + new AnagramClue("A ELF KNOWS", "Snowflake", new WorldPoint(2872, 3934, 0), "Weiss"), + new AnagramClue("A HEART", "Aretha", new WorldPoint(1814, 3851, 0), "Soul altar", "32 - 5x = 22, what is x?", "2"), + new AnagramClue("A ZEN SHE", "Zenesha", new WorldPoint(2652, 3295, 0), "Platebody Southern Ardougne centre square"), + new AnagramClue("ACE MATCH ELM", "Cam The Camel", new WorldPoint(3300, 3231, 0), "North of the glider in Al Kharid"), + new AnagramClue("AHA JAR", "Jaraah", new WorldPoint(3359, 3276, 0), "Duel Arena hospital"), + new AnagramClue("AN PAINT TONIC", "Captain Ninto", new WorldPoint(2865, 9877, 0), "Bar under White Wolf Mountain"), + new AnagramClue("ARC O LINE", "Caroline", new WorldPoint(2715, 3302, 0), "North Witchaven next to the row boat", "How many fishermen are there on the fishing platform?", "11"), + new AnagramClue("ARE COL", "Oracle", new WorldPoint(3013, 3501, 0), "Ice Mountain West of Edgeville", "If x is 15 and y is 3 what is 3x + y?", "48"), + new AnagramClue("ARMCHAIR THE PELT", "Charlie the Tramp", new WorldPoint(3209, 3392, 0), "South entrance of Varrock", "How many coins would I have if I had 0 coins and attempted to buy 3 loaves of bread?", "0"), + new AnagramClue("ARR! SO I AM A CRUST, AND?", "Ramara du Croissant", new WorldPoint(2339, 3677, 0), "Piscatoris Fishing Colony"), + new AnagramClue("AT HERG", "Regath", new WorldPoint(1719, 3723, 0), "General Store, Arceuus, Zeah", "What is -5 to the power of 2?", "25"), + new AnagramClue("A BAS", "Saba", new WorldPoint(2858, 3577, 0), "Death Plateau"), + new AnagramClue("AREA CHEF TREK", "Father Aereck", new WorldPoint(3243, 3208, 0), "Lumbridge Church", "How many gravestones are in the church graveyard?", "19 or 20"), + new AnagramClue("BAIL TRIMS", "Brimstail", new WorldPoint(2402, 3419, 0), "West of Stronghold Slayer Cave"), + new AnagramClue("BAKER CLIMB", "Brambickle", new WorldPoint(2783, 3861, 0), "Trollweiss mountain"), + new AnagramClue("BLUE GRIM GUIDED", "Lumbridge Guide", new WorldPoint(3238, 3220, 0), "Lumbridge"), + new AnagramClue("BY LOOK", "Bolkoy", new WorldPoint(2529, 3162, 0), "Tree Gnome Village general store", "How many flowers are there in the clearing below this platform?", "13"), + new AnagramClue("CALAMARI MADE MUD", "Madame Caldarium", new WorldPoint(2553, 2868, 0), "Corsair Cove", "What is 3(5-3)?", "6"), + new AnagramClue("CAR IF ICES", "Sacrifice", new WorldPoint(2209, 3056, 0), "Zul-Andra"), + new AnagramClue("CAREER IN MOON", "Oneiromancer", new WorldPoint(2150, 3866, 0), "Astral altar", "How many Suqah inhabit Lunar isle?", "25"), + new AnagramClue("CLASH ION", "Nicholas", new WorldPoint(1841, 3803, 0), "North of Port Piscarilius fishing shop", "How many windows are in Tynan's shop?", "4"), + new AnagramClue("C ON GAME HOC", "Gnome Coach", new WorldPoint(2395, 3486, 0), "Gnome Ball course", "How many gnomes on the Gnome ball field have red patches on their uniforms?", "6"), + new AnagramClue("COOL NERD", "Old crone", new WorldPoint(3462, 3557, 0), "East of the Slayer Tower", "What is the combined combat level of each species that live in Slayer tower?", "619"), + new AnagramClue("COPPER ORE CRYPTS", "Prospector Percy", new WorldPoint(3061, 3377, 0), "Motherlode Mine", "During a party, everyone shook hands with everyone else. There were 66 handshakes. How many people were at the party?", "12"), + new AnagramClue("DARN DRAKE", "Daer Krand", new WorldPoint(3728, 3302, 0), "Sisterhood Sanctuary (Slepe Dungeon, northeast of Nightmare Arena)"), + new AnagramClue("DED WAR", "Edward", new WorldPoint(3284, 3943, 0), "Inside Rogue's Castle"), + new AnagramClue("DEKAGRAM", "Dark mage", new WorldPoint(3039, 4835, 0), "Centre of the Abyss", "How many rifts are found here in the abyss?", "13"), + new AnagramClue("DO SAY MORE", "Doomsayer", new WorldPoint(3230, 3230, 0), "East of Lumbridge Castle", "What is 40 divided by 1/2 plus 15?", "95"), + new AnagramClue("DIM THARN", "Mandrith", new WorldPoint(3182, 3946, 0), "Wilderness Resource Area"), + new AnagramClue("DR HITMAN", "Mandrith", new WorldPoint(3182, 3946, 0), "Wilderness Resource Area", "How many scorpions live under the pit?", "28"), + new AnagramClue("DR WARDEN FUNK", "Drunken Dwarf", new WorldPoint(2913, 10221, 0), "East Side of Keldagrim"), + new AnagramClue("DRAGONS LAMENT", "Strange Old Man", new WorldPoint(3564, 3288, 0), "Barrows", "One pipe fills a barrel in 1 hour while another pipe can fill the same barrel in 2 hours. How many minutes will it take to fill the take if both pipes are used?", "40"), + new AnagramClue("DT RUN B", "Brundt the Chieftain", new WorldPoint(2658, 3670, 0), "Rellekka, main hall", "How many people are waiting for the next bard to perform?", "4"), + new AnagramClue("DUO PLUG", "Dugopul", new WorldPoint(2803, 2744, 0), "Graveyard on Ape Atoll"), + new AnagramClue("EEK ZERO OP", "Zoo keeper", new WorldPoint(2613, 3269, 0), "Ardougne Zoo", "How many animals are in the Ardougne Zoo?", "40"), + new AnagramClue("EL OW", "Lowe", new WorldPoint(3233, 3423, 0), "Varrock archery store"), + new AnagramClue("ERR CURE IT", "Recruiter", new WorldPoint(2541, 3305, 0), "West Ardougne centre square", "How many houses have a cross on the door?", "20"), + new AnagramClue("FORLUN", "Runolf", new WorldPoint(2512, 10256, 0), "Miscellania & Etceteria Dungeon"), + new AnagramClue("GOBLIN KERN", "King Bolren", new WorldPoint(2541, 3170, 0), "Tree Gnome Village"), + new AnagramClue("GOT A BOY", "Gabooty", new WorldPoint(2790, 3066, 0), "Centre of Tai Bwo Wannai", "How many buildings in the village?", "11"), + new AnagramClue("GULAG RUN", "Uglug Nar", new WorldPoint(2442, 3051, 0), "West of Jiggig"), + new AnagramClue("GOBLETS ODD TOES", "Otto Godblessed", new WorldPoint(2501, 3487, 0), "Otto's Grotto", "How many types of dragon are there beneath the whirlpool's cavern?", "2"), + new AnagramClue("HALT US", "Luthas", new WorldPoint(2938, 3152, 0), "Banana plantation, Karamja"), + new AnagramClue("HE DO POSE. IT IS CULTRRL, MK?", "Riki the sculptor's model", new WorldPoint(2904, 10206, 0), "East Keldagrim, south of kebab seller."), + new AnagramClue("HEORIC", "Eohric", new WorldPoint(2900, 3565, 0), "Top floor of Burthorpe Castle", "King Arthur and Merlin sit down at the Round Table with 8 knights. How many degrees does each get?", "36"), + new AnagramClue("HIS PHOR", "Horphis", new WorldPoint(1639, 3812, 0), "Arceuus Library, Zeah", "On a scale of 1-10, how helpful is Logosia?", "1"), + new AnagramClue("I AM SIR", "Marisi", new WorldPoint(1737, 3557, 0), "Allotment patch, South of Hosidius chapel", "How many cities form the Kingdom of Great Kourend?", "5"), + new AnagramClue("ICY FE", "Fycie", new WorldPoint(2630, 2997, 0), "East Feldip Hills"), + new AnagramClue("I DOOM ICON INN", "Dominic Onion", new WorldPoint(2609, 3116, 0), "Nightmare Zone", "How many reward points does a herb box cost?", "9,500"), + new AnagramClue("I EAT ITS CHART HINTS DO U", "Shiratti the Custodian", new WorldPoint(3427, 2927, 0), "North of fountain, Nardah"), + new AnagramClue("I EVEN", "Nieve", new WorldPoint(2432, 3422, 0), "The slayer master in Gnome Stronghold", "How many farming patches are there in Gnome stronghold?", "2"), + new AnagramClue("I FAFFY RUN", "Fairy Nuff", new WorldPoint(3201, 3169, 0), "North of the bank in Zanaris"), + new AnagramClue("IM N ZEZIM", "Immenizz", new WorldPoint(2592, 4324, 0), "The Imp inside Puro-Puro"), + new AnagramClue("KAY SIR", "Sir Kay", new WorldPoint(2760, 3496, 0), "The courtyard in Camelot Castle", "How many fountains are there within the grounds of Camelot castle.", "6"), + new AnagramClue("LEAKEY", "Kaylee", new WorldPoint(2957, 3370, 0), "Rising Sun Inn in Falador", "How many chairs are there in the Rising Sun?", "18"), + new AnagramClue("LAND DOOMD", "Odd Old Man", new WorldPoint(3359, 3506, 0), "Limestone mine northeast of Varrock"), + new AnagramClue("LARK IN DOG", "King Roald", new WorldPoint(3220, 3476, 0), "Ground floor of Varrock castle", "How many bookcases are there in the Varrock palace library?", "24"), + new AnagramClue("LOW LAG", "Gallow", new WorldPoint(1805, 3566, 0), "Vinery in the Great Kourend", "How many vine patches can you find in this vinery?", "12"), + new AnagramClue("LADDER MEMO GUV", "Guard Vemmeldo", new WorldPoint(2447, 3418, 1), "Gnome Stronghold Bank", "How many magic trees can you find inside the Gnome Stronghold?", "3"), + new AnagramClue("MAL IN TAU", "Luminata", new WorldPoint(3508, 3237, 0), "Near Burgh de Rott entrance"), + new AnagramClue("ME AM THE CALC", "Cam the Camel", new WorldPoint(3300, 3231, 0), "Outside Duel Arena"), + new AnagramClue("MACHETE CLAM", "Cam the Camel", new WorldPoint(3300, 3231, 0), "Outside Duel Arena", "How many items can carry water in Gielinor?", "6"), + new AnagramClue("ME IF", "Femi", new WorldPoint(2461, 3382, 0), "Gates of Tree Gnome Stronghold"), + new AnagramClue("MOLD LA RAN", "Old Man Ral", new WorldPoint(3602, 3209, 0), "Meiyerditch"), + new AnagramClue("MOTHERBOARD", "Brother Omad", new WorldPoint(2606, 3211, 0), "Monastery south of Ardougne", "What is the next number? 12, 13, 15, 17, 111, 113, 117, 119, 123....?", "129"), + new AnagramClue("MUS KIL READER", "Radimus Erkle", new WorldPoint(2726, 3368, 0), "Legends' Guild"), + new AnagramClue("MY MANGLE LAL", "Lammy Langle", new WorldPoint(1688, 3540, 0), "Hosidius spirit tree patch"), + new AnagramClue("NO OWNER", "Oronwen", new WorldPoint(1162, 3178, 0), "Lletya Seamstress shop in Lletya", "What is the minimum amount of quest points required to reach Lletya?", "20"), + new AnagramClue("NOD MED", "Edmond", new WorldPoint(2566, 3332, 0), "Behind the most NW house in East Ardougne", "How many pigeon cages are there around the back of Jerico's house?", "3"), + new AnagramClue("O BIRDZ A ZANY EN PC", "Cap'n Izzy No-Beard", new WorldPoint(2807, 3191, 0), "Brimhaven Agility Arena", "How many Banana Trees are there in the plantation?", "33"), + new AnagramClue("OK CO", "Cook", new WorldPoint(3207, 3214, 0), "Ground floor of Lumbridge Castle", "How many cannons does Lumbridge Castle have?", "9"), + new AnagramClue("OR ZINC FUMES WARD", "Wizard Frumscone", new WorldPoint(2594, 3086, 0), "Downstairs in the Wizards' Guild"), + new AnagramClue("OUR OWN NEEDS", "Nurse Wooned", new WorldPoint(1575, 3590, 0), "Shayzien Infirmary", "How many injured soldiers are in this tent?", "19"), + new AnagramClue("PACINNG A TAIE", "Captain Ginea", new WorldPoint(1561, 3602, 0), "Building east of Shayzien combat ring", "1 soldier can deal with 6 lizardmen. How many soldiers do we need for an army of 678 lizardmen?", "113"), + new AnagramClue("PEAK REFLEX", "Flax keeper", new WorldPoint(2744, 3444, 0), "Flax field south of Seers Village", "If I have 1014 flax, and I spin a third of them into bowstring, how many flax do I have left?", "676"), + new AnagramClue("PEATY PERT", "Party Pete", new WorldPoint(3047, 3376, 0), "Falador Party Room"), + new AnagramClue("PROFS LOSE WRONG PIE", "Professor Onglewip", new WorldPoint(3113, 3162, 0), "Ground floor of Wizards Tower"), + new AnagramClue("QUIT HORRIBLE TYRANT", "Brother Tranquility", new WorldPoint(3681, 2963, 0), "Mos Le'Harmless or Harmony Island", "If I have 49 bottles of rum to share between 7 pirates, how many would each pirate get?", "7"), + new AnagramClue("QUE SIR", "Squire", new WorldPoint(2975, 3343, 0), "Falador Castle Courtyard", "White knights are superior to black knights. 2 white knights can handle 3 black knights. How many knights do we need for an army of 981 black knights?", "654"), + new AnagramClue("R AK MI", "Karim", new WorldPoint(3273, 3181, 0), "Al Kharid Kebab shop", "I have 16 kebabs, I eat one myself and share the rest equally between 3 friends. How many do they have each?", "5"), + new AnagramClue("RAT MAT WITHIN", "Martin Thwait", new WorldPoint(2906, 3537, 0), "Rogues' Den", "How many natural fires burn in Rogue's Den?", "2"), + new AnagramClue("RED ART TANS", "Trader Stan", new WorldPoint(3041, 3193, 0), "Port Sarim Charter ship"), + new AnagramClue("RATAI", "Taria", new WorldPoint(2940, 3223, 0), "Rimmington bush patch", "How many buildings are there in Rimmington?", "7"), + new AnagramClue("R SLICER", "Clerris", new WorldPoint(1761, 3850, 0), "Arceuus mine, Zeah", "If I have 1,000 blood runes, and cast 131 ice barrage spells, how many blood runes do I have left?", "738"), + new AnagramClue("RIP MAUL", "Primula", new WorldPoint(2454, 2853, 1), "Myth's Guild, first floor"), + new AnagramClue("SAND NUT", "Dunstan", new WorldPoint(2919, 3574, 0), "Anvil in north east Burthorpe", "How much smithing experience does one receive for smelting a blurite bar?", "8"), + new AnagramClue("SEQUIN DIRGE", "Queen Sigrid", new WorldPoint(2612, 3867, 0), "Throne room of Etceteria Castle."), + new AnagramClue("SLAM DUSTER GRAIL", "Guildmaster Lars", new WorldPoint(1649, 3498, 0), "Woodcutting guild, Zeah"), + new AnagramClue("SLIDE WOMAN", "Wise Old Man", new WorldPoint(3088, 3253, 0), "Draynor Village", "How many bookcases are in the Wise Old Man's house?", "28"), + new AnagramClue("SNAH", "Hans", new WorldPoint(3218, 3219, 0), "Lumbridge Castle courtyard"), + new AnagramClue("SNAKES SO I SAIL", "Lisse Isaakson", new WorldPoint(2351, 3801, 0), "Neitiznot", "How many arctic logs are required to make a large fremennik round shield?", "2"), + new AnagramClue("TAMED ROCKS", "Dockmaster", new WorldPoint(1822, 3739, 0), "Port Piscarilius, NE of General store", "What is the cube root of 125?", "5"), + new AnagramClue("TEN WIGS ON", "Wingstone", new WorldPoint(3389, 2877, 0), "Between Nardah & Agility Pyramid"), + new AnagramClue("THEM CAL CAME", "Cam the Camel", new WorldPoint(3300, 3231, 0), "Just outside of the Duel Arena"), + new AnagramClue("THICKNO", "Hickton", new WorldPoint(2822, 3442, 0), "Catherby fletching shop", "How many ranges are there in Catherby?", "2"), + new AnagramClue("TWENTY CURE IRON", "New recruit Tony", new WorldPoint(1498, 3544, 0), "Shayzien Graveyard"), + new AnagramClue("UNLEASH NIGHT MIST", "Sigli the Huntsman", new WorldPoint(2660, 3654, 0), "Rellekka", "What is the combined slayer requirement of every monster in the slayer cave?", "302"), + new AnagramClue("VESTE", "Steve", new WorldPoint(2432, 3423, 0), "Upstairs Wyvern Area or Stronghold Slayer Cave", "How many farming patches are there in Gnome stronghold?", "2"), + new AnagramClue("VEIL VEDA", "Evil Dave", new WorldPoint(3079, 9892, 0), "Doris' basement, Edgeville", "What is 333 multiplied by 2?", "666"), + new AnagramClue("WOO AN EGG KIWI", "Awowogei", ObjectID.AWOWOGEI, new WorldPoint(2802, 2765, 0), "Ape Atoll", "If I have 303 bananas, and share them between 31 friends evenly, only handing out full bananas. How many will I have left over?", "24"), + new AnagramClue("YAWNS GY", "Ysgawyn", new WorldPoint(2340, 3167, 0), "Lletya"), + new AnagramClue("MAJORS LAVA BADS AIR", "Ambassador Alvijar", new WorldPoint(2736, 5351, 1), "Dorgesh-Kaan, NE Middle Level", "Double the miles before the initial Dorgeshuun veteran.", "2505"), + new AnagramClue("AN EARL", "Ranael", new WorldPoint(3315, 3163, 0), "Al Kharid skirt shop"), + new AnagramClue("CARPET AHOY", "Apothecary", new WorldPoint(3195, 3404, 0), "Southwest Varrock"), + new AnagramClue("DISORDER", "Sedridor", new WorldPoint(3102, 9570, 0), "Wizards' Tower basement"), + new AnagramClue("I CORD", "Doric", new WorldPoint(2951, 3450, 0), "North of Falador"), + new AnagramClue("IN BAR", "Brian", new WorldPoint(3026, 3246, 0), "Port Sarim battleaxe shop"), + new AnagramClue("RAIN COVE", "Veronica", new WorldPoint(3110, 3330, 0), "Outside Draynor Manor"), + new AnagramClue("RUG DETER", "Gertrude", new WorldPoint(3151, 3412, 0), "West of Varrock, south of the Cooks' Guild"), + new AnagramClue("SIR SHARE RED", "Hairdresser", new WorldPoint(2944, 3381, 0), "Western Falador"), + new AnagramClue("TAUNT ROOF", "Fortunato", new WorldPoint(3080, 3250, 0), "Draynor Village Market"), + new AnagramClue("HICK JET", "Jethick", new WorldPoint(2541, 3305, 0), "West Ardougne", "How many graves are there in the city graveyard?", "38"), + new AnagramClue("RUE GO", "Goreu", new WorldPoint(2335, 3162, 0), "Lletya") + ); + + private final String text; + private final String npc; + private final WorldPoint location; + private final String area; + @Nullable + private final String question; + @Nullable + private final String answer; + private int objectId; + + private AnagramClue(String text, String npc, WorldPoint location, String area) + { + this(text, npc, location, area, null, null); + } + + private AnagramClue(String text, String npc, WorldPoint location, String area, @Nullable String question, @Nullable String answer) + { + this.text = text; + this.npc = npc; + this.location = location; + this.area = area; + this.question = question; + this.answer = answer; + this.objectId = -1; + } + + private AnagramClue(String text, String npc, int objectId, WorldPoint location, String area, String question, String answer) + { + this(text, npc, location, area, question, answer); + this.objectId = objectId; + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Anagram Clue").build()); + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getNpc()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getArea()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (getAnswer() != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("Answer:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getAnswer()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + if (!getLocation().isInScene(plugin.getClient())) + { + return; + } + + // Mark NPC + if (objectId == -1 && plugin.getNpcsToMark() != null) + { + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + // Mark game object + if (objectId != -1) + { + net.runelite.api.Point mousePosition = plugin.getClient().getMouseCanvasPosition(); + + for (TileObject gameObject : plugin.getObjectsToMark()) + { + OverlayUtil.renderHoverableArea(graphics, gameObject.getClickbox(), mousePosition, + CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR); + + OverlayUtil.renderImageLocation(plugin.getClient(), graphics, gameObject.getLocalLocation(), plugin.getClueScrollImage(), IMAGE_Z_OFFSET); + } + } + } + + public static AnagramClue forText(String text) + { + for (AnagramClue clue : CLUES) + { + if (text.equalsIgnoreCase(ANAGRAM_TEXT + clue.text) + || text.equalsIgnoreCase(ANAGRAM_TEXT_BEGINNER + clue.text) + || text.equalsIgnoreCase(clue.question)) + { + return clue; + } + } + + return null; + } + + @Override + public String[] getNpcs() + { + return new String[] {npc}; + } + + @Override + public int[] getObjectIds() + { + return new int[] {objectId}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/BeginnerMapClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/BeginnerMapClue.java new file mode 100644 index 0000000000..3942079105 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/BeginnerMapClue.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019, Hydrox6 + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.widgets.WidgetID; + +@Getter +public class BeginnerMapClue extends MapClue implements LocationClueScroll +{ + private static final ImmutableList CLUES = ImmutableList.of( + new BeginnerMapClue(WidgetID.BEGINNER_CLUE_MAP_CHAMPIONS_GUILD, new WorldPoint(3166, 3361, 0), MapClue.CHAMPIONS_GUILD), + new BeginnerMapClue(WidgetID.BEGINNER_CLUE_MAP_VARROCK_EAST_MINE, new WorldPoint(3290, 3374, 0), MapClue.VARROCK_EAST_MINE), + new BeginnerMapClue(WidgetID.BEGINNER_CLUE_MAP_DRAYNOR, new WorldPoint(3093, 3226, 0), MapClue.SOUTH_OF_DRAYNOR_BANK), + new BeginnerMapClue(WidgetID.BEGINNER_CLUE_MAP_NORTH_OF_FALADOR, new WorldPoint(3043, 3398, 0), MapClue.STANDING_STONES), + new BeginnerMapClue(WidgetID.BEGINNER_CLUE_MAP_WIZARDS_TOWER, new WorldPoint(3110, 3152, 0), MapClue.WIZARDS_TOWER_DIS) + ); + + private final int widgetGroupID; + + private BeginnerMapClue(int widgetGroupID, WorldPoint location, String description) + { + super(-1, location, description); + this.widgetGroupID = widgetGroupID; + setRequiresSpade(true); + } + + // Beginner Map Clues all use the same ItemID, but the WidgetID used to display them is unique + public static BeginnerMapClue forWidgetID(int widgetGroupID) + { + for (BeginnerMapClue clue : CLUES) + { + if (clue.widgetGroupID == widgetGroupID) + { + return clue; + } + } + + return null; + } +} + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java new file mode 100644 index 0000000000..ded2628348 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Set; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class CipherClue extends ClueScroll implements TextClueScroll, NpcClueScroll, LocationClueScroll +{ + private static final Set CLUES = ImmutableSet.of( + new CipherClue("BMJ UIF LFCBC TFMMFS", "Ali the Kebab seller", new WorldPoint(3354, 2974, 0), "Pollnivneach", "How many coins would you need to purchase 133 kebabs from me?", "399"), + new CipherClue("GUHCHO", "Drezel", new WorldPoint(3440, 9895, 0), "Paterdomus", "Please solve this for x: 7x - 28=21", "7"), + new CipherClue("HQNM LZM STSNQ", "Iron Man tutor", new WorldPoint(3227, 3227, 0), "Outside Lumbridge castle", "How many snakeskins are needed in order to craft 44 boots, 29 vambraces and 34 bandanas?", "666"), + new CipherClue("ZHLUG ROG PDQ", "Weird Old Man", new WorldPoint(3224, 3112, 0), "Kalphite Lair entrance. Fairy ring BIQ", "SIX LEGS! All of them have 6! There are 25 of them! How many legs?", "150"), + new CipherClue("ECRVCKP MJCNGF", "Captain Khaled", new WorldPoint(1845, 3754, 0), "Large eastern building in Port Piscarilius", "How many fishing cranes can you find around here?", "5"), + new CipherClue("OVEXON", "Eluned", new WorldPoint(2289, 3144, 0), "Outside Lletya or in Prifddinas after Song of the Elves", "A question on elven crystal math. I have 5 and 3 crystals, large and small respectively. A large crystal is worth 10,000 coins and a small is worth but 1,000. How much are all my crystals worth?", "53,000"), + new CipherClue("VTYR APCNTGLW", "King Percival", new WorldPoint(2634, 4682, 1), "Fisher Realm, first floor. Fairy ring BJR", "How many cannons are on this here castle?", "5"), + new CipherClue("UZZU MUJHRKYYKJ", "Otto Godblessed", new WorldPoint(2501, 3487, 0), "Otto's Grotto", "How many pyre sites are found around this lake?", "3"), + new CipherClue("USBJCPSO", "Traiborn", new WorldPoint(3112, 3162, 0), "First floor of Wizards Tower", "How many air runes would I need to cast 630 wind waves?", "3150"), + new CipherClue("HCKTA IQFHCVJGT", "Fairy Godfather", new WorldPoint(2446, 4428, 0), "Zanaris throne room", "There are 3 inputs and 4 letters on each ring How many total individual fairy ring codes are possible?", "64"), + new CipherClue("ZSBKDO ZODO", "Pirate Pete", new WorldPoint(3680, 3537, 0), "Dock northeast of the Ectofunctus"), + new CipherClue("GBJSZ RVFFO", "Fairy Queen", new WorldPoint(2347, 4435, 0), "Fairy Resistance Hideout"), + new CipherClue("QSPGFTTPS HSBDLMFCPOF", "Professor Gracklebone", new WorldPoint(1625, 3802, 0), "Ground floor of Arceuus Library", "How many round tables can be found on this floor of the library?", "9"), + new CipherClue("IWPPLQTP", "Gunnjorn", new WorldPoint(2541, 3548, 0), "Barbarian Outpost Agility course"), + new CipherClue("BSOPME MZETQPS", "Arnold Lydspor", new WorldPoint(2329, 3689, 0), "Piscatoris Fishing Colony general store/bank") + ); + + private String text; + private String npc; + private WorldPoint location; + private String area; + @Nullable + private String question; + @Nullable + private String answer; + + private CipherClue(String text, String npc, WorldPoint location, String area) + { + this(text, npc, location, area, null, null); + } + + private CipherClue(String text, String npc, WorldPoint location, String area, @Nullable String question, @Nullable String answer) + { + this.text = "The cipher reveals who to speak to next: " + text; + this.npc = npc; + this.location = location; + this.area = area; + this.question = question; + this.answer = answer; + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Cipher Clue").build()); + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getNpc()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getArea()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (getAnswer() != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("Answer:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getAnswer()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + if (!getLocation().isInScene(plugin.getClient())) + { + return; + } + + if (plugin.getNpcsToMark() != null) + { + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + } + + public static CipherClue forText(String text) + { + for (CipherClue clue : CLUES) + { + if (text.equalsIgnoreCase(clue.text) || text.equalsIgnoreCase(clue.question)) + { + return clue; + } + } + + return null; + } + + @Override + public String[] getNpcs() + { + return new String[] {npc}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ClueScroll.java new file mode 100644 index 0000000000..2f3cdd79e5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ClueScroll.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import java.awt.Graphics2D; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Varbits; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.ui.overlay.components.PanelComponent; + +public abstract class ClueScroll +{ + @Setter(AccessLevel.PROTECTED) + @Getter(AccessLevel.PUBLIC) + private boolean requiresSpade; + + @Setter(AccessLevel.PROTECTED) + @Getter(AccessLevel.PUBLIC) + private boolean requiresLight; + + @Setter(AccessLevel.PROTECTED) + @Getter(AccessLevel.PUBLIC) + private Varbits hasFirePit; + + @Setter(AccessLevel.PROTECTED) + @Getter(AccessLevel.PUBLIC) + private Enemy enemy; + + public abstract void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin); + + public abstract void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java new file mode 100644 index 0000000000..8d9a462118 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableMap; +import java.awt.Color; +import java.awt.Graphics2D; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.NonNull; +import net.runelite.api.Varbits; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; +import static net.runelite.client.plugins.cluescrolls.clues.Enemy.*; + +@Getter +public class CoordinateClue extends ClueScroll implements TextClueScroll, LocationClueScroll +{ + @Getter + private static class CoordinateClueInfo + { + private final String directions; + private final boolean lightRequired; + private final Varbits lightSource; + private final Enemy enemy; + + private CoordinateClueInfo(@NonNull String directions) + { + this(directions, null); + } + + private CoordinateClueInfo(@NonNull String directions, Enemy enemy) + { + this.directions = directions; + this.enemy = enemy; + this.lightRequired = false; + this.lightSource = null; + } + + private CoordinateClueInfo(@Nonnull String directions, Enemy enemy, boolean lightRequired, Varbits lightSource) + { + this.directions = directions; + this.enemy = enemy; + this.lightRequired = lightRequired; + this.lightSource = lightSource; + } + } + + private static final ImmutableMap CLUES = new ImmutableMap.Builder() + // Medium + .put(new WorldPoint(2479, 3158, 0), new CoordinateClueInfo("South of fruit tree patch, west of Tree Gnome Village.")) + .put(new WorldPoint(2887, 3154, 0), new CoordinateClueInfo("West of Banana plantation on Karamja.")) + .put(new WorldPoint(2743, 3151, 0), new CoordinateClueInfo("Entrance of Brimhaven dungeon.")) + .put(new WorldPoint(3184, 3150, 0), new CoordinateClueInfo("South of Lumbridge Swamp.")) + .put(new WorldPoint(3217, 3177, 0), new CoordinateClueInfo("East of Lumbridge Swamp.")) + .put(new WorldPoint(3007, 3144, 0), new CoordinateClueInfo("Near the entrance to the Asgarnian Ice Dungeon, south of Port Sarim (AIQ).")) + .put(new WorldPoint(2896, 3119, 0), new CoordinateClueInfo("Near Karambwan fishing spot (DKP).")) + .put(new WorldPoint(2697, 3207, 0), new CoordinateClueInfo("Centre of Moss Giant Island, west of Brimhaven.")) + .put(new WorldPoint(2679, 3110, 0), new CoordinateClueInfo("North of Hazelmere's house (CLS).")) + .put(new WorldPoint(3510, 3074, 0), new CoordinateClueInfo("East of Uzer (DLQ).")) + .put(new WorldPoint(3160, 3251, 0), new CoordinateClueInfo("West of trapdoor leading to H.A.M Hideout.")) + .put(new WorldPoint(2643, 3252, 0), new CoordinateClueInfo("South of Ardougne Zoo, North of Tower of Life (DJP).")) + .put(new WorldPoint(2322, 3061, 0), new CoordinateClueInfo("South-west of Castle wars (BKP).")) + .put(new WorldPoint(2875, 3046, 0), new CoordinateClueInfo("North of nature altar, north of Shilo Village (CKR).")) + .put(new WorldPoint(2849, 3033, 0), new CoordinateClueInfo("West of nature altar, north of Shilo Village (CKR).")) + .put(new WorldPoint(2848, 3296, 0), new CoordinateClueInfo("North of Crandor island.")) + .put(new WorldPoint(2583, 2990, 0), new CoordinateClueInfo("Feldip Hills, south-east of Gu'Thanoth (AKS).")) + .put(new WorldPoint(3179, 3344, 0), new CoordinateClueInfo("In the cow pen north of the Lumbridge windmill.")) + .put(new WorldPoint(2383, 3370, 0), new CoordinateClueInfo("West of the outpost")) + .put(new WorldPoint(3312, 3375, 0), new CoordinateClueInfo("North-west of Exam Centre, on the hill.")) + .put(new WorldPoint(3121, 3384, 0), new CoordinateClueInfo("North-east of Draynor Manor, near River Lum.")) + .put(new WorldPoint(3430, 3388, 0), new CoordinateClueInfo("West of Mort Myre Swamp (BKR).")) + .put(new WorldPoint(2920, 3403, 0), new CoordinateClueInfo("South-east of Taverley, near Lady of the Lake.")) + .put(new WorldPoint(2594, 2899, 0), new CoordinateClueInfo("South-east of Feldip Hills, by the crimson swifts (AKS).")) + .put(new WorldPoint(2387, 3435, 0), new CoordinateClueInfo("West of Tree Gnome Stronghold, near the pen containing terrorbirds.")) + .put(new WorldPoint(2512, 3467, 0), new CoordinateClueInfo("Baxtorian Falls (Bring rope).")) + .put(new WorldPoint(2381, 3468, 0), new CoordinateClueInfo("West of Tree Gnome Stronghold, north of the pen with terrorbirds.")) + .put(new WorldPoint(3005, 3475, 0), new CoordinateClueInfo("Ice Mountain, west of Edgeville Monastery.")) + .put(new WorldPoint(2585, 3505, 0), new CoordinateClueInfo("By the shore line north of the Coal Trucks.")) + .put(new WorldPoint(3443, 3515, 0), new CoordinateClueInfo("South of Slayer Tower (CKS).")) + .put(new WorldPoint(2416, 3516, 0), new CoordinateClueInfo("Tree Gnome Stronghold, west of Grand Tree, near swamp.")) + .put(new WorldPoint(3429, 3523, 0), new CoordinateClueInfo("South of Slayer Tower (CKS).")) + .put(new WorldPoint(2363, 3531, 0), new CoordinateClueInfo("North-east of Eagles' Peak (AKQ).")) + .put(new WorldPoint(2919, 3535, 0), new CoordinateClueInfo("East of Burthorpe pub.")) + .put(new WorldPoint(3548, 3560, 0), new CoordinateClueInfo("Inside Fenkenstrain's Castle.")) + .put(new WorldPoint(1456, 3620, 0), new CoordinateClueInfo("Graveyard west of Shayzien (DJR).")) + .put(new WorldPoint(2735, 3638, 0), new CoordinateClueInfo("East of Rellekka, north-west of Golden Apple Tree (AJR).")) + .put(new WorldPoint(2681, 3653, 0), new CoordinateClueInfo("Rellekka, in the garden of the south-east house.")) + .put(new WorldPoint(2537, 3881, 0), new CoordinateClueInfo("Miscellania (CIP).")) + .put(new WorldPoint(2828, 3234, 0), new CoordinateClueInfo("Southern coast of Crandor.")) + .put(new WorldPoint(1247, 3726, 0), new CoordinateClueInfo("Just inside the Farming Guild")) + .put(new WorldPoint(3770, 3898, 0), new CoordinateClueInfo("On the small island north-east of Fossil Island's mushroom forest.")) + // Hard + .put(new WorldPoint(2209, 3161, 0), new CoordinateClueInfo("North-east of Tyras Camp (BJS if 76 Agility).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2181, 3206, 0), new CoordinateClueInfo("South of Iorwerth Camp.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3081, 3209, 0), new CoordinateClueInfo("Small Island (CLP).", SARADOMIN_WIZARD)) + .put(new WorldPoint(3399, 3246, 0), new CoordinateClueInfo("Behind the Duel Arena.")) + .put(new WorldPoint(2699, 3251, 0), new CoordinateClueInfo("Little island (AIR).", SARADOMIN_WIZARD)) + .put(new WorldPoint(3546, 3251, 0), new CoordinateClueInfo("North-east of Burgh de Rott.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3544, 3256, 0), new CoordinateClueInfo("North-east of Burgh de Rott.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2841, 3267, 0), new CoordinateClueInfo("Crandor island.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3168, 3041, 0), new CoordinateClueInfo("Bedabin Camp.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2542, 3031, 0), new CoordinateClueInfo("Gu'Tanoth, may require 20gp.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2581, 3030, 0), new CoordinateClueInfo("Gu'Tanoth island, enter cave north-west of Feldip Hills (AKS).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2961, 3024, 0), new CoordinateClueInfo("Ship yard (DKP).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2339, 3311, 0), new CoordinateClueInfo("East of Prifddinas on Arandar mountain pass.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3440, 3341, 0), new CoordinateClueInfo("Nature Spirit's grotto (BIP).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2763, 2974, 0), new CoordinateClueInfo("Cairn Isle, west of Shilo Village (CKR).", SARADOMIN_WIZARD)) + .put(new WorldPoint(3138, 2969, 0), new CoordinateClueInfo("West of Bandit Camp in Kharidian Desert.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2924, 2963, 0), new CoordinateClueInfo("On the southern part of eastern Karamja.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2838, 2914, 0), new CoordinateClueInfo("Kharazi Jungle, near water pool (CKR).", SARADOMIN_WIZARD)) + .put(new WorldPoint(3441, 3419, 0), new CoordinateClueInfo("Mort Myre Swamp (BKR).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2950, 2902, 0), new CoordinateClueInfo("South-east of Kharazi Jungle.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2775, 2891, 0), new CoordinateClueInfo("South-west of Kharazi Jungle.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3113, 3602, 0), new CoordinateClueInfo("Wilderness. North of Edgeville (level 11).", ZAMORAK_WIZARD)) + .put(new WorldPoint(2892, 3675, 0), new CoordinateClueInfo("On the summit of Trollheim.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3168, 3677, 0), new CoordinateClueInfo("Wilderness. Graveyard of Shadows.", ZAMORAK_WIZARD)) + .put(new WorldPoint(2853, 3690, 0), new CoordinateClueInfo("Entrance to the troll Stronghold.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3305, 3692, 0), new CoordinateClueInfo("Wilderness. West of eastern green dragon.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3055, 3696, 0), new CoordinateClueInfo("Wilderness. Bandit Camp.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3302, 3696, 0), new CoordinateClueInfo("Wilderness. West of eastern green dragon.", ZAMORAK_WIZARD)) + .put(new WorldPoint(1479, 3696, 0), new CoordinateClueInfo("Lizardman Canyon (DJR).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2712, 3732, 0), new CoordinateClueInfo("North-east of Rellekka (DKS).", SARADOMIN_WIZARD)) + .put(new WorldPoint(2970, 3749, 0), new CoordinateClueInfo("Wilderness. Forgotten Cemetery.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3094, 3764, 0), new CoordinateClueInfo("Wilderness. Mining site north of Bandit Camp.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3311, 3769, 0), new CoordinateClueInfo("Wilderness. North of Venenatis.", ZAMORAK_WIZARD)) + .put(new WorldPoint(1460, 3782, 0), new CoordinateClueInfo("Lovakengj, near burning man.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3244, 3792, 0), new CoordinateClueInfo("Wilderness. South-east of Lava Dragon Isle by some Chaos Dwarves.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3140, 3804, 0), new CoordinateClueInfo("Wilderness. North of Ruins.", ZAMORAK_WIZARD)) + .put(new WorldPoint(2946, 3819, 0), new CoordinateClueInfo("Wilderness. Chaos Temple (level 38).", ZAMORAK_WIZARD)) + .put(new WorldPoint(3771, 3825, 0), new CoordinateClueInfo("Fossil Island. East of Museum Camp.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3013, 3846, 0), new CoordinateClueInfo("Wilderness. West of Lava Maze, before KBD's lair.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3058, 3884, 0), new CoordinateClueInfo("Wilderness. Near runite ore north of Lava Maze.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3290, 3889, 0), new CoordinateClueInfo("Wilderness. Demonic Ruins.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3770, 3897, 0), new CoordinateClueInfo("Small Island north of Fossil Island.", SARADOMIN_WIZARD)) + .put(new WorldPoint(2505, 3899, 0), new CoordinateClueInfo("Small Island north-west of Miscellania (AJS).", SARADOMIN_WIZARD)) + .put(new WorldPoint(3285, 3942, 0), new CoordinateClueInfo("Wilderness. Rogues' Castle.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3159, 3959, 0), new CoordinateClueInfo("Wilderness. North of Deserted Keep, west of Resource Area.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3039, 3960, 0), new CoordinateClueInfo("Wilderness. Pirates' Hideout.", ZAMORAK_WIZARD)) + .put(new WorldPoint(2987, 3963, 0), new CoordinateClueInfo("Wilderness. West of Wilderness Agility Course.", ZAMORAK_WIZARD)) + .put(new WorldPoint(3189, 3963, 0), new CoordinateClueInfo("Wilderness. North of Resource Area, near magic axe hut.", ZAMORAK_WIZARD)) + .put(new WorldPoint(2341, 3697, 0), new CoordinateClueInfo("North-east of the Piscatoris Fishing Colony bank.", SARADOMIN_WIZARD)) + .put(new WorldPoint(3143, 3774, 0), new CoordinateClueInfo("In level 32 Wilderness, by the black chinchompa hunting area.", ZAMORAK_WIZARD)) + .put(new WorldPoint(2992, 3941, 0), new CoordinateClueInfo("Wilderness Agility Course, past the log balance.", ZAMORAK_WIZARD)) + // Elite + .put(new WorldPoint(2357, 3151, 0), new CoordinateClueInfo("Lletya.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3587, 3180, 0), new CoordinateClueInfo("Meiyerditch.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2820, 3078, 0), new CoordinateClueInfo("Tai Bwo Wannai. Hardwood Grove. 100 Trading sticks or elite Karamja diary completion is needed to enter.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3811, 3060, 0), new CoordinateClueInfo("Small island north-east of Mos Le'Harmless.", ARMADYLIAN_OR_BANDOSIAN_GUARD, true, Varbits.FIRE_PIT_MOS_LE_HARMLESS)) + .put(new WorldPoint(2180, 3282, 0), new CoordinateClueInfo("North of Iorwerth Camp.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2870, 2997, 0), new CoordinateClueInfo("North-east corner in Shilo Village.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3302, 2988, 0), new CoordinateClueInfo("On top of a cliff to the west of Pollnivneach.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2511, 2980, 0), new CoordinateClueInfo("Just south of Gu'Tanoth, west of gnome glider.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2732, 3372, 0), new CoordinateClueInfo("Legends' Guild.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3573, 3425, 0), new CoordinateClueInfo("North of Dessous's tomb from Desert Treasure.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3828, 2848, 0), new CoordinateClueInfo("East of Harmony Island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3225, 2838, 0), new CoordinateClueInfo("South of Desert Treasure pyramid.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(1773, 3510, 0), new CoordinateClueInfo("Ruins north of the Hosidius mine.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3822, 3562, 0), new CoordinateClueInfo("North-east of Dragontooth Island. Bring a Ghostspeak Amulet and 25 Ecto-tokens to reach the island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3603, 3564, 0), new CoordinateClueInfo("North of the wrecked ship, outside of Port Phasmatys.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2936, 2721, 0), new CoordinateClueInfo("Eastern shore of Crash Island.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2697, 2705, 0), new CoordinateClueInfo("South-west of Ape Atoll.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2778, 3678, 0), new CoordinateClueInfo("Mountain Camp.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2827, 3740, 0), new CoordinateClueInfo("West of the entrance to the Ice Path, where the Troll child resides.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2359, 3799, 0), new CoordinateClueInfo("Neitiznot.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2194, 3807, 0), new CoordinateClueInfo("Pirates' Cove.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2700, 3808, 0), new CoordinateClueInfo("Northwestern part of the Trollweiss and Rellekka Hunter area (DKS).", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3215, 3835, 0), new CoordinateClueInfo("Wilderness. Lava Dragon Isle.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3369, 3894, 0), new CoordinateClueInfo("Wilderness. Fountain of Rune.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2065, 3923, 0), new CoordinateClueInfo("Outside the western wall on Lunar Isle.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3188, 3933, 0), new CoordinateClueInfo("Wilderness. Resource Area.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2997, 3953, 0), new CoordinateClueInfo("Wilderness. Inside Agility Training Area.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3380, 3963, 0), new CoordinateClueInfo("Wilderness. North of Volcano.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3051, 3736, 0), new CoordinateClueInfo("East of the Wilderness Obelisk in 28 Wilderness.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2316, 3814, 0), new CoordinateClueInfo("West of Neitiznot, near the bridge.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2872, 3937, 0), new CoordinateClueInfo("Weiss.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2484, 4016, 0), new CoordinateClueInfo("Northeast corner of the Island of Stone.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2222, 3331, 0), new CoordinateClueInfo("Prifddinas, west of the Tower of Voices", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(3560, 3987, 0), new CoordinateClueInfo("Lithkren. Digsite pendant teleport if unlocked, otherwise take rowboat from west of Mushroom Meadow Mushtree.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + // Master + .put(new WorldPoint(2178, 3209, 0), new CoordinateClueInfo("South of Iorwerth Camp.", BRASSICAN_MAGE)) + .put(new WorldPoint(2155, 3100, 0), new CoordinateClueInfo("South of Port Tyras (BJS if 76 Agility).", BRASSICAN_MAGE)) + .put(new WorldPoint(2217, 3092, 0), new CoordinateClueInfo("Poison Waste island (DLR).", BRASSICAN_MAGE)) + .put(new WorldPoint(3830, 3060, 0), new CoordinateClueInfo("Small island located north-east of Mos Le'Harmless.", BRASSICAN_MAGE, true, Varbits.FIRE_PIT_MOS_LE_HARMLESS)) + .put(new WorldPoint(2834, 3271, 0), new CoordinateClueInfo("Crandor island.", BRASSICAN_MAGE)) + .put(new WorldPoint(2732, 3284, 0), new CoordinateClueInfo("Witchaven.", BRASSICAN_MAGE)) + .put(new WorldPoint(3622, 3320, 0), new CoordinateClueInfo("Meiyerditch. Outside mine.", BRASSICAN_MAGE)) + .put(new WorldPoint(2303, 3328, 0), new CoordinateClueInfo("East of Prifddinas.", BRASSICAN_MAGE)) + .put(new WorldPoint(3570, 3405, 0), new CoordinateClueInfo("North of Dessous's tomb from Desert Treasure.", BRASSICAN_MAGE)) + .put(new WorldPoint(2840, 3423, 0), new CoordinateClueInfo("Water Obelisk Island.", BRASSICAN_MAGE)) + .put(new WorldPoint(3604, 3564, 0), new CoordinateClueInfo("North of the wrecked ship, outside of Port Phasmatys (ALQ).", BRASSICAN_MAGE)) + .put(new WorldPoint(3085, 3569, 0), new CoordinateClueInfo("Wilderness. Obelisk of Air.", BRASSICAN_MAGE)) + .put(new WorldPoint(2934, 2727, 0), new CoordinateClueInfo("Eastern shore of Crash Island.", BRASSICAN_MAGE)) + .put(new WorldPoint(1451, 3695, 0), new CoordinateClueInfo("West side of Lizardman Canyon with Lizardman shaman.", ANCIENT_WIZARDS)) + .put(new WorldPoint(2538, 3739, 0), new CoordinateClueInfo("Waterbirth Island. Bring a pet rock and rune thrownaxe.", BRASSICAN_MAGE)) + .put(new WorldPoint(1248, 3751, 0), new CoordinateClueInfo("In the north wing of the Farming Guild.", BRASSICAN_MAGE)) + .put(new WorldPoint(1698, 3792, 0), new CoordinateClueInfo("Arceuus church.", ANCIENT_WIZARDS)) + .put(new WorldPoint(2951, 3820, 0), new CoordinateClueInfo("Wilderness. Chaos Temple (level 38).", ANCIENT_WIZARDS)) + .put(new WorldPoint(2202, 3825, 0), new CoordinateClueInfo("Pirates' Cove, between Lunar Isle and Rellekka.", ANCIENT_WIZARDS)) + .put(new WorldPoint(1761, 3853, 0), new CoordinateClueInfo("Arceuus essence mine (CIS).", BRASSICAN_MAGE)) + .put(new WorldPoint(2090, 3863, 0), new CoordinateClueInfo("South of Lunar Isle, west of Astral altar.", ANCIENT_WIZARDS)) + .put(new WorldPoint(1442, 3878, 0), new CoordinateClueInfo("Sulphur Mine.", BRASSICAN_MAGE)) + .put(new WorldPoint(3380, 3929, 0), new CoordinateClueInfo("Wilderness. Near Volcano.", ANCIENT_WIZARDS)) + .put(new WorldPoint(3188, 3939, 0), new CoordinateClueInfo("Wilderness. Resource Area.", BRASSICAN_MAGE)) + .put(new WorldPoint(3304, 3941, 0), new CoordinateClueInfo("Wilderness. East of Rogues' Castle.", ANCIENT_WIZARDS)) + .put(new WorldPoint(2994, 3961, 0), new CoordinateClueInfo("Wilderness. Inside Agility Training Area.", BRASSICAN_MAGE)) + .build(); + + private final String text; + private final WorldPoint location; + /** + * For regions which are mirrored, the location of the the clue in the mirrored region. + */ + @Nullable + private final WorldPoint mirrorLocation; + + public CoordinateClue(String text, WorldPoint location, WorldPoint mirrorLocation) + { + this.text = text; + this.location = location; + this.mirrorLocation = mirrorLocation; + + final CoordinateClueInfo clueInfo = CLUES.get(location); + if (clueInfo != null) + { + setHasFirePit(clueInfo.getLightSource()); + setRequiresLight(clueInfo.lightRequired); + setEnemy(clueInfo.getEnemy()); + } + setRequiresSpade(true); + } + + @Override + public WorldPoint[] getLocations() + { + if (mirrorLocation != null) + { + return new WorldPoint[]{location, mirrorLocation}; + } + else + { + return new WorldPoint[]{location}; + } + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Coordinate Clue").build()); + + final CoordinateClueInfo solution = CLUES.get(location); + + if (solution != null) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(solution.getDirections()) + .build()); + panelComponent.getChildren().add(LineComponent.builder().build()); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Click the clue scroll on your world map to see dig location.") + .build()); + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + for (WorldPoint worldPoint : getLocations()) + { + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), worldPoint); + + if (localLocation != null) + { + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java new file mode 100644 index 0000000000..9360b6aa33 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Set; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.NPC; +import static net.runelite.api.NullObjectID.NULL_1293; +import net.runelite.api.ObjectComposition; +import static net.runelite.api.ObjectID.*; +import net.runelite.api.TileObject; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_FILL_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_HOVER_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueScroll, ObjectClueScroll +{ + public static final Set CLUES = ImmutableSet.of( + new CrypticClue("Show this to Sherlock.", "Sherlock", new WorldPoint(2733, 3415, 0), "Sherlock is located to the east of the Sorcerer's tower in Seers' Village."), + new CrypticClue("Talk to the bartender of the Rusty Anchor in Port Sarim.", "Bartender", new WorldPoint(3045, 3256, 0), "The Rusty Anchor is located in the north of Port Sarim."), + new CrypticClue("The keeper of Melzars... Spare? Skeleton? Anar?", "Oziach", new WorldPoint(3068, 3516, 0), "Speak to Oziach in Edgeville."), + new CrypticClue("Speak to Ulizius.", "Ulizius", new WorldPoint(3444, 3461, 0), "Ulizius is the monk who guards the gate into Mort Myre Swamp. South of fairy ring CKS."), + new CrypticClue("Search for a crate in a building in Hemenster.", CRATE_357, new WorldPoint(2636, 3453, 0), "House northwest of the Ranging Guild. West of Grandpa Jack."), + new CrypticClue("A reck you say; let's pray there aren't any ghosts.", "Father Aereck", new WorldPoint(3242, 3207, 0), "Speak to Father Aereck in Lumbridge."), + new CrypticClue("Search the bucket in the Port Sarim jail.", BUCKET_9568, new WorldPoint(3013, 3179, 0), "Talk to Shantay & identify yourself as an outlaw, refuse to pay the 5gp fine twice and you will be sent to the Port Sarim jail."), + new CrypticClue("Search the crates in a bank in Varrock.", CRATE_5107, new WorldPoint(3187, 9825, 0), "Search in the basement of the West Varrock bank."), + new CrypticClue("Falo the bard wants to see you.", "Falo the Bard", new WorldPoint(2689, 3550, 0), "Speak to Falo the Bard located between Seers' Village and Rellekka. Southwest of fairy ring CJR."), + new CrypticClue("Search a bookcase in the Wizards tower.", BOOKCASE_12539, new WorldPoint(3113, 3159, 0), "The bookcase located on the ground floor of the Wizards' Tower."), + new CrypticClue("Come have a cip with this great soot covered denizen.", "Miner Magnus", new WorldPoint(2527, 3891, 0), "Talk to Miner Magnus on Miscellania, east of the fairy ring CIP. Answer: 8", "How many coal rocks are around here?"), + new CrypticClue("Citric cellar.", "Heckel Funch", new WorldPoint(2490, 3488, 0), "Speak to Heckel Funch on the first floor in the Grand Tree."), + new CrypticClue("I burn between heroes and legends.", "Candle maker", new WorldPoint(2799, 3438, 0), "Speak to the Candle maker in Catherby."), + new CrypticClue("Speak to Sarah at Falador farm.", "Sarah", new WorldPoint(3038, 3292, 0), "Talk to Sarah at Falador farm, north of Port Sarim."), + new CrypticClue("Search for a crate on the ground floor of a house in Seers' Village.", CRATE_25775, new WorldPoint(2699, 3470, 0), "Search inside Phantuwti Fanstuwi Farsight's house, located south of the pub in Seers' Village."), + new CrypticClue("Snah? I feel all confused, like one of those cakes...", "Hans", new WorldPoint(3211, 3219, 0), "Talk to Hans roaming around Lumbridge Castle."), + new CrypticClue("Speak to Sir Kay in Camelot Castle.", "Sir Kay", new WorldPoint(2759, 3497, 0), "Sir Kay can be found in the courtyard at Camelot castle."), + new CrypticClue("Gold I see, yet gold I require. Give me 875 if death you desire.", "Saniboch", new WorldPoint(2745, 3151, 0), "Speak to Saniboch at the Brimhaven Dungeon entrance."), + new CrypticClue("Find a crate close to the monks that like to paaarty!", CRATE_354, new WorldPoint(2614, 3204, 0), "The crate is in the east side of the Ardougne monastery, near Brother Omad."), + new CrypticClue("Identify the back of this over-acting brother. (He's a long way from home.)", "Hamid", new WorldPoint(3376, 3284, 0), "Talk to Hamid, the monk at the altar in the Duel Arena."), + new CrypticClue("In a town where thieves steal from stalls, search for some drawers in the upstairs of a house near the bank.", "Guard", DRAWERS, new WorldPoint(2611, 3324, 1), "Kill any Guard located around East Ardougne for a medium key. Then search the drawers in the upstairs hallway of Jerico's house, which is the house with pigeon cages located south of the northern East Ardougne bank."), + new CrypticClue("His bark is worse than his bite.", "Barker", new WorldPoint(3499, 3503, 0), "Speak to the Barker at Canifis's Barkers' Haberdashery."), + new CrypticClue("The beasts to my east snap claws and tails, The rest to my west can slide and eat fish. The force to my north will jump and they'll wail, Come dig by my fire and make a wish.", new WorldPoint(2598, 3267, 0), "Dig by the torch in the Ardougne Zoo, between the penguins and the scorpions."), + new CrypticClue("A town with a different sort of night-life is your destination. Search for some crates in one of the houses.", CRATE_24344, new WorldPoint(3498, 3507, 0), "Search the crate inside of the clothes shop in Canifis."), + new CrypticClue("Stop crying! Talk to the head.", "Head mourner", new WorldPoint(2042, 4630, 0), "Talk to the Head mourner in the mourner headquarters in West Ardougne."), + new CrypticClue("Search the crate near a cart in Port Khazard.", CRATE_366, new WorldPoint(2660, 3149, 0), "Search by the southern Khazard General Store in Port Khazard."), + new CrypticClue("Speak to the bartender of the Blue Moon Inn in Varrock.", "Bartender", new WorldPoint(3226, 3399, 0), "Talk to the bartender in Blue Moon Inn in Varrock."), + new CrypticClue("This aviator is at the peak of his profession.", "Captain Bleemadge", new WorldPoint(2846, 1749, 0), "Captain Bleemadge, the gnome glider pilot, is found at the top of White Wolf Mountain."), + new CrypticClue("Search the crates in the shed just north of East Ardougne.", CRATE_355, new WorldPoint(2617, 3347, 0), "The crates in the shed north of the northern Ardougne bank."), + new CrypticClue("I wouldn't wear this jean on my legs.", "Father Jean", new WorldPoint(1734, 3576, 0), "Talk to father Jean in the Hosidius church."), + new CrypticClue("Search the crate in the Toad and Chicken pub.", CRATE_354, new WorldPoint(2913, 3536, 0), "The Toad and Chicken pub is located in Burthorpe."), + new CrypticClue("Search chests found in the upstairs of shops in Port Sarim.", CLOSED_CHEST_375, new WorldPoint(3016, 3205, 1), "Search the chest on the east wall found upstairs of Wydin's Food Store in Port Sarim."), + new CrypticClue("Right on the blessed border, cursed by the evil ones. On the spot inaccessible by both; I will be waiting. The bugs' imminent possession holds the answer.", new WorldPoint(3410, 3324, 0), "B I P. Dig right under the fairy ring."), + new CrypticClue("The dead, red dragon watches over this chest. He must really dig the view.", "Barbarian", 375, new WorldPoint(3353, 3332, 0), "Search the chest underneath the Red Dragon's head in the Exam Centre. Kill a MALE Barbarian in Barbarian Village or Barbarian Outpost to receive the key."), + new CrypticClue("My home is grey, and made of stone; A castle with a search for a meal. Hidden in some drawers I am, across from a wooden wheel.", DRAWERS_5618, new WorldPoint(3213, 3216, 1), "Open the drawers inside the room with the spinning wheel on the first floor of Lumbridge Castle."), + new CrypticClue("Come to the evil ledge, Yew know yew want to. Try not to get stung.", new WorldPoint(3089, 3468, 0), "Dig in Edgeville, just east of the Southern Yew tree."), + new CrypticClue("Look in the ground floor crates of houses in Falador.", CRATES_24088, new WorldPoint(3029, 3355, 0), "The house east of the eastern bank in Falador."), + new CrypticClue("You were 3 and I was the 6th. Come speak to me.", "Vannaka", new WorldPoint(3146, 9913, 0), "Speak to Vannaka in Edgeville Dungeon."), + new CrypticClue("Search the crates in Draynor Manor.", CRATE_11485, new WorldPoint(3106, 3369, 2), "Top floor of the Draynor Manor."), + new CrypticClue("Search the crates near a cart in Varrock.", CRATE_5107, new WorldPoint(3226, 3452, 0), "South east of Varrock Palace, south of the tree farming patch."), + new CrypticClue("A Guthixian ring lies between two peaks. Search the stones and you'll find what you seek.", STONES_26633, new WorldPoint(2922, 3484, 0), "Search the stones several steps west of the Guthixian stone circle in Taverley."), + new CrypticClue("Search the boxes in the house near the south entrance to Varrock.", BOXES_5111, new WorldPoint(3203, 3384, 0), "The first house on the left when entering Varrock from the southern entrance."), + new CrypticClue("His head might be hollow, but the crates nearby are filled with surprises.", CRATE_354, new WorldPoint(3478, 3091, 0), "Search the crates near the Clay golem in the ruins of Uzer."), + new CrypticClue("One of the sailors in Port Sarim is your next destination.", "Captain Tobias", new WorldPoint(3026, 3216, 0), "Speak to Captain Tobias on the docks of Port Sarim."), + new CrypticClue("THEY'RE EVERYWHERE!!!! But they were here first. Dig for treasure where the ground is rich with ore.", new WorldPoint(3081, 3421, 0), "Dig at Barbarian Village, next to the Stronghold of Security."), + new CrypticClue("Talk to the mother of a basement dwelling son.", "Doris", new WorldPoint(3079, 3493, 0), "Evil Dave's mother, Doris, is located in the house west of Edgeville bank."), + new CrypticClue("Speak to Ned in Draynor Village.", "Ned", new WorldPoint(3098, 3258, 0), "Ned is found north of the Draynor bank."), + new CrypticClue("Speak to Hans to solve the clue.", "Hans", new WorldPoint(3211, 3219, 0), "Hans can be found at Lumbridge Castle."), + new CrypticClue("Search the crates in Canifis.", CRATE_24344, new WorldPoint(3509, 3497, 0), "Search inside Rufus' Meat Emporium in Canifis."), + new CrypticClue("Search the crates in the Dwarven mine.", CRATE_357, new WorldPoint(3035, 9849, 0), "Search the crate in the room east of the Ice Mountain ladder entrance in the Drogo's Mining Emporium in the Dwarven Mine."), + new CrypticClue("A crate found in the tower of a church is your next location.", CRATE_357, new WorldPoint(2612, 3304, 1), "Climb the ladder and search the crates on the first floor in the Church in Ardougne."), + new CrypticClue("Covered in shadows, the centre of the circle is where you will find the answer.", new WorldPoint(3488, 3289, 0), "Dig in the centre of Mort'ton, where the roads intersect."), + new CrypticClue("I lie lonely and forgotten in mid wilderness, where the dead rise from their beds. Feel free to quarrel and wind me up, and dig while you shoot their heads.", new WorldPoint(3174, 3663, 0), "Directly under the crossbow respawn in the Graveyard of Shadows in level 18 Wilderness."), + new CrypticClue("In the city where merchants are said to have lived, talk to a man with a splendid cape, but a hat dropped by goblins.", "Head chef", new WorldPoint(3143, 3445, 0), "Talk to the Head chef in Cooks' Guild west of Varrock. You will need a chef hat or cooking cape to enter."), + new CrypticClue("The mother of the reptilian sacrifice.", "Zul-Cheray", new WorldPoint(2204, 3050, 0), "Talk to Zul-Cheray in a house near the sacrificial boat at Zul-Andra."), + new CrypticClue("I watch the sea. I watch you fish. I watch your tree.", "Ellena", new WorldPoint(2860, 3431, 0), "Speak to Ellena at Catherby fruit tree patch."), + new CrypticClue("Dig between some ominous stones in Falador.", new WorldPoint(3040, 3399, 0), "Three standing stones inside a walled area. East of the northern Falador gate."), + new CrypticClue("Speak to Rusty north of Falador.", "Rusty", new WorldPoint(2979, 3435, 0), "Rusty can be found northeast of Falador on the way to the Mind altar."), + new CrypticClue("Search a wardrobe in Draynor.", WARDROBE_5622, new WorldPoint(3087, 3261, 0), "Go to Aggie's house in Draynor Village and search the wardrobe in northern wall."), + new CrypticClue("I have many arms but legs, I have just one. I have little family but my seed you can grow on, I am not dead, yet I am but a spirit, and my power, on your quests, you will earn the right to free it.", NULL_1293, new WorldPoint(2544, 3170, 0), "Spirit Tree in Tree Gnome Village. Answer: 13112221", "What is the next number in the sequence? 1, 11, 21, 1211, 111221, 312211"), + new CrypticClue("I am the one who watches the giants. The giants in turn watch me. I watch with two while they watch with one. Come seek where I may be.", "Kamfreena", new WorldPoint(2845, 3539, 0), "Speak to Kamfreena on the top floor of the Warriors' Guild."), + new CrypticClue("In a town where wizards are known to gather, search upstairs in a large house to the north.", "Man", 375, new WorldPoint(2593, 3108, 1), "Search the chest upstairs in the house north of Yanille Wizard's Guild. Kill a man for the key."), + new CrypticClue("Probably filled with wizards socks.", "Wizard", DRAWERS_350, new WorldPoint(3116, 9562, 0), "Search the drawers in the basement of the Wizard's Tower south of Draynor Village. Kill one of the Wizards for the key. Fairy ring DIS"), + new CrypticClue("Even the seers say this clue goes right over their heads.", CRATE_14934, new WorldPoint(2707, 3488, 2), "Search the crate on the Seers Agility Course in Seers Village"), + new CrypticClue("Speak to a Wyse man.", "Wyson the gardener", new WorldPoint(3026, 3378, 0), "Talk to Wyson the gardener at Falador Park."), + new CrypticClue("You'll need to look for a town with a central fountain. Look for a locked chest in the town's chapel.", "Monk" , CLOSED_CHEST_5108, new WorldPoint(3256, 3487, 0), "Search the chest by the stairs in the Varrock church. Kill a Monk in Ardougne Monastery to obtain the key."), + new CrypticClue("Talk to Ambassador Spanfipple in the White Knights Castle.", "Ambassador Spanfipple", new WorldPoint(2979, 3340, 0), "Ambassador Spanfipple can be found roaming on the first floor of the White Knights Castle."), + new CrypticClue("Mine was the strangest birth under the sun. I left the crimson sack, yet life had not begun. Entered the world, and yet was seen by none.", new WorldPoint(2832, 9586, 0), "Inside Karamja Volcano, dig directly underneath the Red spiders' eggs respawn."), + new CrypticClue("Search for a crate in Varrock Castle.", CRATE_5113, new WorldPoint(3224, 3492, 0), "Search the crate in the corner of the kitchen in Varrock Castle."), + new CrypticClue("And so on, and so on, and so on. Walking from the land of many unimportant things leads to a choice of paths.", new WorldPoint(2591, 3879, 0), "Dig on Etceteria next to the Evergreen tree in front of the castle walls."), + new CrypticClue("Speak to Donovan, the Family Handyman.", "Donovan the Family Handyman", new WorldPoint(2743, 3578, 0), "Donovan the Family Handyman is found on the first floor of Sinclair Mansion, north of Seers' Village."), + new CrypticClue("Search the crates in the Barbarian Village helmet shop.", CRATES_11600, new WorldPoint(3073, 3430, 0), "Peksa's Helmet Shop in Barbarian Village."), + new CrypticClue("Search the boxes of Falador's general store.", CRATES_24088, new WorldPoint(2955, 3390, 0), "Falador general store."), + new CrypticClue("In a village made of bamboo, look for some crates under one of the houses.", CRATE_356, new WorldPoint(2800, 3074, 0), "Search the crate by the house at the northern point of the broken jungle fence in Tai Bwo Wannai."), + new CrypticClue("This crate is mine, all mine, even if it is in the middle of the desert.", CRATE_18889, new WorldPoint(3289, 3022, 0), "Center of desert Mining Camp. Search the crates. Requires the metal key from Tourist Trap to enter."), + new CrypticClue("Dig where 4 siblings and I all live with our evil overlord.", new WorldPoint(3195, 3357, 0), "Dig in the chicken pen inside the Champions' Guild"), + new CrypticClue("In a town where the guards are armed with maces, search the upstairs rooms of the Public House.", "Guard dog", 348, new WorldPoint(2574, 3326, 1), "Search the drawers upstairs in the pub north of Ardougne Castle. Kill a Guard dog at Handelmort Mansion to obtain the key."), + new CrypticClue("Four blades I have, yet draw no blood; Still I turn my prey to powder. If you are brave, come search my roof; It is there my blades are louder.", CRATE_12963, new WorldPoint(3166, 3309, 2), "Lumbridge windmill, search the crates on the top floor."), + new CrypticClue("Search through some drawers in the upstairs of a house in Rimmington.", DRAWERS_352, new WorldPoint(2970, 3214, 1), "On the first floor of the house north of Hetty the Witch's house in Rimmington."), + new CrypticClue("Probably filled with books on magic.", BOOKCASE_380, new WorldPoint(3096, 9572, 0), "Search the bookcase in the basement of Wizards' Tower. Fairy ring DIS."), + new CrypticClue("If you look closely enough, it seems that the archers have lost more than their needles.", HAYSTACK, new WorldPoint(2672, 3416, 0), "Search the haystack by the south corner of the Ranging Guild."), + new CrypticClue("Search the crate in the left-hand tower of Lumbridge Castle.", CRATE_357, new WorldPoint(3228, 3212, 1), "Located on the first floor of the southern tower at the Lumbridge Castle entrance."), + new CrypticClue("'Small shoe.' Often found with rod on mushroom.", "Gnome trainer", new WorldPoint(2476, 3428, 0), "Talk to any Gnome trainer in the agility area of the Tree Gnome Stronghold."), + new CrypticClue("I live in a deserted crack collecting soles.", "Genie", new WorldPoint(3371, 9320, 0), "Enter the crack west of Nardah Rug merchant, and talk to the Genie. You'll need a light source and a rope.", true), + new CrypticClue("46 is my number. My body is the colour of burnt orange and crawls among those with eight. Three mouths I have, yet I cannot eat. My blinking blue eye hides my grave.", new WorldPoint(3170, 3885, 0), "Sapphire respawn in the Spider's Nest, lvl 46 Wilderness. Dig under the sapphire spawn."), + new CrypticClue("Green is the colour of my death as the winter-guise, I swoop towards the ground.", new WorldPoint(2780, 3783, 0), "Slide down to where Trollweiss grows on Trollweiss Mountain. Bring a sled."), + new CrypticClue("Talk to a party-goer in Falador.", "Lucy", new WorldPoint(3046, 3382, 0), "Lucy is the bartender on the first floor of the Falador party room."), + new CrypticClue("He knows just how easy it is to lose track of time.", "Brother Kojo", new WorldPoint(2570, 3250, 0), "Speak to Brother Kojo in the Clock Tower. Answer: 22", "On a clock, how many times a day do the minute hand and the hour hand overlap?"), + new CrypticClue("A great view - watch the rapidly drying hides get splashed. Check the box you are sitting on.", BOXES, new WorldPoint(2523, 3493, 1), "Almera's House north of Baxtorian Falls, search boxes on the first floor."), + new CrypticClue("Search the Coffin in Edgeville.", COFFIN, new WorldPoint(3091, 3477, 0), "Search the coffin located by the Edgeville Wilderness teleport lever."), + new CrypticClue("When no weapons are at hand, then is the time to reflect. In Saradomin's name, redemption draws closer...", DRAWERS_350, new WorldPoint(2818, 3351, 0), "On Entrana, search the southern drawer in the house with the cooking range."), + new CrypticClue("Search the crates in a house in Yanille that has a piano.", CRATE_357, new WorldPoint(2598, 3105, 0), "The house is located northwest of the bank in Yanille."), + new CrypticClue("Speak to the staff of Sinclair mansion.", "Louisa", new WorldPoint(2736, 3578, 0), "Speak to Louisa, on the ground floor, found at the Sinclair Mansion. Fairy ring CJR"), + new CrypticClue("I am a token of the greatest love. I have no beginning or end. My eye is red, I can fit like a glove. Go to the place where it's money they lend, And dig by the gate to be my friend.", new WorldPoint(3191, 9825, 0), "Dig by the gate in the basement of the West Varrock bank."), + new CrypticClue("Speak to Kangai Mau.", "Kangai Mau", new WorldPoint(2791, 3183, 0), "Kangai Mau is found in the Shrimp and Parrot in Brimhaven."), + new CrypticClue("Speak to Hajedy.", "Hajedy", new WorldPoint(2779, 3211, 0), "Hajedy is found by the cart, located just south of the Brimhaven docks."), + new CrypticClue("Must be full of railings.", BOXES_6176, new WorldPoint(2576, 3464, 0), "Search the boxes around the hut where the broken Dwarf Cannon is, close to the start of the Dwarf Cannon quest."), + new CrypticClue("I wonder how many bronze swords he has handed out.", "Vannaka", new WorldPoint(3164, 9913, 0), "Talk to Vannaka. He can be found in Edgeville Dungeon."), + new CrypticClue("Read 'How to breed scorpions.' By O.W.Thathurt.", BOOKCASE_380, new WorldPoint(2703, 3409, 1), "Search the northern bookcase on the first floor of the Sorcerer's Tower."), + new CrypticClue("Search the crates in the Port Sarim Fishing shop.", CRATE_9534, new WorldPoint(3012, 3222, 0), "Search the crates, by the door, in Gerrant's Fishy Business in Port Sarim."), + new CrypticClue("Speak to The Lady of the Lake.", "The Lady of the Lake", new WorldPoint(2924, 3405, 0), "Talk to The Lady of the Lake in Taverley."), + new CrypticClue("Rotting next to a ditch. Dig next to the fish.", new WorldPoint(3547, 3183, 0), "Dig next to a fishing spot on the south-east side of Burgh de Rott."), + new CrypticClue("The King's magic won't be wasted by me.", "Guardian Mummy", new WorldPoint(1934, 4427, 0), "Talk to the Guardian mummy inside the Pyramid Plunder minigame in Sophanem."), + new CrypticClue("Dig where the forces of Zamorak and Saradomin collide.", new WorldPoint(3049, 4839, 0), "Dig next to the law rift in the Abyss."), + new CrypticClue("Search the boxes in the goblin house near Lumbridge.", BOXES, new WorldPoint(3245, 3245, 0), "Goblin house on the eastern side of the river outside of Lumbridge."), + new CrypticClue("W marks the spot.", new WorldPoint(2867, 3546, 0), "Dig in the middle of the Warriors' Guild entrance hall."), + new CrypticClue("There is no 'worthier' lord.", "Lord Iorwerth", new WorldPoint(2205, 3252, 0), "Speak to Lord Iorwerth in the elven camp southwest of Prifddinas."), + new CrypticClue("Surviving.", "Sir Vyvin", new WorldPoint(2983, 3338, 0), "Talk to Sir Vyvin on the second floor of Falador castle."), + new CrypticClue("My name is like a tree, yet it is spelt with a 'g'. Come see the fur which is right near me.", "Wilough", new WorldPoint(3221, 3435, 0), "Speak to Wilough, next to the Fur Merchant in Varrock Square."), + new CrypticClue("Speak to Jatix in Taverley.", "Jatix", new WorldPoint(2898, 3428, 0), "Jatix is found in the middle of Taverley."), + new CrypticClue("Speak to Gaius in Taverley.", "Gaius", new WorldPoint(2884, 3450, 0), "Gaius is found at the northwest corner in Taverley."), + new CrypticClue("If a man carried my burden, he would break his back. I am not rich, but leave silver in my track. Speak to the keeper of my trail.", "Gerrant", new WorldPoint(3014, 3222, 0), "Speak to Gerrant in the fish shop in Port Sarim."), + new CrypticClue("Search the drawers in Falador's chain mail shop.", DRAWERS, new WorldPoint(2969, 3311, 0), "Wayne's Chains - Chainmail Specialist store at the southern Falador walls."), + new CrypticClue("Talk to the barber in the Falador barber shop.", "Hairdresser", new WorldPoint(2945, 3379, 0), "The Hairdresser can be found in the barber shop, north of the west Falador bank."), + new CrypticClue("Often sought out by scholars of histories past, find me where words of wisdom speak volumes.", "Examiner", new WorldPoint(3362, 3341, 0), "Speak to an examiner at the Exam Centre."), + new CrypticClue("Generally speaking, his nose was very bent.", "General Bentnoze", new WorldPoint(2957, 3511, 0), "Talk to General Bentnoze in the Goblin Village north of Falador."), + new CrypticClue("Search the bush at the digsite centre.", BUSH_2357, new WorldPoint(3345, 3378, 0), "The bush is on the east side of the first pathway towards the digsite from the Exam Centre."), + new CrypticClue("Someone watching the fights in the Duel Arena is your next destination.", "Jeed", new WorldPoint(3360, 3242, 0), "Talk to Jeed, found on the upper floors, at the Duel Arena."), + new CrypticClue("It seems to have reached the end of the line, and it's still empty.", MINE_CART_6045, new WorldPoint(3041, 9820, 0), "Search the carts in the northern part of the Dwarven Mine."), + new CrypticClue("You'll have to plug your nose if you use this source of herbs.", null, "Kill an Aberrant or Deviant spectre."), + new CrypticClue("When you get tired of fighting, go deep, deep down until you need an antidote.", CRATE_357, new WorldPoint(2576, 9583, 0), "Go to Yanille Agility dungeon and fall into the place with the poison spiders by praying at the Chaos altar. Search the crate by the stairs leading up."), + new CrypticClue("Search the bookcase in the monastery.", BOOKCASE_380, new WorldPoint(3054, 3484, 0), "Search the southeastern bookcase at Edgeville Monastery."), + new CrypticClue("Surprising? I bet he is...", "Sir Prysin", new WorldPoint(3205, 3474, 0), "Talk to Sir Prysin in Varrock Palace."), + new CrypticClue("Search upstairs in the houses of Seers' Village for some drawers.", DRAWERS_25766, new WorldPoint(2716, 3471, 1), "Located in the house with the spinning wheel. South of the Seers' Village bank."), + new CrypticClue("Leader of the Yak City.", "Mawnis Burowgar", new WorldPoint(2336, 3799, 0), "Talk to Mawnis Burowgar in Neitiznot."), + new CrypticClue("Speak to Arhein in Catherby.", "Arhein", new WorldPoint(2803, 3430, 0), "Arhein is just south of the Catherby bank."), + new CrypticClue("Speak to Doric, who lives north of Falador.", "Doric", new WorldPoint(2951, 3451, 0), "Doric is found north of Falador and east of the Taverley gate."), + new CrypticClue("Where the best are commemorated, and a celebratory cup, not just for beer.", new WorldPoint(3388, 3152, 0), "Dig at the Clan Cup Trophy south-west of Citharede Abbey."), + new CrypticClue("'See you in your dreams' said the vegetable man.", "Dominic Onion", new WorldPoint(2608, 3116, 0), "Speak to Dominic Onion at the Nightmare Zone teleport spot."), + new CrypticClue("Try not to step on any aquatic nasties while searching this crate.", CRATE_18204, new WorldPoint(2764, 3273, 0), "Search the crate in Bailey's house on the Fishing Platform."), + new CrypticClue("The cheapest water for miles around, but they react badly to religious icons.", CRATE_354, new WorldPoint(3178, 2987, 0), "Search the crates in the General Store tent in the Desert Bandit Camp."), + new CrypticClue("This village has a problem with cartloads of the undead. Try checking the bookcase to find an answer.", BOOKCASE_394, new WorldPoint(2833, 2992, 0), "Search the bookcase by the doorway of the building just south east of the Shilo Village Gem Mine."), + new CrypticClue("Dobson is my last name, and with gardening I seek fame.", "Horacio", new WorldPoint(2635, 3310, 0), "Horacio, located in the garden of the Handelmort Mansion in East Ardougne."), + new CrypticClue("The magic of 4 colours, an early experience you could learn. The large beast caged up top, rages, as his demised kin's loot now returns.", "Wizard Mizgog", new WorldPoint(3103, 3163, 2), "Speak to Wizard Mizgog at the top of the Wizard's Tower south of Draynor."), + new CrypticClue("Aggie I see. Lonely and southern I feel. I am neither inside nor outside the house, yet no home would be complete without me. The treasure lies beneath me!", new WorldPoint(3085, 3255, 0), "Dig outside the window of Aggie's house in Draynor Village."), + new CrypticClue("Search the chest in Barbarian Village.", CLOSED_CHEST_375, new WorldPoint(3085, 3429, 0), "The chest located in the house with a spinning wheel in Barbarian Village."), + new CrypticClue("Search the crates in the outhouse of the long building in Taverley.", CRATE_357, new WorldPoint(2914, 3433, 0), "Located in the small building attached by a fence to the main building in Taverley. Climb over the stile."), + new CrypticClue("Talk to Ermin.", "Ermin", new WorldPoint(2488, 3409, 1), "Ermin can be found on the first floor of the tree house south-east of the Gnome Agility Course."), + new CrypticClue("Ghostly bones.", null, "Kill an Ankou."), + new CrypticClue("Search through chests found in the upstairs of houses in eastern Falador.", CLOSED_CHEST_375, new WorldPoint(3041, 3364, 1), "The house is located southwest of the Falador Party Room. There are two chests in the room, search the northern chest."), + new CrypticClue("Let's hope you don't meet a watery death when you encounter this fiend.", null, "Kill a waterfiend."), + new CrypticClue("Reflection is the weakness for these eyes of evil.", null, "Kill a basilisk."), + new CrypticClue("Search a bookcase in Lumbridge swamp.", BOOKCASE_9523, new WorldPoint(3146, 3177, 0), "Located in Father Urhney's house in Lumbridge Swamp."), + new CrypticClue("Surround my bones in fire, ontop the wooden pyre. Finally lay me to rest, before my one last test.", null, "Kill a confused/lost barbarian to receive mangled bones. Construct and burn a pyre ship. Kill the ferocious barbarian spirit that spawns to receive a clue casket."), + new CrypticClue("Fiendish cooks probably won't dig the dirty dishes.", new WorldPoint(3043, 4974, 1), "Dig by the fire in the Rogues' Den."), + new CrypticClue("My life was spared but these voices remain, now guarding these iron gates is my bane.", "Key Master", new WorldPoint(1310, 1251, 0), "Speak to the Key Master in Cerberus' Lair."), + new CrypticClue("Search the boxes in one of the tents in Al Kharid.", BOXES_361, new WorldPoint(3308, 3206, 0), "Search the boxes in the tent east of the Al Kharid Silk trader."), + new CrypticClue("One of several rhyming brothers, in business attire with an obsession for paper work.", "Piles", new WorldPoint(3186, 3936, 0), "Speak to Piles in the Wilderness Resource Area. An entry fee of 7,500 coins is required, or less if Wilderness Diaries have been completed."), + new CrypticClue("Search the drawers on the ground floor of a building facing Ardougne's Market.", DRAWERS_350, new WorldPoint(2653, 3320, 0), "Inside Noella's house north of the East Ardougne market."), + new CrypticClue("'A bag belt only?', he asked his balding brothers.", "Abbot Langley", new WorldPoint(3058, 3487, 0), "Talk-to Abbot Langley in Monastery west of Edgeville"), + new CrypticClue("Search the drawers upstairs in Falador's shield shop.", DRAWERS, new WorldPoint(2971, 3386, 1), "Cassie's Shield Shop at the northern Falador entrance."), + new CrypticClue("Go to this building to be illuminated, and check the drawers while you are there.", "Market Guard", DRAWERS_350 , new WorldPoint(2512, 3641, 1), "Search the drawers in the first floor of the Lighthouse. Kill a Rellekka marketplace guard to obtain the key."), + new CrypticClue("Dig near some giant mushrooms, behind the Grand Tree.", new WorldPoint(2458, 3504, 0), "Dig near the red mushrooms northwest of the Grand Tree."), + new CrypticClue("Pentagrams and demons, burnt bones and remains, I wonder what the blood contains.", new WorldPoint(3297, 3890, 0), "Dig under the blood rune spawn next to the Demonic Ruins."), + new CrypticClue("Search the drawers above Varrock's shops.", DRAWERS_7194, new WorldPoint(3206, 3419, 1), "Located upstairs in Thessalia's Fine Clothes shop in Varrock."), + new CrypticClue("Search the drawers in one of Gertrude's bedrooms.", DRAWERS_7194, new WorldPoint(3156, 3406, 0), "Kanel's bedroom (southeastern room), in Gertrude's house south of the Cooking Guild."), + new CrypticClue("Under a giant robotic bird that cannot fly.", new WorldPoint(1756, 4940, 0), "Dig next to the terrorbird display in the south exhibit of Varrock Museum's basement."), + new CrypticClue("Great demons, dragons and spiders protect this blue rock, beneath which, you may find what you seek.", new WorldPoint(3045, 10265, 0), "Dig by the runite rock in the Lava Maze Dungeon."), + new CrypticClue("My giant guardians below the market streets would be fans of rock and roll, if only they could grab hold of it. Dig near my green bubbles!", new WorldPoint(3161, 9904, 0), "Dig near the cauldron by Moss Giants under Varrock Sewers"), + new CrypticClue("Varrock is where I reside, not the land of the dead, but I am so old, I should be there instead. Let's hope your reward is as good as it says, just 1 gold one and you can have it read.", "Gypsy Aris", new WorldPoint(3203, 3424, 0), "Talk to Gypsy Aris, West of Varrock main square."), + new CrypticClue("Speak to a referee.", "Gnome ball referee", new WorldPoint(2386, 3487, 0), "Talk to a Gnome ball referee found on the Gnome ball field in the Gnome Stronghold. Answer: 5096", "What is 57 x 89 + 23?"), + new CrypticClue("This crate holds a better reward than a broken arrow.", CRATE_356, new WorldPoint(2671, 3437, 0), "Inside the Ranging Guild. Search the crate behind the northern most building."), + new CrypticClue("Search the drawers in the house next to the Port Sarim mage shop.", DRAWERS, new WorldPoint(3024, 3259, 0), "House east of Betty's Mage shop in Port Sarim. Contains a cooking sink."), + new CrypticClue("With a name like that, you'd expect a little more than just a few scimitars.", "Daga", new WorldPoint(2759, 2775, 0), "Speak to Daga on Ape Atoll."), + new CrypticClue("Strength potions with red spiders' eggs? He is quite a herbalist.", "Apothecary", new WorldPoint(3194, 3403, 0), "Talk to Apothecary in the South-western Varrock. (the) apothecary is just north-west of the Varrock Swordshop."), + new CrypticClue("Robin wishes to see your finest ranged equipment.", "Robin", new WorldPoint(3673, 3492, 0), "Robin at the inn in Port Phasmatys. Speak to him with +182 in ranged attack bonus. Bonus granted by the toxic blowpipe is ignored."), + new CrypticClue("You will need to under-cook to solve this one.", CRATE_357, new WorldPoint(3219, 9617, 0), "Search the crate in the Lumbridge basement."), + new CrypticClue("Search through some drawers found in Taverley's houses.", DRAWERS_350, new WorldPoint(2894, 3418, 0), "The south-eastern most house in Taverley, south of Jatix's Herblore Shop."), + new CrypticClue("Anger Abbot Langley.", "Abbot Langley", new WorldPoint(3058, 3487, 0), "Speak to Abbot Langley in the Edgeville Monastery while you have a negative prayer bonus (currently only possible with an Ancient staff)."), + new CrypticClue("Dig where only the skilled, the wealthy, or the brave can choose not to visit again.", new WorldPoint(3221, 3219, 0), "Dig at Lumbridge spawn"), + new CrypticClue("Scattered coins and gems fill the floor. The chest you seek is in the north east.", "King Black Dragon", CLOSED_CHEST_375, new WorldPoint(2288, 4702, 0), "Kill the King Black Dragon for a key (elite), and then open the closed chest in the NE corner of the lair."), + new CrypticClue("A ring of water surrounds 4 powerful rings, dig above the ladder located there.", new WorldPoint(1910, 4367, 0), "Dig by the ladder leading to the Dagannoth Kings room in the Waterbirth Island Dungeon. Bring a pet rock and rune thrownaxe."), + new CrypticClue("This place sure is a mess.", "Ewesey", new WorldPoint(1646, 3631, 0), "Ewesey is located in the mess hall in Hosidius."), + new CrypticClue("Here, there are tears, but nobody is crying. Speak to the guardian and show off your alignment to balance.", "Juna", JUNA, new WorldPoint(3252, 9517, 2), "Talk to Juna while wearing three Guthix related items."), + new CrypticClue("You might have to turn over a few stones to progress.", null, "Kill a rock crab."), + new CrypticClue("Dig under Razorlor's toad batta.", new WorldPoint(3139, 4554, 0), "Dig on the toad batta spawn in Tarn's Lair."), + new CrypticClue("Talk to Cassie in Falador.", "Cassie", new WorldPoint(2975, 3383, 0), "Cassie is found just south-east of the northern Falador gate."), + new CrypticClue("Faint sounds of 'Arr', fire giants found deep, the eastern tip of a lake, are the rewards you could reap.", new WorldPoint(3055, 10338, 0), "Dig south of the pillar in the Deep Wilderness Dungeon in the room with the fire giants."), + new CrypticClue("If you're feeling brave, dig beneath the dragon's eye.", new WorldPoint(2410, 4714, 0), "Dig below the mossy rock under the Viyeldi caves (Legend's Quest). Items needed: Pickaxe, unpowered orb, lockpick, spade, any charge orb spell, and either 79 agility or an axe and machete."), + new CrypticClue("Search the tents in the Imperial Guard camp in Burthorpe for some boxes.", BOXES_3686, new WorldPoint(2885, 3540, 0), "Search in the tents in the northwest corner of the soldiers' camp in Burthorpe."), + new CrypticClue("A dwarf, approaching death, but very much in the light.", "Thorgel", new WorldPoint(1863, 4639, 0), "Speak to Thorgel at the entrance to the Death altar."), + new CrypticClue("You must be 100 to play with me.", "Squire (Veteran)", new WorldPoint(2638, 2656, 0), "Speak to the Veteran boat squire at Pest Control."), + new CrypticClue("Three rule below and three sit at top. Come dig at my entrance.", new WorldPoint(2523, 3739, 0), "Dig in front of the entrance to the Waterbirth Island Dungeon."), + new CrypticClue("Search the drawers in the ground floor of a shop in Yanille.", DRAWERS_350, new WorldPoint(2570, 3085, 0), "Search the drawers in Yanille's hunting shop."), + new CrypticClue("Search the drawers of houses in Burthorpe.", DRAWERS, new WorldPoint(2929, 3570, 0), "Inside Hild's house in the northeast corner of Burthorpe."), + new CrypticClue("Where safe to speak, the man who offers the pouch of smallest size wishes to see your alignment.", "Mage of Zamorak", new WorldPoint(3260, 3385, 0), "Speak to the Mage of Zamorak south of the Rune Shop in Varrock while wearing three Zamorakian items."), + new CrypticClue("Search the crates in the guard house of the northern gate of East Ardougne.", CRATE_356, new WorldPoint(2645, 3338, 0), "The guard house northwest of the East Ardougne market."), + new CrypticClue("Go to the village being attacked by trolls, search the drawers in one of the houses.", "Penda", DRAWERS_350, new WorldPoint(2921, 3577, 0), "Go to Dunstan's house in the northeast corner of Burthorpe. Kill Penda in the Toad and Chicken to obtain the key."), + new CrypticClue("You'll get licked.", null, "Kill a Bloodveld."), + new CrypticClue("She's small but can build both literally and figuratively, as long as you have their favour.", "Lovada", new WorldPoint(1486, 3834, 0), "Speak to Lovada by the entrance to the blast mine in Lovakengj."), + new CrypticClue("Dig in front of the icy arena where 1 of 4 was fought.", new WorldPoint(2874, 3757, 0), "North of Trollheim, where you fought Kamil from Desert Treasure."), + new CrypticClue("Speak to Roavar.", "Roavar", new WorldPoint(3494, 3474, 0), "Talk to Roavar in the Canifis tavern."), + new CrypticClue("Search the drawers downstairs of houses in the eastern part of Falador.", DRAWERS_350, new WorldPoint(3039, 3342, 0), "House is located east of the eastern Falador bank and south of the fountain. The house is indicated by a cooking range icon on the minimap."), + new CrypticClue("Search the drawers found upstairs in East Ardougne's houses.", DRAWERS, new WorldPoint(2574, 3326, 1), "Upstairs of the pub north of the Ardougne Castle."), + new CrypticClue("The far north eastern corner where 1 of 4 was defeated, the shadows still linger.", new WorldPoint(2744, 5116, 0), "Dig on the northeastern-most corner of the Shadow Dungeon. Bring a ring of visibility."), + new CrypticClue("Search the drawers in a house in Draynor Village.", DRAWERS_350, new WorldPoint(3097, 3277, 0), "The drawer is located in the northernmost house in Draynor Village."), + new CrypticClue("Search the boxes in a shop in Taverley.", BOXES_360, new WorldPoint(2886, 3449, 0), "The box inside Gaius' Two Handed Shop in Taverley."), + new CrypticClue("I lie beneath the first descent to the holy encampment.", new WorldPoint(2914, 5300, 1), "Dig immediately after climbing down the first set of rocks towards Saradomin's encampment within the God Wars Dungeon."), + new CrypticClue("Search the upstairs drawers of a house in a village where pirates are known to have a good time.", "Pirate", 348, new WorldPoint(2809, 3165, 1), "The house in the southeast corner of Brimhaven, northeast of Davon's Amulet Store. Kill any Pirate located around Brimhaven to obtain the key."), + new CrypticClue("Search the chest in the Duke of Lumbridge's bedroom.", CLOSED_CHEST_375, new WorldPoint(3209, 3218, 1), "The Duke's room is on the first floor in Lumbridge Castle."), + new CrypticClue("Talk to the Doomsayer.", "Doomsayer", new WorldPoint(3232, 3228, 0), "Doomsayer can be found just north of Lumbridge Castle entrance."), + new CrypticClue("Search the chests upstairs in Al Kharid Palace.", CLOSED_CHEST_375, new WorldPoint(3301, 3169, 1), "The chest is located, in the northeast corner, on the first floor of the Al Kharid Palace."), + new CrypticClue("Search the boxes just outside the Armour shop in East Ardougne.", BOXES_361, new WorldPoint(2654, 3299, 0), "Outside Zenesha's Plate Mail Body Shop in East Ardougne."), + new CrypticClue("Surrounded by white walls and gems.", "Herquin", new WorldPoint(2945, 3335, 0), "Talk to Herquin, the gem store owner in Falador."), + new CrypticClue("Monk's residence in the far west. See robe storage device.", DRAWERS_350, new WorldPoint(1746, 3490, 0), "Search the drawers in the south tent of the monk's camp on the southern coast of Hosidius, directly south of the player-owned house portal."), + new CrypticClue("Search the drawers in Catherby's Archery shop.", DRAWERS_350, new WorldPoint(2825, 3442, 0), "Hickton's Archery Emporium in Catherby."), + new CrypticClue("The hand ain't listening.", "The Face", new WorldPoint(3019, 3232, 0), "Talk to The Face located by the manhole just north of the Port Sarim fishing shop."), + new CrypticClue("Search the chest in the left-hand tower of Camelot Castle.", CLOSED_CHEST_25592, new WorldPoint(2748, 3495, 2), "Located on the second floor of the western tower of Camelot."), + new CrypticClue("Anger those who adhere to Saradomin's edicts to prevent travel.", "Monk of Entrana", new WorldPoint(3042, 3236, 0), "Port Sarim Docks, try to charter a ship to Entrana with armour or weapons equipped."), + new CrypticClue("South of a river in a town surrounded by the undead, what lies beneath the furnace?", new WorldPoint(2857, 2966, 0), "Dig in front of the Shilo Village furnace."), + new CrypticClue("Talk to the Squire in the White Knights' castle in Falador.", "Squire", new WorldPoint(2977, 3343, 0), "The squire is located in the courtyard of the White Knights' Castle."), + new CrypticClue("Thanks, Grandma!", "Tynan", new WorldPoint(1836, 3786, 0), "Tynan can be found in the north-east corner of Port Piscarilius."), + new CrypticClue("In a town where everyone has perfect vision, seek some locked drawers in a house that sits opposite a workshop.", "Chicken", DRAWERS_25766, new WorldPoint(2709, 3478, 0), "The Seers' Village house south of the Elemental Workshop entrance. Kill any Chicken to obtain a key."), + new CrypticClue("The treasure is buried in a small building full of bones. Here is a hint: it's not near a graveyard.", new WorldPoint(3356, 3507, 0), "In the western building near the Limestone quarry east of Varrock. Dig south of the box of bones in the smaller building."), + new CrypticClue("Search the crates in East Ardougne's general store.", CRATE_357, new WorldPoint(2615, 3291, 0), "Located south of the Ardougne church."), + new CrypticClue("Come brave adventurer, your sense is on fire. If you talk to me, it's an old god you desire.", "Viggora", null, "Speak to Viggora while wearing a ring of visibility and a Ghostspeak amulet."), + new CrypticClue("2 musical birds. Dig in front of the spinning light.", new WorldPoint(2671, 10396, 0), "Dig in front of the spinning light in Ping and Pong's room inside the Iceberg"), + new CrypticClue("Search the wheelbarrow in Rimmington mine.", WHEELBARROW_9625, new WorldPoint(2978, 3239, 0), "The Rimmington mining site is located north of Rimmington."), + new CrypticClue("Belladonna, my dear. If only I had gloves, then I could hold you at last.", "Tool Leprechaun", new WorldPoint(3088, 3357, 0), "Talk to Tool Leprechaun at Draynor Manor."), + new CrypticClue("Impossible to make angry", "Abbot Langley", new WorldPoint(3059, 3486, 0), "Speak to Abbot Langley at the Edgeville Monastery."), + new CrypticClue("Search the crates in Horvik's armoury.", CRATE_5106, new WorldPoint(3228, 3433, 0), "Horvik's in Varrock."), + new CrypticClue("Ghommal wishes to be impressed by how strong your equipment is.", "Ghommal", new WorldPoint(2878, 3546, 0), "Speak to Ghommal at the Warriors' Guild with a total Melee Strength bonus of over 100."), + new CrypticClue("Shhhh!", "Logosia", new WorldPoint(1633, 3808, 0), "Speak to Logosia in the Arceuus Library's ground floor."), + new CrypticClue("Salty peter.", "Konoo", new WorldPoint(1703, 3524, 0), "Talk to Konoo who is digging saltpetre in Hosidius, north-east of the Woodcutting Guild."), + new CrypticClue("Talk to Zeke in Al Kharid.", "Zeke", new WorldPoint(3287, 3190, 0), "Zeke is the owner of the scimitar shop in Al Kharid."), + new CrypticClue("Guthix left his mark in a fiery lake, dig at the tip of it.", new WorldPoint(3069, 3935, 0), "Dig at the tip of the lava lake that is shaped like a Guthixian symbol, west of the Mage Arena."), + new CrypticClue("Search the drawers in the upstairs of a house in Catherby.", DRAWERS_350, new WorldPoint(2809, 3451, 1), "Perdu's house in Catherby."), + new CrypticClue("Search a crate in the Haymaker's arms.", CRATE_27532, new WorldPoint(1720, 3652, 1), "Search the crate in the north-east corner of The Haymaker's Arms tavern east of Kourend Castle."), + new CrypticClue("Desert insects is what I see. Taking care of them was my responsibility. Your solution is found by digging near me.", new WorldPoint(3307, 9505, 0), "Dig next to the Entomologist, Kalphite area, near Shantay Pass."), + new CrypticClue("Search the crates in the most north-western house in Al Kharid.", CRATE_358, new WorldPoint(3289, 3202, 0), "Search the crates in the house, marked with a cooking range icon, southeast of the gem stall in Al Kharid."), + new CrypticClue("You will have to fly high where a sword cannot help you.", null, "Kill an Aviansie."), + new CrypticClue("A massive battle rages beneath so be careful when you dig by the large broken crossbow.", new WorldPoint(2927, 3761, 0), "NE of the God Wars Dungeon entrance, climb the rocky handholds & dig by large crossbow."), + new CrypticClue("Mix yellow with blue and add heat, make sure you bring protection.", null, "Kill a green or brutal green dragon."), + new CrypticClue("Speak to Ellis in Al Kharid.", "Ellis", new WorldPoint(3276, 3191, 0), "Ellis is tanner just north of Al Kharid bank."), + new CrypticClue("Search the chests in the Dwarven Mine.", CLOSED_CHEST_375, new WorldPoint(3000, 9798, 0), "The chest is on the western wall, where Hura's Crossbow Shop is, in the Dwarven Mine."), + new CrypticClue("In a while...", null, "Kill a crocodile."), + new CrypticClue("A chisel and hammer reside in his home, strange for one of magic. Impress him with your magical equipment.", "Wizard Cromperty", new WorldPoint(2682, 3325, 0), "Wizard Cromperty, NE corner of East Ardougne. +100 magic attack bonus needed"), + new CrypticClue("You have all of the elements available to solve this clue. Fortunately you do not have to go as far as to stand in a draft.", CRATE_18506, new WorldPoint(2723, 9891, 0), "Search the crate, west of the Air Elementals, inside the Elemental Workshop."), + new CrypticClue("A demon's best friend holds the next step of this clue.", null, "Kill a hellhound."), + new CrypticClue("Dig in the centre of a great kingdom of 5 cities.", new WorldPoint(1639, 3673, 0), "Dig in front of the large statue in the centre of Great Kourend."), + new CrypticClue("Hopefully this set of armour will help you to keep surviving.", "Sir Vyvin", new WorldPoint(2982, 3336, 2), "Speak to Sir Vyvin, located in the White Knight's Castle, while wearing a white platebody and platelegs."), + new CrypticClue("The beasts retreat, for their Queen is gone; the song of this town still plays on. Dig near the birthplace of a blade, be careful not to melt your spade.", new WorldPoint(2342, 3677, 0), "Dig in front of the small furnace in the Piscatoris Fishing Colony."), + new CrypticClue("Darkness wanders around me, but fills my mind with knowledge.", "Biblia", new WorldPoint(1633, 3825, 2), "Speak to Biblia on the Arceuus Library's top floor."), + new CrypticClue("I would make a chemistry joke, but I'm afraid I wouldn't get a reaction.", "Chemist", new WorldPoint(2932, 3212, 0), "Talk to the Chemist in Rimmington"), + new CrypticClue("Show this to Hazelmere.", "Hazelmere", new WorldPoint(2677, 3088, 1), "Located upstairs in the house east of Yanille, north of fairy ring CLS."), + new CrypticClue("Does one really need a fire to stay warm here?", new WorldPoint(3816, 3810, 0), "Dig next to the fire near the Volcanic Mine entrance on Fossil Island."), + new CrypticClue("Search the open crate found in the Hosidius kitchens.", CRATE_27533, new WorldPoint(1683, 3616, 0), "The kitchens are north-west of the town in Hosidius."), + new CrypticClue("Dig under Ithoi's cabin.", new WorldPoint(2529, 2838, 0), "Dig under Ithoi's cabin in the Corsair Cove."), + new CrypticClue("Search the drawers, upstairs in the bank to the East of Varrock.", DRAWERS_7194, new WorldPoint(3250, 3420, 1), "Search the drawers upstairs in Varrock east bank."), + new CrypticClue("Speak to Hazelmere.", "Hazelmere", new WorldPoint(2677, 3088, 1), "Located upstairs in the house east of Yanille, north of fairy ring CLS. Answer: 6859", "What is 19 to the power of 3?"), + new CrypticClue("The effects of this fire are magnified.", new WorldPoint(1179, 3626, 0), "Dig by the fire beside Ket'sal K'uk in the westernmost part of the Kebos Swamp."), + new CrypticClue("Always walking around the castle grounds and somehow knows everyone's age.", "Hans", new WorldPoint(3221, 3218, 0), "Talk to Hans walking around Lumbridge Castle."), + new CrypticClue("In the place Duke Horacio calls home, talk to a man with a hat dropped by goblins.", "Cook", new WorldPoint(3208, 3213, 0), "Talk to the Cook in Lumbridge Castle."), + new CrypticClue("In a village of barbarians, I am the one who guards the village from up high.", "Hunding", new WorldPoint(3097, 3432, 2), "Talk to Hunding atop the tower on the east side of Barbarian Village."), + new CrypticClue("Talk to Charlie the Tramp in Varrock.", "Charlie the Tramp", new WorldPoint(3209, 3390, 0), "Talk to Charlie the Tramp by the southern entrance to Varrock. He will give you a task."), + new CrypticClue("Near the open desert I reside, to get past me you must abide. Go forward if you dare, for when you pass me, you'll be sweating by your hair.", "Shantay", new WorldPoint(3303, 3123, 0), "Talk to Shantay at the Shantay Pass south of Al Kharid."), + new CrypticClue("Search the chest in Fred the Farmer's bedroom.", CLOSED_CHEST_375, new WorldPoint(3185, 3274, 0), "Search the chest by Fred the Farmer's bed in his house north-west of Lumbridge."), + new CrypticClue("Search the eastern bookcase in Father Urhney's house.", BOOKCASE_9523, new WorldPoint(3149, 3177, 0), "Father Urhney's house is found in the western end of the Lumbridge Swamp."), + new CrypticClue("Talk to Morgan in his house at Draynor Village.", "Morgan", new WorldPoint(3098, 3268, 0), "Morgan can be found in the house with the quest start map icon in Northern Draynor Village."), + new CrypticClue("Talk to Charles at Port Piscarilius.", "Charles", new WorldPoint(1821, 3690, 0), "Charles is found by Veos' ship in Port Piscarilius."), + new CrypticClue("Search the crate in Rommiks crafting shop in Rimmington.", CRATE_9533, new WorldPoint(2946, 3207, 0), "The crates in Rommik's Crafty Supplies in Rimmington."), + new CrypticClue("Talk to Ali the Leaflet Dropper north of the Al Kharid mine.", "Ali the Leaflet Dropper", new WorldPoint(3283, 3329, 0), "Ali the Leaflet Dropper can be found roaming north of the Al Kharid mine."), + new CrypticClue("Talk to the cook in the Blue Moon Inn in Varrock.", "Cook", new WorldPoint(3230, 3401, 0), "The Blue Moon Inn can be found by the southern entrance to Varrock."), + new CrypticClue("Search the single crate in Horvik's smithy in Varrock.", CRATE_5106, new WorldPoint(3228, 3433, 0), "Horvik's Smithy is found north-east of of Varrock Square."), + new CrypticClue("Search the crates in Falador General store.", CRATES_24088, new WorldPoint(2955, 3390, 0), "The Falador General Store can be found by the northern entrance to the city."), + new CrypticClue("Talk to Wayne at Wayne's Chains in Falador.", "Wayne", new WorldPoint(2972, 3312, 0), "Wayne's shop is found directly south of the White Knights' Castle."), + new CrypticClue("Search the boxes next to a chest that needs a crystal key.", BOXES_360, new WorldPoint(2915, 3452, 0), "The Crystal chest can be found in the house directly south of the Witch's house in Taverley."), + new CrypticClue("Talk to Turael in Burthorpe.", "Turael", new WorldPoint(2930, 3536, 0), "Turael is located in the small house east of the Toad and Chicken inn in Burthorpe."), + new CrypticClue("More resources than I can handle, but in a very dangerous area. Can't wait to strike gold!", new WorldPoint(3183, 3941, 0), "Dig between the three gold ores in the Wilderness Resource Area."), + new CrypticClue("Observing someone in a swamp, under the telescope lies treasure.", new WorldPoint(2221, 3091, 0), "Dig next to the telescope on Broken Handz's island in the poison wastes. (Accessible only through fairy ring DLR)"), + new CrypticClue("A general who sets a 'shining' example.", "General Hining", new WorldPoint(2186, 3148, 0), "Talk to General Hining in Tyras Camp."), + new CrypticClue("Has no one told you it is rude to ask a lady her age?", "Mawrth", new WorldPoint(2333, 3165, 0), "Talk to Mawrth in Lletya."), + new CrypticClue("Elvish onions.", new WorldPoint(3303, 6092, 0), "Dig in the onion patch east of the Prifddinas allotments.") + ); + + private final String text; + private final String npc; + private final int objectId; + private final WorldPoint location; + private final String solution; + @Nullable + private final String questionText; + + private CrypticClue(String text, WorldPoint location, String solution) + { + this(text, null, -1, location, solution); + } + + private CrypticClue(String text, int objectId, WorldPoint location, String solution) + { + this(text, null, objectId, location, solution, null); + } + + private CrypticClue(String text, String npc, WorldPoint location, String solution) + { + this(text, npc, -1, location, solution, null); + } + + private CrypticClue(String text, String npc, WorldPoint location, String solution, boolean requiresLight) + { + this(text, npc, location, solution); + setRequiresLight(requiresLight); + } + + private CrypticClue(String text, int objectId, WorldPoint location, String solution, String questionText) + { + this(text, null, objectId, location, solution, questionText); + } + + private CrypticClue(String text, String npc, WorldPoint location, String solution, String questionText) + { + this(text, npc, -1, location, solution, questionText); + } + + private CrypticClue(String text, String npc, int objectId, WorldPoint location, String solution) + { + this(text, npc, objectId, location, solution, null); + } + + private CrypticClue(String text, String npc, int objectId, WorldPoint location, String solution, @Nullable String questionText) + { + this.text = text; + this.npc = npc; + this.objectId = objectId; + this.location = location; + this.solution = solution; + this.questionText = questionText; + setRequiresSpade(getLocation() != null && getNpc() == null && objectId == -1); + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Cryptic Clue").build()); + + if (getNpc() != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getNpc()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + + if (objectId != -1) + { + ObjectComposition object = plugin.getClient().getObjectDefinition(objectId); + + if (object != null && object.getImpostorIds() != null) + { + object = object.getImpostor(); + } + + if (object != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("Object:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(object.getName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + } + + panelComponent.getChildren().add(LineComponent.builder().left("Solution:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getSolution()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + // Mark dig location + if (getLocation() != null && getNpc() == null && objectId == -1) + { + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation()); + + if (localLocation != null) + { + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + } + + // Mark NPC + if (plugin.getNpcsToMark() != null) + { + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + // Mark game object + if (objectId != -1) + { + net.runelite.api.Point mousePosition = plugin.getClient().getMouseCanvasPosition(); + + if (plugin.getObjectsToMark() != null) + { + for (TileObject gameObject : plugin.getObjectsToMark()) + { + OverlayUtil.renderHoverableArea(graphics, gameObject.getClickbox(), mousePosition, + CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR); + + OverlayUtil.renderImageLocation(plugin.getClient(), graphics, gameObject.getLocalLocation(), plugin.getClueScrollImage(), IMAGE_Z_OFFSET); + } + } + } + } + + public static CrypticClue forText(String text) + { + for (CrypticClue clue : CLUES) + { + if (text.equalsIgnoreCase(clue.text) || text.equalsIgnoreCase(clue.questionText)) + { + return clue; + } + } + + return null; + } + + @Override + public int[] getObjectIds() + { + return new int[] {objectId}; + } + + @Override + public String[] getNpcs() + { + return new String[] {npc}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java new file mode 100644 index 0000000000..d18aa4d6f2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.Client; +import static net.runelite.api.EquipmentInventorySlot.*; +import static net.runelite.api.EquipmentInventorySlot.LEGS; +import net.runelite.api.Item; +import net.runelite.api.ItemID; +import static net.runelite.api.ItemID.*; +import net.runelite.api.Perspective; +import net.runelite.api.ScriptID; +import net.runelite.api.Varbits; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.plugins.cluescrolls.clues.emote.Emote; +import static net.runelite.client.plugins.cluescrolls.clues.emote.Emote.*; +import static net.runelite.client.plugins.cluescrolls.clues.emote.Emote.BULL_ROARER; +import net.runelite.client.plugins.cluescrolls.clues.emote.STASHUnit; +import static net.runelite.client.plugins.cluescrolls.clues.emote.STASHUnit.*; +import static net.runelite.client.plugins.cluescrolls.clues.emote.STASHUnit.SHANTAY_PASS; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.*; +import static net.runelite.client.plugins.cluescrolls.clues.Enemy.*; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClueScroll +{ + private static final Set CLUES = ImmutableSet.of( + new EmoteClue("Beckon on the east coast of the Kharazi Jungle. Beware of double agents! Equip any vestment stole and a heraldic rune shield.", "Kharazi Jungle", NORTHEAST_CORNER_OF_THE_KHARAZI_JUNGLE, new WorldPoint(2954, 2933, 0), DOUBLE_AGENT_108, BECKON, any("Any stole", item(GUTHIX_STOLE), item(SARADOMIN_STOLE), item(ZAMORAK_STOLE), item(ARMADYL_STOLE), item(BANDOS_STOLE), item(ANCIENT_STOLE)), any("Any heraldic rune shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))), + new EmoteClue("Cheer in the Barbarian Agility Arena. Headbang before you talk to me. Equip a steel platebody, maple shortbow and a Wilderness cape.", "Barbarian Outpost", BARBARIAN_OUTPOST_OBSTACLE_COURSE, new WorldPoint(2552, 3556, 0), CHEER, HEADBANG, item(STEEL_PLATEBODY), item(MAPLE_SHORTBOW), range("Any team cape", TEAM1_CAPE, TEAM50_CAPE)), + new EmoteClue("Bow upstairs in the Edgeville Monastery. Equip a completed prayer book.", "Edgeville Monastery", SOUTHEAST_CORNER_OF_THE_MONASTERY, new WorldPoint(3056, 3484, 1), BOW, any("Any god book", item(HOLY_BOOK), item(BOOK_OF_BALANCE), item(UNHOLY_BOOK), item(BOOK_OF_LAW), item(BOOK_OF_WAR), item(BOOK_OF_DARKNESS))), + new EmoteClue("Cheer in the Shadow dungeon. Equip a rune crossbow, climbing boots and any mitre.", "Shadow dungeon", ENTRANCE_OF_THE_CAVE_OF_DAMIS, new WorldPoint(2629, 5071, 0), CHEER, any("Any mitre", item(GUTHIX_MITRE), item(SARADOMIN_MITRE), item(ZAMORAK_MITRE), item(ANCIENT_MITRE), item(BANDOS_MITRE), item(ARMADYL_MITRE)), item(RUNE_CROSSBOW), item(CLIMBING_BOOTS), item(RING_OF_VISIBILITY)), + new EmoteClue("Cheer at the top of the agility pyramid. Beware of double agents! Equip a blue mystic robe top, and any rune heraldic shield.", "Agility Pyramid", AGILITY_PYRAMID, new WorldPoint(3043, 4697, 3), DOUBLE_AGENT_108, CHEER, item(MYSTIC_ROBE_TOP), any("Any rune heraldic shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))), + new EmoteClue("Dance in Iban's temple. Beware of double agents! Equip Iban's staff, a black mystic top and a black mystic bottom.", "Iban's temple", WELL_OF_VOYAGE, new WorldPoint(2011, 4712, 0), DOUBLE_AGENT_141, DANCE, any("Any iban's staff", item(IBANS_STAFF), item(IBANS_STAFF_U)), item(MYSTIC_ROBE_TOP_DARK), item(MYSTIC_ROBE_BOTTOM_DARK)), + new EmoteClue("Dance on the Fishing Platform. Equip barrows gloves, an amulet of glory and a dragon med helm.", "Fishing Platform", SOUTHEAST_CORNER_OF_THE_FISHING_PLATFORM, new WorldPoint(2782, 3273, 0), DANCE, any("Any amulet of glory", item(AMULET_OF_GLORY), item(AMULET_OF_GLORY1), item(AMULET_OF_GLORY2), item(AMULET_OF_GLORY3), item(AMULET_OF_GLORY4), item(AMULET_OF_GLORY5), item(AMULET_OF_GLORY6)), item(BARROWS_GLOVES), item(DRAGON_MED_HELM)), + new EmoteClue("Flap at the death altar. Beware of double agents! Equip a death tiara, a legend's cape and any ring of wealth.", "Death altar", DEATH_ALTAR, new WorldPoint(2205, 4838, 0), DOUBLE_AGENT_141, FLAP, any("Any ring of wealth", item(RING_OF_WEALTH), item(RING_OF_WEALTH_1), item(RING_OF_WEALTH_2), item(RING_OF_WEALTH_3), item(RING_OF_WEALTH_4), item(RING_OF_WEALTH_5), item(RING_OF_WEALTH_I), item(RING_OF_WEALTH_I1), item(RING_OF_WEALTH_I2), item(RING_OF_WEALTH_I3), item(RING_OF_WEALTH_I4), item(RING_OF_WEALTH_I5)), item(DEATH_TIARA), item(CAPE_OF_LEGENDS)), + new EmoteClue("Headbang in the Fight Arena pub. Equip a pirate bandana, a dragonstone necklace and and a magic longbow.", "Fight Arena pub", OUTSIDE_THE_BAR_BY_THE_FIGHT_ARENA, new WorldPoint(2568, 3149, 0), HEADBANG, any("Any pirate bandana", item(PIRATE_BANDANA), item(PIRATE_BANDANA_7124), item(PIRATE_BANDANA_7130), item(PIRATE_BANDANA_7136)), item(DRAGON_NECKLACE), item(MAGIC_LONGBOW)), + new EmoteClue("Do a jig at the barrows chest. Beware of double agents! Equip any full barrows set.", "Barrows chest", BARROWS_CHEST, new WorldPoint(3551, 9694, 0), DOUBLE_AGENT_141, JIG, any("Any full barrows set", all(any("Ahrim's hood", item(AHRIMS_HOOD), range(AHRIMS_HOOD_100, AHRIMS_HOOD_0)), any("Ahrim's staff", item(AHRIMS_STAFF), range(AHRIMS_STAFF_100, AHRIMS_STAFF_0)), any("Ahrim's robetop", item(AHRIMS_ROBETOP), range(AHRIMS_ROBETOP_100, AHRIMS_ROBETOP_0)), any("Ahrim's robeskirt", item(AHRIMS_ROBESKIRT), range(AHRIMS_ROBESKIRT_100, AHRIMS_ROBESKIRT_0))), all(any("Dharok's helm", item(DHAROKS_HELM), range(DHAROKS_HELM_100, DHAROKS_HELM_0)), any("Dharok's greataxe", item(DHAROKS_GREATAXE), range(DHAROKS_GREATAXE_100, DHAROKS_GREATAXE_0)), any("Dharok's platebody", item(DHAROKS_PLATEBODY), range(DHAROKS_PLATEBODY_100, DHAROKS_PLATEBODY_0)), any("Dharok's platelegs", item(DHAROKS_PLATELEGS), range(DHAROKS_PLATELEGS_100, DHAROKS_PLATELEGS_0))), all(any("Guthan's helm", item(GUTHANS_HELM), range(GUTHANS_HELM_100, GUTHANS_HELM_0)), any("Guthan's warspear", item(GUTHANS_WARSPEAR), range(GUTHANS_WARSPEAR_100, GUTHANS_WARSPEAR_0)), any("Guthan's platebody", item(GUTHANS_PLATEBODY), range(GUTHANS_PLATEBODY_100, GUTHANS_PLATEBODY_0)), any("Guthan's chainskirt", item(GUTHANS_CHAINSKIRT), range(GUTHANS_CHAINSKIRT_100, GUTHANS_CHAINSKIRT_0))), all(any("Karil's coif", item(KARILS_COIF), range(KARILS_COIF_100, KARILS_COIF_0)), any("Karil's crossbow", item(KARILS_CROSSBOW), range(KARILS_CROSSBOW_100, KARILS_CROSSBOW_0)), any("Karil's leathertop", item(KARILS_LEATHERTOP), range(KARILS_LEATHERTOP_100, KARILS_LEATHERTOP_0)), any("Karil's leatherskirt", item(KARILS_LEATHERSKIRT), range(KARILS_LEATHERSKIRT_100, KARILS_LEATHERSKIRT_0))), all(any("Torag's helm", item(TORAGS_HELM), range(TORAGS_HELM_100, TORAGS_HELM_0)), any("Torag's hammers", item(TORAGS_HAMMERS), range(TORAGS_HAMMERS_100, TORAGS_HAMMERS_0)), any("Torag's platebody", item(TORAGS_PLATEBODY), range(TORAGS_PLATEBODY_100, TORAGS_PLATEBODY_0)), any("Torag's platelegs", item(TORAGS_PLATELEGS), range(TORAGS_PLATELEGS_100, TORAGS_PLATELEGS_0))), all(any("Verac's helm", item(VERACS_HELM), range(VERACS_HELM_100, VERACS_HELM_0)), any("Verac's flail", item(VERACS_FLAIL), range(VERACS_FLAIL_100, VERACS_FLAIL_0)), any("Verac's brassard", item(VERACS_BRASSARD), range(VERACS_BRASSARD_100, VERACS_BRASSARD_0)), any("Verac's plateskirt", item(VERACS_PLATESKIRT), range(VERACS_PLATESKIRT_100, VERACS_PLATESKIRT_0))))), + new EmoteClue("Jig at Jiggig. Beware of double agents! Equip a Rune spear, rune platelegs and any rune heraldic helm.", "Jiggig", IN_THE_MIDDLE_OF_JIGGIG, new WorldPoint(2477, 3047, 0), DOUBLE_AGENT_108, JIG, range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5), item(RUNE_SPEAR), item(RUNE_PLATELEGS)), + new EmoteClue("Cheer at the games room. Have nothing equipped at all when you do.", "Games room", null, new WorldPoint(2207, 4952, 0), CHEER, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)), + new EmoteClue("Panic on the pier where you catch the Fishing trawler. Have nothing equipped at all when you do.", "Fishing trawler", null, new WorldPoint(2676, 3169, 0), PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)), + new EmoteClue("Panic in the heart of the Haunted Woods. Beware of double agents! Have no items equipped when you do.", "Haunted Woods (ALQ)", null, new WorldPoint(3611, 3492, 0), DOUBLE_AGENT_108, PANIC, emptySlot("Nothing at all", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)), + new EmoteClue("Show your anger towards the Statue of Saradomin in Ellamaria's garden. Beware of double agents! Equip a zamorak godsword.", "Varrock Castle", BY_THE_BEAR_CAGE_IN_VARROCK_PALACE_GARDENS, new WorldPoint(3230, 3478, 0), DOUBLE_AGENT_141, ANGRY, any("Zamorak godsword", item(ZAMORAK_GODSWORD), item(ZAMORAK_GODSWORD_OR))), + new EmoteClue("Show your anger at the Wise old man. Beware of double agents! Equip an abyssal whip, a legend's cape and some spined chaps.", "Draynor Village", BEHIND_MISS_SCHISM_IN_DRAYNOR_VILLAGE, new WorldPoint(3088, 3254, 0), DOUBLE_AGENT_141, ANGRY, any("Abyssal whip", item(ABYSSAL_WHIP), item(VOLCANIC_ABYSSAL_WHIP), item(FROZEN_ABYSSAL_WHIP)), item(CAPE_OF_LEGENDS), item(SPINED_CHAPS)), + new EmoteClue("Beckon by a collection of crystalline maple trees. Beware of double agents! Equip Bryophyta's staff and a nature tiara.", "North of Prifddinas", CRYSTALLINE_MAPLE_TREES, new WorldPoint(2211, 3427, 0), DOUBLE_AGENT_141, BECKON, range("Bryophyta's staff", BRYOPHYTAS_STAFF_UNCHARGED, BRYOPHYTAS_STAFF), item(NATURE_TIARA)), + new EmoteClue("Beckon in the Digsite, near the eastern winch. Bow before you talk to me. Equip a green gnome hat, snakeskin boots and an iron pickaxe.", "Digsite", DIGSITE, new WorldPoint(3370, 3425, 0), BECKON, BOW, item(GREEN_HAT), item(SNAKESKIN_BOOTS), item(IRON_PICKAXE)), + new EmoteClue("Beckon in Tai Bwo Wannai. Clap before you talk to me. Equip green dragonhide chaps, a ring of dueling and a mithril medium helmet.", "Tai Bwo Wannai", SOUTH_OF_THE_SHRINE_IN_TAI_BWO_WANNAI_VILLAGE, new WorldPoint(2803, 3073, 0), BECKON, CLAP, item(GREEN_DHIDE_CHAPS), any("Ring of dueling", item(RING_OF_DUELING1), item(RING_OF_DUELING2), item(RING_OF_DUELING3), item(RING_OF_DUELING4), item(RING_OF_DUELING5), item(RING_OF_DUELING6), item(RING_OF_DUELING7), item(RING_OF_DUELING8)), item(MITHRIL_MED_HELM)), + new EmoteClue("Beckon in the combat ring of Shayzien. Show your anger before you talk to me. Equip an adamant platebody, adamant full helm and adamant platelegs.", "Shayzien combat ring", WEST_OF_THE_SHAYZIEN_COMBAT_RING, new WorldPoint(1545, 3594, 0), BECKON, ANGRY, item(ADAMANT_PLATELEGS), item(ADAMANT_PLATEBODY), item(ADAMANT_FULL_HELM)), + new EmoteClue("Bow near Lord Iorwerth. Beware of double agents! Equip a charged crystal bow.", "Lord Iorwerth's camp", TENT_IN_LORD_IORWERTHS_ENCAMPMENT, new WorldPoint(2205, 3252, 0), DOUBLE_AGENT_141, BOW, any("Crystal Bow", item(CRYSTAL_BOW), item(CRYSTAL_BOW_24123))), + new EmoteClue("Bow in the Iorwerth Camp. Beware of double agents! Equip a charged crystal bow.", "Lord Iorwerth's camp", TENT_IN_LORD_IORWERTHS_ENCAMPMENT, new WorldPoint(2205, 3252, 0), DOUBLE_AGENT_141, BOW, any("Crystal Bow", item(CRYSTAL_BOW), item(CRYSTAL_BOW_24123))), + new EmoteClue("Bow outside the entrance to the Legends' Guild. Equip iron platelegs, an emerald amulet and an oak longbow.", "Legend's Guild", OUTSIDE_THE_LEGENDS_GUILD_GATES, new WorldPoint(2729, 3349, 0), BOW, item(IRON_PLATELEGS), item(OAK_LONGBOW), item(EMERALD_AMULET)), + new EmoteClue("Bow on the ground floor of the Legend's guild. Equip Legend's cape, a dragon battleaxe and an amulet of glory.", "Legend's Guild", OUTSIDE_THE_LEGENDS_GUILD_DOOR, new WorldPoint(2728, 3377, 0), BOW, item(CAPE_OF_LEGENDS), item(DRAGON_BATTLEAXE), any("Any amulet of glory", item(AMULET_OF_GLORY), item(AMULET_OF_GLORY1), item(AMULET_OF_GLORY2), item(AMULET_OF_GLORY3), item(AMULET_OF_GLORY4), item(AMULET_OF_GLORY5), item(AMULET_OF_GLORY6))), + new EmoteClue("Bow in the ticket office of the Duel Arena. Equip an iron chain body, leather chaps and coif.", "Duel Arena", MUBARIZS_ROOM_AT_THE_DUEL_ARENA, new WorldPoint(3314, 3241, 0), BOW, item(IRON_CHAINBODY), item(LEATHER_CHAPS), item(COIF)), + new EmoteClue("Bow at the top of the lighthouse. Beware of double agents! Equip a blue dragonhide body, blue dragonhide vambraces and no jewelry.", "Lighthouse", TOP_FLOOR_OF_THE_LIGHTHOUSE, new WorldPoint(2511, 3641, 2), DOUBLE_AGENT_108, BOW, item(BLUE_DHIDE_BODY), item(BLUE_DHIDE_VAMBRACES), emptySlot("No jewellery", AMULET, RING)), + new EmoteClue("Blow a kiss between the tables in Shilo Village bank. Beware of double agents! Equip a blue mystic hat, bone spear and rune platebody.", "Shilo Village", SHILO_VILLAGE_BANK, new WorldPoint(2851, 2954, 0), DOUBLE_AGENT_108, BLOW_KISS, item(MYSTIC_HAT), item(BONE_SPEAR), item(RUNE_PLATEBODY)), + new EmoteClue("Blow a kiss in the heart of the lava maze. Equip black dragonhide chaps, a spotted cape and a rolling pin.", "Lava maze", NEAR_A_LADDER_IN_THE_WILDERNESS_LAVA_MAZE, new WorldPoint(3069, 3861, 0), BLOW_KISS, item(BLACK_DHIDE_CHAPS), any("Spotted cape", item(SPOTTED_CAPE), item(SPOTTED_CAPE_10073)), item(ROLLING_PIN)), + new EmoteClue("Blow a kiss outside K'ril Tsutsaroth's chamber. Beware of double agents! Equip a zamorak full helm and the shadow sword.", "K'ril's chamber", OUTSIDE_KRIL_TSUTSAROTHS_ROOM, new WorldPoint(2925, 5333, 2), DOUBLE_AGENT_141, BLOW_KISS, item(ZAMORAK_FULL_HELM), item(SHADOW_SWORD)), + new EmoteClue("Cheer at the Druids' Circle. Equip a blue wizard hat, a bronze two-handed sword and HAM boots.", "Taverley stone circle", TAVERLEY_STONE_CIRCLE, new WorldPoint(2924, 3478, 0), CHEER, item(BLUE_WIZARD_HAT), item(BRONZE_2H_SWORD), item(HAM_BOOTS)), + new EmoteClue("Cheer in the Edgeville general store. Dance before you talk to me. Equip a brown apron, leather boots and leather gloves.", "Edgeville", NORTH_OF_EVIL_DAVES_HOUSE_IN_EDGEVILLE, new WorldPoint(3080, 3509, 0), CHEER, DANCE, item(BROWN_APRON), item(LEATHER_BOOTS), item(LEATHER_GLOVES)), + new EmoteClue("Cheer in the Ogre Pen in the Training Camp. Show you are angry before you talk to me. Equip a green dragonhide body and chaps and a steel square shield.", "King Lathas' camp", OGRE_CAGE_IN_KING_LATHAS_TRAINING_CAMP, new WorldPoint(2527, 3375, 0), CHEER, ANGRY, item(GREEN_DHIDE_BODY), item(GREEN_DHIDE_CHAPS), item(STEEL_SQ_SHIELD)), + new EmoteClue("Cheer in the Entrana church. Beware of double agents! Equip a full set of black dragonhide armour.", "Entrana church", ENTRANA_CHAPEL, new WorldPoint(2852, 3349, 0), DOUBLE_AGENT_141, CHEER, item(BLACK_DHIDE_VAMBRACES), item(BLACK_DHIDE_CHAPS), item(BLACK_DHIDE_BODY)), + new EmoteClue("Cheer for the monks at Port Sarim. Equip a coif, steel plateskirt and a sapphire necklace.", "Port Sarim", NEAR_THE_ENTRANA_FERRY_IN_PORT_SARIM, new WorldPoint(3047, 3237, 0), CHEER, item(COIF), item(STEEL_PLATESKIRT), item(SAPPHIRE_NECKLACE)), + new EmoteClue("Clap in the main exam room in the Exam Centre. Equip a white apron, green gnome boots and leather gloves.", "Exam Centre", OUTSIDE_THE_DIGSITE_EXAM_CENTRE, new WorldPoint(3361, 3339, 0), CLAP, item(WHITE_APRON), item(GREEN_BOOTS), item(LEATHER_GLOVES)), + new EmoteClue("Clap on the causeway to the Wizards' Tower. Equip an iron medium helmet, emerald ring and a white apron.", "Wizards' Tower", ON_THE_BRIDGE_TO_THE_MISTHALIN_WIZARDS_TOWER, new WorldPoint(3113, 3196, 0), CLAP, item(IRON_MED_HELM), item(EMERALD_RING), item(WHITE_APRON)), + new EmoteClue("Clap on the top level of the mill, north of East Ardougne. Equip a blue gnome robe top, HAM robe bottom and an unenchanted tiara.", "East Ardougne", UPSTAIRS_IN_THE_ARDOUGNE_WINDMILL, new WorldPoint(2635, 3385, 3), CLAP, item(BLUE_ROBE_TOP), item(HAM_ROBE), item(TIARA)), + new EmoteClue("Clap in Seers court house. Spin before you talk to me. Equip an adamant halberd, blue mystic robe bottom and a diamond ring.", "Seers Village", OUTSIDE_THE_SEERS_VILLAGE_COURTHOUSE, new WorldPoint(2735, 3469, 0), CLAP, SPIN, item(ADAMANT_HALBERD), item(MYSTIC_ROBE_BOTTOM), item(DIAMOND_RING)), + new EmoteClue("Clap in the magic axe hut. Beware of double agents! Equip only some flared trousers.", "Magic axe hut", OUTSIDE_THE_WILDERNESS_AXE_HUT, new WorldPoint(3191, 3960, 0), DOUBLE_AGENT_141, CLAP, item(FLARED_TROUSERS), item(LOCKPICK), emptySlot("Nothing else", HEAD, CAPE, AMULET, WEAPON, BODY, SHIELD, GLOVES, BOOTS, RING, AMMO)), + new EmoteClue("Clap your hands north of Mount Karuulm Spin before you talk to me. Equip an adamant warhammer, a ring of life and a pair of mithril boots.", "Mount Karuulm", NORTH_OF_MOUNT_KARUULM, new WorldPoint(1306, 3839, 0), CLAP, SPIN, item(ADAMANT_WARHAMMER), item(RING_OF_LIFE), item(MITHRIL_BOOTS)), + new EmoteClue("Cry in the Catherby Ranging shop. Bow before you talk to me. Equip blue gnome boots, a hard leather body and an unblessed silver sickle.", "Catherby", HICKTONS_ARCHERY_EMPORIUM, new WorldPoint(2823, 3443, 0), CRY, BOW, item(BLUE_BOOTS), item(HARDLEATHER_BODY), item(SILVER_SICKLE)), + new EmoteClue("Cry on the shore of Catherby beach. Laugh before you talk to me, equip an adamant sq shield, a bone dagger and mithril platebody.", "Catherby", OUTSIDE_HARRYS_FISHING_SHOP_IN_CATHERBY, new WorldPoint(2852, 3429, 0), CRY, LAUGH, item(ADAMANT_SQ_SHIELD), item(BONE_DAGGER), item(MITHRIL_PLATEBODY)), + new EmoteClue("Cry on top of the western tree in the Gnome Agility Arena. Indicate 'no' before you talk to me. Equip a steel kiteshield, ring of forging and green dragonhide chaps.", "Gnome Stronghold", GNOME_STRONGHOLD_BALANCING_ROPE, new WorldPoint(2473, 3420, 2), CRY, NO, item(STEEL_KITESHIELD), item(RING_OF_FORGING), item(GREEN_DHIDE_CHAPS)), + new EmoteClue("Cry in the TzHaar gem store. Beware of double agents! Equip a fire cape and TokTz-Xil-Ul.", "Tzhaar gem store", TZHAAR_GEM_STORE, new WorldPoint(2463, 5149, 0), DOUBLE_AGENT_141, CRY, any("Fire cape", item(FIRE_CAPE), item(FIRE_CAPE_L), item(FIRE_MAX_CAPE), item(FIRE_MAX_CAPE_L), item(INFERNAL_CAPE), item(INFERNAL_CAPE_L), item(INFERNAL_MAX_CAPE_21285), item(INFERNAL_MAX_CAPE_L)), item(TOKTZXILUL)), + new EmoteClue("Cry in the Draynor Village jail. Jump for joy before you talk to me. Equip an adamant sword, a sapphire amulet and an adamant plateskirt.", "Draynor Village jail", OUTSIDE_DRAYNOR_VILLAGE_JAIL, new WorldPoint(3128, 3245, 0), CRY, JUMP_FOR_JOY, item(ADAMANT_SWORD), item(SAPPHIRE_AMULET), item(ADAMANT_PLATESKIRT)), + new EmoteClue("Dance at the crossroads north of Draynor. Equip an iron chain body, a sapphire ring and a longbow.", "Draynor Village", CROSSROADS_NORTH_OF_DRAYNOR_VILLAGE, new WorldPoint(3109, 3294, 0), DANCE, item(IRON_CHAINBODY), item(SAPPHIRE_RING), item(LONGBOW)), + new EmoteClue("Dance in the Party Room. Equip a steel full helmet, steel platebody and an iron plateskirt.", "Falador Party Room", OUTSIDE_THE_FALADOR_PARTY_ROOM, new WorldPoint(3045, 3376, 0), DANCE, item(STEEL_FULL_HELM), item(STEEL_PLATEBODY), item(IRON_PLATESKIRT)), + new EmoteClue("Dance in the shack in Lumbridge Swamp. Equip a bronze dagger, iron full helmet and a gold ring.", "Lumbridge swamp", NEAR_A_SHED_IN_LUMBRIDGE_SWAMP, new WorldPoint(3203, 3169, 0), DANCE, item(BRONZE_DAGGER), item(IRON_FULL_HELM), item(GOLD_RING)), + new EmoteClue("Dance in the dark caves beneath Lumbridge Swamp. Blow a kiss before you talk to me. Equip an air staff, Bronze full helm and an amulet of power.", "Lumbridge swamp caves", LUMBRIDGE_SWAMP_CAVES, new WorldPoint(3168, 9571, 0), DANCE, BLOW_KISS, Varbits.FIRE_PIT_LUMBRIDGE_SWAMP, item(STAFF_OF_AIR), item(BRONZE_FULL_HELM), item(AMULET_OF_POWER)), + new EmoteClue("Dance at the cat-doored pyramid in Sophanem. Beware of double agents! Equip a ring of life, an uncharged amulet of glory and an adamant two-handed sword.", "Pyramid Of Sophanem", OUTSIDE_THE_GREAT_PYRAMID_OF_SOPHANEM, new WorldPoint(3294, 2781, 0), DOUBLE_AGENT_108, DANCE, item(RING_OF_LIFE), item(AMULET_OF_GLORY), item(ADAMANT_2H_SWORD)), + new EmoteClue("Dance in the centre of Canifis. Bow before you talk to me. Equip a green gnome robe top, mithril plate legs and an iron two-handed sword.", "Canifis", CENTRE_OF_CANIFIS, new WorldPoint(3492, 3488, 0), DANCE, BOW, item(GREEN_ROBE_TOP), item(MITHRIL_PLATELEGS), item(IRON_2H_SWORD)), + new EmoteClue("Dance in the King Black Dragon's lair. Beware of double agents! Equip a black dragonhide body, black dragonhide vambraces and a black dragon mask.", "King black dragon's lair", KING_BLACK_DRAGONS_LAIR, new WorldPoint(2271, 4680, 0), DOUBLE_AGENT_141, DANCE, item(BLACK_DHIDE_BODY), item(BLACK_DHIDE_VAMBRACES), item(BLACK_DRAGON_MASK)), + new EmoteClue("Dance at the entrance to the Grand Exchange. Equip a pink skirt, pink robe top and a body tiara.", "Grand Exchange", SOUTH_OF_THE_GRAND_EXCHANGE, new WorldPoint(3165, 3467, 0), DANCE, item(PINK_SKIRT), item(PINK_ROBE_TOP), item(BODY_TIARA)), + new EmoteClue("Goblin Salute in the Goblin Village. Beware of double agents! Equip a bandos godsword, a bandos cloak and a bandos platebody.", "Goblin Village", OUTSIDE_MUDKNUCKLES_HUT, new WorldPoint(2956, 3505, 0), DOUBLE_AGENT_141, GOBLIN_SALUTE, item(BANDOS_PLATEBODY), item(BANDOS_CLOAK), any("Bandos godsword", item(BANDOS_GODSWORD), item(BANDOS_GODSWORD_OR))), + new EmoteClue("Headbang in the mine north of Al Kharid. Equip a desert shirt, leather gloves and leather boots.", "Al Kharid mine", AL_KHARID_SCORPION_MINE, new WorldPoint(3299, 3289, 0), HEADBANG, item(DESERT_SHIRT), item(LEATHER_GLOVES), item(LEATHER_BOOTS)), + new EmoteClue("Headbang at the exam centre. Beware of double agents! Equip a mystic fire staff, a diamond bracelet and rune boots.", "Exam Centre", INSIDE_THE_DIGSITE_EXAM_CENTRE, new WorldPoint(3362, 3340, 0), DOUBLE_AGENT_108, HEADBANG, item(MYSTIC_FIRE_STAFF), item(DIAMOND_BRACELET), item(RUNE_BOOTS)), + new EmoteClue("Headbang at the top of Slayer Tower. Equip a seercull, a combat bracelet and helm of Neitiznot.", "Slayer Tower", OUTSIDE_THE_SLAYER_TOWER_GARGOYLE_ROOM, new WorldPoint(3421, 3537, 2), HEADBANG, item(SEERCULL), range("Combat bracelet", COMBAT_BRACELET4, COMBAT_BRACELET), item(HELM_OF_NEITIZNOT)), + new EmoteClue("Dance a jig by the entrance to the Fishing Guild. Equip an emerald ring, a sapphire amulet, and a bronze chain body.", "Fishing Guild", OUTSIDE_THE_FISHING_GUILD, new WorldPoint(2610, 3391, 0), JIG, item(EMERALD_RING), item(SAPPHIRE_AMULET), item(BRONZE_CHAINBODY)), + new EmoteClue("Dance a jig under Shantay's Awning. Bow before you talk to me. Equip a pointed blue snail helmet, an air staff and a bronze square shield.", "Shantay Pass", SHANTAY_PASS, new WorldPoint(3304, 3124, 0), JIG, BOW, any("Bruise blue snelm (pointed)", item(BRUISE_BLUE_SNELM_3343)), item(STAFF_OF_AIR), item(BRONZE_SQ_SHIELD)), + new EmoteClue("Do a jig in Varrock's rune store. Equip an air tiara and a staff of water.", "Varrock rune store", AUBURYS_SHOP_IN_VARROCK, new WorldPoint(3253, 3401, 0), JIG, item(AIR_TIARA), item(STAFF_OF_WATER)), + new EmoteClue("Jump for joy at the beehives. Equip a desert shirt, green gnome robe bottoms and a steel axe.", "Catherby", CATHERBY_BEEHIVE_FIELD, new WorldPoint(2759, 3445, 0), JUMP_FOR_JOY, item(DESERT_SHIRT), item(GREEN_ROBE_BOTTOMS), item(STEEL_AXE)), + new EmoteClue("Jump for joy in Yanille bank. Dance a jig before you talk to me. Equip a brown apron, adamantite medium helmet and snakeskin chaps.", "Yanille", OUTSIDE_YANILLE_BANK, new WorldPoint(2610, 3092, 0), JUMP_FOR_JOY, JIG, item(BROWN_APRON), item(ADAMANT_MED_HELM), item(SNAKESKIN_CHAPS)), + new EmoteClue("Jump for joy in the TzHaar sword shop. Shrug before you talk to me. Equip a Steel longsword, Blue D'hide body and blue mystic gloves.", "Tzhaar weapon store", TZHAAR_WEAPONS_STORE, new WorldPoint(2477, 5146, 0), JUMP_FOR_JOY, SHRUG, item(STEEL_LONGSWORD), item(BLUE_DHIDE_BODY), item(MYSTIC_GLOVES)), + new EmoteClue("Jump for joy in the Ancient Cavern. Equip a granite shield, splitbark body and any rune heraldic helm.", "Ancient cavern", ENTRANCE_OF_THE_CAVERN_UNDER_THE_WHIRLPOOL, new WorldPoint(1768, 5366, 1), JUMP_FOR_JOY, item(GRANITE_SHIELD), item(SPLITBARK_BODY), range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5)), + new EmoteClue("Jump for joy at the Neitiznot rune rock. Equip Rune boots, a proselyte hauberk and a dragonstone ring.", "Fremennik Isles", NEAR_A_RUNITE_ROCK_IN_THE_FREMENNIK_ISLES, new WorldPoint(2375, 3850, 0), JUMP_FOR_JOY, item(RUNE_BOOTS), item(PROSELYTE_HAUBERK), item(DRAGONSTONE_RING)), + new EmoteClue("Jump for joy in the centre of Zul-Andra. Beware of double agents! Equip a dragon 2h sword, bandos boots and an obsidian cape.", "Zul-Andra", NEAR_THE_PIER_IN_ZULANDRA, new WorldPoint(2199, 3056, 0), DOUBLE_AGENT_141, JUMP_FOR_JOY, item(DRAGON_2H_SWORD), item(BANDOS_BOOTS), item(OBSIDIAN_CAPE)), + new EmoteClue("Laugh by the fountain of heroes. Equip splitbark legs, dragon boots and a Rune longsword.", "Fountain of heroes", FOUNTAIN_OF_HEROES, new WorldPoint(2920, 9893, 0), LAUGH, item(SPLITBARK_LEGS), any("Dragon boots", item(DRAGON_BOOTS), item(DRAGON_BOOTS_G)), item(RUNE_LONGSWORD)), + new EmoteClue("Laugh in Jokul's tent in the Mountain Camp. Beware of double agents! Equip a rune full helmet, blue dragonhide chaps and a fire battlestaff.", "Mountain Camp", MOUNTAIN_CAMP_GOAT_ENCLOSURE, new WorldPoint(2812, 3681, 0), DOUBLE_AGENT_108, LAUGH, item(RUNE_FULL_HELM), item(BLUE_DHIDE_CHAPS), item(FIRE_BATTLESTAFF)), + new EmoteClue("Laugh at the crossroads south of the Sinclair Mansion. Equip a cowl, a blue wizard robe top and an iron scimitar.", "Sinclair Mansion", ROAD_JUNCTION_SOUTH_OF_SINCLAIR_MANSION, new WorldPoint(2741, 3536, 0), LAUGH, item(LEATHER_COWL), item(BLUE_WIZARD_ROBE), item(IRON_SCIMITAR)), + new EmoteClue("Laugh in front of the gem store in Ardougne market. Equip a Castlewars bracelet, a dragonstone amulet and a ring of forging.", "Ardougne", NEAR_THE_GEM_STALL_IN_ARDOUGNE_MARKET, new WorldPoint(2666, 3304, 0), LAUGH, any("Castle wars bracelet", range(CASTLE_WARS_BRACELET3, CASTLE_WARS_BRACELET1)), item(DRAGONSTONE_AMULET), item(RING_OF_FORGING)), + new EmoteClue("Panic in the Limestone Mine. Equip bronze platelegs, a steel pickaxe and a steel medium helmet.", "Limestone Mine", LIMESTONE_MINE, new WorldPoint(3372, 3498, 0), PANIC, item(BRONZE_PLATELEGS), item(STEEL_PICKAXE), item(STEEL_MED_HELM)), + new EmoteClue("Panic by the mausoleum in Morytania. Wave before you speak to me. Equip a mithril plate skirt, a maple longbow and no boots.", "Morytania mausoleum, access via the experiments cave", MAUSOLEUM_OFF_THE_MORYTANIA_COAST, new WorldPoint(3504, 3576, 0), PANIC, WAVE, item(MITHRIL_PLATESKIRT), item(MAPLE_LONGBOW), emptySlot("No boots", BOOTS)), + new EmoteClue("Panic on the Wilderness volcano bridge. Beware of double agents! Equip any headband and crozier.", "Wilderness volcano", VOLCANO_IN_THE_NORTHEASTERN_WILDERNESS, new WorldPoint(3368, 3935, 0), DOUBLE_AGENT_65, PANIC, any("Any headband", range(RED_HEADBAND, BROWN_HEADBAND), range(WHITE_HEADBAND, GREEN_HEADBAND)), any("Any crozier", item(ANCIENT_CROZIER), item(ARMADYL_CROZIER), item(BANDOS_CROZIER), range(SARADOMIN_CROZIER, ZAMORAK_CROZIER))), + new EmoteClue("Panic by the pilot on White Wolf Mountain. Beware of double agents! Equip mithril platelegs, a ring of life and a rune axe.", "White Wolf Mountain", GNOME_GLIDER_ON_WHITE_WOLF_MOUNTAIN, new WorldPoint(2847, 3499, 0), DOUBLE_AGENT_108, PANIC, item(MITHRIL_PLATELEGS), item(RING_OF_LIFE), item(RUNE_AXE)), + new EmoteClue("Panic by the big egg where no one dare goes and the ground is burnt. Beware of double agents! Equip a dragon med helm, a TokTz-Ket-Xil, a brine sabre, rune platebody and an uncharged amulet of glory.", "Lava dragon isle", SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE, new WorldPoint(3227, 3831, 0), DOUBLE_AGENT_141, PANIC, item(DRAGON_MED_HELM), item(TOKTZKETXIL), item(BRINE_SABRE), item(RUNE_PLATEBODY), any("Uncharged Amulet of glory", item(AMULET_OF_GLORY))), + new EmoteClue("Panic at the area flowers meet snow. Equip Blue D'hide vambraces, a dragon spear and a rune plateskirt.", "Trollweiss mountain", HALFWAY_DOWN_TROLLWEISS_MOUNTAIN, new WorldPoint(2776, 3781, 0), PANIC, item(BLUE_DHIDE_VAMBRACES), item(DRAGON_SPEAR), item(RUNE_PLATESKIRT), item(SLED_4084)), + new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), DOUBLE_AGENT_141, PUSH_UP, item(DRAGON_BATTLEAXE), any("Dragon defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_T), item(DRAGON_DEFENDER_L)), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I), item(TWISTED_SLAYER_HELMET), item(TWISTED_SLAYER_HELMET_I))), + new EmoteClue("Blow a raspberry in the bank of the Warriors' Guild. Beware of double agents! Equip a dragon battleaxe, a slayer helm of any kind and a dragon defender or avernic defender.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), DOUBLE_AGENT_141, RASPBERRY, item(DRAGON_BATTLEAXE), any("Dragon defender or Avernic defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_T), item(DRAGON_DEFENDER_L), item(AVERNIC_DEFENDER), item(AVERNIC_DEFENDER_L)), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I), item(TWISTED_SLAYER_HELMET), item(TWISTED_SLAYER_HELMET_I))), + new EmoteClue("Blow a raspberry at the monkey cage in Ardougne Zoo. Equip a studded leather body, bronze platelegs and a normal staff with no orb.", "Ardougne Zoo", NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO, new WorldPoint(2607, 3282, 0), RASPBERRY, item(STUDDED_BODY), item(BRONZE_PLATELEGS), item(STAFF)), + new EmoteClue("Blow raspberries outside the entrance to Keep Le Faye. Equip a coif, an iron platebody and leather gloves.", "Keep Le Faye", OUTSIDE_KEEP_LE_FAYE, new WorldPoint(2757, 3401, 0), RASPBERRY, item(COIF), item(IRON_PLATEBODY), item(LEATHER_GLOVES)), + new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", "Fishing Guild", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), DOUBLE_AGENT_108, RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)), + new EmoteClue("Salute in the banana plantation. Beware of double agents! Equip a diamond ring, amulet of power, and nothing on your chest and legs.", "Karamja", WEST_SIDE_OF_THE_KARAMJA_BANANA_PLANTATION, new WorldPoint(2914, 3168, 0), DOUBLE_AGENT_108, SALUTE, item(DIAMOND_RING), item(AMULET_OF_POWER), emptySlot("Nothing on chest & legs", BODY, LEGS)), + new EmoteClue("Salute in the Warriors' guild bank. Equip only a black salamander.", "Warriors' guild", WARRIORS_GUILD_BANK, new WorldPoint(2844, 3542, 0), SALUTE, item(BLACK_SALAMANDER), emptySlot("Nothing else", HEAD, CAPE, AMULET, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)), + new EmoteClue("Salute in the centre of the mess hall. Beware of double agents! Equip a rune halberd rune platebody, and an amulet of strength.", "Hosidius mess hall", HOSIDIUS_MESS, new WorldPoint(1646, 3631, 0), DOUBLE_AGENT_108, SALUTE, item(RUNE_HALBERD), item(RUNE_PLATEBODY), item(AMULET_OF_STRENGTH)), + new EmoteClue("Shrug in the mine near Rimmington. Equip a gold necklace, a gold ring and a bronze spear.", "Rimmington mine", RIMMINGTON_MINE, new WorldPoint(2976, 3238, 0), SHRUG, item(GOLD_NECKLACE), item(GOLD_RING), item(BRONZE_SPEAR)), + new EmoteClue("Shrug in Catherby bank. Yawn before you talk to me. Equip a maple longbow, green d'hide chaps and an iron med helm.", "Catherby", OUTSIDE_CATHERBY_BANK, new WorldPoint(2808, 3440, 0), SHRUG, YAWN, item(MAPLE_LONGBOW), item(GREEN_DHIDE_CHAPS), item(IRON_MED_HELM)), + new EmoteClue("Shrug in the Zamorak temple found in the Eastern Wilderness. Beware of double agents! Equip rune platelegs, an iron platebody and blue dragonhide vambraces.", "Chaos temple", CHAOS_TEMPLE_IN_THE_SOUTHEASTERN_WILDERNESS, new WorldPoint(3239, 3611, 0), DOUBLE_AGENT_65, SHRUG, item(RUNE_PLATELEGS), item(IRON_PLATEBODY), item(BLUE_DHIDE_VAMBRACES)), + new EmoteClue("Shrug in the Shayzien command tent. Equip a blue mystic robe bottom, a rune kiteshield and any bob shirt.", "Shayzien command tent", SHAYZIEN_WAR_TENT, new WorldPoint(1555, 3537, 0), SHRUG, item(MYSTIC_ROBE_BOTTOM), item(RUNE_KITESHIELD), range("Any bob shirt", BOBS_RED_SHIRT, BOBS_PURPLE_SHIRT)), + new EmoteClue("Slap your head in the centre of the Kourend catacombs. Beware of double agents! Equip the arclight and the amulet of the damned.", "Kourend catacombs", CENTRE_OF_THE_CATACOMBS_OF_KOUREND, new WorldPoint(1663, 10045, 0), DOUBLE_AGENT_141, SLAP_HEAD, item(ARCLIGHT), any("Amulet of the damned", item(AMULET_OF_THE_DAMNED), item(AMULET_OF_THE_DAMNED_FULL))), + new EmoteClue("Spin at the crossroads north of Rimmington. Equip a green gnome hat, cream gnome top and leather chaps.", "Rimmington", ROAD_JUNCTION_NORTH_OF_RIMMINGTON, new WorldPoint(2981, 3276, 0), SPIN, item(GREEN_HAT), item(CREAM_ROBE_TOP), item(LEATHER_CHAPS)), + new EmoteClue("Spin in Draynor Manor by the fountain. Equip an iron platebody, studded leather chaps and a bronze full helmet.", "Draynor Manor", DRAYNOR_MANOR_BY_THE_FOUNTAIN, new WorldPoint(3088, 3336, 0), SPIN, item(IRON_PLATEBODY), item(STUDDED_CHAPS), item(BRONZE_FULL_HELM)), + new EmoteClue("Spin in front of the Soul altar. Beware of double agents! Equip a dragon pickaxe, helm of neitiznot and a pair of rune boots.", "Soul altar", SOUL_ALTAR, new WorldPoint(1815, 3856, 0), DOUBLE_AGENT_141, SPIN, any("Dragon or Crystal pickaxe", item(DRAGON_PICKAXE), item(DRAGON_PICKAXE_12797), item(INFERNAL_PICKAXE), item(INFERNAL_PICKAXE_UNCHARGED), item(DRAGON_PICKAXE_OR), item(CRYSTAL_PICKAXE), item(CRYSTAL_PICKAXE_INACTIVE)), item(HELM_OF_NEITIZNOT), item(RUNE_BOOTS)), + new EmoteClue("Spin in the Varrock Castle courtyard. Equip a black axe, a coif and a ruby ring.", "Varrock Castle", OUTSIDE_VARROCK_PALACE_COURTYARD, new WorldPoint(3213, 3463, 0), SPIN, item(BLACK_AXE), item(COIF), item(RUBY_RING)), + new EmoteClue("Spin in West Ardougne Church. Equip a dragon spear and red dragonhide chaps.", "West Ardougne Church", CHAPEL_IN_WEST_ARDOUGNE, new WorldPoint(2530, 3290, 0), SPIN, item(DRAGON_SPEAR), item(RED_DHIDE_CHAPS)), + new EmoteClue("Spin on the bridge by the Barbarian Village. Salute before you talk to me. Equip purple gloves, a steel kiteshield and a mithril full helmet.", "Barbarian Village", EAST_OF_THE_BARBARIAN_VILLAGE_BRIDGE, new WorldPoint(3105, 3420, 0), SPIN, SALUTE, item(PURPLE_GLOVES), item(STEEL_KITESHIELD), item(MITHRIL_FULL_HELM)), + new EmoteClue("Stamp in the Enchanted valley west of the waterfall. Beware of double agents! Equip a dragon axe.", "Enchanted Valley (BKQ)", NORTHWESTERN_CORNER_OF_THE_ENCHANTED_VALLEY, new WorldPoint(3030, 4522, 0), DOUBLE_AGENT_141, STAMP, any("Dragon or Crystal axe", item(DRAGON_AXE), item(CRYSTAL_AXE), item(CRYSTAL_AXE_INACTIVE), item(INFERNAL_AXE), item(INFERNAL_AXE_UNCHARGED))), + new EmoteClue("Think in middle of the wheat field by the Lumbridge mill. Equip a blue gnome robetop, a turquoise gnome robe bottom and an oak shortbow.", "Lumbridge mill", WHEAT_FIELD_NEAR_THE_LUMBRIDGE_WINDMILL, new WorldPoint(3159, 3298, 0), THINK, item(BLUE_ROBE_TOP), item(TURQUOISE_ROBE_BOTTOMS), item(OAK_SHORTBOW)), + new EmoteClue("Think in the centre of the Observatory. Spin before you talk to me. Equip a mithril chain body, green dragonhide chaps and a ruby amulet.", "Observatory", OBSERVATORY, new WorldPoint(2439, 3161, 0), THINK, SPIN, item(MITHRIL_CHAINBODY), item(GREEN_DHIDE_CHAPS), item(RUBY_AMULET)), + new EmoteClue("Wave along the south fence of the Lumber Yard. Equip a hard leather body, leather chaps and a bronze axe.", "Lumber Yard", NEAR_THE_SAWMILL_OPERATORS_BOOTH, new WorldPoint(3307, 3491, 0), WAVE, item(HARDLEATHER_BODY), item(LEATHER_CHAPS), item(BRONZE_AXE)), + new EmoteClue("Wave in the Falador gem store. Equip a Mithril pickaxe, Black platebody and an Iron Kiteshield.", "Falador", NEAR_HERQUINS_SHOP_IN_FALADOR, new WorldPoint(2945, 3335, 0), WAVE, item(MITHRIL_PICKAXE), item(BLACK_PLATEBODY), item(IRON_KITESHIELD)), + new EmoteClue("Wave on Mudskipper Point. Equip a black cape, leather chaps and a steel mace.", "Mudskipper Point", MUDSKIPPER_POINT, new WorldPoint(2989, 3110, 0), WAVE, item(BLACK_CAPE), item(LEATHER_CHAPS), item(STEEL_MACE)), + new EmoteClue("Wave on the northern wall of Castle Drakan. Beware of double agents! Wear a dragon sq shield, splitbark body and any boater.", "Castle Drakan", NORTHERN_WALL_OF_CASTLE_DRAKAN, new WorldPoint(3562, 3379, 0), DOUBLE_AGENT_141, WAVE, any("Dragon sq shield", item(DRAGON_SQ_SHIELD), item(DRAGON_SQ_SHIELD_G)), item(SPLITBARK_BODY), any("Any boater", item(RED_BOATER), item(ORANGE_BOATER), item(GREEN_BOATER), item(BLUE_BOATER), item(BLACK_BOATER), item(PINK_BOATER), item(PURPLE_BOATER), item(WHITE_BOATER))), + new EmoteClue("Yawn in the 7th room of Pyramid Plunder. Beware of double agents! Equip a pharaoh sceptre and a full set of menaphite robes.", "Pyramid Plunder", _7TH_CHAMBER_OF_JALSAVRAH, new WorldPoint(1944, 4427, 0), DOUBLE_AGENT_141, YAWN, any("Pharaoh's sceptre", item(PHARAOHS_SCEPTRE), item(PHARAOHS_SCEPTRE_1), item(PHARAOHS_SCEPTRE_2), item(PHARAOHS_SCEPTRE_3), item(PHARAOHS_SCEPTRE_4), item(PHARAOHS_SCEPTRE_5), item(PHARAOHS_SCEPTRE_6), item(PHARAOHS_SCEPTRE_7), item(PHARAOHS_SCEPTRE_8)), any("Full set of menaphite robes", all(item(MENAPHITE_PURPLE_HAT), item(MENAPHITE_PURPLE_TOP), range(MENAPHITE_PURPLE_ROBE, MENAPHITE_PURPLE_KILT)), all(item(MENAPHITE_RED_HAT), item(MENAPHITE_RED_TOP), range(MENAPHITE_RED_ROBE, MENAPHITE_RED_KILT)))), + new EmoteClue("Yawn in the Varrock library. Equip a green gnome robe top, HAM robe bottom and an iron warhammer.", "Varrock Castle", VARROCK_PALACE_LIBRARY, new WorldPoint(3209, 3492, 0), YAWN, item(GREEN_ROBE_TOP), item(HAM_ROBE), item(IRON_WARHAMMER)), + new EmoteClue("Yawn in Draynor Marketplace. Equip studded leather chaps, an iron kiteshield and a steel longsword.", "Draynor", DRAYNOR_VILLAGE_MARKET, new WorldPoint(3083, 3253, 0), YAWN, item(STUDDED_CHAPS), item(IRON_KITESHIELD), item(STEEL_LONGSWORD)), + new EmoteClue("Yawn in the Castle Wars lobby. Shrug before you talk to me. Equip a ruby amulet, a mithril scimitar and a Wilderness cape.", "Castle Wars", CASTLE_WARS_BANK, new WorldPoint(2440, 3092, 0), YAWN, SHRUG, item(RUBY_AMULET), item(MITHRIL_SCIMITAR), range("Any team cape", TEAM1_CAPE, TEAM50_CAPE)), + new EmoteClue("Yawn in the rogues' general store. Beware of double agents! Equip an adamant square shield, blue dragon vambraces and a rune pickaxe.", "Rogues general store", NOTERAZZOS_SHOP_IN_THE_WILDERNESS, new WorldPoint(3026, 3701, 0), DOUBLE_AGENT_65, YAWN, item(ADAMANT_SQ_SHIELD), item(BLUE_DHIDE_VAMBRACES), item(RUNE_PICKAXE)), + new EmoteClue("Yawn at the top of Trollheim. Equip a lava battlestaff, black dragonhide vambraces and a mind shield.", "Trollheim Mountain", ON_TOP_OF_TROLLHEIM_MOUNTAIN, new WorldPoint(2887, 3676, 0), YAWN, any("Lava battlestaff", item(LAVA_BATTLESTAFF), item(LAVA_BATTLESTAFF_21198)), item(BLACK_DHIDE_VAMBRACES), item(MIND_SHIELD)), + new EmoteClue("Yawn in the centre of Arceuus library. Nod your head before you talk to me. Equip blue dragonhide vambraces, adamant boots and an adamant dagger.", "Arceuus library", ENTRANCE_OF_THE_ARCEUUS_LIBRARY, new WorldPoint(1632, 3807, 0), YAWN, YES, item(BLUE_DHIDE_VAMBRACES), item(ADAMANT_BOOTS), item(ADAMANT_DAGGER)), + new EmoteClue("Swing a bullroarer at the top of the Watchtower. Beware of double agents! Equip a dragon plateskirt, climbing boots and a dragon chainbody.", "Yanille Watchtower", TOP_FLOOR_OF_THE_YANILLE_WATCHTOWER, new WorldPoint(2930, 4717, 2), DOUBLE_AGENT_141, BULL_ROARER, any("Dragon plateskirt", item(DRAGON_PLATESKIRT), item(DRAGON_PLATESKIRT_G)), item(CLIMBING_BOOTS), any("Dragon chainbody", item(DRAGON_CHAINBODY_3140), item(DRAGON_CHAINBODY_G)), item(ItemID.BULL_ROARER)), + new EmoteClue("Blow a raspberry at Gypsy Aris in her tent. Equip a gold ring and a gold necklace.", "Varrock", GYPSY_TENT_ENTRANCE, new WorldPoint(3203, 3424, 0), RASPBERRY, item(GOLD_RING), item(GOLD_NECKLACE)), + new EmoteClue("Bow to Brugsen Bursen at the Grand Exchange.", "Grand Exchange", null, new WorldPoint(3164, 3477, 0), BOW), + new EmoteClue("Cheer at Iffie Nitter. Equip a chef hat and a red cape.", "Varrock", FINE_CLOTHES_ENTRANCE, new WorldPoint(3205, 3416, 0), CHEER, item(CHEFS_HAT), item(RED_CAPE)), + new EmoteClue("Clap at Bob's Brilliant Axes. Equip a bronze axe and leather boots.", "Lumbridge", BOB_AXES_ENTRANCE, new WorldPoint(3231, 3203, 0), CLAP, item(BRONZE_AXE), item(LEATHER_BOOTS)), + new EmoteClue("Panic at Al Kharid mine.", "Al Kharid mine", null, new WorldPoint(3300, 3314, 0), PANIC), + new EmoteClue("Spin at Flynn's Mace Shop.", "Falador", null, new WorldPoint(2950, 3387, 0), SPIN)); + + private static final String UNICODE_CHECK_MARK = "\u2713"; + private static final String UNICODE_BALLOT_X = "\u2717"; + + private final String text; + private final String locationName; + @Nullable + private final STASHUnit stashUnit; + private final WorldPoint location; + private final Emote firstEmote; + private final Emote secondEmote; + private final ItemRequirement[] itemRequirements; + + private EmoteClue(String text, String locationName, STASHUnit stashUnit, WorldPoint location, Emote firstEmote, @Nonnull ItemRequirement... itemRequirements) + { + this(text, locationName, stashUnit, location, firstEmote, null, itemRequirements); + } + + private EmoteClue(String text, String locationName, STASHUnit stashUnit, WorldPoint location, Enemy enemy, Emote firstEmote, @Nonnull ItemRequirement... itemRequirements) + { + this(text, locationName, stashUnit, location, firstEmote, null, itemRequirements); + setEnemy(enemy); + } + + private EmoteClue(String text, String locationName, STASHUnit stashUnit, WorldPoint location, Emote firstEmote, Emote secondEmote, @Nonnull ItemRequirement... itemRequirements) + { + this.text = text; + this.locationName = locationName; + this.stashUnit = stashUnit; + this.location = location; + this.firstEmote = firstEmote; + this.secondEmote = secondEmote; + this.itemRequirements = itemRequirements; + } + + private EmoteClue(String text, String locationName, @Nullable STASHUnit stashUnit, WorldPoint location, Emote firstEmote, Emote secondEmote, @Nonnull Varbits firePit, @Nonnull ItemRequirement... itemRequirements) + { + this(text, locationName, stashUnit, location, firstEmote, secondEmote, itemRequirements); + setRequiresLight(true); + setHasFirePit(firePit); + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Emote Clue").build()); + panelComponent.getChildren().add(LineComponent.builder().left("Emotes:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getFirstEmote().getName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (getSecondEmote() != null) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(getSecondEmote().getName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getLocationName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (itemRequirements.length > 0) + { + Client client = plugin.getClient(); + + if (stashUnit != null) + { + client.runScript(ScriptID.WATSON_STASH_UNIT_CHECK, stashUnit.getObjectId(), 0, 0, 0); + int[] intStack = client.getIntStack(); + boolean stashUnitBuilt = intStack[0] == 1; + + panelComponent.getChildren().add(LineComponent.builder() + .left("STASH Unit:") + .right(stashUnitBuilt ? UNICODE_CHECK_MARK : UNICODE_BALLOT_X) + .rightColor(stashUnitBuilt ? Color.GREEN : Color.RED) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder().left("Equip:").build()); + + Item[] equipment = plugin.getEquippedItems(); + Item[] inventory = plugin.getInventoryItems(); + + // If equipment is null, the player is wearing nothing + if (equipment == null) + { + equipment = new Item[0]; + } + + // If inventory is null, the player has nothing in their inventory + if (inventory == null) + { + inventory = new Item[0]; + } + + Item[] combined = new Item[equipment.length + inventory.length]; + System.arraycopy(equipment, 0, combined, 0, equipment.length); + System.arraycopy(inventory, 0, combined, equipment.length, inventory.length); + + for (ItemRequirement requirement : itemRequirements) + { + boolean equipmentFulfilled = requirement.fulfilledBy(equipment); + boolean combinedFulfilled = requirement.fulfilledBy(combined); + + panelComponent.getChildren().add(LineComponent.builder() + .left(requirement.getCollectiveName(client)) + .leftColor(TITLED_CONTENT_COLOR) + .right(combinedFulfilled ? UNICODE_CHECK_MARK : UNICODE_BALLOT_X) + .rightColor(equipmentFulfilled ? Color.GREEN : (combinedFulfilled ? Color.ORANGE : Color.RED)) + .build()); + } + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + LocalPoint localPoint = LocalPoint.fromWorld(plugin.getClient(), getLocation()); + + if (localPoint != null) + { + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localPoint, plugin.getEmoteImage(), Color.ORANGE); + } + + if (stashUnit != null) + { + final WorldPoint[] worldPoints = stashUnit.getWorldPoints(); + + for (final WorldPoint worldPoint : worldPoints) + { + final LocalPoint stashUnitLocalPoint = LocalPoint.fromWorld(plugin.getClient(), worldPoint); + + if (stashUnitLocalPoint != null) + { + final Polygon poly = Perspective.getCanvasTilePoly(plugin.getClient(), stashUnitLocalPoint); + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, Color.RED); + } + } + } + } + } + + public static EmoteClue forText(String text) + { + for (EmoteClue clue : CLUES) + { + if (clue.getText().equalsIgnoreCase(text)) + { + return clue; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java new file mode 100644 index 0000000000..6590079639 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/Enemy.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020, Trevor + * 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.cluescrolls.clues; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum Enemy +{ + //appears in hard clue emote steps in the wilderness + DOUBLE_AGENT_65("Double Agent level 65"), + //appears in hard clue emote steps not in the wilderness + DOUBLE_AGENT_108("Double Agent level 108"), + //appears for master clue emote steps all areas + DOUBLE_AGENT_141("Double Agent level 141"), + //appears for hard clue coordinate steps in the wilderness + ZAMORAK_WIZARD("Zamorak Wizard"), + //appears for hard clue coordinate steps not in the wilderness + SARADOMIN_WIZARD("Saradomin Wizard"), + //appears for elite clue coordinate steps all areas + ARMADYLIAN_OR_BANDOSIAN_GUARD("Armadylian OR Bandosian Guard"), + //appears for master clue coordinate and hot cold clues when single-way combat + BRASSICAN_MAGE("Brassican Mage"), + //appears for master clue coordinate and hot cold clues when multi-way combat + ANCIENT_WIZARDS("Ancient Wizard Trio"), + //There is a master hot cold step that overlaps the border of multi and single according to the wiki. + BRASSICAN_OR_WIZARDS("Brassican Mage OR Ancient Wizards"); + + private final String text; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FairyRingClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FairyRingClue.java new file mode 100644 index 0000000000..6fc915db54 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FairyRingClue.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Set; +import lombok.Getter; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class FairyRingClue extends ClueScroll implements TextClueScroll, LocationClueScroll +{ + private static final Set CLUES = ImmutableSet.of( + new FairyRingClue("A I R 2 3 3 1", new WorldPoint(2702, 3246, 0)), + new FairyRingClue("A I Q 0 4 4 0", new WorldPoint(3000, 3110, 0)), + new FairyRingClue("A L P 1 1 4 0", new WorldPoint(2504, 3633, 0)), + new FairyRingClue("B L P 6 2 0 0", new WorldPoint(2439, 5132, 0)), + new FairyRingClue("B J R 1 1 2 3", new WorldPoint(2648, 4729, 0)), + new FairyRingClue("B I P 7 0 1 3", new WorldPoint(3407, 3330, 0)), + new FairyRingClue("C I S 0 0 0 9", new WorldPoint(1630, 3868, 0)), + new FairyRingClue("C K P 0 2 2 4", new WorldPoint(2073, 4846, 0)), + new FairyRingClue("D I P 8 5 1 1", new WorldPoint(3041, 4770, 0)), + new FairyRingClue("D K S 2 3 1 0", new WorldPoint(2747, 3720, 0)) + ); + + private String text; + private WorldPoint location; + + private FairyRingClue(String text, WorldPoint location) + { + this.text = text; + this.location = location; + setRequiresSpade(true); + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Fairy Ring Clue").build()); + panelComponent.getChildren().add(LineComponent.builder().left("Code:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getText().substring(0, 5)) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Travel to the fairy ring to see where to dig.") + .build()); + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation()); + + if (localLocation == null) + { + return; + } + + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + + public static FairyRingClue forText(String text) + { + for (FairyRingClue clue : CLUES) + { + if (clue.text.equalsIgnoreCase(text)) + { + return clue; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java new file mode 100644 index 0000000000..77910ab61d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019, Twiglet1022 + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableList; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.List; +import javax.annotation.Nonnull; +import lombok.Getter; +import net.runelite.api.Item; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.api.ItemID.*; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollection; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import net.runelite.client.plugins.cluescrolls.clues.item.RangeItemRequirement; +import net.runelite.client.plugins.cluescrolls.clues.item.SingleItemRequirement; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class FaloTheBardClue extends ClueScroll implements TextClueScroll, NpcClueScroll +{ + private static final List CLUES = ImmutableList.of( + new FaloTheBardClue("A blood red weapon, a strong curved sword, found on the island of primate lords.", item(DRAGON_SCIMITAR)), + new FaloTheBardClue("A book that preaches of some great figure, lending strength, might and vigour.", any("Any god book (must be complete)", item(HOLY_BOOK), item(BOOK_OF_BALANCE), item(UNHOLY_BOOK), item(BOOK_OF_LAW), item(BOOK_OF_WAR), item(BOOK_OF_DARKNESS))), + new FaloTheBardClue("A bow of elven craft was made, it shimmers bright, but will soon fade.", any("Crystal Bow", item(CRYSTAL_BOW), item(CRYSTAL_BOW_24123))), + new FaloTheBardClue("A fiery axe of great inferno, when you use it, you'll wonder where the logs go.", item(INFERNAL_AXE)), + new FaloTheBardClue("A mark used to increase one's grace, found atop a seer's place.", item(MARK_OF_GRACE)), + new FaloTheBardClue("A molten beast with fiery breath, you acquire these with its death.", item(LAVA_DRAGON_BONES)), + new FaloTheBardClue("A shiny helmet of flight, to obtain this with melee, struggle you might.", item(ARMADYL_HELMET)), + // The wiki doesn't specify whether the trimmed dragon defender will work so I've assumed that it doesn't + new FaloTheBardClue("A sword held in the other hand, red its colour, Cyclops strength you must withstand.", any("Dragon or Avernic Defender", item(DRAGON_DEFENDER), item(DRAGON_DEFENDER_L), item(AVERNIC_DEFENDER), item(AVERNIC_DEFENDER_L))), + new FaloTheBardClue("A token used to kill mythical beasts, in hopes of a blade or just for an xp feast.", item(WARRIOR_GUILD_TOKEN)), + new FaloTheBardClue("Green is my favourite, mature ale I do love, this takes your herblore above.", item(GREENMANS_ALEM)), + new FaloTheBardClue("It can hold down a boat or crush a goat, this object, you see, is quite heavy.", item(BARRELCHEST_ANCHOR)), + new FaloTheBardClue("It comes from the ground, underneath the snowy plain. Trolls aplenty, with what looks like a mane.", item(BASALT)), + new FaloTheBardClue("No attack to wield, only strength is required, made of obsidian, but with no room for a shield.", item(TZHAARKETOM)), + new FaloTheBardClue("Penance healers runners and more, obtaining this body often gives much deplore.", any("Fighter Torso", item(FIGHTER_TORSO), item(FIGHTER_TORSO_L))), + new FaloTheBardClue("Strangely found in a chest, many believe these gloves are the best.", item(BARROWS_GLOVES)), + new FaloTheBardClue("These gloves of white won't help you fight, but aid in cooking, they just might.", item(COOKING_GAUNTLETS)), + new FaloTheBardClue("They come from some time ago, from a land unto the east. Fossilised they have become, this small and gentle beast.", item(NUMULITE)), + new FaloTheBardClue("To slay a dragon you must first do, before this chest piece can be put on you.", item(RUNE_PLATEBODY)), + new FaloTheBardClue("Vampyres are agile opponents, damaged best with a weapon of many components.", any("Rod of Ivandis or Ivandis/Blisterwood flail", range(ROD_OF_IVANDIS_10, ROD_OF_IVANDIS_1), item(IVANDIS_FLAIL), item(BLISTERWOOD_FLAIL))) + ); + + private static final WorldPoint LOCATION = new WorldPoint(2689, 3550, 0); + private static final String FALO_THE_BARD = "Falo the Bard"; + + private static SingleItemRequirement item(int itemId) + { + return new SingleItemRequirement(itemId); + } + + private static AnyRequirementCollection any(String name, ItemRequirement... requirements) + { + return new AnyRequirementCollection(name, requirements); + } + + private static RangeItemRequirement range(int startItemId, int endItemId) + { + return range(null, startItemId, endItemId); + } + + private static RangeItemRequirement range(String name, int startItemId, int endItemId) + { + return new RangeItemRequirement(name, startItemId, endItemId); + } + + private final String text; + @Nonnull + private final ItemRequirement[] itemRequirements; + + private FaloTheBardClue(String text, @Nonnull ItemRequirement... itemRequirements) + { + this.text = text; + this.itemRequirements = itemRequirements; + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Falo the Bard Clue").build()); + + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(FALO_THE_BARD) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Item:").build()); + + Item[] inventory = plugin.getInventoryItems(); + + // If inventory is null, the player has nothing in their inventory + if (inventory == null) + { + inventory = new Item[0]; + } + + for (ItemRequirement requirement : itemRequirements) + { + boolean inventoryFulfilled = requirement.fulfilledBy(inventory); + + panelComponent.getChildren().add(LineComponent.builder() + .left(requirement.getCollectiveName(plugin.getClient())) + .leftColor(TITLED_CONTENT_COLOR) + .right(inventoryFulfilled ? "\u2713" : "\u2717") + .rightColor(inventoryFulfilled ? Color.GREEN : Color.RED) + .build()); + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + if (!LOCATION.isInScene(plugin.getClient())) + { + return; + } + + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + @Override + public String[] getNpcs() + { + return new String[] {FALO_THE_BARD}; + } + + public static FaloTheBardClue forText(String text) + { + for (FaloTheBardClue clue : CLUES) + { + if (clue.text.equalsIgnoreCase(text)) + { + return clue; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java new file mode 100644 index 0000000000..24ab3264bf --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2018, Eadgars Ruse + * Copyright (c) 2019, Jordan Atwood + * 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.cluescrolls.clues; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.NPC; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdLocation; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdSolver; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperature; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperatureChange; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@EqualsAndHashCode(callSuper = false, exclude = { "hotColdSolver", "location" }) +@Getter +@Slf4j +public class HotColdClue extends ClueScroll implements LocationClueScroll, LocationsClueScroll, TextClueScroll, NpcClueScroll +{ + private static final HotColdClue BEGINNER_CLUE = new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Reldo may have a clue.", + "Reldo", + "Speak to Reldo to receive a strange device.", + new WorldPoint(3211, 3494, 0)); + private static final HotColdClue MASTER_CLUE = new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Jorral may have a clue.", + "Jorral", + "Speak to Jorral to receive a strange device.", + new WorldPoint(2436, 3347, 0)); + private static final HotColdClue MASTER_CLUE_LEAGUE = new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Watson may have a clue.", + "Watson", + "Speak to Watson to receive a strange device.", + new WorldPoint(1645, 3572, 0)); + + private final String text; + private final String npc; + private final String solution; + private final WorldPoint npcLocation; + @Nullable + private HotColdSolver hotColdSolver; + private WorldPoint location; + + public static HotColdClue forText(String text) + { + if (BEGINNER_CLUE.text.equalsIgnoreCase(text)) + { + BEGINNER_CLUE.reset(); + return BEGINNER_CLUE; + } + else if (MASTER_CLUE.text.equalsIgnoreCase(text)) + { + MASTER_CLUE.reset(); + return MASTER_CLUE; + } + else if (MASTER_CLUE_LEAGUE.text.equalsIgnoreCase(text)) + { + MASTER_CLUE_LEAGUE.reset(); + return MASTER_CLUE_LEAGUE; + } + + return null; + } + + private HotColdClue(String text, String npc, String solution, WorldPoint npcLocation) + { + this.text = text; + this.npc = npc; + this.solution = solution; + this.npcLocation = npcLocation; + setRequiresSpade(true); + initializeSolver(); + } + + @Override + public WorldPoint[] getLocations() + { + if (hotColdSolver == null) + { + return new WorldPoint[0]; + } + + if (hotColdSolver.getLastWorldPoint() == null) + { + return new WorldPoint[] {npcLocation}; + } + else + { + return hotColdSolver.getPossibleLocations().stream().map(HotColdLocation::getWorldPoint).toArray(WorldPoint[]::new); + } + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + if (hotColdSolver == null) + { + return; + } + + panelComponent.getChildren().add(TitleComponent.builder() + .text("Hot/Cold Clue") + .build()); + + // strange device has not been tested yet, show how to get it + if (hotColdSolver.getLastWorldPoint() == null && location == null) + { + if (getNpc() != null) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("NPC:") + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getNpc()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Solution:") + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getSolution()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + // strange device has been tested, show possible locations for final dig spot + else + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Possible locations:") + .build()); + + final Map locationCounts = new EnumMap<>(HotColdArea.class); + final Collection digLocations = hotColdSolver.getPossibleLocations(); + + for (HotColdLocation hotColdLocation : digLocations) + { + HotColdArea hotColdArea = hotColdLocation.getHotColdArea(); + + if (locationCounts.containsKey(hotColdArea)) + { + locationCounts.put(hotColdArea, locationCounts.get(hotColdArea) + 1); + } + else + { + locationCounts.put(hotColdArea, 1); + } + } + + if (digLocations.size() > 10) + { + for (HotColdArea area : locationCounts.keySet()) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(area.getName()) + .right(Integer.toString(locationCounts.get(area))) + .build()); + } + } + else + { + for (HotColdArea area : locationCounts.keySet()) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(area.getName() + ':') + .build()); + + for (HotColdLocation hotColdLocation : digLocations) + { + if (hotColdLocation.getHotColdArea() == area) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("- " + hotColdLocation.getArea()) + .leftColor(Color.LIGHT_GRAY) + .build()); + + if (digLocations.size() <= 5 && hotColdLocation.getEnemy() != null) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(hotColdLocation.getEnemy().getText()) + .leftColor(Color.YELLOW) + .build()); + } + } + } + } + } + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + if (hotColdSolver == null) + { + return; + } + + // when final location has been found + if (location != null) + { + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation()); + + if (localLocation != null) + { + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + + return; + } + + // when strange device hasn't been activated yet, show npc who gives you the strange device + if (hotColdSolver.getLastWorldPoint() == null && plugin.getNpcsToMark() != null) + { + for (NPC npcToMark : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npcToMark, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + // once the number of possible dig locations is below 10, show the dig spots + final Collection digLocations = hotColdSolver.getPossibleLocations(); + if (digLocations.size() < 10) + { + // Mark potential dig locations + for (HotColdLocation hotColdLocation : digLocations) + { + WorldPoint wp = hotColdLocation.getWorldPoint(); + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), wp.getX(), wp.getY()); + + if (localLocation == null) + { + return; + } + + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + } + } + + public boolean update(final String message, final ClueScrollPlugin plugin) + { + if (hotColdSolver == null) + { + return false; + } + + final Set temperatureSet; + + if (this == BEGINNER_CLUE) + { + temperatureSet = HotColdTemperature.BEGINNER_HOT_COLD_TEMPERATURES; + } + else if (this == MASTER_CLUE || this == MASTER_CLUE_LEAGUE) + { + temperatureSet = HotColdTemperature.MASTER_HOT_COLD_TEMPERATURES; + } + else + { + temperatureSet = null; + } + + final HotColdTemperature temperature = HotColdTemperature.getFromTemperatureSet(temperatureSet, message); + + if (temperature == null) + { + return false; + } + + // Convert from real to overworld + final WorldPoint localWorld = ClueScrollPlugin.getMirrorPoint(plugin.getClient().getLocalPlayer().getWorldLocation(), true); + + if (localWorld == null) + { + return false; + } + + boolean master = this == MASTER_CLUE || this == MASTER_CLUE_LEAGUE; + if ((this == BEGINNER_CLUE && temperature == HotColdTemperature.BEGINNER_VISIBLY_SHAKING) + || (master && temperature == HotColdTemperature.MASTER_VISIBLY_SHAKING)) + { + markFinalSpot(localWorld); + } + else + { + location = null; + } + + final HotColdTemperatureChange temperatureChange = HotColdTemperatureChange.of(message); + hotColdSolver.signal(localWorld, temperature, temperatureChange); + + return true; + } + + @Override + public void reset() + { + location = null; + initializeSolver(); + } + + private void initializeSolver() + { + final boolean isBeginner; + + if (this == BEGINNER_CLUE) + { + isBeginner = true; + } + else if (this == MASTER_CLUE || this == MASTER_CLUE_LEAGUE) + { + isBeginner = false; + } + else + { + log.warn("Hot cold solver could not be initialized, clue type is unknown; text: {}, npc: {}, solution: {}", + text, npc, solution); + hotColdSolver = null; + return; + } + + final Set locations = Arrays.stream(HotColdLocation.values()) + .filter(l -> l.isBeginnerClue() == isBeginner) + .collect(Collectors.toSet()); + hotColdSolver = new HotColdSolver(locations); + } + + private void markFinalSpot(WorldPoint wp) + { + this.location = wp; + } + + @Override + public String[] getNpcs() + { + return new String[] {npc}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationClueScroll.java new file mode 100644 index 0000000000..194aefcb5a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationClueScroll.java @@ -0,0 +1,38 @@ +/* + * 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.cluescrolls.clues; + +import net.runelite.api.coords.WorldPoint; + +public interface LocationClueScroll +{ + WorldPoint getLocation(); + + default WorldPoint[] getLocations() + { + WorldPoint location = getLocation(); + return location == null ? new WorldPoint[0] : new WorldPoint[]{location}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java new file mode 100644 index 0000000000..eff9d9efd7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java @@ -0,0 +1,34 @@ +/* + * 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.cluescrolls.clues; + +import net.runelite.api.coords.WorldPoint; + +public interface LocationsClueScroll +{ + void reset(); + + WorldPoint[] getLocations(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java new file mode 100644 index 0000000000..2800580294 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Set; +import lombok.Getter; +import static net.runelite.api.ItemID.*; +import net.runelite.api.ObjectComposition; +import static net.runelite.api.ObjectID.CRATE_18506; +import static net.runelite.api.ObjectID.CRATE_2620; +import static net.runelite.api.ObjectID.CRATE_354; +import static net.runelite.api.ObjectID.CRATE_357; +import static net.runelite.api.ObjectID.CRATE_6616; +import net.runelite.api.TileObject; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_FILL_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_HOVER_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@Getter +public class MapClue extends ClueScroll implements ObjectClueScroll +{ + public static final String CHAMPIONS_GUILD = "West of the Champions' Guild"; + public static final String VARROCK_EAST_MINE = "Outside Varrock East Mine"; + public static final String STANDING_STONES = "At the standing stones north of Falador"; + public static final String WIZARDS_TOWER_DIS = "On the south side of the Wizard's Tower (DIS)"; + public static final String SOUTH_OF_DRAYNOR_BANK = "South of Draynor Village Bank"; + + private static final Set CLUES = ImmutableSet.of( + new MapClue(CLUE_SCROLL_EASY_12179, new WorldPoint(3300, 3291, 0), "Al Kharid mine"), + new MapClue(CLUE_SCROLL_EASY_2713, new WorldPoint(3166, 3361, 0), CHAMPIONS_GUILD), + new MapClue(CLUE_SCROLL_EASY_2716, new WorldPoint(3290, 3374, 0), VARROCK_EAST_MINE), + new MapClue(CLUE_SCROLL_EASY_2719, new WorldPoint(3043, 3398, 0), STANDING_STONES), + new MapClue(CLUE_SCROLL_EASY_3516, new WorldPoint(2612, 3482, 0), "Brother Galahad's house, West of McGrubor's Wood."), + new MapClue(CLUE_SCROLL_EASY_3518, new WorldPoint(3110, 3152, 0), WIZARDS_TOWER_DIS), + new MapClue(CLUE_SCROLL_EASY_7236, new WorldPoint(2970, 3415, 0), "North of Falador."), + new MapClue(CLUE_SCROLL_MEDIUM_2827, new WorldPoint(3091, 3227, 0), SOUTH_OF_DRAYNOR_BANK), + new MapClue(CLUE_SCROLL_MEDIUM_3596, new WorldPoint(2907, 3295, 0), "West of the Crafting Guild"), + new MapClue(CLUE_SCROLL_MEDIUM_3598, new WorldPoint(2658, 3488, 0), CRATE_357, "The crate in McGrubor's Wood. Fairy ring ALS"), + new MapClue(CLUE_SCROLL_MEDIUM_3599, new WorldPoint(2651, 3231, 0), "North of the Tower of Life. Fairy ring DJP"), + new MapClue(CLUE_SCROLL_MEDIUM_3601, new WorldPoint(2565, 3248, 0), CRATE_354, "The crate west of the Clocktower."), + new MapClue(CLUE_SCROLL_MEDIUM_3602, new WorldPoint(2924, 3210, 0), "Behind the Chemist's house in Rimmington."), + new MapClue(CLUE_SCROLL_MEDIUM_7286, new WorldPoint(2536, 3865, 0), "Miscellania. Fairy ring CIP"), + new MapClue(CLUE_SCROLL_MEDIUM_7288, new WorldPoint(3434, 3265, 0), "Mort Myre Swamp, west of Mort'ton. Fairy ring BIP"), + new MapClue(CLUE_SCROLL_MEDIUM_7290, new WorldPoint(2454, 3230, 0), "At the entrance to the Ourania Cave."), + new MapClue(CLUE_SCROLL_MEDIUM_7292, new WorldPoint(2578, 3597, 0), "South-east of the Lighthouse. Fairy ring ALP"), + new MapClue(CLUE_SCROLL_MEDIUM_7294, new WorldPoint(2666, 3562, 0), "Between Seers' Village and Rellekka. South-west of Fairy ring CJR"), + new MapClue(CLUE_SCROLL_HARD, new WorldPoint(3309, 3503, 0), CRATE_2620, "A crate in the Lumber Yard, north-east of Varrock."), + new MapClue(CLUE_SCROLL_HARD_2729, new WorldPoint(3190, 3963, 0), "Behind the Magic axe hut in level 56 Wilderness."), + new MapClue(CLUE_SCROLL_HARD_3520, new WorldPoint(2615, 3078, 0), "Yanille anvils, south of the bank."), + new MapClue(CLUE_SCROLL_HARD_3522, new WorldPoint(2488, 3308, 0), "In the western section of West Ardougne."), + new MapClue(CLUE_SCROLL_HARD_3524, new WorldPoint(2457, 3182, 0), CRATE_18506, "In a crate by the stairs to the Observatory Dungeon."), + new MapClue(CLUE_SCROLL_HARD_3525, new WorldPoint(3026, 3628, 0), CRATE_354, "In a crate at the Dark Warriors' Fortress in level 14 Wilderness."), + new MapClue(CLUE_SCROLL_HARD_7239, new WorldPoint(3021, 3912, 0), "South-east of the Wilderness Agility Course in level 50 Wilderness."), + new MapClue(CLUE_SCROLL_HARD_7241, new WorldPoint(2722, 3338, 0), "South of the Legends' Guild. Fairy ring BLR"), + new MapClue(CLUE_SCROLL_ELITE_12130, new WorldPoint(2449, 3130, 0), "South-west of Tree Gnome Village."), + new MapClue(CLUE_SCROLL_ELITE_19782, new WorldPoint(2953, 9523, 1), "In the Mogre Camp, near Port Khazard. You require a Diving Apparatus and a Fishbowl Helmet"), + new MapClue(CLUE_SCROLL_ELITE_19783, new WorldPoint(2202, 3062, 0), "Zul-Andra. Fairy ring BJS"), + new MapClue(CLUE_SCROLL_ELITE_19784, new WorldPoint(1815, 3852, 0), "At the Soul Altar, north-east of the Arceuus essence mine."), + new MapClue(CLUE_SCROLL_ELITE_19785, new WorldPoint(3538, 3208, 0), "East of Burgh de Rott."), + new MapClue(CLUE_SCROLL_ELITE_19786, new WorldPoint(2703, 2716, 0), CRATE_6616, "The crate in south-western Ape Atoll") + ); + + private final int itemId; + private final WorldPoint location; + private final int objectId; + private final String description; + + private MapClue(int itemId, WorldPoint location) + { + this(itemId, location, -1); + } + + private MapClue(int itemId, WorldPoint location, int objectId) + { + this(itemId, location, objectId, null); + } + + MapClue(int itemId, WorldPoint location, String description) + { + this(itemId, location, -1, description); + } + + private MapClue(int itemId, WorldPoint location, int objectId, String description) + { + this.itemId = itemId; + this.location = location; + this.objectId = objectId; + this.description = description; + setRequiresSpade(objectId == -1); + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Map Clue").build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Click the clue scroll along the edge of your world map to see your destination.") + .build()); + + if (objectId != -1) + { + ObjectComposition objectToClick = plugin.getClient().getObjectDefinition(getObjectId()); + + String objectName = "N/A"; + + if (objectToClick != null) + { + objectName = objectToClick.getName(); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Travel to the destination and click the " + objectName + ".") + .build()); + } + else + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Travel to the destination and dig on the marked tile.") + .build()); + } + + if (description != null) + { + panelComponent.getChildren().add(LineComponent.builder().build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(description) + .build()); + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation()); + + if (localLocation == null) + { + return; + } + + // Mark game object + if (objectId != -1) + { + net.runelite.api.Point mousePosition = plugin.getClient().getMouseCanvasPosition(); + + if (plugin.getObjectsToMark() != null) + { + for (TileObject gameObject : plugin.getObjectsToMark()) + { + OverlayUtil.renderHoverableArea(graphics, gameObject.getClickbox(), mousePosition, + CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR); + + OverlayUtil.renderImageLocation(plugin.getClient(), graphics, gameObject.getLocalLocation(), plugin.getClueScrollImage(), IMAGE_Z_OFFSET); + } + } + } + // Mark tile + else + { + OverlayUtil.renderTileOverlay(plugin.getClient(), graphics, localLocation, plugin.getSpadeImage(), Color.ORANGE); + } + } + + public static MapClue forItemId(int itemId) + { + for (MapClue clue : CLUES) + { + if (clue.itemId == itemId) + { + return clue; + } + } + + return null; + } + + @Override + public int[] getObjectIds() + { + return new int[] {objectId}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MusicClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MusicClue.java new file mode 100644 index 0000000000..cc27dabbbb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MusicClue.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2019, Hydrox6 + * 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.cluescrolls.clues; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class MusicClue extends ClueScroll implements NpcClueScroll +{ + private static final WorldPoint LOCATION = new WorldPoint(2990, 3384, 0); + private static final String CECILIA = "Cecilia"; + private static final Pattern SONG_PATTERN = Pattern.compile("([A-Za-z !&',.]+)"); + + private final String song; + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Music Clue").build()); + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(CECILIA) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left("Falador Park") + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Song:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(song) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + if (!LOCATION.isInScene(plugin.getClient())) + { + return; + } + + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + @Override + public String[] getNpcs() + { + return new String[] {CECILIA}; + } + + public static MusicClue forText(String text) + { + final Matcher m = SONG_PATTERN.matcher(text); + if (m.find()) + { + final String song = m.group(1); + return new MusicClue(song); + } + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NamedObjectClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NamedObjectClueScroll.java new file mode 100644 index 0000000000..1b52b1c213 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NamedObjectClueScroll.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, Jordan Atwood + * 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.cluescrolls.clues; + +import javax.annotation.Nullable; + +/** + * Represents a clue which should highlight objects of a given name rather than a specific ID and location, as some + * clues will call for a general action which can be completed at any number of locations. The area in which this + * highlighting should occur can be restricted by giving a non-null array of region IDs where only objects within those + * regions will be highlighted. + */ +public interface NamedObjectClueScroll +{ + String[] getObjectNames(); + + @Nullable + int[] getObjectRegions(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java new file mode 100644 index 0000000000..d343693e0a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +public interface NpcClueScroll +{ + String[] getNpcs(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java new file mode 100644 index 0000000000..c832cb23ae --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +public interface ObjectClueScroll extends LocationClueScroll +{ + int[] getObjectIds(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java new file mode 100644 index 0000000000..4f7adb6db6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2019 Hydrox6 + * 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.cluescrolls.clues; + +import com.google.common.collect.ImmutableSet; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.Item; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.Point; +import net.runelite.api.TileObject; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_FILL_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_HOVER_BORDER_COLOR; +import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollection; +import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.*; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import net.runelite.client.plugins.cluescrolls.clues.item.SingleItemRequirement; +import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; + +@Getter +public class SkillChallengeClue extends ClueScroll implements NpcClueScroll, NamedObjectClueScroll +{ + @AllArgsConstructor + @Getter + enum ChallengeType + { + CHARLIE("Charlie the Tramp", "Southern Entrance to Varrock"), + SHERLOCK("Sherlock", "East of the Sorcerer's Tower in Seers' Village"); + + private String name; + private String location; + } + + private static final AnyRequirementCollection ANY_PICKAXE = any("Any Pickaxe", + item(ItemID.BRONZE_PICKAXE), + item(ItemID.IRON_PICKAXE), + item(ItemID.STEEL_PICKAXE), + item(ItemID.BLACK_PICKAXE), + item(ItemID.MITHRIL_PICKAXE), + item(ItemID.ADAMANT_PICKAXE), + item(ItemID.RUNE_PICKAXE), + item(ItemID.DRAGON_PICKAXE), + item(ItemID.DRAGON_PICKAXE_12797), + item(ItemID.DRAGON_PICKAXE_OR), + item(ItemID.INFERNAL_PICKAXE), + item(ItemID.INFERNAL_PICKAXE_UNCHARGED), + item(ItemID.GILDED_PICKAXE), + item(ItemID._3RD_AGE_PICKAXE), + item(ItemID.CRYSTAL_PICKAXE) + ); + + private static final AnyRequirementCollection ANY_AXE = any("Any Axe", + item(ItemID.BRONZE_AXE), + item(ItemID.IRON_AXE), + item(ItemID.STEEL_AXE), + item(ItemID.BLACK_AXE), + item(ItemID.MITHRIL_AXE), + item(ItemID.ADAMANT_AXE), + item(ItemID.RUNE_AXE), + item(ItemID.DRAGON_AXE), + item(ItemID.INFERNAL_AXE), + item(ItemID.INFERNAL_AXE_UNCHARGED), + item(ItemID.GILDED_AXE), + item(ItemID._3RD_AGE_AXE), + item(ItemID.CRYSTAL_AXE) + ); + + private static final AnyRequirementCollection ANY_HARPOON = any("Harpoon", + item(ItemID.HARPOON), + item(ItemID.BARBTAIL_HARPOON), + item(ItemID.DRAGON_HARPOON), + item(ItemID.INFERNAL_HARPOON), + item(ItemID.INFERNAL_HARPOON_UNCHARGED), + item(ItemID.CRYSTAL_HARPOON) + ); + + private static final Set CLUES = ImmutableSet.of( + // Charlie Tasks + new SkillChallengeClue("Cook a Pike", "i need to cook charlie a pike.", "i need to take the cooked pike to charlie.", item(ItemID.PIKE), item(ItemID.RAW_PIKE)), + new SkillChallengeClue("Cook a Trout", "i need to cook charlie a trout.", "i need to take the cooked trout to charlie.", item(ItemID.TROUT), item(ItemID.RAW_TROUT)), + new SkillChallengeClue("Craft a Leather Body", "i need to craft charlie a leather body.", "i need to take the leather body i crafted to charlie.", item(ItemID.LEATHER_BODY), item(ItemID.LEATHER), item(ItemID.NEEDLE), item(ItemID.THREAD)), + new SkillChallengeClue("Craft some Leather Chaps", "i need to craft charlie some leather chaps.", "i need to take the leather chaps i crafted to charlie.", item(ItemID.LEATHER_CHAPS), item(ItemID.LEATHER), item(ItemID.NEEDLE), item(ItemID.THREAD)), + new SkillChallengeClue("Fish a Herring", "i need to fish charlie a herring.", "i need to take a raw herring to charlie.", item(ItemID.RAW_HERRING), any("Fishing rod", item(ItemID.FISHING_ROD), item(ItemID.PEARL_FISHING_ROD)), item(ItemID.FISHING_BAIT)), + new SkillChallengeClue("Fish a Trout", "i need to fish charlie a trout.", "i need to take a raw trout to charlie.", item(ItemID.RAW_TROUT), any("Fly fishing rod", item(ItemID.FLY_FISHING_ROD), item(ItemID.PEARL_FLY_FISHING_ROD)), item(ItemID.FEATHER)), + new SkillChallengeClue("Mine a piece of Iron Ore", "i need to mine charlie a piece of iron ore from an iron vein.", "i need to take the iron ore to charlie.", item(ItemID.IRON_ORE), ANY_PICKAXE), + new SkillChallengeClue("Smith an Iron Dagger", "i need to smith charlie one iron dagger.", "i need to take the iron dagger i smithed to charlie.", item(ItemID.IRON_DAGGER), item(ItemID.IRON_BAR), item(ItemID.HAMMER)), + // Elite Sherlock Tasks + new SkillChallengeClue("Equip a Dragon Scimitar.", true, any("Any Dragon Scimitar", item(ItemID.DRAGON_SCIMITAR), item(ItemID.DRAGON_SCIMITAR_OR))), + new SkillChallengeClue("Enchant some Dragonstone Jewellery.", "enchant a piece of dragonstone jewellery.", + xOfItem(ItemID.COSMIC_RUNE, 1), + any("Water Rune x15", xOfItem(ItemID.WATER_RUNE, 15), xOfItem(ItemID.MIST_RUNE, 15), xOfItem(ItemID.MUD_RUNE, 15), xOfItem(ItemID.STEAM_RUNE, 15), item(ItemID.STAFF_OF_WATER), item(ItemID.WATER_BATTLESTAFF), item(ItemID.MYSTIC_WATER_STAFF), item(ItemID.MUD_BATTLESTAFF), item(ItemID.MYSTIC_MUD_STAFF), item(ItemID.MIST_BATTLESTAFF), item(ItemID.MYSTIC_MIST_STAFF), item(ItemID.STEAM_BATTLESTAFF), item(ItemID.MYSTIC_STEAM_STAFF), item(ItemID.STEAM_BATTLESTAFF_12795), item(ItemID.MYSTIC_STEAM_STAFF_12796), item(ItemID.KODAI_WAND)), + any("Earth Rune x15", xOfItem(ItemID.EARTH_RUNE, 15), xOfItem(ItemID.DUST_RUNE, 15), xOfItem(ItemID.MUD_RUNE, 15), xOfItem(ItemID.LAVA_RUNE, 15), item(ItemID.STAFF_OF_EARTH), item(ItemID.EARTH_BATTLESTAFF), item(ItemID.MYSTIC_EARTH_STAFF), item(ItemID.MUD_BATTLESTAFF), item(ItemID.MYSTIC_MUD_STAFF), item(ItemID.DUST_BATTLESTAFF), item(ItemID.MYSTIC_DUST_STAFF), item(ItemID.LAVA_BATTLESTAFF), item(ItemID.MYSTIC_LAVA_STAFF), item(ItemID.LAVA_BATTLESTAFF_21198), item(ItemID.MYSTIC_LAVA_STAFF_21200)), + any("Unenchanted Dragonstone Jewellery", item(ItemID.DRAGONSTONE_RING), item(ItemID.DRAGON_NECKLACE), item(ItemID.DRAGONSTONE_BRACELET), item(ItemID.DRAGONSTONE_AMULET))), + new SkillChallengeClue("Craft a nature rune.", item(ItemID.PURE_ESSENCE)), + new SkillChallengeClue("Catch a mottled eel with aerial fishing in Lake Molch.", any("Fish chunks or King worms", item(ItemID.FISH_CHUNKS), item(ItemID.KING_WORM)), emptySlot("No Gloves", EquipmentInventorySlot.GLOVES), emptySlot("No Weapon", EquipmentInventorySlot.WEAPON), emptySlot("No Shield", EquipmentInventorySlot.SHIELD)), + new SkillChallengeClue("Score a goal in skullball.", true, any("Ring of Charos", item(ItemID.RING_OF_CHAROS), item(ItemID.RING_OF_CHAROSA))), + new SkillChallengeClue("Complete a lap of Ape atoll agility course.", true, any("Ninja Monkey Greegree", item(ItemID.NINJA_MONKEY_GREEGREE), item(ItemID.NINJA_MONKEY_GREEGREE_4025), item(ItemID.KRUK_MONKEY_GREEGREE))), + new SkillChallengeClue("Create a super defence potion.", item(ItemID.CADANTINE_POTION_UNF), item(ItemID.WHITE_BERRIES)), + new SkillChallengeClue("Steal from a chest in Ardougne Castle."), + new SkillChallengeClue("Craft a green dragonhide body.", xOfItem(ItemID.GREEN_DRAGON_LEATHER, 3), item(ItemID.NEEDLE), item(ItemID.THREAD)), + new SkillChallengeClue("String a yew longbow.", item(ItemID.YEW_LONGBOW_U), item(ItemID.BOW_STRING)), + new SkillChallengeClue("Kill a Dust Devil.", "slay a dust devil.", true, any("Facemask or Slayer Helmet", item(ItemID.FACEMASK), item(ItemID.SLAYER_HELMET), item(ItemID. SLAYER_HELMET_I), item(ItemID. BLACK_SLAYER_HELMET), item(ItemID. BLACK_SLAYER_HELMET_I), item(ItemID. PURPLE_SLAYER_HELMET), item(ItemID. PURPLE_SLAYER_HELMET_I), item(ItemID. RED_SLAYER_HELMET), item(ItemID. RED_SLAYER_HELMET_I), item(ItemID.GREEN_SLAYER_HELMET), item(ItemID. GREEN_SLAYER_HELMET_I), item(ItemID. TURQUOISE_SLAYER_HELMET), item(ItemID. TURQUOISE_SLAYER_HELMET_I), item(ItemID. HYDRA_SLAYER_HELMET), item(ItemID. HYDRA_SLAYER_HELMET_I))), + new SkillChallengeClue("Catch a black warlock.", item(ItemID.BUTTERFLY_JAR), any("Butterfly Net", item(ItemID.BUTTERFLY_NET), item(ItemID.MAGIC_BUTTERFLY_NET))), + new SkillChallengeClue("Catch a red chinchompa.", item(ItemID.BOX_TRAP)), + new SkillChallengeClue("Mine a mithril ore.", ANY_PICKAXE), + new SkillChallengeClue("Smith a mithril 2h sword.", item(ItemID.HAMMER), xOfItem(ItemID.MITHRIL_BAR, 3)), + new SkillChallengeClue("Catch a raw shark.", ANY_HARPOON), + new SkillChallengeClue("Cut a yew log.", ANY_AXE), + new SkillChallengeClue("Fix a magical lamp in Dorgesh-Kaan.", new String[] { "Broken lamp" }, new int[] { 10834, 10835 }, item(ItemID.LIGHT_ORB)), + new SkillChallengeClue("Burn a yew log.", item(ItemID.YEW_LOGS), item(ItemID.TINDERBOX)), + new SkillChallengeClue("Cook a swordfish", "cook a swordfish", item(ItemID.RAW_SWORDFISH)), + new SkillChallengeClue("Craft multiple cosmic runes from a single essence.", item(ItemID.PURE_ESSENCE)), + new SkillChallengeClue("Plant a watermelon seed.", item(ItemID.RAKE), item(ItemID.SEED_DIBBER), xOfItem(ItemID.WATERMELON_SEED, 3)), + new SkillChallengeClue("Activate the Chivalry prayer."), + new SkillChallengeClue("Hand in a Tier 2 or higher set of Shayzien supply armour", "take the lovakengj armourers a boxed set of shayzien supply armour at tier 2 or above.", any("Shayzien Supply Set (Tier 2 or higher)", item(ItemID.SHAYZIEN_SUPPLY_SET_2), item(ItemID.SHAYZIEN_SUPPLY_SET_3), item(ItemID.SHAYZIEN_SUPPLY_SET_4), item(ItemID.SHAYZIEN_SUPPLY_SET_5))), + // Master Sherlock Tasks + new SkillChallengeClue("Equip an abyssal whip in front of the abyssal demons of the Slayer Tower.", true, any("Abyssal Whip", item(ItemID.ABYSSAL_WHIP), item(ItemID.FROZEN_ABYSSAL_WHIP), item(ItemID.VOLCANIC_ABYSSAL_WHIP))), + new SkillChallengeClue("Smith a runite med helm.", item(ItemID.HAMMER), item(ItemID.RUNITE_BAR)), + new SkillChallengeClue("Teleport to a spirit tree you planted yourself."), + new SkillChallengeClue("Create a Barrows teleport tablet.", item(ItemID.DARK_ESSENCE_BLOCK), xOfItem(ItemID.BLOOD_RUNE, 1), xOfItem(ItemID.LAW_RUNE, 2), xOfItem(ItemID.SOUL_RUNE, 2)), + new SkillChallengeClue("Kill a Nechryael in the Slayer Tower.", "slay a nechryael in the slayer tower."), + new SkillChallengeClue("Kill a Spiritual Mage while wearing something from their god.", "kill the spiritual, magic and godly whilst representing their own god."), + new SkillChallengeClue("Create an unstrung dragonstone amulet at a furnace.", item(ItemID.GOLD_BAR), item(ItemID.DRAGONSTONE), item(ItemID.AMULET_MOULD)), + new SkillChallengeClue("Burn a magic log.", item(ItemID.MAGIC_LOGS), item(ItemID.TINDERBOX)), + new SkillChallengeClue("Burn a redwood log.", item(ItemID.REDWOOD_LOGS), item(ItemID.TINDERBOX)), + new SkillChallengeClue("Complete a lap of Rellekka's Rooftop Agility Course", "complete a lap of the rellekka rooftop agility course whilst sporting the finest amount of grace.", true, + all("A full Graceful set", + any("", item(ItemID.GRACEFUL_HOOD), item(ItemID.GRACEFUL_HOOD_11851), item(ItemID.GRACEFUL_HOOD_13579), item(ItemID.GRACEFUL_HOOD_13580), item(ItemID.GRACEFUL_HOOD_13591), item(ItemID.GRACEFUL_HOOD_13592), item(ItemID.GRACEFUL_HOOD_13603), item(ItemID.GRACEFUL_HOOD_13604), item(ItemID.GRACEFUL_HOOD_13615), item(ItemID.GRACEFUL_HOOD_13616), item(ItemID.GRACEFUL_HOOD_13627), item(ItemID.GRACEFUL_HOOD_13628), item(ItemID.GRACEFUL_HOOD_13667), item(ItemID.GRACEFUL_HOOD_13668), item(ItemID.GRACEFUL_HOOD_21061), item(ItemID.GRACEFUL_HOOD_21063), item(ItemID.GRACEFUL_HOOD_24743), item(ItemID.GRACEFUL_HOOD_24745)), + any("", item(ItemID.GRACEFUL_CAPE), item(ItemID.GRACEFUL_CAPE_11853), item(ItemID.GRACEFUL_CAPE_13581), item(ItemID.GRACEFUL_CAPE_13582), item(ItemID.GRACEFUL_CAPE_13593), item(ItemID.GRACEFUL_CAPE_13594), item(ItemID.GRACEFUL_CAPE_13605), item(ItemID.GRACEFUL_CAPE_13606), item(ItemID.GRACEFUL_CAPE_13617), item(ItemID.GRACEFUL_CAPE_13618), item(ItemID.GRACEFUL_CAPE_13629), item(ItemID.GRACEFUL_CAPE_13630), item(ItemID.GRACEFUL_CAPE_13669), item(ItemID.GRACEFUL_CAPE_13670), item(ItemID.GRACEFUL_CAPE_21064), item(ItemID.GRACEFUL_CAPE_21066), item(ItemID.GRACEFUL_CAPE_24746), item(ItemID.GRACEFUL_CAPE_24748), item(ItemID.AGILITY_CAPE), item(ItemID.AGILITY_CAPE_13340), item(ItemID.AGILITY_CAPET), item(ItemID.AGILITY_CAPET_13341), item(ItemID.MAX_CAPE), item(ItemID.MAX_CAPE_13342)), + any("", item(ItemID.GRACEFUL_TOP), item(ItemID.GRACEFUL_TOP_11855), item(ItemID.GRACEFUL_TOP_13583), item(ItemID.GRACEFUL_TOP_13584), item(ItemID.GRACEFUL_TOP_13595), item(ItemID.GRACEFUL_TOP_13596), item(ItemID.GRACEFUL_TOP_13607), item(ItemID.GRACEFUL_TOP_13608), item(ItemID.GRACEFUL_TOP_13619), item(ItemID.GRACEFUL_TOP_13620), item(ItemID.GRACEFUL_TOP_13631), item(ItemID.GRACEFUL_TOP_13632), item(ItemID.GRACEFUL_TOP_13671), item(ItemID.GRACEFUL_TOP_13672), item(ItemID.GRACEFUL_TOP_21067), item(ItemID.GRACEFUL_TOP_21069), item(ItemID.GRACEFUL_TOP_24749), item(ItemID.GRACEFUL_TOP_24751)), + any("", item(ItemID.GRACEFUL_LEGS), item(ItemID.GRACEFUL_LEGS_11857), item(ItemID.GRACEFUL_LEGS_13585), item(ItemID.GRACEFUL_LEGS_13586), item(ItemID.GRACEFUL_LEGS_13597), item(ItemID.GRACEFUL_LEGS_13598), item(ItemID.GRACEFUL_LEGS_13609), item(ItemID.GRACEFUL_LEGS_13610), item(ItemID.GRACEFUL_LEGS_13621), item(ItemID.GRACEFUL_LEGS_13622), item(ItemID.GRACEFUL_LEGS_13633), item(ItemID.GRACEFUL_LEGS_13634), item(ItemID.GRACEFUL_LEGS_13673), item(ItemID.GRACEFUL_LEGS_13674), item(ItemID.GRACEFUL_LEGS_21070), item(ItemID.GRACEFUL_LEGS_21072), item(ItemID.GRACEFUL_LEGS_24752), item(ItemID.GRACEFUL_LEGS_24754)), + any("", item(ItemID.GRACEFUL_GLOVES), item(ItemID.GRACEFUL_GLOVES_11859), item(ItemID.GRACEFUL_GLOVES_13587), item(ItemID.GRACEFUL_GLOVES_13588), item(ItemID.GRACEFUL_GLOVES_13599), item(ItemID.GRACEFUL_GLOVES_13600), item(ItemID.GRACEFUL_GLOVES_13611), item(ItemID.GRACEFUL_GLOVES_13612), item(ItemID.GRACEFUL_GLOVES_13623), item(ItemID.GRACEFUL_GLOVES_13624), item(ItemID.GRACEFUL_GLOVES_13635), item(ItemID.GRACEFUL_GLOVES_13636), item(ItemID.GRACEFUL_GLOVES_13675), item(ItemID.GRACEFUL_GLOVES_13676), item(ItemID.GRACEFUL_GLOVES_21073), item(ItemID.GRACEFUL_GLOVES_21075), item(ItemID.GRACEFUL_GLOVES_24755), item(ItemID.GRACEFUL_GLOVES_24757)), + any("", item(ItemID.GRACEFUL_BOOTS), item(ItemID.GRACEFUL_BOOTS_11861), item(ItemID.GRACEFUL_BOOTS_13589), item(ItemID.GRACEFUL_BOOTS_13590), item(ItemID.GRACEFUL_BOOTS_13601), item(ItemID.GRACEFUL_BOOTS_13602), item(ItemID.GRACEFUL_BOOTS_13613), item(ItemID.GRACEFUL_BOOTS_13614), item(ItemID.GRACEFUL_BOOTS_13625), item(ItemID.GRACEFUL_BOOTS_13626), item(ItemID.GRACEFUL_BOOTS_13637), item(ItemID.GRACEFUL_BOOTS_13638), item(ItemID.GRACEFUL_BOOTS_13677), item(ItemID.GRACEFUL_BOOTS_13678), item(ItemID.GRACEFUL_BOOTS_21076), item(ItemID.GRACEFUL_BOOTS_21078), item(ItemID.GRACEFUL_BOOTS_24758), item(ItemID.GRACEFUL_BOOTS_24760)))), + new SkillChallengeClue("Mix an anti-venom potion.", item(ItemID.ANTIDOTE4_5952), xOfItem(ItemID.ZULRAHS_SCALES, 20)), + new SkillChallengeClue("Mine a piece of Runite ore", "mine a piece of runite ore whilst sporting the finest mining gear.", true, ANY_PICKAXE, all("Prospector kit", item(ItemID.PROSPECTOR_HELMET), any("", item(ItemID.PROSPECTOR_JACKET), item(ItemID.VARROCK_ARMOUR_4)), item(ItemID.PROSPECTOR_LEGS), item(ItemID.PROSPECTOR_BOOTS))), + new SkillChallengeClue("Steal a gem from the Ardougne market."), + new SkillChallengeClue("Pickpocket an elf."), + new SkillChallengeClue("Bind a blood rune at the blood altar.", item(ItemID.DARK_ESSENCE_FRAGMENTS)), + new SkillChallengeClue("Create a ranging mix potion.", "mix a ranging mix potion.", item(ItemID.RANGING_POTION2), item(ItemID.CAVIAR)), + new SkillChallengeClue("Fletch a rune dart.", item(ItemID.RUNE_DART_TIP), item(ItemID.FEATHER)), + new SkillChallengeClue("Cremate a set of fiyr remains.", any("Magic or Redwood Pyre Logs", item(ItemID.MAGIC_PYRE_LOGS), item(ItemID.REDWOOD_PYRE_LOGS)), item(ItemID.TINDERBOX), item(ItemID.FIYR_REMAINS)), + new SkillChallengeClue("Dissect a sacred eel.", item(ItemID.KNIFE), any("Fishing rod", item(ItemID.FISHING_ROD), item(ItemID.PEARL_FISHING_ROD)), item(ItemID.FISHING_BAIT)), + new SkillChallengeClue("Kill a lizardman shaman."), + new SkillChallengeClue("Catch an Anglerfish.", "angle for an anglerfish whilst sporting the finest fishing gear.", true, any("Fishing rod", item(ItemID.FISHING_ROD), item(ItemID.PEARL_FISHING_ROD)), item(ItemID.SANDWORMS), all("Angler's outfit", item(ItemID.ANGLER_HAT), item(ItemID.ANGLER_TOP), item(ItemID.ANGLER_WADERS), item(ItemID.ANGLER_BOOTS))), + new SkillChallengeClue("Chop a redwood log.", "chop a redwood log whilst sporting the finest lumberjack gear.", true, ANY_AXE, all("Lumberjack outfit", item(ItemID.LUMBERJACK_HAT), item(ItemID.LUMBERJACK_TOP), item(ItemID.LUMBERJACK_LEGS), item(ItemID.LUMBERJACK_BOOTS))), + new SkillChallengeClue("Craft a light orb in the Dorgesh-Kaan bank.", item(ItemID.CAVE_GOBLIN_WIRE), item(ItemID.EMPTY_LIGHT_ORB)), + new SkillChallengeClue("Kill a reanimated Abyssal Demon.", "kill a reanimated abyssal.", xOfItem(ItemID.SOUL_RUNE, 4), xOfItem(ItemID.BLOOD_RUNE, 1), any("Nature Rune x4", xOfItem(ItemID.NATURE_RUNE, 4), item(ItemID.BRYOPHYTAS_STAFF)), range("Ensouled abyssal head", ItemID.ENSOULED_ABYSSAL_HEAD, ItemID.ENSOULED_ABYSSAL_HEAD_13508)), + new SkillChallengeClue("Kill a Fiyr shade inside Mort'tons shade catacombs.", any("Any Silver Shade Key", item(ItemID.SILVER_KEY_RED), item(ItemID.SILVER_KEY_BROWN), item(ItemID.SILVER_KEY_CRIMSON), item(ItemID.SILVER_KEY_BLACK), item(ItemID.SILVER_KEY_PURPLE))) + ); + + private final ChallengeType type; + private final String challenge; + private final String rawChallenge; + private final String returnText; + private final ItemRequirement[] itemRequirements; + private final SingleItemRequirement returnItem; + private final boolean requireEquip; + private final String[] objectNames; + private final int[] objectRegions; + @Setter + private boolean challengeCompleted; + + // Charlie Tasks + private SkillChallengeClue(String challenge, String rawChallenge, String returnText, SingleItemRequirement returnItem, ItemRequirement ... itemRequirements) + { + this.type = ChallengeType.CHARLIE; + this.challenge = challenge; + this.rawChallenge = rawChallenge; + this.returnText = returnText; + this.itemRequirements = itemRequirements; + this.returnItem = returnItem; + this.challengeCompleted = false; + this.requireEquip = false; + this.objectNames = new String[0]; + this.objectRegions = null; + } + + // Non-cryptic Sherlock Tasks + private SkillChallengeClue(String challenge, ItemRequirement ... itemRequirements) + { + this(challenge, challenge.toLowerCase(), itemRequirements); + } + + // Non-cryptic Sherlock Tasks + private SkillChallengeClue(String challenge, String[] objectNames, int[] objectRegions, ItemRequirement ... itemRequirements) + { + this(challenge, challenge.toLowerCase(), false, objectNames, objectRegions, itemRequirements); + } + + // Non-cryptic Sherlock Tasks + private SkillChallengeClue(String challenge, boolean requireEquip, ItemRequirement ... itemRequirements) + { + this(challenge, challenge.toLowerCase(), requireEquip, new String[0], null, itemRequirements); + } + + // Sherlock Tasks + private SkillChallengeClue(String challenge, String rawChallenge, ItemRequirement ... itemRequirements) + { + this(challenge, rawChallenge, false, new String[0], null, itemRequirements); + } + + // Sherlock Tasks + private SkillChallengeClue(String challenge, String rawChallenge, boolean requireEquip, ItemRequirement ... itemRequirements) + { + this(challenge, rawChallenge, requireEquip, new String[0], null, itemRequirements); + } + + // Sherlock Tasks + private SkillChallengeClue(String challenge, String rawChallenge, boolean requireEquip, String[] objectNames, int[] objectRegions, ItemRequirement ... itemRequirements) + { + this.type = ChallengeType.SHERLOCK; + this.challenge = challenge; + this.rawChallenge = rawChallenge; + this.itemRequirements = itemRequirements; + this.challengeCompleted = false; + this.requireEquip = requireEquip; + this.objectNames = objectNames; + this.objectRegions = objectRegions; + this.returnText = "" + rawChallenge + ""; + + this.returnItem = null; + } + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.getChildren().add(TitleComponent.builder().text("Skill Challenge Clue").build()); + + if (!challengeCompleted) + { + panelComponent.getChildren().add(LineComponent.builder().left("Challenge:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(challenge) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (itemRequirements.length > 0) + { + panelComponent.getChildren().add(LineComponent.builder().left(requireEquip ? "Equipment:" : "Items Required:").build()); + + for (LineComponent line : getRequirements(plugin, requireEquip, itemRequirements)) + { + panelComponent.getChildren().add(line); + } + } + } + else + { + panelComponent.getChildren().add(LineComponent.builder().left("NPC:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(type.getName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(type.getLocation()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + + if (returnItem != null) + { + panelComponent.getChildren().add(LineComponent.builder().left("Item:").build()); + for (LineComponent line : getRequirements(plugin, false, returnItem)) + { + panelComponent.getChildren().add(line); + } + } + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + // Mark NPC + if (plugin.getNpcsToMark() != null) + { + for (NPC npc : plugin.getNpcsToMark()) + { + OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET); + } + } + + // Mark objects + if (!challengeCompleted && objectNames.length > 0 && plugin.getNamedObjectsToMark() != null) + { + final Point mousePosition = plugin.getClient().getMouseCanvasPosition(); + + for (final TileObject object : plugin.getNamedObjectsToMark()) + { + if (plugin.getClient().getPlane() != object.getPlane()) + { + continue; + } + + OverlayUtil.renderHoverableArea(graphics, object.getClickbox(), mousePosition, + CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR); + + OverlayUtil.renderImageLocation(plugin.getClient(), graphics, object.getLocalLocation(), plugin.getClueScrollImage(), IMAGE_Z_OFFSET); + } + } + } + + private static List getRequirements(ClueScrollPlugin plugin, boolean requireEquipped, ItemRequirement ... requirements) + { + List components = new ArrayList<>(); + + Item[] equipment = plugin.getEquippedItems(); + Item[] inventory = plugin.getInventoryItems(); + + // If equipment is null, the player is wearing nothing + if (equipment == null) + { + equipment = new Item[0]; + } + + // If inventory is null, the player has nothing in their inventory + if (inventory == null) + { + inventory = new Item[0]; + } + + Item[] combined = new Item[equipment.length + inventory.length]; + System.arraycopy(equipment, 0, combined, 0, equipment.length); + System.arraycopy(inventory, 0, combined, equipment.length, inventory.length); + + for (ItemRequirement requirement : requirements) + { + boolean equipmentFulfilled = requirement.fulfilledBy(equipment); + boolean combinedFulfilled = requirement.fulfilledBy(combined); + + components.add(LineComponent.builder() + .left(requirement.getCollectiveName(plugin.getClient())) + .leftColor(TITLED_CONTENT_COLOR) + .right(combinedFulfilled ? "\u2713" : "\u2717") + .rightColor(equipmentFulfilled || (combinedFulfilled && !requireEquipped) ? Color.GREEN : (combinedFulfilled ? Color.ORANGE : Color.RED)) + .build()); + } + + return components; + } + + public static SkillChallengeClue forText(String text, String rawText) + { + for (SkillChallengeClue clue : CLUES) + { + if (rawText.equalsIgnoreCase(clue.returnText)) + { + clue.setChallengeCompleted(true); + return clue; + } + else if (text.equals(clue.rawChallenge)) + { + clue.setChallengeCompleted(false); + return clue; + } + } + return null; + } + + @Override + public String[] getNpcs() + { + return new String[] {type.getName()}; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/TextClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/TextClueScroll.java new file mode 100644 index 0000000000..d402606618 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/TextClueScroll.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues; + +public interface TextClueScroll +{ + String getText(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java new file mode 100644 index 0000000000..52c78d94c8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2018, Eadgars Ruse + * 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.cluescrolls.clues; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.InventoryID; +import net.runelite.api.ItemContainer; +import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_1; +import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_2; +import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_3; +import net.runelite.api.coords.WorldPoint; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; +import net.runelite.client.util.Text; + +@Getter +@RequiredArgsConstructor +public class ThreeStepCrypticClue extends ClueScroll implements TextClueScroll, ObjectClueScroll, NpcClueScroll, LocationsClueScroll +{ + public static ThreeStepCrypticClue forText(String plainText, String text) + { + final String[] split = text.split("
\\s*
"); + final List> steps = new ArrayList<>(split.length); + + for (String part : split) + { + boolean isDone = part.contains(""); + final String rawText = Text.sanitizeMultilineText(part); + + for (CrypticClue clue : CrypticClue.CLUES) + { + if (!rawText.equalsIgnoreCase(clue.getText())) + { + continue; + } + + steps.add(new AbstractMap.SimpleEntry<>(clue, isDone)); + break; + } + } + + if (steps.isEmpty() || steps.size() < 3) + { + return null; + } + + return new ThreeStepCrypticClue(steps, plainText); + } + + private final List> clueSteps; + private final String text; + + @Override + public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin) + { + panelComponent.setPreferredSize(new Dimension(200, 0)); + + for (int i = 0; i < clueSteps.size(); i++) + { + final Map.Entry e = clueSteps.get(i); + + if (!e.getValue()) + { + CrypticClue c = e.getKey(); + + panelComponent.getChildren().add(TitleComponent.builder().text("Cryptic Clue #" + (i + 1)).build()); + panelComponent.getChildren().add(LineComponent.builder().left("Solution:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(c.getSolution()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + } + } + } + + @Override + public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin) + { + for (Map.Entry e : clueSteps) + { + if (!e.getValue()) + { + e.getKey().makeWorldOverlayHint(graphics, plugin); + } + } + } + + public boolean update(int containerId, final ItemContainer itemContainer) + { + if (containerId == InventoryID.INVENTORY.getId()) + { + return checkForPart(itemContainer, TORN_CLUE_SCROLL_PART_1, 0) || + checkForPart(itemContainer, TORN_CLUE_SCROLL_PART_2, 1) || + checkForPart(itemContainer, TORN_CLUE_SCROLL_PART_3, 2); + } + + return false; + } + + private boolean checkForPart(final ItemContainer itemContainer, int clueScrollPart, int index) + { + // If we have the part then that step is done + if (itemContainer.contains(clueScrollPart)) + { + final Map.Entry entry = clueSteps.get(index); + + if (!entry.getValue()) + { + entry.setValue(true); + return true; + } + } + + return false; + } + + @Override + public void reset() + { + for (Map.Entry clueStep : clueSteps) + { + clueStep.setValue(false); + } + } + + @Override + public WorldPoint getLocation() + { + return null; + } + + @Override + public WorldPoint[] getLocations() + { + return clueSteps.stream() + .filter(s -> !s.getValue()) + .map(s -> s.getKey().getLocation()) + .toArray(WorldPoint[]::new); + } + + @Override + public String[] getNpcs() + { + return clueSteps.stream() + .filter(s -> !s.getValue()) + .map(s -> s.getKey().getNpc()) + .toArray(String[]::new); + } + + @Override + public int[] getObjectIds() + { + return clueSteps.stream() + .filter(s -> !s.getValue()) + .mapToInt(s -> s.getKey().getObjectId()) + .toArray(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/Emote.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/Emote.java new file mode 100644 index 0000000000..1be5825d72 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/Emote.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.emote; + +import lombok.Getter; +import static net.runelite.api.SpriteID.*; + +@Getter +public enum Emote +{ + BULL_ROARER("Bull Roarer", -1), + YES("Yes", EMOTE_YES), + NO("No", EMOTE_NO), + THINK("Think", EMOTE_THINK), + BOW("Bow", EMOTE_BOW), + ANGRY("Angry", EMOTE_ANGRY), + CRY("Cry", EMOTE_CRY), + LAUGH("Laugh", EMOTE_LAUGH), + CHEER("Cheer", EMOTE_CHEER), + WAVE("Wave", EMOTE_WAVE), + BECKON("Beckon", EMOTE_BECKON), + DANCE("Dance", EMOTE_DANCE), + CLAP("Clap", EMOTE_CLAP), + PANIC("Panic", EMOTE_PANIC), + JIG("Jig", EMOTE_JIG), + SPIN("Spin", EMOTE_SPIN), + HEADBANG("Headbang", EMOTE_HEADBANG), + JUMP_FOR_JOY("Jump for Joy", EMOTE_JUMP_FOR_JOY), + RASPBERRY("Raspberry", EMOTE_RASPBERRY), + YAWN("Yawn", EMOTE_YAWN), + SALUTE("Salute", EMOTE_SALUTE), + SHRUG("Shrug", EMOTE_SHRUG), + BLOW_KISS("Blow Kiss", EMOTE_BLOW_KISS), + GOBLIN_SALUTE("Goblin Salute", EMOTE_GOBLIN_SALUTE), + SLAP_HEAD("Slap Head", EMOTE_SLAP_HEAD), + STAMP("Stamp", EMOTE_STAMP), + FLAP("Flap", EMOTE_FLAP), + PUSH_UP("Push up", EMOTE_PUSH_UP); + + private String name; + private int spriteId; + + Emote(String name, int spriteId) + { + this.name = name; + this.spriteId = spriteId; + } + + public boolean hasSprite() + { + return spriteId != -1; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java new file mode 100644 index 0000000000..5eca73a7c0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/emote/STASHUnit.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018 Abex + * 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.cluescrolls.clues.emote; + +import lombok.Getter; +import net.runelite.api.NullObjectID; +import net.runelite.api.coords.WorldPoint; + +@Getter +public enum STASHUnit +{ + NEAR_A_SHED_IN_LUMBRIDGE_SWAMP(NullObjectID.NULL_28958, new WorldPoint(3201, 3171, 0)), + ON_THE_BRIDGE_TO_THE_MISTHALIN_WIZARDS_TOWER(NullObjectID.NULL_28959, new WorldPoint(3115, 3194, 0)), + DRAYNOR_VILLAGE_MARKET(NullObjectID.NULL_28960, new WorldPoint(3083, 3254, 0)), + LIMESTONE_MINE(NullObjectID.NULL_28961, new WorldPoint(3373, 3498, 0)), + OUTSIDE_THE_LEGENDS_GUILD_GATES(NullObjectID.NULL_28962, new WorldPoint(2735, 3350, 0)), + MUDSKIPPER_POINT(NullObjectID.NULL_28963, new WorldPoint(2988, 3111, 0)), + NEAR_THE_ENTRANA_FERRY_IN_PORT_SARIM(NullObjectID.NULL_28964, new WorldPoint(3050, 3237, 0)), + AL_KHARID_SCORPION_MINE(NullObjectID.NULL_28965, new WorldPoint(3303, 3289, 0)), + DRAYNOR_MANOR_BY_THE_FOUNTAIN(NullObjectID.NULL_28966, new WorldPoint(3089, 3331, 0)), + WHEAT_FIELD_NEAR_THE_LUMBRIDGE_WINDMILL(NullObjectID.NULL_28967, new WorldPoint(3163, 3297, 0)), + CROSSROADS_NORTH_OF_DRAYNOR_VILLAGE(NullObjectID.NULL_28968, new WorldPoint(3111, 3289, 0)), + RIMMINGTON_MINE(NullObjectID.NULL_28969, new WorldPoint(2976, 3239, 0)), + VARROCK_PALACE_LIBRARY(NullObjectID.NULL_28970, new WorldPoint(3214, 3490, 0)), + UPSTAIRS_IN_THE_ARDOUGNE_WINDMILL(NullObjectID.NULL_28971, new WorldPoint(2635, 3386, 2)), + OUTSIDE_THE_FALADOR_PARTY_ROOM(NullObjectID.NULL_28972, new WorldPoint(3043, 3371, 0)), + TAVERLEY_STONE_CIRCLE(NullObjectID.NULL_28973, new WorldPoint(2924, 3477, 0)), + CATHERBY_BEEHIVE_FIELD(NullObjectID.NULL_28974, new WorldPoint(2764, 3438, 0)), + NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO(NullObjectID.NULL_28975, new WorldPoint(2608, 3284, 0)), + ROAD_JUNCTION_NORTH_OF_RIMMINGTON(NullObjectID.NULL_28976, new WorldPoint(2981, 3278, 0)), + OUTSIDE_THE_FISHING_GUILD(NullObjectID.NULL_28977, new WorldPoint(2608, 3393, 0)), + OUTSIDE_KEEP_LE_FAYE(NullObjectID.NULL_28978, new WorldPoint(2756, 3399, 0)), + ROAD_JUNCTION_SOUTH_OF_SINCLAIR_MANSION(NullObjectID.NULL_28979, new WorldPoint(2735, 3534, 0)), + OUTSIDE_THE_DIGSITE_EXAM_CENTRE(NullObjectID.NULL_28980, new WorldPoint(3353, 3343, 0)), + NEAR_THE_SAWMILL_OPERATORS_BOOTH(NullObjectID.NULL_28981, new WorldPoint(3298, 3490, 0)), + MUBARIZS_ROOM_AT_THE_DUEL_ARENA(NullObjectID.NULL_28982, new WorldPoint(3316, 3242, 0)), + OUTSIDE_VARROCK_PALACE_COURTYARD(NullObjectID.NULL_28983, new WorldPoint(3211, 3456, 0)), + NEAR_HERQUINS_SHOP_IN_FALADOR(NullObjectID.NULL_28984, new WorldPoint(2941, 3339, 0)), + SOUTH_OF_THE_GRAND_EXCHANGE(NullObjectID.NULL_28985, new WorldPoint(3159, 3464, 0)), + AUBURYS_SHOP_IN_VARROCK(NullObjectID.NULL_28986, new WorldPoint(3252, 3404, 0)), + CENTRE_OF_CANIFIS(NullObjectID.NULL_28987, new WorldPoint(3491, 3489, 0)), + MAUSOLEUM_OFF_THE_MORYTANIA_COAST(NullObjectID.NULL_28988, new WorldPoint(3500, 3575, 0)), + EAST_OF_THE_BARBARIAN_VILLAGE_BRIDGE(NullObjectID.NULL_28989, new WorldPoint(3110, 3422, 0)), + SOUTH_OF_THE_SHRINE_IN_TAI_BWO_WANNAI_VILLAGE(NullObjectID.NULL_28990, new WorldPoint(2802, 3081, 0)), + CASTLE_WARS_BANK(NullObjectID.NULL_28991, new WorldPoint(2444, 3093, 0)), + BARBARIAN_OUTPOST_OBSTACLE_COURSE(NullObjectID.NULL_28992, new WorldPoint(2541, 3550, 0)), + GNOME_STRONGHOLD_BALANCING_ROPE(NullObjectID.NULL_28993, new WorldPoint(2473, 3418, 2)), + OUTSIDE_YANILLE_BANK(NullObjectID.NULL_28994, new WorldPoint(2603, 3091, 0)), + OBSERVATORY(NullObjectID.NULL_28995, new WorldPoint(2439, 3166, 0)), + OGRE_CAGE_IN_KING_LATHAS_TRAINING_CAMP(NullObjectID.NULL_28996, new WorldPoint(2533, 3377, 0)), + DIGSITE(NullObjectID.NULL_28997, new WorldPoint(3370, 3420, 0)), + HICKTONS_ARCHERY_EMPORIUM(NullObjectID.NULL_28998, new WorldPoint(2825, 3441, 0)), + SHANTAY_PASS(NullObjectID.NULL_28999, new WorldPoint(3308, 3125, 0)), + LUMBRIDGE_SWAMP_CAVES(NullObjectID.NULL_29000, new WorldPoint(3222, 9584, 0), new WorldPoint(3167, 9570, 0)), + OUTSIDE_CATHERBY_BANK(NullObjectID.NULL_29001, new WorldPoint(2807, 3437, 0)), + OUTSIDE_THE_SEERS_VILLAGE_COURTHOUSE(NullObjectID.NULL_29002, new WorldPoint(2731, 3475, 0)), + OUTSIDE_HARRYS_FISHING_SHOP_IN_CATHERBY(NullObjectID.NULL_29003, new WorldPoint(2837, 3436, 0)), + TZHAAR_WEAPONS_STORE(NullObjectID.NULL_29004, new WorldPoint(2479, 5146, 0)), + NORTH_OF_EVIL_DAVES_HOUSE_IN_EDGEVILLE(NullObjectID.NULL_29005, new WorldPoint(3077, 3503, 0)), + WEST_OF_THE_SHAYZIEN_COMBAT_RING(NullObjectID.NULL_29006, new WorldPoint(1534, 3591, 0)), + ENTRANCE_OF_THE_ARCEUUS_LIBRARY(NullObjectID.NULL_29007, new WorldPoint(1642, 3809, 0)), + OUTSIDE_DRAYNOR_VILLAGE_JAIL(NullObjectID.NULL_29008, new WorldPoint(3130, 3250, 0)), + CHAOS_TEMPLE_IN_THE_SOUTHEASTERN_WILDERNESS(NullObjectID.NULL_29009, new WorldPoint(3245, 3609, 0)), + FISHING_GUILD_BANK(NullObjectID.NULL_29010, new WorldPoint(2593, 3409, 0)), + TOP_FLOOR_OF_THE_LIGHTHOUSE(NullObjectID.NULL_29011, new WorldPoint(2512, 3640, 2)), + OUTSIDE_THE_GREAT_PYRAMID_OF_SOPHANEM(NullObjectID.NULL_29012, new WorldPoint(3291, 2780, 0)), + NOTERAZZOS_SHOP_IN_THE_WILDERNESS(NullObjectID.NULL_29013, new WorldPoint(3027, 3699, 0)), + WEST_SIDE_OF_THE_KARAMJA_BANANA_PLANTATION(NullObjectID.NULL_29014, new WorldPoint(2909, 3169, 0)), + MOUNTAIN_CAMP_GOAT_ENCLOSURE(NullObjectID.NULL_29015, new WorldPoint(2810, 3677, 0)), + GNOME_GLIDER_ON_WHITE_WOLF_MOUNTAIN(NullObjectID.NULL_29016, new WorldPoint(2849, 3496, 0)), + SHILO_VILLAGE_BANK(NullObjectID.NULL_29017, new WorldPoint(2853, 2952, 0)), + INSIDE_THE_DIGSITE_EXAM_CENTRE(NullObjectID.NULL_29018, new WorldPoint(3356, 3333, 0)), + NORTHEAST_CORNER_OF_THE_KHARAZI_JUNGLE(NullObjectID.NULL_29019, new WorldPoint(2952, 2932, 0)), + VOLCANO_IN_THE_NORTHEASTERN_WILDERNESS(NullObjectID.NULL_29020, new WorldPoint(3368, 3930, 0)), + IN_THE_MIDDLE_OF_JIGGIG(NullObjectID.NULL_29021, new WorldPoint(2478, 3048, 0)), + AGILITY_PYRAMID(NullObjectID.NULL_29022, new WorldPoint(3357, 2830, 0)), + HOSIDIUS_MESS(NullObjectID.NULL_29023, new WorldPoint(1646, 3632, 0)), + CHAPEL_IN_WEST_ARDOUGNE(NullObjectID.NULL_29024, new WorldPoint(2527, 3294, 0)), + NEAR_A_RUNITE_ROCK_IN_THE_FREMENNIK_ISLES(NullObjectID.NULL_29025, new WorldPoint(2374, 3847, 0)), + NEAR_A_LADDER_IN_THE_WILDERNESS_LAVA_MAZE(NullObjectID.NULL_29026, new WorldPoint(3069, 3862, 0)), + ENTRANCE_OF_THE_CAVE_OF_DAMIS(NullObjectID.NULL_29027, new WorldPoint(2629, 5070, 0)), + WARRIORS_GUILD_BANK(NullObjectID.NULL_29028, new WorldPoint(2844, 3537, 0)), + SOUTHEAST_CORNER_OF_THE_MONASTERY(NullObjectID.NULL_29029, new WorldPoint(3056, 3482, 0)), + SOUTHEAST_CORNER_OF_THE_FISHING_PLATFORM(NullObjectID.NULL_29030, new WorldPoint(2787, 3277, 0)), + OUTSIDE_THE_SLAYER_TOWER_GARGOYLE_ROOM(NullObjectID.NULL_29031, new WorldPoint(3423, 3534, 2)), + ON_TOP_OF_TROLLHEIM_MOUNTAIN(NullObjectID.NULL_29032, new WorldPoint(2886, 3676, 0)), + FOUNTAIN_OF_HEROES(NullObjectID.NULL_29033, new WorldPoint(2916, 9891, 0)), + ENTRANCE_OF_THE_CAVERN_UNDER_THE_WHIRLPOOL(NullObjectID.NULL_29034, new WorldPoint(1764, 5367, 1), new WorldPoint(1636, 5367, 1)), + HALFWAY_DOWN_TROLLWEISS_MOUNTAIN(NullObjectID.NULL_29035, new WorldPoint(2782, 3787, 0)), + SHAYZIEN_WAR_TENT(NullObjectID.NULL_29036, new WorldPoint(1550, 3541, 0)), + OUTSIDE_THE_LEGENDS_GUILD_DOOR(NullObjectID.NULL_29037, new WorldPoint(2727, 3371, 0)), + NEAR_THE_GEM_STALL_IN_ARDOUGNE_MARKET(NullObjectID.NULL_29038, new WorldPoint(2672, 3302, 0)), + OUTSIDE_THE_BAR_BY_THE_FIGHT_ARENA(NullObjectID.NULL_29039, new WorldPoint(2571, 3150, 0)), + SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE(NullObjectID.NULL_29040, new WorldPoint(3228, 3830, 0)), + NEAR_THE_PIER_IN_ZULANDRA(NullObjectID.NULL_29041, new WorldPoint(2203, 3059, 0)), + BARROWS_CHEST(NullObjectID.NULL_29042, new WorldPoint(3547, 9690, 0)), + WELL_OF_VOYAGE(NullObjectID.NULL_29043, new WorldPoint(2006, 4709, 1)), + NORTHERN_WALL_OF_CASTLE_DRAKAN(NullObjectID.NULL_29044, new WorldPoint(3563, 3379, 0)), + _7TH_CHAMBER_OF_JALSAVRAH(NullObjectID.NULL_29045, new WorldPoint(1951, 4431, 0)), + SOUL_ALTAR(NullObjectID.NULL_29046, new WorldPoint(1810, 3855, 0)), + WARRIORS_GUILD_BANK_29047(NullObjectID.NULL_29047, new WorldPoint(2845, 3545, 0)), + ENTRANA_CHAPEL(NullObjectID.NULL_29048, new WorldPoint(2851, 3355, 0)), + TZHAAR_GEM_STORE(NullObjectID.NULL_29049, new WorldPoint(2466, 5150, 0)), + TENT_IN_LORD_IORWERTHS_ENCAMPMENT(NullObjectID.NULL_29050, new WorldPoint(2198, 3257, 0)), + OUTSIDE_MUDKNUCKLES_HUT(NullObjectID.NULL_29051, new WorldPoint(2959, 3502, 0)), + CENTRE_OF_THE_CATACOMBS_OF_KOUREND(NullObjectID.NULL_29052, new WorldPoint(1661, 10045, 0)), + KING_BLACK_DRAGONS_LAIR(NullObjectID.NULL_29053, new WorldPoint(2286, 4680, 0)), + OUTSIDE_KRIL_TSUTSAROTHS_ROOM(NullObjectID.NULL_29054, new WorldPoint(2931, 5337, 2)), + BY_THE_BEAR_CAGE_IN_VARROCK_PALACE_GARDENS(NullObjectID.NULL_29055, new WorldPoint(3232, 3494, 0)), + OUTSIDE_THE_WILDERNESS_AXE_HUT(NullObjectID.NULL_29056, new WorldPoint(3186, 3958, 0)), + TOP_FLOOR_OF_THE_YANILLE_WATCHTOWER(NullObjectID.NULL_29057, new WorldPoint(2930, 4718, 2)), + DEATH_ALTAR(NullObjectID.NULL_29058, new WorldPoint(2210, 4842, 0)), + BEHIND_MISS_SCHISM_IN_DRAYNOR_VILLAGE(NullObjectID.NULL_29059, new WorldPoint(3095, 3254, 0)), + NORTHWESTERN_CORNER_OF_THE_ENCHANTED_VALLEY(NullObjectID.NULL_29060, new WorldPoint(3022, 4517, 0)), + NORTH_OF_MOUNT_KARUULM(NullObjectID.NULL_34647, new WorldPoint(1308, 3840, 0)), + GYPSY_TENT_ENTRANCE(NullObjectID.NULL_34736, new WorldPoint(3206, 3422, 0)), + FINE_CLOTHES_ENTRANCE(NullObjectID.NULL_34737, new WorldPoint(3209, 3416, 0)), + BOB_AXES_ENTRANCE(NullObjectID.NULL_34738, new WorldPoint(3233, 3200, 0)), + CRYSTALLINE_MAPLE_TREES(NullObjectID.NULL_34953, new WorldPoint(2213, 3427, 0)); + + private final int objectId; + private final WorldPoint[] worldPoints; + + STASHUnit(int objectId, WorldPoint... worldPoints) + { + this.objectId = objectId; + this.worldPoints = worldPoints; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdArea.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdArea.java new file mode 100644 index 0000000000..0835fba279 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdArea.java @@ -0,0 +1,47 @@ +/* + * 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.cluescrolls.clues.hotcold; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum HotColdArea +{ + ASGARNIA("Asgarnia"), + DESERT("Desert"), + FELDIP_HILLS("Feldip Hills"), + FREMENNIK_PROVINCE("Fremennik Province"), + KANDARIN("Kandarin"), + KARAMJA("Karamja"), + MISTHALIN("Misthalin"), + MORYTANIA("Morytania"), + WESTERN_PROVINCE("Western Province"), + WILDERNESS("Wilderness"), + ZEAH("Zeah"); + + private final String name; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java new file mode 100644 index 0000000000..b624e206a2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018, Eadgars Ruse + * Copyright (c) 2018, Adam + * Copyright (c) 2019, Jordan Atwood + * 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.cluescrolls.clues.hotcold; + +import java.awt.Rectangle; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.cluescrolls.clues.Enemy; +import static net.runelite.client.plugins.cluescrolls.clues.Enemy.*; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.ASGARNIA; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.DESERT; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.FELDIP_HILLS; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.FREMENNIK_PROVINCE; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.KANDARIN; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.KARAMJA; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.MISTHALIN; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.MORYTANIA; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.WESTERN_PROVINCE; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.WILDERNESS; +import static net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea.ZEAH; + +// The locations contains all hot/cold points and their descriptions according to the wiki +// these central points were obtained by checking wiki location pictures against a coordinate map +// some central points points may be slightly off-center +// calculations are done considering the 9x9 grid around the central point where the strange device shakes +// because the calculations consider the 9x9 grid, slightly off-center points should still be found by the calculations +@AllArgsConstructor +@Getter +public enum HotColdLocation +{ + ASGARNIA_WARRIORS(new WorldPoint(2860, 3562, 0), ASGARNIA, "North of the Warriors' Guild in Burthorpe.", BRASSICAN_MAGE), + ASGARNIA_JATIX(new WorldPoint(2915, 3425, 0), ASGARNIA, "East of Jatix's Herblore Shop in Taverley.", BRASSICAN_MAGE), + ASGARNIA_BARB(new WorldPoint(3033, 3438, 0), ASGARNIA, "West of Barbarian Village.", BRASSICAN_MAGE), + ASGARNIA_MIAZRQA(new WorldPoint(2972, 3486, 0), ASGARNIA, "North of Miazrqa's tower, outside Goblin Village.", BRASSICAN_MAGE), + ASGARNIA_COW(new WorldPoint(3031, 3304, 0), ASGARNIA, "In the cow pen north of Sarah's Farming Shop.", ANCIENT_WIZARDS), + ASGARNIA_PARTY_ROOM(new WorldPoint(3030, 3364, 0), ASGARNIA, "Outside the Falador Party Room.", BRASSICAN_MAGE), + ASGARNIA_CRAFT_GUILD(new WorldPoint(2917, 3295, 0), ASGARNIA, "Outside the Crafting Guild cow pen.", BRASSICAN_MAGE), + ASGARNIA_RIMMINGTON(new WorldPoint(2976, 3239, 0), ASGARNIA, "In the centre of the Rimmington mine.", BRASSICAN_MAGE), + ASGARNIA_MUDSKIPPER(new WorldPoint(2987, 3110, 0), ASGARNIA, "Mudskipper Point, near the starfish in the south-west corner.", BRASSICAN_MAGE), + ASGARNIA_TROLL(new WorldPoint(2910, 3616, 0), ASGARNIA, "The Troll arena, where the player fights Dad during the Troll Stronghold quest. Bring climbing boots if travelling from Burthorpe.", BRASSICAN_MAGE), + DESERT_GENIE(new WorldPoint(3359, 2912, 0), DESERT, "West of Nardah genie cave.", BRASSICAN_MAGE), + DESERT_ALKHARID_MINE(new WorldPoint(3279, 3263, 0), DESERT, "West of Al Kharid mine.", BRASSICAN_MAGE), + DESERT_MENAPHOS_GATE(new WorldPoint(3223, 2820, 0), DESERT, "North of Menaphos gate.", BRASSICAN_MAGE), + DESERT_BEDABIN_CAMP(new WorldPoint(3161, 3047, 0), DESERT, "Bedabin Camp, near the north tent.", BRASSICAN_MAGE), + DESERT_UZER(new WorldPoint(3432, 3105, 0), DESERT, "West of Uzer.", BRASSICAN_MAGE), + DESERT_POLLNIVNEACH(new WorldPoint(3288, 2976, 0), DESERT, "West of Pollnivneach.", BRASSICAN_MAGE), + DESERT_MTA(new WorldPoint(3347, 3295, 0), DESERT, "Next to Mage Training Arena.", BRASSICAN_MAGE), + DESERT_SHANTY(new WorldPoint(3292, 3107, 0), DESERT, "South-west of Shantay Pass.", BRASSICAN_MAGE), + DRAYNOR_MANOR_MUSHROOMS(new WorldPoint(3096, 3379, 0), MISTHALIN, "Patch of mushrooms just northwest of Draynor Manor"), + DRAYNOR_WHEAT_FIELD(new WorldPoint(3120, 3282, 0), MISTHALIN, "Inside the wheat field next to Draynor Village"), + FELDIP_HILLS_JIGGIG(new WorldPoint(2409, 3053, 0), FELDIP_HILLS, "West of Jiggig, east of the fairy ring bkp.", BRASSICAN_MAGE), + FELDIP_HILLS_SW(new WorldPoint(2586, 2897, 0), FELDIP_HILLS, "West of the southeasternmost lake in Feldip Hills.", BRASSICAN_MAGE), + FELDIP_HILLS_GNOME_GLITER(new WorldPoint(2555, 2972, 0), FELDIP_HILLS, "East of the gnome glider (Lemantolly Undri).", BRASSICAN_MAGE), + FELDIP_HILLS_RANTZ(new WorldPoint(2611, 2950, 0), FELDIP_HILLS, "South of Rantz, west of the empty glass bottles.", BRASSICAN_MAGE), + FELDIP_HILLS_SOUTH(new WorldPoint(2486, 3007, 0), FELDIP_HILLS, "South of Jiggig.", BRASSICAN_MAGE), + FELDIP_HILLS_RED_CHIN(new WorldPoint(2530, 2901, 0), FELDIP_HILLS, "Outside the red chinchompa hunting ground entrance, south of the Hunting expert's hut.", BRASSICAN_MAGE), + FELDIP_HILLS_SE(new WorldPoint(2569, 2918, 0), FELDIP_HILLS, "South-east of the ∩-shaped lake, near the Hunter icon.", BRASSICAN_MAGE), + FELDIP_HILLS_CW_BALLOON(new WorldPoint(2451, 3112, 0), FELDIP_HILLS, "Directly west of the Castle Wars balloon.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_MTN_CAMP(new WorldPoint(2800, 3669, 0), FREMENNIK_PROVINCE, "At the Mountain Camp.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_RELLEKKA_HUNTER(new WorldPoint(2720, 3784, 0), FREMENNIK_PROVINCE, "At the Rellekka Hunter area, near the Hunter icon.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_KELGADRIM_ENTRANCE(new WorldPoint(2711, 3689, 0), FREMENNIK_PROVINCE, "West of the Keldagrim entrance mine.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_SW(new WorldPoint(2604, 3648, 0), FREMENNIK_PROVINCE, "Outside the fence in the south-western corner of Rellekka.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_LIGHTHOUSE(new WorldPoint(2585, 3601, 0), FREMENNIK_PROVINCE, "South-east of the Lighthouse.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_ETCETERIA_CASTLE(new WorldPoint(2617, 3862, 0), FREMENNIK_PROVINCE, "South-east of Etceteria's castle.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_MISC_COURTYARD(new WorldPoint(2527, 3868, 0), FREMENNIK_PROVINCE, "Outside Miscellania's courtyard.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_FREMMY_ISLES_MINE(new WorldPoint(2374, 3850, 0), FREMENNIK_PROVINCE, "Central Fremennik Isles mine.", ANCIENT_WIZARDS), + FREMENNIK_PROVINCE_WEST_ISLES_MINE(new WorldPoint(2313, 3850, 0), FREMENNIK_PROVINCE, "West Fremennik Isles mine.", ANCIENT_WIZARDS), + FREMENNIK_PROVINCE_WEST_JATIZSO_ENTRANCE(new WorldPoint(2393, 3812, 0), FREMENNIK_PROVINCE, "West of the Jatizso mine entrance.", BRASSICAN_MAGE), + FREMENNIK_PROVINCE_PIRATES_COVE(new WorldPoint(2210, 3814, 0), FREMENNIK_PROVINCE, "Pirates' Cove", ANCIENT_WIZARDS), + FREMENNIK_PROVINCE_ASTRAL_ALTER(new WorldPoint(2149, 3865, 0), FREMENNIK_PROVINCE, "Astral altar", ANCIENT_WIZARDS), + FREMENNIK_PROVINCE_LUNAR_VILLAGE(new WorldPoint(2084, 3916, 0), FREMENNIK_PROVINCE, "Lunar Isle, inside the village.", ANCIENT_WIZARDS), + FREMENNIK_PROVINCE_LUNAR_NORTH(new WorldPoint(2106, 3949, 0), FREMENNIK_PROVINCE, "Lunar Isle, north of the village.", ANCIENT_WIZARDS), + ICE_MOUNTAIN(new WorldPoint(3007, 3475, 0), MISTHALIN, "Atop Ice Mountain"), + KANDARIN_SINCLAR_MANSION(new WorldPoint(2730, 3588, 0), KANDARIN, "North-west of the Sinclair Mansion, near the log balance shortcut.", BRASSICAN_MAGE), + KANDARIN_CATHERBY(new WorldPoint(2774, 3436, 0), KANDARIN, "Catherby, between the bank and the beehives, near small rock formation.", BRASSICAN_MAGE), + KANDARIN_GRAND_TREE(new WorldPoint(2448, 3503, 0), KANDARIN, "Grand Tree, just east of the terrorchick gnome enclosure.", BRASSICAN_MAGE), + KANDARIN_SEERS(new WorldPoint(2735, 3486, 0), KANDARIN, "Between the Seers' Village bank and Camelot.", BRASSICAN_MAGE), + KANDARIN_MCGRUBORS_WOOD(new WorldPoint(2653, 3485, 0), KANDARIN, "McGrubor's Wood", BRASSICAN_MAGE), + KANDARIN_FISHING_BUILD(new WorldPoint(2590, 3369, 0), KANDARIN, "South of Fishing Guild", BRASSICAN_MAGE), + KANDARIN_WITCHHAVEN(new WorldPoint(2707, 3306, 0), KANDARIN, "Outside Witchaven, west of Jeb, Holgart, and Caroline.", BRASSICAN_MAGE), + KANDARIN_NECRO_TOWER(new WorldPoint(2667, 3241, 0), KANDARIN, "Ground floor inside the Necromancer Tower. Easily accessed by using fairy ring code djp.", ANCIENT_WIZARDS), + KANDARIN_FIGHT_ARENA(new WorldPoint(2587, 3135, 0), KANDARIN, "South of the Fight Arena, north-west of the Nightmare Zone.", BRASSICAN_MAGE), + KANDARIN_TREE_GNOME_VILLAGE(new WorldPoint(2530, 3164, 0), KANDARIN, "Tree Gnome Village, near the general store icon.", BRASSICAN_MAGE), + KANDARIN_GRAVE_OF_SCORPIUS(new WorldPoint(2467, 3227, 0), KANDARIN, "Grave of Scorpius", BRASSICAN_MAGE), + KANDARIN_KHAZARD_BATTLEFIELD(new WorldPoint(2522, 3252, 0), KANDARIN, "Khazard Battlefield, south of Tracker gnome 2.", BRASSICAN_MAGE), + KANDARIN_WEST_ARDY(new WorldPoint(2535, 3322, 0), KANDARIN, "West Ardougne, near the staircase outside the Civic Office.", BRASSICAN_MAGE), + KANDARIN_SW_TREE_GNOME_STRONGHOLD(new WorldPoint(2411, 3429, 0), KANDARIN, "South-west Tree Gnome Stronghold", BRASSICAN_MAGE), + KANDARIN_OUTPOST(new WorldPoint(2457, 3362, 0), KANDARIN, "South of the Tree Gnome Stronghold, north-east of the Outpost.", BRASSICAN_MAGE), + KANDARIN_BAXTORIAN_FALLS(new WorldPoint(2530, 3477, 0), KANDARIN, "South-east of Almera's house on Baxtorian Falls.", BRASSICAN_MAGE), + KANDARIN_BA_AGILITY_COURSE(new WorldPoint(2540, 3548, 0), KANDARIN, "Inside the Barbarian Agility Course. Completion of Alfred Grimhand's Barcrawl is required.", BRASSICAN_MAGE), + KARAMJA_MUSA_POINT(new WorldPoint(2913, 3169, 0), KARAMJA, "Musa Point, banana plantation.", BRASSICAN_MAGE), + KARAMJA_BRIMHAVEN_FRUIT_TREE(new WorldPoint(2782, 3215, 0), KARAMJA, "Brimhaven, east of the fruit tree patch.", BRASSICAN_MAGE), + KARAMJA_WEST_BRIMHAVEN(new WorldPoint(2718, 3167, 0), KARAMJA, "West of Brimhaven.", BRASSICAN_MAGE), + KARAMJA_GLIDER(new WorldPoint(2966, 2975, 0), KARAMJA, "West of the gnome glider.", BRASSICAN_MAGE), + KARAMJA_KHARAZI_NE(new WorldPoint(2904, 2925, 0), KARAMJA, "North-eastern part of Kharazi Jungle.", BRASSICAN_MAGE), + KARAMJA_KHARAZI_SW(new WorldPoint(2786, 2899, 0), KARAMJA, "South-western part of Kharazi Jungle.", BRASSICAN_MAGE), + KARAMJA_CRASH_ISLAND(new WorldPoint(2909, 2737, 0), KARAMJA, "Northern part of Crash Island.", BRASSICAN_MAGE), + LUMBRIDGE_COW_FIELD( new WorldPoint(3174, 3336, 0), MISTHALIN, "Cow field north of Lumbridge"), + MISTHALIN_VARROCK_STONE_CIRCLE(new WorldPoint(3225, 3356, 0), MISTHALIN, "South of the stone circle near Varrock's entrance.", BRASSICAN_MAGE), + MISTHALIN_LUMBRIDGE(new WorldPoint(3234, 3169, 0), MISTHALIN, "Just north-west of the Lumbridge Fishing tutor.", BRASSICAN_MAGE), + MISTHALIN_LUMBRIDGE_2(new WorldPoint(3169, 3279, 0), MISTHALIN, "North of the pond between Lumbridge and Draynor Village.", BRASSICAN_MAGE), + MISTHALIN_GERTUDES(new WorldPoint(3154, 3421, 0), MISTHALIN, "North-east of Gertrude's house west of Varrock.", BRASSICAN_MAGE), + MISTHALIN_DRAYNOR_BANK(new WorldPoint(3098, 3234, 0), MISTHALIN, "South of Draynor Village bank.", BRASSICAN_MAGE), + MISTHALIN_LUMBER_YARD(new WorldPoint(3301, 3484, 0), MISTHALIN, "South of Lumber Yard, east of Assistant Serf.", BRASSICAN_MAGE), + MORYTANIA_BURGH_DE_ROTT(new WorldPoint(3546, 3252, 0), MORYTANIA, "In the north-east area of Burgh de Rott, by the reverse-L-shaped ruins.", BRASSICAN_MAGE), + MORYTANIA_DARKMEYER(new WorldPoint(3604, 3326, 0), MORYTANIA, "Southwestern part of Darkmeyer.", BRASSICAN_MAGE), + MORYTANIA_PORT_PHASMATYS(new WorldPoint(3611, 3485, 0), MORYTANIA, "West of Port Phasmatys, south-east of fairy ring.", BRASSICAN_MAGE), + MORYTANIA_HOLLOWS(new WorldPoint(3499, 3421, 0), MORYTANIA, "Inside The Hollows, south of the bridge which was repaired in a quest.", BRASSICAN_MAGE), + MORYTANIA_SWAMP(new WorldPoint(3418, 3372, 0), MORYTANIA, "Inside the Mort Myre Swamp, north-west of the Nature Grotto.", BRASSICAN_MAGE), + MORYTANIA_HAUNTED_MINE(new WorldPoint(3444, 3255, 0), MORYTANIA, "At Haunted Mine quest start.", BRASSICAN_MAGE), + MORYTANIA_MAUSOLEUM(new WorldPoint(3499, 3539, 0), MORYTANIA, "South of the Mausoleum.", BRASSICAN_MAGE), + MORYTANIA_MOS_LES_HARMLESS(new WorldPoint(3740, 3041, 0), MORYTANIA, "Northern area of Mos Le'Harmless, between the lakes.", BRASSICAN_MAGE), + MORYTANIA_MOS_LES_HARMLESS_BAR(new WorldPoint(3666, 2972, 0), MORYTANIA, "Near Mos Le'Harmless southern bar.", BRASSICAN_MAGE), + MORYTANIA_DRAGONTOOTH_NORTH(new WorldPoint(3811, 3569, 0), MORYTANIA, "Northern part of Dragontooth Island.", BRASSICAN_MAGE), + MORYTANIA_DRAGONTOOTH_SOUTH(new WorldPoint(3803, 3532, 0), MORYTANIA, "Southern part of Dragontooth Island.", BRASSICAN_MAGE), + MORYTANIA_SLEPE_TENTS(new WorldPoint(3769, 3383, 0), MORYTANIA, "North-east of Slepe, near the tents.", BRASSICAN_MAGE), + NORTHEAST_OF_AL_KHARID_MINE(new WorldPoint(3332, 3313, 0), MISTHALIN, "Northeast of Al Kharid Mine"), + WESTERN_PROVINCE_EAGLES_PEAK(new WorldPoint(2297, 3529, 0), WESTERN_PROVINCE, "North-west of Eagles' Peak.", BRASSICAN_MAGE), + WESTERN_PROVINCE_PISCATORIS(new WorldPoint(2334, 3685, 0), WESTERN_PROVINCE, "Piscatoris Fishing Colony", ANCIENT_WIZARDS), + WESTERN_PROVINCE_PISCATORIS_HUNTER_AREA(new WorldPoint(2359, 3564, 0), WESTERN_PROVINCE, "Eastern part of Piscatoris Hunter area, south-west of the Falconry.", BRASSICAN_MAGE), + WESTERN_PROVINCE_ARANDAR(new WorldPoint(2370, 3319, 0), WESTERN_PROVINCE, "South-west of the crystal gate to Arandar.", ANCIENT_WIZARDS), + WESTERN_PROVINCE_ELF_CAMP_EAST(new WorldPoint(2268, 3242, 0), WESTERN_PROVINCE, "East of Iorwerth Camp.", BRASSICAN_MAGE), + WESTERN_PROVINCE_ELF_CAMP_NW(new WorldPoint(2174, 3280, 0), WESTERN_PROVINCE, "North-west of Iorwerth Camp.", BRASSICAN_MAGE), + WESTERN_PROVINCE_LLETYA(new WorldPoint(2337, 3166, 0), WESTERN_PROVINCE, "In Lletya.", BRASSICAN_MAGE), + WESTERN_PROVINCE_TYRAS(new WorldPoint(2206, 3158, 0), WESTERN_PROVINCE, "Near Tyras Camp.", BRASSICAN_MAGE), + WESTERN_PROVINCE_ZULANDRA(new WorldPoint(2196, 3057, 0), WESTERN_PROVINCE, "The northern house at Zul-Andra.", BRASSICAN_MAGE), + WILDERNESS_5(new WorldPoint(3173, 3556, 0), WILDERNESS, "North of the Grand Exchange, level 5 Wilderness.", ANCIENT_WIZARDS), + WILDERNESS_12(new WorldPoint(3036, 3612, 0), WILDERNESS, "South-east of the Dark Warriors' Fortress, level 12 Wilderness.", ANCIENT_WIZARDS), + WILDERNESS_20(new WorldPoint(3222, 3679, 0), WILDERNESS, "East of the Corporeal Beast's lair, level 20 Wilderness.", ANCIENT_WIZARDS), + WILDERNESS_27(new WorldPoint(3174, 3736, 0), WILDERNESS, "Inside the Ruins north of the Graveyard of Shadows, level 27 Wilderness.", BRASSICAN_MAGE), + WILDERNESS_28(new WorldPoint(3377, 3737, 0), WILDERNESS, "East of Venenatis' nest, level 28 Wilderness.", BRASSICAN_MAGE), + WILDERNESS_32(new WorldPoint(3311, 3773, 0), WILDERNESS, "North of Venenatis' nest, level 32 Wilderness.", ANCIENT_WIZARDS), + WILDERNESS_35(new WorldPoint(3152, 3796, 0), WILDERNESS, "East of the Wilderness canoe exit, level 35 Wilderness.", BRASSICAN_OR_WIZARDS), + WILDERNESS_37(new WorldPoint(2974, 3814, 0), WILDERNESS, "South-east of the Chaos Temple, level 37 Wilderness.", BRASSICAN_MAGE), + WILDERNESS_38(new WorldPoint(3293, 3813, 0), WILDERNESS, "South of Callisto, level 38 Wilderness.", ANCIENT_WIZARDS), + WILDERNESS_49(new WorldPoint(3136, 3914, 0), WILDERNESS, "South-west of the Deserted Keep, level 49 Wilderness.", BRASSICAN_MAGE), + WILDERNESS_54(new WorldPoint(2981, 3944, 0), WILDERNESS, "West of the Wilderness Agility Course, level 54 Wilderness.", BRASSICAN_MAGE), + ZEAH_BLASTMINE_BANK(new WorldPoint(1504, 3859, 0), ZEAH, "Next to the bank in the Lovakengj blast mine.", BRASSICAN_MAGE), + ZEAH_BLASTMINE_NORTH(new WorldPoint(1488, 3881, 0), ZEAH, "Northern part of the Lovakengj blast mine.", BRASSICAN_MAGE), + ZEAH_LOVAKITE_FURNACE(new WorldPoint(1507, 3819, 0), ZEAH, "Next to the lovakite furnace in Lovakengj.", ANCIENT_WIZARDS), + ZEAH_LOVAKENGJ_MINE(new WorldPoint(1477, 3778, 0), ZEAH, "Next to mithril rock in the Lovakengj mine.", ANCIENT_WIZARDS), + ZEAH_SULPHR_MINE(new WorldPoint(1428, 3869, 0), ZEAH, "Western entrance in the Lovakengj sulphur mine.", BRASSICAN_MAGE), + ZEAH_SHAYZIEN_BANK(new WorldPoint(1517, 3603, 0), ZEAH, "South-east of the bank in Shayzien.", ANCIENT_WIZARDS), + ZEAH_OVERPASS(new WorldPoint(1467, 3714, 0), ZEAH, "Overpass between Lovakengj and Shayzien.", BRASSICAN_MAGE), + ZEAH_LIZARDMAN(new WorldPoint(1490, 3698, 0), ZEAH, "Within Lizardman Canyon, east of the ladder. Requires 5% favour with Shayzien.", ANCIENT_WIZARDS), + ZEAH_COMBAT_RING(new WorldPoint(1559, 3582, 0), ZEAH, "Shayzien, south-east of the Combat Ring.", BRASSICAN_MAGE), + ZEAH_SHAYZIEN_BANK_2(new WorldPoint(1491, 3623, 0), ZEAH, "North-west of the bank in Shayzien.", ANCIENT_WIZARDS), + ZEAH_LIBRARY(new WorldPoint(1603, 3843, 0), ZEAH, "North-west of the Arceuus Library.", BRASSICAN_MAGE), + ZEAH_HOUSECHURCH(new WorldPoint(1682, 3792, 0), ZEAH, "By the entrance to the Arceuus church.", ANCIENT_WIZARDS), + ZEAH_DARK_ALTAR(new WorldPoint(1698, 3881, 0), ZEAH, "West of the Dark Altar.", BRASSICAN_MAGE), + ZEAH_ARCEUUS_HOUSE(new WorldPoint(1710, 3700, 0), ZEAH, "By the southern entrance to Arceuus.", ANCIENT_WIZARDS), + ZEAH_ESSENCE_MINE(new WorldPoint(1762, 3852, 0), ZEAH, "By the Arceuus essence mine.", BRASSICAN_MAGE), + ZEAH_ESSENCE_MINE_NE(new WorldPoint(1773, 3867, 0), ZEAH, "North-east of the Arceuus essence mine.", BRASSICAN_MAGE), + ZEAH_PISCARILUS_MINE(new WorldPoint(1768, 3705, 0), ZEAH, "South of the Piscarilius mine.", ANCIENT_WIZARDS), + ZEAH_GOLDEN_FIELD_TAVERN(new WorldPoint(1718, 3643, 0), ZEAH, "South of the gravestone in Kingstown.", BRASSICAN_MAGE), + ZEAH_MESS_HALL(new WorldPoint(1656, 3621, 0), ZEAH, "East of the Mess hall.", ANCIENT_WIZARDS), + ZEAH_WATSONS_HOUSE(new WorldPoint(1653, 3573, 0), ZEAH, "East of Watson's house.", ANCIENT_WIZARDS), + ZEAH_VANNAHS_FARM_STORE(new WorldPoint(1807, 3523, 0), ZEAH, "North of Tithe Farm, next to the pond.", BRASSICAN_MAGE), + ZEAH_FARMING_GUILD_W(new WorldPoint(1208, 3736, 0), ZEAH, "West of the Farming Guild.", BRASSICAN_MAGE), + ZEAH_DAIRY_COW(new WorldPoint(1324, 3722, 0), ZEAH, "North-east of the Kebos Lowlands, east of the dairy cow.", BRASSICAN_MAGE), + ZEAH_CRIMSON_SWIFTS(new WorldPoint(1187, 3580, 0), ZEAH, "South-west of the Kebos Swamp, below the crimson swifts.", BRASSICAN_MAGE); + + private final boolean beginnerClue; + private final WorldPoint worldPoint; + private final HotColdArea hotColdArea; + private final String area; + private final Enemy enemy; + + HotColdLocation(WorldPoint worldPoint, HotColdArea hotColdArea, String areaDescription, Enemy enemy) + { + this(false, worldPoint, hotColdArea, areaDescription, enemy); + } + + HotColdLocation(WorldPoint worldPoint, HotColdArea hotColdArea, String areaDescription) + { + //only master clues have enemies, so if no enemy it is a beginner clue + this(true, worldPoint, hotColdArea, areaDescription, null); + } + + public Rectangle getRect() + { + final int digRadius = beginnerClue ? HotColdTemperature.BEGINNER_VISIBLY_SHAKING.getMaxDistance() : + HotColdTemperature.MASTER_VISIBLY_SHAKING.getMaxDistance(); + return new Rectangle(worldPoint.getX() - digRadius, worldPoint.getY() - digRadius, digRadius * 2 + 1, digRadius * 2 + 1); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdSolver.java new file mode 100644 index 0000000000..daa57e2bf7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdSolver.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018, Eadgars Ruse + * Copyright (c) 2019, Jordan Atwood + * 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.cluescrolls.clues.hotcold; + +import com.google.common.annotations.VisibleForTesting; +import java.awt.Rectangle; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.Getter; +import net.runelite.api.coords.WorldPoint; + +/** + * Solution finder for hot-cold style puzzles. + *

+ * These puzzles are established by having some way to test the distance from the solution via "warmth", where being + * colder means one is farther away from the target, and being warmer means one is closer to it, with the goal being to + * reach the most warm value to discover the solution point. Hot-cold puzzles in Old School RuneScape are implemented + * with specific set of solution points, so this solver will filter from a provided set of possible solutions as new + * signals of temperatures and temperature changes are provided. + */ +@Getter +public class HotColdSolver +{ + private final Set possibleLocations; + @Nullable + private WorldPoint lastWorldPoint; + + public HotColdSolver(Set possibleLocations) + { + this.possibleLocations = possibleLocations; + } + + /** + * Process a hot-cold update given a {@link WorldPoint} where a check occurred and the resulting temperature and + * temperature change discovered at that point. This will filter the set of possible locations which can be the + * solution. + * + * @param worldPoint The point where a hot-cold check occurred + * @param temperature The temperature of the checked point + * @param temperatureChange The change of temperature of the checked point compared to the previously-checked point + * @return A set of {@link HotColdLocation}s which are still possible after the filtering occurs. This return value + * is the same as would be returned by {@code getPossibleLocations()}. + */ + public Set signal(@Nonnull final WorldPoint worldPoint, @Nonnull final HotColdTemperature temperature, @Nullable final HotColdTemperatureChange temperatureChange) + { + // when the strange device reads a temperature, that means that the center of the final dig location + // is a range of squares away from the player's current location (Chebyshev AKA Chess-board distance) + int maxSquaresAway = temperature.getMaxDistance(); + int minSquaresAway = temperature.getMinDistance(); + + // maxDistanceArea encompasses all of the points that are within the max possible distance from the player + final Rectangle maxDistanceArea = new Rectangle( + worldPoint.getX() - maxSquaresAway, + worldPoint.getY() - maxSquaresAway, + 2 * maxSquaresAway + 1, + 2 * maxSquaresAway + 1); + // minDistanceArea encompasses all of the points that are within the min possible distance from the player + final Rectangle minDistanceArea = new Rectangle( + worldPoint.getX() - minSquaresAway, + worldPoint.getY() - minSquaresAway, + 2 * minSquaresAway + 1, + 2 * minSquaresAway + 1); + + // eliminate from consideration dig spots that lie entirely within the min range or entirely outside of the max range + possibleLocations.removeIf(entry -> minDistanceArea.contains(entry.getRect()) || !maxDistanceArea.intersects(entry.getRect())); + + // if a previous world point has been recorded, we can consider the warmer/colder result from the strange device + if (lastWorldPoint != null && temperatureChange != null) + { + switch (temperatureChange) + { + case COLDER: + // eliminate spots that are absolutely warmer + possibleLocations.removeIf(entry -> isFirstPointCloserRect(worldPoint, lastWorldPoint, entry.getRect())); + break; + case WARMER: + // eliminate spots that are absolutely colder + possibleLocations.removeIf(entry -> isFirstPointCloserRect(lastWorldPoint, worldPoint, entry.getRect())); + break; + case SAME: + // eliminate spots which are absolutely colder or warmer (as they would not yield a SAME temperature change) + possibleLocations.removeIf(entry -> + isFirstPointCloserRect(worldPoint, lastWorldPoint, entry.getRect()) + || isFirstPointCloserRect(lastWorldPoint, worldPoint, entry.getRect())); + } + } + + lastWorldPoint = worldPoint; + return getPossibleLocations(); + } + + /** + * Determines whether the first point passed is closer to each corner of the given rectangle than the second point. + * + * @param firstPoint First point to test. Return result will be relating to this point's location. + * @param secondPoint Second point to test + * @param rect Rectangle, whose corner points will be compared to the first and second points passed + * @return {@code true} if {@code firstPoint} is closer to each of {@code rect}'s four corner points than + * {@code secondPoint}, {@code false} otherwise. + * @see WorldPoint#distanceTo2D + */ + @VisibleForTesting + static boolean isFirstPointCloserRect(final WorldPoint firstPoint, final WorldPoint secondPoint, final Rectangle rect) + { + final WorldPoint nePoint = new WorldPoint((rect.x + rect.width), (rect.y + rect.height), 0); + + if (!isFirstPointCloser(firstPoint, secondPoint, nePoint)) + { + return false; + } + + final WorldPoint sePoint = new WorldPoint((rect.x + rect.width), rect.y, 0); + + if (!isFirstPointCloser(firstPoint, secondPoint, sePoint)) + { + return false; + } + + final WorldPoint nwPoint = new WorldPoint(rect.x, (rect.y + rect.height), 0); + + if (!isFirstPointCloser(firstPoint, secondPoint, nwPoint)) + { + return false; + } + + final WorldPoint swPoint = new WorldPoint(rect.x, rect.y, 0); + return (isFirstPointCloser(firstPoint, secondPoint, swPoint)); + } + + /** + * Determines whether the first point passed is closer to the given point of comparison than the second point. + * + * @param firstPoint First point to test. Return result will be relating to this point's location. + * @param secondPoint Second point to test + * @param worldPoint Point to compare to the first and second points passed + * @return {@code true} if {@code firstPoint} is closer to {@code worldPoint} than {@code secondPoint}, + * {@code false} otherwise. + * @see WorldPoint#distanceTo2D + */ + @VisibleForTesting + static boolean isFirstPointCloser(final WorldPoint firstPoint, final WorldPoint secondPoint, final WorldPoint worldPoint) + { + return firstPoint.distanceTo2D(worldPoint) < secondPoint.distanceTo2D(worldPoint); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperature.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperature.java new file mode 100644 index 0000000000..21171eec8d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperature.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019, Jordan Atwood + * 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.cluescrolls.clues.hotcold; + +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.client.util.Text; + +@AllArgsConstructor +@Getter +public enum HotColdTemperature +{ + ICE_COLD("ice cold", 500, 5000), + VERY_COLD("very cold", 200, 499), + COLD("cold", 150, 199), + WARM("warm", 100, 149), + HOT("hot", 70, 99), + VERY_HOT("very hot", 30, 69), + BEGINNER_INCREDIBLY_HOT("incredibly hot", 4, 29), + BEGINNER_VISIBLY_SHAKING("visibly shaking", 0, 3), + MASTER_INCREDIBLY_HOT("incredibly hot", 5, 29), + MASTER_VISIBLY_SHAKING("visibly shaking", 0, 4); + + public static final Set BEGINNER_HOT_COLD_TEMPERATURES = Sets.immutableEnumSet( + ICE_COLD, + VERY_COLD, + COLD, + WARM, + HOT, + VERY_HOT, + BEGINNER_INCREDIBLY_HOT, + BEGINNER_VISIBLY_SHAKING + ); + public static final Set MASTER_HOT_COLD_TEMPERATURES = Sets.immutableEnumSet( + ICE_COLD, + VERY_COLD, + COLD, + WARM, + HOT, + VERY_HOT, + MASTER_INCREDIBLY_HOT, + MASTER_VISIBLY_SHAKING + ); + + private final String text; + private final int minDistance; + private final int maxDistance; + + private static final String DEVICE_USED_START_TEXT = "The device is "; + + /** + * Gets the temperature from a set of temperatures corresponding to the passed string. + * + * @param temperatureSet A set of temperature values to select from + * @param message A string containing a temperature value + * @return The corresponding enum from the given temperature set. + *

+ * Note that in cases where two temperature values in the given set are equally likely to be the given + * temperature (say, two temperatures with identical text values), the behavior is undefined. + */ + @Nullable + public static HotColdTemperature getFromTemperatureSet(final Set temperatureSet, final String message) + { + if (!message.startsWith(DEVICE_USED_START_TEXT) || temperatureSet == null) + { + return null; + } + + final String messageStart = Text.fromCSV(message).get(0); + final List possibleTemperatures = new ArrayList<>(); + + for (final HotColdTemperature temperature : temperatureSet) + { + if (messageStart.contains(temperature.getText())) + { + possibleTemperatures.add(temperature); + } + } + + return possibleTemperatures.stream() + // For messages such as "The device is very cold", this will choose the Enum with text of greatest length so + // that VERY_COLD would be selected over COLD, though both Enums have matching text for this message. + .max(Comparator.comparingInt(x -> (x.getText()).length())) + .orElse(null); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperatureChange.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperatureChange.java new file mode 100644 index 0000000000..e9077bc9e1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdTemperatureChange.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019, Jordan Atwood + * 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.cluescrolls.clues.hotcold; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum HotColdTemperatureChange +{ + WARMER("and warmer than"), + SAME("and the same temperature as"), + COLDER("but colder than"); + + private final String text; + + public static HotColdTemperatureChange of(final String message) + { + if (!message.endsWith(" last time.")) + { + return null; + } + + for (final HotColdTemperatureChange change : values()) + { + if (message.contains(change.text)) + { + return change; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AllRequirementsCollection.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AllRequirementsCollection.java new file mode 100644 index 0000000000..a9187bb5a0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AllRequirementsCollection.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; + +public class AllRequirementsCollection implements ItemRequirement +{ + private String name; + private ItemRequirement[] requirements; + + public AllRequirementsCollection(String name, ItemRequirement... requirements) + { + this.name = name; + this.requirements = requirements; + } + + public AllRequirementsCollection(ItemRequirement... requirements) + { + this("N/A", requirements); + } + + @Override + public boolean fulfilledBy(int itemId) + { + for (ItemRequirement requirement : requirements) + { + if (requirement.fulfilledBy(itemId)) + { + return true; + } + } + + return false; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + for (ItemRequirement requirement : requirements) + { + if (!requirement.fulfilledBy(items)) + { + return false; + } + } + + return true; + } + + @Override + public String getCollectiveName(Client client) + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AnyRequirementCollection.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AnyRequirementCollection.java new file mode 100644 index 0000000000..ae223285c5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/AnyRequirementCollection.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; + +public class AnyRequirementCollection implements ItemRequirement +{ + private final String name; + private final ItemRequirement[] requirements; + + public AnyRequirementCollection(String name, ItemRequirement... requirements) + { + this.name = name; + this.requirements = requirements; + } + + @Override + public boolean fulfilledBy(int itemId) + { + for (ItemRequirement requirement : requirements) + { + if (requirement.fulfilledBy(itemId)) + { + return true; + } + } + + return false; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + for (ItemRequirement requirement : requirements) + { + if (requirement.fulfilledBy(items)) + { + return true; + } + } + + return false; + } + + @Override + public String getCollectiveName(Client client) + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirement.java new file mode 100644 index 0000000000..0f55c90c28 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirement.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; + +public interface ItemRequirement +{ + boolean fulfilledBy(int itemId); + + boolean fulfilledBy(Item[] items); + + String getCollectiveName(Client client); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirements.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirements.java new file mode 100644 index 0000000000..c97fd5cf97 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/ItemRequirements.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.EquipmentInventorySlot; + +public class ItemRequirements +{ + public static SingleItemRequirement item(int itemId) + { + return new SingleItemRequirement(itemId); + } + + public static RangeItemRequirement range(int startItemId, int endItemId) + { + return range(null, startItemId, endItemId); + } + + public static RangeItemRequirement range(String name, int startItemId, int endItemId) + { + return new RangeItemRequirement(name, startItemId, endItemId); + } + + public static AnyRequirementCollection any(String name, ItemRequirement... requirements) + { + return new AnyRequirementCollection(name, requirements); + } + + public static AllRequirementsCollection all(ItemRequirement... requirements) + { + return new AllRequirementsCollection(requirements); + } + + public static AllRequirementsCollection all(String name, ItemRequirement... requirements) + { + return new AllRequirementsCollection(name, requirements); + } + + public static SlotLimitationRequirement emptySlot(String description, EquipmentInventorySlot... slots) + { + return new SlotLimitationRequirement(description, slots); + } + + public static MultipleOfItemRequirement xOfItem(int itemId, int quantity) + { + return new MultipleOfItemRequirement(itemId, quantity); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/MultipleOfItemRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/MultipleOfItemRequirement.java new file mode 100644 index 0000000000..88432a4ca3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/MultipleOfItemRequirement.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Hydrox6 + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; + +public class MultipleOfItemRequirement implements ItemRequirement +{ + private final int itemId; + private final int quantity; + + public MultipleOfItemRequirement(int itemId, int quantity) + { + this.itemId = itemId; + this.quantity = quantity; + } + + @Override + public boolean fulfilledBy(int itemId) + { + return itemId == this.itemId && this.quantity == 1; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + int quantityFound = 0; + for (Item item : items) + { + if (item.getId() == itemId) + { + quantityFound += item.getQuantity(); + if (quantityFound >= quantity) + { + return true; + } + } + } + + return false; + } + + @Override + public String getCollectiveName(Client client) + { + ItemComposition definition = client.getItemDefinition(itemId); + + if (definition == null) + { + return "N/A"; + } + + return definition.getName() + " x" + this.quantity; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/RangeItemRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/RangeItemRequirement.java new file mode 100644 index 0000000000..ef81ced371 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/RangeItemRequirement.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; + +public class RangeItemRequirement implements ItemRequirement +{ + private final String name; + private final int startItemId; + private final int endItemId; + + public RangeItemRequirement(String name, int startItemId, int endItemId) + { + this.name = name; + this.startItemId = startItemId; + this.endItemId = endItemId; + } + + @Override + public boolean fulfilledBy(int itemId) + { + return itemId >= startItemId && itemId <= endItemId; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + for (Item item : items) + { + if (item.getId() >= startItemId && item.getId() <= endItemId) + { + return true; + } + } + + return false; + } + + @Override + public String getCollectiveName(Client client) + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SingleItemRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SingleItemRequirement.java new file mode 100644 index 0000000000..1571e004af --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SingleItemRequirement.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; + +public class SingleItemRequirement implements ItemRequirement +{ + private final int itemId; + + public SingleItemRequirement(int itemId) + { + this.itemId = itemId; + } + + @Override + public boolean fulfilledBy(int itemId) + { + return this.itemId == itemId; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + for (Item item : items) + { + if (item.getId() == itemId) + { + return true; + } + } + + return false; + } + + @Override + public String getCollectiveName(Client client) + { + ItemComposition definition = client.getItemDefinition(itemId); + + if (definition == null) + { + return "N/A"; + } + + return definition.getName(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SlotLimitationRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SlotLimitationRequirement.java new file mode 100644 index 0000000000..f52f0ee19d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/item/SlotLimitationRequirement.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018, Lotto + * 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.cluescrolls.clues.item; + +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.Item; + +public class SlotLimitationRequirement implements ItemRequirement +{ + private final String description; + private final EquipmentInventorySlot[] slots; + + public SlotLimitationRequirement(String description, EquipmentInventorySlot... slots) + { + this.description = description; + this.slots = slots; + } + + @Override + public boolean fulfilledBy(int itemId) + { + return false; + } + + @Override + public boolean fulfilledBy(Item[] items) + { + for (EquipmentInventorySlot slot : slots) + { + if (slot.getSlotIdx() >= items.length) + { + continue; //We can't check the slot, because there is nothing in it, the array hasn't been resized + } + + if (items[slot.getSlotIdx()].getId() != -1) + { + return false; + } + } + + return true; + } + + @Override + public String getCollectiveName(Client client) + { + return description; + } +} 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 new file mode 100644 index 0000000000..63c5af1624 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLUtil.java @@ -0,0 +1,136 @@ +/* + * 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.gpu; + +import com.jogamp.opengl.GL4; +import java.nio.charset.StandardCharsets; + +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) + { + gl.glGetIntegerv(pname, buf, 0); + 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); + assert buf[0] > -1; + return buf[0]; + } + + static int glGetProgram(GL4 gl, int program, int pname) + { + gl.glGetProgramiv(program, pname, buf, 0); + assert buf[0] > -1; + return buf[0]; + } + + static String glGetShaderInfoLog(GL4 gl, int shader) + { + byte[] err = new byte[ERR_LEN]; + gl.glGetShaderInfoLog(shader, ERR_LEN, buf, 0, err, 0); + return new String(err, 0, buf[0], StandardCharsets.UTF_8); + } + + static String glGetProgramInfoLog(GL4 gl, int program) + { + byte[] err = new byte[ERR_LEN]; + gl.glGetProgramInfoLog(program, ERR_LEN, buf, 0, err, 0); + return new String(err, 0, buf[0], StandardCharsets.UTF_8); + } + + static int glGenVertexArrays(GL4 gl) + { + gl.glGenVertexArrays(1, buf, 0); + return buf[0]; + } + + static void glDeleteVertexArrays(GL4 gl, int vertexArray) + { + buf[0] = vertexArray; + gl.glDeleteVertexArrays(1, buf, 0); + } + + static int glGenBuffers(GL4 gl) + { + gl.glGenBuffers(1, buf, 0); + return buf[0]; + } + + static void glDeleteBuffer(GL4 gl, int buffer) + { + buf[0] = buffer; + gl.glDeleteBuffers(1, buf, 0); + } + + static int glGenTexture(GL4 gl) + { + gl.glGenTextures(1, buf, 0); + return buf[0]; + } + + static void glDeleteTexture(GL4 gl, int texture) + { + buf[0] = texture; + gl.glDeleteTextures(1, buf, 0); + } + + static int glGenFrameBuffer(GL4 gl) + { + gl.glGenFramebuffers(1, buf, 0); + return buf[0]; + } + + static void glDeleteFrameBuffer(GL4 gl, int frameBuffer) + { + buf[0] = frameBuffer; + gl.glDeleteFramebuffers(1, buf, 0); + } + + static int glGenRenderbuffer(GL4 gl) + { + gl.glGenRenderbuffers(1, buf, 0); + return buf[0]; + } + + static void glDeleteRenderbuffers(GL4 gl, int renderBuffer) + { + buf[0] = renderBuffer; + gl.glDeleteRenderbuffers(1, buf, 0); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuFloatBuffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuFloatBuffer.java new file mode 100644 index 0000000000..5e5bbb529e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuFloatBuffer.java @@ -0,0 +1,80 @@ +/* + * 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.gpu; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +class GpuFloatBuffer +{ + private FloatBuffer buffer = allocateDirect(65536); + + void put(float texture, float u, float v, float pad) + { + buffer.put(texture).put(u).put(v).put(pad); + } + + void flip() + { + buffer.flip(); + } + + void clear() + { + buffer.clear(); + } + + void ensureCapacity(int size) + { + int capacity = buffer.capacity(); + final int position = buffer.position(); + if ((capacity - position) < size) + { + do + { + capacity *= 2; + } + while ((capacity - position) < size); + + FloatBuffer newB = allocateDirect(capacity); + buffer.flip(); + newB.put(buffer); + buffer = newB; + } + } + + FloatBuffer getBuffer() + { + return buffer; + } + + static FloatBuffer allocateDirect(int size) + { + return ByteBuffer.allocateDirect(size * Float.BYTES) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java new file mode 100644 index 0000000000..1e85111c19 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuIntBuffer.java @@ -0,0 +1,85 @@ +/* + * 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.gpu; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; + +class GpuIntBuffer +{ + private IntBuffer buffer = allocateDirect(65536); + + void put(int x, int y, int z) + { + buffer.put(x).put(y).put(z); + } + + void put(int x, int y, int z, int c) + { + buffer.put(x).put(y).put(z).put(c); + } + + void flip() + { + buffer.flip(); + } + + void clear() + { + buffer.clear(); + } + + void ensureCapacity(int size) + { + int capacity = buffer.capacity(); + final int position = buffer.position(); + if ((capacity - position) < size) + { + do + { + capacity *= 2; + } + while ((capacity - position) < size); + + IntBuffer newB = allocateDirect(capacity); + buffer.flip(); + newB.put(buffer); + buffer = newB; + } + } + + IntBuffer getBuffer() + { + return buffer; + } + + static IntBuffer allocateDirect(int size) + { + return ByteBuffer.allocateDirect(size * Integer.BYTES) + .order(ByteOrder.nativeOrder()) + .asIntBuffer(); + } +} 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 new file mode 100644 index 0000000000..67b2c1bcc1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -0,0 +1,1634 @@ +/* + * 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.gpu; + +import com.google.common.primitives.Ints; +import com.google.inject.Provides; +import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration; +import com.jogamp.nativewindow.awt.JAWTWindow; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL4; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLContext; +import com.jogamp.opengl.GLDrawable; +import com.jogamp.opengl.GLDrawableFactory; +import com.jogamp.opengl.GLException; +import com.jogamp.opengl.GLFBODrawable; +import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.math.Matrix4; +import java.awt.Canvas; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import jogamp.nativewindow.SurfaceScaleUtils; +import jogamp.nativewindow.jawt.x11.X11JAWTWindow; +import jogamp.nativewindow.macosx.OSXUtil; +import jogamp.newt.awt.NewtFactoryAWT; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.BufferProvider; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Model; +import net.runelite.api.NodeCache; +import net.runelite.api.Perspective; +import net.runelite.api.Renderable; +import net.runelite.api.Scene; +import net.runelite.api.SceneTileModel; +import net.runelite.api.SceneTilePaint; +import net.runelite.api.Texture; +import net.runelite.api.TextureProvider; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.hooks.DrawCallbacks; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.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 net.runelite.client.plugins.gpu.config.AntiAliasingMode; +import net.runelite.client.plugins.gpu.config.UIScalingMode; +import net.runelite.client.plugins.gpu.template.Template; +import net.runelite.client.ui.DrawManager; +import net.runelite.client.util.OSType; + +@PluginDescriptor( + name = "GPU", + description = "Utilizes the GPU", + enabledByDefault = false, + tags = {"fog", "draw distance"}, + loadInSafeMode = false +) +@Slf4j +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; + private static final int FLAG_SCENE_BUFFER = Integer.MIN_VALUE; + private static final int DEFAULT_DISTANCE = 25; + static final int MAX_DISTANCE = 90; + static final int MAX_FOG_DEPTH = 100; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private GpuPluginConfig config; + + @Inject + private TextureManager textureManager; + + @Inject + private SceneUploader sceneUploader; + + @Inject + private DrawManager drawManager; + + @Inject + private PluginManager pluginManager; + + private boolean useComputeShaders; + + private Canvas canvas; + private JAWTWindow jawtWindow; + private GL4 gl; + private GLContext glContext; + private GLDrawable glDrawable; + + static final String LINUX_VERSION_HEADER = + "#version 420\n" + + "#extension GL_ARB_compute_shader : require\n" + + "#extension GL_ARB_shader_storage_buffer_object : require\n" + + "#extension GL_ARB_explicit_attrib_location : require\n"; + static final String WINDOWS_VERSION_HEADER = "#version 430\n"; + + static final Shader PROGRAM = new Shader() + .add(GL4.GL_VERTEX_SHADER, "vert.glsl") + .add(GL4.GL_FRAGMENT_SHADER, "frag.glsl"); + + static final Shader COMPUTE_PROGRAM = new Shader() + .add(GL4.GL_COMPUTE_SHADER, "comp.glsl"); + + static final Shader SMALL_COMPUTE_PROGRAM = new Shader() + .add(GL4.GL_COMPUTE_SHADER, "comp_small.glsl"); + + static final Shader UNORDERED_COMPUTE_PROGRAM = new Shader() + .add(GL4.GL_COMPUTE_SHADER, "comp_unordered.glsl"); + + static final Shader UI_PROGRAM = new Shader() + .add(GL4.GL_VERTEX_SHADER, "vertui.glsl") + .add(GL4.GL_FRAGMENT_SHADER, "fragui.glsl"); + + private int glProgram; + private int glComputeProgram; + private int glSmallComputeProgram; + private int glUnorderedComputeProgram; + private int glUiProgram; + + private int vaoHandle; + + private int interfaceTexture; + + private int vaoUiHandle; + private int vboUiHandle; + + private int fboSceneHandle; + private int texSceneHandle; + private int rboSceneHandle; + + // scene vertex buffer id + private int bufferId; + // scene uv buffer id + private int uvBufferId; + + private int tmpBufferId; // temporary scene vertex buffer + private int tmpUvBufferId; // temporary scene uv buffer + private int tmpModelBufferId; // scene model buffer, large + private int tmpModelBufferSmallId; // scene model buffer, small + private int tmpModelBufferUnorderedId; + private int tmpOutBufferId; // target vertex buffer for compute shaders + private int tmpOutUvBufferId; // target uv buffer for compute shaders + + private int textureArrayId; + + private int uniformBufferId; + private final IntBuffer uniformBuffer = GpuIntBuffer.allocateDirect(5 + 3 + 2048 * 4); + private final float[] textureOffsets = new float[128]; + + private GpuIntBuffer vertexBuffer; + private GpuFloatBuffer uvBuffer; + + private GpuIntBuffer modelBufferUnordered; + private GpuIntBuffer modelBufferSmall; + private GpuIntBuffer modelBuffer; + + private int unorderedModels; + + /** + * number of models in small buffer + */ + private int smallModels; + + /** + * number of models in large buffer + */ + private int largeModels; + + /** + * offset in the target buffer for model + */ + private int targetBufferOffset; + + /** + * offset into the temporary scene vertex buffer + */ + private int tempOffset; + + /** + * offset into the temporary scene uv buffer + */ + private int tempUvOffset; + + private int lastCanvasWidth; + private int lastCanvasHeight; + private int lastStretchedCanvasWidth; + private int lastStretchedCanvasHeight; + private AntiAliasingMode lastAntiAliasingMode; + private int lastAnisotropicFilteringLevel = -1; + + private int centerX; + private int centerY; + private int yaw; + private int pitch; + // fields for non-compute draw + private boolean drawingModel; + private int modelX, modelY, modelZ; + private int modelOrientation; + + // Uniforms + private int uniColorBlindMode; + private int uniUiColorBlindMode; + private int uniUseFog; + private int uniFogColor; + private int uniFogDepth; + private int uniDrawDistance; + private int uniProjectionMatrix; + private int uniBrightness; + private int uniTex; + private int uniTexSamplingMode; + private int uniTexSourceDimensions; + private int uniTexTargetDimensions; + private int uniTextures; + private int uniTextureOffsets; + private int uniBlockSmall; + private int uniBlockLarge; + private int uniBlockMain; + private int uniSmoothBanding; + + @Override + protected void startUp() + { + clientThread.invoke(() -> + { + try + { + bufferId = uvBufferId = uniformBufferId = tmpBufferId = tmpUvBufferId = tmpModelBufferId = tmpModelBufferSmallId = tmpModelBufferUnorderedId = tmpOutBufferId = tmpOutUvBufferId = -1; + texSceneHandle = fboSceneHandle = rboSceneHandle = -1; // AA FBO + unorderedModels = smallModels = largeModels = 0; + drawingModel = false; + + canvas = client.getCanvas(); + + if (!canvas.isDisplayable()) + { + return false; + } + + // OSX supports up to OpenGL 4.1, however 4.3 is required for compute shaders + useComputeShaders = config.useComputeShaders() && OSType.getOSType() != OSType.MacOS; + + canvas.setIgnoreRepaint(true); + + vertexBuffer = new GpuIntBuffer(); + uvBuffer = new GpuFloatBuffer(); + + modelBufferUnordered = new GpuIntBuffer(); + modelBufferSmall = new GpuIntBuffer(); + modelBuffer = new GpuIntBuffer(); + + if (log.isDebugEnabled()) + { + System.setProperty("jogl.debug", "true"); + } + + GLProfile.initSingleton(); + + invokeOnMainThread(() -> + { + GLProfile glProfile = GLProfile.get(GLProfile.GL4); + + GLCapabilities glCaps = new GLCapabilities(glProfile); + AWTGraphicsConfiguration config = AWTGraphicsConfiguration.create(canvas.getGraphicsConfiguration(), glCaps, glCaps); + + jawtWindow = NewtFactoryAWT.getNativeWindow(canvas, config); + canvas.setFocusable(true); + + GLDrawableFactory glDrawableFactory = GLDrawableFactory.getFactory(glProfile); + + jawtWindow.lockSurface(); + try + { + glDrawable = glDrawableFactory.createGLDrawable(jawtWindow); + glDrawable.setRealized(true); + + glContext = glDrawable.createContext(null); + if (log.isDebugEnabled()) + { + // Debug config on context needs to be set before .makeCurrent call + glContext.enableGLDebugMessage(true); + } + } + finally + { + jawtWindow.unlockSurface(); + } + + int res = glContext.makeCurrent(); + if (res == GLContext.CONTEXT_NOT_CURRENT) + { + throw new GLException("Unable to make context current"); + } + + // Surface needs to be unlocked on X11 window otherwise input is blocked + if (jawtWindow instanceof X11JAWTWindow && jawtWindow.getLock().isLocked()) + { + jawtWindow.unlockSurface(); + } + + this.gl = glContext.getGL().getGL4(); + gl.setSwapInterval(0); + + if (log.isDebugEnabled()) + { + gl.glEnable(gl.GL_DEBUG_OUTPUT); + + // Suppress warning messages which flood the log on NVIDIA systems. + gl.getContext().glDebugMessageControl(gl.GL_DEBUG_SOURCE_API, gl.GL_DEBUG_TYPE_OTHER, + gl.GL_DEBUG_SEVERITY_NOTIFICATION, 0, null, 0, false); + } + + initVao(); + try + { + initProgram(); + } + catch (ShaderException ex) + { + throw new RuntimeException(ex); + } + initInterfaceTexture(); + initUniformBuffer(); + initBuffers(); + }); + + client.setDrawCallbacks(this); + client.setGpu(true); + + // force rebuild of main buffer provider to enable alpha channel + client.resizeCanvas(); + + lastCanvasWidth = lastCanvasHeight = -1; + lastStretchedCanvasWidth = lastStretchedCanvasHeight = -1; + lastAntiAliasingMode = null; + + textureArrayId = -1; + + // increase size of model cache for dynamic objects since we are extending scene size + NodeCache cachedModels2 = client.getCachedModels2(); + cachedModels2.setCapacity(256); + cachedModels2.setRemainingCapacity(256); + cachedModels2.reset(); + + if (client.getGameState() == GameState.LOGGED_IN) + { + uploadScene(); + } + } + catch (Throwable e) + { + log.error("Error starting GPU plugin", e); + + SwingUtilities.invokeLater(() -> + { + try + { + pluginManager.setPluginEnabled(this, false); + pluginManager.stopPlugin(this); + } + catch (PluginInstantiationException ex) + { + log.error("error stopping plugin", ex); + } + }); + + shutDown(); + } + return true; + }); + } + + @Override + protected void shutDown() + { + clientThread.invoke(() -> + { + client.setGpu(false); + client.setDrawCallbacks(null); + + invokeOnMainThread(() -> + { + if (gl != null) + { + if (textureArrayId != -1) + { + textureManager.freeTextureArray(gl, textureArrayId); + textureArrayId = -1; + } + + if (uniformBufferId != -1) + { + glDeleteBuffer(gl, uniformBufferId); + uniformBufferId = -1; + } + + shutdownBuffers(); + shutdownInterfaceTexture(); + shutdownProgram(); + shutdownVao(); + shutdownAAFbo(); + } + + if (jawtWindow != null) + { + if (!jawtWindow.getLock().isLocked()) + { + jawtWindow.lockSurface(); + } + + if (glContext != null) + { + glContext.destroy(); + } + + // this crashes on osx when the plugin is turned back on, don't know why + // we'll just leak the window... + if (OSType.getOSType() != OSType.MacOS) + { + NewtFactoryAWT.destroyNativeWindow(jawtWindow); + } + } + }); + + GLProfile.shutdown(); + + jawtWindow = null; + gl = null; + glDrawable = null; + glContext = null; + + vertexBuffer = null; + uvBuffer = null; + + modelBufferSmall = null; + modelBuffer = null; + modelBufferUnordered = null; + + lastAnisotropicFilteringLevel = -1; + + // force main buffer provider rebuild to turn off alpha channel + client.resizeCanvas(); + }); + } + + @Provides + GpuPluginConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(GpuPluginConfig.class); + } + + private void initProgram() throws ShaderException + { + String versionHeader = OSType.getOSType() == OSType.Linux ? LINUX_VERSION_HEADER : WINDOWS_VERSION_HEADER; + Template template = new Template(); + template.add(key -> + { + if ("version_header".equals(key)) + { + return versionHeader; + } + return null; + }); + template.addInclude(GpuPlugin.class); + + glProgram = PROGRAM.compile(gl, template); + glUiProgram = UI_PROGRAM.compile(gl, template); + + if (useComputeShaders) + { + glComputeProgram = COMPUTE_PROGRAM.compile(gl, template); + glSmallComputeProgram = SMALL_COMPUTE_PROGRAM.compile(gl, template); + glUnorderedComputeProgram = UNORDERED_COMPUTE_PROGRAM.compile(gl, template); + } + + initUniforms(); + } + + private void initUniforms() + { + uniProjectionMatrix = gl.glGetUniformLocation(glProgram, "projectionMatrix"); + uniBrightness = gl.glGetUniformLocation(glProgram, "brightness"); + uniSmoothBanding = gl.glGetUniformLocation(glProgram, "smoothBanding"); + uniUseFog = gl.glGetUniformLocation(glProgram, "useFog"); + uniFogColor = gl.glGetUniformLocation(glProgram, "fogColor"); + uniFogDepth = gl.glGetUniformLocation(glProgram, "fogDepth"); + uniDrawDistance = gl.glGetUniformLocation(glProgram, "drawDistance"); + uniColorBlindMode = gl.glGetUniformLocation(glProgram, "colorBlindMode"); + + uniTex = gl.glGetUniformLocation(glUiProgram, "tex"); + uniTexSamplingMode = gl.glGetUniformLocation(glUiProgram, "samplingMode"); + uniTexTargetDimensions = gl.glGetUniformLocation(glUiProgram, "targetDimensions"); + uniTexSourceDimensions = gl.glGetUniformLocation(glUiProgram, "sourceDimensions"); + uniUiColorBlindMode = gl.glGetUniformLocation(glUiProgram, "colorBlindMode"); + uniTextures = gl.glGetUniformLocation(glProgram, "textures"); + uniTextureOffsets = gl.glGetUniformLocation(glProgram, "textureOffsets"); + + uniBlockSmall = gl.glGetUniformBlockIndex(glSmallComputeProgram, "uniforms"); + uniBlockLarge = gl.glGetUniformBlockIndex(glComputeProgram, "uniforms"); + uniBlockMain = gl.glGetUniformBlockIndex(glProgram, "uniforms"); + } + + private void shutdownProgram() + { + gl.glDeleteProgram(glProgram); + glProgram = -1; + + gl.glDeleteProgram(glComputeProgram); + glComputeProgram = -1; + + gl.glDeleteProgram(glSmallComputeProgram); + glSmallComputeProgram = -1; + + gl.glDeleteProgram(glUnorderedComputeProgram); + glUnorderedComputeProgram = -1; + + gl.glDeleteProgram(glUiProgram); + glUiProgram = -1; + } + + private void initVao() + { + // Create VAO + vaoHandle = glGenVertexArrays(gl); + + // Create UI VAO + vaoUiHandle = glGenVertexArrays(gl); + // Create UI buffer + vboUiHandle = glGenBuffers(gl); + gl.glBindVertexArray(vaoUiHandle); + + FloatBuffer vboUiBuf = GpuFloatBuffer.allocateDirect(5 * 4); + vboUiBuf.put(new float[]{ + // positions // texture coords + 1f, 1f, 0.0f, 1.0f, 0f, // top right + 1f, -1f, 0.0f, 1.0f, 1f, // bottom right + -1f, -1f, 0.0f, 0.0f, 1f, // bottom left + -1f, 1f, 0.0f, 0.0f, 0f // top left + }); + vboUiBuf.rewind(); + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vboUiHandle); + gl.glBufferData(gl.GL_ARRAY_BUFFER, vboUiBuf.capacity() * Float.BYTES, vboUiBuf, gl.GL_STATIC_DRAW); + + // position attribute + gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, false, 5 * Float.BYTES, 0); + gl.glEnableVertexAttribArray(0); + + // texture coord attribute + gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, false, 5 * Float.BYTES, 3 * Float.BYTES); + gl.glEnableVertexAttribArray(1); + + // unbind VBO + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0); + } + + private void shutdownVao() + { + glDeleteVertexArrays(gl, vaoHandle); + vaoHandle = -1; + + glDeleteBuffer(gl, vboUiHandle); + vboUiHandle = -1; + + glDeleteVertexArrays(gl, vaoUiHandle); + vaoUiHandle = -1; + } + + private void initBuffers() + { + bufferId = glGenBuffers(gl); + uvBufferId = glGenBuffers(gl); + tmpBufferId = glGenBuffers(gl); + tmpUvBufferId = glGenBuffers(gl); + tmpModelBufferId = glGenBuffers(gl); + tmpModelBufferSmallId = glGenBuffers(gl); + tmpModelBufferUnorderedId = glGenBuffers(gl); + tmpOutBufferId = glGenBuffers(gl); + tmpOutUvBufferId = glGenBuffers(gl); + } + + private void shutdownBuffers() + { + if (bufferId != -1) + { + glDeleteBuffer(gl, bufferId); + bufferId = -1; + } + + if (uvBufferId != -1) + { + glDeleteBuffer(gl, uvBufferId); + uvBufferId = -1; + } + + if (tmpBufferId != -1) + { + glDeleteBuffer(gl, tmpBufferId); + tmpBufferId = -1; + } + + if (tmpUvBufferId != -1) + { + glDeleteBuffer(gl, tmpUvBufferId); + tmpUvBufferId = -1; + } + + if (tmpModelBufferId != -1) + { + glDeleteBuffer(gl, tmpModelBufferId); + tmpModelBufferId = -1; + } + + if (tmpModelBufferSmallId != -1) + { + glDeleteBuffer(gl, tmpModelBufferSmallId); + tmpModelBufferSmallId = -1; + } + + if (tmpModelBufferUnorderedId != -1) + { + glDeleteBuffer(gl, tmpModelBufferUnorderedId); + tmpModelBufferUnorderedId = -1; + } + + if (tmpOutBufferId != -1) + { + glDeleteBuffer(gl, tmpOutBufferId); + tmpOutBufferId = -1; + } + + if (tmpOutUvBufferId != -1) + { + glDeleteBuffer(gl, tmpOutUvBufferId); + tmpOutUvBufferId = -1; + } + } + + private void initInterfaceTexture() + { + interfaceTexture = glGenTexture(gl); + gl.glBindTexture(gl.GL_TEXTURE_2D, interfaceTexture); + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR); + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR); + gl.glBindTexture(gl.GL_TEXTURE_2D, 0); + } + + private void shutdownInterfaceTexture() + { + glDeleteTexture(gl, interfaceTexture); + interfaceTexture = -1; + } + + private void initUniformBuffer() + { + uniformBufferId = glGenBuffers(gl); + gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, uniformBufferId); + uniformBuffer.clear(); + uniformBuffer.put(new int[8]); + final int[] pad = new int[2]; + for (int i = 0; i < 2048; i++) + { + uniformBuffer.put(Perspective.SINE[i]); + uniformBuffer.put(Perspective.COSINE[i]); + uniformBuffer.put(pad); + } + uniformBuffer.flip(); + + gl.glBufferData(gl.GL_UNIFORM_BUFFER, uniformBuffer.limit() * Integer.BYTES, uniformBuffer, gl.GL_DYNAMIC_DRAW); + gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, 0); + } + + private void initAAFbo(int width, int height, int aaSamples) + { + // Create and bind the FBO + fboSceneHandle = glGenFrameBuffer(gl); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, fboSceneHandle); + + // Create color render buffer + rboSceneHandle = glGenRenderbuffer(gl); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, rboSceneHandle); + gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, aaSamples, gl.GL_RGBA, width, height); + gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_RENDERBUFFER, rboSceneHandle); + + // Create texture + texSceneHandle = glGenTexture(gl); + gl.glBindTexture(gl.GL_TEXTURE_2D_MULTISAMPLE, texSceneHandle); + gl.glTexImage2DMultisample(gl.GL_TEXTURE_2D_MULTISAMPLE, aaSamples, gl.GL_RGBA, width, height, true); + + // Bind texture + gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D_MULTISAMPLE, texSceneHandle, 0); + + // Reset + gl.glBindTexture(gl.GL_TEXTURE_2D_MULTISAMPLE, 0); + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, 0); + } + + private void shutdownAAFbo() + { + if (texSceneHandle != -1) + { + glDeleteTexture(gl, texSceneHandle); + texSceneHandle = -1; + } + + if (fboSceneHandle != -1) + { + glDeleteFrameBuffer(gl, fboSceneHandle); + fboSceneHandle = -1; + } + + if (rboSceneHandle != -1) + { + glDeleteRenderbuffers(gl, rboSceneHandle); + rboSceneHandle = -1; + } + } + + @Override + public void drawScene(int cameraX, int cameraY, int cameraZ, int cameraPitch, int cameraYaw, int plane) + { + centerX = client.getCenterX(); + centerY = client.getCenterY(); + yaw = client.getCameraYaw(); + pitch = client.getCameraPitch(); + + final Scene scene = client.getScene(); + scene.setDrawDistance(getDrawDistance()); + } + + @Override + public void drawScenePaint(int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, + SceneTilePaint paint, int tileZ, int tileX, int tileY, + int zoom, int centerX, int centerY) + { + if (!useComputeShaders) + { + targetBufferOffset += sceneUploader.upload(paint, + tileZ, tileX, tileY, + vertexBuffer, uvBuffer, + Perspective.LOCAL_TILE_SIZE * tileX, + Perspective.LOCAL_TILE_SIZE * tileY, + true + ); + } + else if (paint.getBufferLen() > 0) + { + final int localX = tileX * Perspective.LOCAL_TILE_SIZE; + final int localY = 0; + final int localZ = tileY * Perspective.LOCAL_TILE_SIZE; + + GpuIntBuffer b = modelBufferUnordered; + ++unorderedModels; + + b.ensureCapacity(8); + IntBuffer buffer = b.getBuffer(); + buffer.put(paint.getBufferOffset()); + buffer.put(paint.getUvBufferOffset()); + buffer.put(2); + buffer.put(targetBufferOffset); + buffer.put(FLAG_SCENE_BUFFER); + buffer.put(localX).put(localY).put(localZ); + + targetBufferOffset += 2 * 3; + } + } + + @Override + public void drawSceneModel(int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, + SceneTileModel model, int tileZ, int tileX, int tileY, + int zoom, int centerX, int centerY) + { + if (!useComputeShaders) + { + targetBufferOffset += sceneUploader.upload(model, + tileX, tileY, + vertexBuffer, uvBuffer, + tileX << Perspective.LOCAL_COORD_BITS, tileY << Perspective.LOCAL_COORD_BITS, true); + } + else if (model.getBufferLen() > 0) + { + final int localX = tileX * Perspective.LOCAL_TILE_SIZE; + final int localY = 0; + final int localZ = tileY * Perspective.LOCAL_TILE_SIZE; + + GpuIntBuffer b = modelBufferUnordered; + ++unorderedModels; + + b.ensureCapacity(8); + IntBuffer buffer = b.getBuffer(); + buffer.put(model.getBufferOffset()); + buffer.put(model.getUvBufferOffset()); + buffer.put(model.getBufferLen() / 3); + buffer.put(targetBufferOffset); + buffer.put(FLAG_SCENE_BUFFER); + buffer.put(localX).put(localY).put(localZ); + + targetBufferOffset += model.getBufferLen(); + } + } + + @Override + public void draw() + { + invokeOnMainThread(this::drawFrame); + } + + private void resize(int canvasWidth, int canvasHeight, int viewportWidth, int viewportHeight) + { + if (canvasWidth != lastCanvasWidth || canvasHeight != lastCanvasHeight) + { + lastCanvasWidth = canvasWidth; + lastCanvasHeight = canvasHeight; + + gl.glBindTexture(gl.GL_TEXTURE_2D, interfaceTexture); + gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, canvasWidth, canvasHeight, 0, gl.GL_BGRA, gl.GL_UNSIGNED_INT_8_8_8_8_REV, null); + gl.glBindTexture(gl.GL_TEXTURE_2D, 0); + + if (OSType.getOSType() == OSType.MacOS && glDrawable instanceof GLFBODrawable) + { + // GLDrawables created with createGLDrawable() do not have a resize listener + // I don't know why this works with Windows/Linux, but on OSX + // it prevents JOGL from resizing its FBOs and underlying GL textures. So, + // we manually trigger a resize here. + GLFBODrawable glfboDrawable = (GLFBODrawable) glDrawable; + glfboDrawable.resetSize(gl); + } + } + } + + private void drawFrame() + { + if (jawtWindow.getAWTComponent() != client.getCanvas()) + { + // We inject code in the game engine mixin to prevent the client from doing canvas replacement, + // so this should not ever be hit + log.warn("Canvas invalidated!"); + shutDown(); + startUp(); + return; + } + + if (client.getGameState() == GameState.LOADING || client.getGameState() == GameState.HOPPING) + { + // While the client is loading it doesn't draw + return; + } + + final int canvasHeight = client.getCanvasHeight(); + final int canvasWidth = client.getCanvasWidth(); + + final int viewportHeight = client.getViewportHeight(); + final int viewportWidth = client.getViewportWidth(); + + resize(canvasWidth, canvasHeight, viewportWidth, viewportHeight); + + // Setup anti-aliasing + final AntiAliasingMode antiAliasingMode = config.antiAliasingMode(); + final boolean aaEnabled = antiAliasingMode != AntiAliasingMode.DISABLED; + + if (aaEnabled) + { + gl.glEnable(gl.GL_MULTISAMPLE); + + final Dimension stretchedDimensions = client.getStretchedDimensions(); + + final int stretchedCanvasWidth = client.isStretchedEnabled() ? stretchedDimensions.width : canvasWidth; + final int stretchedCanvasHeight = client.isStretchedEnabled() ? stretchedDimensions.height : canvasHeight; + + // Re-create fbo + if (lastStretchedCanvasWidth != stretchedCanvasWidth + || lastStretchedCanvasHeight != stretchedCanvasHeight + || lastAntiAliasingMode != antiAliasingMode) + { + shutdownAAFbo(); + + final int maxSamples = glGetInteger(gl, gl.GL_MAX_SAMPLES); + final int samples = Math.min(antiAliasingMode.getSamples(), maxSamples); + + initAAFbo(stretchedCanvasWidth, stretchedCanvasHeight, samples); + + lastStretchedCanvasWidth = stretchedCanvasWidth; + lastStretchedCanvasHeight = stretchedCanvasHeight; + } + + gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, fboSceneHandle); + } + else + { + gl.glDisable(gl.GL_MULTISAMPLE); + shutdownAAFbo(); + } + + lastAntiAliasingMode = antiAliasingMode; + + // Clear scene + int sky = client.getSkyboxColor(); + gl.glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); + gl.glClear(gl.GL_COLOR_BUFFER_BIT); + + // Upload buffers + vertexBuffer.flip(); + uvBuffer.flip(); + modelBuffer.flip(); + modelBufferSmall.flip(); + modelBufferUnordered.flip(); + + IntBuffer vertexBuffer = this.vertexBuffer.getBuffer(); + FloatBuffer uvBuffer = this.uvBuffer.getBuffer(); + IntBuffer modelBuffer = this.modelBuffer.getBuffer(); + IntBuffer modelBufferSmall = this.modelBufferSmall.getBuffer(); + IntBuffer modelBufferUnordered = this.modelBufferUnordered.getBuffer(); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, gl.GL_DYNAMIC_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpUvBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, gl.GL_DYNAMIC_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBuffer.limit() * Integer.BYTES, modelBuffer, gl.GL_DYNAMIC_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferSmallId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBufferSmall.limit() * Integer.BYTES, modelBufferSmall, gl.GL_DYNAMIC_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferUnorderedId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBufferUnordered.limit() * Integer.BYTES, modelBufferUnordered, gl.GL_DYNAMIC_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpOutBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, + targetBufferOffset * 16, // each vertex is an ivec4, which is 16 bytes + null, + gl.GL_STREAM_DRAW); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpOutUvBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, + targetBufferOffset * 16, + null, + gl.GL_STREAM_DRAW); + + // UBO. Only the first 32 bytes get modified here, the rest is the constant sin/cos table. + gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, uniformBufferId); + uniformBuffer.clear(); + uniformBuffer + .put(yaw) + .put(pitch) + .put(centerX) + .put(centerY) + .put(client.getScale()) + .put(client.getCameraX2()) + .put(client.getCameraY2()) + .put(client.getCameraZ2()); + uniformBuffer.flip(); + + gl.glBufferSubData(gl.GL_UNIFORM_BUFFER, 0, uniformBuffer.limit() * Integer.BYTES, uniformBuffer); + gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, 0); + + gl.glBindBufferBase(gl.GL_UNIFORM_BUFFER, 0, uniformBufferId); + + // Draw 3d scene + final TextureProvider textureProvider = client.getTextureProvider(); + if (textureProvider != null) + { + if (useComputeShaders) + { + gl.glUniformBlockBinding(glSmallComputeProgram, uniBlockSmall, 0); + gl.glUniformBlockBinding(glComputeProgram, uniBlockLarge, 0); + + /* + * Compute is split into two separate programs 'small' and 'large' to + * save on GPU resources. Small will sort <= 512 faces, large will do <= 4096. + */ + + // unordered + gl.glUseProgram(glUnorderedComputeProgram); + + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferUnorderedId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + + gl.glDispatchCompute(unorderedModels, 1, 1); + + // small + gl.glUseProgram(glSmallComputeProgram); + + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferSmallId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + + gl.glDispatchCompute(smallModels, 1, 1); + + // large + gl.glUseProgram(glComputeProgram); + + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + + gl.glDispatchCompute(largeModels, 1, 1); + + gl.glMemoryBarrier(gl.GL_SHADER_STORAGE_BARRIER_BIT); + } + + if (textureArrayId == -1) + { + // lazy init textures as they may not be loaded at plugin start. + // this will return -1 and retry if not all textures are loaded yet, too. + textureArrayId = textureManager.initTextureArray(textureProvider, gl); + } + + final Texture[] textures = textureProvider.getTextures(); + int renderHeightOff = client.getViewportYOffset(); + int renderWidthOff = client.getViewportXOffset(); + int renderCanvasHeight = canvasHeight; + int renderViewportHeight = viewportHeight; + int renderViewportWidth = viewportWidth; + + // Setup anisotropic filtering + final int anisotropicFilteringLevel = config.anisotropicFilteringLevel(); + + if (textureArrayId != -1 && lastAnisotropicFilteringLevel != anisotropicFilteringLevel) + { + textureManager.setAnisotropicFilteringLevel(textureArrayId, anisotropicFilteringLevel, gl); + lastAnisotropicFilteringLevel = anisotropicFilteringLevel; + } + + if (client.isStretchedEnabled()) + { + Dimension dim = client.getStretchedDimensions(); + renderCanvasHeight = dim.height; + + double scaleFactorY = dim.getHeight() / canvasHeight; + double scaleFactorX = dim.getWidth() / canvasWidth; + + // Pad the viewport a little because having ints for our viewport dimensions can introduce off-by-one errors. + final int padding = 1; + + // Ceil the sizes because even if the size is 599.1 we want to treat it as size 600 (i.e. render to the x=599 pixel). + renderViewportHeight = (int) Math.ceil(scaleFactorY * (renderViewportHeight)) + padding * 2; + renderViewportWidth = (int) Math.ceil(scaleFactorX * (renderViewportWidth )) + padding * 2; + + // Floor the offsets because even if the offset is 4.9, we want to render to the x=4 pixel anyway. + renderHeightOff = (int) Math.floor(scaleFactorY * (renderHeightOff)) - padding; + renderWidthOff = (int) Math.floor(scaleFactorX * (renderWidthOff )) - padding; + } + + glDpiAwareViewport(renderWidthOff, renderCanvasHeight - renderViewportHeight - renderHeightOff, renderViewportWidth, renderViewportHeight); + + gl.glUseProgram(glProgram); + + final int drawDistance = getDrawDistance(); + final int fogDepth = config.fogDepth(); + 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.glUniform1i(uniDrawDistance, drawDistance * Perspective.LOCAL_TILE_SIZE); + + // Brightness happens to also be stored in the texture provider, so we use that + gl.glUniform1f(uniBrightness, (float) textureProvider.getBrightness()); + gl.glUniform1f(uniSmoothBanding, config.smoothBanding() ? 0f : 1f); + gl.glUniform1i(uniColorBlindMode, config.colorBlindMode().ordinal()); + + // Calculate projection matrix + Matrix4 projectionMatrix = new Matrix4(); + projectionMatrix.scale(client.getScale(), client.getScale(), 1); + projectionMatrix.multMatrix(makeProjectionMatrix(viewportWidth, viewportHeight, 50)); + projectionMatrix.rotate((float) (Math.PI - pitch * Perspective.UNIT), -1, 0, 0); + projectionMatrix.rotate((float) (yaw * Perspective.UNIT), 0, 1, 0); + projectionMatrix.translate(-client.getCameraX2(), -client.getCameraY2(), -client.getCameraZ2()); + gl.glUniformMatrix4fv(uniProjectionMatrix, 1, false, projectionMatrix.getMatrix(), 0); + + for (int id = 0; id < textures.length; ++id) + { + Texture texture = textures[id]; + if (texture == null) + { + continue; + } + + textureProvider.load(id); // trips the texture load flag which lets textures animate + + textureOffsets[id * 2] = texture.getU(); + textureOffsets[id * 2 + 1] = texture.getV(); + } + + // Bind uniforms + gl.glUniformBlockBinding(glProgram, uniBlockMain, 0); + gl.glUniform1i(uniTextures, 1); // texture sampler array is bound to texture1 + gl.glUniform2fv(uniTextureOffsets, 128, textureOffsets, 0); + + // We just allow the GL to do face culling. Note this requires the priority renderer + // to have logic to disregard culled faces in the priority depth testing. + gl.glEnable(gl.GL_CULL_FACE); + + // Enable blending for alpha + gl.glEnable(gl.GL_BLEND); + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA); + + // Draw buffers + gl.glBindVertexArray(vaoHandle); + + // When using compute shaders, draw using the output buffer of the compute. Otherwise + // only use the temporary buffers, which will contain the full scene. + gl.glEnableVertexAttribArray(0); + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, useComputeShaders ? tmpOutBufferId : tmpBufferId); + gl.glVertexAttribIPointer(0, 4, gl.GL_INT, 0, 0); + + gl.glEnableVertexAttribArray(1); + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, useComputeShaders ? tmpOutUvBufferId : tmpUvBufferId); + gl.glVertexAttribPointer(1, 4, gl.GL_FLOAT, false, 0, 0); + + gl.glDrawArrays(gl.GL_TRIANGLES, 0, targetBufferOffset); + + gl.glDisable(gl.GL_BLEND); + gl.glDisable(gl.GL_CULL_FACE); + + gl.glUseProgram(0); + } + + if (aaEnabled) + { + gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, fboSceneHandle); + gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, 0); + gl.glBlitFramebuffer(0, 0, lastStretchedCanvasWidth, lastStretchedCanvasHeight, + 0, 0, lastStretchedCanvasWidth, lastStretchedCanvasHeight, + gl.GL_COLOR_BUFFER_BIT, gl.GL_NEAREST); + + // Reset + gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, 0); + } + + vertexBuffer.clear(); + uvBuffer.clear(); + modelBuffer.clear(); + modelBufferSmall.clear(); + modelBufferUnordered.clear(); + + targetBufferOffset = 0; + smallModels = largeModels = unorderedModels = 0; + tempOffset = 0; + tempUvOffset = 0; + + // Texture on UI + drawUi(canvasHeight, canvasWidth); + + glDrawable.swapBuffers(); + + drawManager.processDrawComplete(this::screenshot); + } + + private float[] makeProjectionMatrix(float w, float h, float n) + { + return new float[] + { + 2 / w, 0, 0, 0, + 0, 2 / h, 0, 0, + 0, 0, -1, -1, + 0, 0, -2 * n, 0 + }; + } + + private void drawUi(final int canvasHeight, final int canvasWidth) + { + final BufferProvider bufferProvider = client.getBufferProvider(); + final int[] pixels = bufferProvider.getPixels(); + final int width = bufferProvider.getWidth(); + final int height = bufferProvider.getHeight(); + + gl.glEnable(gl.GL_BLEND); + + vertexBuffer.clear(); // reuse vertex buffer for interface + vertexBuffer.ensureCapacity(pixels.length); + + IntBuffer interfaceBuffer = vertexBuffer.getBuffer(); + interfaceBuffer.put(pixels); + vertexBuffer.flip(); + + gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA); + gl.glBindTexture(gl.GL_TEXTURE_2D, interfaceTexture); + + gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, width, height, gl.GL_BGRA, gl.GL_UNSIGNED_INT_8_8_8_8_REV, interfaceBuffer); + + // Use the texture bound in the first pass + final UIScalingMode uiScalingMode = config.uiScalingMode(); + gl.glUseProgram(glUiProgram); + gl.glUniform1i(uniTex, 0); + gl.glUniform1i(uniTexSamplingMode, uiScalingMode.getMode()); + gl.glUniform2i(uniTexSourceDimensions, canvasWidth, canvasHeight); + gl.glUniform1i(uniUiColorBlindMode, config.colorBlindMode().ordinal()); + + if (client.isStretchedEnabled()) + { + Dimension dim = client.getStretchedDimensions(); + glDpiAwareViewport(0, 0, dim.width, dim.height); + gl.glUniform2i(uniTexTargetDimensions, dim.width, dim.height); + } + else + { + glDpiAwareViewport(0, 0, canvasWidth, canvasHeight); + gl.glUniform2i(uniTexTargetDimensions, canvasWidth, canvasHeight); + } + + // Set the sampling function used when stretching the UI. + // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. + // See https://www.khronos.org/opengl/wiki/Sampler_Object for details. + if (client.isStretchedEnabled()) + { + // GL_NEAREST makes sampling for bicubic/xBR simpler, so it should be used whenever linear isn't + final int function = uiScalingMode == UIScalingMode.LINEAR ? gl.GL_LINEAR : gl.GL_NEAREST; + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, function); + gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, function); + } + + // Texture on UI + gl.glBindVertexArray(vaoUiHandle); + gl.glDrawArrays(gl.GL_TRIANGLE_FAN, 0, 4); + + // Reset + gl.glBindTexture(gl.GL_TEXTURE_2D, 0); + gl.glBindVertexArray(0); + gl.glUseProgram(0); + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA); + gl.glDisable(gl.GL_BLEND); + + vertexBuffer.clear(); + } + + /** + * Convert the front framebuffer to an Image + * + * @return + */ + private Image screenshot() + { + int width = client.getCanvasWidth(); + int height = client.getCanvasHeight(); + + if (client.isStretchedEnabled()) + { + Dimension dim = client.getStretchedDimensions(); + width = dim.width; + height = dim.height; + } + + if (OSType.getOSType() != OSType.MacOS) + { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final AffineTransform t = graphics.getTransform(); + width = getScaledValue(t.getScaleX(), width); + height = getScaledValue(t.getScaleY(), height); + graphics.dispose(); + } + + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4) + .order(ByteOrder.nativeOrder()); + + gl.glReadBuffer(gl.GL_FRONT); + gl.glReadPixels(0, 0, width, height, GL.GL_RGBA, gl.GL_UNSIGNED_BYTE, buffer); + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = buffer.get() & 0xff; + int g = buffer.get() & 0xff; + int b = buffer.get() & 0xff; + buffer.get(); // alpha + + pixels[(height - y - 1) * width + x] = (r << 16) | (g << 8) | b; + } + } + + return image; + } + + @Override + public void animate(Texture texture, int diff) + { + textureManager.animate(texture, diff); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (!useComputeShaders || gameStateChanged.getGameState() != GameState.LOGGED_IN) + { + return; + } + + uploadScene(); + } + + private void uploadScene() + { + vertexBuffer.clear(); + uvBuffer.clear(); + + sceneUploader.upload(client.getScene(), vertexBuffer, uvBuffer); + + vertexBuffer.flip(); + uvBuffer.flip(); + + IntBuffer vertexBuffer = this.vertexBuffer.getBuffer(); + FloatBuffer uvBuffer = this.uvBuffer.getBuffer(); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, bufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, gl.GL_STATIC_COPY); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, uvBufferId); + gl.glBufferData(gl.GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, gl.GL_STATIC_COPY); + + gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0); + + vertexBuffer.clear(); + uvBuffer.clear(); + } + + /** + * Check is a model is visible and should be drawn. + */ + private boolean isVisible(Model model, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int _x, int _y, int _z, long hash) + { + final int XYZMag = model.getXYZMag(); + final int zoom = client.get3dZoom(); + final int modelHeight = model.getModelHeight(); + + int Rasterizer3D_clipMidX2 = client.getRasterizer3D_clipMidX2(); + int Rasterizer3D_clipNegativeMidX = client.getRasterizer3D_clipNegativeMidX(); + int Rasterizer3D_clipNegativeMidY = client.getRasterizer3D_clipNegativeMidY(); + int Rasterizer3D_clipMidY2 = client.getRasterizer3D_clipMidY2(); + + int var11 = yawCos * _z - yawSin * _x >> 16; + int var12 = pitchSin * _y + pitchCos * var11 >> 16; + int var13 = pitchCos * XYZMag >> 16; + int var14 = var12 + var13; + if (var14 > 50) + { + int var15 = _z * yawSin + yawCos * _x >> 16; + int var16 = (var15 - XYZMag) * zoom; + if (var16 / var14 < Rasterizer3D_clipMidX2) + { + int var17 = (var15 + XYZMag) * zoom; + if (var17 / var14 > Rasterizer3D_clipNegativeMidX) + { + int var18 = pitchCos * _y - var11 * pitchSin >> 16; + int var19 = pitchSin * XYZMag >> 16; + int var20 = (var18 + var19) * zoom; + if (var20 / var14 > Rasterizer3D_clipNegativeMidY) + { + int var21 = (pitchCos * modelHeight >> 16) + var19; + int var22 = (var18 - var21) * zoom; + return var22 / var14 < Rasterizer3D_clipMidY2; + } + } + } + } + return false; + } + + /** + * Draw a renderable in the scene + * + * @param renderable + * @param orientation + * @param pitchSin + * @param pitchCos + * @param yawSin + * @param yawCos + * @param x + * @param y + * @param z + * @param hash + */ + @Override + public void draw(Renderable renderable, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash) + { + if (!useComputeShaders) + { + Model model = renderable instanceof Model ? (Model) renderable : renderable.getModel(); + if (model != null) + { + model.calculateBoundsCylinder(); + model.calculateExtreme(orientation); + + if (!isVisible(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash)) + { + return; + } + + client.checkClickbox(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + + modelX = x + client.getCameraX2(); + modelY = y + client.getCameraY2(); + modelZ = z + client.getCameraZ2(); + modelOrientation = orientation; + int triangleCount = model.getTrianglesCount(); + vertexBuffer.ensureCapacity(12 * triangleCount); + uvBuffer.ensureCapacity(12 * triangleCount); + + drawingModel = true; + + renderable.draw(orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + + drawingModel = false; + } + } + // Model may be in the scene buffer + else if (renderable instanceof Model && ((Model) renderable).getSceneId() == sceneUploader.sceneId) + { + Model model = (Model) renderable; + + model.calculateBoundsCylinder(); + model.calculateExtreme(orientation); + + if (!isVisible(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash)) + { + return; + } + + client.checkClickbox(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + + int tc = Math.min(MAX_TRIANGLE, model.getTrianglesCount()); + int uvOffset = model.getUvBufferOffset(); + + GpuIntBuffer b = bufferForTriangles(tc); + + b.ensureCapacity(8); + IntBuffer buffer = b.getBuffer(); + buffer.put(model.getBufferOffset()); + buffer.put(uvOffset); + 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; + } + else + { + // Temporary model (animated or otherwise not a static Model on the scene) + Model model = renderable instanceof Model ? (Model) renderable : renderable.getModel(); + if (model != null) + { + // Apply height to renderable from the model + model.setModelHeight(model.getModelHeight()); + + model.calculateBoundsCylinder(); + model.calculateExtreme(orientation); + + if (!isVisible(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash)) + { + return; + } + + client.checkClickbox(model, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash); + + boolean hasUv = model.getFaceTextures() != null; + + int faces = Math.min(MAX_TRIANGLE, model.getTrianglesCount()); + vertexBuffer.ensureCapacity(12 * faces); + uvBuffer.ensureCapacity(12 * faces); + int len = 0; + for (int i = 0; i < faces; ++i) + { + len += sceneUploader.pushFace(model, i, false, vertexBuffer, uvBuffer, 0, 0, 0, 0); + } + + GpuIntBuffer b = bufferForTriangles(faces); + + b.ensureCapacity(8); + IntBuffer buffer = b.getBuffer(); + buffer.put(tempOffset); + buffer.put(hasUv ? tempUvOffset : -1); + buffer.put(len / 3); + buffer.put(targetBufferOffset); + buffer.put((model.getRadius() << 12) | orientation); + buffer.put(x + client.getCameraX2()).put(y + client.getCameraY2()).put(z + client.getCameraZ2()); + + tempOffset += len; + if (hasUv) + { + tempUvOffset += len; + } + + targetBufferOffset += len; + } + } + } + + @Override + public boolean drawFace(Model model, int face) + { + if (!drawingModel) + { + return false; + } + + targetBufferOffset += sceneUploader.pushFace(model, face, true, vertexBuffer, uvBuffer, modelX, modelY, modelZ, modelOrientation); + return true; + } + + /** + * returns the correct buffer based on triangle count and updates model count + * + * @param triangles + * @return + */ + private GpuIntBuffer bufferForTriangles(int triangles) + { + if (triangles <= SMALL_TRIANGLE_COUNT) + { + ++smallModels; + return modelBufferSmall; + } + else + { + ++largeModels; + return modelBuffer; + } + } + + private int getScaledValue(final double scale, final int value) + { + return SurfaceScaleUtils.scale(value, (float) scale); + } + + private void glDpiAwareViewport(final int x, final int y, final int width, final int height) + { + if (OSType.getOSType() == OSType.MacOS) + { + // JOGL seems to handle DPI scaling for us already + gl.glViewport(x, y, width, height); + } + else + { + final Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + final AffineTransform t = graphics.getTransform(); + gl.glViewport( + getScaledValue(t.getScaleX(), x), + getScaledValue(t.getScaleY(), y), + getScaledValue(t.getScaleX(), width), + getScaledValue(t.getScaleY(), height)); + graphics.dispose(); + } + } + + private int getDrawDistance() + { + final int limit = useComputeShaders ? MAX_DISTANCE : DEFAULT_DISTANCE; + return Ints.constrainToRange(config.drawDistance(), 0, limit); + } + + private static void invokeOnMainThread(Runnable runnable) + { + if (OSType.getOSType() == OSType.MacOS) + { + OSXUtil.RunOnMainThread(true, false, runnable); + } + else + { + runnable.run(); + } + } +} 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 new file mode 100644 index 0000000000..bef0ce2921 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPluginConfig.java @@ -0,0 +1,138 @@ +/* + * 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.gpu; + +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 static net.runelite.client.plugins.gpu.GpuPlugin.MAX_FOG_DEPTH; +import net.runelite.client.plugins.gpu.config.AntiAliasingMode; +import static net.runelite.client.plugins.gpu.GpuPlugin.MAX_DISTANCE; +import net.runelite.client.plugins.gpu.config.ColorBlindMode; +import net.runelite.client.plugins.gpu.config.UIScalingMode; + +@ConfigGroup("gpu") +public interface GpuPluginConfig extends Config +{ + @Range( + max = MAX_DISTANCE + ) + @ConfigItem( + keyName = "drawDistance", + name = "Draw Distance", + description = "Draw distance", + position = 1 + ) + default int drawDistance() + { + return 25; + } + + @ConfigItem( + keyName = "smoothBanding", + name = "Remove Color Banding", + description = "Smooths out the color banding that is present in the CPU renderer", + position = 2 + ) + default boolean smoothBanding() + { + return false; + } + + @ConfigItem( + keyName = "antiAliasingMode", + name = "Anti Aliasing", + description = "Configures the anti-aliasing mode", + position = 3 + ) + default AntiAliasingMode antiAliasingMode() + { + return AntiAliasingMode.DISABLED; + } + + @ConfigItem( + keyName = "uiScalingMode", + name = "UI scaling mode", + description = "Sampling function to use for the UI in stretched mode", + position = 4 + ) + default UIScalingMode uiScalingMode() + { + return UIScalingMode.LINEAR; + } + + @Range( + max = MAX_FOG_DEPTH + ) + @ConfigItem( + keyName = "fogDepth", + name = "Fog depth", + description = "Distance from the scene edge the fog starts", + position = 5 + ) + default int fogDepth() + { + return 0; + } + + @ConfigItem( + keyName = "useComputeShaders", + name = "Compute Shaders", + description = "Offloads face sorting to GPU, enabling extended draw distance. Requires plugin restart.", + warning = "This feature requires OpenGL 4.3 to use. Please check that your GPU supports this.\nRestart the plugin for changes to take effect.", + position = 6 + ) + default boolean useComputeShaders() + { + return true; + } + + @Range( + min = 0, + max = 16 + ) + @ConfigItem( + keyName = "anisotropicFilteringLevel", + name = "Anisotropic Filtering", + description = "Configures the anisotropic filtering level.", + position = 7 + ) + default int anisotropicFilteringLevel() + { + return 0; + } + + @ConfigItem( + keyName = "colorBlindMode", + name = "Colorblindness Correction", + description = "Adjusts colors to account for colorblindness", + position = 8 + ) + default ColorBlindMode colorBlindMode() + { + return ColorBlindMode.NONE; + } +} 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 new file mode 100644 index 0000000000..3d1585e625 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/SceneUploader.java @@ -0,0 +1,604 @@ +/* + * 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.gpu; + +import com.google.common.base.Stopwatch; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.DecorativeObject; +import net.runelite.api.GameObject; +import net.runelite.api.GroundObject; +import net.runelite.api.Model; +import net.runelite.api.Perspective; +import net.runelite.api.Point; +import net.runelite.api.Renderable; +import net.runelite.api.Scene; +import net.runelite.api.SceneTileModel; +import net.runelite.api.SceneTilePaint; +import net.runelite.api.Tile; +import net.runelite.api.WallObject; + +@Singleton +@Slf4j +class SceneUploader +{ + @Inject + private Client client; + + int sceneId = (int) (System.currentTimeMillis() / 1000L); + private int offset; + private int uvoffset; + + void upload(Scene scene, GpuIntBuffer vertexbuffer, GpuFloatBuffer uvBuffer) + { + Stopwatch stopwatch = Stopwatch.createStarted(); + + ++sceneId; + offset = 0; + uvoffset = 0; + vertexbuffer.clear(); + uvBuffer.clear(); + + for (int z = 0; z < Constants.MAX_Z; ++z) + { + for (int x = 0; x < Constants.SCENE_SIZE; ++x) + { + for (int y = 0; y < Constants.SCENE_SIZE; ++y) + { + Tile tile = scene.getTiles()[z][x][y]; + if (tile != null) + { + upload(tile, vertexbuffer, uvBuffer); + } + } + } + } + + stopwatch.stop(); + log.debug("Scene upload time: {}", stopwatch); + } + + private void upload(Tile tile, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer) + { + Tile bridge = tile.getBridge(); + if (bridge != null) + { + upload(bridge, vertexBuffer, uvBuffer); + } + + SceneTilePaint sceneTilePaint = tile.getSceneTilePaint(); + if (sceneTilePaint != null) + { + sceneTilePaint.setBufferOffset(offset); + if (sceneTilePaint.getTexture() != -1) + { + sceneTilePaint.setUvBufferOffset(uvoffset); + } + else + { + sceneTilePaint.setUvBufferOffset(-1); + } + Point tilePoint = tile.getSceneLocation(); + int len = upload(sceneTilePaint, + tile.getRenderLevel(), tilePoint.getX(), tilePoint.getY(), + vertexBuffer, uvBuffer, + 0, 0, false); + sceneTilePaint.setBufferLen(len); + offset += len; + if (sceneTilePaint.getTexture() != -1) + { + uvoffset += len; + } + } + + SceneTileModel sceneTileModel = tile.getSceneTileModel(); + if (sceneTileModel != null) + { + sceneTileModel.setBufferOffset(offset); + if (sceneTileModel.getTriangleTextureId() != null) + { + sceneTileModel.setUvBufferOffset(uvoffset); + } + else + { + sceneTileModel.setUvBufferOffset(-1); + } + Point tilePoint = tile.getSceneLocation(); + int len = upload(sceneTileModel, + tilePoint.getX(), tilePoint.getY(), + vertexBuffer, uvBuffer, + 0, 0, false); + sceneTileModel.setBufferLen(len); + offset += len; + if (sceneTileModel.getTriangleTextureId() != null) + { + uvoffset += len; + } + } + + WallObject wallObject = tile.getWallObject(); + if (wallObject != null) + { + Renderable renderable1 = wallObject.getRenderable(); + if (renderable1 instanceof Model) + { + uploadModel((Model) renderable1, vertexBuffer, uvBuffer); + } + + Renderable renderable2 = wallObject.getRenderable2(); + if (renderable2 instanceof Model) + { + uploadModel((Model) renderable2, vertexBuffer, uvBuffer); + } + } + + GroundObject groundObject = tile.getGroundObject(); + if (groundObject != null) + { + Renderable renderable = groundObject.getRenderable(); + if (renderable instanceof Model) + { + uploadModel((Model) renderable, vertexBuffer, uvBuffer); + } + } + + DecorativeObject decorativeObject = tile.getDecorativeObject(); + if (decorativeObject != null) + { + Renderable renderable = decorativeObject.getRenderable(); + if (renderable instanceof Model) + { + uploadModel((Model) renderable, vertexBuffer, uvBuffer); + } + + Renderable renderable2 = decorativeObject.getRenderable2(); + if (renderable2 instanceof Model) + { + uploadModel((Model) renderable2, vertexBuffer, uvBuffer); + } + } + + GameObject[] gameObjects = tile.getGameObjects(); + for (GameObject gameObject : gameObjects) + { + if (gameObject == null) + { + continue; + } + + Renderable renderable = gameObject.getRenderable(); + if (renderable instanceof Model) + { + uploadModel((Model) gameObject.getRenderable(), vertexBuffer, uvBuffer); + } + } + } + + int upload(SceneTilePaint tile, int tileZ, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, + int offsetX, int offsetY, boolean padUvs) + { + final int[][][] tileHeights = client.getTileHeights(); + + final int localX = offsetX; + final int localY = offsetY; + + int swHeight = tileHeights[tileZ][tileX][tileY]; + int seHeight = tileHeights[tileZ][tileX + 1][tileY]; + int neHeight = tileHeights[tileZ][tileX + 1][tileY + 1]; + int nwHeight = tileHeights[tileZ][tileX][tileY + 1]; + + final int neColor = tile.getNeColor(); + final int nwColor = tile.getNwColor(); + final int seColor = tile.getSeColor(); + final int swColor = tile.getSwColor(); + + if (neColor == 12345678) + { + return 0; + } + + vertexBuffer.ensureCapacity(24); + uvBuffer.ensureCapacity(24); + + // 0,0 + int vertexDx = localX; + int vertexDy = localY; + int vertexDz = swHeight; + final int c1 = swColor; + + // 1,0 + int vertexCx = localX + Perspective.LOCAL_TILE_SIZE; + int vertexCy = localY; + int vertexCz = seHeight; + final int c2 = seColor; + + // 1,1 + int vertexAx = localX + Perspective.LOCAL_TILE_SIZE; + int vertexAy = localY + Perspective.LOCAL_TILE_SIZE; + int vertexAz = neHeight; + final int c3 = neColor; + + // 0,1 + int vertexBx = localX; + int vertexBy = localY + Perspective.LOCAL_TILE_SIZE; + int vertexBz = nwHeight; + final int c4 = nwColor; + + vertexBuffer.put(vertexAx, vertexAz, vertexAy, c3); + vertexBuffer.put(vertexBx, vertexBz, vertexBy, c4); + vertexBuffer.put(vertexCx, vertexCz, vertexCy, c2); + + vertexBuffer.put(vertexDx, vertexDz, vertexDy, c1); + vertexBuffer.put(vertexCx, vertexCz, vertexCy, c2); + vertexBuffer.put(vertexBx, vertexBz, vertexBy, c4); + + if (padUvs || tile.getTexture() != -1) + { + float tex = tile.getTexture() + 1f; + uvBuffer.put(tex, 1.0f, 1.0f, 0f); + uvBuffer.put(tex, 0.0f, 1.0f, 0f); + uvBuffer.put(tex, 1.0f, 0.0f, 0f); + + uvBuffer.put(tex, 0.0f, 0.0f, 0f); + uvBuffer.put(tex, 1.0f, 0.0f, 0f); + uvBuffer.put(tex, 0.0f, 1.0f, 0f); + } + + return 6; + } + + int upload(SceneTileModel sceneTileModel, int tileX, int tileY, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, + int offsetX, int offsetY, boolean padUvs) + { + final int[] faceX = sceneTileModel.getFaceX(); + final int[] faceY = sceneTileModel.getFaceY(); + final int[] faceZ = sceneTileModel.getFaceZ(); + + final int[] vertexX = sceneTileModel.getVertexX(); + final int[] vertexY = sceneTileModel.getVertexY(); + final int[] vertexZ = sceneTileModel.getVertexZ(); + + final int[] triangleColorA = sceneTileModel.getTriangleColorA(); + final int[] triangleColorB = sceneTileModel.getTriangleColorB(); + final int[] triangleColorC = sceneTileModel.getTriangleColorC(); + + final int[] triangleTextures = sceneTileModel.getTriangleTextureId(); + + final int faceCount = faceX.length; + + vertexBuffer.ensureCapacity(faceCount * 12); + uvBuffer.ensureCapacity(faceCount * 12); + + int baseX = Perspective.LOCAL_TILE_SIZE * tileX; + int baseY = Perspective.LOCAL_TILE_SIZE * tileY; + + int cnt = 0; + for (int i = 0; i < faceCount; ++i) + { + final int triangleA = faceX[i]; + final int triangleB = faceY[i]; + final int triangleC = faceZ[i]; + + final int colorA = triangleColorA[i]; + final int colorB = triangleColorB[i]; + final int colorC = triangleColorC[i]; + + if (colorA == 12345678) + { + continue; + } + + cnt += 3; + + // vertexes are stored in scene local, convert to tile local + int vertexXA = vertexX[triangleA] - baseX; + int vertexZA = vertexZ[triangleA] - baseY; + + int vertexXB = vertexX[triangleB] - baseX; + int vertexZB = vertexZ[triangleB] - baseY; + + int vertexXC = vertexX[triangleC] - baseX; + int vertexZC = vertexZ[triangleC] - baseY; + + vertexBuffer.put(vertexXA + offsetX, vertexY[triangleA], vertexZA + offsetY, colorA); + vertexBuffer.put(vertexXB + offsetX, vertexY[triangleB], vertexZB + offsetY, colorB); + vertexBuffer.put(vertexXC + offsetX, vertexY[triangleC], vertexZC + offsetY, colorC); + + if (padUvs || triangleTextures != null) + { + if (triangleTextures != null && triangleTextures[i] != -1) + { + float tex = triangleTextures[i] + 1f; + uvBuffer.put(tex, vertexXA / 128f, vertexZA / 128f, 0f); + uvBuffer.put(tex, vertexXB / 128f, vertexZB / 128f, 0f); + uvBuffer.put(tex, vertexXC / 128f, vertexZC / 128f, 0f); + } + else + { + uvBuffer.put(0, 0, 0, 0f); + uvBuffer.put(0, 0, 0, 0f); + uvBuffer.put(0, 0, 0, 0f); + } + } + } + + return cnt; + } + + private void uploadModel(Model model, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer) + { + if (model.getSceneId() == sceneId) + { + return; // model has already been uploaded + } + + model.setBufferOffset(offset); + if (model.getFaceTextures() != null) + { + model.setUvBufferOffset(uvoffset); + } + else + { + model.setUvBufferOffset(-1); + } + model.setSceneId(sceneId); + + final int triangleCount = model.getTrianglesCount(); + + vertexBuffer.ensureCapacity(triangleCount * 12); + uvBuffer.ensureCapacity(triangleCount * 12); + + final int[] vertexX = model.getVerticesX(); + final int[] vertexY = model.getVerticesY(); + final int[] vertexZ = model.getVerticesZ(); + + final int[] trianglesX = model.getTrianglesX(); + final int[] trianglesY = model.getTrianglesY(); + final int[] trianglesZ = model.getTrianglesZ(); + + final int[] color1s = model.getFaceColors1(); + final int[] color2s = model.getFaceColors2(); + final int[] color3s = model.getFaceColors3(); + + final byte[] transparencies = model.getTriangleTransparencies(); + final short[] faceTextures = model.getFaceTextures(); + final byte[] facePriorities = model.getFaceRenderPriorities(); + + float[][] u = model.getFaceTextureUCoordinates(); + float[][] v = model.getFaceTextureVCoordinates(); + + int len = 0; + for (int face = 0; face < triangleCount; ++face) + { + int color1 = color1s[face]; + int color2 = color2s[face]; + int color3 = color3s[face]; + + if (color3 == -1) + { + color2 = color3 = color1; + } + else if (color3 == -2) + { + vertexBuffer.put(0, 0, 0, 0); + vertexBuffer.put(0, 0, 0, 0); + vertexBuffer.put(0, 0, 0, 0); + + if (faceTextures != null) + { + uvBuffer.put(0, 0, 0, 0); + uvBuffer.put(0, 0, 0, 0); + uvBuffer.put(0, 0, 0, 0); + } + + len += 3; + continue; + } + + int packAlphaPriority = packAlphaPriority(faceTextures, transparencies, facePriorities, face); + + int triangleA = trianglesX[face]; + int triangleB = trianglesY[face]; + int triangleC = trianglesZ[face]; + + vertexBuffer.put(vertexX[triangleA], vertexY[triangleA], vertexZ[triangleA], packAlphaPriority | color1); + vertexBuffer.put(vertexX[triangleB], vertexY[triangleB], vertexZ[triangleB], packAlphaPriority | color2); + vertexBuffer.put(vertexX[triangleC], vertexY[triangleC], vertexZ[triangleC], packAlphaPriority | color3); + + if (faceTextures != null) + { + pushUvForFace(faceTextures, u, v, face, uvBuffer); + } + + len += 3; + } + + offset += len; + if (model.getFaceTextures() != null) + { + uvoffset += len; + } + } + + int pushFace(Model model, int face, boolean padUvs, GpuIntBuffer vertexBuffer, GpuFloatBuffer uvBuffer, + int xOffset, int yOffset, int zOffset, int orientation) + { + final int[] vertexX = model.getVerticesX(); + final int[] vertexY = model.getVerticesY(); + final int[] vertexZ = model.getVerticesZ(); + + final int[] trianglesX = model.getTrianglesX(); + final int[] trianglesY = model.getTrianglesY(); + final int[] trianglesZ = model.getTrianglesZ(); + + final int[] color1s = model.getFaceColors1(); + final int[] color2s = model.getFaceColors2(); + final int[] color3s = model.getFaceColors3(); + + final byte[] transparencies = model.getTriangleTransparencies(); + final short[] faceTextures = model.getFaceTextures(); + final byte[] facePriorities = model.getFaceRenderPriorities(); + + int triangleA = trianglesX[face]; + int triangleB = trianglesY[face]; + int triangleC = trianglesZ[face]; + + int color1 = color1s[face]; + int color2 = color2s[face]; + int color3 = color3s[face]; + + int packedAlphaPriority = packAlphaPriority(faceTextures, transparencies, facePriorities, face); + + int sin = 0, cos = 0; + if (orientation != 0) + { + sin = Perspective.SINE[orientation]; + cos = Perspective.COSINE[orientation]; + } + + if (color3 == -1) + { + color2 = color3 = color1; + } + else if (color3 == -2) + { + vertexBuffer.put(0, 0, 0, 0); + vertexBuffer.put(0, 0, 0, 0); + vertexBuffer.put(0, 0, 0, 0); + + if (padUvs || faceTextures != null) + { + uvBuffer.put(0, 0, 0, 0f); + uvBuffer.put(0, 0, 0, 0f); + uvBuffer.put(0, 0, 0, 0f); + } + return 3; + } + + int a, b, c; + + a = vertexX[triangleA]; + b = vertexY[triangleA]; + c = vertexZ[triangleA]; + + if (orientation != 0) + { + int x = c * sin + a * cos >> 16; + int z = c * cos - a * sin >> 16; + + a = x; + c = z; + } + + a += xOffset; + b += yOffset; + c += zOffset; + + vertexBuffer.put(a, b, c, packedAlphaPriority | color1); + + a = vertexX[triangleB]; + b = vertexY[triangleB]; + c = vertexZ[triangleB]; + + if (orientation != 0) + { + int x = c * sin + a * cos >> 16; + int z = c * cos - a * sin >> 16; + + a = x; + c = z; + } + + a += xOffset; + b += yOffset; + c += zOffset; + + vertexBuffer.put(a, b, c, packedAlphaPriority | color2); + + a = vertexX[triangleC]; + b = vertexY[triangleC]; + c = vertexZ[triangleC]; + + if (orientation != 0) + { + int x = c * sin + a * cos >> 16; + int z = c * cos - a * sin >> 16; + + a = x; + c = z; + } + + a += xOffset; + b += yOffset; + c += zOffset; + + vertexBuffer.put(a, b, c, packedAlphaPriority | color3); + + float[][] u = model.getFaceTextureUCoordinates(); + float[][] v = model.getFaceTextureVCoordinates(); + if (padUvs || faceTextures != null) + { + pushUvForFace(faceTextures, u, v, face, uvBuffer); + } + + return 3; + } + + private static int packAlphaPriority(short[] faceTextures, byte[] faceTransparencies, byte[] facePriorities, int face) + { + int alpha = 0; + if (faceTransparencies != null && (faceTextures == null || faceTextures[face] == -1)) + { + alpha = (faceTransparencies[face] & 0xFF) << 24; + } + int priority = 0; + if (facePriorities != null) + { + priority = (facePriorities[face] & 0xff) << 16; + } + return alpha | priority; + } + + private static void pushUvForFace(short[] faceTextures, float[][] u, float[][] v, int face, GpuFloatBuffer uvBuffer) + { + float[] uf, vf; + if (faceTextures != null && u != null && v != null && (uf = u[face]) != null && (vf = v[face]) != null) + { + float texture = faceTextures[face] + 1f; + uvBuffer.put(texture, uf[0], vf[0], 0f); + uvBuffer.put(texture, uf[1], vf[1], 0f); + uvBuffer.put(texture, uf[2], vf[2], 0f); + } + else + { + uvBuffer.put(0, 0, 0, 0); + uvBuffer.put(0, 0, 0, 0); + uvBuffer.put(0, 0, 0, 0); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Shader.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Shader.java new file mode 100644 index 0000000000..bba1827245 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/Shader.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018, Adam + * Copyright (c) 2020 Abex + * 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; + +import com.google.common.annotations.VisibleForTesting; +import com.jogamp.opengl.GL4; +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.client.plugins.gpu.template.Template; + +public class Shader +{ + @VisibleForTesting + final List units = new ArrayList<>(); + + @RequiredArgsConstructor + @VisibleForTesting + static class Unit + { + @Getter + private final int type; + + @Getter + private final String filename; + } + + public Shader add(int type, String name) + { + units.add(new Unit(type, name)); + return this; + } + + public int compile(GL4 gl, Template template) throws ShaderException + { + int program = gl.glCreateProgram(); + int[] shaders = new int[units.size()]; + int i = 0; + boolean ok = false; + try + { + while (i < shaders.length) + { + Unit unit = units.get(i); + int shader = gl.glCreateShader(unit.type); + if (shader == 0) + { + throw new ShaderException("Unable to create shader of type " + unit.type); + } + + String source = template.load(unit.filename); + gl.glShaderSource(shader, 1, new String[]{source}, null); + gl.glCompileShader(shader); + + if (GLUtil.glGetShader(gl, shader, gl.GL_COMPILE_STATUS) != gl.GL_TRUE) + { + String err = GLUtil.glGetShaderInfoLog(gl, shader); + gl.glDeleteShader(shader); + throw new ShaderException(err); + } + gl.glAttachShader(program, shader); + shaders[i++] = shader; + } + + gl.glLinkProgram(program); + + if (GLUtil.glGetProgram(gl, program, gl.GL_LINK_STATUS) == gl.GL_FALSE) + { + String err = GLUtil.glGetProgramInfoLog(gl, program); + throw new ShaderException(err); + } + + gl.glValidateProgram(program); + + if (GLUtil.glGetProgram(gl, program, gl.GL_VALIDATE_STATUS) == gl.GL_FALSE) + { + String err = GLUtil.glGetProgramInfoLog(gl, program); + throw new ShaderException(err); + } + + ok = true; + } + finally + { + while (i > 0) + { + int shader = shaders[--i]; + gl.glDetachShader(program, shader); + gl.glDeleteShader(shader); + } + + if (!ok) + { + gl.glDeleteProgram(program); + } + } + + return program; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/ShaderException.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/ShaderException.java new file mode 100644 index 0000000000..78a36eaa34 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/ShaderException.java @@ -0,0 +1,33 @@ +/* + * 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.gpu; + +class ShaderException extends Exception +{ + ShaderException(String message) + { + super(message); + } +} 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 new file mode 100644 index 0000000000..351eba2823 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java @@ -0,0 +1,270 @@ +/* + * 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.gpu; + +import com.jogamp.opengl.GL4; +import java.nio.ByteBuffer; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Texture; +import net.runelite.api.TextureProvider; + +@Singleton +@Slf4j +class TextureManager +{ + private static final float PERC_64 = 1f / 64f; + private static final float PERC_128 = 1f / 128f; + + private static final int TEXTURE_SIZE = 128; + + int initTextureArray(TextureProvider textureProvider, GL4 gl) + { + if (!allTexturesLoaded(textureProvider)) + { + return -1; + } + + Texture[] textures = textureProvider.getTextures(); + + int textureArrayId = GLUtil.glGenTexture(gl); + gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, textureArrayId); + gl.glTexStorage3D(gl.GL_TEXTURE_2D_ARRAY, 8, gl.GL_RGBA8, TEXTURE_SIZE, TEXTURE_SIZE, textures.length); + + gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST); + gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST); + + gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE); + + // Set brightness to 1.0d to upload unmodified textures to GPU + double save = textureProvider.getBrightness(); + textureProvider.setBrightness(1.0d); + + updateTextures(textureProvider, gl, textureArrayId); + + textureProvider.setBrightness(save); + + gl.glActiveTexture(gl.GL_TEXTURE1); + gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, textureArrayId); + gl.glGenerateMipmap(gl.GL_TEXTURE_2D_ARRAY); + gl.glActiveTexture(gl.GL_TEXTURE0); + + return textureArrayId; + } + + void setAnisotropicFilteringLevel(int textureArrayId, int level, GL4 gl) + { + gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, textureArrayId); + + //level = 0 means no mipmaps and no anisotropic filtering + if (level == 0) + { + gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST); + } + //level = 1 means with mipmaps but without anisotropic filtering GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT defaults to 1.0 which is off + //level > 1 enables anisotropic filtering. It's up to the vendor what the values mean + //Even if anisotropic filtering isn't supported, mipmaps will be enabled with any level >= 1 + else + { + // Set on GL_NEAREST_MIPMAP_LINEAR (bilinear filtering with mipmaps) since the pixel nature of the game means that nearest filtering + // looks best for objects up close but allows linear filtering to resolve possible aliasing and noise with mipmaps from far away objects. + gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST_MIPMAP_LINEAR); + } + + if (gl.isExtensionAvailable("GL_EXT_texture_filter_anisotropic")) + { + final float maxSamples = GLUtil.glGetFloat(gl, gl.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT); + //Clamp from 1 to max GL says it supports. + final float anisoLevel = Math.max(1, Math.min(maxSamples, level)); + gl.glTexParameterf(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoLevel); + } + } + + void freeTextureArray(GL4 gl, int textureArrayId) + { + GLUtil.glDeleteTexture(gl, textureArrayId); + } + + /** + * Check if all textures have been loaded and cached yet. + * + * @param textureProvider + * @return + */ + private boolean allTexturesLoaded(TextureProvider textureProvider) + { + Texture[] textures = textureProvider.getTextures(); + if (textures == null || textures.length == 0) + { + return false; + } + + for (int textureId = 0; textureId < textures.length; textureId++) + { + Texture texture = textures[textureId]; + if (texture != null) + { + int[] pixels = textureProvider.load(textureId); + if (pixels == null) + { + return false; + } + } + } + + return true; + } + + private void updateTextures(TextureProvider textureProvider, GL4 gl, int textureArrayId) + { + Texture[] textures = textureProvider.getTextures(); + + gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, textureArrayId); + + int cnt = 0; + for (int textureId = 0; textureId < textures.length; textureId++) + { + Texture texture = textures[textureId]; + if (texture != null) + { + int[] srcPixels = textureProvider.load(textureId); + if (srcPixels == null) + { + log.warn("No pixels for texture {}!", textureId); + continue; // this can't happen + } + + ++cnt; + + if (srcPixels.length != TEXTURE_SIZE * TEXTURE_SIZE) + { + // The texture storage is 128x128 bytes, and will only work correctly with the + // 128x128 textures from high detail mode + log.warn("Texture size for {} is {}!", textureId, srcPixels.length); + continue; + } + + byte[] pixels = convertPixels(srcPixels, TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE); + ByteBuffer pixelBuffer = ByteBuffer.wrap(pixels); + gl.glTexSubImage3D(gl.GL_TEXTURE_2D_ARRAY, 0, 0, 0, textureId, TEXTURE_SIZE, TEXTURE_SIZE, + 1, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixelBuffer); + } + } + + log.debug("Uploaded textures {}", cnt); + } + + private static byte[] convertPixels(int[] srcPixels, int width, int height, int textureWidth, int textureHeight) + { + byte[] pixels = new byte[textureWidth * textureHeight * 4]; + + int pixelIdx = 0; + int srcPixelIdx = 0; + + int offset = (textureWidth - width) * 4; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int rgb = srcPixels[srcPixelIdx++]; + if (rgb != 0) + { + pixels[pixelIdx++] = (byte) (rgb >> 16); + pixels[pixelIdx++] = (byte) (rgb >> 8); + pixels[pixelIdx++] = (byte) rgb; + pixels[pixelIdx++] = (byte) -1; + } + else + { + pixelIdx += 4; + } + } + pixelIdx += offset; + } + return pixels; + } + + /** + * Animate the given texture + * + * @param texture + * @param diff Number of elapsed client ticks since last animation + */ + void animate(Texture texture, int diff) + { + final int[] pixels = texture.getPixels(); + if (pixels == null) + { + return; + } + + final int animationSpeed = texture.getAnimationSpeed(); + final float uvdiff = pixels.length == 4096 ? PERC_64 : PERC_128; + + float u = texture.getU(); + float v = texture.getV(); + + int offset = animationSpeed * diff; + float d = (float) offset * uvdiff; + + switch (texture.getAnimationDirection()) + { + case 1: + v -= d; + if (v < 0f) + { + v += 1f; + } + break; + case 3: + v += d; + if (v > 1f) + { + v -= 1f; + } + break; + case 2: + u -= d; + if (u < 0f) + { + u += 1f; + } + break; + case 4: + u += d; + if (u > 1f) + { + u -= 1f; + } + break; + default: + return; + } + + texture.setU(u); + texture.setV(v); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AntiAliasingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AntiAliasingMode.java new file mode 100644 index 0000000000..3d1efa2d56 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/AntiAliasingMode.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018, Lotto + * 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 AntiAliasingMode +{ + DISABLED("Disabled", 0), + MSAA_2("MSAA x2", 2), + MSAA_4("MSAA x4", 4), + MSAA_8("MSAA x8", 8), + MSAA_16("MSAA x16", 16); + + private final String name; + private final int samples; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java new file mode 100644 index 0000000000..9a544c0e6a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/ColorBlindMode.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Ben Poulson + * 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; + +public enum ColorBlindMode +{ + NONE, + PROTANOPE, + DEUTERANOPE, + TRITANOPE; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java new file mode 100644 index 0000000000..04c01697c5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 logarrhytmic + * 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 UIScalingMode +{ + NEAREST("Nearest Neighbor", 0), + LINEAR("Bilinear", 0), + MITCHELL("Bicubic (Mitchell)", 1), + CATMULL_ROM("Bicubic (Catmull-Rom)", 2), + XBR("xBR", 3); + + private final String name; + private final int mode; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/template/Template.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/template/Template.java new file mode 100644 index 0000000000..67ed68ca7f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/template/Template.java @@ -0,0 +1,103 @@ +/* + * 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.gpu.template; + +import com.google.common.io.CharStreams; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class Template +{ + private final List> resourceLoaders = new ArrayList<>(); + + public String process(String str) + { + StringBuilder sb = new StringBuilder(); + for (String line : str.split("\r?\n")) + { + if (line.startsWith("#include ")) + { + String resource = line.substring(9); + String resourceStr = load(resource); + sb.append(resourceStr); + } + else + { + sb.append(line).append('\n'); + } + } + return sb.toString(); + } + + public String load(String filename) + { + for (Function loader : resourceLoaders) + { + String value = loader.apply(filename); + if (value != null) + { + return process(value); + } + } + + return ""; + } + + public Template add(Function fn) + { + resourceLoaders.add(fn); + return this; + } + + public Template addInclude(Class clazz) + { + return add(f -> + { + InputStream is = clazz.getResourceAsStream(f); + if (is != null) + { + return inputStreamToString(is); + } + return null; + }); + } + + private static String inputStreamToString(InputStream in) + { + try + { + return CharStreams.toString(new InputStreamReader(in, StandardCharsets.UTF_8)); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpProgressBarLabel.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpProgressBarLabel.java index 35d19a63db..0f884135c7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpProgressBarLabel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpProgressBarLabel.java @@ -26,9 +26,7 @@ package net.runelite.client.plugins.xptracker; import lombok.AllArgsConstructor; import lombok.Getter; - import java.util.function.Function; - import static net.runelite.client.plugins.xptracker.XpInfoBox.TWO_DECIMAL_FORMAT; @Getter diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java index 5abfc92c89..fb3235ba1f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java @@ -500,7 +500,7 @@ public class XpTrackerPlugin extends Plugin MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry(); menuEntry.setTarget(skillText); menuEntry.setOption(hasOverlay(skill) ? MENUOP_REMOVE_CANVAS_TRACKER : MENUOP_ADD_CANVAS_TRACKER); - menuEntry.setParam0(event.getActionParam0()); + menuEntry.setActionParam(event.getActionParam()); menuEntry.setParam1(widgetID); menuEntry.setType(MenuAction.RUNELITE.getId()); @@ -513,9 +513,6 @@ public class XpTrackerPlugin extends Plugin if (event.getMenuAction().getId() != MenuAction.RUNELITE.getId() || TO_GROUP(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID) { - System.out.println("opcode " + event.getMenuAction()); - System.out.println("id " + event.getMenuAction().getId()); - System.out.println("group " + TO_GROUP(event.getWidgetId())); return; } @@ -538,8 +535,6 @@ public class XpTrackerPlugin extends Plugin case MENUOP_REMOVE_CANVAS_TRACKER: removeOverlay(skill); break; - default: - System.out.println(event.getMenuOption()); } } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt b/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt index fb8bd463ce..e0400293b0 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt @@ -974,7 +974,7 @@ c 7 4 #100 R 19 19 21 20 -// Cosmic entity's plane +// Cosmic renderable's plane #040404 r 32 75 diff --git a/runelite-client/src/main/resources/scripts/BankBuildTab.hash b/runelite-client/src/main/resources/scripts/BankBuildTab.hash deleted file mode 100644 index 430b55f587..0000000000 --- a/runelite-client/src/main/resources/scripts/BankBuildTab.hash +++ /dev/null @@ -1 +0,0 @@ -57892EA7C7677AF80D1DA094F34C8A8738F2F4E5DDA2E861680F7D598664B216 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/BankBuildTab.rs2asm b/runelite-client/src/main/resources/scripts/BankBuildTab.rs2asm deleted file mode 100644 index 972c1cb457..0000000000 --- a/runelite-client/src/main/resources/scripts/BankBuildTab.rs2asm +++ /dev/null @@ -1,238 +0,0 @@ -.id 509 -.int_stack_count 12 -.string_stack_count 0 -.int_var_count 18 -.string_var_count 0 - iconst 0 - istore 12 - iload 11 - iconst 0 - if_icmpeq LABEL6 - jump LABEL19 -LABEL6: - iload 2 - iconst 0 - if_icmpgt LABEL10 - jump LABEL19 -LABEL10: - invoke 514 - iconst 1 - if_icmpeq LABEL14 - jump LABEL19 -LABEL14: - iload 8 - iconst 15 - sconst "tabTextSpace" ; - runelite_callback ; - add - istore 12 - jump LABEL21 -LABEL19: - iload 8 - istore 12 -LABEL21: - iconst -1 - istore 13 - iconst 0 - istore 14 - iconst 0 - istore 15 - iconst 0 - sconst "rowIndexInit" ; - runelite_callback ; - istore 16 - iconst 0 - istore 17 -LABEL31: - iload 0 - iload 1 - if_icmplt LABEL35 - jump LABEL107 -LABEL35: - iload 3 - iload 0 - cc_find - iconst 1 - if_icmpeq LABEL41 - jump LABEL102 -LABEL41: - iconst 95 - iload 0 - inv_getobj - istore 13 - iload 13 - invoke 279 - iconst 1 - if_icmpeq LABEL50 - jump LABEL102 -LABEL50: - iconst 0 - cc_sethide - iload 13 - iconst 95 - iload 0 - inv_getnum - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - invoke 278 - iload 15 - iconst 1 - add - istore 15 - iload 12 - iload 17 - iconst 36 - multiply - add - istore 14 - iconst 51 - iload 16 - iconst 36 - iload 9 - add - multiply - add - iload 14 - iconst 0 - iconst 0 - cc_setposition - iload 14 - iconst 32 - add - istore 14 - iload 16 - iload 10 - if_icmplt LABEL91 - jump LABEL96 -LABEL91: - iload 16 - iconst 1 - add - sconst "rowIndex" ; - runelite_callback ; - istore 16 - jump LABEL102 -LABEL96: - iconst 0 - iload 17 - iconst 1 - add - istore 17 - sconst "rowIndex" ; - runelite_callback ; - istore 16 -LABEL102: - iload 0 - iconst 1 - add - istore 0 - jump LABEL31 -LABEL107: - iload 14 - iconst 0 - if_icmple LABEL111 - jump LABEL114 -LABEL111: - iload 8 - iload 15 - return -LABEL114: - iload 11 - iconst 0 - if_icmpeq LABEL118 - jump LABEL189 -LABEL118: - iload 16 - iconst 0 - if_icmpgt LABEL122 - jump LABEL166 -LABEL122: - iload 3 - iconst 816 - iconst 9 - iconst 2 - multiply - add - iload 2 - add - cc_find - iconst 1 - if_icmpeq LABEL134 - jump LABEL166 -LABEL134: - iconst 0 - sconst "hideTabText" ; - runelite_callback ; - cc_sethide - iconst 51 - iload 16 - iconst 36 - iload 9 - add - multiply - add - iload 14 - iconst 32 - sub - iconst 0 - iconst 0 - cc_setposition - iconst 8 - iload 16 - sub - istore 16 - iload 16 - iconst 36 - multiply - iload 16 - iconst 1 - sub - iload 9 - multiply - add - iconst 32 - iconst 0 - iconst 0 - cc_setsize -LABEL166: - iload 12 - iload 8 - if_icmpgt LABEL170 - jump LABEL189 -LABEL170: - iload 3 - iconst 816 - iconst 9 - add - iload 2 - add - iconst 1 - sub - cc_find - iconst 1 - if_icmpeq LABEL182 - jump LABEL189 -LABEL182: - iconst 51 - iload 8 - iconst 0 - iconst 0 - cc_setposition - iconst 0 - sconst "hideTabText" ; - runelite_callback ; - cc_sethide -LABEL189: - iload 14 - iload 2 ; - sconst "newBankRow" ; - runelite_callback ; - pop_int ; - iload 15 - return - iconst 0 - iconst 0 - return diff --git a/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.hash b/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.hash deleted file mode 100644 index aaf9322bfa..0000000000 --- a/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.hash +++ /dev/null @@ -1 +0,0 @@ -F7482938547BB43B14BCE44F35FC7555EBA9EF3A9D2E4AAEA916DF2855387F07 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.rs2asm b/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.rs2asm deleted file mode 100644 index 4dc759c716..0000000000 --- a/runelite-client/src/main/resources/scripts/BankLayoutTabSeparator.rs2asm +++ /dev/null @@ -1,36 +0,0 @@ -.id 510 -.int_stack_count 3 -.string_stack_count 0 -.int_var_count 3 -.string_var_count 0 - iload 1 - iconst 816 - iload 0 - add - iconst 1 - sub - cc_find - iconst 1 - if_icmpeq LABEL10 - jump LABEL19 -LABEL10: - iconst 51 - iload 2 - iconst 5 - add - iconst 0 - iconst 0 - cc_setposition - iconst 0 - sconst "hideLine" ; - runelite_callback ; - cc_sethide -LABEL19: - iload 2 - iconst 12 - sconst "lineSpace" ; - runelite_callback ; - add - return - iconst 0 - return diff --git a/runelite-client/src/main/resources/scripts/BankSearchLayout.rs2asm b/runelite-client/src/main/resources/scripts/BankSearchLayout.rs2asm index 8a926db3d3..485a6d08e9 100644 --- a/runelite-client/src/main/resources/scripts/BankSearchLayout.rs2asm +++ b/runelite-client/src/main/resources/scripts/BankSearchLayout.rs2asm @@ -836,8 +836,6 @@ LABEL729: iload 10 iload 11 iload 28 - sconst "addLastRow" - runelite_callback iload 29 iload 12 iload 13 diff --git a/runelite-client/src/main/resources/scripts/BankpinButtonSetup.hash b/runelite-client/src/main/resources/scripts/BankpinButtonSetup.hash new file mode 100644 index 0000000000..7d72d3140b --- /dev/null +++ b/runelite-client/src/main/resources/scripts/BankpinButtonSetup.hash @@ -0,0 +1 @@ +48C575A71CD8CDDBFC8A7C0D2F8915A5EE263731213FFC23BEFFF39B6441A9FF \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/BankpinButtonSetup.rs2asm b/runelite-client/src/main/resources/scripts/BankpinButtonSetup.rs2asm new file mode 100644 index 0000000000..4f783cfe53 --- /dev/null +++ b/runelite-client/src/main/resources/scripts/BankpinButtonSetup.rs2asm @@ -0,0 +1,121 @@ +.id 679 +.int_stack_count 21 +.string_stack_count 0 +.int_var_count 21 +.string_var_count 0 + iload 0 + cc_deleteall + iload 0 + iconst 3 + iconst 0 + cc_create + iconst 64 + iconst 64 + iconst 0 + iconst 0 + cc_setsize + iconst 0 + iconst 0 + iconst 0 + iconst 0 + cc_setposition + iconst 16777215 + cc_setcolour + iconst 1 + cc_setfill + iconst 255 + cc_settrans + iload 0 + iconst 4 + iconst 1 + cc_create 1 + iconst 10 + iconst 15 + iconst 0 + iconst 0 + cc_setsize 1 + iconst 5 + iload 0 + if_getwidth + iconst 15 + sub + randominc + add + iconst 5 + iload 0 + if_getheight + iconst 20 + sub + randominc + add + iconst 0 + iconst 0 + cc_setposition 1 + iconst 1 + iconst 1 + iconst 0 + cc_settextalign 1 + iconst 496 + cc_settextfont 1 + iconst 16744192 + cc_setcolour 1 + iconst 0 + cc_settextshadow 1 + sconst "" + cc_settext 1 + iconst 683 + iconst -2147483645 + iconst -2147483643 + iload 1 + clientclock + iconst 5 + add + sconst "Iiii" + cc_setontimer 1 + iconst 684 + iconst 0 + iconst -2147483645 + iconst -2147483643 + cc_getid 1 + sconst "1Iii" + cc_setonmouserepeat + iconst 684 + iconst 1 + iconst -2147483645 + iconst -2147483643 + cc_getid 1 + sconst "1Iii" + cc_setonmouseleave + iconst 1 + sconst "Select" + cc_setop + iconst 685 + iload 1 + iload 2 + iload 3 + iload 4 + iload 5 + iload 6 + iload 7 + iload 8 + iload 9 + iload 10 + iload 11 + iload 12 + iload 13 + iload 14 + iload 15 + iload 16 + iload 17 + iload 18 + iload 19 + iload 20 + sconst "iiiIIIIIIIIIIIIIIIII" + cc_setonop + iload 0 ; button component id + iload 1 ; 0-9 + sconst "bankpinButtonSetup" + runelite_callback + pop_int ; 0-9 + pop_int ; button component id + return diff --git a/runelite-client/src/main/resources/scripts/CommandScript.rs2asm b/runelite-client/src/main/resources/scripts/CommandScript.rs2asm index 57cf201063..3e568fe0a2 100644 --- a/runelite-client/src/main/resources/scripts/CommandScript.rs2asm +++ b/runelite-client/src/main/resources/scripts/CommandScript.rs2asm @@ -53,7 +53,7 @@ LABEL35: istore 4 LABEL37: staffmodlevel - iconst -1 + iconst 0 if_icmpgt LABEL41 jump LABEL43 LABEL41: diff --git a/runelite-client/src/main/resources/scripts/CustomJoinClan.rs2asm b/runelite-client/src/main/resources/scripts/CustomJoinClan.rs2asm deleted file mode 100644 index fc700389de..0000000000 --- a/runelite-client/src/main/resources/scripts/CustomJoinClan.rs2asm +++ /dev/null @@ -1,38 +0,0 @@ -;;; -; -; Copyright (c) 2019, Lucas -; 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. -; -;;;;;;;;;;;;;;; -; ; -; Join a cc ; -; ; -;;;;;;;;;;;;;;; -.id 10690 -.int_stack_count 0 -.string_stack_count 1 -.int_var_count 0 -.string_var_count 1 - sload 0 - clan_joinchat - return diff --git a/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.hash b/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.hash deleted file mode 100644 index 9560cbccda..0000000000 --- a/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.hash +++ /dev/null @@ -1 +0,0 @@ -F4729C2DE16BB31A779316E5D5080A9B5E6871C752BB4534863E0790D5F35154 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.rs2asm b/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.rs2asm deleted file mode 100644 index 45bf0691bf..0000000000 --- a/runelite-client/src/main/resources/scripts/MagicSpellBookRedraw.rs2asm +++ /dev/null @@ -1,870 +0,0 @@ -.id 2611 -.int_stack_count 11 -.string_stack_count 2 -.int_var_count 30 -.string_var_count 2 - sconst "startSpellRedraw" - runelite_callback - iconst 190 - istore 11 - iconst 261 - istore 12 - iconst 0 - istore 13 - iload 10 - iconst 1 - if_icmpeq LABEL10 - jump LABEL76 -LABEL10: - iconst 6 - iconst 240 - iconst 1 - iconst 0 - iload 3 - if_setsize - iconst 190 - iconst 6 - sub - iconst 240 - istore 12 - istore 11 - iconst 0 - iconst 0 - iconst 1 - iconst 0 - iload 3 - if_setposition - iconst 0 - iload 4 - if_sethide - sload 0 - iconst 190 - iconst 494 - parawidth - sload 1 - iconst 190 - iconst 494 - parawidth - invoke 1045 - iconst 14 - add - istore 13 - iload 13 - iconst 0 - iconst 0 - iconst 1 - iload 5 - if_setsize - iload 13 - iconst 0 - iconst 0 - iconst 1 - iload 6 - if_setsize - iconst 190 - iload 13 - iconst 2 - multiply - sub - iconst 3 - div - istore 13 - iload 13 - iconst 0 - iconst 0 - iconst 1 - iload 5 - if_setposition - iload 13 - iconst 0 - iconst 2 - iconst 1 - iload 6 - if_setposition - jump LABEL147 -LABEL76: - get_varbit 6718 - iconst 1 - if_icmpeq LABEL80 - jump LABEL109 -LABEL80: - iconst 0 - iconst 0 - iconst 1 - iconst 1 - iload 3 - if_setsize - iconst 0 - iconst 0 - iconst 1 - iconst 1 - iload 3 - if_setposition - iconst 1 - iload 4 - if_sethide - iconst -1 - iload 10 - iload 5 - iload 6 - iload 0 - iload 1 - iload 7 - iload 8 - iload 9 - iload 2 - sload 0 - sload 1 - invoke 2603 - jump LABEL147 -LABEL109: - iconst 6 - iconst 240 - iconst 1 - iconst 0 - iload 3 - if_setsize - iconst 190 - iconst 6 - sub - iconst 240 - istore 12 - istore 11 - iconst 0 - iconst 0 - iconst 1 - iconst 0 - iload 3 - if_setposition - iconst 0 - iload 4 - if_sethide - sload 1 - iconst 190 - iconst 494 - parawidth - iconst 14 - add - iconst 0 - iconst 0 - iconst 1 - iload 6 - if_setsize - iconst 0 - iconst 0 - iconst 1 - iconst 1 - iload 6 - if_setposition -LABEL147: - iload 0 - cc_deleteall - iload 1 - cc_deleteall - iload 2 - cc_deleteall - iconst 105 - iconst 103 - iconst 1981 - get_varbit 4070 - enum - istore 14 - iload 14 - iconst 1982 - if_icmpne LABEL163 - jump LABEL165 -LABEL163: - iconst 1982 - invoke 2618 -LABEL165: - iload 14 - iconst 1983 - if_icmpne LABEL169 - jump LABEL171 -LABEL169: - iconst 1983 - invoke 2618 -LABEL171: - iload 14 - iconst 1984 - if_icmpne LABEL175 - jump LABEL177 -LABEL175: - iconst 1984 - invoke 2618 -LABEL177: - iload 14 - iconst 1985 - if_icmpne LABEL181 - jump LABEL183 -LABEL181: - iconst 1985 - invoke 2618 -LABEL183: - iload 14 - iconst -1 - if_icmpeq LABEL187 - jump LABEL188 -LABEL187: - return -LABEL188: - iload 14 - enum_getoutputcount - istore 15 - iload 15 - define_array 105 - iconst 0 - istore 16 - iconst -1 - istore 17 - iconst 0 - istore 18 - iload 10 - iconst 0 - if_icmpeq LABEL203 - jump LABEL242 -LABEL203: - get_varbit 6718 - iconst 1 - if_icmpeq LABEL207 - jump LABEL242 -LABEL207: - iload 18 - iload 15 - if_icmplt LABEL211 - jump LABEL241 -LABEL211: - iconst 105 - iconst 111 - iload 14 - iload 18 - enum - istore 17 - invoke 3160 - iconst 1 - if_icmpeq LABEL221 - jump LABEL224 -LABEL221: - iload 17 - invoke 3159 - istore 17 -LABEL224: - iconst 0 - iload 17 - iconst 596 - oc_param - if_sethide - iload 16 - iload 18 - set_array_int - iload 16 - iconst 1 - add - istore 16 - iload 18 - iconst 1 - add - istore 18 - jump LABEL207 -LABEL241: - jump LABEL287 -LABEL242: - iload 18 - iload 15 - if_icmplt LABEL246 - jump LABEL287 -LABEL246: - iconst 105 - iconst 111 - iload 14 - iload 18 - enum - istore 17 - invoke 3160 - iconst 1 - if_icmpeq LABEL256 - jump LABEL259 -LABEL256: - iload 17 - invoke 3159 - istore 17 -LABEL259: - iload 17 - invoke 2619 - iconst 1 - if_icmpeq FILTER_SPELL - jump LABEL277 -FILTER_SPELL: - iconst 1 ; boolean the callback modifies - iload 17 - iconst 596 ; widgetID, to populate config - oc_param - iload 17 - iconst 601 ; spell_name - oc_param ; look up from object composition - sconst "shouldFilterSpell" - runelite_callback - pop_string ; pop the name - pop_int ; and the widgetID - iconst 1 ; default true, so the script still functions without plugin on - if_icmpeq LABEL264 - jump LABEL277 -LABEL264: - iconst 0 - iload 17 - iconst 596 - oc_param - if_sethide - iload 16 - iload 18 - set_array_int - iload 16 - iconst 1 - add - istore 16 - jump LABEL282 -LABEL277: - iconst 1 - iload 17 - iconst 596 - oc_param - if_sethide -LABEL282: - iload 18 - iconst 1 - add - istore 18 - jump LABEL242 -LABEL287: - iload 16 - iconst 2 - if_icmpge LABEL291 - jump LABEL299 -LABEL291: - iconst 0 - iconst 0 - iload 16 - iconst 1 - sub - iload 14 - invoke 2621 - jump LABEL330 -LABEL299: - iload 16 - iconst 0 - if_icmple LABEL303 - jump LABEL330 -LABEL303: - iload 0 - iconst 4 - iconst 0 - cc_create - iconst 0 - iconst 0 - iconst 1 - iconst 1 - cc_setsize - iconst 0 - iconst 0 - iconst 1 - iconst 1 - cc_setposition - iconst 16750623 - cc_setcolour - iconst 495 - cc_settextfont - iconst 1 - cc_settextshadow - iconst 1 - iconst 1 - iconst 0 - cc_settextalign - sconst "No spells match your selected filters." - cc_settext - return -LABEL330: - iconst 24 - istore 19 - iconst 0 - istore 20 - iconst 0 - istore 21 - iconst 0 - istore 22 - iconst 0 - istore 23 - iload 12 - istore 24 - iconst 0 - sconst "isMobileSpellbookEnabled" - runelite_callback - iconst 1 - if_icmpeq LABEL346 - jump LABEL436 -LABEL346: - iload 16 - iconst 15 - if_icmple LABEL350 - jump LABEL355 -LABEL350: - iconst 40 - iconst 3 - sconst "resizeSpell" - runelite_callback - istore 20 - istore 19 - jump LABEL374 -LABEL355: - iload 16 - iconst 20 - if_icmple LABEL359 - jump LABEL364 -LABEL359: - iconst 40 - iconst 4 - istore 20 - istore 19 - jump LABEL374 -LABEL364: - iconst 4 - iconst 7 - iload 16 - iconst 8 - add - iconst 9 - div - invoke 1046 - invoke 1045 - istore 20 -LABEL374: - iconst 0 - iconst 5 - iconst 7 - iload 19 - scale - iload 11 - iload 19 - iload 20 - multiply - sub - iload 20 - iconst 1 - sub - div - invoke 1046 - invoke 1045 - istore 23 - iconst 1 - iload 16 - iload 20 - iconst 1 - sub - add - iload 20 - div - invoke 1045 - istore 21 - iload 21 - iconst 2 - if_icmpge LABEL405 - jump LABEL419 -LABEL405: - iconst 0 - iload 23 - iload 12 - iload 19 - iload 21 - multiply - sub - iload 21 - iconst 1 - sub - div - invoke 1046 - invoke 1045 - istore 22 -LABEL419: - iload 21 - iload 19 - multiply - iload 21 - iconst 1 - sub - iload 22 - multiply - add - istore 24 - iconst 0 - iconst 0 - iconst 1 - iconst 1 - iload 0 - if_setposition - jump LABEL591 -LABEL436: - get_varbit 6718 - iconst 1 - if_icmpeq LABEL440 - jump LABEL512 -LABEL440: - get_varbit 4070 - switch - 1: LABEL457 - 2: LABEL472 - 3: LABEL487 - iconst 7 - iconst 10 - iconst 0 - iconst 0 - istore 22 - istore 23 - istore 21 - istore 20 - iconst 1 - iconst 15 - iconst 1 - iconst 0 - iload 0 - if_setposition - jump LABEL501 -LABEL457: - iconst 4 - iconst 7 - iconst 20 - iconst 4 - istore 22 - istore 23 - istore 21 - istore 20 - iconst 2 - iconst 8 - iconst 1 - iconst 0 - iload 0 - if_setposition - jump LABEL501 -LABEL472: - iconst 6 - iconst 8 - iconst 6 - iconst 5 - istore 22 - istore 23 - istore 21 - istore 20 - iconst 0 - iconst 8 - iconst 1 - iconst 0 - iload 0 - if_setposition - jump LABEL501 -LABEL487: - iconst 4 - iconst 9 - iconst 21 - iconst 5 - istore 22 - istore 23 - istore 21 - istore 20 - iconst 0 - iconst 3 - iconst 1 - iconst 0 - iload 0 - if_setposition -LABEL501: - iload 21 - iload 19 - multiply - iload 21 - iconst 1 - sub - iload 22 - multiply - add - istore 24 - jump LABEL591 -LABEL512: - iload 16 - iconst 28 - if_icmple LABEL516 - jump LABEL519 -LABEL516: - iconst 4 - istore 20 - jump LABEL529 -LABEL519: - iconst 4 - iconst 7 - iload 16 - iconst 8 - add - iconst 9 - div - invoke 1046 - invoke 1045 - istore 20 -LABEL529: - iconst 0 - iload 19 - iload 11 - iload 19 - iload 20 - multiply - sub - iload 20 - iconst 1 - sub - div - invoke 1046 - invoke 1045 - istore 23 - iconst 1 - iload 16 - iload 20 - iconst 1 - sub - add - iload 20 - div - invoke 1045 - istore 21 - iload 21 - iconst 2 - if_icmpge LABEL557 - jump LABEL571 -LABEL557: - iconst 0 - iload 23 - iload 12 - iload 19 - iload 21 - multiply - sub - iload 21 - iconst 1 - sub - div - invoke 1046 - invoke 1045 - istore 22 -LABEL571: - iload 21 - iload 19 - multiply - iload 21 - iconst 1 - sub - iload 22 - multiply - add - iload 12 - iconst 30 - sub - invoke 1045 - istore 24 - iconst 0 - iconst 0 - iconst 1 - iconst 1 - iload 0 - if_setposition -LABEL591: - iload 20 - iload 19 - multiply - iload 20 - iconst 1 - sub - iload 23 - multiply - add ; start of the label until here calcs total width - iload 24 ; total height - sconst "setSpellAreaSize" - runelite_callback - iconst 0 - iconst 0 - iload 0 - if_setsize - iconst -1 - istore 25 - iload 19 - iload 23 - add - istore 26 - iload 19 - iload 22 - add - istore 27 - iconst 0 - istore 28 - iconst 0 - istore 29 - iconst 0 - istore 18 -LABEL621: - iload 18 - iload 16 - if_icmplt LABEL625 - jump LABEL762 -LABEL625: - iconst 105 - iconst 111 - iload 14 - iload 18 - get_array_int - enum - istore 17 - invoke 3160 - iconst 1 - if_icmpeq LABEL636 - jump LABEL639 -LABEL636: - iload 17 - invoke 3159 - istore 17 -LABEL639: - iload 17 - iconst 596 - oc_param - istore 25 - iload 19 - iload 19 - iload 25 - sconst "resizeIndividualSpells" - runelite_callback - pop_int - iconst 0 - iconst 0 - iload 25 - if_setsize - iload 18 - iload 20 - mod - iload 26 - multiply - iload 18 - iload 20 - div - iload 27 - multiply - istore 29 - istore 28 - iload 28 - iload 29 - iconst 0 - iconst 0 - iload 25 - sconst "setSpellPosition" - runelite_callback - if_setposition - iload 17 - iload 25 - invoke 2614 - iconst 1 - if_icmpeq LABEL673 - jump LABEL689 -LABEL673: - iload 19 - iconst 40 - if_icmpge LABEL677 - jump LABEL683 -LABEL677: - iload 17 - iconst 599 - oc_param - iload 25 - if_setgraphic - jump LABEL688 -LABEL683: - iload 17 - iconst 597 - oc_param - iload 25 - if_setgraphic -LABEL688: - jump LABEL704 -LABEL689: - iload 19 - iconst 40 - if_icmpge LABEL693 - jump LABEL699 -LABEL693: - iload 17 - iconst 600 - oc_param - iload 25 - if_setgraphic - jump LABEL704 -LABEL699: - iload 17 - iconst 598 - oc_param - iload 25 - if_setgraphic -LABEL704: - iload 25 - invoke 2615 - iload 10 - iconst 1 - if_icmpeq LABEL710 - jump LABEL737 -LABEL710: - iload 1 - iconst 5 - iload 18 - cc_create - iload 19 - iload 19 - iconst 0 - iconst 0 - cc_setsize - iload 28 - iload 29 - iconst 0 - iconst 0 - cc_setposition - iload 17 - iconst 1 - cc_setobject - iconst 255 - cc_settrans - iconst 2612 - iload 17 - iload 1 - iload 2 - iload 12 - sconst "oIIi" - cc_setonclick - jump LABEL757 -LABEL737: - iconst 2622 - iconst 1 - iload 17 - iconst -2147483645 - iconst -1 - iload 2 - iload 12 - sconst "1oIiIi" - iload 25 - if_setonmouserepeat - iconst 2622 - iconst 0 - iload 17 - iconst -2147483645 - iconst -1 - iload 2 - iload 12 - sconst "1oIiIi" - iload 25 - if_setonmouseleave -LABEL757: - iload 18 - iconst 1 - add - istore 18 - jump LABEL621 -LABEL762: - return diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.hash b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.hash new file mode 100644 index 0000000000..83a3a4c586 --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.hash @@ -0,0 +1 @@ +5464D17DCD348F352EFFE6AA6AEEC5A5609ECBA30EAC2CB2B3D479D2C0DDDA9A \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.rs2asm b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.rs2asm new file mode 100644 index 0000000000..1763832ed6 --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomListener.rs2asm @@ -0,0 +1,75 @@ +.id 3898 +.int_stack_count 6 +.string_stack_count 0 +.int_var_count 11 +.string_var_count 0 + get_varbit 4606 + iconst 0 + if_icmpne LABEL4 + jump LABEL5 +LABEL4: + return +LABEL5: + iconst 512 + istore 6 + iconst 512 + istore 7 + iload 2 + iconst 16 + sub + istore 8 + iconst 0 + iload 3 + invoke 1045 + istore 3 + iload 2 + iconst 16 + sub + iload 3 + invoke 1046 + istore 3 + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + istore 9 + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + istore 10 + iload 3 + iload 9 + multiply + iload 8 + div + iconst 128 + sconst "outerZoomLimit" + runelite_callback + add + istore 6 + iload 3 + iload 10 + multiply + iload 8 + div + iconst 128 + sconst "outerZoomLimit" + runelite_callback + add + istore 7 + iload 0 + iload 1 + iload 7 + iload 6 + iload 2 + iload 4 + iload 5 + invoke 3899 + return diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.hash b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.hash new file mode 100644 index 0000000000..e6ff467a74 --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.hash @@ -0,0 +1 @@ +AA98471D04D9CB1172253D0B479EFD2D58394BDD2852F3AE8CD2B2D46FA826C3 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.rs2asm b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.rs2asm new file mode 100644 index 0000000000..aef3b1ab39 --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSetter.rs2asm @@ -0,0 +1,96 @@ +.id 3899 +.int_stack_count 7 +.string_stack_count 0 +.int_var_count 11 +.string_var_count 0 + get_varbit 4606 + iconst 0 + if_icmpne LABEL4 + jump LABEL5 +LABEL4: + return +LABEL5: + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iload 2 + invoke 1046 + istore 2 + iconst 128 + sconst "outerZoomLimit" + runelite_callback + iload 2 + invoke 1045 + istore 2 + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iload 3 + invoke 1046 + istore 3 + iconst 128 + sconst "outerZoomLimit" + runelite_callback + iload 3 + invoke 1045 + istore 3 + iload 2 + iload 3 + viewport_setfov + iconst 0 + istore 7 + iconst 0 + istore 8 + viewport_geteffectivesize + istore 8 + istore 7 + iload 8 + iconst 334 + sub + istore 9 + iload 9 + iconst 0 + if_icmplt LABEL39 + jump LABEL42 +LABEL39: + iconst 0 + istore 9 + jump LABEL48 +LABEL42: + iload 9 + iconst 100 + if_icmpgt LABEL46 + jump LABEL48 +LABEL46: + iconst 100 + istore 9 +LABEL48: + iload 2 + iload 3 + iload 2 + sub + iload 9 + multiply + iconst 100 + div + add + istore 10 + iconst 25 + iconst 25 + iload 10 + multiply + iconst 256 + div + add + cam_setfollowheight + iload 2 + iload 3 + set_varc_int 74 + set_varc_int 73 + iload 0 + iload 1 + iload 4 + iload 5 + iload 6 + invoke 3900 + return diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.hash b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.hash new file mode 100644 index 0000000000..6b984c313e --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.hash @@ -0,0 +1 @@ +03D7F1AF9E8405CB4A74779254E8C65563123F865CC0181186238B038A740755 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.rs2asm b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.rs2asm new file mode 100644 index 0000000000..84406d0edc --- /dev/null +++ b/runelite-client/src/main/resources/scripts/NewOptionsPanelZoomSlider.rs2asm @@ -0,0 +1,78 @@ +.id 3900 +.int_stack_count 5 +.string_stack_count 0 +.int_var_count 11 +.string_var_count 0 + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + istore 5 + iconst 896 + sconst "innerZoomLimit" + runelite_callback + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + istore 6 + iload 2 + iconst 16 + sub + istore 7 + iconst 0 + istore 8 + iconst 0 + istore 9 + viewport_geteffectivesize + istore 9 + istore 8 + iconst 0 + istore 10 + iload 8 + iconst 334 + if_icmpgt LABEL25 + jump LABEL34 +LABEL25: + get_varc_int 74 + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + iload 7 + multiply + iload 5 + div + istore 10 + jump LABEL42 +LABEL34: + get_varc_int 73 + iconst 128 + sconst "outerZoomLimit" + runelite_callback + sub + iload 7 + multiply + iload 6 + div + istore 10 +LABEL42: + iload 0 + iload 1 + cc_find + iconst 1 + if_icmpeq LABEL48 + jump LABEL55 +LABEL48: + iload 4 + iload 10 + add + iload 3 + iconst 0 + iconst 0 + cc_setposition +LABEL55: + return diff --git a/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.hash b/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.hash index f4a40ef439..1f6beef765 100644 --- a/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.hash +++ b/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.hash @@ -1 +1 @@ -AA7DEC763D0B598D920956D8D46E890C502B71CB7D022A89F7930BD91E1F8468 \ No newline at end of file +A1B6D1B291AA3594728DDEA47049E17119F5CCB6F8E757E1524FA89DE92F9A34 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.rs2asm b/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.rs2asm index 3abf21a570..32595fbc27 100644 --- a/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.rs2asm +++ b/runelite-client/src/main/resources/scripts/OptionsPanelZoomUpdater.rs2asm @@ -23,9 +23,9 @@ runelite_callback sub istore 1 - iconst 17104910 + iconst 7602235 if_getwidth - iconst 17104911 + iconst 7602236 if_getwidth sub istore 2 @@ -78,6 +78,6 @@ LABEL44: iconst 0 iconst 0 iconst 0 - iconst 17104911 + iconst 7602236 if_setposition return diff --git a/runelite-client/src/main/resources/scripts/SendPublicMessage.rs2asm b/runelite-client/src/main/resources/scripts/SendPublicMessage.rs2asm deleted file mode 100644 index eccfc90457..0000000000 --- a/runelite-client/src/main/resources/scripts/SendPublicMessage.rs2asm +++ /dev/null @@ -1,39 +0,0 @@ -;;; -; -; Copyright (c) 2019, Lucas -; 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. -; -;;;;;;;;;;;;;;;;;;;;;;;;;;; -; ; -; Send a public message ; -; ; -;;;;;;;;;;;;;;;;;;;;;;;;;;; -.id 13337 -.int_stack_count 0 -.string_stack_count 1 -.int_var_count 0 -.string_var_count 1 - sload 0 - iconst 0 - chat_sendpublic - return diff --git a/runelite-client/src/main/resources/scripts/SpecbarRedraw.hash b/runelite-client/src/main/resources/scripts/SpecbarRedraw.hash deleted file mode 100644 index 9715ca5575..0000000000 --- a/runelite-client/src/main/resources/scripts/SpecbarRedraw.hash +++ /dev/null @@ -1 +0,0 @@ -1EDA8CA79506CC62A192A844B88AC924BAD3060D9E32150C43458E135908329B \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/SpecbarRedraw.rs2asm b/runelite-client/src/main/resources/scripts/SpecbarRedraw.rs2asm deleted file mode 100644 index b71ba6aae4..0000000000 --- a/runelite-client/src/main/resources/scripts/SpecbarRedraw.rs2asm +++ /dev/null @@ -1,247 +0,0 @@ -.id 187 -.int_stack_count 1 -.string_stack_count 0 -.int_var_count 5 -.string_var_count 1 - iconst 0 - istore 1 - map_members - iconst 1 - if_icmpeq LABEL9 - get_varbit 5314 - iconst 1 - if_icmpeq LABEL9 - jump LABEL14 -LABEL9: - iconst 94 - iconst 3 - inv_getobj - invoke 3648 - istore 1 -LABEL14: - iload 1 - iconst 0 - if_icmple LABEL18 - jump LABEL28 -LABEL18: - iconst 1 ; What we compare the boolean with - iconst 0 ; Boolean - sconst "drawSpecbarAnyway" - runelite_callback - if_icmpeq LABEL38 - iconst 1 - iconst 38862883 - if_sethide - iconst 190 - iconst 28 - iconst 0 - iconst 0 - iconst 38862850 - if_setsize - return -LABEL28: - invoke 1972 - iconst 1 - if_icmpeq LABEL32 - jump LABEL38 -LABEL32: - iconst 190 - iconst 16 - iconst 0 - iconst 0 - iconst 38862850 - if_setsize -LABEL38: - iconst 0 - istore 2 - iconst 38862883 - if_gethide - iconst 1 - if_icmpeq LABEL45 - jump LABEL47 -LABEL45: - iconst 1 - istore 2 -LABEL47: - iconst 0 - iconst 38862883 - if_sethide - get_varp 301 - iconst 0 - if_icmpgt LABEL54 - jump LABEL58 -LABEL54: - iconst 16776960 - iconst 38862888 - if_setcolour - jump LABEL61 -LABEL58: - iconst 16 - iconst 38862888 - if_setcolour -LABEL61: - get_varp 300 - istore 3 - iload 3 - iconst 0 - if_icmplt LABEL67 - jump LABEL69 -LABEL67: - iconst 0 - istore 3 -LABEL69: - sconst "Special Attack: " - iload 3 - iconst 10 - div - tostring - sconst "%" - join_string 3 - iconst 38862888 - if_settext - iload 0 - iload 3 - iload 2 - invoke 189 - iconst 38862884 - iconst 0 - invoke 835 - pop_int ; Specbar is fully built here - iload 1 - iconst 0 - if_icmple RETURN ; Return if the weapon isn't supposed to have a spec - jump CONTINUE ; Idk why I'm doing it like this but it's the jagex way - RETURN: - return - CONTINUE: - iload 3 - iload 1 - if_icmpge LABEL86 - jump LABEL90 -LABEL86: - iconst 3767611 - iconst 38862887 - if_setcolour - jump LABEL93 -LABEL90: - iconst 12907 - iconst 38862887 - if_setcolour -LABEL93: - iconst 38862884 - iconst 0 - invoke 835 - pop_int - iconst 94 - iconst 3 - inv_getobj - istore 4 - iconst 111 - iconst 115 - iconst 1739 - iload 4 - enum - sconst " (" - iload 4 - invoke 3648 - iconst 10 - div - tostring - sconst "%)" - join_string 4 - sstore 0 - iload 4 - switch - 22737: LABEL118 - 22740: LABEL118 - 22743: LABEL118 - 22731: LABEL118 - 22734: LABEL118 - jump LABEL126 -LABEL118: - iconst 111 - iconst 115 - iconst 1739 - iload 4 - enum - sconst " 5-100% " - join_string 2 - sstore 0 -LABEL126: - get_varbit 5712 - iconst 0 - if_icmpeq LABEL130 - jump LABEL192 -LABEL130: - iload 4 - iconst 11235 - if_icmpeq LABEL146 - iload 4 - iconst 20408 - if_icmpeq LABEL146 - iload 4 - iconst 12765 - if_icmpeq LABEL146 - iload 4 - iconst 12768 - if_icmpeq LABEL146 - iload 4 - iconst 12767 - if_icmpeq LABEL146 - jump LABEL176 -LABEL146: - iconst 94 - iconst 13 - inv_getobj - iconst 11212 - if_icmpeq LABEL167 - iconst 94 - iconst 13 - inv_getobj - iconst 11227 - if_icmpeq LABEL167 - iconst 94 - iconst 13 - inv_getobj - iconst 11228 - if_icmpeq LABEL167 - iconst 94 - iconst 13 - inv_getobj - iconst 11229 - if_icmpeq LABEL167 - jump LABEL176 -LABEL167: - sconst "Descent of Dragons: Deal a double attack with dragon arrows that inflicts up to 50% more damage (minimum damage of 8 per hit). (" - iload 4 - invoke 3648 - iconst 10 - div - tostring - sconst "%)" - join_string 3 - sstore 0 -LABEL176: - iconst 526 - iconst -2147483645 - iconst -1 - iconst 38862890 - sload 0 - iconst 25 - iconst 160 - sconst "IiIsii" - iconst 38862883 - if_setonmouserepeat - iconst 40 - iconst 38862890 - sconst "I" - iconst 38862883 - if_setonmouseleave - jump LABEL196 -LABEL192: - iconst -1 - sconst "" - iconst 38862883 - if_setonmouserepeat -LABEL196: - return diff --git a/runelite-client/src/main/resources/scripts/UpdateBankPin.hash b/runelite-client/src/main/resources/scripts/UpdateBankPin.hash deleted file mode 100644 index 92d8396619..0000000000 --- a/runelite-client/src/main/resources/scripts/UpdateBankPin.hash +++ /dev/null @@ -1 +0,0 @@ -2A73E4C408881BB0EBDDE9BB05910C55F0313FA90BA907B722859E0183A713E7 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/UpdateBankPin.rs2asm b/runelite-client/src/main/resources/scripts/UpdateBankPin.rs2asm deleted file mode 100644 index d2f1cd24b3..0000000000 --- a/runelite-client/src/main/resources/scripts/UpdateBankPin.rs2asm +++ /dev/null @@ -1,494 +0,0 @@ -.id 653 -.int_stack_count 19 -.string_stack_count 0 -.int_var_count 22 -.string_var_count 0 - iload 0 - sconst "bankpin" - runelite_callback - iconst 3 - if_icmpeq LABEL4 - jump LABEL20 -LABEL4: - sconst "Finally, the FOURTH digit." - iload 18 - if_settext - sconst "*" - iload 14 - if_settext - sconst "*" - iload 15 - if_settext - sconst "*" - iload 16 - if_settext - sconst "?" - iload 17 - if_settext - jump LABEL128 -LABEL20: - iload 0 - iconst 2 - if_icmpeq LABEL24 - jump LABEL40 -LABEL24: - sconst "Time for the THIRD digit." - iload 18 - if_settext - sconst "*" - iload 14 - if_settext - sconst "*" - iload 15 - if_settext - sconst "?" - iload 16 - if_settext - sconst "?" - iload 17 - if_settext - jump LABEL128 -LABEL40: - iload 0 - iconst 1 - if_icmpeq LABEL44 - jump LABEL60 -LABEL44: - sconst "Now click the SECOND digit." - iload 18 - if_settext - sconst "*" - iload 14 - if_settext - sconst "?" - iload 15 - if_settext - sconst "?" - iload 16 - if_settext - sconst "?" - iload 17 - if_settext - jump LABEL128 -LABEL60: - iload 0 - iconst 0 - if_icmpeq LABEL64 - jump LABEL80 -LABEL64: - sconst "First click the FIRST digit." - iload 18 - if_settext - sconst "?" - iload 14 - if_settext - sconst "?" - iload 15 - if_settext - sconst "?" - iload 16 - if_settext - sconst "?" - iload 17 - if_settext - jump LABEL128 -LABEL80: - sconst "Submitting..." - iload 18 - if_settext - sconst "*" - iload 14 - if_settext - sconst "*" - iload 15 - if_settext - sconst "*" - iload 16 - if_settext - sconst "*" - iload 17 - if_settext - iload 4 - cc_deleteall - iload 5 - cc_deleteall - iload 6 - cc_deleteall - iload 7 - cc_deleteall - iload 8 - cc_deleteall - iload 9 - cc_deleteall - iload 10 - cc_deleteall - iload 11 - cc_deleteall - iload 12 - cc_deleteall - iload 13 - cc_deleteall - iconst -1 - sconst "" - iload 2 - if_setonop - iload 2 - if_clearops - iconst -1 - sconst "" - iload 3 - if_setonop - iload 3 - if_clearops - return -LABEL128: - iconst 10 - define_array 73 - iconst 0 - iload 4 - set_array_int - iconst 1 - iload 5 - set_array_int - iconst 2 - iload 6 - set_array_int - iconst 3 - iload 7 - set_array_int - iconst 4 - iload 8 - set_array_int - iconst 5 - iload 9 - set_array_int - iconst 6 - iload 10 - set_array_int - iconst 7 - iload 11 - set_array_int - iconst 8 - iload 12 - set_array_int - iconst 9 - iload 13 - set_array_int - iconst 0 - istore 19 - iconst -1 - istore 20 - iconst 20 - istore 21 -LABEL166: - iload 21 - iconst 0 - if_icmpgt LABEL170 - jump LABEL188 -LABEL170: - iload 21 - iconst 1 - sub - istore 21 - iconst 9 - random - istore 19 - iconst 9 - get_array_int - istore 20 - iconst 9 - iload 19 - get_array_int - set_array_int - iload 19 - iload 20 - set_array_int - jump LABEL166 -LABEL188: - iconst 0 - get_array_int - iconst 0 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 1 - get_array_int - iconst 1 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 2 - get_array_int - iconst 2 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 3 - get_array_int - iconst 3 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 4 - get_array_int - iconst 4 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 5 - get_array_int - iconst 5 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 6 - get_array_int - iconst 6 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 7 - get_array_int - iconst 7 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 8 - get_array_int - iconst 8 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 9 - get_array_int - iconst 9 - iload 0 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - invoke 679 - iconst 1 - iload 2 - if_gettext - iload 2 - if_setop - iconst 686 - iconst 12345 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - sconst "iIIIIIIIIIIIIIIIII" - iload 2 - if_setonop - iconst 1 - iload 3 - if_gettext - iload 3 - if_setop - iconst 686 - iconst 54321 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - iload 11 - iload 12 - iload 13 - iload 14 - iload 15 - iload 16 - iload 17 - iload 18 - sconst "iIIIIIIIIIIIIIIIII" - iload 3 - if_setonop - sconst "bankPinButtons" ; push event name - runelite_callback ; invoke callback - return diff --git a/runelite-client/src/main/resources/scripts/XpDropChanged.hash b/runelite-client/src/main/resources/scripts/XpDropChanged.hash deleted file mode 100644 index a1f5ae3d57..0000000000 --- a/runelite-client/src/main/resources/scripts/XpDropChanged.hash +++ /dev/null @@ -1 +0,0 @@ -A4641387DC1A542BDEB6F3728D23944F968EF2FD3EA2868B5B6AAF6961D0C6E0 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/XpDropChanged.rs2asm b/runelite-client/src/main/resources/scripts/XpDropChanged.rs2asm deleted file mode 100644 index 401c12d4de..0000000000 --- a/runelite-client/src/main/resources/scripts/XpDropChanged.rs2asm +++ /dev/null @@ -1,919 +0,0 @@ -.id 1004 -.int_stack_count 34 -.string_stack_count 0 -.int_var_count 48 -.string_var_count 1 - iload 0 - iconst 1 - if_icmpeq LABEL4 - jump LABEL9 -LABEL4: - get_varc_int 207 - iconst -1 - if_icmpeq LABEL8 - jump LABEL9 -LABEL8: - return -LABEL9: - iconst 23 - iconst 1 - add - istore 34 - iload 34 - define_array 83 - iload 34 - define_array 65641 - iconst 0 - istore 35 - iconst 0 - istore 36 - iload 0 - iconst 1 - if_icmpeq LABEL25 - jump LABEL81 -LABEL25: - get_varc_int 207 - iconst -1 - if_icmpne LABEL29 - jump LABEL80 -LABEL29: - get_varc_int 207 - iconst 269500481 - if_icmpeq LABEL33 - jump LABEL39 -LABEL33: - iload 1 - iload 3 - iload 8 - iload 9 - invoke 997 - jump LABEL65 -LABEL39: - iload 35 - iconst 105 - iconst 83 - iconst 681 - get_varc_int 207 - coordx - enum - set_array_int - iload 35 - get_varc_int 207 - coordy - set_array_int 1 - iload 35 - get_array_int - iconst -1 - if_icmpne LABEL56 - jump LABEL65 -LABEL56: - iload 35 - get_array_int 1 - iconst 0 - if_icmpgt LABEL61 - jump LABEL65 -LABEL61: - iload 35 - iconst 1 - add - istore 35 -LABEL65: - get_varc_int 208 - get_varc_int 209 - get_varc_int 210 - get_varc_int 211 - get_varc_int 212 - get_varc_int 213 - iconst -1 - set_varc_int 213 - set_varc_int 212 - set_varc_int 211 - set_varc_int 210 - set_varc_int 209 - set_varc_int 208 - set_varc_int 207 - jump LABEL25 -LABEL80: - jump LABEL518 -LABEL81: - sconst "newXpDrop" - runelite_callback - iconst 10 - stat_xp - iload 25 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL90 - jump LABEL100 -LABEL90: - iload 35 - iconst 10 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL100: - iconst 0 - stat_xp - iload 11 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL109 - jump LABEL119 -LABEL109: - iload 35 - iconst 0 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL119: - iconst 2 - stat_xp - iload 12 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL128 - jump LABEL138 -LABEL128: - iload 35 - iconst 2 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL138: - iconst 4 - stat_xp - iload 13 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL147 - jump LABEL157 -LABEL147: - iload 35 - iconst 4 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL157: - iconst 6 - stat_xp - iload 14 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL166 - jump LABEL176 -LABEL166: - iload 35 - iconst 6 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL176: - iconst 1 - stat_xp - iload 15 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL185 - jump LABEL195 -LABEL185: - iload 35 - iconst 1 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL195: - iconst 3 - stat_xp - iload 16 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt HP_XP_GAINED - jump LABEL214 -HP_XP_GAINED: - iload 35 - iconst 3 - set_array_int - iload 35 - iload 36 - sconst "hpXpGained" - runelite_callback - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL214: - iconst 5 - stat_xp - iload 17 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL223 - jump LABEL233 -LABEL223: - iload 35 - iconst 5 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL233: - iconst 16 - stat_xp - iload 18 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL242 - jump LABEL252 -LABEL242: - iload 35 - iconst 16 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL252: - iconst 15 - stat_xp - iload 19 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL261 - jump LABEL271 -LABEL261: - iload 35 - iconst 15 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL271: - iconst 17 - stat_xp - iload 20 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL280 - jump LABEL290 -LABEL280: - iload 35 - iconst 17 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL290: - iconst 12 - stat_xp - iload 21 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL299 - jump LABEL309 -LABEL299: - iload 35 - iconst 12 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL309: - iconst 20 - stat_xp - iload 22 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL318 - jump LABEL328 -LABEL318: - iload 35 - iconst 20 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL328: - iconst 14 - stat_xp - iload 23 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL337 - jump LABEL347 -LABEL337: - iload 35 - iconst 14 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL347: - iconst 13 - stat_xp - iload 24 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL356 - jump LABEL366 -LABEL356: - iload 35 - iconst 13 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL366: - iconst 7 - stat_xp - iload 26 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL375 - jump LABEL385 -LABEL375: - iload 35 - iconst 7 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL385: - iconst 11 - stat_xp - iload 27 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL394 - jump LABEL404 -LABEL394: - iload 35 - iconst 11 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL404: - iconst 8 - stat_xp - iload 28 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL413 - jump LABEL423 -LABEL413: - iload 35 - iconst 8 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL423: - iconst 9 - stat_xp - iload 29 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL432 - jump LABEL442 -LABEL432: - iload 35 - iconst 9 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL442: - iconst 18 - stat_xp - iload 30 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL451 - jump LABEL461 -LABEL451: - iload 35 - iconst 18 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL461: - iconst 19 - stat_xp - iload 31 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL470 - jump LABEL480 -LABEL470: - iload 35 - iconst 19 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL480: - iconst 22 - stat_xp - iload 32 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL489 - jump LABEL499 -LABEL489: - iload 35 - iconst 22 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL499: - iconst 21 - stat_xp - iload 33 - sub - istore 36 - iload 36 - iconst 0 - if_icmpgt LABEL508 - jump LABEL518 -LABEL508: - iload 35 - iconst 21 - set_array_int - iload 35 - iload 36 - set_array_int 1 - iload 35 - iconst 1 - add - istore 35 -LABEL518: - iconst 0 - istore 37 - iconst 0 - istore 38 - iconst 494 - istore 39 - iconst 494 - istore 40 - iconst 16 - istore 41 - iconst 0 - istore 42 - iconst 0 - istore 43 - iconst 0 - istore 44 - iconst 0 - istore 45 - iconst 0 - istore 46 - iconst -1 - istore 47 - sconst "" - sstore 0 - iload 35 - iconst 0 - if_icmpgt LABEL546 - jump LABEL789 -LABEL546: - iload 16 - iconst 0 - if_icmpgt LABEL550 - jump LABEL789 -LABEL550: - clientclock - get_varc_int 76 - sub - iconst 10 - if_icmpgt LABEL556 - jump LABEL789 -LABEL556: - get_varbit 4693 - iconst 1 - if_icmpeq LABEL560 - jump LABEL571 -LABEL560: - invoke 1972 - iconst 0 - if_icmpeq LABEL564 - jump LABEL571 -LABEL564: - iconst 495 - iconst 495 - iconst 25 - istore 41 - istore 40 - istore 39 - jump LABEL585 -LABEL571: - get_varbit 4693 - iconst 2 - if_icmpeq LABEL575 - jump LABEL585 -LABEL575: - invoke 1972 - iconst 0 - if_icmpeq LABEL579 - jump LABEL585 -LABEL579: - iconst 496 - iconst 496 - iconst 25 - istore 41 - istore 40 - istore 39 -LABEL585: - iload 8 - if_getheight - istore 42 - iload 42 - iconst 100 - if_icmplt LABEL592 - jump LABEL594 -LABEL592: - iconst 100 - istore 42 -LABEL594: - iload 41 - iconst 105 - iconst 105 - iconst 1171 - get_varbit 4722 - enum - multiply - iload 42 - div - iconst 1 - add - istore 43 -LABEL606: - iload 37 - iload 35 - if_icmplt LABEL610 - jump LABEL784 -LABEL610: - iload 38 - iconst 0 - if_icmpeq LABEL614 - jump LABEL623 -LABEL614: - iload 0 - iconst 0 - if_icmpeq LABEL618 - jump LABEL623 -LABEL618: - iload 37 - get_array_int - set_varc_int 72 - iconst 1 - istore 38 -LABEL623: - get_varc_int 71 - iconst 0 - if_icmpgt LABEL627 - jump LABEL638 -LABEL627: - get_varc_int 71 - clientclock - iload 43 - sub - if_icmpgt LABEL633 - jump LABEL638 -LABEL633: - get_varc_int 71 - iload 43 - add - istore 44 - jump LABEL640 -LABEL638: - clientclock - istore 44 -LABEL640: - iload 44 - clientclock - iload 43 - iload 10 - multiply - add - if_icmplt LABEL648 - jump LABEL781 -LABEL648: - iconst 105 - iconst 73 - iconst 1163 - get_varc_int 70 - enum - istore 47 - iconst 0 - iload 47 - if_sethide - iload 37 - get_array_int 1 - istore 46 - iload 47 - iconst 5 - iconst 1 - cc_create - iconst 83 - iconst 100 - iconst 255 - iload 37 - get_array_int - enum - cc_setgraphic - iconst 0 - iconst 0 - iconst 0 - iconst 5 - cc_setposition - iconst 1 - cc_sethide - iconst 1 - istore 45 - iload 37 - iconst 1 - add - istore 37 -LABEL684: - get_varbit 4696 - iconst 1 - if_icmpeq LABEL688 - jump LABEL732 -LABEL688: - iload 37 - iload 35 - if_icmplt LABEL692 - jump LABEL732 -LABEL692: - iload 45 - iconst 5 - if_icmplt LABEL696 - jump LABEL732 -LABEL696: - iload 46 - iconst 1000000 - if_icmplt LABEL700 - jump LABEL732 -LABEL700: - iload 46 - iload 37 - get_array_int 1 - add - istore 46 - iload 45 - iconst 1 - add - istore 45 - iload 47 - iconst 5 - iload 45 - cc_create - iconst 83 - iconst 100 - iconst 255 - iload 37 - get_array_int - enum - cc_setgraphic - iconst 0 - iconst 0 - iconst 0 - iconst 5 - cc_setposition - iconst 1 - cc_sethide - iload 37 - iconst 1 - add - istore 37 - jump LABEL684 -LABEL732: - iload 46 - sconst "," - invoke 46 - sconst "xpDropAddDamage" - runelite_callback - sstore 0 - iload 0 - iconst 1 - if_icmpeq LABEL740 - jump LABEL745 -LABEL740: - sconst "" - sconst " " - sload 0 - join_string 3 - sstore 0 -LABEL745: - iload 47 - iconst 0 - cc_find - iconst 1 - if_icmpeq LABEL751 - jump LABEL766 -LABEL751: - sload 0 - cc_settext - iconst 0 - iconst 0 - iconst 0 - iconst 5 - cc_setposition - iconst 1 - cc_sethide - iload 47 - iload 41 - iload 39 - iload 40 - sload 0 - invoke 996 -LABEL766: - iconst 1005 - iload 47 - iload 44 - sconst "Ii" - iload 47 - if_setontimer - iload 44 - set_varc_int 71 - get_varc_int 70 - iconst 1 - add - iload 10 - mod - set_varc_int 70 - jump LABEL783 -LABEL781: - iload 35 - istore 37 -LABEL783: - jump LABEL606 -LABEL784: - iload 1 - iload 3 - iload 8 - iload 9 - invoke 997 -LABEL789: - iload 0 - iconst 0 - if_icmpeq LABEL793 - jump LABEL812 -LABEL793: - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - invoke 999 - iload 1 - iload 2 - iload 3 - iload 4 - iload 5 - iload 6 - iload 7 - iload 8 - iload 9 - iload 10 - invoke 1003 -LABEL812: - return diff --git a/runelite-client/src/main/resources/scripts/options_allsounds.hash b/runelite-client/src/main/resources/scripts/options_allsounds.hash deleted file mode 100644 index 84863d9bd2..0000000000 --- a/runelite-client/src/main/resources/scripts/options_allsounds.hash +++ /dev/null @@ -1 +0,0 @@ -950ADB6A28E029005D24F99A65EF4D2AC4486EDC680D8770F4435F0300AA1299 \ No newline at end of file diff --git a/runelite-client/src/main/resources/scripts/options_allsounds.rs2asm b/runelite-client/src/main/resources/scripts/options_allsounds.rs2asm deleted file mode 100644 index 2d4e94e9ce..0000000000 --- a/runelite-client/src/main/resources/scripts/options_allsounds.rs2asm +++ /dev/null @@ -1,140 +0,0 @@ -.id 358 -.int_stack_count 6 -.string_stack_count 0 -.int_var_count 6 -.string_var_count 0 -; callback "optionsAllSounds" -; Used by the MusicPlugin to hide the vanilla (blue) volume handles -; Enable the MusicPlugin and go to the volume options panel. There should -; only be a green handle on the slider - iload 0 - sconst "optionsAllSounds" - runelite_callback - istore 0 - iload 0 - iconst 4 - if_icmpeq LABEL4 - jump LABEL20 -LABEL4: - iconst 687 - iload 1 - if_setgraphic - iconst 693 - iload 2 - if_setgraphic - iconst 694 - iload 3 - if_setgraphic - iconst 695 - iload 4 - if_setgraphic - iconst 696 - iload 5 - if_setgraphic - jump LABEL115 -LABEL20: - iload 0 - iconst 3 - if_icmpeq LABEL24 - jump LABEL40 -LABEL24: - iconst 692 - iload 1 - if_setgraphic - iconst 688 - iload 2 - if_setgraphic - iconst 694 - iload 3 - if_setgraphic - iconst 695 - iload 4 - if_setgraphic - iconst 696 - iload 5 - if_setgraphic - jump LABEL115 -LABEL40: - iload 0 - iconst 2 - if_icmpeq LABEL44 - jump LABEL60 -LABEL44: - iconst 692 - iload 1 - if_setgraphic - iconst 693 - iload 2 - if_setgraphic - iconst 689 - iload 3 - if_setgraphic - iconst 695 - iload 4 - if_setgraphic - iconst 696 - iload 5 - if_setgraphic - jump LABEL115 -LABEL60: - iload 0 - iconst 1 - if_icmpeq LABEL64 - jump LABEL80 -LABEL64: - iconst 692 - iload 1 - if_setgraphic - iconst 693 - iload 2 - if_setgraphic - iconst 694 - iload 3 - if_setgraphic - iconst 690 - iload 4 - if_setgraphic - iconst 696 - iload 5 - if_setgraphic - jump LABEL115 -LABEL80: - iload 0 - iconst 0 - if_icmpeq LABEL84 - jump LABEL100 -LABEL84: - iconst 692 - iload 1 - if_setgraphic - iconst 693 - iload 2 - if_setgraphic - iconst 694 - iload 3 - if_setgraphic - iconst 695 - iload 4 - if_setgraphic - iconst 691 - iload 5 - if_setgraphic - jump LABEL115 -LABEL100: - iconst 692 - iload 1 - if_setgraphic - iconst 693 - iload 2 - if_setgraphic - iconst 694 - iload 3 - if_setgraphic - iconst 695 - iload 4 - if_setgraphic - iconst 696 - iload 5 - if_setgraphic -LABEL115: - return diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java index 72712b78b4..4734e790b4 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java @@ -36,7 +36,7 @@ import net.runelite.api.mixins.Shadow; import net.runelite.api.util.Text; import net.runelite.rs.api.RSActor; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSNPC; import net.runelite.rs.api.RSPlayer; import net.runelite.rs.api.RSProjectile; @@ -101,7 +101,7 @@ public abstract class EntityHiderMixin implements RSScene @Copy("newGameObject") @Replace("newGameObject") - boolean copy$addEntityMarker(int var1, int var2, int var3, int var4, int var5, int x, int y, int var8, RSEntity entity, int var10, boolean var11, long var12, int var13) + boolean copy$addEntityMarker(int var1, int var2, int var3, int var4, int var5, int x, int y, int var8, RSRenderable entity, int var10, boolean var11, long var12, int var13) { final boolean shouldDraw = shouldDraw(entity, false); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/MenuMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/MenuMixin.java index bd9687bb28..87bd27d283 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/MenuMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/MenuMixin.java @@ -188,7 +188,7 @@ public abstract class MenuMixin implements RSClient getMenuTargets()[i] = entry.getTarget(); getMenuIdentifiers()[i] = entry.getIdentifier(); getMenuOpcodes()[i] = entry.getOpcode(); - getMenuArguments1()[i] = entry.getActionParam0(); + getMenuArguments1()[i] = entry.getActionParam(); getMenuArguments2()[i] = entry.getActionParam1(); getMenuForceLeftClick()[i] = entry.isForceLeftClick(); } @@ -215,7 +215,7 @@ public abstract class MenuMixin implements RSClient tempMenuAction.setOption(entry.getOption()); tempMenuAction.setOpcode(entry.getOpcode()); tempMenuAction.setIdentifier(entry.getIdentifier()); - tempMenuAction.setParam0(entry.getActionParam0()); + tempMenuAction.setParam0(entry.getActionParam()); tempMenuAction.setParam1(entry.getActionParam1()); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java index 90e39a4ea0..59d195e324 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java @@ -10,7 +10,7 @@ import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSBoundaryObject; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSModel; @Mixin(RSBoundaryObject.class) @@ -39,7 +39,7 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject @Inject public RSModel getModelA() { - RSEntity entity = getEntity1(); + RSRenderable entity = getRenderable(); if (entity == null) { return null; @@ -58,7 +58,7 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject @Inject public RSModel getModelB() { - RSEntity entity = getEntity2(); + RSRenderable entity = getRenderable2(); if (entity == null) { return null; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 6f751bb1c3..fc9d9d1cbd 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -67,7 +67,7 @@ import net.runelite.api.NPC; import net.runelite.api.NPCComposition; import net.runelite.api.NameableContainer; import net.runelite.api.Node; -import net.runelite.api.ObjectDefinition; +import net.runelite.api.ObjectComposition; import static net.runelite.api.Perspective.LOCAL_TILE_SIZE; import net.runelite.api.Player; import net.runelite.api.Point; @@ -754,7 +754,7 @@ public abstract class RSClientMixin implements RSClient entry.setTarget(menuTargets[i]); entry.setIdentifier(menuIdentifiers[i]); entry.setOpcode(menuTypes[i]); - entry.setActionParam0(params0[i]); + entry.setActionParam(params0[i]); entry.setActionParam1(params1[i]); entry.setForceLeftClick(leftClick[i]); } @@ -785,7 +785,7 @@ public abstract class RSClientMixin implements RSClient menuTargets[count] = entry.getTarget(); menuIdentifiers[count] = entry.getIdentifier(); menuTypes[count] = entry.getOpcode(); - params0[count] = entry.getActionParam0(); + params0[count] = entry.getActionParam(); params1[count] = entry.getActionParam1(); leftClick[count] = entry.isForceLeftClick(); ++count; @@ -832,7 +832,7 @@ public abstract class RSClientMixin implements RSClient targets[oldCount] = event.getTarget(); identifiers[oldCount] = event.getIdentifier(); opcodes[oldCount] = event.getOpcode(); - arguments1[oldCount] = event.getActionParam0(); + arguments1[oldCount] = event.getActionParam(); arguments2[oldCount] = event.getActionParam1(); forceLeftClick[oldCount] = event.isForceLeftClick(); } @@ -1426,12 +1426,12 @@ public abstract class RSClientMixin implements RSClient client.getLogger().info( "|MenuAction|: MenuOption={} MenuTarget={} Id={} Opcode={} Param0={} Param1={} CanvasX={} CanvasY={} Authentic={}", menuOptionClicked.getOption(), menuOptionClicked.getTarget(), menuOptionClicked.getIdentifier(), - menuOptionClicked.getOpcode(), menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), + menuOptionClicked.getOpcode(), menuOptionClicked.getActionParam(), menuOptionClicked.getActionParam1(), canvasX, canvasY, authentic ); } - copy$menuAction(menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(), + copy$menuAction(menuOptionClicked.getActionParam(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(), menuOptionClicked.getIdentifier(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), canvasX, canvasY); } @@ -1925,7 +1925,7 @@ public abstract class RSClientMixin implements RSClient @Inject @Override - public ObjectDefinition getObjectComposition(int objectId) + public ObjectComposition getObjectDefinition(int objectId) { assert this.isClientThread() : "getObjectDefinition must be called on client thread"; return getRSObjectComposition(objectId); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java index 46e8504ee9..e819d6a8cf 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSDynamicObjectMixin.java @@ -34,7 +34,7 @@ import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSDynamicObject; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSModel; @Mixin(RSDynamicObject.class) @@ -83,7 +83,7 @@ public abstract class RSDynamicObjectMixin implements RSDynamicObject @MethodHook(value = "", end = true) @Inject - public void rl$init(int id, int type, int orientation, int plane, int x, int y, int animationID, boolean var8, RSEntity var9) + public void rl$init(int id, int type, int orientation, int plane, int x, int y, int animationID, boolean var8, RSRenderable var9) { if (animationID != -1) { diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java index 1ae112a915..047bc1ca9f 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java @@ -8,7 +8,7 @@ import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSFloorDecoration; import net.runelite.rs.api.RSModel; @@ -39,7 +39,7 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration @Override public RSModel getModel() { - RSEntity entity = getEntity(); + RSRenderable entity = getRenderable(); if (entity == null) { return null; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java index 2935fcdaa5..9131abf894 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java @@ -33,7 +33,7 @@ import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSGameObject; import net.runelite.rs.api.RSModel; @@ -61,7 +61,7 @@ public abstract class RSGameObjectMixin implements RSGameObject @Override public RSModel getModel() { - RSEntity renderable = getEntity(); + RSRenderable renderable = getRenderable(); if (renderable == null) { return null; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java index c276cc4397..c7c6849798 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java @@ -24,11 +24,11 @@ */ package net.runelite.mixins; -import net.runelite.api.Entity; +import net.runelite.api.Renderable; import net.runelite.api.Perspective; import net.runelite.api.Tile; -import net.runelite.api.TileModel; -import net.runelite.api.TilePaint; +import net.runelite.api.SceneTileModel; +import net.runelite.api.SceneTilePaint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.hooks.DrawCallbacks; import net.runelite.api.mixins.Copy; @@ -45,7 +45,7 @@ import net.runelite.rs.api.RSScene; import net.runelite.rs.api.RSTile; import net.runelite.rs.api.RSTileItem; import net.runelite.rs.api.RSTileItemPile; -import net.runelite.rs.api.RSTileModel; +import net.runelite.rs.api.RSSceneTileModel; import net.runelite.rs.api.RSWallDecoration; @Mixin(RSScene.class) @@ -412,7 +412,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("newWallDecoration") @Replace("newWallDecoration") @SuppressWarnings("InfiniteRecursion") - public void copy$addBoundaryDecoration(int plane, int x, int y, int floor, Entity var5, Entity var6, int var7, int var8, int var9, int var10, long hash, int var12) + public void copy$addBoundaryDecoration(int plane, int x, int y, int floor, Renderable var5, Renderable var6, int var7, int var8, int var9, int var10, long hash, int var12) { copy$addBoundaryDecoration(plane, x, y, floor, var5, var6, var7, var8, var9, var10, hash, var12); Tile tile = getTiles()[plane][x][y]; @@ -429,7 +429,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("newGroundItemPile") @Replace("newGroundItemPile") @SuppressWarnings("InfiniteRecursion") - public void copy$addItemPile(int plane, int x, int y, int hash, Entity var5, long var6, Entity var7, Entity var8) + public void copy$addItemPile(int plane, int x, int y, int hash, Renderable var5, long var6, Renderable var7, Renderable var8) { copy$addItemPile(plane, x, y, hash, var5, var6, var7, var8); Tile tile = getTiles()[plane][x][y]; @@ -446,7 +446,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("newFloorDecoration") @Replace("newFloorDecoration") @SuppressWarnings("InfiniteRecursion") - public void copy$groundObjectSpawned(int plane, int x, int y, int floor, Entity var5, long hash, int var7) + public void copy$groundObjectSpawned(int plane, int x, int y, int floor, Renderable var5, long hash, int var7) { copy$groundObjectSpawned(plane, x, y, floor, var5, hash, var7); Tile tile = getTiles()[plane][x][y]; @@ -463,7 +463,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("newBoundaryObject") @Replace("newBoundaryObject") @SuppressWarnings("InfiniteRecursion") - public void copy$addBoundary(int plane, int x, int y, int floor, Entity var5, Entity var6, int var7, int var8, long hash, int var10) + public void copy$addBoundary(int plane, int x, int y, int floor, Renderable var5, Renderable var6, int var7, int var8, long hash, int var10) { copy$addBoundary(plane, x, y, floor, var5, var6, var7, var8, hash, var10); Tile tile = getTiles()[plane][x][y]; @@ -479,7 +479,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("drawTileUnderlay") @Replace("drawTileUnderlay") - public void copy$drawTileUnderlay(TilePaint tile, int z, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y) + public void copy$drawTileUnderlay(SceneTilePaint tile, int z, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y) { if (!client.isGpu()) { @@ -604,7 +604,7 @@ public abstract class RSSceneMixin implements RSScene @Copy("drawTileOverlay") @Replace("drawTileOverlay") - public void copy$drawTileOverlay(TileModel tile, int pitchSin, int pitchCos, int yawSin, int yawCos, int tileX, int tileY) + public void copy$drawTileOverlay(SceneTileModel tile, int pitchSin, int pitchCos, int yawSin, int yawCos, int tileX, int tileY) { if (!client.isGpu()) { @@ -638,7 +638,7 @@ public abstract class RSSceneMixin implements RSScene return; } - RSTileModel tileModel = (RSTileModel) tile; + RSSceneTileModel tileModel = (RSSceneTileModel) tile; final int[] faceX = tileModel.getFaceX(); final int[] faceY = tileModel.getFaceY(); @@ -806,7 +806,7 @@ public abstract class RSSceneMixin implements RSScene if (shape != 0 && shape != 1) { Tile tile = getTiles()[z][x][y]; - TileModel sceneTileModel = tile.getTileModel(); + SceneTileModel sceneTileModel = tile.getSceneTileModel(); sceneTileModel.setUnderlaySwColor(underlaySwColor); sceneTileModel.setUnderlayNwColor(underlayNwColor); @@ -834,7 +834,7 @@ public abstract class RSSceneMixin implements RSScene { return; } - TilePaint sceneTilePaint = tile.getTilePaint(); + SceneTilePaint sceneTilePaint = tile.getSceneTilePaint(); if (sceneTilePaint != null) { int rgb = sceneTilePaint.getRBG(); @@ -889,7 +889,7 @@ public abstract class RSSceneMixin implements RSScene return; } - TileModel sceneTileModel = tile.getTileModel(); + SceneTileModel sceneTileModel = tile.getSceneTileModel(); if (sceneTileModel != null) { int shape = sceneTileModel.getShape(); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileModelMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTileModelMixin.java similarity index 94% rename from runelite-mixins/src/main/java/net/runelite/mixins/RSTileModelMixin.java rename to runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTileModelMixin.java index 06cc88a4fa..5bedbfe389 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileModelMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTileModelMixin.java @@ -2,10 +2,10 @@ package net.runelite.mixins; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; -import net.runelite.rs.api.RSTileModel; +import net.runelite.rs.api.RSSceneTileModel; -@Mixin(RSTileModel.class) -public abstract class RSTileModelMixin implements RSTileModel +@Mixin(RSSceneTileModel.class) +public abstract class RSSceneTileModelMixin implements RSSceneTileModel { @Inject private int rl$modelBufferOffset; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTilePaintMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTilePaintMixin.java similarity index 85% rename from runelite-mixins/src/main/java/net/runelite/mixins/RSTilePaintMixin.java rename to runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTilePaintMixin.java index 4f31d16757..87d66d8845 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTilePaintMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneTilePaintMixin.java @@ -2,10 +2,10 @@ package net.runelite.mixins; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; -import net.runelite.rs.api.RSTilePaint; +import net.runelite.rs.api.RSSceneTilePaint; -@Mixin(RSTilePaint.class) -public abstract class RSTilePaintMixin implements RSTilePaint +@Mixin(RSSceneTilePaint.class) +public abstract class RSSceneTilePaintMixin implements RSSceneTilePaint { @Inject private int rl$paintModelBufferOffset; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileItemPileMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileItemPileMixin.java index fa4324e50a..d9ee208878 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileItemPileMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileItemPileMixin.java @@ -1,7 +1,7 @@ package net.runelite.mixins; import java.awt.geom.Area; -import net.runelite.api.Entity; +import net.runelite.api.Renderable; import net.runelite.api.Model; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; @@ -38,19 +38,19 @@ public abstract class RSTileItemPileMixin implements RSTileItemPile @Override public Model getModelBottom() { - Entity entity = getBottom(); - if (entity == null) + Renderable renderable = getBottom(); + if (renderable == null) { return null; } - if (entity instanceof Model) + if (renderable instanceof Model) { - return (Model) entity; + return (Model) renderable; } else { - return entity.getModel(); + return renderable.getModel(); } } @@ -58,19 +58,19 @@ public abstract class RSTileItemPileMixin implements RSTileItemPile @Override public Model getModelMiddle() { - Entity entity = getMiddle(); - if (entity == null) + Renderable renderable = getMiddle(); + if (renderable == null) { return null; } - if (entity instanceof Model) + if (renderable instanceof Model) { - return (Model) entity; + return (Model) renderable; } else { - return entity.getModel(); + return renderable.getModel(); } } @@ -78,19 +78,19 @@ public abstract class RSTileItemPileMixin implements RSTileItemPile @Override public Model getModelTop() { - Entity entity = getTop(); - if (entity == null) + Renderable renderable = getTop(); + if (renderable == null) { return null; } - if (entity instanceof Model) + if (renderable instanceof Model) { - return (Model) entity; + return (Model) renderable; } else { - return entity.getModel(); + return renderable.getModel(); } } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java index fc17b28a92..0634cb6d2e 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java @@ -61,7 +61,7 @@ import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSActor; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSGameObject; import net.runelite.rs.api.RSGraphicsObject; import net.runelite.rs.api.RSTileItemPile; @@ -403,13 +403,13 @@ public abstract class RSTileMixin implements RSTile boolean currentInvalid = false, prevInvalid = false; if (current != null) { - RSEntity renderable = current.getEntity(); + RSRenderable renderable = current.getRenderable(); currentInvalid = renderable instanceof RSActor || renderable instanceof RSProjectile || renderable instanceof RSGraphicsObject; } if (previous != null) { - RSEntity renderable = previous.getEntity(); + RSRenderable renderable = previous.getRenderable(); prevInvalid = renderable instanceof RSActor || renderable instanceof RSProjectile || renderable instanceof RSGraphicsObject; } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java index 16da2a8abf..47e64a6932 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java @@ -9,7 +9,7 @@ import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSRenderable; import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSWallDecoration; @@ -40,7 +40,7 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration @Override public RSModel getModel1() { - RSEntity renderable = getEntity1(); + RSRenderable renderable = getRenderable(); if (renderable == null) { return null; @@ -64,7 +64,7 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration @Override public RSModel getModel2() { - RSEntity renderable = getEntity2(); + RSRenderable renderable = getRenderable2(); if (renderable == null) { return null; diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java index 46d9b32f3e..9ae1d8b6b1 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java @@ -28,7 +28,7 @@ package net.runelite.rs.api; import net.runelite.api.Actor; import net.runelite.mapping.Import; -public interface RSActor extends RSEntity, Actor +public interface RSActor extends RSRenderable, Actor { @Import("targetIndex") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSBoundaryObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSBoundaryObject.java index f1b026ab23..16ffa7fa0d 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSBoundaryObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSBoundaryObject.java @@ -25,13 +25,13 @@ public interface RSBoundaryObject extends WallObject @Override int getOrientationB(); - @Import("entity1") + @Import("renderable1") @Override - RSEntity getEntity1(); + RSRenderable getRenderable(); - @Import("entity2") + @Import("renderable2") @Override - RSEntity getEntity2(); + RSRenderable getRenderable2(); @Import("flags") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java index 58e9db1442..1c375ed3a1 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSDynamicObject.java @@ -1,10 +1,10 @@ package net.runelite.rs.api; import net.runelite.api.DynamicObject; -import net.runelite.api.Entity; +import net.runelite.api.Renderable; import net.runelite.mapping.Import; -public interface RSDynamicObject extends RSEntity, DynamicObject, Entity +public interface RSDynamicObject extends RSRenderable, DynamicObject, Renderable { @Import("id") int getId(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSFloorDecoration.java b/runescape-api/src/main/java/net/runelite/rs/api/RSFloorDecoration.java index 5d5e59a697..30e5380b5f 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSFloorDecoration.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSFloorDecoration.java @@ -15,9 +15,9 @@ public interface RSFloorDecoration extends GroundObject @Import("y") int getY(); - @Import("entity") + @Import("renderable") @Override - RSEntity getEntity(); + RSRenderable getRenderable(); void setPlane(int plane); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java index 38fe708ce2..ae132af104 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java @@ -5,8 +5,8 @@ import net.runelite.mapping.Import; public interface RSGameObject extends GameObject { - @Import("entity") - RSEntity getEntity(); + @Import("renderable") + RSRenderable getRenderable(); @Import("plane") int getPlane(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGraphicsObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGraphicsObject.java index 00da0ef382..3187c22103 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGraphicsObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGraphicsObject.java @@ -3,7 +3,7 @@ package net.runelite.rs.api; import net.runelite.api.GraphicsObject; import net.runelite.mapping.Import; -public interface RSGraphicsObject extends GraphicsObject, RSEntity +public interface RSGraphicsObject extends GraphicsObject, RSRenderable { @Import("id") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java index 2fdc124a48..bb17d2c718 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java @@ -28,7 +28,7 @@ import java.awt.Shape; import net.runelite.api.Model; import net.runelite.mapping.Import; -public interface RSModel extends RSEntity, Model +public interface RSModel extends RSRenderable, Model { @Import("verticesCount") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSModelData.java b/runescape-api/src/main/java/net/runelite/rs/api/RSModelData.java index 77eaf9ce9c..f05b004217 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSModelData.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSModelData.java @@ -2,7 +2,7 @@ package net.runelite.rs.api; import net.runelite.mapping.Import; -public interface RSModelData extends RSEntity +public interface RSModelData extends RSRenderable { @Import("faceCount") int getTriangleFaceCount(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSObjectComposition.java b/runescape-api/src/main/java/net/runelite/rs/api/RSObjectComposition.java index 9bbb9e64b0..254af94bc4 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSObjectComposition.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSObjectComposition.java @@ -1,9 +1,9 @@ package net.runelite.rs.api; -import net.runelite.api.ObjectDefinition; +import net.runelite.api.ObjectComposition; import net.runelite.mapping.Import; -public interface RSObjectComposition extends ObjectDefinition +public interface RSObjectComposition extends ObjectComposition { @Import("id") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSProjectile.java b/runescape-api/src/main/java/net/runelite/rs/api/RSProjectile.java index ae8ab12836..5f6283632c 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSProjectile.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSProjectile.java @@ -31,7 +31,7 @@ package net.runelite.rs.api; import net.runelite.api.Projectile; import net.runelite.mapping.Import; -public interface RSProjectile extends RSEntity, Projectile +public interface RSProjectile extends RSRenderable, Projectile { @Import("id") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSEntity.java b/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java similarity index 94% rename from runescape-api/src/main/java/net/runelite/rs/api/RSEntity.java rename to runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java index efe46c9901..d91c112655 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSEntity.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java @@ -24,10 +24,10 @@ */ package net.runelite.rs.api; -import net.runelite.api.Entity; +import net.runelite.api.Renderable; import net.runelite.mapping.Import; -public interface RSEntity extends RSNode, Entity +public interface RSRenderable extends RSNode, Renderable { @Import("height") int getModelHeight(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSScene.java b/runescape-api/src/main/java/net/runelite/rs/api/RSScene.java index b2862b23e0..8118b79f51 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSScene.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSScene.java @@ -45,10 +45,10 @@ public interface RSScene extends Scene int getMinLevel(); @Import("newGroundItemPile") - void newGroundItemPile(int plane, int x, int y, int hash, RSEntity var5, long var6, RSEntity var7, RSEntity var8); + void newGroundItemPile(int plane, int x, int y, int hash, RSRenderable var5, long var6, RSRenderable var7, RSRenderable var8); @Import("newGameObject") - boolean newGameObject(int plane, int startX, int startY, int var4, int var5, int centerX, int centerY, int height, RSEntity entity, int orientation, boolean tmp, long tag, int flags); + boolean newGameObject(int plane, int startX, int startY, int var4, int var5, int centerX, int centerY, int height, RSRenderable entity, int orientation, boolean tmp, long tag, int flags); @Import("removeGameObject") void removeGameObject(GameObject gameObject); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTileModel.java b/runescape-api/src/main/java/net/runelite/rs/api/RSSceneTileModel.java similarity index 89% rename from runescape-api/src/main/java/net/runelite/rs/api/RSTileModel.java rename to runescape-api/src/main/java/net/runelite/rs/api/RSSceneTileModel.java index 65e3ce0f29..dd4b226f12 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTileModel.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSSceneTileModel.java @@ -1,9 +1,9 @@ package net.runelite.rs.api; -import net.runelite.api.TileModel; +import net.runelite.api.SceneTileModel; import net.runelite.mapping.Import; -public interface RSTileModel extends TileModel +public interface RSSceneTileModel extends SceneTileModel { @Import("underlayRgb") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTilePaint.java b/runescape-api/src/main/java/net/runelite/rs/api/RSSceneTilePaint.java similarity index 79% rename from runescape-api/src/main/java/net/runelite/rs/api/RSTilePaint.java rename to runescape-api/src/main/java/net/runelite/rs/api/RSSceneTilePaint.java index 8534304d5f..f1a90a8017 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTilePaint.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSSceneTilePaint.java @@ -1,9 +1,9 @@ package net.runelite.rs.api; -import net.runelite.api.TilePaint; +import net.runelite.api.SceneTilePaint; import net.runelite.mapping.Import; -public interface RSTilePaint extends TilePaint +public interface RSSceneTilePaint extends SceneTilePaint { @Import("rgb") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java index 9866baa0eb..dbc4092311 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java @@ -4,8 +4,8 @@ import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GroundObject; import net.runelite.api.TileItemPile; -import net.runelite.api.TileModel; -import net.runelite.api.TilePaint; +import net.runelite.api.SceneTileModel; +import net.runelite.api.SceneTilePaint; import net.runelite.api.Tile; import net.runelite.api.WallObject; import net.runelite.mapping.Import; @@ -34,11 +34,11 @@ public interface RSTile extends Tile @Import("paint") @Override - TilePaint getTilePaint(); + SceneTilePaint getSceneTilePaint(); @Import("model") @Override - TileModel getTileModel(); + SceneTileModel getSceneTileModel(); @Import("x") int getX(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTileItem.java b/runescape-api/src/main/java/net/runelite/rs/api/RSTileItem.java index c36b842657..4d01f82233 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTileItem.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSTileItem.java @@ -3,7 +3,7 @@ import net.runelite.api.Tile; import net.runelite.api.TileItem; import net.runelite.mapping.Import; -public interface RSTileItem extends RSEntity, TileItem +public interface RSTileItem extends RSRenderable, TileItem { @Import("id") @Override diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTileItemPile.java b/runescape-api/src/main/java/net/runelite/rs/api/RSTileItemPile.java index 1967207277..9887289064 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTileItemPile.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSTileItemPile.java @@ -20,15 +20,15 @@ public interface RSTileItemPile extends TileItemPile @Import("first") @Override - RSEntity getBottom(); + RSRenderable getBottom(); @Import("second") @Override - RSEntity getMiddle(); + RSRenderable getMiddle(); @Import("third") @Override - RSEntity getTop(); + RSRenderable getTop(); void setPlane(int plane); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWallDecoration.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWallDecoration.java index 40360cde7a..bfceac72a4 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWallDecoration.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWallDecoration.java @@ -24,13 +24,13 @@ public interface RSWallDecoration extends DecorativeObject @Import("orientation2") int getOrientation(); - @Import("entity1") + @Import("renderable1") @Override - RSEntity getEntity1(); + RSRenderable getRenderable(); - @Import("entity2") + @Import("renderable2") @Override - RSEntity getEntity2(); + RSRenderable getRenderable2(); void setPlane(int plane); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java index 0d9d782da8..7f08e6cdbd 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java @@ -546,7 +546,7 @@ public interface RSWidget extends Widget @Import("onInvTransmit") @Override - Object[] getOnInvTransmit(); + Object[] getOnInvTransmitListener(); @Import("containsMouse") @Override diff --git a/runescape-client/src/main/java/Actor.java b/runescape-client/src/main/java/Actor.java index 1df5250d8d..580d028e2e 100644 --- a/runescape-client/src/main/java/Actor.java +++ b/runescape-client/src/main/java/Actor.java @@ -6,7 +6,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("cx") @Implements("Actor") -public abstract class Actor extends Entity { +public abstract class Actor extends Renderable +{ @ObfuscatedName("hg") @ObfuscatedSignature( descriptor = "[Llm;" diff --git a/runescape-client/src/main/java/BoundaryObject.java b/runescape-client/src/main/java/BoundaryObject.java index 203d40cf82..252f63edac 100644 --- a/runescape-client/src/main/java/BoundaryObject.java +++ b/runescape-client/src/main/java/BoundaryObject.java @@ -42,13 +42,13 @@ public final class BoundaryObject { descriptor = "Ler;" ) @Export("entity1") - public Entity entity1; + public Renderable renderable1; @ObfuscatedName("n") @ObfuscatedSignature( descriptor = "Ler;" ) @Export("entity2") - public Entity entity2; + public Renderable renderable2; @ObfuscatedName("p") @ObfuscatedGetter( longValue = 6107262315709824135L diff --git a/runescape-client/src/main/java/Canvas.java b/runescape-client/src/main/java/Canvas.java index 7350a53c52..f8056eb5ce 100644 --- a/runescape-client/src/main/java/Canvas.java +++ b/runescape-client/src/main/java/Canvas.java @@ -73,7 +73,7 @@ public final class Canvas extends java.awt.Canvas { Login.Login_loadingPercent = 10; // L: 1737 Client.titleLoadingStage = 30; // L: 1738 } else if (Client.titleLoadingStage == 30) { // L: 1741 - TilePaint.archive0 = WorldMapCacheName.newArchive(0, false, true, true); // L: 1742 + SceneTilePaint.archive0 = WorldMapCacheName.newArchive(0, false, true, true); // L: 1742 WorldMapSprite.archive1 = WorldMapCacheName.newArchive(1, false, true, true); // L: 1743 DynamicObject.archive2 = WorldMapCacheName.newArchive(2, true, false, true); // L: 1744 class330.archive3 = WorldMapCacheName.newArchive(3, false, true, true); // L: 1745 @@ -98,7 +98,7 @@ public final class Canvas extends java.awt.Canvas { Client.titleLoadingStage = 40; // L: 1764 } else if (Client.titleLoadingStage == 40) { // L: 1767 byte var23 = 0; // L: 1768 - var12 = var23 + TilePaint.archive0.percentage() * 4 / 100; // L: 1769 + var12 = var23 + SceneTilePaint.archive0.percentage() * 4 / 100; // L: 1769 var12 += WorldMapSprite.archive1.percentage() * 4 / 100; // L: 1770 var12 += DynamicObject.archive2.percentage() * 2 / 100; // L: 1771 var12 += class330.archive3.percentage() * 2 / 100; // L: 1772 @@ -125,7 +125,7 @@ public final class Canvas extends java.awt.Canvas { Login.Login_loadingPercent = 30; // L: 1791 } else { - UserComparator4.method3469(TilePaint.archive0, "Animations"); // L: 1794 + UserComparator4.method3469(SceneTilePaint.archive0, "Animations"); // L: 1794 UserComparator4.method3469(WorldMapSprite.archive1, "Skeletons"); // L: 1795 UserComparator4.method3469(class227.archive4, "Sound FX"); // L: 1796 UserComparator4.method3469(GameShell.archive5, "Maps"); // L: 1797 @@ -276,7 +276,7 @@ public final class Canvas extends java.awt.Canvas { World.method1849(DynamicObject.archive2, BuddyRankComparator.archive7); // L: 1920 ParamDefinition.method4526(DynamicObject.archive2); // L: 1921 Actor.method1821(DynamicObject.archive2, BuddyRankComparator.archive7, Client.isMembersWorld, class297.fontPlain11); // L: 1922 - PcmPlayer.method2538(DynamicObject.archive2, TilePaint.archive0, WorldMapSprite.archive1); // L: 1923 + PcmPlayer.method2538(DynamicObject.archive2, SceneTilePaint.archive0, WorldMapSprite.archive1); // L: 1923 ModelData0.method3331(DynamicObject.archive2, BuddyRankComparator.archive7); // L: 1924 class200.method3712(DynamicObject.archive2); // L: 1925 Archive var18 = DynamicObject.archive2; // L: 1926 diff --git a/runescape-client/src/main/java/Client.java b/runescape-client/src/main/java/Client.java index 5e657a1cdf..1ff9f35b70 100644 --- a/runescape-client/src/main/java/Client.java +++ b/runescape-client/src/main/java/Client.java @@ -3056,7 +3056,7 @@ public final class Client extends GameShell implements Usernamed { var5.packetBuffer.method5718(Messages.archive8.hash); // L: 2380 var5.packetBuffer.method5587(DynamicObject.archive2.hash); // L: 2381 var5.packetBuffer.method5718(class330.archive3.hash); // L: 2382 - var5.packetBuffer.method5587(TilePaint.archive0.hash); // L: 2383 + var5.packetBuffer.method5587(SceneTilePaint.archive0.hash); // L: 2383 var5.packetBuffer.writeInt(class227.archive4.hash); // L: 2384 var5.packetBuffer.method5587(0); // L: 2385 var5.packetBuffer.method5588(BuddyRankComparator.archive7.hash); // L: 2386 diff --git a/runescape-client/src/main/java/DynamicObject.java b/runescape-client/src/main/java/DynamicObject.java index 0be7257fe0..6bef8a7cfa 100644 --- a/runescape-client/src/main/java/DynamicObject.java +++ b/runescape-client/src/main/java/DynamicObject.java @@ -6,7 +6,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("cn") @Implements("DynamicObject") -public class DynamicObject extends Entity { +public class DynamicObject extends Renderable +{ @ObfuscatedName("rl") @ObfuscatedGetter( intValue = 2126595663 @@ -84,7 +85,7 @@ public class DynamicObject extends Entity { @ObfuscatedSignature( descriptor = "(IIIIIIIZLer;)V" ) - DynamicObject(int var1, int var2, int var3, int var4, int var5, int var6, int var7, boolean var8, Entity var9) { + DynamicObject(int var1, int var2, int var3, int var4, int var5, int var6, int var7, boolean var8, Renderable var9) { this.id = var1; this.type = var2; // L: 21 this.orientation = var3; // L: 22 diff --git a/runescape-client/src/main/java/FloorDecoration.java b/runescape-client/src/main/java/FloorDecoration.java index 9c51587fb4..e4df626962 100644 --- a/runescape-client/src/main/java/FloorDecoration.java +++ b/runescape-client/src/main/java/FloorDecoration.java @@ -30,7 +30,7 @@ public final class FloorDecoration { descriptor = "Ler;" ) @Export("entity") - public Entity entity; + public Renderable renderable; @ObfuscatedName("t") @ObfuscatedGetter( longValue = 2801138791321477185L diff --git a/runescape-client/src/main/java/FontName.java b/runescape-client/src/main/java/FontName.java index b20c892560..77bd1a1a46 100644 --- a/runescape-client/src/main/java/FontName.java +++ b/runescape-client/src/main/java/FontName.java @@ -157,7 +157,7 @@ public class FontName { if (var1 != -1412584499 && !var10.isScrollBar) { // L: 8954 class200.field2390 = var0; // L: 8955 Client.field923 = var6; // L: 8956 - Entity.field1893 = var7; // L: 8957 + Renderable.field1893 = var7; // L: 8957 continue; // L: 8958 } diff --git a/runescape-client/src/main/java/GameObject.java b/runescape-client/src/main/java/GameObject.java index 2bbc869660..37424002e1 100644 --- a/runescape-client/src/main/java/GameObject.java +++ b/runescape-client/src/main/java/GameObject.java @@ -42,7 +42,7 @@ public final class GameObject { descriptor = "Ler;" ) @Export("entity") - public Entity entity; + public Renderable renderable; @ObfuscatedName("j") @ObfuscatedGetter( intValue = 1255599163 diff --git a/runescape-client/src/main/java/GraphicsObject.java b/runescape-client/src/main/java/GraphicsObject.java index c46b81eae1..ad2271dea2 100644 --- a/runescape-client/src/main/java/GraphicsObject.java +++ b/runescape-client/src/main/java/GraphicsObject.java @@ -6,7 +6,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("ce") @Implements("GraphicsObject") -public final class GraphicsObject extends Entity { +public final class GraphicsObject extends Renderable +{ @ObfuscatedName("a") public static String[] field1117; @ObfuscatedName("h") diff --git a/runescape-client/src/main/java/KeyHandler.java b/runescape-client/src/main/java/KeyHandler.java index f4bce87a80..ee14cfd09a 100644 --- a/runescape-client/src/main/java/KeyHandler.java +++ b/runescape-client/src/main/java/KeyHandler.java @@ -217,7 +217,7 @@ public final class KeyHandler implements KeyListener, FocusListener { class200.field2390 = null; // L: 8921 FontName.drawInterface(class9.Widget_interfaceComponents[var0], -1, var1, var2, var3, var4, var5, var6, var7); // L: 8922 if (class200.field2390 != null) { // L: 8923 - FontName.drawInterface(class200.field2390, -1412584499, var1, var2, var3, var4, Client.field923, Entity.field1893, var7); // L: 8924 + FontName.drawInterface(class200.field2390, -1412584499, var1, var2, var3, var4, Client.field923, Renderable.field1893, var7); // L: 8924 class200.field2390 = null; // L: 8925 } diff --git a/runescape-client/src/main/java/Model.java b/runescape-client/src/main/java/Model.java index 4d79dc1306..6e08e26ef7 100644 --- a/runescape-client/src/main/java/Model.java +++ b/runescape-client/src/main/java/Model.java @@ -5,7 +5,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("eh") @Implements("Model") -public class Model extends Entity { +public class Model extends Renderable +{ @ObfuscatedName("h") @ObfuscatedSignature( descriptor = "Leh;" diff --git a/runescape-client/src/main/java/ModelData.java b/runescape-client/src/main/java/ModelData.java index a5c50c6cc3..d71b4c3c98 100644 --- a/runescape-client/src/main/java/ModelData.java +++ b/runescape-client/src/main/java/ModelData.java @@ -5,7 +5,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("es") @Implements("ModelData") -public class ModelData extends Entity { +public class ModelData extends Renderable +{ @ObfuscatedName("av") static int[] field1598; @ObfuscatedName("aq") diff --git a/runescape-client/src/main/java/ObjectComposition.java b/runescape-client/src/main/java/ObjectComposition.java index 5a90164d10..9171913f44 100644 --- a/runescape-client/src/main/java/ObjectComposition.java +++ b/runescape-client/src/main/java/ObjectComposition.java @@ -581,7 +581,7 @@ public class ObjectComposition extends DualNode { garbageValue = "-18803" ) @Export("getEntity") - public final Entity getEntity(int var1, int var2, int[][] var3, int var4, int var5, int var6) { + public final Renderable getEntity(int var1, int var2, int[][] var3, int var4, int var5, int var6) { long var7; if (this.models == null) { // L: 254 var7 = (long)(var2 + (this.id << 10)); @@ -589,7 +589,7 @@ public class ObjectComposition extends DualNode { var7 = (long)(var2 + (var1 << 3) + (this.id << 10)); // L: 255 } - Object var9 = (Entity)ObjectDefinition_cachedEntities.get(var7); // L: 256 + Object var9 = (Renderable)ObjectDefinition_cachedEntities.get(var7); // L: 256 if (var9 == null) { // L: 257 ModelData var10 = this.getModelData(var1, var2); // L: 258 if (var10 == null) { // L: 259 @@ -620,7 +620,7 @@ public class ObjectComposition extends DualNode { } } - return (Entity)var9; // L: 276 + return (Renderable)var9; // L: 276 } @ObfuscatedName("p") diff --git a/runescape-client/src/main/java/Projectile.java b/runescape-client/src/main/java/Projectile.java index 4df01195ad..055ed7e6ab 100644 --- a/runescape-client/src/main/java/Projectile.java +++ b/runescape-client/src/main/java/Projectile.java @@ -6,7 +6,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("cu") @Implements("Projectile") -public final class Projectile extends Entity { +public final class Projectile extends Renderable +{ @ObfuscatedName("h") @ObfuscatedGetter( intValue = -1007905183 diff --git a/runescape-client/src/main/java/Entity.java b/runescape-client/src/main/java/Renderable.java similarity index 94% rename from runescape-client/src/main/java/Entity.java rename to runescape-client/src/main/java/Renderable.java index 46fc30e959..4f15f82cd7 100644 --- a/runescape-client/src/main/java/Entity.java +++ b/runescape-client/src/main/java/Renderable.java @@ -5,8 +5,8 @@ import net.runelite.mapping.ObfuscatedName; import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("er") -@Implements("Entity") -public abstract class Entity extends DualNode { +@Implements("Renderable") +public abstract class Renderable extends DualNode { @ObfuscatedName("nw") @ObfuscatedGetter( intValue = -1743769759 @@ -19,7 +19,7 @@ public abstract class Entity extends DualNode { @Export("height") public int height; - protected Entity() { + protected Renderable() { this.height = 1000; // L: 6 } // L: 8 diff --git a/runescape-client/src/main/java/Scene.java b/runescape-client/src/main/java/Scene.java index fadbf3ff0a..1dbe5c7e92 100644 --- a/runescape-client/src/main/java/Scene.java +++ b/runescape-client/src/main/java/Scene.java @@ -318,10 +318,10 @@ public class Scene { @ObfuscatedName("j") @Export("addTile") public void addTile(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9, int var10, int var11, int var12, int var13, int var14, int var15, int var16, int var17, int var18, int var19, int var20) { - TilePaint var21; + SceneTilePaint var21; int var22; if (var4 == 0) { // L: 171 - var21 = new TilePaint(var11, var12, var13, var14, -1, var19, false); // L: 172 + var21 = new SceneTilePaint(var11, var12, var13, var14, -1, var19, false); // L: 172 for (var22 = var1; var22 >= 0; --var22) { // L: 173 if (this.tiles[var22][var2][var3] == null) { @@ -331,7 +331,7 @@ public class Scene { this.tiles[var1][var2][var3].paint = var21; // L: 174 } else if (var4 != 1) { // L: 177 - TileModel var23 = new TileModel(var4, var5, var6, var2, var3, var7, var8, var9, var10, var11, var12, var13, var14, var15, var16, var17, var18, var19, var20); // L: 183 + SceneTileModel var23 = new SceneTileModel(var4, var5, var6, var2, var3, var7, var8, var9, var10, var11, var12, var13, var14, var15, var16, var17, var18, var19, var20); // L: 183 for (var22 = var1; var22 >= 0; --var22) { // L: 184 if (this.tiles[var22][var2][var3] == null) { @@ -341,7 +341,7 @@ public class Scene { this.tiles[var1][var2][var3].model = var23; // L: 185 } else { - var21 = new TilePaint(var15, var16, var17, var18, var6, var20, var8 == var7 && var7 == var9 && var10 == var7); // L: 178 + var21 = new SceneTilePaint(var15, var16, var17, var18, var6, var20, var8 == var7 && var7 == var9 && var10 == var7); // L: 178 for (var22 = var1; var22 >= 0; --var22) { // L: 179 if (this.tiles[var22][var2][var3] == null) { @@ -358,10 +358,10 @@ public class Scene { descriptor = "(IIIILer;JI)V" ) @Export("newFloorDecoration") - public void newFloorDecoration(int var1, int var2, int var3, int var4, Entity var5, long var6, int var8) { + public void newFloorDecoration(int var1, int var2, int var3, int var4, Renderable var5, long var6, int var8) { if (var5 != null) { // L: 189 FloorDecoration var9 = new FloorDecoration(); // L: 190 - var9.entity = var5; // L: 191 + var9.renderable = var5; // L: 191 var9.x = var2 * 128 + 64; // L: 192 var9.y = var3 * 128 + 64; // L: 193 var9.tileHeight = var4; // L: 194 @@ -380,7 +380,7 @@ public class Scene { descriptor = "(IIIILer;JLer;Ler;)V" ) @Export("newGroundItemPile") - public void newGroundItemPile(int var1, int var2, int var3, int var4, Entity var5, long var6, Entity var8, Entity var9) { + public void newGroundItemPile(int var1, int var2, int var3, int var4, Renderable var5, long var6, Renderable var8, Renderable var9) { TileItemPile var10 = new TileItemPile(); // L: 202 var10.first = var5; // L: 203 var10.x = var2 * 128 + 64; // L: 204 @@ -393,8 +393,8 @@ public class Scene { Tile var12 = this.tiles[var1][var2][var3]; // L: 211 if (var12 != null) { // L: 212 for (int var13 = 0; var13 < var12.gameObjectsCount; ++var13) { // L: 213 - if ((var12.gameObjects[var13].flags & 256) == 256 && var12.gameObjects[var13].entity instanceof Model) { // L: 214 - Model var14 = (Model)var12.gameObjects[var13].entity; // L: 215 + if ((var12.gameObjects[var13].flags & 256) == 256 && var12.gameObjects[var13].renderable instanceof Model) { // L: 214 + Model var14 = (Model)var12.gameObjects[var13].renderable; // L: 215 var14.calculateBoundsCylinder(); // L: 216 if (var14.height > var11) { // L: 217 var11 = var14.height; @@ -416,7 +416,7 @@ public class Scene { descriptor = "(IIIILer;Ler;IIJI)V" ) @Export("newBoundaryObject") - public void newBoundaryObject(int var1, int var2, int var3, int var4, Entity var5, Entity var6, int var7, int var8, long var9, int var11) { + public void newBoundaryObject(int var1, int var2, int var3, int var4, Renderable var5, Renderable var6, int var7, int var8, long var9, int var11) { if (var5 != null || var6 != null) { // L: 227 BoundaryObject var12 = new BoundaryObject(); // L: 228 var12.tag = var9; // L: 229 @@ -424,8 +424,8 @@ public class Scene { var12.x = var2 * 128 + 64; // L: 231 var12.y = var3 * 128 + 64; // L: 232 var12.tileHeight = var4; // L: 233 - var12.entity1 = var5; // L: 234 - var12.entity2 = var6; // L: 235 + var12.renderable1 = var5; // L: 234 + var12.renderable2 = var6; // L: 235 var12.orientationA = var7; // L: 236 var12.orientationB = var8; // L: 237 @@ -444,7 +444,7 @@ public class Scene { descriptor = "(IIIILer;Ler;IIIIJI)V" ) @Export("newWallDecoration") - public void newWallDecoration(int var1, int var2, int var3, int var4, Entity var5, Entity var6, int var7, int var8, int var9, int var10, long var11, int var13) { + public void newWallDecoration(int var1, int var2, int var3, int var4, Renderable var5, Renderable var6, int var7, int var8, int var9, int var10, long var11, int var13) { if (var5 != null) { // L: 243 WallDecoration var14 = new WallDecoration(); // L: 244 var14.tag = var11; // L: 245 @@ -452,8 +452,8 @@ public class Scene { var14.x = var2 * 128 + 64; // L: 247 var14.y = var3 * 128 + 64; // L: 248 var14.tileHeight = var4; // L: 249 - var14.entity1 = var5; // L: 250 - var14.entity2 = var6; // L: 251 + var14.renderable1 = var5; // L: 250 + var14.renderable2 = var6; // L: 251 var14.orientation = var7; // L: 252 var14.orientation2 = var8; // L: 253 var14.xOffset = var9; // L: 254 @@ -473,7 +473,7 @@ public class Scene { @ObfuscatedSignature( descriptor = "(IIIIIILer;IJI)Z" ) - public boolean method3160(int var1, int var2, int var3, int var4, int var5, int var6, Entity var7, int var8, long var9, int var11) { + public boolean method3160(int var1, int var2, int var3, int var4, int var5, int var6, Renderable var7, int var8, long var9, int var11) { if (var7 == null) { // L: 261 return true; } else { @@ -488,7 +488,7 @@ public class Scene { descriptor = "(IIIIILer;IJZ)Z" ) @Export("drawEntity") - public boolean drawEntity(int var1, int var2, int var3, int var4, int var5, Entity var6, int var7, long var8, boolean var10) { + public boolean drawEntity(int var1, int var2, int var3, int var4, int var5, Renderable var6, int var7, long var8, boolean var10) { if (var6 == null) { // L: 268 return true; } else { @@ -527,7 +527,7 @@ public class Scene { descriptor = "(IIIIILer;IJIIII)Z" ) @Export("addNullableObject") - public boolean addNullableObject(int var1, int var2, int var3, int var4, int var5, Entity var6, int var7, long var8, int var10, int var11, int var12, int var13) { + public boolean addNullableObject(int var1, int var2, int var3, int var4, int var5, Renderable var6, int var7, long var8, int var10, int var11, int var12, int var13) { return var6 == null ? true : this.newGameObject(var1, var10, var11, var12 - var10 + 1, var13 - var11 + 1, var2, var3, var4, var6, var7, true, var8, 0); // L: 287 288 } @@ -536,7 +536,7 @@ public class Scene { descriptor = "(IIIIIIIILer;IZJI)Z" ) @Export("newGameObject") - boolean newGameObject(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, Entity var9, int var10, boolean var11, long var12, int var14) { + boolean newGameObject(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, Renderable var9, int var10, boolean var11, long var12, int var14) { int var16; for (int var15 = var2; var15 < var2 + var4; ++var15) { // L: 292 for (var16 = var3; var16 < var3 + var5; ++var16) { // L: 293 @@ -558,7 +558,7 @@ public class Scene { var21.centerX = var6; // L: 303 var21.centerY = var7; // L: 304 var21.height = var8; // L: 305 - var21.entity = var9; // L: 306 + var21.renderable = var9; // L: 306 var21.orientation = var10; // L: 307 var21.startX = var2; // L: 308 var21.startY = var3; // L: 309 @@ -836,33 +836,33 @@ public class Scene { if (var7 != null) { // L: 485 BoundaryObject var8 = var7.boundaryObject; // L: 486 ModelData var10; - if (var8 != null && var8.entity1 instanceof ModelData) { // L: 487 - ModelData var9 = (ModelData)var8.entity1; // L: 488 + if (var8 != null && var8.renderable1 instanceof ModelData) { // L: 487 + ModelData var9 = (ModelData)var8.renderable1; // L: 488 this.method3192(var9, var4, var5, var6, 1, 1); // L: 489 - if (var8.entity2 instanceof ModelData) { // L: 490 - var10 = (ModelData)var8.entity2; // L: 491 + if (var8.renderable2 instanceof ModelData) { // L: 490 + var10 = (ModelData)var8.renderable2; // L: 491 this.method3192(var10, var4, var5, var6, 1, 1); // L: 492 ModelData.method2872(var9, var10, 0, 0, 0, false); // L: 493 - var8.entity2 = var10.toModel(var10.ambient, var10.contrast, var1, var2, var3); // L: 494 + var8.renderable2 = var10.toModel(var10.ambient, var10.contrast, var1, var2, var3); // L: 494 } - var8.entity1 = var9.toModel(var9.ambient, var9.contrast, var1, var2, var3); // L: 496 + var8.renderable1 = var9.toModel(var9.ambient, var9.contrast, var1, var2, var3); // L: 496 } for (int var12 = 0; var12 < var7.gameObjectsCount; ++var12) { // L: 498 GameObject var14 = var7.gameObjects[var12]; // L: 499 - if (var14 != null && var14.entity instanceof ModelData) { // L: 500 - ModelData var11 = (ModelData)var14.entity; // L: 501 + if (var14 != null && var14.renderable instanceof ModelData) { // L: 500 + ModelData var11 = (ModelData)var14.renderable; // L: 501 this.method3192(var11, var4, var5, var6, var14.endX - var14.startX + 1, var14.endY - var14.startY + 1); // L: 502 - var14.entity = var11.toModel(var11.ambient, var11.contrast, var1, var2, var3); // L: 503 + var14.renderable = var11.toModel(var11.ambient, var11.contrast, var1, var2, var3); // L: 503 } } FloorDecoration var13 = var7.floorDecoration; // L: 506 - if (var13 != null && var13.entity instanceof ModelData) { // L: 507 - var10 = (ModelData)var13.entity; // L: 508 + if (var13 != null && var13.renderable instanceof ModelData) { // L: 507 + var10 = (ModelData)var13.renderable; // L: 508 this.method3191(var10, var4, var5, var6); // L: 509 - var13.entity = var10.toModel(var10.ambient, var10.contrast, var1, var2, var3); // L: 510 + var13.renderable = var10.toModel(var10.ambient, var10.contrast, var1, var2, var3); // L: 510 } } } @@ -880,32 +880,32 @@ public class Scene { ModelData var6; if (var3 < this.xSize) { // L: 519 var5 = this.tiles[var2][var3 + 1][var4]; // L: 520 - if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.entity instanceof ModelData) { // L: 521 - var6 = (ModelData)var5.floorDecoration.entity; // L: 522 + if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.renderable instanceof ModelData) { // L: 521 + var6 = (ModelData)var5.floorDecoration.renderable; // L: 522 ModelData.method2872(var1, var6, 128, 0, 0, true); // L: 523 } } if (var4 < this.xSize) { // L: 526 var5 = this.tiles[var2][var3][var4 + 1]; // L: 527 - if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.entity instanceof ModelData) { // L: 528 - var6 = (ModelData)var5.floorDecoration.entity; // L: 529 + if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.renderable instanceof ModelData) { // L: 528 + var6 = (ModelData)var5.floorDecoration.renderable; // L: 529 ModelData.method2872(var1, var6, 0, 0, 128, true); // L: 530 } } if (var3 < this.xSize && var4 < this.ySize) { // L: 533 var5 = this.tiles[var2][var3 + 1][var4 + 1]; // L: 534 - if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.entity instanceof ModelData) { // L: 535 - var6 = (ModelData)var5.floorDecoration.entity; // L: 536 + if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.renderable instanceof ModelData) { // L: 535 + var6 = (ModelData)var5.floorDecoration.renderable; // L: 536 ModelData.method2872(var1, var6, 128, 0, 128, true); // L: 537 } } if (var3 < this.xSize && var4 > 0) { // L: 540 var5 = this.tiles[var2][var3 + 1][var4 - 1]; // L: 541 - if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.entity instanceof ModelData) { // L: 542 - var6 = (ModelData)var5.floorDecoration.entity; // L: 543 + if (var5 != null && var5.floorDecoration != null && var5.floorDecoration.renderable instanceof ModelData) { // L: 542 + var6 = (ModelData)var5.floorDecoration.renderable; // L: 543 ModelData.method2872(var1, var6, 128, 0, -128, true); // L: 544 } } @@ -935,21 +935,21 @@ public class Scene { BoundaryObject var17 = var15.boundaryObject; // L: 565 if (var17 != null) { // L: 566 ModelData var18; - if (var17.entity1 instanceof ModelData) { // L: 567 - var18 = (ModelData)var17.entity1; // L: 568 + if (var17.renderable1 instanceof ModelData) { // L: 567 + var18 = (ModelData)var17.renderable1; // L: 568 ModelData.method2872(var1, var18, (1 - var5) * 64 + (var13 - var3) * 128, var16, (var14 - var4) * 128 + (1 - var6) * 64, var7); // L: 569 } - if (var17.entity2 instanceof ModelData) { // L: 571 - var18 = (ModelData)var17.entity2; // L: 572 + if (var17.renderable2 instanceof ModelData) { // L: 571 + var18 = (ModelData)var17.renderable2; // L: 572 ModelData.method2872(var1, var18, (1 - var5) * 64 + (var13 - var3) * 128, var16, (var14 - var4) * 128 + (1 - var6) * 64, var7); // L: 573 } } for (int var23 = 0; var23 < var15.gameObjectsCount; ++var23) { // L: 576 GameObject var19 = var15.gameObjects[var23]; // L: 577 - if (var19 != null && var19.entity instanceof ModelData) { // L: 578 - ModelData var20 = (ModelData)var19.entity; // L: 579 + if (var19 != null && var19.renderable instanceof ModelData) { // L: 578 + ModelData var20 = (ModelData)var19.renderable; // L: 579 int var21 = var19.endX - var19.startX + 1; // L: 580 int var22 = var19.endY - var19.startY + 1; // L: 581 ModelData.method2872(var1, var20, (var21 - var5) * 64 + (var19.startX - var3) * 128, var16, (var19.startY - var4) * 128 + (var22 - var6) * 64, var7); // L: 582 @@ -973,7 +973,7 @@ public class Scene { public void drawTileMinimap(int[] var1, int var2, int var3, int var4, int var5, int var6) { Tile var7 = this.tiles[var4][var5][var6]; // L: 593 if (var7 != null) { // L: 594 - TilePaint var8 = var7.paint; // L: 595 + SceneTilePaint var8 = var7.paint; // L: 595 int var10; if (var8 != null) { // L: 596 int var18 = var8.rgb; // L: 597 @@ -988,7 +988,7 @@ public class Scene { } } else { - TileModel var9 = var7.model; // L: 608 + SceneTileModel var9 = var7.model; // L: 608 if (var9 != null) { // L: 609 var10 = var9.shape; // L: 610 int var11 = var9.rotation; // L: 611 @@ -1355,13 +1355,13 @@ public class Scene { var10 = var9.boundaryObject; // L: 909 if (var10 != null) { // L: 910 - var10.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var10.x - Scene_cameraX, var10.tileHeight - Scene_cameraY, var10.y - Scene_cameraZ, var10.tag); + var10.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var10.x - Scene_cameraX, var10.tileHeight - Scene_cameraY, var10.y - Scene_cameraZ, var10.tag); } for (var11 = 0; var11 < var9.gameObjectsCount; ++var11) { // L: 911 var12 = var9.gameObjects[var11]; // L: 912 if (var12 != null) { // L: 913 - var12.entity.draw(var12.orientation, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var12.centerX - Scene_cameraX, var12.height - Scene_cameraY, var12.centerY - Scene_cameraZ, var12.tag); + var12.renderable.draw(var12.orientation, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var12.centerX - Scene_cameraX, var12.height - Scene_cameraY, var12.centerY - Scene_cameraZ, var12.tag); } } } @@ -1424,17 +1424,17 @@ public class Scene { } if ((var31.orientationA & var11) != 0 && !this.method3208(var7, var4, var5, var31.orientationA)) { // L: 965 - var31.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var31.x - Scene_cameraX, var31.tileHeight - Scene_cameraY, var31.y - Scene_cameraZ, var31.tag); + var31.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var31.x - Scene_cameraX, var31.tileHeight - Scene_cameraY, var31.y - Scene_cameraZ, var31.tag); } if ((var31.orientationB & var11) != 0 && !this.method3208(var7, var4, var5, var31.orientationB)) { // L: 966 - var31.entity2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var31.x - Scene_cameraX, var31.tileHeight - Scene_cameraY, var31.y - Scene_cameraZ, var31.tag); + var31.renderable2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var31.x - Scene_cameraX, var31.tileHeight - Scene_cameraY, var31.y - Scene_cameraZ, var31.tag); } } - if (var13 != null && !this.method3213(var7, var4, var5, var13.entity1.height)) { // L: 968 + if (var13 != null && !this.method3213(var7, var4, var5, var13.renderable1.height)) { // L: 968 if ((var13.orientation & var11) != 0) { // L: 969 - var13.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var13.x - Scene_cameraX + var13.xOffset, var13.tileHeight - Scene_cameraY, var13.y - Scene_cameraZ + var13.yOffset, var13.tag); // L: 970 + var13.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var13.x - Scene_cameraX + var13.xOffset, var13.tileHeight - Scene_cameraY, var13.y - Scene_cameraZ + var13.yOffset, var13.tag); // L: 970 } else if (var13.orientation == 256) { // L: 972 var14 = var13.x - Scene_cameraX; // L: 973 var15 = var13.tileHeight - Scene_cameraY; // L: 974 @@ -1454,9 +1454,9 @@ public class Scene { } if (var19 < var18) { // L: 983 - var13.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var14 + var13.xOffset, var15, var16 + var13.yOffset, var13.tag); // L: 984 - } else if (var13.entity2 != null) { // L: 986 - var13.entity2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var14, var15, var16, var13.tag); // L: 987 + var13.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var14 + var13.xOffset, var15, var16 + var13.yOffset, var13.tag); // L: 984 + } else if (var13.renderable2 != null) { // L: 986 + var13.renderable2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var14, var15, var16, var13.tag); // L: 987 } } } @@ -1464,7 +1464,7 @@ public class Scene { if (var20) { // L: 991 FloorDecoration var22 = var3.floorDecoration; // L: 992 if (var22 != null) { // L: 993 - var22.entity.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var22.x - Scene_cameraX, var22.tileHeight - Scene_cameraY, var22.y - Scene_cameraZ, var22.tag); + var22.renderable.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var22.x - Scene_cameraX, var22.tileHeight - Scene_cameraY, var22.y - Scene_cameraZ, var22.tag); } TileItemPile var23 = var3.tileItemPile; // L: 994 @@ -1529,7 +1529,7 @@ public class Scene { if (var20) { // L: 1029 var10 = var3.boundaryObject; // L: 1030 if (!this.method3208(var7, var4, var5, var10.orientationA)) { // L: 1031 - var10.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var10.x - Scene_cameraX, var10.tileHeight - Scene_cameraY, var10.y - Scene_cameraZ, var10.tag); + var10.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var10.x - Scene_cameraX, var10.tileHeight - Scene_cameraY, var10.y - Scene_cameraZ, var10.tag); } var3.drawGameObjectEdges = 0; // L: 1032 @@ -1628,8 +1628,8 @@ public class Scene { GameObject var33 = gameObjects[var25]; // L: 1093 var33.lastDrawn = Scene_drawnCount; // L: 1094 - if (!this.method3210(var7, var33.startX, var33.endX, var33.startY, var33.endY, var33.entity.height)) { // L: 1095 - var33.entity.draw(var33.orientation, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var33.centerX - Scene_cameraX, var33.height - Scene_cameraY, var33.centerY - Scene_cameraZ, var33.tag); // L: 1096 + if (!this.method3210(var7, var33.startX, var33.endX, var33.startY, var33.endY, var33.renderable.height)) { // L: 1095 + var33.renderable.draw(var33.orientation, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var33.centerX - Scene_cameraX, var33.height - Scene_cameraY, var33.centerY - Scene_cameraZ, var33.tag); // L: 1096 } for (var14 = var33.startX; var14 <= var33.endX; ++var14) { // L: 1098 @@ -1702,9 +1702,9 @@ public class Scene { if (var3.field1679 != 0) { // L: 1138 WallDecoration var29 = var3.wallDecoration; // L: 1139 - if (var29 != null && !this.method3213(var7, var4, var5, var29.entity1.height)) { // L: 1140 + if (var29 != null && !this.method3213(var7, var4, var5, var29.renderable1.height)) { // L: 1140 if ((var29.orientation & var3.field1679) != 0) { // L: 1141 - var29.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var29.x - Scene_cameraX + var29.xOffset, var29.tileHeight - Scene_cameraY, var29.y - Scene_cameraZ + var29.yOffset, var29.tag); // L: 1142 + var29.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var29.x - Scene_cameraX + var29.xOffset, var29.tileHeight - Scene_cameraY, var29.y - Scene_cameraZ + var29.yOffset, var29.tag); // L: 1142 } else if (var29.orientation == 256) { // L: 1144 var11 = var29.x - Scene_cameraX; // L: 1145 var25 = var29.tileHeight - Scene_cameraY; // L: 1146 @@ -1723,9 +1723,9 @@ public class Scene { } if (var16 >= var15) { // L: 1155 - var29.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var11 + var29.xOffset, var25, var24 + var29.yOffset, var29.tag); // L: 1156 - } else if (var29.entity2 != null) { // L: 1158 - var29.entity2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var11, var25, var24, var29.tag); // L: 1159 + var29.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var11 + var29.xOffset, var25, var24 + var29.yOffset, var29.tag); // L: 1156 + } else if (var29.renderable2 != null) { // L: 1158 + var29.renderable2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var11, var25, var24, var29.tag); // L: 1159 } } } @@ -1733,11 +1733,11 @@ public class Scene { BoundaryObject var27 = var3.boundaryObject; // L: 1163 if (var27 != null) { // L: 1164 if ((var27.orientationB & var3.field1679) != 0 && !this.method3208(var7, var4, var5, var27.orientationB)) { // L: 1165 - var27.entity2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var27.x - Scene_cameraX, var27.tileHeight - Scene_cameraY, var27.y - Scene_cameraZ, var27.tag); + var27.renderable2.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var27.x - Scene_cameraX, var27.tileHeight - Scene_cameraY, var27.y - Scene_cameraZ, var27.tag); } if ((var27.orientationA & var3.field1679) != 0 && !this.method3208(var7, var4, var5, var27.orientationA)) { // L: 1166 - var27.entity1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var27.x - Scene_cameraX, var27.tileHeight - Scene_cameraY, var27.y - Scene_cameraZ, var27.tag); + var27.renderable1.draw(0, Scene_cameraPitchSine, Scene_cameraPitchCosine, Scene_cameraYawSine, Scene_cameraYawCosine, var27.x - Scene_cameraX, var27.tileHeight - Scene_cameraY, var27.y - Scene_cameraZ, var27.tag); } } } @@ -1785,7 +1785,7 @@ public class Scene { descriptor = "(Lew;IIIIIII)V" ) @Export("drawTileUnderlay") - void drawTileUnderlay(TilePaint var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8) { + void drawTileUnderlay(SceneTilePaint var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8) { int var9; int var10 = var9 = (var7 << 7) - Scene_cameraX; // L: 1195 int var11; @@ -1896,7 +1896,7 @@ public class Scene { descriptor = "(Leq;IIIIII)V" ) @Export("drawTileOverlay") - void drawTileOverlay(TileModel var1, int var2, int var3, int var4, int var5, int var6, int var7) { + void drawTileOverlay(SceneTileModel var1, int var2, int var3, int var4, int var5, int var6, int var7) { int var8 = var1.vertexX.length; // L: 1283 int var9; @@ -1918,13 +1918,13 @@ public class Scene { } if (var1.triangleTextureId != null) { // L: 1295 - TileModel.field1610[var9] = var10; // L: 1296 - TileModel.field1621[var9] = var13; // L: 1297 - TileModel.field1622[var9] = var12; // L: 1298 + SceneTileModel.field1610[var9] = var10; // L: 1296 + SceneTileModel.field1621[var9] = var13; // L: 1297 + SceneTileModel.field1622[var9] = var12; // L: 1298 } - TileModel.field1618[var9] = var10 * Rasterizer3D.Rasterizer3D_zoom / var12 + Rasterizer3D.Rasterizer3D_clipMidX; // L: 1300 - TileModel.field1619[var9] = var13 * Rasterizer3D.Rasterizer3D_zoom / var12 + Rasterizer3D.Rasterizer3D_clipMidY; // L: 1301 + SceneTileModel.field1618[var9] = var10 * Rasterizer3D.Rasterizer3D_zoom / var12 + Rasterizer3D.Rasterizer3D_clipMidX; // L: 1300 + SceneTileModel.field1619[var9] = var13 * Rasterizer3D.Rasterizer3D_zoom / var12 + Rasterizer3D.Rasterizer3D_clipMidY; // L: 1301 } Rasterizer3D.Rasterizer3D_alpha = 0; // L: 1303 @@ -1934,12 +1934,12 @@ public class Scene { var10 = var1.faceX[var9]; // L: 1306 var11 = var1.faceY[var9]; // L: 1307 var12 = var1.faceZ[var9]; // L: 1308 - var13 = TileModel.field1618[var10]; // L: 1309 - int var14 = TileModel.field1618[var11]; // L: 1310 - int var15 = TileModel.field1618[var12]; // L: 1311 - int var16 = TileModel.field1619[var10]; // L: 1312 - int var17 = TileModel.field1619[var11]; // L: 1313 - int var18 = TileModel.field1619[var12]; // L: 1314 + var13 = SceneTileModel.field1618[var10]; // L: 1309 + int var14 = SceneTileModel.field1618[var11]; // L: 1310 + int var15 = SceneTileModel.field1618[var12]; // L: 1311 + int var16 = SceneTileModel.field1619[var10]; // L: 1312 + int var17 = SceneTileModel.field1619[var11]; // L: 1313 + int var18 = SceneTileModel.field1619[var12]; // L: 1314 if ((var13 - var14) * (var18 - var17) - (var16 - var17) * (var15 - var14) > 0) { // L: 1315 Rasterizer3D.field1791 = false; // L: 1316 if (var13 < 0 || var14 < 0 || var15 < 0 || var13 > Rasterizer3D.Rasterizer3D_clipWidth || var14 > Rasterizer3D.Rasterizer3D_clipWidth || var15 > Rasterizer3D.Rasterizer3D_clipWidth) { // L: 1317 @@ -1954,9 +1954,9 @@ public class Scene { if (var1.triangleTextureId != null && var1.triangleTextureId[var9] != -1) { // L: 1322 if (!Scene_isLowDetail) { // L: 1326 if (var1.isFlat) { // L: 1327 - Rasterizer3D.drawTexturedTile(var16, var17, var18, var13, var14, var15, var1.triangleColorA[var9], var1.triangleColorB[var9], var1.triangleColorC[var9], TileModel.field1610[0], TileModel.field1610[1], TileModel.field1610[3], TileModel.field1621[0], TileModel.field1621[1], TileModel.field1621[3], TileModel.field1622[0], TileModel.field1622[1], TileModel.field1622[3], var1.triangleTextureId[var9]); + Rasterizer3D.drawTexturedTile(var16, var17, var18, var13, var14, var15, var1.triangleColorA[var9], var1.triangleColorB[var9], var1.triangleColorC[var9], SceneTileModel.field1610[0], SceneTileModel.field1610[1], SceneTileModel.field1610[3], SceneTileModel.field1621[0], SceneTileModel.field1621[1], SceneTileModel.field1621[3], SceneTileModel.field1622[0], SceneTileModel.field1622[1], SceneTileModel.field1622[3], var1.triangleTextureId[var9]); } else { - Rasterizer3D.drawTexturedTile(var16, var17, var18, var13, var14, var15, var1.triangleColorA[var9], var1.triangleColorB[var9], var1.triangleColorC[var9], TileModel.field1610[var10], TileModel.field1610[var11], TileModel.field1610[var12], TileModel.field1621[var10], TileModel.field1621[var11], TileModel.field1621[var12], TileModel.field1622[var10], TileModel.field1622[var11], TileModel.field1622[var12], var1.triangleTextureId[var9]); // L: 1328 + Rasterizer3D.drawTexturedTile(var16, var17, var18, var13, var14, var15, var1.triangleColorA[var9], var1.triangleColorB[var9], var1.triangleColorC[var9], SceneTileModel.field1610[var10], SceneTileModel.field1610[var11], SceneTileModel.field1610[var12], SceneTileModel.field1621[var10], SceneTileModel.field1621[var11], SceneTileModel.field1621[var12], SceneTileModel.field1622[var10], SceneTileModel.field1622[var11], SceneTileModel.field1622[var12], var1.triangleTextureId[var9]); // L: 1328 } } else { int var19 = Rasterizer3D.Rasterizer3D_textureLoader.getAverageTextureRGB(var1.triangleTextureId[var9]); // L: 1331 diff --git a/runescape-client/src/main/java/TileModel.java b/runescape-client/src/main/java/SceneTileModel.java similarity index 97% rename from runescape-client/src/main/java/TileModel.java rename to runescape-client/src/main/java/SceneTileModel.java index d11a4c5c6a..68044a822b 100644 --- a/runescape-client/src/main/java/TileModel.java +++ b/runescape-client/src/main/java/SceneTileModel.java @@ -3,8 +3,9 @@ import net.runelite.mapping.Implements; import net.runelite.mapping.ObfuscatedName; @ObfuscatedName("eq") -@Implements("TileModel") -public final class TileModel { +@Implements("SceneTileModel") +public final class SceneTileModel +{ @ObfuscatedName("f") static int[] field1618; @ObfuscatedName("a") @@ -75,7 +76,7 @@ public final class TileModel { field1603 = new int[][]{{0, 1, 2, 3, 0, 0, 1, 3}, {1, 1, 2, 3, 1, 0, 1, 3}, {0, 1, 2, 3, 1, 0, 1, 3}, {0, 0, 1, 2, 0, 0, 2, 4, 1, 0, 4, 3}, {0, 0, 1, 4, 0, 0, 4, 3, 1, 1, 2, 4}, {0, 0, 4, 3, 1, 0, 1, 2, 1, 0, 2, 4}, {0, 1, 2, 4, 1, 0, 1, 4, 1, 0, 4, 3}, {0, 4, 1, 2, 0, 4, 2, 5, 1, 0, 4, 5, 1, 0, 5, 3}, {0, 4, 1, 2, 0, 4, 2, 3, 0, 4, 3, 5, 1, 0, 4, 5}, {0, 0, 4, 5, 1, 4, 1, 2, 1, 4, 2, 3, 1, 4, 3, 5}, {0, 0, 1, 5, 0, 1, 4, 5, 0, 1, 2, 4, 1, 0, 5, 3, 1, 5, 4, 3, 1, 4, 2, 3}, {1, 0, 1, 5, 1, 1, 4, 5, 1, 1, 2, 4, 0, 0, 5, 3, 0, 5, 4, 3, 0, 4, 2, 3}, {1, 0, 5, 4, 1, 0, 1, 5, 0, 0, 4, 3, 0, 4, 5, 3, 0, 5, 2, 3, 0, 1, 2, 5}}; // L: 41 } - TileModel(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9, int var10, int var11, int var12, int var13, int var14, int var15, int var16, int var17, int var18, int var19) { + SceneTileModel(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9, int var10, int var11, int var12, int var13, int var14, int var15, int var16, int var17, int var18, int var19) { this.isFlat = true; // L: 16 if (var7 != var6 || var8 != var6 || var9 != var6) { // L: 58 this.isFlat = false; diff --git a/runescape-client/src/main/java/TilePaint.java b/runescape-client/src/main/java/SceneTilePaint.java similarity index 96% rename from runescape-client/src/main/java/TilePaint.java rename to runescape-client/src/main/java/SceneTilePaint.java index a6541123c4..89f945d96e 100644 --- a/runescape-client/src/main/java/TilePaint.java +++ b/runescape-client/src/main/java/SceneTilePaint.java @@ -5,8 +5,9 @@ import net.runelite.mapping.ObfuscatedName; import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("ew") -@Implements("TilePaint") -public final class TilePaint { +@Implements("SceneTilePaint") +public final class SceneTilePaint +{ @ObfuscatedName("dt") @ObfuscatedSignature( descriptor = "Lil;" @@ -53,7 +54,7 @@ public final class TilePaint { @Export("rgb") int rgb; - TilePaint(int var1, int var2, int var3, int var4, int var5, int var6, boolean var7) { + SceneTilePaint(int var1, int var2, int var3, int var4, int var5, int var6, boolean var7) { this.isFlat = true; // L: 9 this.swColor = var1; // L: 13 this.seColor = var2; // L: 14 diff --git a/runescape-client/src/main/java/ScriptEvent.java b/runescape-client/src/main/java/ScriptEvent.java index d4f9ce83aa..ecaacefb25 100644 --- a/runescape-client/src/main/java/ScriptEvent.java +++ b/runescape-client/src/main/java/ScriptEvent.java @@ -158,10 +158,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 939 var30 = var9.getModel(22, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 22, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 940 + var30 = new DynamicObject(var4, 22, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 940 } - var7.newFloorDecoration(var0, var2, var3, var17, (Entity)var30, var20, var22); // L: 941 + var7.newFloorDecoration(var0, var2, var3, var17, (Renderable)var30, var20, var22); // L: 941 if (var9.interactType == 1) { // L: 942 var8.setBlockedByFloorDec(var2, var3); } @@ -171,10 +171,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 955 var30 = var9.getModel(var6, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, var6, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 956 + var30 = new DynamicObject(var4, var6, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 956 } - var7.method3160(var0, var2, var3, var17, 1, 1, (Entity)var30, 0, var20, var22); // L: 957 + var7.method3160(var0, var2, var3, var17, 1, 1, (Renderable)var30, 0, var20, var22); // L: 957 if (var9.interactType != 0) { // L: 958 var8.addGameObject(var2, var3, var10, var11, var9.boolean1); } @@ -183,10 +183,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 963 var30 = var9.getModel(0, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 0, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 964 + var30 = new DynamicObject(var4, 0, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 964 } - var7.newBoundaryObject(var0, var2, var3, var17, (Entity)var30, (Entity)null, Tiles.field512[var5], 0, var20, var22); // L: 965 + var7.newBoundaryObject(var0, var2, var3, var17, (Renderable)var30, (Renderable)null, Tiles.field512[var5], 0, var20, var22); // L: 965 if (var9.interactType != 0) { // L: 966 var8.method3611(var2, var3, var6, var5, var9.boolean1); } @@ -195,10 +195,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 971 var30 = var9.getModel(1, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 1, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 972 + var30 = new DynamicObject(var4, 1, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 972 } - var7.newBoundaryObject(var0, var2, var3, var17, (Entity)var30, (Entity)null, Tiles.field513[var5], 0, var20, var22); // L: 973 + var7.newBoundaryObject(var0, var2, var3, var17, (Renderable)var30, (Renderable)null, Tiles.field513[var5], 0, var20, var22); // L: 973 if (var9.interactType != 0) { // L: 974 var8.method3611(var2, var3, var6, var5, var9.boolean1); } @@ -213,11 +213,11 @@ public class ScriptEvent extends Node { var31 = var9.getModel(2, var5 + 4, var16, var18, var17, var19); // L: 982 var25 = var9.getModel(2, var23, var16, var18, var17, var19); // L: 983 } else { - var31 = new DynamicObject(var4, 2, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 986 - var25 = new DynamicObject(var4, 2, var23, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 987 + var31 = new DynamicObject(var4, 2, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 986 + var25 = new DynamicObject(var4, 2, var23, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 987 } - var7.newBoundaryObject(var0, var2, var3, var17, (Entity)var31, (Entity)var25, Tiles.field512[var5], Tiles.field512[var23], var20, var22); // L: 989 + var7.newBoundaryObject(var0, var2, var3, var17, (Renderable)var31, (Renderable)var25, Tiles.field512[var5], Tiles.field512[var23], var20, var22); // L: 989 if (var9.interactType != 0) { // L: 990 var8.method3611(var2, var3, var6, var5, var9.boolean1); } @@ -226,10 +226,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 995 var30 = var9.getModel(3, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 3, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 996 + var30 = new DynamicObject(var4, 3, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 996 } - var7.newBoundaryObject(var0, var2, var3, var17, (Entity)var30, (Entity)null, Tiles.field513[var5], 0, var20, var22); // L: 997 + var7.newBoundaryObject(var0, var2, var3, var17, (Renderable)var30, (Renderable)null, Tiles.field513[var5], 0, var20, var22); // L: 997 if (var9.interactType != 0) { // L: 998 var8.method3611(var2, var3, var6, var5, var9.boolean1); } @@ -238,10 +238,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 1003 var30 = var9.getModel(var6, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, var6, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1004 + var30 = new DynamicObject(var4, var6, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1004 } - var7.method3160(var0, var2, var3, var17, 1, 1, (Entity)var30, 0, var20, var22); // L: 1005 + var7.method3160(var0, var2, var3, var17, 1, 1, (Renderable)var30, 0, var20, var22); // L: 1005 if (var9.interactType != 0) { // L: 1006 var8.addGameObject(var2, var3, var10, var11, var9.boolean1); } @@ -250,10 +250,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 1011 var30 = var9.getModel(4, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 4, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1012 + var30 = new DynamicObject(var4, 4, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1012 } - var7.newWallDecoration(var0, var2, var3, var17, (Entity)var30, (Entity)null, Tiles.field512[var5], 0, 0, 0, var20, var22); // L: 1013 + var7.newWallDecoration(var0, var2, var3, var17, (Renderable)var30, (Renderable)null, Tiles.field512[var5], 0, 0, 0, var20, var22); // L: 1013 } else { long var24; Object var26; @@ -267,10 +267,10 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 1021 var26 = var9.getModel(4, var5, var16, var18, var17, var19); } else { - var26 = new DynamicObject(var4, 4, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1022 + var26 = new DynamicObject(var4, 4, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1022 } - var7.newWallDecoration(var0, var2, var3, var17, (Entity)var26, (Entity)null, Tiles.field512[var5], 0, var23 * Tiles.field518[var5], var23 * Tiles.field515[var5], var20, var22); // L: 1023 + var7.newWallDecoration(var0, var2, var3, var17, (Renderable)var26, (Renderable)null, Tiles.field512[var5], 0, var23 * Tiles.field518[var5], var23 * Tiles.field515[var5], var20, var22); // L: 1023 } else if (var6 == 6) { // L: 1026 var23 = 8; // L: 1027 var24 = var7.getBoundaryObjectTag(var0, var2, var3); // L: 1028 @@ -281,19 +281,19 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 1031 var26 = var9.getModel(4, var5 + 4, var16, var18, var17, var19); } else { - var26 = new DynamicObject(var4, 4, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1032 + var26 = new DynamicObject(var4, 4, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1032 } - var7.newWallDecoration(var0, var2, var3, var17, (Entity)var26, (Entity)null, 256, var5, var23 * Tiles.field509[var5], var23 * Tiles.field517[var5], var20, var22); // L: 1033 + var7.newWallDecoration(var0, var2, var3, var17, (Renderable)var26, (Renderable)null, 256, var5, var23 * Tiles.field509[var5], var23 * Tiles.field517[var5], var20, var22); // L: 1033 } else if (var6 == 7) { // L: 1036 int var29 = var5 + 2 & 3; // L: 1038 if (var9.animationId == -1 && var9.transforms == null) { // L: 1039 var30 = var9.getModel(4, var29 + 4, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 4, var29 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1040 + var30 = new DynamicObject(var4, 4, var29 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1040 } - var7.newWallDecoration(var0, var2, var3, var17, (Entity)var30, (Entity)null, 256, var29, 0, 0, var20, var22); // L: 1041 + var7.newWallDecoration(var0, var2, var3, var17, (Renderable)var30, (Renderable)null, 256, var29, 0, 0, var20, var22); // L: 1041 } else if (var6 == 8) { // L: 1044 var23 = 8; // L: 1045 var24 = var7.getBoundaryObjectTag(var0, var2, var3); // L: 1046 @@ -307,11 +307,11 @@ public class ScriptEvent extends Node { var26 = var9.getModel(4, var5 + 4, var16, var18, var17, var19); // L: 1052 var27 = var9.getModel(4, var28 + 4, var16, var18, var17, var19); // L: 1053 } else { - var26 = new DynamicObject(var4, 4, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1056 - var27 = new DynamicObject(var4, 4, var28 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 1057 + var26 = new DynamicObject(var4, 4, var5 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1056 + var27 = new DynamicObject(var4, 4, var28 + 4, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 1057 } - var7.newWallDecoration(var0, var2, var3, var17, (Entity)var26, (Entity)var27, 256, var5, var23 * Tiles.field509[var5], var23 * Tiles.field517[var5], var20, var22); // L: 1059 + var7.newWallDecoration(var0, var2, var3, var17, (Renderable)var26, (Renderable)var27, 256, var5, var23 * Tiles.field509[var5], var23 * Tiles.field517[var5], var20, var22); // L: 1059 } } } @@ -319,11 +319,11 @@ public class ScriptEvent extends Node { if (var9.animationId == -1 && var9.transforms == null) { // L: 947 var30 = var9.getModel(10, var5, var16, var18, var17, var19); } else { - var30 = new DynamicObject(var4, 10, var5, var1, var2, var3, var9.animationId, var9.field3429, (Entity)null); // L: 948 + var30 = new DynamicObject(var4, 10, var5, var1, var2, var3, var9.animationId, var9.field3429, (Renderable)null); // L: 948 } if (var30 != null) { // L: 949 - var7.method3160(var0, var2, var3, var17, var10, var11, (Entity)var30, var6 == 11 ? 256 : 0, var20, var22); + var7.method3160(var0, var2, var3, var17, var10, var11, (Renderable)var30, var6 == 11 ? 256 : 0, var20, var22); } if (var9.interactType != 0) { // L: 950 diff --git a/runescape-client/src/main/java/Tile.java b/runescape-client/src/main/java/Tile.java index 27ceaa09ab..f1407c48d3 100644 --- a/runescape-client/src/main/java/Tile.java +++ b/runescape-client/src/main/java/Tile.java @@ -36,13 +36,13 @@ public final class Tile extends Node { descriptor = "Lew;" ) @Export("paint") - TilePaint paint; + SceneTilePaint paint; @ObfuscatedName("j") @ObfuscatedSignature( descriptor = "Leq;" ) @Export("model") - TileModel model; + SceneTileModel model; @ObfuscatedName("n") @ObfuscatedSignature( descriptor = "Lev;" diff --git a/runescape-client/src/main/java/TileItem.java b/runescape-client/src/main/java/TileItem.java index 46686b3cb0..cf3865a904 100644 --- a/runescape-client/src/main/java/TileItem.java +++ b/runescape-client/src/main/java/TileItem.java @@ -7,7 +7,8 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("ct") @Implements("TileItem") -public final class TileItem extends Entity { +public final class TileItem extends Renderable +{ @ObfuscatedName("h") @ObfuscatedGetter( intValue = 1316978929 diff --git a/runescape-client/src/main/java/TileItemPile.java b/runescape-client/src/main/java/TileItemPile.java index c67c541149..a0b38c247f 100644 --- a/runescape-client/src/main/java/TileItemPile.java +++ b/runescape-client/src/main/java/TileItemPile.java @@ -43,19 +43,19 @@ public final class TileItemPile { descriptor = "Ler;" ) @Export("first") - Entity first; + Renderable first; @ObfuscatedName("t") @ObfuscatedSignature( descriptor = "Ler;" ) @Export("second") - Entity second; + Renderable second; @ObfuscatedName("j") @ObfuscatedSignature( descriptor = "Ler;" ) @Export("third") - Entity third; + Renderable third; @ObfuscatedName("n") @ObfuscatedGetter( longValue = 7869520931752751385L diff --git a/runescape-client/src/main/java/UserComparator4.java b/runescape-client/src/main/java/UserComparator4.java index 180d62d37c..bb67a019f8 100644 --- a/runescape-client/src/main/java/UserComparator4.java +++ b/runescape-client/src/main/java/UserComparator4.java @@ -259,10 +259,10 @@ public class UserComparator4 implements Comparator { if (var31 != null) { // L: 6801 var11 = Occluder.Entity_unpackID(var31.tag); // L: 6802 if (var3 == 2) { // L: 6803 - var31.entity1 = new DynamicObject(var11, 2, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var31.entity1); // L: 6804 - var31.entity2 = new DynamicObject(var11, 2, var4 + 1 & 3, GameObject.Client_plane, var8, var9, var6, false, var31.entity2); // L: 6805 + var31.renderable1 = new DynamicObject(var11, 2, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var31.renderable1); // L: 6804 + var31.renderable2 = new DynamicObject(var11, 2, var4 + 1 & 3, GameObject.Client_plane, var8, var9, var6, false, var31.renderable2); // L: 6805 } else { - var31.entity1 = new DynamicObject(var11, var3, var4, GameObject.Client_plane, var8, var9, var6, false, var31.entity1); // L: 6807 + var31.renderable1 = new DynamicObject(var11, var3, var4, GameObject.Client_plane, var8, var9, var6, false, var31.renderable1); // L: 6807 } } } @@ -273,15 +273,15 @@ public class UserComparator4 implements Comparator { var11 = Occluder.Entity_unpackID(var42.tag); // L: 6813 if (var3 != 4 && var3 != 5) { // L: 6814 if (var3 == 6) { // L: 6815 - var42.entity1 = new DynamicObject(var11, 4, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var42.entity1); + var42.renderable1 = new DynamicObject(var11, 4, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var42.renderable1); } else if (var3 == 7) { // L: 6816 - var42.entity1 = new DynamicObject(var11, 4, (var4 + 2 & 3) + 4, GameObject.Client_plane, var8, var9, var6, false, var42.entity1); + var42.renderable1 = new DynamicObject(var11, 4, (var4 + 2 & 3) + 4, GameObject.Client_plane, var8, var9, var6, false, var42.renderable1); } else if (var3 == 8) { // L: 6817 - var42.entity1 = new DynamicObject(var11, 4, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var42.entity1); // L: 6818 - var42.entity2 = new DynamicObject(var11, 4, (var4 + 2 & 3) + 4, GameObject.Client_plane, var8, var9, var6, false, var42.entity2); // L: 6819 + var42.renderable1 = new DynamicObject(var11, 4, var4 + 4, GameObject.Client_plane, var8, var9, var6, false, var42.renderable1); // L: 6818 + var42.renderable2 = new DynamicObject(var11, 4, (var4 + 2 & 3) + 4, GameObject.Client_plane, var8, var9, var6, false, var42.renderable2); // L: 6819 } } else { - var42.entity1 = new DynamicObject(var11, 4, var4, GameObject.Client_plane, var8, var9, var6, false, var42.entity1); + var42.renderable1 = new DynamicObject(var11, 4, var4, GameObject.Client_plane, var8, var9, var6, false, var42.renderable1); } } } @@ -293,14 +293,14 @@ public class UserComparator4 implements Comparator { } if (var43 != null) { // L: 6826 - var43.entity = new DynamicObject(Occluder.Entity_unpackID(var43.tag), var3, var4, GameObject.Client_plane, var8, var9, var6, false, var43.entity); + var43.renderable = new DynamicObject(Occluder.Entity_unpackID(var43.tag), var3, var4, GameObject.Client_plane, var8, var9, var6, false, var43.renderable); } } if (var5 == 3) { // L: 6828 FloorDecoration var44 = ArchiveLoader.scene.getFloorDecoration(GameObject.Client_plane, var8, var9); // L: 6829 if (var44 != null) { // L: 6830 - var44.entity = new DynamicObject(Occluder.Entity_unpackID(var44.tag), 22, var4, GameObject.Client_plane, var8, var9, var6, false, var44.entity); + var44.renderable = new DynamicObject(Occluder.Entity_unpackID(var44.tag), 22, var4, GameObject.Client_plane, var8, var9, var6, false, var44.renderable); } } } diff --git a/runescape-client/src/main/java/WallDecoration.java b/runescape-client/src/main/java/WallDecoration.java index 7d346eb013..fedcca2314 100644 --- a/runescape-client/src/main/java/WallDecoration.java +++ b/runescape-client/src/main/java/WallDecoration.java @@ -54,13 +54,13 @@ public final class WallDecoration { descriptor = "Ler;" ) @Export("entity1") - public Entity entity1; + public Renderable renderable1; @ObfuscatedName("l") @ObfuscatedSignature( descriptor = "Ler;" ) @Export("entity2") - public Entity entity2; + public Renderable renderable2; @ObfuscatedName("z") @ObfuscatedGetter( longValue = 3554481859042868621L diff --git a/runescape-client/src/main/java/WorldMapID.java b/runescape-client/src/main/java/WorldMapID.java index b70e72d2d2..27ddf4a313 100644 --- a/runescape-client/src/main/java/WorldMapID.java +++ b/runescape-client/src/main/java/WorldMapID.java @@ -639,7 +639,7 @@ public class WorldMapID { } } - TilePaint.method3154(var0); // L: 3971 + SceneTilePaint.method3154(var0); // L: 3971 } // L: 3972 @ObfuscatedName("ik") diff --git a/runescape-client/src/main/java/WorldMapRectangle.java b/runescape-client/src/main/java/WorldMapRectangle.java index 96c0169d34..ef6515ca90 100644 --- a/runescape-client/src/main/java/WorldMapRectangle.java +++ b/runescape-client/src/main/java/WorldMapRectangle.java @@ -136,7 +136,7 @@ public final class WorldMapRectangle { Widget.Widget_cachedSpriteMasks.clear(); // L: 2900 ((TextureProvider)Rasterizer3D.Rasterizer3D_textureLoader).clear(); // L: 2902 Script.Script_cached.clear(); // L: 2903 - TilePaint.archive0.clearFiles(); // L: 2904 + SceneTilePaint.archive0.clearFiles(); // L: 2904 WorldMapSprite.archive1.clearFiles(); // L: 2905 class330.archive3.clearFiles(); // L: 2906 class227.archive4.clearFiles(); // L: 2907 diff --git a/runescape-client/src/main/java/class238.java b/runescape-client/src/main/java/class238.java index 7e762f1e54..20382e130c 100644 --- a/runescape-client/src/main/java/class238.java +++ b/runescape-client/src/main/java/class238.java @@ -41,7 +41,7 @@ public class class238 { garbageValue = "1585982662" ) public static String method4166(CharSequence var0) { - String var1 = ParamDefinition.base37DecodeLong(Entity.method3340(var0)); // L: 75 + String var1 = ParamDefinition.base37DecodeLong(Renderable.method3340(var0)); // L: 75 if (var1 == null) { // L: 76 var1 = ""; } diff --git a/runescape-client/src/main/java/class7.java b/runescape-client/src/main/java/class7.java index d697d70e67..5ebfdf7f0c 100644 --- a/runescape-client/src/main/java/class7.java +++ b/runescape-client/src/main/java/class7.java @@ -137,10 +137,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 300 var34 = var8.getEntity(22, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 22, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 301 + var34 = new DynamicObject(var3, 22, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 301 } - var6.newFloorDecoration(var0, var1, var2, var16, (Entity)var34, var19, var21); // L: 302 + var6.newFloorDecoration(var0, var1, var2, var16, (Renderable)var34, var19, var21); // L: 302 if (var8.interactType == 1 && var7 != null) { // L: 303 var7.setBlockedByFloorDec(var1, var2); } @@ -152,10 +152,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 329 var34 = var8.getEntity(var5, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, var5, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 330 + var34 = new DynamicObject(var3, var5, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 330 } - var6.method3160(var0, var1, var2, var16, 1, 1, (Entity)var34, 0, var19, var21); // L: 331 + var6.method3160(var0, var1, var2, var16, 1, 1, (Renderable)var34, 0, var19, var21); // L: 331 if (var5 >= 12 && var5 <= 17 && var5 != 13 && var0 > 0) { // L: 332 var10000 = GrandExchangeOfferWorldComparator.field52[var0][var1]; var10000[var2] |= 2340; @@ -169,10 +169,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 338 var34 = var8.getEntity(0, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 0, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 339 + var34 = new DynamicObject(var3, 0, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 339 } - var6.newBoundaryObject(var0, var1, var2, var16, (Entity)var34, (Entity)null, Tiles.field512[var4], 0, var19, var21); // L: 340 + var6.newBoundaryObject(var0, var1, var2, var16, (Renderable)var34, (Renderable)null, Tiles.field512[var4], 0, var19, var21); // L: 340 if (var4 == 0) { // L: 341 if (var8.clipped) { // L: 342 Tiles.field511[var0][var1][var2] = 50; // L: 343 @@ -227,10 +227,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 375 var34 = var8.getEntity(1, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 1, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 376 + var34 = new DynamicObject(var3, 1, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 376 } - var6.newBoundaryObject(var0, var1, var2, var16, (Entity)var34, (Entity)null, Tiles.field513[var4], 0, var19, var21); // L: 377 + var6.newBoundaryObject(var0, var1, var2, var16, (Renderable)var34, (Renderable)null, Tiles.field513[var4], 0, var19, var21); // L: 377 if (var8.clipped) { // L: 378 if (var4 == 0) { // L: 379 Tiles.field511[var0][var1][var2 + 1] = 50; @@ -257,11 +257,11 @@ public enum class7 implements Enumerated { var31 = var8.getEntity(2, var4 + 4, var15, var17, var16, var18); // L: 392 var32 = var8.getEntity(2, var28, var15, var17, var16, var18); // L: 393 } else { - var31 = new DynamicObject(var3, 2, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 396 - var32 = new DynamicObject(var3, 2, var28, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 397 + var31 = new DynamicObject(var3, 2, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 396 + var32 = new DynamicObject(var3, 2, var28, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 397 } - var6.newBoundaryObject(var0, var1, var2, var16, (Entity)var31, (Entity)var32, Tiles.field512[var4], Tiles.field512[var28], var19, var21); // L: 399 + var6.newBoundaryObject(var0, var1, var2, var16, (Renderable)var31, (Renderable)var32, Tiles.field512[var4], Tiles.field512[var28], var19, var21); // L: 399 if (var8.modelClipped) { // L: 400 if (var4 == 0) { // L: 401 var10000 = GrandExchangeOfferWorldComparator.field52[var0][var1]; // L: 402 @@ -298,10 +298,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 424 var34 = var8.getEntity(3, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 3, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 425 + var34 = new DynamicObject(var3, 3, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 425 } - var6.newBoundaryObject(var0, var1, var2, var16, (Entity)var34, (Entity)null, Tiles.field513[var4], 0, var19, var21); // L: 426 + var6.newBoundaryObject(var0, var1, var2, var16, (Renderable)var34, (Renderable)null, Tiles.field513[var4], 0, var19, var21); // L: 426 if (var8.clipped) { // L: 427 if (var4 == 0) { // L: 428 Tiles.field511[var0][var1][var2 + 1] = 50; @@ -322,10 +322,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 438 var34 = var8.getEntity(var5, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, var5, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 439 + var34 = new DynamicObject(var3, var5, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 439 } - var6.method3160(var0, var1, var2, var16, 1, 1, (Entity)var34, 0, var19, var21); // L: 440 + var6.method3160(var0, var1, var2, var16, 1, 1, (Renderable)var34, 0, var19, var21); // L: 440 if (var8.interactType != 0 && var7 != null) { // L: 441 var7.addGameObject(var1, var2, var9, var10, var8.boolean1); } @@ -338,10 +338,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 447 var34 = var8.getEntity(4, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 4, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 448 + var34 = new DynamicObject(var3, 4, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 448 } - var6.newWallDecoration(var0, var1, var2, var16, (Entity)var34, (Entity)null, Tiles.field512[var4], 0, 0, 0, var19, var21); // L: 449 + var6.newWallDecoration(var0, var1, var2, var16, (Renderable)var34, (Renderable)null, Tiles.field512[var4], 0, 0, 0, var19, var21); // L: 449 } else { Object var25; long var29; @@ -355,10 +355,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 457 var25 = var8.getEntity(4, var4, var15, var17, var16, var18); } else { - var25 = new DynamicObject(var3, 4, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 458 + var25 = new DynamicObject(var3, 4, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 458 } - var6.newWallDecoration(var0, var1, var2, var16, (Entity)var25, (Entity)null, Tiles.field512[var4], 0, var28 * Tiles.field518[var4], var28 * Tiles.field515[var4], var19, var21); // L: 459 + var6.newWallDecoration(var0, var1, var2, var16, (Renderable)var25, (Renderable)null, Tiles.field512[var4], 0, var28 * Tiles.field518[var4], var28 * Tiles.field515[var4], var19, var21); // L: 459 } else if (var5 == 6) { // L: 462 var28 = 8; // L: 463 var29 = var6.getBoundaryObjectTag(var0, var1, var2); // L: 464 @@ -369,19 +369,19 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 467 var25 = var8.getEntity(4, var4 + 4, var15, var17, var16, var18); } else { - var25 = new DynamicObject(var3, 4, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 468 + var25 = new DynamicObject(var3, 4, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 468 } - var6.newWallDecoration(var0, var1, var2, var16, (Entity)var25, (Entity)null, 256, var4, var28 * Tiles.field509[var4], var28 * Tiles.field517[var4], var19, var21); // L: 469 + var6.newWallDecoration(var0, var1, var2, var16, (Renderable)var25, (Renderable)null, 256, var4, var28 * Tiles.field509[var4], var28 * Tiles.field517[var4], var19, var21); // L: 469 } else if (var5 == 7) { // L: 472 var23 = var4 + 2 & 3; // L: 474 if (var8.animationId == -1 && var8.transforms == null) { // L: 475 var34 = var8.getEntity(4, var23 + 4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 4, var23 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 476 + var34 = new DynamicObject(var3, 4, var23 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 476 } - var6.newWallDecoration(var0, var1, var2, var16, (Entity)var34, (Entity)null, 256, var23, 0, 0, var19, var21); // L: 477 + var6.newWallDecoration(var0, var1, var2, var16, (Renderable)var34, (Renderable)null, 256, var23, 0, 0, var19, var21); // L: 477 } else if (var5 == 8) { // L: 480 var28 = 8; // L: 481 var29 = var6.getBoundaryObjectTag(var0, var1, var2); // L: 482 @@ -395,11 +395,11 @@ public enum class7 implements Enumerated { var25 = var8.getEntity(4, var4 + 4, var15, var17, var16, var18); // L: 488 var26 = var8.getEntity(4, var27 + 4, var15, var17, var16, var18); // L: 489 } else { - var25 = new DynamicObject(var3, 4, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 492 - var26 = new DynamicObject(var3, 4, var27 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 493 + var25 = new DynamicObject(var3, 4, var4 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 492 + var26 = new DynamicObject(var3, 4, var27 + 4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 493 } - var6.newWallDecoration(var0, var1, var2, var16, (Entity)var25, (Entity)var26, 256, var4, var28 * Tiles.field509[var4], var28 * Tiles.field517[var4], var19, var21); // L: 495 + var6.newWallDecoration(var0, var1, var2, var16, (Renderable)var25, (Renderable)var26, 256, var4, var28 * Tiles.field509[var4], var28 * Tiles.field517[var4], var19, var21); // L: 495 } } } @@ -407,10 +407,10 @@ public enum class7 implements Enumerated { if (var8.animationId == -1 && var8.transforms == null) { // L: 308 var34 = var8.getEntity(10, var4, var15, var17, var16, var18); } else { - var34 = new DynamicObject(var3, 10, var4, var0, var1, var2, var8.animationId, var8.field3429, (Entity)null); // L: 309 + var34 = new DynamicObject(var3, 10, var4, var0, var1, var2, var8.animationId, var8.field3429, (Renderable)null); // L: 309 } - if (var34 != null && var6.method3160(var0, var1, var2, var16, var9, var10, (Entity)var34, var5 == 11 ? 256 : 0, var19, var21) && var8.clipped) { // L: 310 311 + if (var34 != null && var6.method3160(var0, var1, var2, var16, var9, var10, (Renderable)var34, var5 == 11 ? 256 : 0, var19, var21) && var8.clipped) { // L: 310 311 var23 = 15; // L: 312 if (var34 instanceof Model) { // L: 313 var23 = ((Model)var34).method2991() / 4; // L: 314