diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java index 0ab9260f7f..bb49ea6393 100644 --- a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java @@ -24,6 +24,7 @@ */ package net.runelite.http.api.ge; +import java.time.Instant; import lombok.Data; import net.runelite.http.api.worlds.WorldType; @@ -42,4 +43,6 @@ public class GrandExchangeTrade private int offer; private int slot; private WorldType worldType; -} + private int seq; + private Instant resetTime; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/WidgetNode.java b/runelite-api/src/main/java/net/runelite/api/WidgetNode.java index 65625a97fa..96d8c112e4 100644 --- a/runelite-api/src/main/java/net/runelite/api/WidgetNode.java +++ b/runelite-api/src/main/java/net/runelite/api/WidgetNode.java @@ -12,4 +12,9 @@ public interface WidgetNode extends Node * @see net.runelite.api.widgets.Widget */ int getId(); + + /** + * @see net.runelite.api.widgets.WidgetModalMode + */ + int getModalMode(); } diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetClosed.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetClosed.java new file mode 100644 index 0000000000..218fc9ba70 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetClosed.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 Abex + * 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.api.events; + +import lombok.Value; + +/** + * Posted when an interface is about to be closed + */ +@Value +public class WidgetClosed +{ + /** + * The ID of the interface that is closed + */ + private final int groupId; + + /** + * @see net.runelite.api.widgets.WidgetModalMode + */ + private final int modalMode; + + /** + * If the interface will be unloaded or if it will be immediately reloaded + */ + private final boolean unload; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetModalMode.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetModalMode.java new file mode 100644 index 0000000000..30a8d875ae --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetModalMode.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Abex + * 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.api.widgets; + +public class WidgetModalMode +{ + public static final int MODAL_NOCLICKTHROUGH = 0; + public static final int NON_MODAL = 1; + public static final int MODAL_CLICKTHROUGH = 3; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java index 2b515e5fb4..c996287728 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java @@ -32,6 +32,8 @@ import java.util.Map; import java.util.function.Function; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; +import net.runelite.api.Client; +import net.runelite.api.WidgetNode; import net.runelite.api.widgets.Widget; import net.runelite.client.callback.ClientThread; @@ -40,10 +42,13 @@ public class WidgetInfoTableModel extends AbstractTableModel @Inject private ClientThread clientThread; + @Inject + private Client client; + private static final int COL_FIELD = 0; private static final int COL_VALUE = 1; - private static final List fields = populateWidgetFields(); + private final List fields = populateWidgetFields(); private Widget widget = null; private Map values = null; @@ -132,7 +137,7 @@ public class WidgetInfoTableModel extends AbstractTableModel }); } - private static List populateWidgetFields() + private List populateWidgetFields() { List out = new ArrayList<>(); @@ -194,6 +199,15 @@ public class WidgetInfoTableModel extends AbstractTableModel out.add(new WidgetField<>("NoScrollThrough", Widget::getNoScrollThrough, Widget::setNoScrollThrough, Boolean.class)); out.add(new WidgetField<>("TargetVerb", Widget::getTargetVerb, Widget::setTargetVerb, String.class)); out.add(new WidgetField<>("DragParent", Widget::getDragParent)); + out.add(new WidgetField<>("ModalMode", w -> + { + WidgetNode attachment = client.getComponentTable().get(w.getParentId()); + if (attachment != null) + { + return attachment.getModalMode(); + } + return null; + })); return out; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index 240ea3b8d4..4e7886292e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -119,7 +119,9 @@ import org.apache.commons.text.similarity.FuzzyScore; @Slf4j public class GrandExchangePlugin extends Plugin { - private static final int GE_SLOTS = 8; + @VisibleForTesting + static final int GE_SLOTS = 8; + private static final int GE_LOGIN_BURST_WINDOW = 2; // ticks private static final int OFFER_CONTAINER_ITEM = 21; private static final int OFFER_DEFAULT_ITEM_ID = 6512; private static final String OSB_GE_TEXT = "
OSBuddy Actively traded price: "; @@ -141,6 +143,7 @@ public class GrandExchangePlugin extends Plugin private NavigationButton button; @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) private GrandExchangePanel panel; @Getter(AccessLevel.PACKAGE) @@ -189,7 +192,7 @@ public class GrandExchangePlugin extends Plugin @Inject private GrandExchangeClient grandExchangeClient; - private boolean loginBurstGeUpdates; + private int lastLoginTick; @Inject private OSBGrandExchangeClient osbGrandExchangeClient; @@ -198,6 +201,7 @@ public class GrandExchangePlugin extends Plugin private String machineUuid; private String lastUsername; + private int tradeSeq; /** * Logic from {@link org.apache.commons.text.similarity.FuzzyScore} @@ -314,6 +318,8 @@ public class GrandExchangePlugin extends Plugin osbItem = -1; osbGrandExchangeResult = null; + + lastLoginTick = -1; } @Override @@ -325,6 +331,7 @@ public class GrandExchangePlugin extends Plugin grandExchangeText = null; grandExchangeItem = null; lastUsername = machineUuid = null; + tradeSeq = 0; } @Subscribe @@ -370,26 +377,23 @@ public class GrandExchangePlugin extends Plugin if (offer.getState() == GrandExchangeOfferState.EMPTY && client.getGameState() != GameState.LOGGED_IN) { // Trades are cleared by the client during LOGIN_SCREEN/HOPPING/LOGGING_IN, ignore those so we don't - // zero and re-submit the trade on login as an update + // clear the offer config. return; } - log.debug("GE offer updated: state: {}, slot: {}, item: {}, qty: {}, login: {}", - offer.getState(), slot, offer.getItemId(), offer.getQuantitySold(), loginBurstGeUpdates); + log.debug("GE offer updated: state: {}, slot: {}, item: {}, qty: {}, lastLoginTick: {}", + offer.getState(), slot, offer.getItemId(), offer.getQuantitySold(), lastLoginTick); ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId()); boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot)); + updateLimitTimer(offer); + submitTrade(slot, offer); updateConfig(slot, offer); - - if (loginBurstGeUpdates && slot == GE_SLOTS - 1) // slots are sent sequentially on login; this is the last one - { - loginBurstGeUpdates = false; - } } @VisibleForTesting @@ -403,6 +407,7 @@ public class GrandExchangePlugin extends Plugin } SavedOffer savedOffer = getOffer(slot); + boolean login = client.getTickCount() <= lastLoginTick + GE_LOGIN_BURST_WINDOW; if (savedOffer == null && (state == GrandExchangeOfferState.BUYING || state == GrandExchangeOfferState.SELLING) && offer.getQuantitySold() == 0) { // new offer @@ -413,7 +418,9 @@ public class GrandExchangePlugin extends Plugin grandExchangeTrade.setOffer(offer.getPrice()); grandExchangeTrade.setSlot(slot); grandExchangeTrade.setWorldType(getGeWorldType()); - grandExchangeTrade.setLogin(loginBurstGeUpdates); + grandExchangeTrade.setLogin(login); + grandExchangeTrade.setSeq(tradeSeq++); + grandExchangeTrade.setResetTime(getLimitResetTime(offer.getItemId())); log.debug("Submitting new trade: {}", grandExchangeTrade); grandExchangeClient.submit(grandExchangeTrade); @@ -444,7 +451,9 @@ public class GrandExchangePlugin extends Plugin grandExchangeTrade.setOffer(offer.getPrice()); grandExchangeTrade.setSlot(slot); grandExchangeTrade.setWorldType(getGeWorldType()); - grandExchangeTrade.setLogin(loginBurstGeUpdates); + grandExchangeTrade.setLogin(login); + grandExchangeTrade.setSeq(tradeSeq++); + grandExchangeTrade.setResetTime(getLimitResetTime(offer.getItemId())); log.debug("Submitting cancelled: {}", grandExchangeTrade); grandExchangeClient.submit(grandExchangeTrade); @@ -469,7 +478,9 @@ public class GrandExchangePlugin extends Plugin grandExchangeTrade.setOffer(offer.getPrice()); grandExchangeTrade.setSlot(slot); grandExchangeTrade.setWorldType(getGeWorldType()); - grandExchangeTrade.setLogin(loginBurstGeUpdates); + grandExchangeTrade.setLogin(login); + grandExchangeTrade.setSeq(tradeSeq++); + grandExchangeTrade.setResetTime(getLimitResetTime(offer.getItemId())); log.debug("Submitting trade: {}", grandExchangeTrade); grandExchangeClient.submit(grandExchangeTrade); @@ -508,8 +519,6 @@ public class GrandExchangePlugin extends Plugin savedOffer.setSpent(offer.getSpent()); savedOffer.setState(offer.getState()); setOffer(slot, savedOffer); - - updateLimitTimer(offer); } } @@ -532,14 +541,19 @@ public class GrandExchangePlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { - if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN) + switch (gameStateChanged.getGameState()) { - panel.getOffersPanel().resetOffers(); - loginBurstGeUpdates = true; - } - else if (gameStateChanged.getGameState() == GameState.LOGGED_IN) - { - grandExchangeClient.setMachineId(getMachineUuid()); + case LOGIN_SCREEN: + panel.getOffersPanel().resetOffers(); + break; + case LOGGING_IN: + case HOPPING: + case CONNECTION_LOST: + lastLoginTick = client.getTickCount(); + break; + case LOGGED_IN: + grandExchangeClient.setMachineId(getMachineUuid()); + break; } } @@ -952,12 +966,14 @@ public class GrandExchangePlugin extends Plugin } hasher.putUnencodedChars(username); machineUuid = hasher.hash().toString(); + tradeSeq = 0; return machineUuid; } catch (SocketException ex) { log.debug("unable to generate machine id", ex); machineUuid = null; + tradeSeq = 0; return null; } } 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 dc9a44a0d1..9637f6524f 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 @@ -43,7 +43,8 @@ public enum Tab HOPS("Hops Patches", ItemID.BARLEY), BUSH("Bush Patches", ItemID.POISON_IVY_BERRIES), 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}; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index 494320517a..76ceb67bc6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -33,6 +33,8 @@ import net.runelite.client.config.Units; public interface TimeTrackingConfig extends Config { String CONFIG_GROUP = "timetracking"; + String FARM_TICK_OFFSET = "farmTickOffset"; + String FARM_TICK_OFFSET_PRECISION = "farmTickOffsetPrecision"; String AUTOWEED = "autoweed"; String BIRD_HOUSE = "birdhouse"; String BOTANIST = "botanist"; 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 9c99ed27cd..990324c93c 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 @@ -25,6 +25,8 @@ */ package net.runelite.client.plugins.timetracking; +import com.google.inject.Inject; +import com.google.inject.name.Named; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; @@ -38,16 +40,18 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; 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.plugins.timetracking.clocks.ClockManager; 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.hunter.BirdHouseTracker; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.components.materialtabs.MaterialTab; import net.runelite.client.ui.components.materialtabs.MaterialTabGroup; +import net.runelite.client.util.AsyncBufferedImage; class TimeTrackingPanel extends PluginPanel { @@ -64,9 +68,11 @@ class TimeTrackingPanel extends PluginPanel @Nullable private TabContentPanel activeTabPanel = null; - TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, - FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager, - FarmingContractManager farmingContractManager) + @Inject + TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, FarmingTracker farmingTracker, + BirdHouseTracker birdHouseTracker, ClockManager clockManager, + FarmingContractManager farmingContractManager, ConfigManager configManager, + @Named("developerMode") boolean developerMode) { super(false); @@ -93,6 +99,11 @@ class TimeTrackingPanel extends PluginPanel { addTab(tab, farmingTracker.createTabPanel(tab, farmingContractManager)); } + + if (developerMode) + { + addTab(Tab.TIME_OFFSET, new FarmingNextTickPanel(farmingTracker, config, configManager)); + } } private void addTab(Tab tab, TabContentPanel tabContentPanel) 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 55ff192afd..e77843bcdc 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 @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.timetracking; +import com.google.inject.Inject; import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.time.Instant; @@ -32,20 +33,21 @@ import java.time.temporal.ChronoUnit; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; -import net.runelite.client.events.ConfigChanged; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.CommandExecuted; import net.runelite.api.events.GameTick; +import net.runelite.api.events.WidgetClosed; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetModalMode; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.RuneScapeProfileChanged; -import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.CONFIG_GROUP; @@ -88,18 +90,15 @@ public class TimeTrackingPlugin extends Plugin @Inject private ClockManager clockManager; - @Inject - private ItemManager itemManager; - - @Inject - private TimeTrackingConfig config; - @Inject private InfoBoxManager infoBoxManager; @Inject private ScheduledExecutorService executorService; + @Inject + private ConfigManager configManager; + private ScheduledFuture panelUpdateFuture; private TimeTrackingPanel panel; @@ -109,6 +108,8 @@ public class TimeTrackingPlugin extends Plugin private WorldPoint lastTickLocation; private boolean lastTickPostLogin; + private int lastModalCloseTick = 0; + @Provides TimeTrackingConfig provideConfig(ConfigManager configManager) { @@ -125,7 +126,7 @@ public class TimeTrackingPlugin extends Plugin 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() .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 public void onGameTick(GameTick t) { @@ -206,7 +217,7 @@ public class TimeTrackingPlugin extends Plugin } boolean birdHouseDataChanged = birdHouseTracker.updateData(loc); - boolean farmingDataChanged = farmingTracker.updateData(loc); + boolean farmingDataChanged = farmingTracker.updateData(loc, client.getTickCount() - lastModalCloseTick); boolean farmingContractDataChanged = farmingContractManager.updateData(loc); if (birdHouseDataChanged || farmingDataChanged || farmingContractDataChanged) @@ -235,6 +246,15 @@ public class TimeTrackingPlugin extends Plugin farmingContractManager.setContract(null); } + @Subscribe + private void onWidgetClosed(WidgetClosed ev) + { + if (ev.getModalMode() != WidgetModalMode.NON_MODAL) + { + lastModalCloseTick = client.getTickCount(); + } + } + @Schedule(period = 10, unit = ChronoUnit.SECONDS) public void checkCompletion() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractManager.java index e2b411733f..99f8599a95 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingContractManager.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.timetracking.farming; +import com.google.inject.Singleton; import java.time.Instant; import java.util.regex.Matcher; 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.util.Text; +@Singleton public class FarmingContractManager { private static final int GUILDMASTER_JANE_NPC_ID = NullNpcID.NULL_8628; @@ -264,7 +266,7 @@ public class FarmingContractManager continue; } - if ((contract.requiresHealthCheck() && state == CropState.HARVESTABLE) + if ((contract.getPatchImplementation().isHealthCheckRequired() && state == CropState.HARVESTABLE) && !(hasEmptyPatch || hasDiseasedPatch || hasDeadPatch)) { summary = SummaryState.OCCUPIED; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingNextTickPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingNextTickPanel.java new file mode 100644 index 0000000000..100de2194d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingNextTickPanel.java @@ -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> 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 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 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); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java index c768d30c14..a759d2c31b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java @@ -54,4 +54,10 @@ public class FarmingRegion { return true; } + + @Override + public String toString() + { + return name; + } } 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 298e91a5ce..409ee85e1d 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,20 +27,24 @@ package net.runelite.client.plugins.timetracking.farming; import com.google.inject.Inject; import com.google.inject.Singleton; import java.time.Instant; +import java.util.Collection; import java.util.EnumMap; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Varbits; import net.runelite.api.coords.WorldPoint; import net.runelite.api.vars.Autoweed; +import net.runelite.api.widgets.WidgetModalMode; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.timetracking.SummaryState; import net.runelite.client.plugins.timetracking.Tab; import net.runelite.client.plugins.timetracking.TimeTrackingConfig; +@Slf4j @Singleton public class FarmingTracker { @@ -58,9 +62,11 @@ public class FarmingTracker */ private final Map completionTimes = new EnumMap<>(Tab.class); + private boolean newRegionLoaded; + private Collection lastRegions; + @Inject - private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, - TimeTrackingConfig config, FarmingWorld farmingWorld) + private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld) { this.client = client; this.itemManager = itemManager; @@ -69,7 +75,6 @@ public class FarmingTracker this.farmingWorld = farmingWorld; } - public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager 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. */ - public boolean updateData(WorldPoint location) + public boolean updateData(WorldPoint location, int timeSinceModalClose) { 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)); 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())) - { - if (!region.isInBounds(location)) - { - continue; - } + Collection newRegions = farmingWorld.getRegionsForLocation(location); + 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 // timetracking...=: long unixNow = Instant.now().getEpochSecond(); @@ -121,20 +137,62 @@ public class FarmingTracker if (storedValue != null) { String[] parts = storedValue.split(":"); - if (parts.length == 2 && parts[0].equals(strVarbit)) + if (parts.length == 2) { - long unixTime = 0; - try + if (parts[0].equals(strVarbit)) { - 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) { updateCompletionTime(); @@ -153,6 +215,47 @@ public class FarmingTracker 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 public PatchPrediction predictPatch(FarmingPatch patch) { @@ -203,8 +306,7 @@ public class FarmingTracker int stage = state.getStage(); int stages = state.getStages(); - int tickrate = state.getTickRate() * 60; - int farmingTickLength = 5 * 60; + int tickrate = state.getTickRate(); if (autoweed && state.getProduce() == Produce.WEEDS) { @@ -216,17 +318,16 @@ public class FarmingTracker if (botanist) { tickrate /= 5; - farmingTickLength /= 5; } long doneEstimate = 0; if (tickrate > 0) { - long tickNow = (unixNow + farmingTickLength) / tickrate; - long tickTime = (unixTime + farmingTickLength) / tickrate; - int delta = (int) (tickNow - tickTime); + long tickNow = getTickTime(tickrate, 0, unixNow); + long tickTime = getTickTime(tickrate, 0, unixTime); + int delta = (int) (tickNow - tickTime) / (tickrate * 60); - doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate + farmingTickLength; + doneEstimate = getTickTime(tickrate, stages - 1 - stage, tickTime); stage += delta; 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() { summaries.clear(); completionTimes.clear(); + lastRegions = null; updateCompletionTime(); } 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 9d9812b319..bbeab0a639 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 @@ -29,6 +29,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.inject.Singleton; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -36,6 +37,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.stream.Collectors; import lombok.Getter; import net.runelite.api.Varbits; import net.runelite.api.coords.WorldPoint; @@ -44,7 +46,7 @@ import net.runelite.client.plugins.timetracking.Tab; @Singleton class FarmingWorld { - @Getter + @SuppressWarnings("PMD.ImmutableField") private Multimap regions = HashMultimap.create(); @Getter @@ -64,11 +66,11 @@ class FarmingWorld // It may be worth it to add a specialization for these patches add(new FarmingRegion("Al Kharid", 13106, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.CACTUS) - )); + ), 13362, 13105); add(new FarmingRegion("Ardougne", 10290, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) - )); + ), 10546); add(new FarmingRegion("Ardougne", 10548, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), @@ -80,7 +82,7 @@ class FarmingWorld add(new FarmingRegion("Brimhaven", 11058, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) - )); + ), 11057); add(new FarmingRegion("Catherby", 11062, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), @@ -93,9 +95,10 @@ class FarmingWorld @Override public boolean isInBounds(WorldPoint loc) { - if (loc.getY() < 3456) + if (loc.getX() >= 2816 && loc.getY() < 3456) { - return loc.getX() <= 2840 && loc.getY() > 3440; + //Upstairs sends different varbits + return loc.getX() < 2840 && loc.getY() >= 3440 && loc.getPlane() == 0; } return true; } @@ -104,10 +107,11 @@ class FarmingWorld new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE) ) { + //The fruit tree patch is always sent when upstairs in 11317 @Override public boolean isInBounds(WorldPoint loc) { - return loc.getX() > 2840 || loc.getY() < 3440; + return loc.getX() >= 2840 || loc.getY() < 3440 || loc.getPlane() == 1; } }); @@ -121,7 +125,7 @@ class FarmingWorld add(new FarmingRegion("Entrana", 11060, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) - )); + ), 11316); add(new FarmingRegion("Etceteria", 10300, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH), @@ -130,7 +134,7 @@ class FarmingWorld add(new FarmingRegion("Falador", 11828, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) - )); + ), 12084); add(new FarmingRegion("Falador", 12083, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), @@ -142,7 +146,8 @@ class FarmingWorld @Override public boolean isInBounds(WorldPoint loc) { - return loc.getY() > 3280; + //Not on region boundary due to Port Sarim Spirit Tree patch + return loc.getY() >= 3272; } }); @@ -150,7 +155,30 @@ class FarmingWorld new FarmingPatch("East", Varbits.FARMING_4771, PatchImplementation.HARDWOOD_TREE), new FarmingPatch("Middle", Varbits.FARMING_4772, PatchImplementation.HARDWOOD_TREE), new FarmingPatch("West", Varbits.FARMING_4773, PatchImplementation.HARDWOOD_TREE) - ), 14907); + ) + { + @Override + public boolean isInBounds(WorldPoint loc) + { + //Hardwood tree varbits are sent anywhere on plane 0 of fossil island. + //Varbits get sent 1 tick earlier than expected when climbing certain ladders and stairs + + //Stairs to house on the hill + if (loc.getX() == 3753 && loc.getY() >= 3868 && loc.getY() <= 3870) + { + return false; + } + + //East and west ladders to rope bridge + if ((loc.getX() == 3729 || loc.getX() == 3728 || loc.getX() == 3747 || loc.getX() == 3746) + && loc.getY() <= 3832 && loc.getY() >= 3830) + { + return false; + } + + return loc.getPlane() == 0; + } + }, 14907, 14908, 15164, 14652, 14906, 14650, 15162, 15163); add(new FarmingRegion("Seaweed", 15008, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.SEAWEED), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.SEAWEED) @@ -159,7 +187,7 @@ class FarmingWorld add(new FarmingRegion("Gnome Stronghold", 9781, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.FRUIT_TREE) - )); + ), 9782, 9526, 9525); add(new FarmingRegion("Harmony", 15148, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), @@ -171,11 +199,9 @@ class FarmingWorld new FarmingPatch("South West", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB), - new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) - )); - add(new FarmingRegion("Kourend", 6711, + new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST), new FarmingPatch("", Varbits.FARMING_7904, PatchImplementation.SPIRIT_TREE) - )); + ), 6711); add(new FarmingRegion("Kourend", 7223, new FarmingPatch("East 1", Varbits.GRAPES_4953, PatchImplementation.GRAPES), new FarmingPatch("East 2", Varbits.GRAPES_4954, PatchImplementation.GRAPES), @@ -200,23 +226,29 @@ class FarmingWorld )); add(new FarmingRegion("Lumbridge", 12594, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) - )); + ), 12850); add(new FarmingRegion("Morytania", 13622, new FarmingPatch("Mushroom", Varbits.FARMING_4771, PatchImplementation.MUSHROOM) - )); + ), 13878); add(new FarmingRegion("Morytania", 14391, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB), new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) - )); - + ), 14390); add(new FarmingRegion("Port Sarim", 12082, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) - )); + ) + { + @Override + public boolean isInBounds(WorldPoint loc) + { + return loc.getY() < 3272; + } + }, 12083); add(new FarmingRegion("Rimmington", 11570, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) @@ -224,7 +256,7 @@ class FarmingWorld add(new FarmingRegion("Seers' Village", 10551, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) - )); + ), 10550); add(new FarmingRegion("Tai Bwo Wannai", 11056, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.CALQUAT) @@ -232,11 +264,11 @@ class FarmingWorld add(new FarmingRegion("Taverley", 11573, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) - )); + ), 11829); add(new FarmingRegion("Tree Gnome Village", 9777, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE) - )); + ), 10033); add(new FarmingRegion("Troll Stronghold", 11321, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HERB) @@ -258,6 +290,7 @@ class FarmingWorld new FarmingPatch("Hespori", Varbits.FARMING_7908, PatchImplementation.HESPORI) )); + //Full 3x3 region area centered on farming guild add(farmingGuildRegion = new FarmingRegion("Farming Guild", 4922, new FarmingPatch("", Varbits.FARMING_7905, PatchImplementation.TREE), new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.HERB), @@ -272,18 +305,20 @@ class FarmingWorld new FarmingPatch("Anima", Varbits.FARMING_7911, PatchImplementation.ANIMA), new FarmingPatch("", Varbits.FARMING_7910, PatchImplementation.CELASTRUS), new FarmingPatch("", Varbits.FARMING_7907, PatchImplementation.REDWOOD) - )); + ), 5177, 5178, 5179, 4921, 4923, 4665, 4666, 4667); + //All of Prifddinas, and all of Prifddinas Underground add(new FarmingRegion("Prifddinas", 13151, - new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), - new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), - new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), - new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.CRYSTAL_TREE), - new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.COMPOST) // TODO: Find correct varbit - )); + new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), + new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.CRYSTAL_TREE), + new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.COMPOST) // TODO: Find correct varbit + ), 12895, 12894, 13150, + /* Underground */ 12994, 12993, 12737, 12738, 12126, 12127, 13250); // Finalize - this.regions = Multimaps.unmodifiableMultimap(regions); + this.regions = Multimaps.unmodifiableMultimap(this.regions); Map> umtabs = new TreeMap<>(); for (Map.Entry> e : tabs.entrySet()) { @@ -306,4 +341,11 @@ class FarmingWorld .add(p); } } + + Collection getRegionsForLocation(WorldPoint location) + { + return this.regions.get(location.getRegionID()).stream() + .filter(region -> region.isInBounds(location)) + .collect(Collectors.toSet()); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java index c4af8c2596..1707f0281e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java @@ -33,7 +33,7 @@ import net.runelite.client.plugins.timetracking.Tab; @Getter public enum PatchImplementation { - BELLADONNA(Tab.SPECIAL, "") + BELLADONNA(Tab.SPECIAL, "", false) { @Override PatchState forVarbitValue(int value) @@ -71,7 +71,7 @@ public enum PatchImplementation return null; } }, - MUSHROOM(Tab.SPECIAL, "") + MUSHROOM(Tab.SPECIAL, "", false) { @Override PatchState forVarbitValue(int value) @@ -109,7 +109,7 @@ public enum PatchImplementation return null; } }, - HESPORI(Tab.SPECIAL, "") + HESPORI(Tab.SPECIAL, "", true) { @Override PatchState forVarbitValue(int value) @@ -137,7 +137,7 @@ public enum PatchImplementation return null; } }, - ALLOTMENT(Tab.ALLOTMENT, "") + ALLOTMENT(Tab.ALLOTMENT, "", false) { @Override PatchState forVarbitValue(int value) @@ -470,7 +470,7 @@ public enum PatchImplementation return null; } }, - HERB(Tab.HERB, "") + HERB(Tab.HERB, "", false) { @Override PatchState forVarbitValue(int value) @@ -738,7 +738,7 @@ public enum PatchImplementation return null; } }, - FLOWER(Tab.FLOWER, "") + FLOWER(Tab.FLOWER, "", false) { @Override PatchState forVarbitValue(int value) @@ -1011,7 +1011,7 @@ public enum PatchImplementation return null; } }, - BUSH(Tab.BUSH, "") + BUSH(Tab.BUSH, "", true) { @Override PatchState forVarbitValue(int value) @@ -1244,7 +1244,7 @@ public enum PatchImplementation return null; } }, - FRUIT_TREE(Tab.FRUIT_TREE, "") + FRUIT_TREE(Tab.FRUIT_TREE, "", true) { @Override PatchState forVarbitValue(int value) @@ -1527,7 +1527,7 @@ public enum PatchImplementation return null; } }, - HOPS(Tab.HOPS, "") + HOPS(Tab.HOPS, "", false) { @Override PatchState forVarbitValue(int value) @@ -1830,7 +1830,7 @@ public enum PatchImplementation return null; } }, - TREE(Tab.TREE, "") + TREE(Tab.TREE, "", true) { @Override PatchState forVarbitValue(int value) @@ -2113,7 +2113,7 @@ public enum PatchImplementation return null; } }, - HARDWOOD_TREE(Tab.TREE, "Hardwood Trees") + HARDWOOD_TREE(Tab.TREE, "Hardwood Trees", true) { @Override PatchState forVarbitValue(int value) @@ -2196,7 +2196,7 @@ public enum PatchImplementation return null; } }, - REDWOOD(Tab.TREE, "Redwood Trees") + REDWOOD(Tab.TREE, "Redwood Trees", true) { @Override PatchState forVarbitValue(int value) @@ -2244,7 +2244,7 @@ public enum PatchImplementation return null; } }, - SPIRIT_TREE(Tab.TREE, "Spirit Trees") + SPIRIT_TREE(Tab.TREE, "Spirit Trees", true) { @Override PatchState forVarbitValue(int value) @@ -2292,7 +2292,7 @@ public enum PatchImplementation return null; } }, - ANIMA(Tab.SPECIAL, "") + ANIMA(Tab.SPECIAL, "", false) { @Override PatchState forVarbitValue(int value) @@ -2339,7 +2339,7 @@ public enum PatchImplementation return null; } }, - CACTUS(Tab.SPECIAL, "Cactus") + CACTUS(Tab.SPECIAL, "Cactus", true) { @Override PatchState forVarbitValue(int value) @@ -2412,7 +2412,7 @@ public enum PatchImplementation return null; } }, - SEAWEED(Tab.SPECIAL, "Seaweed") + SEAWEED(Tab.SPECIAL, "Seaweed", false) { @Override PatchState forVarbitValue(int value) @@ -2450,7 +2450,7 @@ public enum PatchImplementation return null; } }, - CALQUAT(Tab.FRUIT_TREE, "Calquat") + CALQUAT(Tab.FRUIT_TREE, "Calquat", true) { @Override PatchState forVarbitValue(int value) @@ -2493,7 +2493,7 @@ public enum PatchImplementation return null; } }, - CELASTRUS(Tab.FRUIT_TREE, "Celastrus") + CELASTRUS(Tab.FRUIT_TREE, "Celastrus", true) { @Override PatchState forVarbitValue(int value) @@ -2551,7 +2551,7 @@ public enum PatchImplementation return null; } }, - GRAPES(Tab.GRAPE, "") + GRAPES(Tab.GRAPE, "", true) { @Override PatchState forVarbitValue(int value) @@ -2576,7 +2576,7 @@ public enum PatchImplementation return null; } }, - CRYSTAL_TREE(Tab.FRUIT_TREE, "Crystal Tree") + CRYSTAL_TREE(Tab.FRUIT_TREE, "Crystal Tree", true) { @Override PatchState forVarbitValue(int value) @@ -2604,7 +2604,7 @@ public enum PatchImplementation return null; } }, - COMPOST(Tab.SPECIAL, "Compost Bin") + COMPOST(Tab.SPECIAL, "Compost Bin", true) { @Override PatchState forVarbitValue(int value) @@ -2677,7 +2677,7 @@ public enum PatchImplementation return null; } }, - GIANT_COMPOST(Tab.SPECIAL, "Giant Compost Bin") + GIANT_COMPOST(Tab.SPECIAL, "Giant Compost Bin", true) { @Override PatchState forVarbitValue(int value) @@ -2787,4 +2787,6 @@ public enum PatchImplementation private final Tab tab; private final String name; + + private final boolean healthCheckRequired; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java index ae19316f54..033b358ad6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java @@ -65,13 +65,13 @@ public enum Produce POISON_IVY("Poison ivy", "Poison ivy berries", PatchImplementation.BUSH, ItemID.POISON_IVY_BERRIES, 20, 9, 20, 5), // Hop crops - BARLEY("Barley", ItemID.BARLEY, 10, 5, 0, 3), - HAMMERSTONE("Hammerstone", ItemID.HAMMERSTONE_HOPS, 10, 5, 0, 3), - ASGARNIAN("Asgarnian", ItemID.ASGARNIAN_HOPS, 10, 6, 0, 3), - JUTE("Jute", ItemID.JUTE_FIBRE, 10, 6, 0, 3), - YANILLIAN("Yanillian", ItemID.YANILLIAN_HOPS, 10, 7, 0, 3), - KRANDORIAN("Krandorian", ItemID.KRANDORIAN_HOPS, 10, 8, 0, 3), - WILDBLOOD("Wildblood", ItemID.WILDBLOOD_HOPS, 10, 9, 0, 3), + BARLEY("Barley", PatchImplementation.HOPS, ItemID.BARLEY, 10, 5, 0, 3), + HAMMERSTONE("Hammerstone", PatchImplementation.HOPS, ItemID.HAMMERSTONE_HOPS, 10, 5, 0, 3), + ASGARNIAN("Asgarnian", PatchImplementation.HOPS, ItemID.ASGARNIAN_HOPS, 10, 6, 0, 3), + JUTE("Jute", PatchImplementation.HOPS, ItemID.JUTE_FIBRE, 10, 6, 0, 3), + YANILLIAN("Yanillian", PatchImplementation.HOPS, ItemID.YANILLIAN_HOPS, 10, 7, 0, 3), + KRANDORIAN("Krandorian", PatchImplementation.HOPS, ItemID.KRANDORIAN_HOPS, 10, 8, 0, 3), + WILDBLOOD("Wildblood", PatchImplementation.HOPS, ItemID.WILDBLOOD_HOPS, 10, 9, 0, 3), // Herb crops GUAM("Guam", PatchImplementation.HERB, ItemID.GUAM_LEAF, 20, 5, 0, 3), @@ -113,25 +113,25 @@ public enum Produce POTATO_CACTUS("Potato cactus", "Potato cacti", PatchImplementation.CACTUS, ItemID.POTATO_CACTUS, 10, 8, 5, 7), // Hardwood - TEAK("Teak", ItemID.TEAK_LOGS, 560, 8), - MAHOGANY("Mahogany", ItemID.MAHOGANY_LOGS, 640, 9), + TEAK("Teak", PatchImplementation.HARDWOOD_TREE, ItemID.TEAK_LOGS, 560, 8), + MAHOGANY("Mahogany", PatchImplementation.HARDWOOD_TREE, ItemID.MAHOGANY_LOGS, 640, 9), // Anima - ATTAS("Attas", NullItemID.NULL_22940, 640, 9), - IASOR("Iasor", NullItemID.NULL_22939, 640, 9), - KRONOS("Kronos", NullItemID.NULL_22938, 640, 9), + ATTAS("Attas", PatchImplementation.ANIMA, NullItemID.NULL_22940, 640, 9), + IASOR("Iasor", PatchImplementation.ANIMA, NullItemID.NULL_22939, 640, 9), + KRONOS("Kronos", PatchImplementation.ANIMA, NullItemID.NULL_22938, 640, 9), // Special crops - SEAWEED("Seaweed", ItemID.GIANT_SEAWEED, 10, 5, 0, 4), - GRAPE("Grape", ItemID.GRAPES, 5, 8, 0, 5), - MUSHROOM("Mushroom", ItemID.MUSHROOM, 40, 7, 0, 7), - BELLADONNA("Belladonna", ItemID.CAVE_NIGHTSHADE, 80, 5), - CALQUAT("Calquat", ItemID.CALQUAT_FRUIT, 160, 9, 0, 7), - SPIRIT_TREE("Spirit tree", ItemID.SPIRIT_TREE, 320, 13), + SEAWEED("Seaweed", PatchImplementation.SEAWEED, ItemID.GIANT_SEAWEED, 10, 5, 0, 4), + GRAPE("Grape", PatchImplementation.GRAPES, ItemID.GRAPES, 5, 8, 0, 5), + MUSHROOM("Mushroom", PatchImplementation.MUSHROOM, ItemID.MUSHROOM, 40, 7, 0, 7), + BELLADONNA("Belladonna", PatchImplementation.BELLADONNA, ItemID.CAVE_NIGHTSHADE, 80, 5), + CALQUAT("Calquat", PatchImplementation.CALQUAT, ItemID.CALQUAT_FRUIT, 160, 9, 0, 7), + SPIRIT_TREE("Spirit tree", PatchImplementation.SPIRIT_TREE, ItemID.SPIRIT_TREE, 320, 13), CELASTRUS("Celastrus", "Celastrus tree", PatchImplementation.CELASTRUS, ItemID.BATTLESTAFF, 160, 6, 0, 4), REDWOOD("Redwood", "Redwood tree", PatchImplementation.REDWOOD, ItemID.REDWOOD_LOGS, 640, 11), - HESPORI("Hespori", NullItemID.NULL_23044, 640, 4, 0, 2), - CRYSTAL_TREE("Crystal tree", ItemID.CRYSTAL_SHARDS, 80, 7), + HESPORI("Hespori", PatchImplementation.HESPORI, NullItemID.NULL_23044, 640, 4, 0, 2), + CRYSTAL_TREE("Crystal tree", PatchImplementation.CRYSTAL_TREE, ItemID.CRYSTAL_SHARDS, 80, 7), // Compost bins EMPTY_COMPOST_BIN("Compost Bin", PatchImplementation.COMPOST, ItemID.COMPOST_BIN, 0, 1, 0, 0), // Dummy produce for the empty state @@ -204,20 +204,6 @@ public enum Produce this(name, name, null, itemID, tickrate, stages, 0, 1); } - boolean requiresHealthCheck() - { - switch (this.patchImplementation) - { - case BUSH: - case TREE: - case CACTUS: - case REDWOOD: - case CELASTRUS: - return true; - } - return false; - } - @Nullable static Produce getByItemID(int itemId) {