From d0f708e26a3329953180669fc368d4e1dc35b471 Mon Sep 17 00:00:00 2001 From: Ruben Amendoeira Date: Sun, 22 Apr 2018 04:39:05 +0100 Subject: [PATCH] Hiscore Plugin redesign Overall: - Applied new colors, positions and sizes, following issue #1342. Search: - Applied the new IconTextField, with search, error and loading indicator (and respective image files). - Blocked tabs witching while results are loading. Endpoints: - Moved the endpoints to right below the search bar (this follows a more logical sequence of usage). - Changed the endpoint presentation style and size. The selected endpoint now displays a orange underline. - Edited the endpoint icons to better fit the visual context. - Changed the way currently selected endpoint is stored. Stats: - Changed the sizing of the labels/panels. - Changed the font to a smaller version. Total/Combat: - Switched the order of the combat and total indicator - Changed the font to a smaller version. Clues/Minigames: - Changed the font to a smaller version. Details Panel: - Completely removed the details panel, instead went for a more in-line with the game approach, tooltips! - Rewrote the way skills and labels are matched - Added html progress bar to the next level --- .../client/plugins/hiscore/HiscorePanel.java | 687 +++++++++--------- .../client/ui/components/IconTextField.java | 11 + .../net/runelite/client/util/SwingUtil.java | 1 + .../plugins/hiscore/hardcore_ironman.png | Bin 280 -> 15224 bytes .../client/plugins/hiscore/ironman.png | Bin 307 -> 15218 bytes .../client/plugins/hiscore/normal.png | Bin 391 -> 16678 bytes .../client/plugins/hiscore/search.png | Bin 388 -> 0 bytes .../plugins/hiscore/ultimate_ironman.png | Bin 365 -> 14668 bytes 8 files changed, 374 insertions(+), 325 deletions(-) delete mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/search.png 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 3bf64d811c..95ef5b8b41 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 @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Adam + * Copyright (c) 2018, Psikoi * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,10 +26,8 @@ package net.runelite.client.plugins.hiscore; import com.google.common.base.Strings; -import java.awt.BorderLayout; -import java.awt.Color; +import com.google.common.collect.ImmutableList; import java.awt.Dimension; -import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; @@ -39,29 +38,22 @@ import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; import javax.imageio.ImageIO; import javax.inject.Inject; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JTextArea; -import javax.swing.JToggleButton; -import javax.swing.UIManager; -import javax.swing.border.Border; -import javax.swing.event.MouseInputAdapter; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Experience; import net.runelite.api.Player; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.components.IconTextField; import net.runelite.client.util.StackFormatter; @@ -102,13 +94,17 @@ import net.runelite.http.api.hiscore.Skill; @Slf4j public class HiscorePanel extends PluginPanel { - private static final String SKILL_NAME = "SKILL_NAME"; - private static final String SKILL = "SKILL"; + /* The maximum allowed username length in runescape accounts */ + private static final int MAX_USERNAME_LENGTH = 12; + + private static final ImageIcon SEARCH_ICON; + private static final ImageIcon LOADING_ICON; + private static final ImageIcon ERROR_ICON; /** * Real skills, ordered in the way they should be displayed in the panel. */ - private static final Set SKILLS = new LinkedHashSet<>(Arrays.asList( + private static final List SKILLS = ImmutableList.of( ATTACK, HITPOINTS, MINING, STRENGTH, AGILITY, SMITHING, DEFENCE, HERBLORE, FISHING, @@ -117,7 +113,7 @@ public class HiscorePanel extends PluginPanel MAGIC, FLETCHING, WOODCUTTING, RUNECRAFT, SLAYER, FARMING, CONSTRUCTION, HUNTER - )); + ); @Inject ScheduledExecutorService executor; @@ -132,24 +128,45 @@ public class HiscorePanel extends PluginPanel 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 JProgressBar progressBar; - private List endpointButtons; + /* A list of all the selectable endpoints (ironman, deadman, etc) */ + private final List endPoints = new ArrayList<>(); private final HiscoreClient hiscoreClient = new HiscoreClient(); + private HiscoreResult result; + /* The currently selected endpoint */ + private HiscoreEndpoint selectedEndPoint; + + /* Used to prevent users from switching endpoint tabs while the results are loading */ + private boolean loading = false; + + static + { + try + { + synchronized (ImageIO.class) + { + SEARCH_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("search.png"))); + LOADING_ICON = new ImageIcon(IconTextField.class.getResource("loading_spinner_darker.gif")); + ERROR_ICON = new ImageIcon(ImageIO.read(IconTextField.class.getResourceAsStream("error.png"))); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + @Inject public HiscorePanel(HiscoreConfig config) { super(); this.config = config; - // Panel "constants" - // This was an EtchedBorder, but the style would change when the window was maximized. - Border subPanelBorder = BorderFactory.createLineBorder(this.getBackground().brighter(), 2); + setBorder(new EmptyBorder(10, 10, 0, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); // Create GBL to arrange sub items GridBagLayout gridBag = new GridBagLayout(); @@ -160,28 +177,11 @@ public class HiscorePanel extends PluginPanel c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.NORTH; - // Search box - JPanel inputPanel = new JPanel(); - inputPanel.setLayout(new BorderLayout(7, 7)); - inputPanel.setBorder(subPanelBorder); - - ImageIcon search; - try - { - BufferedImage icon; - synchronized (ImageIO.class) - { - icon = ImageIO.read(HiscorePanel.class.getResourceAsStream("search.png")); - } - search = new ImageIcon(icon); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - input = new IconTextField(); - input.setIcon(search); + input.setPreferredSize(new Dimension(100, 30)); + input.setBackground(ColorScheme.DARKER_GRAY_COLOR); + input.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); + input.setIcon(SEARCH_ICON); input.addActionListener(e -> executor.execute(this::lookup)); input.addMouseListener(new MouseAdapter() { @@ -192,7 +192,6 @@ public class HiscorePanel extends PluginPanel { return; } - if (client == null) { return; @@ -206,97 +205,20 @@ public class HiscorePanel extends PluginPanel } } }); - inputPanel.add(input, BorderLayout.CENTER); c.gridx = 0; c.gridy = 0; c.weightx = 1; c.weighty = 0; - c.insets = new Insets(0, 0, 3, 0); - gridBag.setConstraints(inputPanel, c); - add(inputPanel); - - // Panel that holds skill icons - GridLayout stats = new GridLayout(8, 3); - statsPanel.setLayout(stats); - statsPanel.setBorder(subPanelBorder); - - // For each skill on the ingame skill panel, create a Label and add it to the UI - for (HiscoreSkill skill : SKILLS) - { - JPanel panel = makeSkillPanel(skill.getName(), skill); - statsPanel.add(panel); - } - - c.gridx = 0; - c.gridy = 1; - gridBag.setConstraints(statsPanel, c); - add(statsPanel); - - JPanel totalPanel = new JPanel(); - totalPanel.setBorder(subPanelBorder); - totalPanel.setLayout(new GridLayout(1, 2)); - - totalPanel.add(makeSkillPanel(OVERALL.getName(), OVERALL)); - totalPanel.add(makeSkillPanel("Combat", null)); - - c.gridx = 0; - c.gridy = 2; - 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()); - - // Rather than using one JLabel for each line, make a JTextArea look and act like a JLabel - details.setEditable(false); - details.setCursor(null); - details.setOpaque(false); - details.setFocusable(false); - details.setWrapStyleWord(true); - details.setLineWrap(true); - details.setMargin(new Insets(2, 4, 4, 4)); - details.setRows(6); - details.setText(""); - - detailsPanel.add(details, BorderLayout.CENTER); - - progressBar = new JProgressBar(); - progressBar.setStringPainted(true); - progressBar.setValue(0); - progressBar.setMinimum(0); - progressBar.setMaximum(100); - progressBar.setBackground(Color.RED); - progressBar.setVisible(false); - - detailsPanel.add(progressBar, BorderLayout.SOUTH); - - c.gridx = 0; - c.gridy = 4; - gridBag.setConstraints(detailsPanel, c); - add(detailsPanel); + c.insets = new Insets(0, 0, 10, 0); + gridBag.setConstraints(input, c); + add(input); + /* The container for all the endpoint selectors */ JPanel endpointPanel = new JPanel(); - endpointPanel.setBorder(subPanelBorder); + endpointPanel.setOpaque(false); + endpointPanel.setLayout(new GridLayout(1, 5, 7, 1)); - endpointButtons = new ArrayList<>(); for (HiscoreEndpoint endpoint : HiscoreEndpoint.values()) { try @@ -307,25 +229,32 @@ public class HiscorePanel extends PluginPanel iconImage = ImageIO.read(HiscorePanel.class.getResourceAsStream( endpoint.name().toLowerCase() + ".png")); } - JToggleButton button = new JToggleButton(); - button.setIcon(new ImageIcon(iconImage)); - 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 -> executor.execute(this::lookup))); - button.addMouseListener(new MouseAdapter() + + JPanel panel = new JPanel(); + JLabel label = new JLabel(); + + label.setIcon(new ImageIcon(iconImage)); + + panel.add(label); + panel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + panel.setToolTipText(endpoint.getName() + " Hiscores"); + panel.addMouseListener(new MouseAdapter() { @Override - public void mouseReleased(MouseEvent e) + public void mouseClicked(MouseEvent e) { + if (loading) + { + return; + } + executor.execute(HiscorePanel.this::lookup); + selectedEndPoint = endpoint; updateButtons(); } }); - endpointButtons.add(button); - endpointButtonGroup.add(button); - endpointPanel.add(button); + + endPoints.add(panel); + endpointPanel.add(panel); } catch (IOException ex) { @@ -333,156 +262,61 @@ public class HiscorePanel extends PluginPanel } } - endpointButtons.get(0).setSelected(true); - endpointButtons.get(0).setBackground(Color.CYAN); + /* Default endpoint is the general (normal) endpoint */ + selectedEndPoint = HiscoreEndpoint.NORMAL; + updateButtons(); c.gridx = 0; - c.gridy = 5; - // Last item has a nonzero weighty so it will expand to fill vertical space - c.weighty = 1; + c.gridy = 1; gridBag.setConstraints(endpointPanel, c); add(endpointPanel); - } - void addInputKeyListener(KeyListener l) - { - this.input.addKeyListener(l); - } + // Panel that holds skill icons + GridLayout stats = new GridLayout(8, 3); + statsPanel.setLayout(stats); + statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + statsPanel.setBorder(new EmptyBorder(5, 0, 5, 0)); - void removeInputKeyListener(KeyListener l) - { - this.input.removeKeyListener(l); - } - - private void changeDetail(String skillName, HiscoreSkill skill) - { - if (result == null || result.getPlayer() == null) + // For each skill on the ingame skill panel, create a Label and add it to the UI + for (HiscoreSkill skill : SKILLS) { - return; + JPanel panel = makeSkillPanel(skill); + panel.setOpaque(false); + statsPanel.add(panel); } - String text; - int progress = -1; - switch (skillName) - { - case "Combat": - { - double combatLevel = Experience.getCombatLevelPrecise( - result.getAttack().getLevel(), - result.getStrength().getLevel(), - result.getDefence().getLevel(), - result.getHitpoints().getLevel(), - result.getMagic().getLevel(), - result.getRanged().getLevel(), - result.getPrayer().getLevel() - ); - text = "Skill: Combat" + System.lineSeparator() - + "Exact Combat Level: " + StackFormatter.formatNumber(combatLevel) + System.lineSeparator() - + "Experience: " + StackFormatter.formatNumber(result.getAttack().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 allRank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); - String easyRank = (result.getClueScrollEasy().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollEasy().getRank()); - String mediumRank = (result.getClueScrollMedium().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMedium().getRank()); - String hardRank = (result.getClueScrollHard().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollHard().getRank()); - String eliteRank = (result.getClueScrollElite().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollElite().getRank()); - String masterRank = (result.getClueScrollMaster().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMaster().getRank()); - String all = (result.getClueScrollAll().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollAll().getLevel())); - String easy = (result.getClueScrollEasy().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollEasy().getLevel())); - String medium = (result.getClueScrollMedium().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMedium().getLevel())); - String hard = (result.getClueScrollHard().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollHard().getLevel())); - String elite = (result.getClueScrollElite().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollElite().getLevel())); - String master = (result.getClueScrollMaster().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMaster().getLevel())); - text = "All clues: " + all + " | Rank: " + allRank + System.lineSeparator() - + "Easy: " + easy + " | Rank: " + easyRank + System.lineSeparator() - + "Medium: " + medium + " | Rank: " + mediumRank + System.lineSeparator() - + "Hard: " + hard + " | Rank: " + hardRank + System.lineSeparator() - + "Elite: " + elite + " | Rank: " + eliteRank + System.lineSeparator() - + "Master: " + master + " | Rank: " + masterRank; - break; - } - case "Bounty Hunter - Rogue": - { - String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterRogue().getRank()); - text = "Bounty Hunter - Rogue Kills" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Bounty Hunter - Hunter": - { - String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterHunter().getRank()); - text = "Bounty Hunter - Hunter Kills" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Last Man Standing": - { - String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getLastManStanding().getRank()); - text = "Last Man Standing" + System.lineSeparator() - + "Rank: " + rank; - break; - } - case "Overall": - { - Skill requestedSkill = result.getSkill(skill); - String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); - String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); - text = "Skill: " + skillName + System.lineSeparator() - + "Rank: " + rank + System.lineSeparator() - + "Experience: " + exp; - break; - } - default: - { - Skill requestedSkill = result.getSkill(skill); - String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); - String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); - String remainingXp; - if (requestedSkill.getRank() == -1) - { - remainingXp = "Unranked"; - } - else - { - int currentLevel = Experience.getLevelForXp((int) requestedSkill.getExperience()); - int currentXp = (int) requestedSkill.getExperience(); - int xpForCurrentLevel = Experience.getXpForLevel(currentLevel); - int xpForNextLevel = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1; + c.gridx = 0; + c.gridy = 2; + gridBag.setConstraints(statsPanel, c); + add(statsPanel); - remainingXp = xpForNextLevel != -1 ? StackFormatter.formatNumber(xpForNextLevel - currentXp) : "0"; + JPanel totalPanel = new JPanel(); + totalPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + totalPanel.setLayout(new GridLayout(1, 2)); - double xpGained = currentXp - xpForCurrentLevel; - double xpGoal = xpForNextLevel != -1 ? xpForNextLevel - xpForCurrentLevel : 100; - progress = (int) ((xpGained / xpGoal) * 100f); + totalPanel.add(makeSkillPanel(null)); //combat has no hiscore skill, refered to as null + totalPanel.add(makeSkillPanel(OVERALL)); - } - text = "Skill: " + skillName + System.lineSeparator() - + "Rank: " + rank + System.lineSeparator() - + "Experience: " + exp + System.lineSeparator() - + "Remaining XP: " + remainingXp; - break; - } - } + c.gridx = 0; + c.gridy = 3; + gridBag.setConstraints(totalPanel, c); + add(totalPanel); - details.setFont(UIManager.getFont("Label.font")); - details.setText(text); + JPanel minigamePanel = new JPanel(); + // 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.setBackground(ColorScheme.DARKER_GRAY_COLOR); - if (progress >= 0) - { - progressBar.setVisible(true); - progressBar.setValue(progress); - progressBar.setBackground(Color.getHSBColor((progress / 100.f) * (120.f / 360.f), 1, 1)); - } - else - { - progressBar.setVisible(false); - } + minigamePanel.add(makeSkillPanel(CLUE_SCROLL_ALL)); + minigamePanel.add(makeSkillPanel(LAST_MAN_STANDING)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_ROGUE)); + minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_HUNTER)); + c.gridx = 0; + c.gridy = 4; + gridBag.setConstraints(minigamePanel, c); + add(minigamePanel); } @Override @@ -492,16 +326,14 @@ public class HiscorePanel extends PluginPanel input.requestFocusInWindow(); } - private JPanel makeSkillPanel(String skillName, HiscoreSkill skill) + /* Builds a JPanel displaying an icon and level/number associated with it */ + private JPanel makeSkillPanel(HiscoreSkill skill) { JLabel label = new JLabel(); + label.setFont(FontManager.getRunescapeSmallFont()); label.setText("--"); - // Store the skill that the label displays so we can tell them apart - label.putClientProperty(SKILL_NAME, skillName); - label.putClientProperty(SKILL, skill); - - String skillIcon = "skill_icons_small/" + skillName.toLowerCase() + ".png"; + String skillIcon = "skill_icons_small/" + (skill == null ? "combat" : skill.getName().toLowerCase()) + ".png"; log.debug("Loading skill icon from {}", skillIcon); try @@ -518,36 +350,31 @@ public class HiscorePanel extends PluginPanel log.warn(null, ex); } - // Show skill details on hover - label.addMouseListener(new MouseInputAdapter() - { - @Override - public void mouseEntered(MouseEvent e) - { - JLabel source = (JLabel) e.getSource(); - String skillName = (String) source.getClientProperty(SKILL_NAME); - HiscoreSkill skill = (HiscoreSkill) label.getClientProperty(SKILL); - changeDetail(skillName, skill); - } - }); - skillLabels.add(label); + boolean totalLabel = skill == HiscoreSkill.OVERALL || skill == null; //overall or combat + label.setIconTextGap(totalLabel ? 10 : 4); JPanel skillPanel = new JPanel(); + skillPanel.setOpaque(false); + skillPanel.setBorder(new EmptyBorder(2, 0, 2, 0)); + skillLabels.add(label); skillPanel.add(skillLabels.get(skillLabels.size() - 1)); + return skillPanel; } public void lookup(String username) { input.setText(username); + + selectedEndPoint = HiscoreEndpoint.NORMAL; //reset the endpoint to regular player + updateButtons(); + lookup(); } private void lookup() { String lookup = input.getText(); - details.setText("Loading..."); - progressBar.setVisible(false); lookup = sanitize(lookup); @@ -556,32 +383,66 @@ public class HiscorePanel extends PluginPanel return; } + /* Runescape usernames can't be longer than 12 characters long */ + if (lookup.length() > MAX_USERNAME_LENGTH) + { + input.setIcon(ERROR_ICON); + loading = false; + return; + } + + input.setEditable(false); + input.setIcon(LOADING_ICON); + loading = true; + for (JLabel label : skillLabels) { label.setText("--"); } + // if for some reason no endpoint was selected, default to normal + if (selectedEndPoint == null) + { + selectedEndPoint = HiscoreEndpoint.NORMAL; + } + try { - HiscoreEndpoint endpoint = HiscoreEndpoint.valueOf(endpointButtonGroup.getSelection().getActionCommand()); - log.debug("Hiscore endpoint " + endpoint.name() + " selected"); - - result = hiscoreClient.lookup(lookup, endpoint); + log.debug("Hiscore endpoint " + selectedEndPoint.name() + " selected"); + result = hiscoreClient.lookup(lookup, selectedEndPoint); } catch (IOException ex) { log.warn("Error fetching Hiscore data " + ex.getMessage()); - details.setText("Error fetching Hiscore data"); - progressBar.setVisible(false); + input.setIcon(ERROR_ICON); + input.setEditable(true); + loading = false; return; } + /* + For some reason, the fetch results would sometimes return a not null object + with all null attributes, to check for that, i'll just null check one of the attributes. + */ + if (result.getAttack() == null) + { + input.setIcon(ERROR_ICON); + input.setEditable(true); + loading = false; + return; + } + + //successful player search + input.setIcon(SEARCH_ICON); + input.setEditable(true); + loading = false; + + int index = 0; for (JLabel label : skillLabels) { - String skillName = (String) label.getClientProperty(SKILL_NAME); - HiscoreSkill skill = (HiscoreSkill) label.getClientProperty(SKILL); + HiscoreSkill skill = find(index); - if (skillName.equals("Combat")) + if (skill == null) { if (result.getPlayer() != null) { @@ -600,7 +461,6 @@ public class HiscorePanel extends PluginPanel else if (result.getSkill(skill) != null && result.getSkill(skill).getRank() != -1) { Skill s = result.getSkill(skill); - int level; if (config.virtualLevels() && SKILLS.contains(skill)) { @@ -613,12 +473,191 @@ public class HiscorePanel extends PluginPanel label.setText(Integer.toString(level)); } + + label.setToolTipText(detailsHtml(skill)); + index++; + } + } + + void addInputKeyListener(KeyListener l) + { + this.input.addInputKeyListener(l); + } + + void removeInputKeyListener(KeyListener l) + { + this.input.removeInputKeyListener(l); + } + + /* + Returns a hiscore skill based on it's display order. + */ + private HiscoreSkill find(int index) + { + if (index < SKILLS.size()) + { + return SKILLS.get(index); } - // Clear details panel - details.setFont(UIManager.getFont("Label.font").deriveFont(Font.ITALIC)); - details.setText("Hover over a skill for details"); - progressBar.setVisible(false); + switch (index - SKILLS.size()) + { + case 0: + return null; + case 1: + return HiscoreSkill.OVERALL; + case 2: + return HiscoreSkill.CLUE_SCROLL_ALL; + case 3: + return HiscoreSkill.LAST_MAN_STANDING; + case 4: + return HiscoreSkill.BOUNTY_HUNTER_ROGUE; + case 5: + return HiscoreSkill.BOUNTY_HUNTER_HUNTER; + } + + return null; + } + + /* + Builds a html string to display on tooltip (when hovering a skill). + */ + private String detailsHtml(HiscoreSkill skill) + { + String openingTags = ""; + String closingTags = ""; + + String content = ""; + + if (result != null) + { + if (skill == null) + { + double combatLevel = Experience.getCombatLevelPrecise( + result.getAttack().getLevel(), + result.getStrength().getLevel(), + result.getDefence().getLevel(), + result.getHitpoints().getLevel(), + result.getMagic().getLevel(), + result.getRanged().getLevel(), + result.getPrayer().getLevel() + ); + + double combatExperience = result.getAttack().getExperience() + + result.getStrength().getExperience() + result.getDefence().getExperience() + + result.getHitpoints().getExperience() + result.getMagic().getExperience() + + result.getRanged().getExperience() + result.getPrayer().getExperience(); + + content += "

