thieving: Add chest respawn timer (#1855)

* Add chest respawn timer

* Update copyright

* Rearrange config

* Remove chestOverlay when unloading

* Avoid reading settings every frame

* Minor fixes

* Remove eventbus from overlay, store endTime only and respawnTime is computed

* Update chestOverlay on startup

* Invert the percent to ensure we're counting up

* Compute respawn for every world

This should be more optimized - we use less memory and iterate less

* Remove lazy getter

* Bugfixes
This commit is contained in:
Manatsawin Hanmongkolchai
2019-11-04 02:50:09 +07:00
committed by Lucwousin
parent 8c9bde0855
commit 70fd10b6bb
5 changed files with 366 additions and 5 deletions

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2019, whs
* 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.thieving;
import com.google.common.collect.ImmutableMap;
import java.time.Duration;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.ObjectID;
enum Chest
{
TEN_COIN(Duration.ofMillis(6000), ObjectID.CHEST_11735),
FIFTY_COIN(Duration.ofMillis(46000), ObjectID.CHEST_11737),
NATURE_RUNE(Duration.ofMillis(10000), ObjectID.CHEST_11736),
STEEL_ARROWTIPS(Duration.ofMillis(77000), ObjectID.CHEST_11742),
AVERAGE_CHEST(Duration.ofMillis(90000), ObjectID.CHEST_22697),
BLOOD_RUNE(Duration.ofMillis(120000), ObjectID.CHEST_11738),
ARDOUGNE_CASTLE(Duration.ofMillis(400000), ObjectID.CHEST_11739), // FIXME: Please time
RICH_CHEST(Duration.ofMillis(300000), ObjectID.CHEST_22681), // FIXME: Please time
ROGUE_CASTLE(Duration.ofMillis(10000), ObjectID.CHEST_26757); // FIXME: Please time
private static final Map<Integer, Chest> CHESTS;
static
{
ImmutableMap.Builder<Integer, Chest> builder = new ImmutableMap.Builder<>();
for (Chest chest : values())
{
for (int id : chest.ids)
{
builder.put(id, chest);
}
}
CHESTS = builder.build();
}
@Getter
private final Duration respawnTime;
private final int[] ids;
Chest(Duration respawnTime, int... ids)
{
this.respawnTime = respawnTime;
this.ids = ids;
}
static Chest of(int id)
{
return CHESTS.get(id);
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2019, whs
* 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.thieving;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.AccessLevel;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.ProgressPieComponent;
@Singleton
class ChestOverlay extends Overlay
{
private final Client client;
private final List<ChestRespawn> respawns;
@Setter(AccessLevel.PACKAGE)
private Color pieFillColor;
@Setter(AccessLevel.PACKAGE)
private Color pieBorderColor;
@Setter(AccessLevel.PACKAGE)
private boolean respawnPieInverted;
@Setter(AccessLevel.PACKAGE)
private int respawnPieDiameter;
@Inject
private ChestOverlay(final Client client, final ThievingPlugin plugin)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.respawns = plugin.getRespawns();
this.client = client;
}
@Override
public Dimension render(Graphics2D graphics)
{
if (respawns.isEmpty())
{
return null;
}
Instant now = Instant.now();
for (Iterator<ChestRespawn> it = respawns.iterator(); it.hasNext(); )
{
ChestRespawn chestRespawn = it.next();
float percent = 1.0f - (now.until(chestRespawn.getEndTime(), ChronoUnit.MILLIS) / (float) chestRespawn.getRespawnTime());
if (percent > 1.0f)
{
it.remove();
continue;
}
if (chestRespawn.getWorld() != client.getWorld())
{
continue;
}
WorldPoint worldPoint = chestRespawn.getWorldPoint();
LocalPoint loc = LocalPoint.fromWorld(client, worldPoint);
if (loc == null)
{
continue;
}
Point point = Perspective.localToCanvas(client, loc, client.getPlane(), 0);
if (point == null)
{
continue;
}
if (respawnPieInverted)
{
percent = 1.0f - percent;
}
ProgressPieComponent ppc = new ProgressPieComponent();
ppc.setDiameter(respawnPieDiameter);
ppc.setBorderColor(pieBorderColor);
ppc.setFill(pieFillColor);
ppc.setPosition(point);
ppc.setProgress(percent);
ppc.render(graphics);
}
return null;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019, whs
* 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.thieving;
import java.time.Instant;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.coords.WorldPoint;
@RequiredArgsConstructor
@Getter(AccessLevel.PACKAGE)
class ChestRespawn
{
private final Chest chest;
private final WorldPoint worldPoint;
private final Instant endTime;
private final int world;
private long respawnTime = -1;
long getRespawnTime()
{
if (respawnTime != -1)
{
return respawnTime;
}
respawnTime = chest.getRespawnTime().toMillis();
return respawnTime;
}
}

View File

@@ -26,15 +26,18 @@
*/
package net.runelite.client.plugins.thieving;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
import net.runelite.client.config.Range;
@ConfigGroup("thieving")
public interface ThievingConfig extends Config
{
@ConfigItem
(
@ConfigItem(
position = 1,
keyName = "statTimeout",
name = "Reset stats (minutes)",
@@ -44,4 +47,53 @@ public interface ThievingConfig extends Config
{
return 5;
}
@ConfigSection(
name = "Chest",
description = "",
position = 2,
keyName = "chestSection"
)
default boolean chestSection()
{
return false;
}
@Alpha
@ConfigItem(
keyName = "respawnColor",
name = "Respawn timer color",
description = "Configures the color of the respawn timer",
section = "chestSection"
)
default Color respawnColor()
{
return Color.YELLOW;
}
@ConfigItem(
keyName = "respawnPieInverted",
name = "Invert respawn timer",
description = "Configures whether the respawn timer goes from empty to full or the other way around",
section = "chestSection"
)
default boolean respawnPieInverted()
{
return false;
}
@Range(
min = 1,
max = 50
)
@ConfigItem(
keyName = "respawnPieDiameter",
name = "Respawn pie diameter",
description = "Configures how big the respawn timer pie is",
section = "chestSection"
)
default int respawnPieDiameter()
{
return 30;
}
}

View File

@@ -26,16 +26,23 @@
*/
package net.runelite.client.plugins.thieving;
import com.google.inject.Provides;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.AccessLevel;
import lombok.Getter;
import com.google.inject.Provides;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.GameObjectDespawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
@@ -46,8 +53,7 @@ import net.runelite.client.plugins.PluginType;
import net.runelite.client.plugins.xptracker.XpTrackerPlugin;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor
(
@PluginDescriptor(
name = "Thieving",
description = "Show thieving overlay",
tags = {"overlay", "skilling", "thieving", "pickpocketing"},
@@ -58,12 +64,18 @@ import net.runelite.client.ui.overlay.OverlayManager;
@PluginDependency(XpTrackerPlugin.class)
public class ThievingPlugin extends Plugin
{
@Inject
private Client client;
@Inject
private ThievingConfig config;
@Inject
private ThievingOverlay overlay;
@Inject
private ChestOverlay chestOverlay;
@Inject
private OverlayManager overlayManager;
@@ -73,7 +85,11 @@ public class ThievingPlugin extends Plugin
@Getter(AccessLevel.PACKAGE)
private ThievingSession session;
@Getter(AccessLevel.PACKAGE)
private final List<ChestRespawn> respawns = new ArrayList<>();
private int statTimeout;
private boolean recentlyLoggedIn = false;
@Provides
ThievingConfig getConfig(ConfigManager configManager)
@@ -88,8 +104,14 @@ public class ThievingPlugin extends Plugin
this.statTimeout = config.statTimeout();
chestOverlay.setPieFillColor(config.respawnColor());
chestOverlay.setPieBorderColor(config.respawnColor().darker());
chestOverlay.setRespawnPieInverted(config.respawnPieInverted());
chestOverlay.setRespawnPieDiameter(config.respawnPieDiameter());
session = null;
overlayManager.add(overlay);
overlayManager.add(chestOverlay);
}
@Override
@@ -98,18 +120,31 @@ public class ThievingPlugin extends Plugin
eventBus.unregister(this);
overlayManager.remove(overlay);
overlayManager.remove(chestOverlay);
session = null;
}
private void addSubscriptions()
{
eventBus.subscribe(ConfigChanged.class, this, this::onConfigChanged);
eventBus.subscribe(GameStateChanged.class, this, this::onGameStateChanged);
eventBus.subscribe(GameTick.class, this, this::onGameTick);
eventBus.subscribe(ChatMessage.class, this, this::onChatMessage);
eventBus.subscribe(GameObjectDespawned.class, this, this::onGameObjectDespawned);
}
private void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOGGED_IN)
{
recentlyLoggedIn = true;
}
}
private void onGameTick(GameTick gameTick)
{
recentlyLoggedIn = false;
if (session == null || this.statTimeout == 0)
{
return;
@@ -156,6 +191,23 @@ public class ThievingPlugin extends Plugin
}
}
private void onGameObjectDespawned(GameObjectDespawned event)
{
if (client.getGameState() != GameState.LOGGED_IN || recentlyLoggedIn)
{
return;
}
final GameObject object = event.getGameObject();
Chest chest = Chest.of(object.getId());
if (chest != null)
{
ChestRespawn chestRespawn = new ChestRespawn(chest, object.getWorldLocation(), Instant.now().plus(chest.getRespawnTime()), client.getWorld());
respawns.add(chestRespawn);
}
}
private void onConfigChanged(ConfigChanged event)
{
if (!"thieving".equals(event.getGroup()))
@@ -164,6 +216,10 @@ public class ThievingPlugin extends Plugin
}
this.statTimeout = config.statTimeout();
chestOverlay.setPieFillColor(config.respawnColor());
chestOverlay.setPieBorderColor(config.respawnColor().darker());
chestOverlay.setRespawnPieInverted(config.respawnPieInverted());
chestOverlay.setRespawnPieDiameter(config.respawnPieDiameter());
}
}