Skip to content

Commit

Permalink
Add feature to pause skill timers on logout or after idle period
Browse files Browse the repository at this point in the history
  • Loading branch information
LeviSchuck authored and Adam- committed Jul 19, 2018
1 parent aa8d2e5 commit f0e35fc
Show file tree
Hide file tree
Showing 8 changed files with 395 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
@Slf4j
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;

@Getter(AccessLevel.PACKAGE)
Expand All @@ -74,6 +83,9 @@ class XpInfoBox extends JPanel
private final JLabel expHour = new JLabel();
private final JLabel expLeft = 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
{
Expand All @@ -98,12 +110,16 @@ class XpInfoBox extends JPanel
final JMenuItem resetOthers = new JMenuItem("Reset others");
resetOthers.addActionListener(e -> xpTrackerPlugin.resetOtherSkillState(skill));

// Create reset others menu
pauseSkill.addActionListener(e -> xpTrackerPlugin.pauseSkill(skill, !paused));

// Create popup menu
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
popupMenu.add(openXpTracker);
popupMenu.add(reset);
popupMenu.add(resetOthers);
popupMenu.add(pauseSkill);

JLabel skillIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(skill)));
skillIcon.setHorizontalAlignment(SwingConstants.CENTER);
Expand Down Expand Up @@ -138,6 +154,7 @@ class XpInfoBox extends JPanel
progressBar.setMaximumValue(100);
progressBar.setBackground(new Color(61, 56, 49));
progressBar.setForeground(SkillColor.values()[skill.ordinal()].getColor());
progressBar.setDimmedText("Paused");

progressWrapper.add(progressBar, BorderLayout.NORTH);

Expand All @@ -157,12 +174,12 @@ void reset()
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)
{
Expand All @@ -172,6 +189,8 @@ private void rebuildAsync(boolean updated, XpSnapshotSingle xpSnapshotSingle)
panel.revalidate();
}

paused = skillPaused;

// Update information labels
expGained.setText(htmlLabel("XP Gained: ", xpSnapshotSingle.getXpGainedInSession()));
expLeft.setText(htmlLabel("XP Left: ", xpSnapshotSingle.getXpRemainingToGoal()));
Expand All @@ -181,28 +200,42 @@ private void rebuildAsync(boolean updated, XpSnapshotSingle xpSnapshotSingle)
progressBar.setValue(xpSnapshotSingle.getSkillProgressToGoal());
progressBar.setCenterLabel(xpSnapshotSingle.getSkillProgressToGoal() + "%");
progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getStartLevel());
progressBar.setRightLabel("Lvl. " + (xpSnapshotSingle.getEndLevel()));
progressBar.setRightLabel("Lvl. " + xpSnapshotSingle.getEndLevel());

progressBar.setToolTipText("<html>"
+ xpSnapshotSingle.getActionsInSession() + " actions done"
+ "<br/>"
+ xpSnapshotSingle.getActionsPerHour() + " actions/hr"
+ "<br/>"
+ xpSnapshotSingle.getTimeTillGoal() + " till goal lvl"
+ "</html>");
progressBar.setToolTipText(String.format(
HTML_TOOL_TIP_TEMPLATE,
xpSnapshotSingle.getActionsInSession(),
xpSnapshotSingle.getActionsPerHour(),
xpSnapshotSingle.getTimeTillGoal()));

progressBar.setDimmed(skillPaused);

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()));
}

static String htmlLabel(String key, int value)
{
String valueStr = StackFormatter.quantityToRSDecimalStack(value);

return "<html><body style = 'color:" + SwingUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR) + "'>" + key + "<span style = 'color:white'>" + valueStr + "</span></body></html>";
return String.format(HTML_LABEL_TEMPLATE, SwingUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ void resetSkill(Skill skill)
}
}

void updateSkillExperience(boolean updated, Skill skill, XpSnapshotSingle xpSnapshotSingle)
void updateSkillExperience(boolean updated, boolean paused, Skill skill, XpSnapshotSingle xpSnapshotSingle)
{
final XpInfoBox xpInfoBox = infoBoxes.get(skill);

if (xpInfoBox != null)
{
xpInfoBox.update(updated, xpSnapshotSingle);
xpInfoBox.update(updated, paused, xpSnapshotSingle);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018, Levi <[email protected]>
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2018, Levi <[email protected]>
* 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ XpUpdateResult updateSkill(Skill skill, int currentXp, int goalStartXp, int goal
}
}

void tick(Skill skill, long delta)
{
getSkill(skill).tick(delta);
}

/**
* 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.
Expand Down
Loading

0 comments on commit f0e35fc

Please sign in to comment.