clues: Create NamedObjectClueScroll interface

This commit adds a new clue type, NamedObjectClueScroll, which allows
highlighting of objects based on object name. (and optionally region
ID(s) where that object should reside)

This commit introduces two new fidlds to the ClueScrollPlugin class to
track player plane changes to address some unusual client behavior.
Namely, when a player travels up or down planes, multiloc objects only
have their varbit values updated on the tick after the plane change
occurs. Since these objects must be in their desired state before
checking their name, these fields are used to track both the player's
plane, and whether the plugin should wait an extra tick to process the
new plane's objects based on a plane change occurring.
This commit is contained in:
Jordan Atwood
2020-02-11 13:39:01 -08:00
parent dc6942197e
commit c48d8db3a1
2 changed files with 258 additions and 2 deletions

View File

@@ -37,9 +37,13 @@ import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import joptsimple.internal.Strings;
@@ -65,12 +69,24 @@ import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.CommandExecuted;
import net.runelite.api.events.DecorativeObjectChanged;
import net.runelite.api.events.DecorativeObjectDespawned;
import net.runelite.api.events.DecorativeObjectSpawned;
import net.runelite.api.events.GameObjectChanged;
import net.runelite.api.events.GameObjectDespawned;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.GroundObjectChanged;
import net.runelite.api.events.GroundObjectDespawned;
import net.runelite.api.events.GroundObjectSpawned;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.events.WallObjectChanged;
import net.runelite.api.events.WallObjectDespawned;
import net.runelite.api.events.WallObjectSpawned;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
@@ -96,6 +112,7 @@ import net.runelite.client.plugins.cluescrolls.clues.LocationClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.LocationsClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.MapClue;
import net.runelite.client.plugins.cluescrolls.clues.MusicClue;
import net.runelite.client.plugins.cluescrolls.clues.NamedObjectClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.NpcClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.ObjectClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.SkillChallengeClue;
@@ -108,6 +125,7 @@ import net.runelite.client.ui.overlay.components.TextComponent;
import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.ArrayUtils;
@PluginDescriptor(
name = "Clue Scroll",
@@ -137,6 +155,9 @@ public class ClueScrollPlugin extends Plugin
@Getter
private final List<TileObject> objectsToMark = new ArrayList<>();
@Getter
private final Set<TileObject> namedObjectsToMark = new HashSet<>();
@Getter
private Item[] equippedItems;
@@ -180,6 +201,12 @@ public class ClueScrollPlugin extends Plugin
private Integer clueItemId;
private boolean worldMapPointsSet = false;
// Some objects will only update to their "active" state when changing to their plane after varbit changes,
// which take one extra tick to fire after the plane change. These fields are used to track those changes and delay
// scans of the current plane's tiles accordingly.
private int currentPlane = -1;
private boolean namedObjectCheckThisTick;
private final TextComponent textComponent = new TextComponent();
@Provides
@@ -211,8 +238,11 @@ public class ClueScrollPlugin extends Plugin
overlayManager.remove(clueScrollWorldOverlay);
overlayManager.remove(clueScrollMusicOverlay);
npcsToMark.clear();
namedObjectsToMark.clear();
inventoryItems = null;
equippedItems = null;
currentPlane = -1;
namedObjectCheckThisTick = false;
resetClue(true);
}
@@ -344,6 +374,94 @@ public class ClueScrollPlugin extends Plugin
}
}
@Subscribe
public void onDecorativeObjectChanged(final DecorativeObjectChanged event)
{
tileObjectChangedHandler(event.getPrevious(), event.getDecorativeObject());
}
@Subscribe
public void onDecorativeObjectDespawned(final DecorativeObjectDespawned event)
{
tileObjectDespawnedHandler(event.getDecorativeObject());
}
@Subscribe
public void onDecorativeObjectSpawned(final DecorativeObjectSpawned event)
{
tileObjectSpawnedHandler(event.getDecorativeObject());
}
@Subscribe
public void onGameObjectChanged(final GameObjectChanged event)
{
tileObjectChangedHandler(event.getPrevious(), event.getGameObject());
}
@Subscribe
public void onGameObjectDespawned(final GameObjectDespawned event)
{
tileObjectDespawnedHandler(event.getGameObject());
}
@Subscribe
public void onGameObjectSpawned(final GameObjectSpawned event)
{
tileObjectSpawnedHandler(event.getGameObject());
}
@Subscribe
public void onGroundObjectChanged(final GroundObjectChanged event)
{
tileObjectChangedHandler(event.getPrevious(), event.getGroundObject());
}
@Subscribe
public void onGroundObjectDespawned(final GroundObjectDespawned event)
{
tileObjectDespawnedHandler(event.getGroundObject());
}
@Subscribe
public void onGroundObjectSpawned(final GroundObjectSpawned event)
{
tileObjectSpawnedHandler(event.getGroundObject());
}
@Subscribe
public void onWallObjectChanged(final WallObjectChanged event)
{
tileObjectChangedHandler(event.getPrevious(), event.getWallObject());
}
@Subscribe
public void onWallObjectDespawned(final WallObjectDespawned event)
{
tileObjectDespawnedHandler(event.getWallObject());
}
@Subscribe
public void onWallObjectSpawned(final WallObjectSpawned event)
{
tileObjectSpawnedHandler(event.getWallObject());
}
private void tileObjectChangedHandler(final TileObject prev, final TileObject changedTo)
{
tileObjectDespawnedHandler(prev);
tileObjectSpawnedHandler(changedTo);
}
private void tileObjectDespawnedHandler(final TileObject despawned)
{
namedObjectsToMark.remove(despawned);
}
private void tileObjectSpawnedHandler(final TileObject spawned)
{
checkClueNamedObject(clue, spawned);
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
@@ -356,7 +474,14 @@ public class ClueScrollPlugin extends Plugin
@Subscribe
public void onGameStateChanged(final GameStateChanged event)
{
if (event.getGameState() == GameState.LOGIN_SCREEN)
final GameState state = event.getGameState();
if (state != GameState.LOGGED_IN)
{
namedObjectsToMark.clear();
}
if (state == GameState.LOGIN_SCREEN)
{
resetClue(true);
}
@@ -425,6 +550,20 @@ public class ClueScrollPlugin extends Plugin
}
}
// Load the current plane's tiles if a tick has elapsed since the player has changed planes
if (namedObjectCheckThisTick)
{
namedObjectCheckThisTick = false;
checkClueNamedObjects(clue);
}
// Delay one tick when changing planes before scanning for new named objects on the new plane
if (currentPlane != client.getPlane())
{
currentPlane = client.getPlane();
namedObjectCheckThisTick = true;
}
// Reset clue when receiving a new beginner or master clue
// These clues use a single item ID, so we cannot detect step changes based on the item ID changing
final Widget chatDialogClueItem = client.getWidget(WidgetInfo.DIALOG_SPRITE_SPRITE);
@@ -525,6 +664,7 @@ public class ClueScrollPlugin extends Plugin
worldMapPointManager.removeIf(ClueScrollWorldMapPoint.class::isInstance);
worldMapPointsSet = false;
npcsToMark.clear();
namedObjectsToMark.clear();
if (config.displayHintArrows())
{
@@ -713,7 +853,6 @@ public class ClueScrollPlugin extends Plugin
final Scene scene = client.getScene();
final Tile[][][] tiles = scene.getTiles();
final Tile tile = tiles[client.getPlane()][localLocation.getSceneX()][localLocation.getSceneY()];
objectsToMark.clear();
for (GameObject object : tile.getGameObjects())
{
@@ -781,6 +920,81 @@ public class ClueScrollPlugin extends Plugin
}
}
/**
* Scans all of the current plane's loaded tiles for {@link TileObject}s and passes any found objects to
* {@link ClueScrollPlugin#checkClueNamedObject(ClueScroll, TileObject)} for storing in the cache of discovered
* named objects.
*
* @param clue The active clue scroll
*/
private void checkClueNamedObjects(@Nullable ClueScroll clue)
{
if (!(clue instanceof NamedObjectClueScroll))
{
return;
}
// Search loaded tiles for objects
for (final Tile[] tiles : client.getScene().getTiles()[client.getPlane()])
{
for (final Tile tile : tiles)
{
if (tile == null)
{
continue;
}
for (final GameObject object : tile.getGameObjects())
{
if (object == null)
{
continue;
}
checkClueNamedObject(clue, object);
}
}
}
}
/**
* Checks passed objects against the active clue's object names and regions. If the clue is a
* {@link NamedObjectClueScroll} and the object matches its allowable object names and is within its regions, the
* object will be stored in the cache of discovered named objects.
*
* @param clue The active clue scroll
* @param object The spawned or scanned object
*/
private void checkClueNamedObject(@Nullable final ClueScroll clue, @Nonnull final TileObject object)
{
if (!(clue instanceof NamedObjectClueScroll))
{
return;
}
final NamedObjectClueScroll namedObjectClue = (NamedObjectClueScroll) clue;
final String[] objectNames = namedObjectClue.getObjectNames();
final int[] regionIds = namedObjectClue.getObjectRegions();
if (objectNames == null || objectNames.length == 0
|| regionIds != null && !ArrayUtils.contains(regionIds, object.getWorldLocation().getRegionID()))
{
return;
}
final ObjectComposition comp = client.getObjectDefinition(object.getId());
final ObjectComposition impostor = comp.getImpostorIds() != null ? comp.getImpostor() : comp;
for (final String name : objectNames)
{
if (comp.getName().equals(name) || impostor.getName().equals(name))
{
namedObjectsToMark.add(object);
}
}
}
private void updateClue(final ClueScroll clue)
{
if (clue == null || clue == this.clue)
@@ -790,6 +1004,7 @@ public class ClueScrollPlugin extends Plugin
resetClue(false);
checkClueNPCs(clue, client.getCachedNPCs());
checkClueNamedObjects(clue);
// If we have a clue, save that knowledge
// so the clue window doesn't have to be open.
this.clue = clue;

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2020, Jordan Atwood <jordan.atwood423@gmail.com>
* 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.cluescrolls.clues;
import javax.annotation.Nullable;
/**
* Represents a clue which should highlight objects of a given name rather than a specific ID and location, as some
* clues will call for a general action which can be completed at any number of locations. The area in which this
* highlighting should occur can be restricted by giving a non-null array of region IDs where only objects within those
* regions will be highlighted.
*/
public interface NamedObjectClueScroll
{
String[] getObjectNames();
@Nullable
int[] getObjectRegions();
}