Skill: Combat

"; + content += "

Exact Combat Level: " + StackFormatter.formatNumber(combatLevel) + "

"; + content += "

Experience: " + StackFormatter.formatNumber(combatExperience) + "

"; + } + else + { + switch (skill) + { + case CLUE_SCROLL_ALL: + { + String rank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); + String allRank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollAll().getRank()); + String easyRank = (result.getClueScrollEasy().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollEasy().getRank()); + String mediumRank = (result.getClueScrollMedium().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMedium().getRank()); + String hardRank = (result.getClueScrollHard().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollHard().getRank()); + String eliteRank = (result.getClueScrollElite().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollElite().getRank()); + String masterRank = (result.getClueScrollMaster().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getClueScrollMaster().getRank()); + String all = (result.getClueScrollAll().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollAll().getLevel())); + String easy = (result.getClueScrollEasy().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollEasy().getLevel())); + String medium = (result.getClueScrollMedium().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMedium().getLevel())); + String hard = (result.getClueScrollHard().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollHard().getLevel())); + String elite = (result.getClueScrollElite().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollElite().getLevel())); + String master = (result.getClueScrollMaster().getLevel() == -1 ? "0" : StackFormatter.formatNumber(result.getClueScrollMaster().getLevel())); + content += "

