diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java index 2b263406ab..98c956eab1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java @@ -27,7 +27,6 @@ package net.runelite.client.plugins.xptracker; import java.time.Duration; import java.time.Instant; -import java.time.LocalTime; import lombok.Data; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Experience; @@ -54,11 +53,6 @@ class XpStateSingle return toHourly(xpGained); } - int getXpSec() - { - return getXpHr() / 3600; - } - int getActionsHr() { return toHourly(actions); @@ -71,12 +65,21 @@ class XpStateSingle return 0; } + return (int) ((1.0 / (getTimeElapsedInSeconds() / 3600.0)) * value); + } + + private long getTimeElapsedInSeconds() + { + if (skillTimeStart == null) + { + return 0; + } + // If the skill started just now, we can divide by near zero, this results in odd behavior. // To prevent that, pretend the skill has been active for a minute (60 seconds) // This will create a lower estimate for the first minute, // but it isn't ridiculous like saying 2 billion XP per hour. - long timeElapsedInSeconds = Math.max(60, Duration.between(skillTimeStart, Instant.now()).getSeconds()); - return (int) ((1.0 / (timeElapsedInSeconds / 3600.0)) * value); + return Math.max(60, Duration.between(skillTimeStart, Instant.now()).getSeconds()); } int getXpRemaining() @@ -120,11 +123,43 @@ class XpStateSingle String getTimeTillLevel() { - if (getXpSec() > 0) + long seconds = getTimeElapsedInSeconds(); + + if (seconds <= 0 || xpGained <= 0) { - return LocalTime.MIN.plusSeconds( getXpRemaining() / getXpSec() ).toString(); + // Infinity symbol + return "\u221e"; } - return "\u221e"; + + // formula is xpRemaining / xpPerSecond + // xpPerSecond being xpGained / seconds + // This can be simplified so division is only done once and we can work in whole numbers! + long remainingSeconds = (getXpRemaining() * seconds) / xpGained; + + // Java 8 doesn't have good duration / period objects to represent spans of time that can be formatted + // Rather than importing another dependency like joda time (which is practically built into java 10) + // below will be a custom formatter that handles spans larger than 1 day + + long durationDays = remainingSeconds / (24 * 60 * 60); + long durationHours = (remainingSeconds % (24 * 60 * 60)) / (60 * 60); + long durationMinutes = (remainingSeconds % (60 * 60)) / 60; + long durationSeconds = remainingSeconds % 60; + + if (durationDays > 1) + { + return String.format("%d days %02d:%02d:%02d", durationDays, durationHours, durationMinutes, durationSeconds); + } + else if (durationDays == 1) + { + return String.format("1 day %02d:%02d:%02d", durationHours, durationMinutes, durationSeconds); + } + else if (durationHours > 0) + { + return String.format("%02d:%02d:%02d", durationHours, durationMinutes, durationSeconds); + } + + // Minutes and seconds will always be present + return String.format("%02d:%02d", durationMinutes, durationSeconds); }