Add farming tick offset to time tracking plugin

This commit is contained in:
Morgan Lewis
2020-12-22 13:42:37 -07:00
parent 519968f568
commit d4e91c60cd
8 changed files with 321 additions and 42 deletions

View File

@@ -43,7 +43,8 @@ public enum Tab
HOPS("Hops Patches", ItemID.BARLEY), HOPS("Hops Patches", ItemID.BARLEY),
BUSH("Bush Patches", ItemID.POISON_IVY_BERRIES), BUSH("Bush Patches", ItemID.POISON_IVY_BERRIES),
GRAPE("Grape Patches", ItemID.GRAPES), GRAPE("Grape Patches", ItemID.GRAPES),
SPECIAL("Special Patches", ItemID.MUSHROOM); SPECIAL("Special Patches", ItemID.MUSHROOM),
TIME_OFFSET("Farming Tick Offset", ItemID.WATERING_CAN);
public static final Tab[] FARMING_TABS = {HERB, TREE, FRUIT_TREE, SPECIAL, FLOWER, ALLOTMENT, BUSH, GRAPE, HOPS}; public static final Tab[] FARMING_TABS = {HERB, TREE, FRUIT_TREE, SPECIAL, FLOWER, ALLOTMENT, BUSH, GRAPE, HOPS};

View File

