time tracking: add bird house tracker

This commit is contained in:
takuyakanbr
2018-07-28 23:15:02 +08:00
parent b502964142
commit 15e0fd4306
15 changed files with 719 additions and 33 deletions

View File

@@ -95,7 +95,15 @@ public enum VarPlayer
SLAYER_GOAL_END(1272),
FARMING_GOAL_END(1273),
CONSTRUCTION_GOAL_END(1274),
HUNTER_GOAL_END(1275);
HUNTER_GOAL_END(1275),
/**
* Bird house states
*/
BIRD_HOUSE_MEADOW_NORTH(1626),
BIRD_HOUSE_MEADOW_SOUTH(1627),
BIRD_HOUSE_VALLEY_NORTH(1628),
BIRD_HOUSE_VALLEY_SOUTH(1629);
private final int id;
}

View File

@@ -241,6 +241,11 @@ public class WidgetID
static final int SPEC_ORB = 28;
}
static class LoginClickToPlayScreen
{
static final int MESSAGE_OF_THE_DAY = 3;
}
static class Viewport
{
static final int MINIMAP_RESIZABLE_WIDGET = 17;

View File

@@ -139,6 +139,7 @@ public enum WidgetInfo
MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_ORB),
LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0),
LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, WidgetID.LoginClickToPlayScreen.MESSAGE_OF_THE_DAY),
FIXED_VIEWPORT(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.Viewport.FIXED_VIEWPORT),
FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.ROOT_INTERFACE_CONTAINER),

View File

@@ -32,6 +32,7 @@ import net.runelite.api.ItemID;
@Getter
public enum Tab
{
BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE),
ALLOTMENT("Allotment Patches", ItemID.CABBAGE),
FLOWER("Flower Patches", ItemID.RED_FLOWERS),
HERB("Herb Patches", ItemID.GRIMY_RANARR_WEED),

View File

