diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java new file mode 100644 index 0000000000..96cf62c6fc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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.timetracking; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.util.ImageUtil; + +class OverviewItemPanel extends JPanel +{ + private static final ImageIcon ARROW_RIGHT_ICON; + + private static final Color HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR; + + private final JLabel statusLabel; + + static + { + ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "/util/arrow_right.png")); + } + + OverviewItemPanel(ItemManager itemManager, TimeTrackingPanel pluginPanel, Tab tab, String title) + { + setBackground(ColorScheme.DARKER_GRAY_COLOR); + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(7, 7, 7, 7)); + + JLabel iconLabel = new JLabel(); + iconLabel.setMinimumSize(new Dimension(36, 32)); + itemManager.getImage(tab.getItemID()).addTo(iconLabel); + add(iconLabel, BorderLayout.WEST); + + JPanel textContainer = new JPanel(); + textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + textContainer.setLayout(new GridLayout(2, 1)); + textContainer.setBorder(new EmptyBorder(5, 7, 5, 7)); + + addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + pluginPanel.switchTab(tab); + setBackground(ColorScheme.DARKER_GRAY_COLOR); + textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + + @Override + public void mouseReleased(MouseEvent e) + { + setBackground(HOVER_COLOR); + textContainer.setBackground(HOVER_COLOR); + } + + @Override + public void mouseEntered(MouseEvent e) + { + setBackground(HOVER_COLOR); + textContainer.setBackground(HOVER_COLOR); + setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + @Override + public void mouseExited(MouseEvent e) + { + setBackground(ColorScheme.DARKER_GRAY_COLOR); + textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + JLabel titleLabel = new JLabel(title); + titleLabel.setForeground(Color.WHITE); + titleLabel.setFont(FontManager.getRunescapeSmallFont()); + + statusLabel = new JLabel(); + statusLabel.setForeground(Color.GRAY); + statusLabel.setFont(FontManager.getRunescapeSmallFont()); + + textContainer.add(titleLabel); + textContainer.add(statusLabel); + + add(textContainer, BorderLayout.CENTER); + + JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON); + add(arrowLabel, BorderLayout.EAST); + } + + void updateStatus(String text, Color color) + { + statusLabel.setText(text); + statusLabel.setForeground(color); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java new file mode 100644 index 0000000000..38bd105709 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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.timetracking; + +import java.awt.Color; +import java.awt.GridLayout; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.timetracking.clocks.ClockManager; +import net.runelite.client.plugins.timetracking.farming.FarmingTracker; +import net.runelite.client.plugins.timetracking.farming.PatchImplementation; +import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker; +import net.runelite.client.ui.ColorScheme; + +class OverviewTabPanel extends TabContentPanel +{ + private final TimeTrackingConfig config; + private final FarmingTracker farmingTracker; + private final BirdHouseTracker birdHouseTracker; + private final ClockManager clockManager; + + private final OverviewItemPanel timerOverview; + private final OverviewItemPanel stopwatchOverview; + private final Map farmingOverviews; + private final OverviewItemPanel birdHouseOverview; + + OverviewTabPanel(ItemManager itemManager, TimeTrackingConfig config, TimeTrackingPanel pluginPanel, + FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager) + { + this.config = config; + this.farmingTracker = farmingTracker; + this.birdHouseTracker = birdHouseTracker; + this.clockManager = clockManager; + + setLayout(new GridLayout(0, 1, 0, 8)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + timerOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Timers"); + add(timerOverview); + + stopwatchOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Stopwatches"); + add(stopwatchOverview); + + birdHouseOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.BIRD_HOUSE, "Bird Houses"); + add(birdHouseOverview); + + farmingOverviews = new LinkedHashMap<>(); + farmingOverviews.put(PatchImplementation.ALLOTMENT, new OverviewItemPanel(itemManager, pluginPanel, Tab.ALLOTMENT, "Allotment Patches")); + farmingOverviews.put(PatchImplementation.FLOWER, new OverviewItemPanel(itemManager, pluginPanel, Tab.FLOWER, "Flower Patches")); + farmingOverviews.put(PatchImplementation.HERB, new OverviewItemPanel(itemManager, pluginPanel, Tab.HERB, "Herb Patches")); + farmingOverviews.put(PatchImplementation.TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.TREE, "Tree Patches")); + farmingOverviews.put(PatchImplementation.FRUIT_TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.FRUIT_TREE, "Fruit Tree Patches")); + farmingOverviews.put(PatchImplementation.HOPS, new OverviewItemPanel(itemManager, pluginPanel, Tab.HOPS, "Hops Patches")); + farmingOverviews.put(PatchImplementation.BUSH, new OverviewItemPanel(itemManager, pluginPanel, Tab.BUSH, "Bush Patches")); + farmingOverviews.put(PatchImplementation.GRAPES, new OverviewItemPanel(itemManager, pluginPanel, Tab.GRAPE, "Grape Patches")); + + for (OverviewItemPanel panel : farmingOverviews.values()) + { + add(panel); + } + } + + @Override + public int getUpdateInterval() + { + return 50; // 10 seconds + } + + @Override + public void update() + { + final long timers = clockManager.getActiveTimerCount(); + final long stopwatches = clockManager.getActiveStopwatchCount(); + + if (timers == 0) + { + timerOverview.updateStatus("No active timers", Color.GRAY); + } + else + { + timerOverview.updateStatus(timers + " active timer" + (timers == 1 ? "" : "s"), ColorScheme.PROGRESS_COMPLETE_COLOR); + } + + if (stopwatches == 0) + { + stopwatchOverview.updateStatus("No active stopwatches", Color.GRAY); + } + else + { + stopwatchOverview.updateStatus(stopwatches + " active stopwatch" + (stopwatches == 1 ? "" : "es"), ColorScheme.PROGRESS_COMPLETE_COLOR); + } + + farmingOverviews.forEach((patchType, panel) -> updateItemPanel(panel, farmingTracker.getCompletionTime(patchType))); + updateItemPanel(birdHouseOverview, birdHouseTracker.getCompletionTime()); + } + + private void updateItemPanel(OverviewItemPanel panel, long completionTime) + { + long duration = completionTime - Instant.now().getEpochSecond(); + + if (completionTime < 0) + { + panel.updateStatus("Unknown", Color.GRAY); + } + else if (duration <= 0) + { + panel.updateStatus("Ready", ColorScheme.PROGRESS_COMPLETE_COLOR); + } + else + { + panel.updateStatus("Ready " + getFormattedEstimate(duration, config.estimateRelative()), Color.GRAY); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index 134083611e..4dfa94c8b9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -32,6 +32,7 @@ import net.runelite.api.ItemID; @Getter public enum Tab { + OVERVIEW("Overview", ItemID.OLD_NOTES), CLOCK("Timers & Stopwatches", ItemID.WATCH), BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE), ALLOTMENT("Allotment Patches", ItemID.CABBAGE), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java index cb58e0c4e7..47740a8b17 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -25,6 +25,10 @@ package net.runelite.client.plugins.timetracking; import java.awt.Dimension; +import java.time.LocalDateTime; +import java.time.format.TextStyle; +import java.time.temporal.ChronoUnit; +import java.util.Locale; import javax.swing.JPanel; public abstract class TabContentPanel extends JPanel @@ -43,4 +47,41 @@ public abstract class TabContentPanel extends JPanel { return super.getPreferredSize(); } + + protected static String getFormattedEstimate(long remainingSeconds, boolean useRelativeTime) + { + if (useRelativeTime) + { + StringBuilder sb = new StringBuilder("in "); + long duration = (remainingSeconds + 59) / 60; + long minutes = duration % 60; + long hours = (duration / 60) % 24; + long days = duration / (60 * 24); + if (days > 0) + { + sb.append(days).append("d "); + } + if (hours > 0) + { + sb.append(hours).append("h "); + } + if (minutes > 0) + { + sb.append(minutes).append("m "); + } + return sb.toString(); + } + else + { + StringBuilder sb = new StringBuilder(); + LocalDateTime endTime = LocalDateTime.now().plus(remainingSeconds, ChronoUnit.SECONDS); + LocalDateTime currentTime = LocalDateTime.now(); + if (endTime.getDayOfWeek() != currentTime.getDayOfWeek()) + { + sb.append(endTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" "); + } + sb.append(String.format("at %d:%02d", endTime.getHour(), endTime.getMinute())); + return sb.toString(); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java index aab4b02c19..81a19cb483 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -30,6 +30,8 @@ import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Image; import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; import javax.annotation.Nullable; import javax.swing.ImageIcon; import javax.swing.JPanel; @@ -55,6 +57,7 @@ class TimeTrackingPanel extends PluginPanel /* This is the panel the tabs' respective panels will be displayed on. */ private final JPanel display = new JPanel(); + private final Map uiTabs = new HashMap<>(); private final MaterialTabGroup tabGroup = new MaterialTabGroup(display); private boolean active; @@ -81,6 +84,7 @@ class TimeTrackingPanel extends PluginPanel add(tabGroup, BorderLayout.NORTH); add(display, BorderLayout.CENTER); + addTab(Tab.OVERVIEW, new OverviewTabPanel(itemManager, config, this, farmingTracker, birdHouseTracker, clockManager)); addTab(Tab.CLOCK, clockManager.getClockTabPanel()); addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel()); @@ -126,13 +130,20 @@ class TimeTrackingPanel extends PluginPanel return true; }); + uiTabs.put(tab, materialTab); tabGroup.addTab(materialTab); + if (config.activeTab() == tab) { tabGroup.select(materialTab); } } + void switchTab(Tab tab) + { + tabGroup.select(uiTabs.get(tab)); + } + /** * Gets the update interval of the active tab panel, in units of 200 milliseconds. */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 1eba76a304..d8b4da011f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -111,6 +111,7 @@ public class TimeTrackingPlugin extends Plugin clockManager.loadTimers(); clockManager.loadStopwatches(); birdHouseTracker.loadFromConfig(); + farmingTracker.loadCompletionTimes(); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); @@ -205,6 +206,7 @@ public class TimeTrackingPlugin extends Plugin public void onUsernameChanged(UsernameChanged e) { farmingTracker.migrateConfiguration(); + farmingTracker.loadCompletionTimes(); birdHouseTracker.loadFromConfig(); panel.update(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java index 5fc68fd375..374bc43ae6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -93,6 +93,16 @@ public class ClockManager SwingUtilities.invokeLater(clockTabPanel::rebuild); } + public long getActiveTimerCount() + { + return timers.stream().filter(Timer::isActive).count(); + } + + public long getActiveStopwatchCount() + { + return stopwatches.stream().filter(Stopwatch::isActive).count(); + } + /** * Checks if any timers have completed, and send notifications if required. */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 429ef56ef5..11c1e2dd0b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -29,12 +29,8 @@ import com.google.common.base.Strings; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.format.TextStyle; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.Set; import javax.swing.JLabel; import javax.swing.border.EmptyBorder; @@ -215,40 +211,9 @@ public class FarmingTabPanel extends TabContentPanel { panel.getEstimate().setText("Done"); } - else if (config.estimateRelative()) - { - int remaining = (int) (59 + doneEstimate - unixNow) / 60; - StringBuilder f = new StringBuilder(); - f.append("Done in "); - int min = remaining % 60; - int hours = (remaining / 60) % 24; - int days = remaining / (60 * 24); - if (days > 0) - { - f.append(days).append("d "); - } - if (hours > 0) - { - f.append(hours).append("h "); - } - if (min > 0) - { - f.append(min).append("m "); - } - panel.getEstimate().setText(f.toString()); - } else { - StringBuilder f = new StringBuilder(); - LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset()); - LocalDateTime ldtNow = LocalDateTime.now(); - f.append("Done "); - if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek()) - { - f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" "); - } - f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute())); - panel.getEstimate().setText(f.toString()); + panel.getEstimate().setText("Done " + getFormattedEstimate(doneEstimate - unixNow, config.estimateRelative())); } } else diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java index 7d4d89b2a8..d88357c180 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -27,6 +27,8 @@ package net.runelite.client.plugins.timetracking.farming; import com.google.inject.Inject; import com.google.inject.Singleton; import java.time.Instant; +import java.util.EnumMap; +import java.util.Map; import net.runelite.api.Client; import net.runelite.api.Varbits; import net.runelite.api.coords.WorldPoint; @@ -59,6 +61,15 @@ public class FarmingTracker } + /** + * The time at which all patches of a particular type will be ready to be harvested, + * or {@code -1} if we have no data about any patch of the given type. + * + * Each value is set to {@code 0} if all patches of that type have already completed + * when updating the value. + */ + private Map completionTimes = new EnumMap<>(PatchImplementation.class); + public FarmingTabPanel createTabPanel(Tab tab) { return new FarmingTabPanel(client, itemManager, configManager, config, farmingWorld.getTabs().get(tab)); @@ -88,9 +99,10 @@ public class FarmingTracker // timetracking...=: String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID(); long unixNow = Instant.now().getEpochSecond(); - for (Varbits varbit : region.getVarbits()) + for (FarmingPatch patch : region.getPatches()) { // Write the config value if it doesn't match what is current, or it is more than 5 minutes old + Varbits varbit = patch.getVarbit(); String key = Integer.toString(varbit.getId()); String strVarbit = Integer.toString(client.getVar(varbit)); String storedValue = configManager.getConfiguration(group, key); @@ -118,6 +130,7 @@ public class FarmingTracker String value = strVarbit + ":" + unixNow; configManager.setConfiguration(group, key, value); + updateCompletionTime(patch.getImplementation()); changed = true; } } @@ -125,6 +138,97 @@ public class FarmingTracker return changed; } + public void loadCompletionTimes() + { + completionTimes.clear(); + + for (PatchImplementation patchType : PatchImplementation.values()) + { + updateCompletionTime(patchType); + } + } + + /** + * Gets the overall completion time for the given patch type. + * @see #completionTimes + */ + public long getCompletionTime(PatchImplementation patchType) + { + Long completionTime = completionTimes.get(patchType); + return completionTime == null ? -1 : completionTime; + } + + /** + * Updates the overall completion time for the given patch type. + * @see #completionTimes + */ + private void updateCompletionTime(PatchImplementation patchType) + { + long maxCompletionTime = 0; + boolean allUnknown = true; + + for (FarmingPatch patch : farmingWorld.getPatchTypes().get(patchType)) + { + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); + String key = Integer.toString(patch.getVarbit().getId()); + String storedValue = configManager.getConfiguration(group, key); + long unixTime = 0; + int value = 0; + + if (storedValue != null) + { + String[] parts = storedValue.split(":"); + if (parts.length == 2) + { + try + { + value = Integer.parseInt(parts[0]); + unixTime = Long.parseLong(parts[1]); + } + catch (NumberFormatException e) + { + // ignored + } + } + } + + PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value); + if (state == null || state.getProduce().getItemID() < 0) + { + continue; // unknown state + } + + int tickrate = state.getTickRate() * 60; + int stage = state.getStage(); + int stages = state.getStages(); + + if (state.getProduce() != Produce.WEEDS && state.getProduce() != Produce.SCARECROW) + { + // update max duration if this patch takes longer to grow + if (tickrate > 0) + { + long tickTime = unixTime / tickrate; + long doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate; + maxCompletionTime = Math.max(maxCompletionTime, doneEstimate); + } + else if (state.getCropState() == CropState.GROWING && stage != stages - 1) + { + continue; // unknown state + } + } + + allUnknown = false; + } + + if (allUnknown) + { + completionTimes.put(patchType, -1L); + return; + } + + completionTimes.put(patchType, (maxCompletionTime <= Instant.now().getEpochSecond()) ? 0 : maxCompletionTime); + } + /** * Migrates configuration data from {@code "farmingTracker"} key to {@code "timetracking"} key. * This method should be removed after a reasonable amount of time. diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java index 7af327ec69..06f5aa8379 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java @@ -26,9 +26,12 @@ package net.runelite.client.plugins.timetracking.farming; import com.google.inject.Singleton; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -47,6 +50,9 @@ class FarmingWorld @Getter private Map> tabs = new HashMap<>(); + @Getter + private Map> patchTypes = new EnumMap<>(PatchImplementation.class); + private final Comparator tabSorter = Comparator .comparing(FarmingPatch::getImplementation) .thenComparing((FarmingPatch p) -> p.getRegion().getName()) @@ -243,6 +249,10 @@ class FarmingWorld tabs .computeIfAbsent(p.getImplementation().getTab(), k -> new TreeSet<>(tabSorter)) .add(p); + + patchTypes + .computeIfAbsent(p.getImplementation(), k -> new ArrayList<>()) + .add(p); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java index 3277938d5e..a43a9e6760 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java @@ -28,12 +28,8 @@ package net.runelite.client.plugins.timetracking.hunter; import java.awt.Color; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.format.TextStyle; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import net.runelite.api.ItemID; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.timetracking.TabContentPanel; @@ -130,48 +126,17 @@ public class BirdHouseTabPanel extends TabContentPanel panel.getEstimate().setText("Built"); break; case SEEDED: - long doneEstimate = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION; - if (doneEstimate < unixNow) + long remainingTime = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION - unixNow; + if (remainingTime <= 0) { panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION); panel.getProgress().setForeground(COMPLETED_COLOR); panel.getEstimate().setText("Done"); } - else if (config.estimateRelative()) - { - int remainingSeconds = (int) (59 + doneEstimate - unixNow); - int remaining = remainingSeconds / 60; - panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION - remainingSeconds); - StringBuilder f = new StringBuilder("Done in "); - int min = remaining % 60; - int hours = (remaining / 60) % 24; - int days = remaining / (60 * 24); - if (days > 0) - { - f.append(days).append("d "); - } - if (hours > 0) - { - f.append(hours).append("h "); - } - if (min > 0) - { - f.append(min).append("m "); - } - panel.getEstimate().setText(f.toString()); - } else { - StringBuilder f = new StringBuilder(); - LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset()); - LocalDateTime ldtNow = LocalDateTime.now(); - f.append("Done "); - if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek()) - { - f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" "); - } - f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute())); - panel.getEstimate().setText(f.toString()); + panel.getProgress().setValue((int) (BirdHouseTracker.BIRD_HOUSE_DURATION - remainingTime)); + panel.getEstimate().setText("Done " + getFormattedEstimate(remainingTime, config.estimateRelative())); } break; default: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java index 6604813681..bcc52c3349 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java @@ -78,6 +78,7 @@ public class BirdHouseTracker * This is set to {@code 0} if the bird houses have already completed * when updating it. */ + @Getter private long completionTime = -1; public BirdHouseTabPanel createBirdHouseTabPanel() diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png new file mode 100644 index 0000000000..92048f0c78 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png differ