Merge pull request #4114 from LeviSchuck/xp-pausing
Add pause skill timing feature to the XP Tracker
This commit is contained in:
@@ -54,6 +54,15 @@ import net.runelite.client.util.SwingUtil;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
class XpInfoBox extends JPanel
|
class XpInfoBox extends JPanel
|
||||||
{
|
{
|
||||||
|
// Templates
|
||||||
|
private static final String HTML_TOOL_TIP_TEMPLATE =
|
||||||
|
"<html>%s actions done<br/>"
|
||||||
|
+ "%s actions/hr<br/>"
|
||||||
|
+ "%s till goal lvl</html>";
|
||||||
|
private static final String HTML_LABEL_TEMPLATE =
|
||||||
|
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
|
||||||
|
|
||||||
|
// Instance members
|
||||||
private final JPanel panel;
|
private final JPanel panel;
|
||||||
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
@Getter(AccessLevel.PACKAGE)
|
||||||
@@ -74,6 +83,9 @@ class XpInfoBox extends JPanel
|
|||||||
private final JLabel expHour = new JLabel();
|
private final JLabel expHour = new JLabel();
|
||||||
private final JLabel expLeft = new JLabel();
|
private final JLabel expLeft = new JLabel();
|
||||||
private final JLabel actionsLeft = new JLabel();
|
private final JLabel actionsLeft = new JLabel();
|
||||||
|
private final JMenuItem pauseSkill = new JMenuItem("Pause");
|
||||||
|
|
||||||
|
private boolean paused = false;
|
||||||
|
|
||||||
XpInfoBox(XpTrackerPlugin xpTrackerPlugin, Client client, JPanel panel, Skill skill, SkillIconManager iconManager) throws IOException
|
XpInfoBox(XpTrackerPlugin xpTrackerPlugin, Client client, JPanel panel, Skill skill, SkillIconManager iconManager) throws IOException
|
||||||
{
|
{
|
||||||
@@ -98,12 +110,16 @@ class XpInfoBox extends JPanel
|
|||||||
final JMenuItem resetOthers = new JMenuItem("Reset others");
|
final JMenuItem resetOthers = new JMenuItem("Reset others");
|
||||||
resetOthers.addActionListener(e -> xpTrackerPlugin.resetOtherSkillState(skill));
|
resetOthers.addActionListener(e -> xpTrackerPlugin.resetOtherSkillState(skill));
|
||||||
|
|
||||||
|
// Create reset others menu
|
||||||
|
pauseSkill.addActionListener(e -> xpTrackerPlugin.pauseSkill(skill, !paused));
|
||||||
|
|
||||||
// Create popup menu
|
// Create popup menu
|
||||||
final JPopupMenu popupMenu = new JPopupMenu();
|
final JPopupMenu popupMenu = new JPopupMenu();
|
||||||
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
|
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
popupMenu.add(openXpTracker);
|
popupMenu.add(openXpTracker);
|
||||||
popupMenu.add(reset);
|
popupMenu.add(reset);
|
||||||
popupMenu.add(resetOthers);
|
popupMenu.add(resetOthers);
|
||||||
|
popupMenu.add(pauseSkill);
|
||||||
|
|
||||||
JLabel skillIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(skill)));
|
JLabel skillIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(skill)));
|
||||||
skillIcon.setHorizontalAlignment(SwingConstants.CENTER);
|
skillIcon.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
@@ -138,6 +154,7 @@ class XpInfoBox extends JPanel
|
|||||||
progressBar.setMaximumValue(100);
|
progressBar.setMaximumValue(100);
|
||||||
progressBar.setBackground(new Color(61, 56, 49));
|
progressBar.setBackground(new Color(61, 56, 49));
|
||||||
progressBar.setForeground(SkillColor.values()[skill.ordinal()].getColor());
|
progressBar.setForeground(SkillColor.values()[skill.ordinal()].getColor());
|
||||||
|
progressBar.setDimmedText("Paused");
|
||||||
|
|
||||||
progressWrapper.add(progressBar, BorderLayout.NORTH);
|
progressWrapper.add(progressBar, BorderLayout.NORTH);
|
||||||
|
|
||||||
@@ -157,12 +174,12 @@ class XpInfoBox extends JPanel
|
|||||||
panel.revalidate();
|
panel.revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(boolean updated, XpSnapshotSingle xpSnapshotSingle)
|
void update(boolean updated, boolean paused, XpSnapshotSingle xpSnapshotSingle)
|
||||||
{
|
{
|
||||||
SwingUtilities.invokeLater(() -> rebuildAsync(updated, xpSnapshotSingle));
|
SwingUtilities.invokeLater(() -> rebuildAsync(updated, paused, xpSnapshotSingle));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rebuildAsync(boolean updated, XpSnapshotSingle xpSnapshotSingle)
|
private void rebuildAsync(boolean updated, boolean skillPaused, XpSnapshotSingle xpSnapshotSingle)
|
||||||
{
|
{
|
||||||
if (updated)
|
if (updated)
|
||||||
{
|
{
|
||||||
@@ -172,6 +189,8 @@ class XpInfoBox extends JPanel
|
|||||||
panel.revalidate();
|
panel.revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paused = skillPaused;
|
||||||
|
|
||||||
// Update information labels
|
// Update information labels
|
||||||
expGained.setText(htmlLabel("XP Gained: ", xpSnapshotSingle.getXpGainedInSession()));
|
expGained.setText(htmlLabel("XP Gained: ", xpSnapshotSingle.getXpGainedInSession()));
|
||||||
expLeft.setText(htmlLabel("XP Left: ", xpSnapshotSingle.getXpRemainingToGoal()));
|
expLeft.setText(htmlLabel("XP Left: ", xpSnapshotSingle.getXpRemainingToGoal()));
|
||||||
@@ -181,28 +200,42 @@ class XpInfoBox extends JPanel
|
|||||||
progressBar.setValue(xpSnapshotSingle.getSkillProgressToGoal());
|
progressBar.setValue(xpSnapshotSingle.getSkillProgressToGoal());
|
||||||
progressBar.setCenterLabel(xpSnapshotSingle.getSkillProgressToGoal() + "%");
|
progressBar.setCenterLabel(xpSnapshotSingle.getSkillProgressToGoal() + "%");
|
||||||
progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getStartLevel());
|
progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getStartLevel());
|
||||||
progressBar.setRightLabel("Lvl. " + (xpSnapshotSingle.getEndLevel()));
|
progressBar.setRightLabel("Lvl. " + xpSnapshotSingle.getEndLevel());
|
||||||
|
|
||||||
progressBar.setToolTipText("<html>"
|
progressBar.setToolTipText(String.format(
|
||||||
+ xpSnapshotSingle.getActionsInSession() + " actions done"
|
HTML_TOOL_TIP_TEMPLATE,
|
||||||
+ "<br/>"
|
xpSnapshotSingle.getActionsInSession(),
|
||||||
+ xpSnapshotSingle.getActionsPerHour() + " actions/hr"
|
xpSnapshotSingle.getActionsPerHour(),
|
||||||
+ "<br/>"
|
xpSnapshotSingle.getTimeTillGoal()));
|
||||||
+ xpSnapshotSingle.getTimeTillGoal() + " till goal lvl"
|
|
||||||
+ "</html>");
|
progressBar.setDimmed(skillPaused);
|
||||||
|
|
||||||
progressBar.repaint();
|
progressBar.repaint();
|
||||||
}
|
}
|
||||||
|
else if (!paused && skillPaused)
|
||||||
|
{
|
||||||
|
// React to the skill state now being paused
|
||||||
|
progressBar.setDimmed(true);
|
||||||
|
progressBar.repaint();
|
||||||
|
paused = true;
|
||||||
|
pauseSkill.setText("Unpause");
|
||||||
|
}
|
||||||
|
else if (paused && !skillPaused)
|
||||||
|
{
|
||||||
|
// React to the skill being unpaused (without update)
|
||||||
|
progressBar.setDimmed(false);
|
||||||
|
progressBar.repaint();
|
||||||
|
paused = false;
|
||||||
|
pauseSkill.setText("Pause");
|
||||||
|
}
|
||||||
|
|
||||||
// Update exp per hour seperately, everytime (not only when there's an update)
|
// Update exp per hour separately, every time (not only when there's an update)
|
||||||
expHour.setText(htmlLabel("XP/Hour: ", xpSnapshotSingle.getXpPerHour()));
|
expHour.setText(htmlLabel("XP/Hour: ", xpSnapshotSingle.getXpPerHour()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static String htmlLabel(String key, int value)
|
static String htmlLabel(String key, int value)
|
||||||
{
|
{
|
||||||
String valueStr = StackFormatter.quantityToRSDecimalStack(value);
|
String valueStr = StackFormatter.quantityToRSDecimalStack(value);
|
||||||
|
return String.format(HTML_LABEL_TEMPLATE, SwingUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
|
||||||
return "<html><body style = 'color:" + SwingUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR) + "'>" + key + "<span style = 'color:white'>" + valueStr + "</span></body></html>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,13 +172,13 @@ class XpPanel extends PluginPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateSkillExperience(boolean updated, Skill skill, XpSnapshotSingle xpSnapshotSingle)
|
void updateSkillExperience(boolean updated, boolean paused, Skill skill, XpSnapshotSingle xpSnapshotSingle)
|
||||||
{
|
{
|
||||||
final XpInfoBox xpInfoBox = infoBoxes.get(skill);
|
final XpInfoBox xpInfoBox = infoBoxes.get(skill);
|
||||||
|
|
||||||
if (xpInfoBox != null)
|
if (xpInfoBox != null)
|
||||||
{
|
{
|
||||||
xpInfoBox.update(updated, xpSnapshotSingle);
|
xpInfoBox.update(updated, paused, xpSnapshotSingle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Levi <me@levischuck.com>
|
||||||
|
* 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 java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.runelite.api.Skill;
|
||||||
|
|
||||||
|
class XpPauseState
|
||||||
|
{
|
||||||
|
// Internal state
|
||||||
|
private final Map<Skill, XpPauseStateSingle> skillPauses = new EnumMap<>(Skill.class);
|
||||||
|
private boolean cachedIsLoggedIn = false;
|
||||||
|
|
||||||
|
boolean pauseSkill(Skill skill)
|
||||||
|
{
|
||||||
|
return findPauseState(skill).manualPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean unpauseSkill(Skill skill)
|
||||||
|
{
|
||||||
|
return findPauseState(skill).unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPaused(Skill skill)
|
||||||
|
{
|
||||||
|
return findPauseState(skill).isPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tickXp(Skill skill, int currentXp, int pauseAfterMinutes)
|
||||||
|
{
|
||||||
|
final XpPauseStateSingle state = findPauseState(skill);
|
||||||
|
|
||||||
|
if (state.getXp() != currentXp)
|
||||||
|
{
|
||||||
|
state.xpChanged(currentXp);
|
||||||
|
}
|
||||||
|
else if (pauseAfterMinutes > 0)
|
||||||
|
{
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
final int pauseAfterMillis = pauseAfterMinutes * 60 * 1000;
|
||||||
|
final long lastChangeMillis = state.getLastChangeMillis();
|
||||||
|
// When config.pauseSkillAfter is 0, it is effectively disabled
|
||||||
|
if (lastChangeMillis != 0 && (now - lastChangeMillis) >= pauseAfterMillis)
|
||||||
|
{
|
||||||
|
state.timeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tickLogout(boolean pauseOnLogout, boolean loggedIn)
|
||||||
|
{
|
||||||
|
// Deduplicated login and logout calls
|
||||||
|
if (!cachedIsLoggedIn && loggedIn)
|
||||||
|
{
|
||||||
|
cachedIsLoggedIn = true;
|
||||||
|
|
||||||
|
for (Skill skill : Skill.values())
|
||||||
|
{
|
||||||
|
findPauseState(skill).login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cachedIsLoggedIn && !loggedIn)
|
||||||
|
{
|
||||||
|
cachedIsLoggedIn = false;
|
||||||
|
|
||||||
|
// If configured, then let the pause state know to pause with reason: logout
|
||||||
|
if (pauseOnLogout)
|
||||||
|
{
|
||||||
|
for (Skill skill : Skill.values())
|
||||||
|
{
|
||||||
|
findPauseState(skill).logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private XpPauseStateSingle findPauseState(Skill skill)
|
||||||
|
{
|
||||||
|
return skillPauses.computeIfAbsent(skill, XpPauseStateSingle::new);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Levi <me@levischuck.com>
|
||||||
|
* 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 java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.runelite.api.Skill;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class XpPauseStateSingle
|
||||||
|
{
|
||||||
|
@Getter
|
||||||
|
private final Skill skill;
|
||||||
|
private final Set<XpPauseReason> pauseReasons = EnumSet.noneOf(XpPauseReason.class);
|
||||||
|
@Getter
|
||||||
|
private long lastChangeMillis;
|
||||||
|
@Getter
|
||||||
|
private int xp;
|
||||||
|
|
||||||
|
boolean isPaused()
|
||||||
|
{
|
||||||
|
return !pauseReasons.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean login()
|
||||||
|
{
|
||||||
|
return pauseReasons.remove(XpPauseReason.PAUSED_LOGOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean logout()
|
||||||
|
{
|
||||||
|
return pauseReasons.add(XpPauseReason.PAUSED_LOGOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean timeout()
|
||||||
|
{
|
||||||
|
return pauseReasons.add(XpPauseReason.PAUSED_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean manualPause()
|
||||||
|
{
|
||||||
|
return pauseReasons.add(XpPauseReason.PAUSE_MANUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean xpChanged(int xp)
|
||||||
|
{
|
||||||
|
this.xp = xp;
|
||||||
|
this.lastChangeMillis = System.currentTimeMillis();
|
||||||
|
return clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean unpause()
|
||||||
|
{
|
||||||
|
this.lastChangeMillis = System.currentTimeMillis();
|
||||||
|
return clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean clearAll()
|
||||||
|
{
|
||||||
|
if (pauseReasons.isEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseReasons.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum XpPauseReason
|
||||||
|
{
|
||||||
|
PAUSE_MANUAL,
|
||||||
|
PAUSED_LOGOUT,
|
||||||
|
PAUSED_TIMEOUT
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,6 +120,11 @@ class XpState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tick(Skill skill, long delta)
|
||||||
|
{
|
||||||
|
getSkill(skill).tick(delta);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcefully initialize a skill with a known start XP from the current XP.
|
* Forcefully initialize a skill with a known start XP from the current XP.
|
||||||
* This is used in resetAndInitState by the plugin. It should not result in showing the XP in the UI.
|
* This is used in resetAndInitState by the plugin. It should not result in showing the XP in the UI.
|
||||||
|
|||||||
@@ -25,8 +25,6 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.xptracker;
|
package net.runelite.client.plugins.xptracker;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -45,7 +43,7 @@ class XpStateSingle
|
|||||||
@Getter
|
@Getter
|
||||||
private int xpGained = 0;
|
private int xpGained = 0;
|
||||||
|
|
||||||
private Instant skillTimeStart = null;
|
private long skillTime = 0;
|
||||||
private int actions = 0;
|
private int actions = 0;
|
||||||
private int startLevelExp = 0;
|
private int startLevelExp = 0;
|
||||||
private int endLevelExp = 0;
|
private int endLevelExp = 0;
|
||||||
@@ -65,7 +63,7 @@ class XpStateSingle
|
|||||||
|
|
||||||
private int toHourly(int value)
|
private int toHourly(int value)
|
||||||
{
|
{
|
||||||
if (skillTimeStart == null)
|
if (skillTime == 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,7 @@ class XpStateSingle
|
|||||||
|
|
||||||
private long getTimeElapsedInSeconds()
|
private long getTimeElapsedInSeconds()
|
||||||
{
|
{
|
||||||
if (skillTimeStart == null)
|
if (skillTime == 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -84,7 +82,7 @@ class XpStateSingle
|
|||||||
// To prevent that, pretend the skill has been active for a minute (60 seconds)
|
// To prevent that, pretend the skill has been active for a minute (60 seconds)
|
||||||
// This will create a lower estimate for the first minute,
|
// This will create a lower estimate for the first minute,
|
||||||
// but it isn't ridiculous like saying 2 billion XP per hour.
|
// but it isn't ridiculous like saying 2 billion XP per hour.
|
||||||
return Math.max(60, Duration.between(skillTimeStart, Instant.now()).getSeconds());
|
return Math.max(60, skillTime / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getXpRemaining()
|
private int getXpRemaining()
|
||||||
@@ -229,15 +227,14 @@ class XpStateSingle
|
|||||||
endLevelExp = goalEndXp;
|
endLevelExp = goalEndXp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is first time we are updating, we just started tracking
|
|
||||||
if (skillTimeStart == null)
|
|
||||||
{
|
|
||||||
skillTimeStart = Instant.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void tick(long delta)
|
||||||
|
{
|
||||||
|
skillTime += delta;
|
||||||
|
}
|
||||||
|
|
||||||
XpSnapshotSingle snapshot()
|
XpSnapshotSingle snapshot()
|
||||||
{
|
{
|
||||||
return XpSnapshotSingle.builder()
|
return XpSnapshotSingle.builder()
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Levi <me@levischuck.com>
|
||||||
|
* 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.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigGroup;
|
||||||
|
import net.runelite.client.config.ConfigItem;
|
||||||
|
|
||||||
|
@ConfigGroup("xpTracker")
|
||||||
|
public interface XpTrackerConfig extends Config
|
||||||
|
{
|
||||||
|
@ConfigItem(
|
||||||
|
position = 0,
|
||||||
|
keyName = "logoutPausing",
|
||||||
|
name = "Pause on Logout",
|
||||||
|
description = "Configures whether skills should pause on logout"
|
||||||
|
)
|
||||||
|
default boolean pauseOnLogout()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
position = 1,
|
||||||
|
keyName = "pauseSkillAfter",
|
||||||
|
name = "Auto pause after",
|
||||||
|
description = "Configures how many minutes passes before pausing a skill while in game and there's no XP, 0 means disabled"
|
||||||
|
)
|
||||||
|
default int pauseSkillAfter()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,9 @@ package net.runelite.client.plugins.xptracker;
|
|||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.inject.Binder;
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Provides;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -43,10 +45,12 @@ import net.runelite.api.WorldType;
|
|||||||
import net.runelite.api.events.ExperienceChanged;
|
import net.runelite.api.events.ExperienceChanged;
|
||||||
import net.runelite.api.events.GameStateChanged;
|
import net.runelite.api.events.GameStateChanged;
|
||||||
import net.runelite.api.events.GameTick;
|
import net.runelite.api.events.GameTick;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
import net.runelite.client.game.SkillIconManager;
|
import net.runelite.client.game.SkillIconManager;
|
||||||
import net.runelite.client.plugins.Plugin;
|
import net.runelite.client.plugins.Plugin;
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
import static net.runelite.client.plugins.xptracker.XpWorldType.NORMAL;
|
import static net.runelite.client.plugins.xptracker.XpWorldType.NORMAL;
|
||||||
|
import net.runelite.client.task.Schedule;
|
||||||
import net.runelite.client.ui.NavigationButton;
|
import net.runelite.client.ui.NavigationButton;
|
||||||
import net.runelite.client.ui.PluginToolbar;
|
import net.runelite.client.ui.PluginToolbar;
|
||||||
import net.runelite.http.api.xp.XpClient;
|
import net.runelite.http.api.xp.XpClient;
|
||||||
@@ -68,15 +72,24 @@ public class XpTrackerPlugin extends Plugin
|
|||||||
@Inject
|
@Inject
|
||||||
private SkillIconManager skillIconManager;
|
private SkillIconManager skillIconManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private XpTrackerConfig xpTrackerConfig;
|
||||||
|
|
||||||
private NavigationButton navButton;
|
private NavigationButton navButton;
|
||||||
private XpPanel xpPanel;
|
private XpPanel xpPanel;
|
||||||
|
|
||||||
private final XpState xpState = new XpState();
|
|
||||||
|
|
||||||
private XpWorldType lastWorldType;
|
private XpWorldType lastWorldType;
|
||||||
private String lastUsername;
|
private String lastUsername;
|
||||||
|
private long lastTickMillis = 0;
|
||||||
|
|
||||||
private final XpClient xpClient = new XpClient();
|
private final XpClient xpClient = new XpClient();
|
||||||
|
private final XpState xpState = new XpState();
|
||||||
|
private final XpPauseState xpPauseState = new XpPauseState();
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
XpTrackerConfig provideConfig(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
return configManager.getConfig(XpTrackerConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(Binder binder)
|
public void configure(Binder binder)
|
||||||
@@ -229,7 +242,7 @@ public class XpTrackerPlugin extends Plugin
|
|||||||
final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp);
|
final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp);
|
||||||
|
|
||||||
final boolean updated = XpUpdateResult.UPDATED.equals(updateResult);
|
final boolean updated = XpUpdateResult.UPDATED.equals(updateResult);
|
||||||
xpPanel.updateSkillExperience(updated, skill, xpState.getSkillSnapshot(skill));
|
xpPanel.updateSkillExperience(updated, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
|
||||||
xpState.recalculateTotal();
|
xpState.recalculateTotal();
|
||||||
xpPanel.updateTotal(xpState.getTotalSnapshot());
|
xpPanel.updateTotal(xpState.getTotalSnapshot());
|
||||||
}
|
}
|
||||||
@@ -237,14 +250,7 @@ public class XpTrackerPlugin extends Plugin
|
|||||||
@Subscribe
|
@Subscribe
|
||||||
public void onGameTick(GameTick event)
|
public void onGameTick(GameTick event)
|
||||||
{
|
{
|
||||||
// Rebuild calculated values like xp/hr in panel
|
rebuildSkills();
|
||||||
for (Skill skill : Skill.values())
|
|
||||||
{
|
|
||||||
xpPanel.updateSkillExperience(false, skill, xpState.getSkillSnapshot(skill));
|
|
||||||
}
|
|
||||||
|
|
||||||
xpState.recalculateTotal();
|
|
||||||
xpPanel.updateTotal(xpState.getTotalSnapshot());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XpSnapshotSingle getSkillSnapshot(Skill skill)
|
XpSnapshotSingle getSkillSnapshot(Skill skill)
|
||||||
@@ -361,4 +367,59 @@ public class XpTrackerPlugin extends Plugin
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Schedule(
|
||||||
|
period = 1,
|
||||||
|
unit = ChronoUnit.SECONDS
|
||||||
|
)
|
||||||
|
public void tickSkillTimes()
|
||||||
|
{
|
||||||
|
// Adjust unpause states
|
||||||
|
for (Skill skill : Skill.values())
|
||||||
|
{
|
||||||
|
xpPauseState.tickXp(skill, client.getSkillExperience(skill), xpTrackerConfig.pauseSkillAfter());
|
||||||
|
}
|
||||||
|
|
||||||
|
xpPauseState.tickLogout(xpTrackerConfig.pauseOnLogout(), !GameState.LOGIN_SCREEN.equals(client.getGameState()));
|
||||||
|
|
||||||
|
if (lastTickMillis == 0)
|
||||||
|
{
|
||||||
|
lastTickMillis = System.currentTimeMillis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long nowMillis = System.currentTimeMillis();
|
||||||
|
final long tickDelta = nowMillis - lastTickMillis;
|
||||||
|
lastTickMillis = nowMillis;
|
||||||
|
|
||||||
|
for (Skill skill : Skill.values())
|
||||||
|
{
|
||||||
|
if (!xpPauseState.isPaused(skill))
|
||||||
|
{
|
||||||
|
xpState.tick(skill, tickDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuildSkills();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildSkills()
|
||||||
|
{
|
||||||
|
// Rebuild calculated values like xp/hr in panel
|
||||||
|
for (Skill skill : Skill.values())
|
||||||
|
{
|
||||||
|
xpPanel.updateSkillExperience(false, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
|
||||||
|
}
|
||||||
|
|
||||||
|
xpState.recalculateTotal();
|
||||||
|
xpPanel.updateTotal(xpState.getTotalSnapshot());
|
||||||
|
}
|
||||||
|
|
||||||
|
void pauseSkill(Skill skill, boolean pause)
|
||||||
|
{
|
||||||
|
if (pause ? xpPauseState.pauseSkill(skill) : xpPauseState.unpauseSkill(skill))
|
||||||
|
{
|
||||||
|
xpPanel.updateSkillExperience(false, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Levi <me@levischuck.com>
|
||||||
|
* 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.ui.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class DimmableJPanel extends JPanel
|
||||||
|
{
|
||||||
|
// Dimming state, allows for restoring original colors before dimming
|
||||||
|
@Getter
|
||||||
|
private boolean dimmed = false;
|
||||||
|
private Color dimmedForeground = null;
|
||||||
|
private Color dimmedBackground = null;
|
||||||
|
private Color undimmedForeground = null;
|
||||||
|
private Color undimmedBackground = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setForeground(Color color)
|
||||||
|
{
|
||||||
|
undimmedForeground = color;
|
||||||
|
dimmedForeground = color.darker();
|
||||||
|
super.setForeground(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackground(Color color)
|
||||||
|
{
|
||||||
|
undimmedBackground = color;
|
||||||
|
dimmedBackground = color.darker();
|
||||||
|
super.setBackground(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getForeground()
|
||||||
|
{
|
||||||
|
return dimmed ? dimmedForeground : undimmedForeground;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color getBackground()
|
||||||
|
{
|
||||||
|
return dimmed ? dimmedBackground : undimmedBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dimming sets all parts of this component with darker colors except for the central label
|
||||||
|
* This is useful for showing that progress is paused
|
||||||
|
* Setting dim to false will restore the original colors from before the component was dimmed.
|
||||||
|
* @param dimmed
|
||||||
|
*/
|
||||||
|
public void setDimmed(boolean dimmed)
|
||||||
|
{
|
||||||
|
this.dimmed = dimmed;
|
||||||
|
|
||||||
|
if (dimmed)
|
||||||
|
{
|
||||||
|
super.setBackground(dimmedBackground);
|
||||||
|
super.setForeground(dimmedForeground);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
super.setBackground(undimmedBackground);
|
||||||
|
super.setForeground(undimmedForeground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,6 @@ import java.awt.Color;
|
|||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -39,7 +38,7 @@ import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
|||||||
/**
|
/**
|
||||||
* A progress bar to be displayed underneath the GE offer item panels
|
* A progress bar to be displayed underneath the GE offer item panels
|
||||||
*/
|
*/
|
||||||
public class ProgressBar extends JPanel
|
public class ProgressBar extends DimmableJPanel
|
||||||
{
|
{
|
||||||
@Setter
|
@Setter
|
||||||
private int maximumValue;
|
private int maximumValue;
|
||||||
@@ -50,11 +49,16 @@ public class ProgressBar extends JPanel
|
|||||||
private final JLabel leftLabel = new JShadowedLabel();
|
private final JLabel leftLabel = new JShadowedLabel();
|
||||||
private final JLabel rightLabel = new JShadowedLabel();
|
private final JLabel rightLabel = new JShadowedLabel();
|
||||||
private final JLabel centerLabel = new JShadowedLabel();
|
private final JLabel centerLabel = new JShadowedLabel();
|
||||||
|
private String centerLabelText = "";
|
||||||
|
private String dimmedText = "";
|
||||||
|
|
||||||
public ProgressBar()
|
public ProgressBar()
|
||||||
{
|
{
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
|
// The background color should be overridden
|
||||||
setBackground(Color.GREEN.darker());
|
setBackground(Color.GREEN.darker());
|
||||||
|
setForeground(Color.GREEN.brighter());
|
||||||
|
|
||||||
setPreferredSize(new Dimension(100, 16));
|
setPreferredSize(new Dimension(100, 16));
|
||||||
|
|
||||||
leftLabel.setFont(FontManager.getRunescapeSmallFont());
|
leftLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
@@ -70,10 +74,10 @@ public class ProgressBar extends JPanel
|
|||||||
centerLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
centerLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
centerLabel.setBorder(new EmptyBorder(2, 0, 0, 0));
|
centerLabel.setBorder(new EmptyBorder(2, 0, 0, 0));
|
||||||
|
|
||||||
|
// Adds components to be automatically redrawn when paintComponents is called
|
||||||
add(leftLabel, BorderLayout.WEST);
|
add(leftLabel, BorderLayout.WEST);
|
||||||
add(centerLabel, BorderLayout.CENTER);
|
add(centerLabel, BorderLayout.CENTER);
|
||||||
add(rightLabel, BorderLayout.EAST);
|
add(rightLabel, BorderLayout.EAST);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -88,20 +92,45 @@ public class ProgressBar extends JPanel
|
|||||||
super.paintComponents(g);
|
super.paintComponents(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDimmed(boolean dimmed)
|
||||||
|
{
|
||||||
|
super.setDimmed(dimmed);
|
||||||
|
|
||||||
|
if (dimmed)
|
||||||
|
{
|
||||||
|
leftLabel.setForeground(Color.GRAY);
|
||||||
|
rightLabel.setForeground(Color.GRAY);
|
||||||
|
centerLabel.setText(dimmedText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftLabel.setForeground(Color.WHITE);
|
||||||
|
rightLabel.setForeground(Color.WHITE);
|
||||||
|
centerLabel.setText(centerLabelText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setLeftLabel(String txt)
|
public void setLeftLabel(String txt)
|
||||||
{
|
{
|
||||||
this.leftLabel.setText(txt);
|
leftLabel.setText(txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRightLabel(String txt)
|
public void setRightLabel(String txt)
|
||||||
{
|
{
|
||||||
this.rightLabel.setText(txt);
|
rightLabel.setText(txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCenterLabel(String txt)
|
public void setCenterLabel(String txt)
|
||||||
{
|
{
|
||||||
this.centerLabel.setText(txt);
|
centerLabelText = txt;
|
||||||
|
centerLabel.setText(isDimmed() ? dimmedText : txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDimmedText(String txt)
|
||||||
|
{
|
||||||
|
dimmedText = txt;
|
||||||
|
centerLabel.setText(isDimmed() ? txt : centerLabelText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getPercentage()
|
public double getPercentage()
|
||||||
|
|||||||
Reference in New Issue
Block a user