Merge pull request #4114 from LeviSchuck/xp-pausing

Add pause skill timing feature to the XP Tracker
This commit is contained in:
Adam
2018-07-18 20:36:34 -04:00
committed by GitHub
10 changed files with 520 additions and 47 deletions

View File

@@ -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>";
} }
} }

View File

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

View File

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

View File

@@ -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
}
}

View File

@@ -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.

View File

@@ -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()

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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()