diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index a57b7f50c1..94e5503508 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -51,6 +51,7 @@ import net.runelite.client.plugins.opponentinfo.OpponentInfo; import net.runelite.client.plugins.pestcontrol.PestControl; import net.runelite.client.plugins.runecraft.Runecraft; import net.runelite.client.plugins.xpglobes.XpGlobes; +import net.runelite.client.plugins.xptracker.XPTracker; import net.runelite.client.plugins.xtea.Xtea; import net.runelite.client.plugins.zulrah.Zulrah; import org.slf4j.Logger; @@ -90,6 +91,7 @@ public class PluginManager plugins.add(new XpGlobes()); plugins.add(new CombatNotifier()); plugins.add(new JewelryCount()); + plugins.add(new XPTracker()); plugins.add(new ExaminePlugin()); if (RuneLite.getOptions().has("developer-mode")) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillXPInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillXPInfo.java new file mode 100644 index 0000000000..b946ba0a69 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/SkillXPInfo.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017, Cameron + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.xptracker; + +import net.runelite.api.Skill; +import java.time.Duration; +import java.time.Instant; + +public class SkillXPInfo +{ + private Skill skill; + private Instant skillTimeStart = null; + private int xpGained = 0; + private int loginXp = 0; + + public SkillXPInfo(int loginXp, Skill skill) + { + this.skill = skill; + this.loginXp = loginXp; + } + + public int getXpHr() + { + long timeElapsedInSeconds = Duration.between( + skillTimeStart, Instant.now()).getSeconds(); + return (int) ((1.0 / (timeElapsedInSeconds / 3600.0)) * xpGained); + + } + + public void reset(int loginXp) + { + xpGained = 0; + this.loginXp = loginXp; + skillTimeStart = null; + } + + public void update(int currentXp) + { + xpGained = currentXp - loginXp; + if (skillTimeStart == null) + skillTimeStart = Instant.now(); + } + + public Instant getSkillTimeStart() + { + return skillTimeStart; + } + + public int getXpGained() + { + return xpGained; + } + + public int getLoginXp() + { + return loginXp; + } + + public Skill getSkill() + { + return this.skill; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPPanel.java new file mode 100644 index 0000000000..097b5e6a7c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPPanel.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017, Cameron + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.xptracker; + +import net.runelite.api.Client; +import net.runelite.api.Skill; +import net.runelite.client.RuneLite; +import net.runelite.client.ui.PluginPanel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.Map; + +public class XPPanel extends PluginPanel +{ + private static final Client client = RuneLite.getClient(); + private static final Logger logger = LoggerFactory.getLogger(XPPanel.class); + private Map labelMap = new HashMap<>(); + private final XPTracker xpTracker; + private JPanel statsPanel; + + public XPPanel(RuneLite runelite, XPTracker xpTracker) + { + this.xpTracker = xpTracker; + + setMinimumSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); + setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); + setSize(PANEL_WIDTH, PANEL_HEIGHT); + setVisible(true); + statsPanel = new JPanel(); + statsPanel.setLayout(new GridLayout(24, 1)); + + try + { + for (Skill skill : Skill.values()) + { + if (skill == Skill.OVERALL) + break; + + JLabel skillLabel = new JLabel(); + labelMap.put(skill, makeSkillPanel(skill, skillLabel)); + } + } + catch (IOException e) + { + logger.warn(null, e); + } + + JButton resetButton = new JButton("Reset All"); + resetButton.setPreferredSize(new Dimension(PANEL_WIDTH, 32)); + resetButton.addActionListener((ActionEvent e) -> + runelite.getExecutor().execute(this::resetAllSkillXpHr)); + + statsPanel.add(resetButton); + JScrollPane scroll = new JScrollPane(statsPanel); + scroll.add(statsPanel); + + add(statsPanel); + } + + private JButton makeSkillResetButton(Skill skill) throws IOException + { + ImageIcon resetIcon = new ImageIcon(ImageIO.read(getClass().getResourceAsStream("reset.png"))); + JButton resetButton = new JButton(resetIcon); + resetButton.setPreferredSize(new Dimension(32, 32)); + resetButton.addActionListener(actionEvent -> resetSkillXpHr(skill)); + return resetButton; + } + + private JPanel makeSkillPanel(Skill skill, JLabel levelLabel) throws IOException + { + BorderLayout borderLayout = new BorderLayout(); + borderLayout.setHgap(12); + JPanel iconLevel = new JPanel(borderLayout); + iconLevel.setPreferredSize(new Dimension(PANEL_WIDTH, 32)); + + String skillIcon = "/skill_icons/" + skill.getName().toLowerCase() + ".png"; + logger.debug("Loading skill icon from {}", skillIcon); + JLabel icon = new JLabel(new ImageIcon(ImageIO.read(XPPanel.class.getResourceAsStream(skillIcon)))); + iconLevel.add(icon, BorderLayout.LINE_START); + + iconLevel.add(levelLabel, BorderLayout.CENTER); + iconLevel.add(makeSkillResetButton(skill), BorderLayout.LINE_END); + + return iconLevel; + } + + public void resetSkillXpHr(Skill skill) + { + int skillIdx = skill.ordinal(); + xpTracker.getXpInfos()[skillIdx].reset(client.getSkillExperience(skill)); + statsPanel.remove(labelMap.get(skill)); + statsPanel.revalidate(); + statsPanel.repaint(); + } + + public void resetAllSkillXpHr() + { + for (SkillXPInfo skillInfo : xpTracker.getXpInfos()) + { + if (skillInfo != null && skillInfo.getSkillTimeStart() != null) + resetSkillXpHr(skillInfo.getSkill()); + } + } + + public void updateAllSkillXpHr() + { + for (SkillXPInfo skillInfo : xpTracker.getXpInfos()) + { + if (skillInfo != null && skillInfo.getSkillTimeStart() != null && + skillInfo.getXpGained() != 0) + updateSkillXpHr(skillInfo); + } + } + + public void updateSkillXpHr(SkillXPInfo skillXPInfo) + { + JPanel skillPanel = labelMap.get(skillXPInfo.getSkill()); + JLabel xpHr = (JLabel) skillPanel.getComponent(1); + xpHr.setText(NumberFormat.getInstance().format(skillXPInfo.getXpHr()) + " xp/hr"); + + if (skillPanel.getParent() != statsPanel) + { + statsPanel.add(skillPanel); + statsPanel.revalidate(); + statsPanel.repaint(); + } + } +} + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java new file mode 100644 index 0000000000..a0504cef8c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XPTracker.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017, Cameron + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.xptracker; + +import com.google.common.eventbus.Subscribe; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.Skill; +import net.runelite.client.RuneLite; +import net.runelite.client.events.ExperienceChanged; +import net.runelite.client.events.GameStateChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.util.RunnableExceptionLogger; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class XPTracker extends Plugin +{ + private static final int NUMBER_OF_SKILLS = Skill.values().length - 1; //ignore overall + + private final RuneLite runeLite = RuneLite.getRunelite(); + private final ClientUI ui = runeLite.getGui(); + private final Client client = RuneLite.getClient(); + private ScheduledFuture future; + + private final NavigationButton navButton = new NavigationButton("XP Tracker"); + private final XPPanel xpPanel = new XPPanel(runeLite, this); + private SkillXPInfo[] xpInfos = new SkillXPInfo[NUMBER_OF_SKILLS]; + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() != GameState.LOGGED_IN) + xpPanel.resetAllSkillXpHr(); + } + + @Subscribe + public void onXpChanged(ExperienceChanged event) + { + Skill skill = event.getSkill(); + String currentUser = client.getLocalPlayer().getName(); + int skillIdx = skill.ordinal(); + + //To catch login ExperienceChanged event. + if (xpInfos[skillIdx] != null) + { + xpInfos[skillIdx].update(client.getSkillExperience(skill)); + } + else + { + xpInfos[skillIdx] = new SkillXPInfo(client.getSkillExperience(skill), + skill); + } + } + + private void setPluginPanel(ActionEvent e) + { + ui.expand(xpPanel); + } + + @Override + protected void startUp() throws Exception + { + navButton.getButton().addActionListener(this::setPluginPanel); + + navButton.getButton().setText("XP"); + ui.getNavigationPanel().addNavigation(navButton); + + ScheduledExecutorService executor = RuneLite.getRunelite().getExecutor(); + future = executor.scheduleAtFixedRate(RunnableExceptionLogger.wrap( + xpPanel::updateAllSkillXpHr), 0, 600, TimeUnit.MILLISECONDS); + + Font font = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream("/runescape.ttf")); + font = font.deriveFont(Font.BOLD, 16); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + ge.registerFont(font); + } + + @Override + protected void shutDown() throws Exception + { + future.cancel(true); + } + + public SkillXPInfo[] getXpInfos() + { + return xpInfos; + } + +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png b/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png new file mode 100644 index 0000000000..4d9d111037 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/xptracker/reset.png differ