@@ -33,6 +33,8 @@ import net.runelite.client.config.Units;
public interface TimeTrackingConfig extends Config public interface TimeTrackingConfig extends Config
{ {
String CONFIG_GROUP = "timetracking"; String CONFIG_GROUP = "timetracking";
String FARM_TICK_OFFSET = "farmTickOffset";
String FARM_TICK_OFFSET_PRECISION = "farmTickOffsetPrecision";
String AUTOWEED = "autoweed"; String AUTOWEED = "autoweed";
String BIRD_HOUSE = "birdhouse"; String BIRD_HOUSE = "birdhouse";
String BOTANIST = "botanist"; String BOTANIST = "botanist";

View File

@@ -25,6 +25,8 @@
*/ */
package net.runelite.client.plugins.timetracking; package net.runelite.client.plugins.timetracking;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.GridLayout; import java.awt.GridLayout;
@@ -38,16 +40,18 @@ import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import net.runelite.client.util.AsyncBufferedImage; import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.clocks.ClockManager; import net.runelite.client.plugins.timetracking.clocks.ClockManager;
import net.runelite.client.plugins.timetracking.farming.FarmingContractManager; import net.runelite.client.plugins.timetracking.farming.FarmingContractManager;
import net.runelite.client.plugins.timetracking.farming.FarmingNextTickPanel;
import net.runelite.client.plugins.timetracking.farming.FarmingTracker; import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker; import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.materialtabs.MaterialTab; import net.runelite.client.ui.components.materialtabs.MaterialTab;
import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; import net.runelite.client.ui.components.materialtabs.MaterialTabGroup;
import net.runelite.client.util.AsyncBufferedImage;
class TimeTrackingPanel extends PluginPanel class TimeTrackingPanel extends PluginPanel
{ {
@@ -64,9 +68,11 @@ class TimeTrackingPanel extends PluginPanel
@Nullable @Nullable
private TabContentPanel activeTabPanel = null; private TabContentPanel activeTabPanel = null;
TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, @Inject
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager, TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, FarmingTracker farmingTracker,
FarmingContractManager farmingContractManager) BirdHouseTracker birdHouseTracker, ClockManager clockManager,
FarmingContractManager farmingContractManager, ConfigManager configManager,
@Named("developerMode") boolean developerMode)
{ {
super(false); super(false);
@@ -93,6 +99,11 @@ class TimeTrackingPanel extends PluginPanel
{ {
addTab(tab, farmingTracker.createTabPanel(tab, farmingContractManager)); addTab(tab, farmingTracker.createTabPanel(tab, farmingContractManager));
} }
if (developerMode)
{
addTab(Tab.TIME_OFFSET, new FarmingNextTickPanel(farmingTracker, config, configManager));
}
} }
private void addTab(Tab tab, TabContentPanel tabContentPanel) private void addTab(Tab tab, TabContentPanel tabContentPanel)

View File

@@ -25,6 +25,7 @@
*/ */
package net.runelite.client.plugins.timetracking; package net.runelite.client.plugins.timetracking;
import com.google.inject.Inject;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.time.Instant; import java.time.Instant;
@@ -32,20 +33,21 @@ import java.time.temporal.ChronoUnit;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import net.runelite.api.ChatMessageType; import net.runelite.api.ChatMessageType;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.CommandExecuted;
import net.runelite.api.events.GameTick; import net.runelite.api.events.GameTick;
import net.runelite.api.events.WidgetClosed;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetModalMode;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.RuneScapeProfileChanged; import net.runelite.client.events.RuneScapeProfileChanged;
import net.runelite.client.game.ItemManager;
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.timetracking.TimeTrackingConfig.CONFIG_GROUP; import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.CONFIG_GROUP;
@@ -88,18 +90,15 @@ public class TimeTrackingPlugin extends Plugin
@Inject @Inject
private ClockManager clockManager; private ClockManager clockManager;
@Inject
private ItemManager itemManager;
@Inject
private TimeTrackingConfig config;
@Inject @Inject
private InfoBoxManager infoBoxManager; private InfoBoxManager infoBoxManager;
@Inject @Inject
private ScheduledExecutorService executorService; private ScheduledExecutorService executorService;
@Inject
private ConfigManager configManager;
private ScheduledFuture panelUpdateFuture; private ScheduledFuture panelUpdateFuture;
private TimeTrackingPanel panel; private TimeTrackingPanel panel;
@@ -109,6 +108,8 @@ public class TimeTrackingPlugin extends Plugin
private WorldPoint lastTickLocation; private WorldPoint lastTickLocation;
private boolean lastTickPostLogin; private boolean lastTickPostLogin;
private int lastModalCloseTick = 0;
@Provides @Provides
TimeTrackingConfig provideConfig(ConfigManager configManager) TimeTrackingConfig provideConfig(ConfigManager configManager)
{ {
@@ -125,7 +126,7 @@ public class TimeTrackingPlugin extends Plugin
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png");
panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager, farmingContractManager); panel = injector.getInstance(TimeTrackingPanel.class);
navButton = NavigationButton.builder() navButton = NavigationButton.builder()
.tooltip("Time Tracking") .tooltip("Time Tracking")
@@ -174,6 +175,16 @@ public class TimeTrackingPlugin extends Plugin
} }
} }
@Subscribe
public void onCommandExecuted(CommandExecuted commandExecuted)
{
if (commandExecuted.getCommand().equals("resetfarmtick"))
{
configManager.unsetRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION);
configManager.unsetRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET);
}
}
@Subscribe @Subscribe
public void onGameTick(GameTick t) public void onGameTick(GameTick t)
{ {
@@ -206,7 +217,7 @@ public class TimeTrackingPlugin extends Plugin
} }
boolean birdHouseDataChanged = birdHouseTracker.updateData(loc); boolean birdHouseDataChanged = birdHouseTracker.updateData(loc);
boolean farmingDataChanged = farmingTracker.updateData(loc); boolean farmingDataChanged = farmingTracker.updateData(loc, client.getTickCount() - lastModalCloseTick);
boolean farmingContractDataChanged = farmingContractManager.updateData(loc); boolean farmingContractDataChanged = farmingContractManager.updateData(loc);
if (birdHouseDataChanged || farmingDataChanged || farmingContractDataChanged) if (birdHouseDataChanged || farmingDataChanged || farmingContractDataChanged)
@@ -235,6 +246,15 @@ public class TimeTrackingPlugin extends Plugin
farmingContractManager.setContract(null); farmingContractManager.setContract(null);
} }
@Subscribe
private void onWidgetClosed(WidgetClosed ev)
{
if (ev.getModalMode() != WidgetModalMode.NON_MODAL)
{
lastModalCloseTick = client.getTickCount();
}
}
@Schedule(period = 10, unit = ChronoUnit.SECONDS) @Schedule(period = 10, unit = ChronoUnit.SECONDS)
public void checkCompletion() public void checkCompletion()
{ {

View File

@@ -25,6 +25,7 @@
*/ */
package net.runelite.client.plugins.timetracking.farming; package net.runelite.client.plugins.timetracking.farming;
import com.google.inject.Singleton;
import java.time.Instant; import java.time.Instant;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -47,6 +48,7 @@ import net.runelite.client.plugins.timetracking.TimeTrackingPlugin;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.Text; import net.runelite.client.util.Text;
@Singleton
public class FarmingContractManager public class FarmingContractManager
{ {
private static final int GUILDMASTER_JANE_NPC_ID = NullNpcID.NULL_8628; private static final int GUILDMASTER_JANE_NPC_ID = NullNpcID.NULL_8628;
@@ -264,7 +266,7 @@ public class FarmingContractManager
continue; continue;
} }
if ((contract.requiresHealthCheck() && state == CropState.HARVESTABLE) if ((contract.getPatchImplementation().isHealthCheckRequired() && state == CropState.HARVESTABLE)
&& !(hasEmptyPatch || hasDiseasedPatch || hasDeadPatch)) && !(hasEmptyPatch || hasDiseasedPatch || hasDeadPatch))
{ {
summary = SummaryState.OCCUPIED; summary = SummaryState.OCCUPIED;

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2020 Morgan Lewis
* 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.farming;
import com.google.inject.Inject;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JTextArea;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.timetracking.TabContentPanel;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
import net.runelite.client.plugins.timetracking.TimeablePanel;
import net.runelite.client.ui.ColorScheme;
public class FarmingNextTickPanel extends TabContentPanel
{
private final FarmingTracker farmingTracker;
private final TimeTrackingConfig config;
private final ConfigManager configManager;
private final List<TimeablePanel<Void>> patchPanels;
private final JTextArea infoTextArea;
@Inject
public FarmingNextTickPanel(
FarmingTracker farmingTracker,
TimeTrackingConfig config,
ConfigManager configManager
)
{
this.farmingTracker = farmingTracker;
this.config = config;
this.configManager = configManager;
this.patchPanels = new ArrayList<>();
setLayout(new GridBagLayout());
setBackground(ColorScheme.DARK_GRAY_COLOR);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
int[] times = {5, 10, 20, 40, 80, 160, 320, 640};
for (int time : times)
{
TimeablePanel<Void> panel = new TimeablePanel<>(null, time + " minute tick", time);
patchPanels.add(panel);
add(panel, c);
c.gridy++;
}
infoTextArea = new JTextArea();
add(infoTextArea, c);
c.gridy++;
}
@Override
public int getUpdateInterval()
{
return 50;
}
@Override
public void update()
{
long unixNow = Instant.now().getEpochSecond();
for (TimeablePanel<Void> panel : patchPanels)
{
int tickLength = panel.getProgress().getMaximumValue();
long nextTick = farmingTracker.getTickTime(tickLength, 1);
panel.getEstimate().setText(getFormattedEstimate(nextTick - unixNow, config.timeFormatMode()));
}
String offsetPrecisionMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION);
String offsetTimeMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET);
infoTextArea.setText("Offset precision:" + offsetPrecisionMins + "\nFarming tick offset: -" + offsetTimeMins);
}
}

View File

@@ -54,4 +54,10 @@ public class FarmingRegion
{ {
return true; return true;
} }
@Override
public String toString()
{
return name;
}
} }

