timetracking: add farming contract functionality

Co-authored-by: Koekkruimels <lifeinvader-dev@outlook.com>
This commit is contained in:
melkypie
2020-05-07 23:25:08 +03:00
committed by Adam
parent fb32b5842d
commit db3b32c093
13 changed files with 691 additions and 88 deletions

View File

@@ -31,6 +31,7 @@ import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.function.BooleanSupplier;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -47,7 +48,12 @@ class OverviewItemPanel extends JPanel
private static final Color HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR;
private final JPanel textContainer;
private final JLabel statusLabel;
private final JLabel arrowLabel;
private final BooleanSupplier isSelectable;
private boolean isHighlighted;
static
{
@@ -56,16 +62,23 @@ class OverviewItemPanel extends JPanel
OverviewItemPanel(ItemManager itemManager, TimeTrackingPanel pluginPanel, Tab tab, String title)
{
this(itemManager, () -> pluginPanel.switchTab(tab), () -> true, tab.getItemID(), title);
}
OverviewItemPanel(ItemManager itemManager, Runnable onTabSwitched, BooleanSupplier isSelectable, int iconItemID, String title)
{
this.isSelectable = isSelectable;
setBackground(ColorScheme.DARKER_GRAY_COLOR);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(7, 7, 7, 7));
JLabel iconLabel = new JLabel();
iconLabel.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
itemManager.getImage(tab.getItemID()).addTo(iconLabel);
itemManager.getImage(iconItemID).addTo(iconLabel);
add(iconLabel, BorderLayout.WEST);
JPanel textContainer = new JPanel();
textContainer = new JPanel();
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setLayout(new GridLayout(2, 1));
textContainer.setBorder(new EmptyBorder(5, 7, 5, 7));
@@ -75,32 +88,27 @@ class OverviewItemPanel extends JPanel
@Override
public void mousePressed(MouseEvent mouseEvent)
{
pluginPanel.switchTab(tab);
setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
onTabSwitched.run();
setHighlighted(false);
}
@Override
public void mouseReleased(MouseEvent e)
{
setBackground(HOVER_COLOR);
textContainer.setBackground(HOVER_COLOR);
setHighlighted(true);
}
@Override
public void mouseEntered(MouseEvent e)
{
setBackground(HOVER_COLOR);
textContainer.setBackground(HOVER_COLOR);
setCursor(new Cursor(Cursor.HAND_CURSOR));
setHighlighted(true);
}
@Override
public void mouseExited(MouseEvent e)
{
setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
setHighlighted(false);
}
});
@@ -117,7 +125,8 @@ class OverviewItemPanel extends JPanel
add(textContainer, BorderLayout.CENTER);
JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON);
arrowLabel = new JLabel(ARROW_RIGHT_ICON);
arrowLabel.setVisible(isSelectable.getAsBoolean());
add(arrowLabel, BorderLayout.EAST);
}
@@ -125,5 +134,26 @@ class OverviewItemPanel extends JPanel
{
statusLabel.setText(text);
statusLabel.setForeground(color);
arrowLabel.setVisible(isSelectable.getAsBoolean());
if (isHighlighted && !isSelectable.getAsBoolean())
{
setHighlighted(false);
}
}
private void setHighlighted(boolean highlighted)
{
if (highlighted && !isSelectable.getAsBoolean())
{
return;
}
setBackground(highlighted ? HOVER_COLOR : ColorScheme.DARKER_GRAY_COLOR);
setCursor(new Cursor(highlighted && getMousePosition(true) != null ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR));
textContainer.setBackground(highlighted ? HOVER_COLOR : ColorScheme.DARKER_GRAY_COLOR);
isHighlighted = highlighted;
}
}

View File

