From b12bd04764ccbc447126a8120210cf85d5639d0f Mon Sep 17 00:00:00 2001 From: Julian Nowaczek Date: Sat, 16 Sep 2017 15:28:42 -0500 Subject: [PATCH] Hiscore feature expansion (#152) * Add remaining Hiscore parameters to HiscoreSkill * Add remaining Hiscore parameters to HiscoreResult * Add remaining Hiscore parameters to HiscoreResultBuilder * Add new Hiscore panel icons (from offical Hiscore website, so they don't match very well) and subpanel for Clue Scrolls, Bounty Hunter - Hunter, Bounty Hunter - Rogue, and Last Man Standing * Add logic to catch unranked hiscores and display them properly. Not currently checking for combat level calculations, but other cases should be covered. * Make HiscoreService and HiscoreClient aware of different hiscore endpoints * Add Spring Editor to convert path variable String to enum, add pretty versions of HiscoreEndpoint names, add new icons for endpoint selection * Fix HiscoreEndpoint.valueof failing silently and preventing lookup, update HiscoreService tests, add Hiscore endpoint selection buttons to HiscorePanel * Replace HiscorePanel skill icons with smaller versions from the official hiscore website * Fix details listing rank instead of experience * Fix details listing rank instead of experience, fix skill panels not being cleared when selecting a different hiscore category, make HiscoreService respond 404 when a Hiscore entry is not found instead of 500. * Fix skill panels not being cleared when selecting a different hiscore category, make HiscoreService respond 404 when a Hiscore entry is not found instead of 500. * Revert changing RuneliteAPI base URL, those changes should not have been committed (local testing only) * Add ClueScrollAll and ClueScrollMaster to HiscoreService tests. * Style cleanup and relocate NotFoundException to http-service package * Use relative path for small skill icons * Move Jagex Hiscore urls from HiscoreService to HiscoreEndpoint * Create new util package in http-service for common exceptions and Spring converters, clean up HiscoreService by streamlining error handling and removing methods for old unit test * Change HiscoreService unit test to use new HiscoreTestService subclass which handles setting the test URL * Change HiscoreEndpoint hiscoreUrls to HttpUrl instead of String * Cleanup formatting, remove unused http-service exception * http-api: cleanup HiscoreEndpoint --- .../http/api/hiscore/HiscoreClient.java | 16 +- .../http/api/hiscore/HiscoreEndpoint.java | 58 ++++++ .../http/api/hiscore/HiscoreResult.java | 166 ++++++++++++++- .../http/api/hiscore/HiscoreSkill.java | 11 +- .../http/service/InstantConverter.java | 47 ----- .../service/SpringBootWebApplication.java | 2 + .../http/service/cache/CacheService.java | 1 + .../http/service/cache/NotFoundException.java | 34 ---- .../service/hiscore/HiscoreResultBuilder.java | 9 + .../http/service/hiscore/HiscoreService.java | 81 +++++--- .../service/util/HiscoreEndpointEditor.java | 40 ++++ .../http/service/util/InstantConverter.java | 48 +++++ .../InternalServerErrorException.java | 38 ++++ .../util/exception/NotFoundException.java | 35 ++++ .../service/SpringBootWebApplicationTest.java | 2 + .../service/hiscore/HiscoreServiceTest.java | 74 +++---- .../service/hiscore/HiscoreTestService.java | 48 +++++ .../client/plugins/hiscore/HiscorePanel.java | 189 +++++++++++++----- .../client/plugins/hiscore/deadman.png | Bin 0 -> 465 bytes .../plugins/hiscore/deadman_selected.png | Bin 0 -> 505 bytes .../plugins/hiscore/hardcore_ironman.png | Bin 0 -> 280 bytes .../hiscore/hardcore_ironman_selected.png | Bin 0 -> 325 bytes .../client/plugins/hiscore/ironman.png | Bin 0 -> 307 bytes .../plugins/hiscore/ironman_selected.png | Bin 0 -> 331 bytes .../client/plugins/hiscore/normal.png | Bin 0 -> 391 bytes .../plugins/hiscore/normal_selected.png | Bin 0 -> 448 bytes .../plugins/hiscore/seasonal_deadman.png | Bin 0 -> 325 bytes .../hiscore/seasonal_deadman_selected.png | Bin 0 -> 373 bytes .../hiscore/skill_icons_small/agility.png | Bin 0 -> 203 bytes .../hiscore/skill_icons_small/attack.png | Bin 0 -> 225 bytes .../bounty hunter - hunter.png | Bin 0 -> 1421 bytes .../bounty hunter - rogue.png | Bin 0 -> 1421 bytes .../skill_icons_small/clue scrolls (all).png | Bin 0 -> 297 bytes .../hiscore/skill_icons_small/combat.png | Bin 0 -> 465 bytes .../skill_icons_small/construction.png | Bin 0 -> 216 bytes .../hiscore/skill_icons_small/cooking.png | Bin 0 -> 220 bytes .../hiscore/skill_icons_small/crafting.png | Bin 0 -> 220 bytes .../hiscore/skill_icons_small/defence.png | Bin 0 -> 190 bytes .../hiscore/skill_icons_small/farming.png | Bin 0 -> 373 bytes .../hiscore/skill_icons_small/firemaking.png | Bin 0 -> 240 bytes .../hiscore/skill_icons_small/fishing.png | Bin 0 -> 240 bytes .../hiscore/skill_icons_small/fletching.png | Bin 0 -> 201 bytes .../hiscore/skill_icons_small/herblore.png | Bin 0 -> 222 bytes .../hiscore/skill_icons_small/hitpoints.png | Bin 0 -> 223 bytes .../hiscore/skill_icons_small/hunter.png | Bin 0 -> 277 bytes .../skill_icons_small/last man standing.png | Bin 0 -> 18662 bytes .../hiscore/skill_icons_small/magic.png | Bin 0 -> 231 bytes .../hiscore/skill_icons_small/mining.png | Bin 0 -> 212 bytes .../hiscore/skill_icons_small/overall.png | Bin 0 -> 291 bytes .../hiscore/skill_icons_small/prayer.png | Bin 0 -> 321 bytes .../hiscore/skill_icons_small/ranged.png | Bin 0 -> 258 bytes .../hiscore/skill_icons_small/runecraft.png | Bin 0 -> 251 bytes .../hiscore/skill_icons_small/slayer.png | Bin 0 -> 546 bytes .../hiscore/skill_icons_small/smithing.png | Bin 0 -> 237 bytes .../hiscore/skill_icons_small/strength.png | Bin 0 -> 232 bytes .../hiscore/skill_icons_small/thieving.png | Bin 0 -> 169 bytes .../hiscore/skill_icons_small/woodcutting.png | Bin 0 -> 240 bytes .../plugins/hiscore/ultimate_ironman.png | Bin 0 -> 365 bytes .../hiscore/ultimate_ironman_selected.png | Bin 0 -> 433 bytes 59 files changed, 692 insertions(+), 207 deletions(-) create mode 100644 http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreEndpoint.java delete mode 100644 http-service/src/main/java/net/runelite/http/service/InstantConverter.java delete mode 100644 http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/HiscoreEndpointEditor.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java create mode 100644 http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java create mode 100644 http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreTestService.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman_selected.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman_selected.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman_selected.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal_selected.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/seasonal_deadman.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/seasonal_deadman_selected.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/agility.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/attack.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - hunter.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - rogue.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/clue scrolls (all).png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/combat.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/construction.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/cooking.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/crafting.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/defence.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/farming.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/firemaking.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/fishing.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/fletching.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/herblore.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hitpoints.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hunter.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/last man standing.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/magic.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/mining.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/overall.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/prayer.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/ranged.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/runecraft.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/slayer.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/smithing.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/strength.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/thieving.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/woodcutting.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman_selected.png diff --git a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreClient.java b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreClient.java index f8257b88d3..bf5499d297 100644 --- a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreClient.java +++ b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreClient.java @@ -40,10 +40,11 @@ public class HiscoreClient { private static final Logger logger = LoggerFactory.getLogger(HiscoreClient.class); - public HiscoreResult lookup(String username) throws IOException + public HiscoreResult lookup(String username, HiscoreEndpoint endpoint) throws IOException { HttpUrl.Builder builder = RuneliteAPI.getApiBase().newBuilder() .addPathSegment("hiscore") + .addPathSegment(endpoint.name().toLowerCase()) .addQueryParameter("username", username); HttpUrl url = builder.build(); @@ -67,10 +68,16 @@ public class HiscoreClient } } - public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill) throws IOException + public HiscoreResult lookup(String username) throws IOException + { + return lookup(username, HiscoreEndpoint.NORMAL); + } + + public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill, HiscoreEndpoint endpoint) throws IOException { HttpUrl.Builder builder = RuneliteAPI.getApiBase().newBuilder() .addPathSegment("hiscore") + .addPathSegment(endpoint.name()) .addPathSegment(skill.toString().toLowerCase()) .addQueryParameter("username", username); @@ -94,4 +101,9 @@ public class HiscoreClient throw new IOException(ex); } } + + public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill) throws IOException + { + return lookup(username, skill, HiscoreEndpoint.NORMAL); + } } diff --git a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreEndpoint.java b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreEndpoint.java new file mode 100644 index 0000000000..c9e11529a1 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreEndpoint.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.api.hiscore; + +import okhttp3.HttpUrl; + +public enum HiscoreEndpoint +{ + NORMAL("Normal", "http://services.runescape.com/m=hiscore_oldschool/index_lite.ws"), + IRONMAN("Ironman", "http://services.runescape.com/m=hiscore_oldschool_ironman/index_lite.ws"), + HARDCORE_IRONMAN("Hardcore Ironman", "http://services.runescape.com/m=hiscore_oldschool_hardcore_ironman/index_lite.ws"), + ULTIMATE_IRONMAN("Ultimate Ironman", "http://services.runescape.com/m=hiscore_oldschool_ultimate/index_lite.ws"), + DEADMAN("Deadman", "http://services.runescape.com/m=hiscore_oldschool_deadman/index_lite.ws"), + SEASONAL_DEADMAN("Seasonal Deadman", "http://services.runescape.com/m=hiscore_oldschool_seasonal/index_lite.ws"); + + private final String name; + private final HttpUrl hiscoreURL; + + HiscoreEndpoint(String name, String hiscoreURL) + { + this.name = name; + this.hiscoreURL = HttpUrl.parse(hiscoreURL); + } + + public String getName() + { + return name; + } + + public HttpUrl getHiscoreURL() + { + return hiscoreURL; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreResult.java b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreResult.java index a1e41e0fa2..06f2921d99 100644 --- a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreResult.java +++ b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreResult.java @@ -53,6 +53,15 @@ public class HiscoreResult private Skill runecraft; private Skill hunter; private Skill construction; + private Skill clueScrollEasy; + private Skill clueScrollMedium; + private Skill clueScrollAll; + private Skill bountyHunterRogue; + private Skill bountyHunterHunter; + private Skill clueScrollHard; + private Skill lastManStanding; + private Skill clueScrollElite; + private Skill clueScrollMaster; public String getPlayer() { @@ -304,6 +313,96 @@ public class HiscoreResult this.construction = construction; } + public Skill getClueScrollEasy() + { + return clueScrollEasy; + } + + public void setClueScrollEasy(Skill clueScrollEasy) + { + this.clueScrollEasy = clueScrollEasy; + } + + public Skill getClueScrollMedium() + { + return clueScrollMedium; + } + + public void setClueScrollMedium(Skill clueScrollMedium) + { + this.clueScrollMedium = clueScrollMedium; + } + + public Skill getClueScrollAll() + { + return clueScrollAll; + } + + public void setClueScrollAll(Skill clueScrollAll) + { + this.clueScrollAll = clueScrollAll; + } + + public Skill getBountyHunterRogue() + { + return bountyHunterRogue; + } + + public void setBountyHunterRogue(Skill bountyHunterRogue) + { + this.bountyHunterRogue = bountyHunterRogue; + } + + public Skill getBountyHunterHunter() + { + return bountyHunterHunter; + } + + public void setBountyHunterHunter(Skill bountyHunterHunter) + { + this.bountyHunterHunter = bountyHunterHunter; + } + + public Skill getClueScrollHard() + { + return clueScrollHard; + } + + public void setClueScrollHard(Skill clueScrollHard) + { + this.clueScrollHard = clueScrollHard; + } + + public Skill getLastManStanding() + { + return lastManStanding; + } + + public void setLastManStanding(Skill lastManStanding) + { + this.lastManStanding = lastManStanding; + } + + public Skill getClueScrollElite() + { + return clueScrollElite; + } + + public void setClueScrollElite(Skill clueScrollElite) + { + this.clueScrollElite = clueScrollElite; + } + + public Skill getClueScrollMaster() + { + return clueScrollMaster; + } + + public void setClueScrollMaster(Skill clueScrollMaster) + { + this.clueScrollMaster = clueScrollMaster; + } + public Skill getSkill(HiscoreSkill skill) { switch (skill) @@ -356,9 +455,27 @@ public class HiscoreResult return getConstruction(); case OVERALL: return getOverall(); + case CLUE_SCROLL_EASY: + return getClueScrollEasy(); + case CLUE_SCROLL_MEDIUM: + return getClueScrollMedium(); + case CLUE_SCROLL_ALL: + return getClueScrollAll(); + case BOUNTY_HUNTER_ROGUE: + return getBountyHunterRogue(); + case BOUNTY_HUNTER_HUNTER: + return getBountyHunterHunter(); + case CLUE_SCROLL_HARD: + return getClueScrollHard(); + case LAST_MAN_STANDING: + return getLastManStanding(); + case CLUE_SCROLL_ELITE: + return getClueScrollElite(); + case CLUE_SCROLL_MASTER: + return getClueScrollMaster(); } - throw new IllegalArgumentException("Invalid skill"); + throw new IllegalArgumentException("Invalid hiscore item"); } @Override @@ -390,6 +507,15 @@ public class HiscoreResult hash = 29 * hash + Objects.hashCode(this.runecraft); hash = 29 * hash + Objects.hashCode(this.hunter); hash = 29 * hash + Objects.hashCode(this.construction); + hash = 29 * hash + Objects.hashCode(this.clueScrollEasy); + hash = 29 * hash + Objects.hashCode(this.clueScrollMedium); + hash = 29 * hash + Objects.hashCode(this.clueScrollAll); + hash = 29 * hash + Objects.hashCode(this.bountyHunterRogue); + hash = 29 * hash + Objects.hashCode(this.bountyHunterHunter); + hash = 29 * hash + Objects.hashCode(this.clueScrollHard); + hash = 29 * hash + Objects.hashCode(this.lastManStanding); + hash = 29 * hash + Objects.hashCode(this.clueScrollElite); + hash = 29 * hash + Objects.hashCode(this.clueScrollMaster); return hash; } @@ -509,12 +635,48 @@ public class HiscoreResult { return false; } + if (!Objects.equals(this.clueScrollEasy, other.clueScrollEasy)) + { + return false; + } + if (!Objects.equals(this.clueScrollMedium, other.clueScrollMedium)) + { + return false; + } + if (!Objects.equals(this.clueScrollAll, other.clueScrollAll)) + { + return false; + } + if (!Objects.equals(this.bountyHunterRogue, other.bountyHunterRogue)) + { + return false; + } + if (!Objects.equals(this.bountyHunterHunter, other.bountyHunterHunter)) + { + return false; + } + if (!Objects.equals(this.clueScrollHard, other.clueScrollHard)) + { + return false; + } + if (!Objects.equals(this.lastManStanding, other.lastManStanding)) + { + return false; + } + if (!Objects.equals(this.clueScrollElite, other.clueScrollElite)) + { + return false; + } + if (!Objects.equals(this.clueScrollMaster, other.clueScrollMaster)) + { + return false; + } return true; } @Override public String toString() { - return "HiscoreResult{" + "player=" + player + ", overall=" + overall + ", attack=" + attack + ", defence=" + defence + ", strength=" + strength + ", hitpoints=" + hitpoints + ", ranged=" + ranged + ", prayer=" + prayer + ", magic=" + magic + ", cooking=" + cooking + ", woodcutting=" + woodcutting + ", fletching=" + fletching + ", fishing=" + fishing + ", firemaking=" + firemaking + ", crafting=" + crafting + ", smithing=" + smithing + ", mining=" + mining + ", herblore=" + herblore + ", agility=" + agility + ", thieving=" + thieving + ", slayer=" + slayer + ", farming=" + farming + ", runecraft=" + runecraft + ", hunter=" + hunter + ", construction=" + construction + '}'; + return "HiscoreResult{" + "player=" + player + ", overall=" + overall + ", attack=" + attack + ", defence=" + defence + ", strength=" + strength + ", hitpoints=" + hitpoints + ", ranged=" + ranged + ", prayer=" + prayer + ", magic=" + magic + ", cooking=" + cooking + ", woodcutting=" + woodcutting + ", fletching=" + fletching + ", fishing=" + fishing + ", firemaking=" + firemaking + ", crafting=" + crafting + ", smithing=" + smithing + ", mining=" + mining + ", herblore=" + herblore + ", agility=" + agility + ", thieving=" + thieving + ", slayer=" + slayer + ", farming=" + farming + ", runecraft=" + runecraft + ", hunter=" + hunter + ", construction=" + construction + ", clueScrollEasy=" + clueScrollEasy + ", clueScrollMedium=" + clueScrollMedium + ", clueScrollAll=" + clueScrollAll + ", bountyHunterRogue=" + bountyHunterRogue + ", bountyHunterHunter=" + bountyHunterHunter + ", clueScrollHard=" + clueScrollHard + ", lastManStanding=" + lastManStanding + ", clueScrollElite=" + clueScrollElite + ", clueScrollMaster=" + clueScrollMaster + '}'; } } diff --git a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreSkill.java b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreSkill.java index 6eee6ca75d..57cc7423d5 100644 --- a/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreSkill.java +++ b/http-api/src/main/java/net/runelite/http/api/hiscore/HiscoreSkill.java @@ -49,7 +49,16 @@ public enum HiscoreSkill FARMING("Farming"), RUNECRAFT("Runecraft"), HUNTER("Hunter"), - CONSTRUCTION("Construction"); + CONSTRUCTION("Construction"), + CLUE_SCROLL_EASY("Clue Scrolls (easy)"), + CLUE_SCROLL_MEDIUM("Clue Scrolls (medium)"), + CLUE_SCROLL_ALL("Clue Scrolls (all)"), + BOUNTY_HUNTER_ROGUE("Bounty Hunter - Rogue"), + BOUNTY_HUNTER_HUNTER("Bounty Hunter - Hunter"), + CLUE_SCROLL_HARD("Clue Scrolls (hard)"), + LAST_MAN_STANDING("Last Man Standing"), + CLUE_SCROLL_ELITE("Clue Scrolls (elite)"), + CLUE_SCROLL_MASTER("Clue Scrolls (master)"); private final String name; diff --git a/http-service/src/main/java/net/runelite/http/service/InstantConverter.java b/http-service/src/main/java/net/runelite/http/service/InstantConverter.java deleted file mode 100644 index 808479018b..0000000000 --- a/http-service/src/main/java/net/runelite/http/service/InstantConverter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2017, 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.http.service; - -import java.sql.Timestamp; -import java.time.Instant; -import org.sql2o.converters.Converter; -import org.sql2o.converters.ConverterException; - -public class InstantConverter implements Converter -{ - @Override - public Instant convert(Object val) throws ConverterException - { - Timestamp ts = (Timestamp) val; - return ts.toInstant(); - } - - @Override - public Object toDatabaseParam(Instant val) - { - return Timestamp.from(val); - } - -} diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java index 5f259427dd..319ecaee9c 100644 --- a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java +++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -31,6 +31,8 @@ import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; + +import net.runelite.http.service.util.InstantConverter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java index 42ef759231..d1f6251608 100644 --- a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java @@ -55,6 +55,7 @@ import net.runelite.cache.fs.jagex.DataFileReadResult; import net.runelite.http.api.cache.Cache; import net.runelite.http.api.cache.CacheArchive; import net.runelite.http.api.cache.CacheIndex; +import net.runelite.http.service.util.exception.NotFoundException; import net.runelite.http.service.cache.beans.ArchiveEntry; import net.runelite.http.service.cache.beans.CacheEntry; import net.runelite.http.service.cache.beans.FileEntry; diff --git a/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java b/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java deleted file mode 100644 index 4787f0f859..0000000000 --- a/http-service/src/main/java/net/runelite/http/service/cache/NotFoundException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2017, 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.http.service.cache; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found") -public class NotFoundException extends RuntimeException -{ - -} diff --git a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreResultBuilder.java b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreResultBuilder.java index 1d8034ab38..cefe39b471 100644 --- a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreResultBuilder.java +++ b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreResultBuilder.java @@ -77,6 +77,15 @@ public class HiscoreResultBuilder hiscoreResult.setRunecraft(skills.get(21)); hiscoreResult.setHunter(skills.get(22)); hiscoreResult.setConstruction(skills.get(23)); + hiscoreResult.setClueScrollEasy(skills.get(24)); + hiscoreResult.setClueScrollMedium(skills.get(25)); + hiscoreResult.setClueScrollAll(skills.get(26)); + hiscoreResult.setBountyHunterRogue(skills.get(27)); + hiscoreResult.setBountyHunterHunter(skills.get(28)); + hiscoreResult.setClueScrollHard(skills.get(29)); + hiscoreResult.setLastManStanding(skills.get(30)); + hiscoreResult.setClueScrollElite(skills.get(31)); + hiscoreResult.setClueScrollMaster(skills.get(32)); return hiscoreResult; } } diff --git a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreService.java b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreService.java index 9d1e1591be..107e9d17f3 100644 --- a/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreService.java +++ b/http-service/src/main/java/net/runelite/http/service/hiscore/HiscoreService.java @@ -26,10 +26,10 @@ package net.runelite.http.service.hiscore; import java.io.IOException; import net.runelite.http.api.RuneliteAPI; -import net.runelite.http.api.hiscore.HiscoreResult; -import net.runelite.http.api.hiscore.SingleHiscoreSkillResult; -import net.runelite.http.api.hiscore.Skill; -import net.runelite.http.api.hiscore.HiscoreSkill; +import net.runelite.http.api.hiscore.*; +import net.runelite.http.service.util.HiscoreEndpointEditor; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import net.runelite.http.service.util.exception.NotFoundException; import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.Response; @@ -39,10 +39,9 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/hiscore") @@ -50,23 +49,36 @@ public class HiscoreService { private static final Logger logger = LoggerFactory.getLogger(HiscoreService.class); - private static final HttpUrl RUNESCAPE_HISCORE_SERVICE = HttpUrl.parse("http://services.runescape.com/m=hiscore_oldschool/index_lite.ws"); - - private HttpUrl url = RUNESCAPE_HISCORE_SERVICE; - - private HiscoreResultBuilder lookupUsername(String username) throws IOException + HiscoreResultBuilder lookupUsername(String username, HiscoreEndpoint endpoint) throws IOException { - HttpUrl hiscoreUrl = url.newBuilder() - .addQueryParameter("player", username) - .build(); + return lookupUsername(username, endpoint.getHiscoreURL()); + } - logger.info("Built URL {}", hiscoreUrl); + HiscoreResultBuilder lookupUsername(String username, HttpUrl hiscoreUrl) throws IOException + { + HttpUrl url = hiscoreUrl.newBuilder() + .addQueryParameter("player", username) + .build(); + + logger.info("Built URL {}", url); Request okrequest = new Request.Builder() - .url(hiscoreUrl) - .build(); + .url(url) + .build(); Response okresponse = RuneliteAPI.CLIENT.newCall(okrequest).execute(); + + if (!okresponse.isSuccessful()) + { + switch (HttpStatus.valueOf(okresponse.code())) + { + case NOT_FOUND: + throw new NotFoundException(); + default: + throw new InternalServerErrorException("Error retrieving data from Jagex Hiscores: " + okresponse.message()); + } + } + String responseStr; try (ResponseBody body = okresponse.body()) @@ -85,13 +97,20 @@ public class HiscoreService { if (count++ >= HiscoreSkill.values().length) { + logger.warn("Jagex Hiscore API returned unexpected data"); break; // rest is other things? } // rank, level, experience int rank = Integer.parseInt(record.get(0)); int level = Integer.parseInt(record.get(1)); - long experience = Long.parseLong(record.get(2)); + + // items that are not skills do not have an experience parameter + long experience = -1; + if (record.size() == 3) + { + experience = Long.parseLong(record.get(2)); + } Skill skill = new Skill(rank, level, experience); hiscoreBuilder.setNextSkill(skill); @@ -100,20 +119,20 @@ public class HiscoreService return hiscoreBuilder; } - @RequestMapping - public HiscoreResult lookup(@RequestParam String username) throws IOException + @RequestMapping("/{endpoint}") + public HiscoreResult lookup(@PathVariable HiscoreEndpoint endpoint, @RequestParam String username) throws IOException { - HiscoreResultBuilder result = lookupUsername(username); + HiscoreResultBuilder result = lookupUsername(username, endpoint); return result.build(); } - @RequestMapping("/{skillName}") - public SingleHiscoreSkillResult singleSkillLookup(@PathVariable String skillName, @RequestParam String username) throws IOException + @RequestMapping("/{endpoint}/{skillName}") + public SingleHiscoreSkillResult singleSkillLookup(@PathVariable HiscoreEndpoint endpoint, @PathVariable String skillName, @RequestParam String username) throws IOException { HiscoreSkill skill = HiscoreSkill.valueOf(skillName.toUpperCase()); // RS api only supports looking up all stats - HiscoreResultBuilder result = lookupUsername(username); + HiscoreResultBuilder result = lookupUsername(username, endpoint); // Find the skill to return Skill requested = result.getSkill(skill.ordinal()); @@ -126,13 +145,9 @@ public class HiscoreService return skillResult; } - public HttpUrl getUrl() + @InitBinder + public void initBinder(WebDataBinder binder) { - return url; - } - - public void setUrl(HttpUrl url) - { - this.url = url; + binder.registerCustomEditor(HiscoreEndpoint.class, new HiscoreEndpointEditor()); } } diff --git a/http-service/src/main/java/net/runelite/http/service/util/HiscoreEndpointEditor.java b/http-service/src/main/java/net/runelite/http/service/util/HiscoreEndpointEditor.java new file mode 100644 index 0000000000..816981f99a --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/HiscoreEndpointEditor.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.service.util; + +import net.runelite.http.api.hiscore.HiscoreEndpoint; + +import java.beans.PropertyEditorSupport; + +public class HiscoreEndpointEditor extends PropertyEditorSupport +{ + @Override + public void setAsText(String text) throws IllegalArgumentException + { + setValue(HiscoreEndpoint.valueOf(text.toUpperCase())); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java b/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java new file mode 100644 index 0000000000..785ad00266 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/InstantConverter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.service.util; + +import java.sql.Timestamp; +import java.time.Instant; +import org.sql2o.converters.Converter; +import org.sql2o.converters.ConverterException; + +public class InstantConverter implements Converter +{ + @Override + public Instant convert(Object val) throws ConverterException + { + Timestamp ts = (Timestamp) val; + return ts.toInstant(); + } + + @Override + public Object toDatabaseParam(Instant val) + { + return Timestamp.from(val); + } + +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java b/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java new file mode 100644 index 0000000000..62adc5b6fc --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/exception/InternalServerErrorException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.service.util.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) +public class InternalServerErrorException extends RuntimeException +{ + public InternalServerErrorException(String message) + { + super(message); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java b/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java new file mode 100644 index 0000000000..83e04ceca6 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/util/exception/NotFoundException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.service.util.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found") +public class NotFoundException extends RuntimeException +{ + +} diff --git a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java index 865363ad51..45e5ed35f3 100644 --- a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java +++ b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java @@ -28,6 +28,8 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; import javax.naming.NamingException; + +import net.runelite.http.service.util.InstantConverter; import org.junit.Ignore; import org.junit.Test; import org.springframework.boot.SpringApplication; diff --git a/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java index 2fcf74bcbb..5fc1706dcf 100644 --- a/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java +++ b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreServiceTest.java @@ -25,6 +25,7 @@ package net.runelite.http.service.hiscore; import java.io.IOException; +import net.runelite.http.api.hiscore.HiscoreEndpoint; import net.runelite.http.api.hiscore.HiscoreResult; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -36,38 +37,38 @@ import org.junit.Test; public class HiscoreServiceTest { private static final String RESPONSE = "654683,705,1304518\n" - + "679419,50,107181\n" - + "550667,48,85764\n" - + "861497,50,101366\n" - + "891591,48,87843\n" - + "-1,1,4\n" - + "840255,27,10073\n" - + "1371912,10,1310\n" - + "432193,56,199795\n" - + "495638,56,198304\n" - + "514466,37,27502\n" - + "456981,54,159727\n" - + "459159,49,93010\n" - + "1028855,8,823\n" - + "862906,29,12749\n" - + "795020,31,16097\n" - + "673591,5,495\n" - + "352676,51,112259\n" - + "428419,40,37235\n" - + "461887,43,51971\n" - + "598582,1,10\n" - + "638177,1,0\n" - + "516239,9,1000\n" - + "492790,1,0\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1\n" - + "-1,-1"; + + "679419,50,107181\n" + + "550667,48,85764\n" + + "861497,50,101366\n" + + "891591,48,87843\n" + + "-1,1,4\n" + + "840255,27,10073\n" + + "1371912,10,1310\n" + + "432193,56,199795\n" + + "495638,56,198304\n" + + "514466,37,27502\n" + + "456981,54,159727\n" + + "459159,49,93010\n" + + "1028855,8,823\n" + + "862906,29,12749\n" + + "795020,31,16097\n" + + "673591,5,495\n" + + "352676,51,112259\n" + + "428419,40,37235\n" + + "461887,43,51971\n" + + "598582,1,10\n" + + "638177,1,0\n" + + "516239,9,1000\n" + + "492790,1,0\n" + + "-1,-1\n" + + "-1,-1\n" + + "531,1432\n" + + "-1,-1\n" + + "-1,-1\n" + + "-1,-1\n" + + "-1,-1\n" + + "-1,-1\n" + + "254,92"; private final MockWebServer server = new MockWebServer(); @@ -86,16 +87,17 @@ public class HiscoreServiceTest } @Test - public void testLookup() throws Exception + public void testNormalLookup() throws Exception { - HiscoreService hiscores = new HiscoreService(); - hiscores.setUrl(server.url("/")); + HiscoreTestService hiscores = new HiscoreTestService(server.url("/")); - HiscoreResult result = hiscores.lookup("zezima"); + HiscoreResult result = hiscores.lookup(HiscoreEndpoint.NORMAL, "zezima"); Assert.assertEquals(50, result.getAttack().getLevel()); Assert.assertEquals(159727L, result.getFishing().getExperience()); Assert.assertEquals(492790, result.getConstruction().getRank()); + Assert.assertEquals(1432, result.getClueScrollAll().getLevel()); + Assert.assertEquals(254, result.getClueScrollMaster().getRank()); } } diff --git a/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreTestService.java b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreTestService.java new file mode 100644 index 0000000000..2ef8b2dd56 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/hiscore/HiscoreTestService.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017, 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: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 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.http.service.hiscore; + +import net.runelite.http.api.hiscore.HiscoreEndpoint; +import okhttp3.HttpUrl; + +import java.io.IOException; + +class HiscoreTestService extends HiscoreService +{ + private HttpUrl testUrl; + + HiscoreTestService(HttpUrl testUrl) + { + this.testUrl = testUrl; + } + + @Override + HiscoreResultBuilder lookupUsername(String username, HiscoreEndpoint endpoint) throws IOException + { + return super.lookupUsername(username, testUrl); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java index 8f7b771eca..f76c08afd8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java @@ -25,38 +25,27 @@ package net.runelite.client.plugins.hiscore; import com.google.common.base.Strings; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Insets; + +import java.awt.*; import java.awt.event.MouseEvent; import java.io.IOException; import java.text.NumberFormat; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import javax.imageio.ImageIO; -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextArea; -import javax.swing.UIManager; +import javax.swing.*; import javax.swing.border.Border; import javax.swing.event.MouseInputAdapter; + import net.runelite.api.Experience; import net.runelite.client.RuneLite; import net.runelite.client.ui.IconTextField; import net.runelite.client.ui.PluginPanel; -import net.runelite.http.api.hiscore.HiscoreClient; -import net.runelite.http.api.hiscore.HiscoreResult; -import net.runelite.http.api.hiscore.HiscoreSkill; +import net.runelite.http.api.hiscore.*; + import static net.runelite.http.api.hiscore.HiscoreSkill.*; -import net.runelite.http.api.hiscore.Skill; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,9 +72,10 @@ public class HiscorePanel extends PluginPanel private final IconTextField input; - private final List skillLabels = new LinkedList<>(); + private final List skillLabels = new ArrayList<>(); private final JPanel statsPanel = new JPanel(); + private final ButtonGroup endpointButtonGroup = new ButtonGroup(); private final JTextArea details = new JTextArea(); private final HiscoreClient client = new HiscoreClient(); @@ -134,6 +124,7 @@ public class HiscorePanel extends PluginPanel input = new IconTextField(); input.setIcon(search); + input.setFont(labelFont.deriveFont(Font.BOLD)); input.addActionListener(e -> { ScheduledExecutorService executor = runelite.getExecutor(); @@ -178,6 +169,22 @@ public class HiscorePanel extends PluginPanel gridBag.setConstraints(totalPanel, c); add(totalPanel); + JPanel minigamePanel = new JPanel(); + minigamePanel.setBorder(subPanelBorder); + // These aren't all on one row because when there's a label with four or more digits it causes the details + // panel to change its size for some reason... + minigamePanel.setLayout(new GridLayout(2, 3)); + + minigamePanel.add(makeSkillPanel(CLUE_SCROLL_ALL.getName(), CLUE_SCROLL_ALL)); + minigamePanel.add(makeSkillPanel(LAST_MAN_STANDING.getName(), LAST_MAN_STANDING)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_ROGUE.getName(), BOUNTY_HUNTER_ROGUE)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_HUNTER.getName(), BOUNTY_HUNTER_HUNTER)); + + c.gridx = 0; + c.gridy = 3; + gridBag.setConstraints(minigamePanel, c); + add(minigamePanel); + JPanel detailsPanel = new JPanel(); detailsPanel.setBorder(subPanelBorder); detailsPanel.setLayout(new BorderLayout()); @@ -197,16 +204,57 @@ public class HiscorePanel extends PluginPanel detailsPanel.add(details, BorderLayout.CENTER); c.gridx = 0; - c.gridy = 3; - // Last item has a nonzero weighty so it will expand to fill vertical space - c.weighty = 1; + c.gridy = 4; gridBag.setConstraints(detailsPanel, c); add(detailsPanel); + + JPanel endpointPanel = new JPanel(); + endpointPanel.setBorder(subPanelBorder); + + List endpointButtons = new ArrayList<>(); + + for (HiscoreEndpoint endpoint : HiscoreEndpoint.values()) + { + try + { + Icon icon = new ImageIcon(ImageIO.read(HiscorePanel.class.getResourceAsStream(endpoint.name() + ".png"))); + Icon selected = new ImageIcon(ImageIO.read(HiscorePanel.class.getResourceAsStream(endpoint.name() + "_selected.png"))); + JToggleButton button = new JToggleButton(); + button.setIcon(icon); + button.setSelectedIcon(selected); + button.setPreferredSize(new Dimension(24,24)); + button.setBackground(Color.WHITE); + button.setFocusPainted(false); + button.setActionCommand(endpoint.name()); + button.setToolTipText(endpoint.getName() + " Hiscores"); + button.addActionListener((e -> + { + ScheduledExecutorService executor = runelite.getExecutor(); + executor.execute(this::lookup); + })); + endpointButtons.add(button); + endpointButtonGroup.add(button); + endpointPanel.add(button); + } + catch (IOException ex) + { + logger.warn(null, ex); + } + } + + endpointButtons.get(0).setSelected(true); + + c.gridx = 0; + c.gridy = 5; + // Last item has a nonzero weighty so it will expand to fill vertical space + c.weighty = 1; + gridBag.setConstraints(endpointPanel, c); + add(endpointPanel); } private void changeDetail(String skillName, HiscoreSkill skill) { - if (result == null) + if (result == null || result.getPlayer() == null) { return; } @@ -216,14 +264,6 @@ public class HiscorePanel extends PluginPanel String text; switch (skillName) { - case "Overall": - { - Skill requestedSkill = result.getOverall(); - text = "Total Level" + System.lineSeparator() - + "Rank: " + formatter.format(requestedSkill.getRank()) + System.lineSeparator() - + "Total Experience: " + formatter.format(requestedSkill.getExperience()); - break; - } case "Combat": { double combatLevel = Experience.getCombatLevelPrecise( @@ -235,19 +275,50 @@ public class HiscorePanel extends PluginPanel result.getRanged().getLevel(), result.getPrayer().getLevel() ); - text = "Exact Combat Level: " + formatter.format(combatLevel) + System.lineSeparator() + text = "Skill: Combat" + System.lineSeparator() + + "Exact Combat Level: " + formatter.format(combatLevel) + System.lineSeparator() + "Experience: " + formatter.format(result.getAttack().getExperience() - + result.getStrength().getExperience() + result.getDefence().getExperience() - + result.getHitpoints().getExperience() + result.getMagic().getExperience() - + result.getRanged().getExperience() + result.getPrayer().getExperience()); + + result.getStrength().getExperience() + result.getDefence().getExperience() + + result.getHitpoints().getExperience() + result.getMagic().getExperience() + + result.getRanged().getExperience() + result.getPrayer().getExperience()); + break; + } + case "Clue Scrolls (all)": + { + String rank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : formatter.format(result.getClueScrollAll().getRank()); + text = "Total Clue Scrolls Completed" + System.lineSeparator() + + "Rank: " + rank; + break; + } + case "Bounty Hunter - Rogue": + { + String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : formatter.format(result.getBountyHunterRogue().getRank()); + text = "Bounty Hunter - Rogue Kills" + System.lineSeparator() + + "Rank: " + rank; + break; + } + case "Bounty Hunter - Hunter": + { + String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : formatter.format(result.getBountyHunterHunter().getRank()); + text = "Bounty Hunter - Hunter Kills" + System.lineSeparator() + + "Rank: " + rank; + break; + } + case "Last Man Standing": + { + String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : formatter.format(result.getLastManStanding().getRank()); + text = "Last Man Standing" + System.lineSeparator() + + "Rank: " + rank; break; } default: { Skill requestedSkill = result.getSkill(skill); + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : formatter.format(requestedSkill.getRank()); + String exp = (requestedSkill.getRank() == -1) ? "Unranked" : formatter.format(requestedSkill.getExperience()); text = "Skill: " + skillName + System.lineSeparator() - + "Rank: " + formatter.format(requestedSkill.getRank()) + System.lineSeparator() - + "Experience: " + formatter.format(requestedSkill.getExperience()); + + "Rank: " + rank + System.lineSeparator() + + "Experience: " + exp; break; } } @@ -265,7 +336,7 @@ public class HiscorePanel extends PluginPanel label.putClientProperty(SKILL_NAME, skillName); label.putClientProperty(SKILL, skill); - String skillIcon = "/skill_icons/" + skillName.toLowerCase() + ".png"; + String skillIcon = "skill_icons_small/" + skillName.toLowerCase() + ".png"; logger.debug("Loading skill icon from {}", skillIcon); try @@ -316,11 +387,14 @@ public class HiscorePanel extends PluginPanel try { - result = client.lookup(lookup); + HiscoreEndpoint endpoint = HiscoreEndpoint.valueOf(endpointButtonGroup.getSelection().getActionCommand()); + logger.debug("Hiscore endpoint " + endpoint.name() + " selected"); + + result = client.lookup(lookup, endpoint); } catch (IOException ex) { - logger.warn(null, ex); + logger.warn("Error fetching Hiscore data " + ex.getMessage()); return; } @@ -331,18 +405,29 @@ public class HiscorePanel extends PluginPanel if (skillName.equals("Combat")) { - int combatLevel = Experience.getCombatLevel( - result.getAttack().getLevel(), - result.getStrength().getLevel(), - result.getDefence().getLevel(), - result.getHitpoints().getLevel(), - result.getMagic().getLevel(), - result.getRanged().getLevel(), - result.getPrayer().getLevel() - ); - label.setText(Integer.toString(combatLevel)); + if (result.getPlayer() != null) + { + int combatLevel = Experience.getCombatLevel( + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() + ); + label.setText(Integer.toString(combatLevel)); + } + else + { + label.setText("--"); + } } - else if (skill != null) + else if (result.getSkill(skill) == null) + { + label.setText("--"); + } + else if (result.getSkill(skill) != null && result.getSkill(skill).getRank() != -1) { label.setText(Integer.toString(result.getSkill(skill).getLevel())); } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman.png new file mode 100644 index 0000000000000000000000000000000000000000..fb55cebf064dd8226b237bf50e0a2fbbd7b4950f GIT binary patch literal 465 zcmV;?0WSWDP)Cf!BGN-q&qpmZH3kv~Cq7QMroyNY|d*|C}3u^;GzkNx(WZ_b=K z2L|}35Q$m?Q!3=yy*}q;GU0eUrtkaoeV_MN zXWZGy5x@$7EPzaci~%GcZ!b8T%{ZM-8HOQ)AYc##48xG!n+7!yserNxAOe7t0>isY z92}h(i;a2}QV7IXp|K}P$t^mO5Mlw+8H~^wi~s8Sa7=TOFcZ&}7h@!|y zl5~qs3LvWksJh>70syx5_94WVma4DMcc0&cl|`@R(JeZQuL`}F>>EJ6md9SpOM%%A zQ2(lDh+VGKQuND}8v5-f`t9aoU7yi=!X`EVc&Eo!Gt_27o9~=kb~T%01|v1_FnqPT zq+C?7ySauRU9+98v(%ZU#I}j3pQdV?DOO#6`~LiO{@3vduUksGzlgLX00000NkvXX Hu0mjfK}y7; literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/deadman_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..bca0e8dc5563486a4aff37c1b8c039b2ea631098 GIT binary patch literal 505 zcmVj1UgMDN1M|?dL+KPKSZ`l1Vym z-u&L5uOqO+yns~qbytXGu|?%u%$MZ_oB_bYkqrQ#2Clne4L|}QRaVY~7m!ZbX7Bli zlgWhR@t9#4G7Lk0JY8{jbDeWw5kO&{97fRB>n&%q8K=`J<2Ys%MU0||aU8Sv(vqM8 z084_38i)X3SpviNdmNse=MGyJr?CFUl&W_YhP$`5!G6c5Q?}`pZ6-;=BuTRU_uCzv zvQ1rx97KXtOVc#B$ln+lpHs~X$j9SS1|4+U0DzrJ6+%&5`T;=3djVN@g@9f~zvE}^ zseaX``@Mk7&86S*7ht9YaxZ;eKw7fYTBEs8ey!2Opxefv+s@9_7}Y2A>C4{z%QK@W z^76s;DE|YR-jxd5sZ_HnOc440gs$4>aAZ_Tswi!LyOcqfs-~)={$IK%s%n;+Q6Hkr vR(FMv?U+O{wr1*o)f}_d^e2LUnjZfHqIIp2<v!UCZLl_d)CasTX@nVAn)tEq{6bp8H*@r%@h%O%+^ z@E&BFu_BQpta$~~r>9T~514D1G&x1)U R_veE==;`X`vd$@?2>=^1X8`~J literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..1cbae387af7bc1c09ff73b69b11b0683d1046702 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|wj^(N7a$D;Kb?2i11Zh|kH}&M z25w;xW@MN(M*=AL)YHW=#Nu>v$^m9InLq#K=hrmaDl7M#bvn`TyO4_q1m4e_5pYF9 zcSrl7^n+;}KE^x?q?>pR0uKxDG;1*G+=yAc;{{_(qtX<+=?hu}^(qc?+?-I%>7`M` zp7cZl1Z1Y!ICUDb0fAf2#@0LrW@hHY%kAp}e!OTieCQ?qgJ)q?g7R{6H78BU+1dO4 z{5`!vHAt#HVZL|3p;b2%PbxoJBp-gso$bWBAK(7VJKyMfzJs^P)o@pM%`|&g!xlTu z*#Qv=#hhX57goPMeSd$!w)3BsYa}-DNp$KS`JfsBvX|l2@|<}++S|lHq2cN3=d#Wz Gp$P!07IGs1 literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ironman.png new file mode 100644 index 0000000000000000000000000000000000000000..a00b92b1f864da7029027c53fe2fe158517e2f3b GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|wj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{c-hm%F~s8Z*2#N?niT|Eru*E}dYRY0RJaddYS9>(e2*0;ov-RqatGiM&gS_8I z--zjvXKIqyGNQkax{zN!EEbqi!nI3M4iow z&$z$vq{}}YQxikYA4gk?-B literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..c00f4ec040ae8484df41085d428a99f043fe9e4a GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkESF zS(gHX3L9h?3zkw+d8davGsMjk zkGbDsb8r9VBo!uu2ab$9={wXi8g74b4xXr|Q#dKW$o*u5@w8>x-j52L&d*`m(s60a zk`3NE^BLAidTTeob@g;goZkB@Z82{|XY^isr!_iXXBW@YuDo~efu6*Hzpr-}_%g60 z)JHC!6ngOL@AGea?wpHCeyeKn%(Jy_E!)Pu6GW9YS5DG@GwFm+sea=mMIMG9UguKF g^Hdk7{QF=3OuDzopr0G%n9O#lD@ literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/normal_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..fbee92c847d4b9e847b5d7d71131bf2a198f141d GIT binary patch literal 448 zcmV;x0YCnUP)c)2$lG5m%HbA zpZnc=f(>OmQZBD+mJsXC709WE^}yHjd)|?JgMXLpNLivIOLU~EyvWA+n_RupRtW?q zx}G0W-Q+AVQ56HdtkCODIX(Uj7C=p42~3K+Ws1cJwyGFtn!pPDBZCkZaG8A{;`@>}Z98yEArbb0XoPP(t^BHkPD>*n<-KmPsIJwBVIc*&G`Mdv0?QqvDJarcT%mf!To zrT@I;`)8qB-?%(h+qm-U?iDt%4xe^CKWVtycJrcjmXi#>+*5tDT<5prirZH|9y+>+26SGiu+h39zF^I7SplfLTmFJ0bf_GRy; zd0rPz-bq`_TwWNVn#sV)QSzVTif(gBM6plmUE$TNYjlJCc2(&A?)tI+IDh??Zn4+} RANfI{;pyt|WsfM@^! literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/seasonal_deadman_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/seasonal_deadman_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6c4820e13aaff5c66b19cc2cb9cffce4e64f2a GIT binary patch literal 373 zcmV-*0gC>KP)eY(j{zyzeLNuFC0*{AEdCFyDs+bI#z#sj;wM4?&7LlgHrt8$%93(@de7x>FG zt*(v>wBYCHRh~kjA$si6BG$(l48B4@D>N-%KuZ^f{0|uDSY`0l)N+*1cah5L;?l-J z>*4BHXwvQy`Js?b7+%r03L;0x*iIkZb5i3lZu#noXh02yf$Od`2vlRMnwHtlL92L} zzo28!%m4v^<&Wx?GC&V=%-MMNM1zby^Vl;3&=bX_=J7Ah5yg@u@Apu8mpcCq)xY1m T?OK)g00000NkvXXu0mjfXJ(!R literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/agility.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/agility.png new file mode 100644 index 0000000000000000000000000000000000000000..19c26d9022e735cf728a217473960dfad46c07ab GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|t_%#v!o013JhwA9*?<&d zNswPKgTu2MX&_FLx4R2N2dk_HNO^%rWHAE+w=f7ZGR&GI0TlG}ba4#fxXycOBj*7H z373cNUf(xdsGXAVzxkwy-ohnY1dnJIpJhL8+rj(cQIvFy*6azN+TWK6zgcNv9sE9I q`>GeNA5ZX34_6j>7=N~c&;IWtkv(40dzS%CX7F_Nb6Mw<&;$S~A3y&9 literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/attack.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/attack.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9aaa87f655a5fbae8b023eaa946b20099c60f0 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFv4DbnY1=4Od`nz{BSL7u|1vm$| zS*Aq!HdSPoS96w^mjh+hPql*dFqQ=Q1v5B2yO9RsBze2LFm$lWdH^|`1s;*b3=G`D zAk4@xYmNj^(8troF@)oKasoqh=xPO%>!%_E8fR`^z+@bLkVVXaMQb6`K0gajo zjO#+Rl@Bt-$Vvr>YEMzn+QDKi<}ih8U4wzx+9+1h#sD6M!qal46Zd^61e(j>>FVdQ I&MBb@0PW*DzW@LL literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - hunter.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - hunter.png new file mode 100644 index 0000000000000000000000000000000000000000..7ac7a92576cd9b72343734083ef8d3b3d918c254 GIT binary patch literal 1421 zcmbu9%}W$v9LK-U%Wmo|o_E`Mu3vJ5s&h z>nZdA@K#k;9HlW|bWNpa{pt{~o3!G-P}$fD=#2B8%&#ev+f?}8`S z#@2HNva)O-mqJOwGouV@0f#eZgTkuF1Co#n50`hkeuFjGq4jhox;0vK9SjXbQ7yO_e!PqMe>x)dRA$N1PBoPZs zFv*gy6K=eZX{K%k? zq_vbfG3BfQ!(z;#@;W~u`pU*5qnc_?!)Q?tS#@q^<5%!E2{g3-6M0uzu&C;h8) zdMEJD`*V>FM@yQNYw=|n@7gq6dNPiS$^lal($pef@=2-^5)zSqVo!PJQc{;H!9ME1drQ-J}o0o1)J@)l)%HHq@S`Z zEFp%I0r4Uhq$H@|ED5y_>g3?SW@Hy~oOx4&^B)dl!}g^7@YN<9ANE06OJbo&LB#me n|54hqOe@ou{AuqukXZQF*3#bWFB!X9CiGSNsw;Z-p1SfKfSP(! literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - rogue.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/bounty hunter - rogue.png new file mode 100644 index 0000000000000000000000000000000000000000..0b74dc96d8f596163209939546c9d2d2e2c80d77 GIT binary patch literal 1421 zcmbtU%S%*I9RALIjN?o*jF0eb4@d~Ia-|f~LrW!)h^X8}M2j{F{s-+^6k=4gYZ2ro zchMrWg@pu`Bta!erui5(&G*dQd*}51Mz5%}i24oZo^yVW?|kQXzdLu%H#bB=#UTKZ zrpEdUoO5La1AJckaPKmwz_oJ?_0aNr(*5i{J=v{|Ep0%wSBC1l)p;GTE1T+1U98Db z`|aVB(pod`|IOM7p_KY7WD-zYYd3*>fUmU}Fb$*=F3eOKM$m$1Z7JbWyBbViKhOuv z0EtroY!hDIZcH%6&}s16FjtombsR_oGe){fG4|y+K58B)wE||z;`2I)o0o^U!Jwhw zNtNfpR3@CX3yY06#g`&eHw`mQRwjL)2q+ka1WoegPz#HQxejc($>uQNDS}I$n1IO4 zLSYO?4xwcpFGo-oGNF$ZV?rvTVJuoUTunxn370PIv0rOE#NoW^!CG8H*0&{u6WnhC z)sNuFgdk|Xe7iP@U%i5^j=h*Bh_KcVxE?-%et$3PhkG#N?K;W&7O7VkBA6G(N|H%@ zQf-5#zzX2N9 BVmJT* literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/clue scrolls (all).png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/clue scrolls (all).png new file mode 100644 index 0000000000000000000000000000000000000000..9a7120c91b4d86959acb1de82572691eec000be0 GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+3?vf;>QaG}Uw}`DE06|)DV4r$rD08lLG@Wq z-4zjiWu6NgoaWUzwimcnW%+iOc$6pE&ZzUBSnj>L!)-~Eb5WettcHNv+<;{*F3q_v z{ngQn+Cnz=dK_I^@fm0wV@Z%-FoVOh8)-mJr>Bc!h{WaC^O1bb1_I0hHjyWs%%U=? z|96*75_CE;|NY@j(*+q$8&{5^EV2VV~#b2^}7sr(-T3ysC n%JdQ5sQ5PZ*GUnzUHbnSl_FR+2(P;m2J*3|tDnm{r-UW|hOlq> literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/combat.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/combat.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e17fb28d076029db6b9713665a223fe83932de GIT binary patch literal 465 zcmV;?0WSWDP)5Iy^gqHS~vZ5vq#r(qny0gMHai7>SK;txU)0&_J(Vm>l+5q2^i->qqf_4d`-VMxPuX z>RKg=QJ4;xra$(X-~YJSqjh5xi)mf~ou3*LQ5Zepkb}InI9!~gJG%#nLIBXk!Wzym z;WnE39}_+?p4Y4E+lVG3QTnWWhyCzSZ?u^2(j4b#5>nXDQOc#F<=JircjrwxD9?37 z>9yq#V)v)w%0c;SOR!W&xUnD0#mNfv>9ZZ9FyrzRw*hKD=dB$M`Lhkl00000NkvXX Hu0mjfBpBBE literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/construction.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/construction.png new file mode 100644 index 0000000000000000000000000000000000000000..3c8e176a3ed491cd69b4a13d73e76ffa31269a66 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^4e$wZ{r~?zkU4YaOdyq(mUiOA ziJv_-ucsHu07V%~g8YIR9G=}s196hP-CYEal|aXmTV0KWt)E2|`nqk^vPL`M@76OW@20%ro6jG7#HoE4lsJ&)cLIKdGV6vSjK zGIdJJoL*kZ7M2zf$=DbfNft>7)&@4N2OIer+C+JrdUpmT0gYzxboFyt=akR{03%#F AHvj+t literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/cooking.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/cooking.png new file mode 100644 index 0000000000000000000000000000000000000000..2030f1d25bea66f72b100557d8073322444c4592 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^3h)VWP0r8x{{8N<9)Z{Z!RmBQ zps-X}bs3OiED7=pW^j0RBMrn!@^*J&=wOxg0CG4BJR*x37`TN&n2}-D90{Ocn5TjR${J7F`2FYG@IS`Ex9%oKE3+n$J= z`E$YT2-}&htEbLp+EjFQbEt)MxJ%Efpz|;6)5_pI;wmpM2a4TF zw4Vl~7)yfuf*Bm1-ADs*lDyqr7&=&GJ%Aj}0*}aI1_o|n5N2eUHAey{80qQa7{YPg zwf7+J0Rs-^zX^>Va}`dpon$bn_&+o2vnGFuSpq}ohG-=}E;hm2uWF65vQIc(v)fP_ z&Y2Q>=|VK$1F4&t_!@hljQC0!qCAg>jC6&7I;J!Gca%qgD@k*tT_@uL2FMJ#}JO| z$q5b2;sy*xGOWG5Q$rkh5-koNkrZcM!a21udLwrSgTe~DWM4fF@ZFy literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/farming.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/farming.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9becd5f51ffba87569075aa7d6b4ef5adb3f1c GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}QGic~E08WskE+Q}D$9y%tITSv z%&93zDoBfLC{1rDNe@Zfo>X`_I_p?*W=utHd|_HtV`+MKZGKr+d__)tOxE%4y8ME) zsF!asJM zJe(=f9HDG*nagDQ#)_p=qo4P`3156d^g_vsO!Gq5n7@oH+@jJBhZofVoyOql>gTe~ HDWM4fvQdc! literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/firemaking.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/firemaking.png new file mode 100644 index 0000000000000000000000000000000000000000..a9297c8edcc81b99e73e8cbd0ec4635f8ad93631 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^3h)VWP0r8RY{`7rpK;v^)(9&e zpzz^}^1DEau_VYZn8D%MjWiG^$=lt9p@UV{1IXbl@Q5sCVBi)8VMc~ob0mO*S)MMA zAsp9D`wsFt8*(`BG;sK&o)O6Au_%Y(`wQXs`@QbQPi_9{Fs-+PW5dctCpc@qPi;FI zz3n&e?M0c3{hD9wsG3>Y`R$k1x8^uE#guNbFC2aH=_(FfYI5b1*cHtj8ZIwazp-;Z c`xATl0|65L|5?hFfVMJty85}Sb4q9e0NkEW8vpEal| zab30NAnySM9_GIp><=!oHbpc{sbAVwuUYXwRBDC%Hqp7v2Rxd)bz7R5GuS1aK5=i{ ztoFx9FU#Wn&eFG4RX0PotWnB8Cwx!Bl}ju8%%lDJOTDxlLVdsWd8RC~FBZ|ivDNMJ aN5)M|B3w0_7+(TyW$<+Mb6Mw<&;$S|g-ied literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/fletching.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/fletching.png new file mode 100644 index 0000000000000000000000000000000000000000..56b50c406751596f865f75a4d55efd3bcf827c2c GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHV5AX?bO)b%F(c@65=Cs%228vbf zWYz&vj3q&S!3+-1Zlr-YN#5=*3>~bp9zYIffk$L90|U1(2s1Lwnj--ebn|p^4B@z5 z+q+TlfC3M5tU>2t78z!N&%e*NBnP@LfApUFq|Rl7);h&sp@uHG)~r4=R8LHcxLNPW mS>5Tb{3Bvnwzx`d0mJ_g{=5yJPEG)t$l&Sf=d#Wzp$PzSi8%)V literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/herblore.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/herblore.png new file mode 100644 index 0000000000000000000000000000000000000000..fde27351c406beb4c5357c1762ea2575bd2c3f47 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^4e$wZ%`eJjc+bF4&A@P#fng;B zLmE(mK{)8H4Ul3i3GxeOaCmkDB*>WL?e4vL>4nJa0`PlBg3pY5YR7 zVY6fp<1)rBi`j-74oYSo^K(3x=I}R1)6Y>aLHTln0%Lb8L$8$R`HWZRlz|p7c)I$z JtaD0e0s#F0K$8Ff literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hitpoints.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hitpoints.png new file mode 100644 index 0000000000000000000000000000000000000000..bf431f7518a0b82655a38783bbbafd8e6d47bef2 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^3h)VWP0r8RY{^_@%gMmN2o$^P z&nT}P@Ce9aED7=pW^j0RBMrn!@^*J&=wOxg0CG4BJR*x37`TN&n2}-D90{Ocq^FBx z2*>s0ga$^j6{1?Pa1 zGa3&Dh#WCUNGLE9Vd1i1&}B5_aoA=l?Z_e_;Si?8z_2Stw25s-N*2%(22WQ%mvv4F FO#s-PGmQWM literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hunter.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/hunter.png new file mode 100644 index 0000000000000000000000000000000000000000..88be793bbb425a8b56d02f912e891f6e54c73665 GIT binary patch literal 277 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 literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/magic.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/magic.png new file mode 100644 index 0000000000000000000000000000000000000000..5e9974c7bd9845f5a074800ea727717877f22976 GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^4e$wZ%`eJbw}Q3GmXm?)9|HrU zG}|Pg%vo1mMj*vl666=m;PC858ivL>4nJa0`PlBg3pY5 literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/mining.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/mining.png new file mode 100644 index 0000000000000000000000000000000000000000..57430882d0ecc3f2dde4c7e188e2098cbc2d7dcc GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-uBp1|ZZ`U*)ipo?PlIcs zK#H*>$S;_|;n|He5GTpo-G!lpRn`N@;VkfoEM{Qf76xHPhFNnYfPx{OE{-7_*LhEF z0DLY40fK2)lo~lxbE}1kZ+5OcvbC9E9JgVe8RR0Xg-6dtDnm{r-UW|g#|!p literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/overall.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/overall.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2dd265a4970c6dd20f2310d2a01face8528074 GIT binary patch literal 291 zcmV+;0o?wHP)8)*1)|sakAQ)o06ohsUc_xqFow-Hs=VUEoX-s`UZWqi${3Dvr}KO!i4|g zoxeMdLm`AA1>>CV`yKY7JARO^`j)~78P{KRfB?TNX8`r^AcyH`!*#IhO`G8u1C$Ri8>d}T+s#_qekUCxh+^Ym*Zw_@>sW;2 p%-exutS;_YuFU*t4f&mfcmb@^ed{f`g=hc(002ovPDHLkV1m;;ef$6b literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/prayer.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/prayer.png new file mode 100644 index 0000000000000000000000000000000000000000..7c348987ebd24249442ad5a685ef1b90647d654a GIT binary patch literal 321 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}RDe&2D?>SmDJ;x8b;G=An-d+33$HF+`*=e}YdJ5_XvUHtzhDN3XE)M7oFs2| z7lsa2Sq~tGv%n*=n1O*?7=#%aX3dcR3Ko01IEF}Ej_r@;Yc}9v*=*z6uw+V8?1At5 z)s}d;p8Hd&a(nl=zYGs}1hfNAXYgo!G$_`r{ci3MYJU6At9OSpb!I!KzT4~1b-z0{ zv~sTplj4!o%cV+-s#j_}N`IJ{F~6W=;!4wc=E=O@o++L_SGjY~+ryk2Ke62IWvROc Pw4TA!)z4*}Q$iB}C~@@x5%^$J=BKE11ej$rU~^>bP0l+XkKgc4O= literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/runecraft.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/runecraft.png new file mode 100644 index 0000000000000000000000000000000000000000..2c52ffe9139bc3f027cfa47eb4105e01de262e22 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPGa2=EDUO)Oy8V9C;L&N*SigvhAS z^73*921cOdat7<=K#H*>$S;_|;n|He5GTpo-G!lpRn`N@;VkfoEM{Qf76xHPhFNnY zfP(p+E{-7_*L{1f`C1HkT$U@Dp3UnI+sW{VO)REiVVqszt&o#n3q?2|rp-%Jv1cd{ ze0$2=^v3Uc-taHN-Hvis{iB<#c~5lYEz#Pk=r7~cIypps$CWns36gWK{xd4LUiHW2 n%p}*nXI_5&SvYg=jRNK^cVsPC@1AY}TFv0;>gTe~DWM4f0(nxQ literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/slayer.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/slayer.png new file mode 100644 index 0000000000000000000000000000000000000000..f02563044c4ed3021994f92795824bcc79613695 GIT binary patch literal 546 zcmV+-0^R+IP)Clmt#r zPI`KJxV8l{EGs%QD@{xm{{H?&Jvi&)-R0cYTU%So%F5Qnx$NZNKsPI~n2FBL&d|TJ zprD{bLqkbONWiI^^z`(dn3k=UiPO=~(96h!f(^m9v6_Z@ii8$FKR}+GBK7aLku zHZh}*gU!nVr=|kN$H)5m`RwZG@9E{@)y_&lI!{jyUs6dyH8xB?H@lyWR!l}wQBhi0 zP%SM0ARiq+I3M29%G=DthlT-)i2(or02m1~r2qf`0d!JMQvg8b*k%9#00Cl4M??UK z1szBL000SaNLh0L01FcU01FcV0GgZ_0002NNklE)pQA2j@_swdocGLOm@x1Q^9>YUJCd)&BB17WhbVy2%?)nf3N4e%Xr_Ju zK$c~q@k(N@0-g74PpW5;7rQeg>o%OM?7@862M7NCR<_yxm;OkH}&M25w;xW@MN(M*=9A?&;zf z!f{=<=b&JN0uRgQjSU@Q4ojGv82Jv#D}KA*#aXp}?%bWNMTfQrE}fD!LGRfW9=>NQ zvev{UX4~)JYPLx_BKt03!G1ZTzzgB)ol;~iTQ{67Q#WDu{&wz^g4|5sRNlg%if0d4 YxqgURyuFgE1GJLC)78&qol`;+02AO!y8r+H literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/strength.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/strength.png new file mode 100644 index 0000000000000000000000000000000000000000..976c550712da6174c7ef10653c9c7d911790babf GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^3h)VWP0r78v(aZ@U|cw<>cXZL zps>W7(z!s2u_VYZn8D%MjWiG^$=lt9p@UV{1IXbl@Q5sCVBi)8VMc~ob0mO*NuDl_ zAsp9r&s^j@puppLQNg0&EYs0f4C@OUv)DA6eywUK6hqnKH9L&ur|qwfNh}tw6-nV| UJ}bOy4bV0QPgg&ebxsLQ0Pbr`YybcN literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/thieving.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/thieving.png new file mode 100644 index 0000000000000000000000000000000000000000..4aca64fdb09668655b409c219a3b66b893760a65 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|uK)l42QqeIEHXsPfj?%=Hx6X z5#j92EaBqp>@0C4EX_AjAX>_l^$@GK!3MU3tDFoG7dX0XZJK`r^)Yz5`njxgN@xNA DP^BvE literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/woodcutting.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/skill_icons_small/woodcutting.png new file mode 100644 index 0000000000000000000000000000000000000000..379d09b76f6365a3cd46be0520ee463fea31a610 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^4e$wZ%`eKWvgKr0$q-@1!Eal|ab34}BkutP9+v%zwOnEjDGVuU3~UZYOO`eMb?@M<6u0|(I-g z(s_`j$;mro(w+;AkAg)tET^py*z9xks-^oo=XWzZUh64s$~q8Wq3^S!TP% boelgsnW8%nRkrv7tz__Y^>bP0l+XkK$j3-^ literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman.png new file mode 100644 index 0000000000000000000000000000000000000000..efa7643d971e6026e959c2c47b2f9fd489fd9eb0 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|wj^(N7a$D;Kb?2i11Zh|kH}&M z25w;xW@MN(M}mQYk=N73F~s8Z-pPh}%?1*!_TguI&Iayq+-0h4T-|qoaR>YJ#FQr- zFB^22%XA*&%Qi{n^G(x22`cZ2{}1<%iGs zHB7(!VkuYMEYIv?g*A6`=KFAbY&d_~_0OwLo8Z{%dh{LDhK8=w@!n{*J5x%0 zYq`22|Ig^n+x08`>t^kH&$wdMDOb6l*{z(pmo>YbKFC)wykY)W$szf<3lwUeu6{1- HoD!M<7HgCy literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/ultimate_ironman_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..d171215c16a221bdf0b2928eaa1034268572a118 GIT binary patch literal 433 zcmV;i0Z#sjP)ox@+TPLO9B7Ygp+pV-k{f!L z_sQ@5S3!w}zA^kj{jLxI)w1ATd%;p``zFJXBuP+8p_Bq(99`P?stb9zT5+V579ztC zU;|QA)rCkY0dkNeNdR(yZLkV^l1;>9jA;?uo_&>Yc}R z*E;pL{Pd=7;`A=-PzL$`$#ypxfLeMQ>#NL75Cmz^U|sB?Z%me!+o582=BIZ%9f`mE b#J1@hV%ht|^SO#p00000NkvXXu0mjf!eqQR literal 0 HcmV?d00001