All: " + all + " Rank: " + allRank + "

"; + content += "

Easy: " + easy + " Rank: " + easyRank + "

"; + content += "

Medium: " + medium + " Rank: " + mediumRank + "

"; + content += "

Hard: " + hard + " Rank: " + hardRank + "

"; + content += "

Elite: " + elite + " Rank: " + eliteRank + "

"; + content += "

Master: " + master + " Rank: " + masterRank + "

"; + break; + } + case BOUNTY_HUNTER_ROGUE: + { + String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterRogue().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case BOUNTY_HUNTER_HUNTER: + { + String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getBountyHunterHunter().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case LAST_MAN_STANDING: + { + String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(result.getLastManStanding().getRank()); + content += "

Rank: " + rank + "

"; + break; + } + case OVERALL: + { + Skill requestedSkill = result.getSkill(skill); + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); + String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); + content += "

Skill: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + break; + } + default: + { + Skill requestedSkill = result.getSkill(skill); + + String rank = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getRank()); + String exp = (requestedSkill.getRank() == -1) ? "Unranked" : StackFormatter.formatNumber(requestedSkill.getExperience()); + String remainingXp; + if (requestedSkill.getRank() == -1) + { + remainingXp = "Unranked"; + } + else + { + int currentLevel = Experience.getLevelForXp((int) requestedSkill.getExperience()); + remainingXp = (currentLevel + 1 <= Experience.MAX_VIRT_LEVEL) ? StackFormatter.formatNumber(Experience.getXpForLevel(currentLevel + 1) - requestedSkill.getExperience()) : "0"; + } + + content += "