@@ -33,6 +33,7 @@ public interface TimeTrackingConfig extends Config
{
String KEY_NAME = "timetracking";
String AUTOWEED = "autoweed";
String BIRD_HOUSE = "birdhouse";
@ConfigItem(
keyName = "estimateRelative",
@@ -44,6 +45,16 @@ public interface TimeTrackingConfig extends Config
return false;
}
@ConfigItem(
keyName = "birdHouseNotification",
name = "Bird house notification",
description = "Notify you when all bird houses are full"
)
default boolean birdHouseNotification()
{
return false;
}
@ConfigItem(
keyName = "activeTab",
name = "Active Tab",

View File

@@ -39,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.client.game.AsyncBufferedImage;
import net.runelite.client.game.ItemManager;
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;
@@ -59,7 +60,8 @@ class TimeTrackingPanel extends PluginPanel
@Nullable
private TabContentPanel activeTabPanel = null;
TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, FarmingTracker farmingTracker)
TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config,
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker)
{
super(false);
@@ -77,6 +79,8 @@ class TimeTrackingPanel extends PluginPanel
add(tabGroup, BorderLayout.NORTH);
add(display, BorderLayout.CENTER);
addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel());
for (Tab tab : Tab.FARMING_TABS)
{
addTab(tab, farmingTracker.createTabPanel(tab));

View File

@@ -37,11 +37,14 @@ import net.runelite.api.GameState;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.UsernameChanged;
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.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
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.NavigationButton;
import net.runelite.client.ui.ClientToolbar;
@@ -49,8 +52,8 @@ import net.runelite.client.util.ImageUtil;
@PluginDescriptor(
name = "Time Tracking",
description = "Enable the Time Tracking panel, which contains farming trackers",
tags = {"farming", "skilling", "panel"}
description = "Enable the Time Tracking panel, which contains farming and bird house trackers",
tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "panel"}
)
@Slf4j
public class TimeTrackingPlugin extends Plugin
@@ -64,6 +67,9 @@ public class TimeTrackingPlugin extends Plugin
@Inject
private FarmingTracker farmingTracker;
@Inject
private BirdHouseTracker birdHouseTracker;
@Inject
private ItemManager itemManager;
@@ -75,6 +81,7 @@ public class TimeTrackingPlugin extends Plugin
private NavigationButton navButton;
private WorldPoint lastTickLocation;
private boolean lastTickPostLogin;
@Provides
TimeTrackingConfig provideConfig(ConfigManager configManager)
@@ -85,9 +92,11 @@ public class TimeTrackingPlugin extends Plugin
@Override
protected void startUp() throws Exception
{
birdHouseTracker.loadFromConfig();
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png");
panel = new TimeTrackingPanel(itemManager, config, farmingTracker);
panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker);
navButton = NavigationButton.builder()
.tooltip("Time Tracking")
@@ -105,6 +114,7 @@ public class TimeTrackingPlugin extends Plugin
protected void shutDown() throws Exception
{
lastTickLocation = null;
lastTickPostLogin = false;
clientToolbar.removeNavigation(navButton);
}
@@ -117,17 +127,32 @@ public class TimeTrackingPlugin extends Plugin
return;
}
// bird house data is only sent after exiting the post-login screen
Widget motd = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
if (motd != null && !motd.isHidden())
{
lastTickPostLogin = true;
return;
}
if (lastTickPostLogin)
{
lastTickPostLogin = false;
return;
}
WorldPoint loc = lastTickLocation;
lastTickLocation = client.getLocalPlayer().getWorldLocation();
if (loc == null || loc.getRegionID() != lastTickLocation.getRegionID())
if (loc == null || loc.getPlane() != 0 || loc.getRegionID() != lastTickLocation.getRegionID())
{
return;
}
boolean birdHouseDataChanged = birdHouseTracker.updateData(loc);
boolean farmingDataChanged = farmingTracker.updateData(loc);
if (farmingDataChanged)
if (birdHouseDataChanged || farmingDataChanged)
{
updatePanel();
}
@@ -137,9 +162,21 @@ public class TimeTrackingPlugin extends Plugin
public void onUsernameChanged(UsernameChanged e)
{
farmingTracker.migrateConfiguration();
birdHouseTracker.loadFromConfig();
updatePanel();
}
@Schedule(period = 10, unit = ChronoUnit.SECONDS)
public void checkCompletion()
{
boolean birdHouseDataChanged = birdHouseTracker.checkCompletion();
if (birdHouseDataChanged)
{
updatePanel();
}
}
@Schedule(period = 10, unit = ChronoUnit.SECONDS)
public void updatePanel()
{

View File

@@ -7,25 +7,24 @@
* 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.
* 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.
* 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;
package net.runelite.client.plugins.timetracking;
import com.google.common.base.Strings;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@@ -40,16 +39,16 @@ import net.runelite.client.ui.components.ThinProgressBar;
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
@Getter
class FarmingPatchPanel extends JPanel
public class TimeablePanel<T> extends JPanel
{
private final FarmingPatch patch;
private final T timeable;
private final JLabel icon = new JLabel();
private final JLabel estimate = new JLabel();
private final ThinProgressBar progress = new ThinProgressBar();
FarmingPatchPanel(FarmingPatch patch)
public TimeablePanel(T timeable, String title, int maximumProgressValue)
{
this.patch = patch;
this.timeable = timeable;
setLayout(new BorderLayout());
setBorder(new EmptyBorder(7, 0, 0, 0));
@@ -66,8 +65,7 @@ class FarmingPatchPanel extends JPanel
infoPanel.setLayout(new GridLayout(2, 1));
infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0));
final JLabel location = new JShadowedLabel(patch.getRegion().getName()
+ (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")"));
final JLabel location = new JShadowedLabel(title);
location.setFont(FontManager.getRunescapeSmallFont());
location.setForeground(Color.WHITE);
@@ -80,6 +78,9 @@ class FarmingPatchPanel extends JPanel
topContainer.add(icon, BorderLayout.WEST);
topContainer.add(infoPanel, BorderLayout.CENTER);
progress.setValue(0);
progress.setMaximumValue(maximumProgressValue);
add(topContainer, BorderLayout.NORTH);
add(progress, BorderLayout.SOUTH);
}

View File