@@ -31,8 +31,11 @@ import java.time.Instant;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import net.runelite.api.ItemID;
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.FarmingTracker;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
import net.runelite.client.ui.ColorScheme;
@@ -43,19 +46,23 @@ class OverviewTabPanel extends TabContentPanel
private final FarmingTracker farmingTracker;
private final BirdHouseTracker birdHouseTracker;
private final ClockManager clockManager;
private final FarmingContractManager farmingContractManager;
private final OverviewItemPanel timerOverview;
private final OverviewItemPanel stopwatchOverview;
private final Map<Tab, OverviewItemPanel> farmingOverviews;
private final OverviewItemPanel birdHouseOverview;
private final OverviewItemPanel farmingContractOverview;
OverviewTabPanel(ItemManager itemManager, TimeTrackingConfig config, TimeTrackingPanel pluginPanel,
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager)
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager,
FarmingContractManager farmingContractManager)
{
this.config = config;
this.farmingTracker = farmingTracker;
this.birdHouseTracker = birdHouseTracker;
this.clockManager = clockManager;
this.farmingContractManager = farmingContractManager;
setLayout(new GridLayout(0, 1, 0, 8));
setBackground(ColorScheme.DARK_GRAY_COLOR);
@@ -80,6 +87,10 @@ class OverviewTabPanel extends TabContentPanel
return p;
}
));
farmingContractOverview = new OverviewItemPanel(itemManager, () -> pluginPanel.switchTab(farmingContractManager.getContractTab()),
farmingContractManager::hasContract, ItemID.SEED_PACK, "Farming Contract");
add(farmingContractOverview);
}
@Override
@@ -113,12 +124,14 @@ class OverviewTabPanel extends TabContentPanel
}
farmingOverviews.forEach((patchType, panel) ->
updateItemPanel(panel, farmingTracker.getSummary(patchType), farmingTracker.getCompletionTime(patchType)));
updateItemPanel(panel, farmingTracker.getSummary(patchType), farmingTracker.getCompletionTime(patchType), null));
updateItemPanel(birdHouseOverview, birdHouseTracker.getSummary(), birdHouseTracker.getCompletionTime());
updateItemPanel(birdHouseOverview, birdHouseTracker.getSummary(), birdHouseTracker.getCompletionTime(), null);
updateItemPanel(farmingContractOverview, farmingContractManager.getSummary(), farmingContractManager.getCompletionTime(),
farmingContractManager.getContractName());
}
private void updateItemPanel(OverviewItemPanel panel, SummaryState summary, long completionTime)
private void updateItemPanel(OverviewItemPanel panel, SummaryState summary, long completionTime, @Nullable String farmingContract)
{
switch (summary)
{
@@ -135,11 +148,13 @@ class OverviewTabPanel extends TabContentPanel
{
panel.updateStatus("Ready " + getFormattedEstimate(duration, config.estimateRelative()), Color.GRAY);
}
break;
}
case EMPTY:
panel.updateStatus("Empty", Color.GRAY);
panel.updateStatus(farmingContract == null ? "Empty" : farmingContract, Color.GRAY);
break;
case OCCUPIED:
panel.updateStatus(farmingContract == null ? "" : farmingContract, Color.RED);
break;
case UNKNOWN:
default:

View File

@@ -28,6 +28,7 @@ public enum SummaryState
{
COMPLETED,
IN_PROGRESS,
OCCUPIED,
EMPTY,
UNKNOWN
}

View File

@@ -48,7 +48,7 @@ public abstract class TabContentPanel extends JPanel
return super.getPreferredSize();
}
protected static String getFormattedEstimate(long remainingSeconds, boolean useRelativeTime)
public static String getFormattedEstimate(long remainingSeconds, boolean useRelativeTime)
{
if (useRelativeTime)
{

View File

@@ -41,6 +41,7 @@ import javax.swing.border.EmptyBorder;
import net.runelite.client.util.AsyncBufferedImage;
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.FarmingTracker;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
import net.runelite.client.ui.ColorScheme;
@@ -64,7 +65,8 @@ class TimeTrackingPanel extends PluginPanel
private TabContentPanel activeTabPanel = null;
TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config,
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager)
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager,
FarmingContractManager farmingContractManager)
{
super(false);
@@ -82,13 +84,14 @@ 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.OVERVIEW, new OverviewTabPanel(itemManager, config, this, farmingTracker, birdHouseTracker, clockManager,
farmingContractManager));
addTab(Tab.CLOCK, clockManager.getClockTabPanel());
addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel());
for (Tab tab : Tab.FARMING_TABS)
{
addTab(tab, farmingTracker.createTabPanel(tab));
addTab(tab, farmingTracker.createTabPanel(tab, farmingContractManager));
}
}

View File