Skill: " + skill.getName() + "

"; + content += "

Rank: " + rank + "

"; + content += "

Experience: " + exp + "

"; + content += "

Remaining XP: " + remainingXp + "

"; + + break; + } + } + } + } + + /** + * Adds a html progress bar to the hover information + */ + if (SKILLS.contains(skill)) + { + int currentLevel = Experience.getLevelForXp((int) result.getSkill(skill).getExperience()); + int currentXp = (int) result.getSkill(skill).getExperience(); + int xpForCurrentLevel = Experience.getXpForLevel(currentLevel); + int xpForNextLevel = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1; + + double xpGained = currentXp - xpForCurrentLevel; + double xpGoal = xpForNextLevel != -1 ? xpForNextLevel - xpForCurrentLevel : 100; + int progress = (int) ((xpGained / xpGoal) * 100f); + + // had to wrap the bar with an empty div, if i added the margin directly to the bar, it would mess up + content += "
" + + "
" + + "
" + + "
" + + "
" + + "
"; + } + + return openingTags + content + closingTags; } private static String sanitize(String lookup) @@ -626,20 +665,18 @@ public class HiscorePanel extends PluginPanel return lookup.replace('\u00A0', ' '); } + /* + When an endpoint gets selected, this method will correctly display the selected one + with an orange underline. + */ private void updateButtons() { - for (JToggleButton button : endpointButtons) + for (JPanel panel : endPoints) { - Color color; - if (button.isSelected()) - { - color = Color.CYAN; - } - else - { - color = Color.WHITE; - } - button.setBackground(color); + panel.setBorder(new EmptyBorder(0, 0, 1, 0)); } + + int selectedIndex = selectedEndPoint.ordinal(); + endPoints.get(selectedIndex).setBorder(new MatteBorder(0, 0, 1, 0, ColorScheme.BRAND_ORANGE)); } -} +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java index fde8d673cb..e016215756 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java @@ -30,6 +30,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionListener; +import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.ImageIcon; @@ -129,6 +130,16 @@ public class IconTextField extends JPanel this.backgroundColor = color; } + public void addInputKeyListener(KeyListener l) + { + textField.addKeyListener(l); + } + + public void removeInputKeyListener(KeyListener l) + { + textField.removeKeyListener(l); + } + public void setHoverBackgroundColor(Color hoverBackgroundColor) { if (hoverBackgroundColor == null) diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index 674ebe07dd..41ddd1b9fb 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -79,6 +79,7 @@ public class SwingUtil // Force heavy-weight popups/tooltips. // Prevents them from being obscured by the game applet. ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); + ToolTipManager.sharedInstance().setInitialDelay(300); JPopupMenu.setDefaultLightWeightPopupEnabled(false); UIManager.put("Button.foreground", Color.WHITE); diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/hardcore_ironman.png index 2f4631d36ea0ebadf0394ece5a54d85e5ad5a755..bf70440802fbb5fb6f392335c26a7cafcdf04509 100644 GIT binary patch literal 15224 zcmeI3Yitx%6vqc*2v#wKhY(}JvZ#UJ>^!%-JK5c)bPH`@ODUDM#30j|yY1NBnQ>;? z-PQz)V#F^^K_Ws>Oe9(od8DF=BEgVIsu+zik!Um~icd_4ny4fK-r48$c6-w(#xHl1 zeVqHh=bSsgd(YYVGEcX*teaPJR}Do`^O~CyZSZ@6`<^=oes1}2qyv84YBqJ*6m{D| z_dSa`cxWj_MSj-WJDtwt21zk80a-P=K_Hhg;cSYE*5yoD=>v|h8}w*;%zx$dS-($H zWB!g{l1-WopjT@evcTq{mUd;RPl>4hx_C`AC&2(2;K;sQCav32F6Ph2mEdo8nDP7a zQ=Gn-zuryg>rA%#8Vn2gf`M9EVQWLaP$a+wLm@GAzmI3RAj1lbz|y=RiL4~@zT(Rt zuYtd!mYR~<5^Ia;pcV7?I*utZOg5VhWQBlX^)Or{5@A@L;dvU)pzT53k#n?euP6j5 z#Yq5Lu{6`s4Bh9(mAj38C+7FNiOR3yb!E(QBHb>sgA|#ZY%*McWvV%;O4-Nkx6=8g zsR{$qAOm#AhJIXCKeN|x47=BuCS_TkHZk0jWU`#Q>UuMos;O}>pd3IOE1WGU%CEK2=hKkv1&2ED#r>OtoQ;SN^0)&ARQ#x&oRLaX1jrG*yyo1)dMGEG>pu zj*iHpLWc#Fqf@*fh%B#waCZS>GU;`n5;|ch{cg3u(1pgL8VU?wSr)5`iYf{UEdW)d zISz#BupAE2YDkSl!l6it5Qxa{oG}!m#v}cZcEhC0TKMTU>F=1E^7E(r9H_WzV9UHg!8^hM*AN<34?kY%kDpV4oX0~&M{hVA`rD~m8iBb2@NrF`jPn4Xm9@ssmj8!rh=6AZ)nC{Qd z5AiH~F`WBZboI)pYZu{Al;eJU>~;Ke!_)|KB!RDv1>XEMJ3#QFzk_?>Cw94d;~dEO)V+ z`J-}0H!~^{izGpS;7x)H&j(QnE(8ePB)IT=5S8FUfZ$Dn3(p5p2`&T(-Xys2d=Qo3 zLV(~+f(y?FQ3);t2;L;P@O%)J;6i}lO@a&02T=(w1PIcvi$RYXM_&X=Q?!ErU;ksv@n?IX6zjRS<`^d=R*0no+Uvjli z>Ze}$c&y{pSl=-Jf{?st?}{V4=XCwDclV-S-`=^w{_V_{TgFeG{FMIbtp&2MZ_~yj zC*Qc3iXR9Mf2(J+dz#OGm0Vv#{q^99Gxq9d*X=mC>DEhIZ?s)HaP-=e9ZeH;-+cFl z_`!K)f8hA-k8bO{ERNr4N|9Bk9`1Ok@$&59^?w38JaG8E=U)nUfA;>+*%NJhHa>MM zaOAPkcVAqXS-yY#nEi?Kk=CcZynL5szP9Yz#H9-pXXjB3w`BRdj}OM&XJeZiTN20D HY=8W3M!}u^ delta 233 zcmexSHiKz`3O`$tx4R1i82ohJT|ZG#yq;l)r;B5V#p&dP1wsibOBCYc{@F7#Gas&2 zQxo~<`u+Xl7pVuAOR`A%FV) zeT}y^PT-W%EkcWvMI h=P!418% 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 index a00b92b1f864da7029027c53fe2fe158517e2f3b..453e0326de4b2f269e33ac78bc5abee5deb18b9c 100644 GIT binary patch literal 15218 zcmeI3Ym5_B6vu}UL6;DD#6XC~NeKpl={(wQJ7Zg1T3|O)*z8t!7c@AXx!q2-ovAak zw7bcI0o14j#F(JGR0u{BvkDS45aLEnOi(eA4;V?5$QPrK6;O~5M(^}Fd$)VDQH)=1 zlRnP<-*e8~-@WH_zRdIOYgf-~TF^vM)XcWlcnAER^-IZ_cu_$WB zT<1N7+Pi-~MTwWSWVhX&SR*M$&MT`%3V4gTJe*BY;pN4=tn>k!PJv!ck9bB;ee9t% zHR9VL3=O zP8?W@spW0W&}k>GoHF|Dh{xk3s=X@f%H?Z`bgRM+Qe=yAp5?t9+sH{(YCidXGh4be zRbfFE}8Bx<=EUT-efFtuf`+yoI+kT&$Rk^_0b zb^y!hHx;mQ6I{x|Myt2AbbWKiL~@h~uN@S%3EUi|>{dDo*Z zpR)EULOK>Joho{9m!_*m!Fn*vqAvCQu15oL*#=Q~9zzW0XShI;7bQL<`T3j_BVnjodSFZp-k0P8k}oLnoI8eVCK?0Qk1E^pq@uXzy5gvcls3$qY)7@6+zZ&e z-Wz7?gRYsiQzgZWtYN~-0#RR>Z8YriDxEZ`O}A`WS3p}l3J1KJrb>!G1q3ziXH?P0 zGeXesXXLcVF#%bTLwqP8C_cUfF_Cb)j|&|)lzyjLVCZsVQ4Ix#uV0owo#q40qR9Aw z>SuT!gqV;V3NmU?6~$0cO!Go5kt^7^MC(iq_B}c4n&YfRg~>QkNQr7%VS=KnFo5$5 z3~Vqe!!^qSkOesg@>Gzc>;RjX#f(>mZf#qcbbI$I}rYV2rL$ZDkym6uHmIQ19t zR12`y(rO*c@hR1@Br8sDiz-fe05!~x8LeMd&9maBjo;46At=Xpxlf#Yr{ygAVooZA1MO zD;Zhu1!|P79^mT0 zYekUlj%%4W#1Z0&|~TzS^I*zNpL zU(xN1io_yG5FmJy;KK7kRDufuf;R~+JRd|QxDX(CliXil5na}_?^Nh_@#35ptAL{U#~qo`Yd!SA0as^d;-%01l_ zCGDZ8dB*wm_rF9@Q>eChOS1UgyRSrsw{f$!FF$B54#iHk?jJe*(Cb@w9-K=bIeg@q z>$H4kXy(o@({0x_uUc``1@XWEYx=(a zC-zW7KYYEnOTDyZ%_B3YWnY+Y7mhBudVR-&H;!HI+r93s8=c#IZ~odA!CsgF2B@?)|(czT|_${!|Xke^#7#YxMo2LKAx|bmg-% ynQu;{cI>ir=I#ygz32A*`Sa@Zl_euLsbts7H@93k^r`byY}?AU@!=IuZv7hsqMcX( delta 260 zcmV+f0sH>)cC!MIBo78+OGiWi{{a60|De66laV18e*n`-L_t(I%e9le5`!QRgqKX- zD(PZJNp}xuXK6=k9x1|%%%w0mhXTg@?2I#@=W=`t3U%?$cNGWcJp&>F0C?|{g0Ab5 zdCrLA&rxfWoKByZIOleaej+9!CL(?@N*-fO?tPh2+qTQt7?W&AYYhPC`#wo*Gl{E6 zgB%m@YCWvA)%exAWoc4N?_1bhj2{3(m=MBL?WSoOR2fB}1t!I7qK@3H>+LcZxw&i@ z?u*AJe%X=SjE!cc;;Y3f#6mLu;%L;t}0000< KMNUMnLSTZ2rF9_y 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 index c00f4ec040ae8484df41085d428a99f043fe9e4a..375dd4e906602c8266810d555534ad6a20ba7b4c 100644 GIT binary patch literal 16678 zcmeI3e{d969l-bQFr=|i#2+*rX1F<|wzhk_d%yN>y&MyBr^&Pz&Ll`c>|}5EU9!X7 z?Qy%wT|&cj%nTz?N~f`617m)48a*s7HlPzN1UjBoGf z=H>E+D(dLGo%^xh_kG{*ci-oI-}mkQv0G#9UtZ8~TLVQ=3)))49q|7X@Vltq27gao zl2h>SrcCQvjiNsON#kpw#`fGwQNjC_u5P_Mdaod#h zanP%zn;e&Z__D*U$W4wl-Y6T*w17UPbtnruhuXWOp$$?%cB~9F1oHxPkOI1B&!>`U zO~^Mn3VsE6ZZtCvd!dWIp~=x~1hjWYWA+v`3+!H}kCs>;Z|4I}&dc*2{%*UA<-82* zX51|8atj_-@VM-y!x3tLXThwT5IVwNDushmlcP`9GXlfpaye(t?Nqb93>OFl7}mwO zTr})KYlCTB%+qOYdC^JPPZ(%YR>|m!nzkE$#kkt9H#rj42!aDYhob{N4^Q&}zZ8gzE-~(s${}jFnH8#4(O|X?nz9(?a-p(y zf{im<%Zj8@s8AU(s{v#EH95))uu{@070a+GtYblxjM^5GjO793ATy)2dRZ0Al9@7l zIqqN_r$xt(ZgLeGg=(A*0%vswsPM756F$yRMdm!jdiy%+&ioI4VsX_}7w)2Z6X0_qgc* z=N0L=pa`Jxk+XLqxvFbZ>J!twKn^kGk(C?% zmuBwzs(camKRt8i9rOQS=8E-YW~DFh9Vd8m=`N`pkwVMai%@Ri?*;!k$oUg%2U{`G=`I z0L|JiD!b~9JgJW8MtVhjkst^Vyhw22`5;ndEVfgkl*RH{rjqOrvN0g$5R#4OfBNTOY z8vg&5qB`m+%cr|3O4vnFOVqd4Eqj=vY*g&tu89A$w?4ALHk!0Nw$U=S(XvOg?9r{` z{g&~5>-d0mZ@*<k~uPi6QHg@UzK!V3Y0O{kHE7TMrJ;o7mj=?3TsPjaZ-G zQg>vl^~g5si`%V7x7QupVLkp3{Me4|sGl6QO^()`9JQVtwVm2!d*xfVOpV#5cH2&m z&6^tg*wpU2$wwETc(n0H-&ykGvBuZO>&}hOfBn1lZ|t4_=Kh<0d2qpN`)+$}f8)7{ z#y6f^cK+#Q=MFG$KV?7v%!>EEzv$OLSpNRuJ3n~gu8S|y?;dWteBAx^;WZz=6#31G zuU?w$ynLeh%E`#(Q|qt%WZlJAQ-3-w{(egR^I7%E*@5Y42!F3 z^@U$=|HYAIn<5YFa=yz=WOjaLQEcV<;WO{QdGmeERPyMSeaGkhR3G`vTko8|@b{?; zBYUTIuDs`RL(lE>J1=cM`sxSk&b+tt!Rc>);b$Ye4_PwFiQn-LUETH9JhNorE5jF; h#_qVN+`M@{75vqzZIhR-EjLo7Ez%x-=8HYw_y>NU2G9Ti delta 380 zcmV-?0fYXgf&qsEGl@`6M-2)Z3IG5A4M|8uQUCw|5C8xG5C{eU001BJ|6u?C00v@9 zM??Ss00000`9r&Zks%j<0VhdBK~y-)wUa$g!$1&)zwtT|R)}qU1H?%X2cV*)3r7eY z7k~z8(os@zfE)pF0u-e5cH$_CWP3dncC1(^hJsADt9d{3W)}`wLneB6`=MPu&Oz0O zOf)jnP8XdHgb3g7HtBSMT@&DkhPT&Mhe&N#i13a=JWl{f(~@3)Pw+g!4-J=BLjYc$ z7hKgJi3s{8!00004nJ z$hLzpWB=2SsX#%=64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<_&89iMb zLo9lyUfS!$94Ns0;Cu+i8 z{}%+OUeN7azj(Hh*$1IN!n1ZBTrg!$xB3m8)|{tLq6L1NCS5XZFMPMc$7F-h*WWzN zN=*xawL83^_38@w;!X0L%9#nF7M;e)Yo4Y=-zrx0-Mwd(Sf6vm1HE1IocwQn zPPycK=h5LWi+8Oue(6ztj`gv}ywgiG-~JR>exuvk+{t9Rr|c@9bK-^ywktLZ)F-D0 gHhZdz-)M`!&lveI>_gskV2Ckzy85}Sb4q9e0JU(RJpcdz 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 index efa7643d971e6026e959c2c47b2f9fd489fd9eb0..07eb519971580328d7f4d059d7a941fdebd785d8 100644 GIT binary patch literal 14668 zcmeI3Ux*t;9LM*-KQ+=mSX!$-bj@L{)Xn}&F3Fmt)=TXjnCo$u+Fbi!H@lO&ZIj(~ zcdyAELe+v$YNajJR$Hx5A4IAz1qBrqd{C4Yl!B-TQWW}7DD8^~>N&fcOMb~Eohp5t zg=Bx3`Tl;tna})YvM+mJd~D0|$o&z9VU~}M&;D zJsaJgC9$-vk^am zTx-_R(B{2(Ducad?-`Z$;&epvd-{$YHHd89Y~I*g>jiIYrB==E5o) zUqr|b4+mAnuHSB$x>a`{$?)AS?e%U)LwVIhIb4q^PKa|tq9~>nF{Q-C4T7i$0tgZ+ zyJ8e9-KflU#1Q48kWi$QBFiuaPNMhQb2`Jf z8-cP_Rm!kcwH!Pwl#?@juV&yCRGKnsx}IukXf&V04Kc&el~lT1mV^Z2#H3u|l6pMO zsj^n)w2G`_sOdzyQVt*%3ebH?=)9%P`nv^(4i6UH(s20pu*y=rT&{>oPDZ-Ki6TmI zDK(Yk^rW6nr;_Q4D2a=SK(Hl=wmJqr_tYxT_;b6U+Ar_OYUB{@Iq({Oy7C4Bye(9#+e<>1O=j z>8>KaASx)*U{>`~L-1qjMp`*3R`= zEUTsKnemS}1=j)|ml~`AuE8a3?aZORYuWcem@*ja^}Pw`<#T zn`wACshO>-8F=~UaCk^e;ztIQ8%iYPM27Ef?)Dfr8mL;z8+bRnepQO8rcZZAPsgc# zj-aFI7%mm~a}QQBY{icb_^E|&J+^c%a%&^pVxv8yTO1gMqe&nQqD^rT5kUdP1!)j% zii?N{3Mei}gJ@G+L_|q$sK>@`DX%KCSi--scC@x5YXj5E7L{LC+K^jDx z;vyn~0*VXLAleic5fKznT#yFQrnrcRpn&3nG>A6EMMMMz6c?mHv?(qkA}FA^APu5T zaS;(g0mTJr5N(Q!hzJTOE=YrDQ(QzuP(X1(8btesxFYQr0g;K{{;T8H{4QQU@DF}P zkkv*e3Jf#<5W_sbpJD!7!1v1xGqH>rSXE*e#Nu9T*>b~@>)dR|N0A$53HGrFo!m-zj)-nzfP_G{^e7vj_%0k zpF91{m0$mOU?Fn!!nMPzfBx|Dw#wcIxsSG7I&h{T+Sg{Mez&>5s2p9bn#h_1vyI UPkeXGf8TL*^H~0aCw9H~FGx%7Hvj+t delta 319 zcmV-F0l@yua_s_;Bo78+OGiWi000000Qp0^e~}>3}Q)b}Zp67}+$WQbqeD25kA$@AsbJAc?&VDW$s` zv;M1Lz*>ve8m%<|S(dHmlko67&kjH-g@>qN0Qd&kDhVMpB-+!~3I+;p2Q3yP&%cu{OJD8W**9Cm|2{TeG(>c~5?tzV893C929r9{Fp932oCUibC)odI0PL@p%Xt R^NIif002ovPDHLkV1jCskkSAE