@@ -45,6 +45,7 @@ import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
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;
import net.runelite.client.ui.FontManager;
@@ -55,7 +56,7 @@ public class FarmingTabPanel extends TabContentPanel
private final ItemManager itemManager;
private final ConfigManager configManager;
private final TimeTrackingConfig config;
private final List<FarmingPatchPanel> patchPanels;
private final List<TimeablePanel<FarmingPatch>> patchPanels;
FarmingTabPanel(Client client, ItemManager itemManager, ConfigManager configManager,
TimeTrackingConfig config, Set<FarmingPatch> patches)
@@ -80,7 +81,8 @@ public class FarmingTabPanel extends TabContentPanel
boolean first = true;
for (FarmingPatch patch : patches)
{
FarmingPatchPanel p = new FarmingPatchPanel(patch);
String title = patch.getRegion().getName() + (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")");
TimeablePanel<FarmingPatch> p = new TimeablePanel<>(patch, title, 1);
/* Show labels to subdivide tabs into sections */
if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName()))
@@ -131,9 +133,9 @@ public class FarmingTabPanel extends TabContentPanel
.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED));
}
for (FarmingPatchPanel panel : patchPanels)
for (TimeablePanel<FarmingPatch> panel : patchPanels)
{
FarmingPatch patch = panel.getPatch();
FarmingPatch patch = panel.getTimeable();
String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
String key = Integer.toString(patch.getVarbit().getId());
String storedValue = configManager.getConfiguration(group, key);

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.ItemID;
@AllArgsConstructor
@Getter
enum BirdHouse
{
NORMAL("Bird House", ItemID.BIRD_HOUSE),
OAK("Oak Bird House", ItemID.OAK_BIRD_HOUSE),
WILLOW("Willow Bird House", ItemID.WILLOW_BIRD_HOUSE),
TEAK("Teak Bird House", ItemID.TEAK_BIRD_HOUSE),
MAPLE("Maple Bird House", ItemID.MAPLE_BIRD_HOUSE),
MAHOGANY("Mahogany Bird House", ItemID.MAHOGANY_BIRD_HOUSE),
YEW("Yew Bird House", ItemID.YEW_BIRD_HOUSE),
MAGIC("Magic Bird House", ItemID.MAGIC_BIRD_HOUSE),
REDWOOD("Redwood Bird House", ItemID.REDWOOD_BIRD_HOUSE);
private final String name;
private final int itemID;
/**
* Gets the {@code BirdHouse} corresponding to the given {@code VarPlayer} value.
*/
@Nullable
static BirdHouse fromVarpValue(int varp)
{
int index = (varp - 1) / 3;
if (varp <= 0 || index >= values().length)
{
return null;
}
return values()[index];
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import lombok.Value;
/**
* Contains data about the state of a particular {@link BirdHouseSpace}, at a particular point in time.
*/
@Value
class BirdHouseData
{
private BirdHouseSpace space;
private int varp;
private long timestamp;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.VarPlayer;
@AllArgsConstructor
@Getter
enum BirdHouseSpace
{
MEADOW_NORTH("Mushroom Meadow (North)", VarPlayer.BIRD_HOUSE_MEADOW_NORTH),
MEADOW_SOUTH("Mushroom Meadow (South)", VarPlayer.BIRD_HOUSE_MEADOW_SOUTH),
VALLEY_NORTH("Verdant Valley (Northeast)", VarPlayer.BIRD_HOUSE_VALLEY_NORTH),
VALLEY_SOUTH("Verdant Valley (Southwest)", VarPlayer.BIRD_HOUSE_VALLEY_SOUTH);
private final String name;
private final VarPlayer varp;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import java.awt.Color;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.client.ui.ColorScheme;
@AllArgsConstructor
@Getter
enum BirdHouseState
{
SEEDED(ColorScheme.PROGRESS_COMPLETE_COLOR),
BUILT(ColorScheme.PROGRESS_INPROGRESS_COLOR),
EMPTY(ColorScheme.MEDIUM_GRAY_COLOR),
UNKNOWN(ColorScheme.MEDIUM_GRAY_COLOR);
private final Color color;
/**
* Gets the {@code BirdHouseState} corresponding to the given {@code VarPlayer} value.
*/
static BirdHouseState fromVarpValue(int varp)
{
if (varp < 0 || varp > BirdHouse.values().length * 3)
{
return UNKNOWN;
}
else if (varp == 0)
{
return EMPTY;
}
else if (varp % 3 == 0)
{
return SEEDED;
}
else
{
return BUILT;
}
}
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright (c) 2018 Abex
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import java.awt.Color;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import net.runelite.api.ItemID;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.TabContentPanel;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
import net.runelite.client.plugins.timetracking.TimeablePanel;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.DynamicGridLayout;
public class BirdHouseTabPanel extends TabContentPanel
{
private static final Color COMPLETED_COLOR = ColorScheme.PROGRESS_COMPLETE_COLOR.darker();
private final ItemManager itemManager;
private final BirdHouseTracker birdHouseTracker;
private final TimeTrackingConfig config;
private final List<TimeablePanel<BirdHouseSpace>> spacePanels;
BirdHouseTabPanel(ItemManager itemManager, BirdHouseTracker birdHouseTracker, TimeTrackingConfig config)
{
this.itemManager = itemManager;
this.birdHouseTracker = birdHouseTracker;
this.config = config;
this.spacePanels = new ArrayList<>();
setLayout(new DynamicGridLayout(0, 1, 0, 0));
setBackground(ColorScheme.DARK_GRAY_COLOR);
boolean first = true;
for (BirdHouseSpace space : BirdHouseSpace.values())
{
TimeablePanel<BirdHouseSpace> panel = new TimeablePanel<>(space, space.getName(), BirdHouseTracker.BIRD_HOUSE_DURATION);
spacePanels.add(panel);
add(panel);
// remove the top border on the first panel
if (first)
{
first = false;
panel.setBorder(null);
}
}
}
@Override
public void update()
{
long unixNow = Instant.now().getEpochSecond();
for (TimeablePanel<BirdHouseSpace> panel : spacePanels)
{
BirdHouseSpace space = panel.getTimeable();
BirdHouseData data = birdHouseTracker.getBirdHouseData().get(space);
int value = -1;
long startTime = 0;
if (data != null)
{
value = data.getVarp();
startTime = data.getTimestamp();
}
BirdHouse birdHouse = BirdHouse.fromVarpValue(value);
BirdHouseState state = BirdHouseState.fromVarpValue(value);
if (birdHouse == null)
{
itemManager.getImage(ItemID.FEATHER).addTo(panel.getIcon());
panel.getProgress().setVisible(false);
}
else
{
itemManager.getImage(birdHouse.getItemID()).addTo(panel.getIcon());
panel.getIcon().setToolTipText(birdHouse.getName());
panel.getProgress().setVisible(true);
}
panel.getProgress().setForeground(state.getColor().darker());
switch (state)
{
case EMPTY:
panel.getIcon().setToolTipText("Empty");
panel.getEstimate().setText("Empty");
break;
case BUILT:
panel.getProgress().setValue(0);
panel.getEstimate().setText("Built");
break;
case SEEDED:
long doneEstimate = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION;
if (doneEstimate < unixNow)
{
panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION);
panel.getProgress().setForeground(COMPLETED_COLOR);
panel.getEstimate().setText("Done");
}
else if (config.estimateRelative())
{
int remainingSeconds = (int) (59 + doneEstimate - unixNow);
int remaining = remainingSeconds / 60;
panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION - remainingSeconds);
StringBuilder f = new StringBuilder("Done in ");
int min = remaining % 60;
int hours = (remaining / 60) % 24;
int days = remaining / (60 * 24);
if (days > 0)
{
f.append(days).append("d ");
}
if (hours > 0)
{
f.append(hours).append("h ");
}
if (min > 0)
{
f.append(min).append("m ");
}
panel.getEstimate().setText(f.toString());
}
else
{
StringBuilder f = new StringBuilder();
LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset());
LocalDateTime ldtNow = LocalDateTime.now();
f.append("Done ");
if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek())
{
f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
}
f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute()));
panel.getEstimate().setText(f.toString());
}
break;
default:
panel.getIcon().setToolTipText("Unknown state");
panel.getEstimate().setText("Unknown");
break;
}
panel.getProgress().update();
}
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright (c) 2018 Abex
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* 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.hunter;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
@Singleton
public class BirdHouseTracker
{
// average time taken to harvest 10 birds, in seconds
static final int BIRD_HOUSE_DURATION = (int) Duration.ofMinutes(50).getSeconds();
private static ImmutableSet<Integer> FOSSIL_ISLAND_REGIONS = ImmutableSet.of(14650, 14651, 14652, 14906, 14907, 15162, 15163);
private final Client client;
private final ItemManager itemManager;
private final ConfigManager configManager;
private final TimeTrackingConfig config;
private final Notifier notifier;
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<BirdHouseSpace, BirdHouseData> birdHouseData = new ConcurrentHashMap<>();
@Inject
private BirdHouseTracker(Client client, ItemManager itemManager, ConfigManager configManager,
TimeTrackingConfig config, Notifier notifier)
{
this.client = client;
this.itemManager = itemManager;
this.configManager = configManager;
this.config = config;
this.notifier = notifier;
}
/**
* The time at which all the bird houses will be ready to be dismantled,
* or {@code -1} if we have no data about any of the bird house spaces.
*
* This is set to {@code 0} if the bird houses have already completed
* when updating it.
*/
private long completionTime = -1;
public BirdHouseTabPanel createBirdHouseTabPanel()
{
return new BirdHouseTabPanel(itemManager, this, config);
}
public void loadFromConfig()
{
birdHouseData.clear();
final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
for (BirdHouseSpace space : BirdHouseSpace.values())
{
String key = Integer.toString(space.getVarp().getId());
String storedValue = configManager.getConfiguration(group, key);
if (storedValue != null)
{
String[] parts = storedValue.split(":");
if (parts.length == 2)
{
try
{
int varp = Integer.parseInt(parts[0]);
long timestamp = Long.parseLong(parts[1]);
birdHouseData.put(space, new BirdHouseData(space, varp, timestamp));
}
catch (NumberFormatException e)
{
// ignored
}
}
}
}
updateCompletionTime();
}
/**
* Updates tracker data if player is within range of any bird house. Returns true if any data was changed.
*/
public boolean updateData(WorldPoint location)
{
boolean changed = false;
if (FOSSIL_ISLAND_REGIONS.contains(location.getRegionID()))
{
final Map<BirdHouseSpace, BirdHouseData> newData = new HashMap<>();
final long currentTime = Instant.now().getEpochSecond();
int removalCount = 0;
for (BirdHouseSpace space : BirdHouseSpace.values())
{
int varp = client.getVar(space.getVarp());
BirdHouseData oldData = birdHouseData.get(space);
int oldVarp = oldData == null ? -1 : oldData.getVarp();
// update data if there isn't one, or if the varp doesn't match
if (varp != oldVarp)
{
newData.put(space, new BirdHouseData(space, varp, currentTime));
changed = true;
}
if (varp <= 0 && oldVarp > 0)
{
removalCount++;
}
}
// Prevent the resetting of bird house data that could occur if the varps have not been updated yet
// after the player enters the region. We assume that players would generally have 3 or 4 bird houses
// built at any time, and that dropping from 3/4 to 0 built bird houses is not normally possible.
if (removalCount > 2)
{
return false;
}
if (changed)
{
birdHouseData.putAll(newData);
updateCompletionTime();
saveToConfig(newData);
}
}
return changed;
}
/**
* Checks if the bird houses have become ready to be dismantled,
* and sends a notification if required.
*/
public boolean checkCompletion()
{
if (completionTime > 0 && completionTime < Instant.now().getEpochSecond())
{
completionTime = 0;
if (config.birdHouseNotification())
{
notifier.notify("Your bird houses are ready to be dismantled.");
}
return true;
}
return false;
}
/**
* Updates the overall completion time of the bird houses.
* @see #completionTime
*/
private void updateCompletionTime()
{
if (birdHouseData.isEmpty())
{
completionTime = -1;
return;
}
long maxCompletionTime = 0;
for (BirdHouseData data : birdHouseData.values())
{
if (BirdHouseState.fromVarpValue(data.getVarp()) == BirdHouseState.SEEDED)
{
maxCompletionTime = Math.max(maxCompletionTime, data.getTimestamp() + BIRD_HOUSE_DURATION);
}
}
completionTime = (maxCompletionTime <= Instant.now().getEpochSecond()) ? 0 : maxCompletionTime;
}
private void saveToConfig(Map<BirdHouseSpace, BirdHouseData> updatedData)
{
final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
for (BirdHouseData data : updatedData.values())
{
String key = Integer.toString(data.getSpace().getVarp().getId());
configManager.setConfiguration(group, key, data.getVarp() + ":" + data.getTimestamp());
}
}
}