View File

@@ -27,20 +27,24 @@ package net.runelite.client.plugins.timetracking.farming;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.Varbits; import net.runelite.api.Varbits;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.api.vars.Autoweed; import net.runelite.api.vars.Autoweed;
import net.runelite.api.widgets.WidgetModalMode;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.SummaryState; import net.runelite.client.plugins.timetracking.SummaryState;
import net.runelite.client.plugins.timetracking.Tab; import net.runelite.client.plugins.timetracking.Tab;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig; import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
@Slf4j
@Singleton @Singleton
public class FarmingTracker public class FarmingTracker
{ {
@@ -58,9 +62,11 @@ public class FarmingTracker
*/ */
private final Map<Tab, Long> completionTimes = new EnumMap<>(Tab.class); private final Map<Tab, Long> completionTimes = new EnumMap<>(Tab.class);
private boolean newRegionLoaded;
private Collection<FarmingRegion> lastRegions;
@Inject @Inject
private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld)
TimeTrackingConfig config, FarmingWorld farmingWorld)
{ {
this.client = client; this.client = client;
this.itemManager = itemManager; this.itemManager = itemManager;
@@ -69,7 +75,6 @@ public class FarmingTracker
this.farmingWorld = farmingWorld; this.farmingWorld = farmingWorld;
} }
public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager farmingContractManager) public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager farmingContractManager)
{ {
return new FarmingTabPanel(this, itemManager, config, farmingWorld.getTabs().get(tab), farmingContractManager); return new FarmingTabPanel(this, itemManager, config, farmingWorld.getTabs().get(tab), farmingContractManager);
@@ -78,10 +83,18 @@ public class FarmingTracker
/** /**
* Updates tracker data for the current region. Returns true if any data was changed. * Updates tracker data for the current region. Returns true if any data was changed.
*/ */
public boolean updateData(WorldPoint location) public boolean updateData(WorldPoint location, int timeSinceModalClose)
{ {
boolean changed = false; boolean changed = false;
//Varbits don't get sent when a modal widget is open so just return
if (client.getComponentTable().getNodes()
.stream()
.anyMatch(widgetNode -> widgetNode.getModalMode() != WidgetModalMode.NON_MODAL))
{
return false;
}
{ {
String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED)); String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED));
if (!autoweed.equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED))) if (!autoweed.equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED)))
@@ -100,13 +113,16 @@ public class FarmingTracker
} }
} }
for (FarmingRegion region : farmingWorld.getRegions().get(location.getRegionID())) Collection<FarmingRegion> newRegions = farmingWorld.getRegionsForLocation(location);
{
if (!region.isInBounds(location))
{
continue;
}
if (!newRegions.equals(lastRegions))
{
newRegionLoaded = true;
log.debug("New region loaded. {} at {} ticks", newRegions.toString(), client.getTickCount());
}
for (FarmingRegion region : newRegions)
{
// Write config with new varbits // Write config with new varbits
// timetracking.<rsprofile>.<regionID>.<VarbitID>=<varbitValue>:<unix time> // timetracking.<rsprofile>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
long unixNow = Instant.now().getEpochSecond(); long unixNow = Instant.now().getEpochSecond();
@@ -121,20 +137,62 @@ public class FarmingTracker
if (storedValue != null) if (storedValue != null)
{ {
String[] parts = storedValue.split(":"); String[] parts = storedValue.split(":");
if (parts.length == 2 && parts[0].equals(strVarbit)) if (parts.length == 2)
{ {
long unixTime = 0; if (parts[0].equals(strVarbit))
try
{ {
unixTime = Long.parseLong(parts[1]); long unixTime = 0;
try
{
unixTime = Long.parseLong(parts[1]);
}
catch (NumberFormatException e)
{
// ignored
}
if (unixTime + (5 * 60) > unixNow && unixNow + 30 > unixTime)
{
continue;
}
} }
catch (NumberFormatException e) else if (!newRegionLoaded && timeSinceModalClose > 1)
{ {
// ignored PatchState previousPatchState = patch.getImplementation().forVarbitValue(Integer.parseInt(parts[0]));
PatchState currentPatchState = patch.getImplementation().forVarbitValue(client.getVar(varbit));
if (previousPatchState == null || currentPatchState == null)
{
continue;
}
int patchTickRate = previousPatchState.getTickRate();
if (isObservedGrowthTick(previousPatchState, currentPatchState))
{
Integer storedOffsetPrecision = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, int.class);
Integer storedOffsetMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET, int.class);
int offsetMins = (int) Math.abs(((Instant.now().getEpochSecond() / 60) % patchTickRate) - patchTickRate);
log.debug("Observed an exact growth tick. Offset is: {} from a {} minute tick", offsetMins, patchTickRate);
if (storedOffsetMins != null && storedOffsetMins != 0 && offsetMins != storedOffsetMins % patchTickRate)
{
WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation();
log.error("Offset error! Observed new offset of {}, previous observed offset was {} ({}) Player Loc:{}", offsetMins, storedOffsetMins, storedOffsetMins % patchTickRate, playerLocation);
}
if (storedOffsetPrecision == null || patchTickRate >= storedOffsetPrecision)
{
log.debug("Found a longer growth tick {}, saving new offset", patchTickRate);
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, patchTickRate);
configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET, offsetMins);
}
}
} }
if (unixTime + (5 * 60) > unixNow && unixNow + 30 > unixTime) else
{ {
continue; log.debug("ignoring growth tick for offset calculation; newRegionLoaded={} timeSinceModalClose={}", newRegionLoaded, timeSinceModalClose);
} }
} }
} }
@@ -145,6 +203,10 @@ public class FarmingTracker
} }
} }
//Do one scan after loading a new region before possibly updating tick offsets
newRegionLoaded = false;
lastRegions = newRegions;
if (changed) if (changed)
{ {
updateCompletionTime(); updateCompletionTime();
@@ -153,6 +215,47 @@ public class FarmingTracker
return changed; return changed;
} }
private boolean isObservedGrowthTick(PatchState previous, PatchState current)
{
//Check the previous state so it will still calculate during the final growth tick
int patchTickRate = previous.getTickRate();
CropState previousCropState = previous.getCropState();
CropState currentCropState = current.getCropState();
Produce previousProduce = previous.getProduce();
//Ignore weeds growing or being cleared.
if (previousProduce == Produce.WEEDS || current.getProduce() == Produce.WEEDS
|| current.getProduce() != previousProduce
|| patchTickRate <= 0)
{
return false;
}
if (previousCropState == CropState.GROWING)
{
if ((currentCropState == CropState.GROWING && current.getStage() - previous.getStage() == 1)
|| currentCropState == CropState.DISEASED)
{
log.debug("Found GROWING -> GROWING or GROWING -> DISEASED");
return true;
}
if (currentCropState == CropState.HARVESTABLE && !previousProduce.getPatchImplementation().isHealthCheckRequired())
{
log.debug("Found GROWING -> HARVESTABLE");
return true;
}
}
if (previousCropState == CropState.DISEASED && currentCropState == CropState.DEAD)
{
log.debug("Found DISEASED -> DEAD");
return true;
}
return false;
}
@Nullable @Nullable
public PatchPrediction predictPatch(FarmingPatch patch) public PatchPrediction predictPatch(FarmingPatch patch)
{ {
@@ -203,8 +306,7 @@ public class FarmingTracker
int stage = state.getStage(); int stage = state.getStage();
int stages = state.getStages(); int stages = state.getStages();
int tickrate = state.getTickRate() * 60; int tickrate = state.getTickRate();
int farmingTickLength = 5 * 60;
if (autoweed && state.getProduce() == Produce.WEEDS) if (autoweed && state.getProduce() == Produce.WEEDS)
{ {
@@ -216,17 +318,16 @@ public class FarmingTracker
if (botanist) if (botanist)
{ {
tickrate /= 5; tickrate /= 5;
farmingTickLength /= 5;
} }
long doneEstimate = 0; long doneEstimate = 0;
if (tickrate > 0) if (tickrate > 0)
{ {
long tickNow = (unixNow + farmingTickLength) / tickrate; long tickNow = getTickTime(tickrate, 0, unixNow);
long tickTime = (unixTime + farmingTickLength) / tickrate; long tickTime = getTickTime(tickrate, 0, unixTime);
int delta = (int) (tickNow - tickTime); int delta = (int) (tickNow - tickTime) / (tickrate * 60);
doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate + farmingTickLength; doneEstimate = getTickTime(tickrate, stages - 1 - stage, tickTime);
stage += delta; stage += delta;
if (stage >= stages) if (stage >= stages)
@@ -244,10 +345,39 @@ public class FarmingTracker
); );
} }
public long getTickTime(int tickRate, int ticks)
{
return getTickTime(tickRate, ticks, Instant.now().getEpochSecond());
}
public long getTickTime(int tickRate, int ticks, long requestedTime)
{
Integer offsetPrecisionMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, int.class);
Integer offsetTimeMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET, int.class);
//All offsets are negative but are stored as positive
long calculatedOffsetTime = 0L;
if (offsetPrecisionMins != null && offsetTimeMins != null && (offsetPrecisionMins >= tickRate || offsetPrecisionMins >= 40))
{
calculatedOffsetTime = (offsetTimeMins % tickRate) * 60;
}
//Calculate "now" as +offset seconds in the future so we calculate the correct ticks
long unixNow = requestedTime + calculatedOffsetTime;
//The time that the tick requested will happen
long timeOfCurrentTick = (unixNow - (unixNow % (tickRate * 60)));
long timeOfGoalTick = timeOfCurrentTick + (ticks * tickRate * 60);
//Move ourselves back to real time
return timeOfGoalTick - calculatedOffsetTime;
}
public void loadCompletionTimes() public void loadCompletionTimes()
{ {
summaries.clear(); summaries.clear();
completionTimes.clear(); completionTimes.clear();
lastRegions = null;
updateCompletionTime(); updateCompletionTime();
} }