@@ -33,10 +33,12 @@ 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.GameTick;
import net.runelite.api.events.UsernameChanged;
import net.runelite.api.widgets.Widget;
@@ -50,11 +52,13 @@ import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.CONFIG
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.STOPWATCHES;
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.TIMERS;
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
import net.runelite.client.plugins.timetracking.farming.FarmingContractManager;
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
import net.runelite.client.task.Schedule;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.ImageUtil;
@PluginDescriptor(
@@ -64,6 +68,8 @@ import net.runelite.client.util.ImageUtil;
)
public class TimeTrackingPlugin extends Plugin
{
private static final String CONTRACT_COMPLETED = "You've completed a Farming Guild Contract. You should return to Guildmaster Jane.";
@Inject
private ClientToolbar clientToolbar;
@@ -76,6 +82,9 @@ public class TimeTrackingPlugin extends Plugin
@Inject
private BirdHouseTracker birdHouseTracker;
@Inject
private FarmingContractManager farmingContractManager;
@Inject
private ClockManager clockManager;
@@ -85,6 +94,9 @@ public class TimeTrackingPlugin extends Plugin
@Inject
private TimeTrackingConfig config;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ScheduledExecutorService executorService;
@@ -113,7 +125,7 @@ public class TimeTrackingPlugin extends Plugin
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png");
panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager);
panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager, farmingContractManager);
navButton = NavigationButton.builder()
.tooltip("Time Tracking")
@@ -140,6 +152,8 @@ public class TimeTrackingPlugin extends Plugin
}
clientToolbar.removeNavigation(navButton);
infoBoxManager.removeInfoBox(farmingContractManager.getInfoBox());
farmingContractManager.setInfoBox(null);
}
@Subscribe
@@ -193,8 +207,9 @@ public class TimeTrackingPlugin extends Plugin
boolean birdHouseDataChanged = birdHouseTracker.updateData(loc);
boolean farmingDataChanged = farmingTracker.updateData(loc);
boolean farmingContractDataChanged = farmingContractManager.updateData(loc);
if (birdHouseDataChanged || farmingDataChanged)
if (birdHouseDataChanged || farmingDataChanged || farmingContractDataChanged)
{
panel.update();
}
@@ -205,9 +220,21 @@ public class TimeTrackingPlugin extends Plugin
{
farmingTracker.loadCompletionTimes();
birdHouseTracker.loadFromConfig();
farmingContractManager.loadContractFromConfig();
panel.update();
}
@Subscribe
public void onChatMessage(ChatMessage event)
{
if (event.getType() != ChatMessageType.GAMEMESSAGE || !event.getMessage().equals(CONTRACT_COMPLETED))
{
return;
}
farmingContractManager.setContract(null);
}
@Schedule(period = 10, unit = ChronoUnit.SECONDS)
public void checkCompletion()
{

View File

@@ -44,8 +44,10 @@ public class TimeablePanel<T> extends JPanel
{
private final T timeable;
private final JLabel icon = new JLabel();
private final JLabel farmingContractIcon = new JLabel();
private final JLabel estimate = new JLabel();
private final ThinProgressBar progress = new ThinProgressBar();
private final JLabel text;
public TimeablePanel(T timeable, String title, int maximumProgressValue)
{
@@ -60,23 +62,25 @@ public class TimeablePanel<T> extends JPanel
topContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
icon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
farmingContractIcon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
JPanel infoPanel = new JPanel();
infoPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
infoPanel.setLayout(new GridLayout(2, 1));
infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0));
final JLabel location = new JShadowedLabel(title);
location.setFont(FontManager.getRunescapeSmallFont());
location.setForeground(Color.WHITE);
text = new JShadowedLabel(title);
text.setFont(FontManager.getRunescapeSmallFont());
text.setForeground(Color.WHITE);
estimate.setFont(FontManager.getRunescapeSmallFont());
estimate.setForeground(Color.GRAY);
infoPanel.add(location);
infoPanel.add(text);
infoPanel.add(estimate);
topContainer.add(icon, BorderLayout.WEST);
topContainer.add(farmingContractIcon, BorderLayout.EAST);
topContainer.add(infoPanel, BorderLayout.CENTER);
progress.setValue(0);

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2019, Koekkruimels <https://github.com/koekkruimels>
* Copyright (c) 2020, melky <https://github.com/melkypie>
* 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 java.awt.Color;
import java.awt.image.BufferedImage;
import java.time.Instant;
import lombok.Getter;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.timetracking.SummaryState;
import net.runelite.client.plugins.timetracking.TabContentPanel;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.overlay.infobox.InfoBox;
import net.runelite.client.util.ColorUtil;
class FarmingContractInfoBox extends InfoBox
{
@Getter
private final Produce contract;
private final FarmingContractManager manager;
private final TimeTrackingConfig config;
FarmingContractInfoBox(BufferedImage image, Plugin plugin, Produce contract, TimeTrackingConfig config, FarmingContractManager manager)
{
super(image, plugin);
this.contract = contract;
this.config = config;
this.manager = manager;
}
@Override
public String getText()
{
return null;
}
@Override
public Color getTextColor()
{
return null;
}
@Override
public String getTooltip()
{
SummaryState summary = manager.getSummary();
Color contractColor;
String contractDescription;
switch (summary)
{
case COMPLETED:
contractDescription = "Ready";
contractColor = ColorScheme.PROGRESS_COMPLETE_COLOR;
break;
case OCCUPIED:
contractDescription = "Occupied";
contractColor = ColorScheme.PROGRESS_ERROR_COLOR;
break;
case IN_PROGRESS:
contractDescription = "Ready " + TabContentPanel.getFormattedEstimate(manager.getCompletionTime() - Instant.now().getEpochSecond(),
config.estimateRelative());
contractColor = Color.GRAY;
break;
case EMPTY:
case UNKNOWN:
default:
contractDescription = null;
contractColor = Color.GRAY;
break;
}
StringBuilder sb = new StringBuilder();
sb.append(ColorUtil.wrapWithColorTag("Farming Contract", Color.WHITE));
sb.append("</br>");
sb.append(ColorUtil.wrapWithColorTag(contract.getName(), contractColor));
if (contractDescription != null)
{
sb.append("</br>");
sb.append(ColorUtil.wrapWithColorTag(contractDescription, contractColor));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,326 @@
/*
* Copyright (c) 2019, Koekkruimels <https://github.com/koekkruimels>
* Copyright (c) 2020, melky <https://github.com/melkypie>
* 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 java.time.Instant;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.NullNpcID;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
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;
import net.runelite.client.plugins.timetracking.TimeTrackingPlugin;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.Text;
public class FarmingContractManager
{
private static final int GUILDMASTER_JANE_NPC_ID = NullNpcID.NULL_8628;
private static final int FARMING_GUILD_REGION_ID = 4922;
private static final Pattern CONTRACT_ASSIGN_PATTERN = Pattern.compile("(?:We need you to grow|Please could you grow) (?:some|a|an) ([a-zA-Z ]+)(?: for us\\?|\\.)");
private static final String CONTRACT_REWARDED = "You'll be wanting a reward then. Here you go.";
private static final String CONFIG_KEY_CONTRACT = "contract";
@Getter
private SummaryState summary = SummaryState.UNKNOWN;
@Inject
private Client client;
@Inject
private ItemManager itemManager;
@Inject
private TimeTrackingConfig config;
@Inject
private TimeTrackingPlugin plugin;
@Inject
private FarmingWorld farmingWorld;
@Inject
private FarmingTracker farmingTracker;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ConfigManager configManager;
@Getter
private Produce contract = null;
@Getter
@Setter
private FarmingContractInfoBox infoBox;
@Getter
private long completionTime;
public void setContract(@Nullable Produce contract)
{
this.contract = contract;
setStoredContract(contract);
handleContractState();
}
public boolean hasContract()
{
return contract != null;
}
@Nullable
public Tab getContractTab()
{
return hasContract() ? contract.getPatchImplementation().getTab() : null;
}
@Nullable
public String getContractName()
{
return hasContract() ? contract.getContractName() : null;
}
public boolean shouldHighlightFarmingTabPanel(@Nonnull FarmingPatch patch)
{
PatchPrediction patchPrediction = farmingTracker.predictPatch(patch);
if (contract != null &&
patch.getRegion().getRegionID() == FARMING_GUILD_REGION_ID &&
contract.getPatchImplementation() == patch.getImplementation() &&
patchPrediction != null &&
(summary == SummaryState.EMPTY &&
(patchPrediction.getProduce() == null || patchPrediction.getProduce() == Produce.WEEDS)
|| patchPrediction.getProduce().equals(contract)))
{
return true;
}
return false;
}
public void loadContractFromConfig()
{
contract = getStoredContract();
handleContractState();
}
public boolean updateData(WorldPoint loc)
{
SummaryState oldSummary = summary;
handleContractState();
if (loc.getRegionID() == FARMING_GUILD_REGION_ID)
{
handleGuildmasterJaneWidgetDialog();
handleInfoBox();
}
else
{
if (infoBox != null)
{
infoBoxManager.removeInfoBox(infoBox);
infoBox = null;
}
}
return oldSummary != summary;
}
private void handleInfoBox()
{
if (infoBox != null)
{
if (contract == null)
{
infoBoxManager.removeInfoBox(infoBox);
infoBox = null;
}
else if (infoBox.getContract() != contract)
{
infoBoxManager.removeInfoBox(infoBox);
infoBox = new FarmingContractInfoBox(itemManager.getImage(contract.getItemID()), plugin, contract, config, this);
infoBoxManager.addInfoBox(infoBox);
}
}
else if (contract != null)
{
infoBox = new FarmingContractInfoBox(itemManager.getImage(contract.getItemID()), plugin, contract, config, this);
infoBoxManager.addInfoBox(infoBox);
}
}
private void handleGuildmasterJaneWidgetDialog()
{
Widget npcDialog = client.getWidget(WidgetInfo.DIALOG_NPC_HEAD_MODEL);
if (npcDialog == null || npcDialog.getModelId() != GUILDMASTER_JANE_NPC_ID)
{
return;
}
String dialogText = Text.removeTags(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT).getText());
if (dialogText.equals(CONTRACT_REWARDED))
{
setContract(null);
}
Matcher matcher = CONTRACT_ASSIGN_PATTERN.matcher(dialogText);
if (!matcher.find())
{
return;
}
String name = matcher.group(1);
Produce farmingContract = Produce.getByContractName(name);
if (farmingContract == null)
{
return;
}
Produce currentFarmingContract = contract;
if (farmingContract == currentFarmingContract)
{
return;
}
setContract(farmingContract);
}
private void handleContractState()
{
if (contract == null)
{
summary = SummaryState.UNKNOWN;
return;
}
PatchImplementation patchImplementation = contract.getPatchImplementation();
boolean hasEmptyPatch = false;
completionTime = Long.MAX_VALUE;
for (FarmingPatch patch : farmingWorld.getFarmingGuildRegion().getPatches())
{
if (patch.getImplementation() != patchImplementation)
{
continue;
}
PatchPrediction prediction = farmingTracker.predictPatch(patch);
if (prediction == null)
{
continue;
}
Produce produce = prediction.getProduce();
if (completionTime == Long.MAX_VALUE)
{
if (produce == null || produce == Produce.WEEDS)
{
summary = SummaryState.EMPTY;
hasEmptyPatch = true;
continue;
}
if ((contract.requiresHealthCheck() && prediction.getCropState() == CropState.HARVESTABLE)
&& !hasEmptyPatch)
{
summary = SummaryState.OCCUPIED;
}
}
if (produce != contract)
{
if (!hasEmptyPatch && completionTime == Long.MAX_VALUE)
{
summary = SummaryState.OCCUPIED;
}
}
else
{
long estimatedTime = Math.min(prediction.getDoneEstimate(), completionTime);
if (estimatedTime <= Instant.now().getEpochSecond())
{
summary = SummaryState.COMPLETED;
completionTime = 0;
break;
}
else
{
summary = SummaryState.IN_PROGRESS;
completionTime = estimatedTime;
}
}
}
}
@Nullable
private Produce getStoredContract()
{
try
{
return Produce.getByItemID(Integer.parseInt(configManager.getConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT)));
}
catch (NumberFormatException ignored)
{
return null;
}
}
private void setStoredContract(@Nullable Produce contract)
{
if (contract != null)
{
configManager.setConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT, String.valueOf(contract.getItemID()));
}
else
{
configManager.unsetConfiguration(getConfigGroup(), CONFIG_KEY_CONTRACT);
}
}
@Nonnull
private String getConfigGroup()
{
return TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
}
}

View File

@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Set;
import javax.swing.JLabel;
import javax.swing.border.EmptyBorder;
import net.runelite.api.ItemID;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.TabContentPanel;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
@@ -47,18 +48,21 @@ public class FarmingTabPanel extends TabContentPanel
private final ItemManager itemManager;
private final TimeTrackingConfig config;
private final List<TimeablePanel<FarmingPatch>> patchPanels;
private final FarmingContractManager farmingContractManager;
FarmingTabPanel(
FarmingTracker farmingTracker,
ItemManager itemManager,
TimeTrackingConfig config,
Set<FarmingPatch> patches
Set<FarmingPatch> patches,
FarmingContractManager farmingContractManager
)
{
this.farmingTracker = farmingTracker;
this.itemManager = itemManager;
this.config = config;
this.patchPanels = new ArrayList<>();
this.farmingContractManager = farmingContractManager;
setLayout(new GridBagLayout());
setBackground(ColorScheme.DARK_GRAY_COLOR);
@@ -187,6 +191,17 @@ public class FarmingTabPanel extends TabContentPanel
{
panel.getProgress().setVisible(false);
}
JLabel farmingContractIcon = panel.getFarmingContractIcon();
if (farmingContractManager.shouldHighlightFarmingTabPanel(patch))
{
itemManager.getImage(ItemID.SEED_PACK).addTo(farmingContractIcon);
farmingContractIcon.setToolTipText(farmingContractManager.getContract().getName());
}
else
{
farmingContractIcon.setIcon(null);
farmingContractIcon.setToolTipText("");
}
}
}
}

View File

@@ -70,9 +70,9 @@ public class FarmingTracker
}
public FarmingTabPanel createTabPanel(Tab tab)
public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager farmingContractManager)
{
return new FarmingTabPanel(this, itemManager, config, farmingWorld.getTabs().get(tab));
return new FarmingTabPanel(this, itemManager, config, farmingWorld.getTabs().get(tab), farmingContractManager);
}
/**

View File

@@ -52,6 +52,9 @@ class FarmingWorld
.thenComparing((FarmingPatch p) -> p.getRegion().getName())
.thenComparing(FarmingPatch::getName);
@Getter
private final FarmingRegion farmingGuildRegion;
FarmingWorld()
{
// Some of these patches get updated in multiple regions.
@@ -229,7 +232,7 @@ class FarmingWorld
new FarmingPatch("Hespori", Varbits.FARMING_7908, PatchImplementation.HESPORI)
));
add(new FarmingRegion("Farming Guild", 4922,
add(farmingGuildRegion = new FarmingRegion("Farming Guild", 4922,
new FarmingPatch("", Varbits.FARMING_7905, PatchImplementation.TREE),
new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.HERB),
new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.BUSH),

View File

@@ -25,6 +25,7 @@
*/
package net.runelite.client.plugins.timetracking.farming;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.ItemID;
@@ -38,30 +39,30 @@ public enum Produce
SCARECROW("Scarecrow", ItemID.SCARECROW, 5, 4),
// Allotment crops
POTATO("Potato", ItemID.POTATO, 10, 5, 0, 3),
ONION("Onion", ItemID.ONION, 10, 5, 0, 3),
CABBAGE("Cabbage", ItemID.CABBAGE, 10, 5, 0, 3),
TOMATO("Tomato", ItemID.TOMATO, 10, 5, 0, 3),
SWEETCORN("Sweetcorn", ItemID.SWEETCORN, 10, 6, 0, 3),
STRAWBERRY("Strawberry", ItemID.STRAWBERRY, 10, 7, 0, 3),
WATERMELON("Watermelon", ItemID.WATERMELON, 10, 8, 0, 3),
SNAPE_GRASS("Snape grass", ItemID.SNAPE_GRASS, 10, 8, 0, 3),
POTATO("Potato", "Potatoes", PatchImplementation.ALLOTMENT, ItemID.POTATO, 10, 5, 0, 3),
ONION("Onion", "Onions", PatchImplementation.ALLOTMENT, ItemID.ONION, 10, 5, 0, 3),
CABBAGE("Cabbage", "Cabbages", PatchImplementation.ALLOTMENT, ItemID.CABBAGE, 10, 5, 0, 3),
TOMATO("Tomato", "Tomatoes", PatchImplementation.ALLOTMENT, ItemID.TOMATO, 10, 5, 0, 3),
SWEETCORN("Sweetcorn", PatchImplementation.ALLOTMENT, ItemID.SWEETCORN, 10, 6, 0, 3),
STRAWBERRY("Strawberry", "Strawberries", PatchImplementation.ALLOTMENT, ItemID.STRAWBERRY, 10, 7, 0, 3),
WATERMELON("Watermelon", "Watermelons", PatchImplementation.ALLOTMENT, ItemID.WATERMELON, 10, 8, 0, 3),
SNAPE_GRASS("Snape grass", PatchImplementation.ALLOTMENT, ItemID.SNAPE_GRASS, 10, 8, 0, 3),
// Flower crops
MARIGOLD("Marigold", ItemID.MARIGOLDS, 5, 5),
ROSEMARY("Rosemary", ItemID.ROSEMARY, 5, 5),
NASTURTIUM("Nasturtium", ItemID.NASTURTIUMS, 5, 5),
WOAD("Woad", ItemID.WOAD_LEAF, 5, 5),
LIMPWURT("Limpwurt", ItemID.LIMPWURT_ROOT, 5, 5),
WHITE_LILY("While lily", ItemID.WHITE_LILY, 5, 5),
MARIGOLD("Marigold", "Marigolds", PatchImplementation.FLOWER, ItemID.MARIGOLDS, 5, 5),
ROSEMARY("Rosemary", PatchImplementation.FLOWER, ItemID.ROSEMARY, 5, 5),
NASTURTIUM("Nasturtium", "Nasturtiums", PatchImplementation.FLOWER, ItemID.NASTURTIUMS, 5, 5),
WOAD("Woad", PatchImplementation.FLOWER, ItemID.WOAD_LEAF, 5, 5),
LIMPWURT("Limpwurt", "Limpwurt roots", PatchImplementation.FLOWER, ItemID.LIMPWURT_ROOT, 5, 5),
WHITE_LILY("White lily", "White lillies", PatchImplementation.FLOWER, ItemID.WHITE_LILY, 5, 5),
// Bush crops
REDBERRIES("Redberry", ItemID.REDBERRIES, 20, 6, 20, 5),
CADAVABERRIES("Cadavaberry", ItemID.CADAVA_BERRIES, 20, 7, 20, 5),
DWELLBERRIES("Dwellberry", ItemID.DWELLBERRIES, 20, 8, 20, 5),
JANGERBERRIES("Jangerberry", ItemID.JANGERBERRIES, 20, 9, 20, 5),
WHITEBERRIES("Whiteberry", ItemID.WHITE_BERRIES, 20, 9, 20, 5),
POISON_IVY("Poison", ItemID.POISON_IVY_BERRIES, 20, 9, 20, 5),
REDBERRIES("Redberry", "Redberries", PatchImplementation.BUSH, ItemID.REDBERRIES, 20, 6, 20, 5),
CADAVABERRIES("Cadavaberry", "Cadava berries", PatchImplementation.BUSH, ItemID.CADAVA_BERRIES, 20, 7, 20, 5),
DWELLBERRIES("Dwellberry", "Dwellberries", PatchImplementation.BUSH, ItemID.DWELLBERRIES, 20, 8, 20, 5),
JANGERBERRIES("Jangerberry", "Jangerberries", PatchImplementation.BUSH, ItemID.JANGERBERRIES, 20, 9, 20, 5),
WHITEBERRIES("Whiteberry", "White berries", PatchImplementation.BUSH, ItemID.WHITE_BERRIES, 20, 9, 20, 5),
POISON_IVY("Poison", "Poison ivy berries", PatchImplementation.BUSH, ItemID.POISON_IVY_BERRIES, 20, 9, 20, 5),
// Hop crops
BARLEY("Barley", ItemID.BARLEY, 10, 5, 0, 3),
@@ -73,43 +74,43 @@ public enum Produce
WILDBLOOD("Wildblood", ItemID.WILDBLOOD_HOPS, 10, 9, 0, 3),
// Herb crops
GUAM("Guam", ItemID.GUAM_LEAF, 20, 5, 0, 3),
MARRENTILL("Marrentill", ItemID.MARRENTILL, 20, 5, 0, 3),
TARROMIN("Tarromin", ItemID.TARROMIN, 20, 5, 0, 3),
HARRALANDER("Harralander", ItemID.HARRALANDER, 20, 5, 0, 3),
RANARR("Ranarr", ItemID.RANARR_WEED, 20, 5, 0, 3),
TOADFLAX("Toadflax", ItemID.TOADFLAX, 20, 5, 0, 3),
IRIT("Irit", ItemID.IRIT_LEAF, 20, 5, 0, 3),
AVANTOE("Avantoe", ItemID.AVANTOE, 20, 5, 0, 3),
KWUARM("Kwuarm", ItemID.KWUARM, 20, 5, 0, 3),
SNAPDRAGON("Snapdragon", ItemID.SNAPDRAGON, 20, 5, 0, 3),
CADANTINE("Cadantine", ItemID.CADANTINE, 20, 5, 0, 3),
LANTADYME("Lantadyme", ItemID.LANTADYME, 20, 5, 0, 3),
DWARF_WEED("Dwarf Weed", ItemID.DWARF_WEED, 20, 5, 0, 3),
TORSTOL("Torstol", ItemID.TORSTOL, 20, 5, 0, 3),
GOUTWEED("Goutweed", ItemID.GOUTWEED, 20, 5, 0, 2),
ANYHERB("Any herb", ItemID.GUAM_LEAF, 20, 5, 0, 3),
GUAM("Guam", PatchImplementation.HERB, ItemID.GUAM_LEAF, 20, 5, 0, 3),
MARRENTILL("Marrentill", PatchImplementation.HERB, ItemID.MARRENTILL, 20, 5, 0, 3),
TARROMIN("Tarromin", PatchImplementation.HERB, ItemID.TARROMIN, 20, 5, 0, 3),
HARRALANDER("Harralander", PatchImplementation.HERB, ItemID.HARRALANDER, 20, 5, 0, 3),
RANARR("Ranarr", PatchImplementation.HERB, ItemID.RANARR_WEED, 20, 5, 0, 3),
TOADFLAX("Toadflax", PatchImplementation.HERB, ItemID.TOADFLAX, 20, 5, 0, 3),
IRIT("Irit", PatchImplementation.HERB, ItemID.IRIT_LEAF, 20, 5, 0, 3),
AVANTOE("Avantoe", PatchImplementation.HERB, ItemID.AVANTOE, 20, 5, 0, 3),
KWUARM("Kwuarm", PatchImplementation.HERB, ItemID.KWUARM, 20, 5, 0, 3),
SNAPDRAGON("Snapdragon", PatchImplementation.HERB, ItemID.SNAPDRAGON, 20, 5, 0, 3),
CADANTINE("Cadantine", PatchImplementation.HERB, ItemID.CADANTINE, 20, 5, 0, 3),
LANTADYME("Lantadyme", PatchImplementation.HERB, ItemID.LANTADYME, 20, 5, 0, 3),
DWARF_WEED("Dwarf Weed", PatchImplementation.HERB, ItemID.DWARF_WEED, 20, 5, 0, 3),
TORSTOL("Torstol", PatchImplementation.HERB, ItemID.TORSTOL, 20, 5, 0, 3),
GOUTWEED("Goutweed", PatchImplementation.HERB, ItemID.GOUTWEED, 20, 5, 0, 2),
ANYHERB("Any herb", PatchImplementation.HERB, ItemID.GUAM_LEAF, 20, 5, 0, 3),
// Tree crops
OAK("Oak", ItemID.OAK_LOGS, 40, 5),
WILLOW("Willow", ItemID.WILLOW_LOGS, 40, 7),
MAPLE("Maple", ItemID.MAPLE_LOGS, 40, 9),
YEW("Yew", ItemID.YEW_LOGS, 40, 11),
MAGIC("Magic", ItemID.MAGIC_LOGS, 40, 13),
OAK("Oak", "Oak tree", PatchImplementation.TREE, ItemID.OAK_LOGS, 40, 5),
WILLOW("Willow", "Willow tree", PatchImplementation.TREE, ItemID.WILLOW_LOGS, 40, 7),
MAPLE("Maple", "Maple tree", PatchImplementation.TREE, ItemID.MAPLE_LOGS, 40, 9),
YEW("Yew", "Yew tree", PatchImplementation.TREE, ItemID.YEW_LOGS, 40, 11),
MAGIC("Magic", "Magic tree", PatchImplementation.TREE, ItemID.MAGIC_LOGS, 40, 13),
// Fruit tree crops
APPLE("Apple", ItemID.COOKING_APPLE, 160, 7, 45, 7),
BANANA("Banana", ItemID.BANANA, 160, 7, 45, 7),
ORANGE("Orange", ItemID.ORANGE, 160, 7, 45, 7),
CURRY("Curry", ItemID.CURRY_LEAF, 160, 7, 45, 7),
PINEAPPLE("Pineapple", ItemID.PINEAPPLE, 160, 7, 45, 7),
PAPAYA("Papaya", ItemID.PAPAYA_FRUIT, 160, 7, 45, 7),
PALM("Palm", ItemID.COCONUT, 160, 7, 45, 7),
DRAGONFRUIT("Dragonfruit", ItemID.DRAGONFRUIT, 160, 7, 45, 7),
APPLE("Apple", "Apple tree", PatchImplementation.FRUIT_TREE, ItemID.COOKING_APPLE, 160, 7, 45, 7),
BANANA("Banana", "Banana tree", PatchImplementation.FRUIT_TREE, ItemID.BANANA, 160, 7, 45, 7),
ORANGE("Orange", "Orange tree", PatchImplementation.FRUIT_TREE, ItemID.ORANGE, 160, 7, 45, 7),
CURRY("Curry", "Curry tree", PatchImplementation.FRUIT_TREE, ItemID.CURRY_LEAF, 160, 7, 45, 7),
PINEAPPLE("Pineapple", "Pineapple plant", PatchImplementation.FRUIT_TREE, ItemID.PINEAPPLE, 160, 7, 45, 7),
PAPAYA("Papaya", "Papaya tree", PatchImplementation.FRUIT_TREE, ItemID.PAPAYA_FRUIT, 160, 7, 45, 7),
PALM("Palm", "Palm tree", PatchImplementation.FRUIT_TREE, ItemID.COCONUT, 160, 7, 45, 7),
DRAGONFRUIT("Dragonfruit", "Dragonfruit tree", PatchImplementation.FRUIT_TREE, ItemID.DRAGONFRUIT, 160, 7, 45, 7),
// Cactus
CACTUS("Cactus", ItemID.CACTUS_SPINE, 80, 8, 20, 4),
POTATO_CACTUS("Potato cactus", ItemID.POTATO_CACTUS, 10, 8, 5, 7),
CACTUS("Cactus", PatchImplementation.CACTUS, ItemID.CACTUS_SPINE, 80, 8, 20, 4),
POTATO_CACTUS("Potato cactus", "Potato cacti", PatchImplementation.CACTUS, ItemID.POTATO_CACTUS, 10, 8, 5, 7),
// Hardwood
TEAK("Teak", ItemID.TEAK_LOGS, 560, 8),
@@ -127,8 +128,8 @@ public enum Produce
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),
CELASTRUS("Celastrus", ItemID.BATTLESTAFF, 160, 6, 0, 4),
REDWOOD("Redwood", ItemID.REDWOOD_LOGS, 640, 11),
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);
@@ -136,6 +137,14 @@ public enum Produce
* User-visible name
*/
private final String name;
/**
* Farming contract names
*/
private final String contractName;
/**
* Patch type for the crop
*/
private final PatchImplementation patchImplementation;
/**
* User-visible item icon
*/
@@ -158,8 +167,68 @@ public enum Produce
*/
private final int harvestStages;
Produce(String name, int itemID, int tickrate, int stages, int regrowTickrate, int harvestStages)
{
this(name, name, null, itemID, tickrate, stages, regrowTickrate, harvestStages);
}
Produce(String name, PatchImplementation patchImplementation, int itemID, int tickrate, int stages, int regrowTickrate, int harvestStages)
{
this(name, name, patchImplementation, itemID, tickrate, stages, regrowTickrate, harvestStages);
}
Produce(String name, String contractName, PatchImplementation patchImplementation, int itemID, int tickrate, int stages)
{
this(name, contractName, patchImplementation, itemID, tickrate, stages, 0, 1);
}
Produce(String name, PatchImplementation patchImplementation, int itemID, int tickrate, int stages)
{
this(name, name, patchImplementation, itemID, tickrate, stages, 0, 1);
}
Produce(String name, int itemID, int tickrate, int stages)
{
this(name, itemID, tickrate, stages, 0, 1);
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)
{
for (Produce produce : Produce.values())
{
if (produce.getItemID() == itemId)
{
return produce;
}
}
return null;
}
@Nullable
static Produce getByContractName(String contractName)
{
for (Produce produce : Produce.values())
{
if (produce.getContractName().equalsIgnoreCase(contractName))
{
return produce;
}
}
return null;
}
}