Add solutions for 3 step cryptic master clues

This extends the functionality of the clue scroll plugin to include
solutions for three step cryptic master clues. The solutions for these
clues are already present in the existing CrypticClue class, but before
this there was not a way for the clues to be recognized. The
ThreeStepCrypticClue class matches the clue scroll text for all three
cryptic clues and keeps the matched clue objects as a list, along with
their completion status. If a clue has a strikethrough tag when reading
the clue, then the plugin will recognize it as complete. The plugin will
also detect when a new clue part has been obtained and update the
completed status of that clue.
This commit is contained in:
Eadgars-Ruse
2018-09-21 14:46:34 -07:00
committed by Jordan Atwood
parent 597c7ef225
commit 0c6fd36ecb
10 changed files with 289 additions and 54 deletions

View File

@@ -32,7 +32,6 @@ import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import javax.inject.Inject;
import lombok.Getter;
@@ -80,11 +79,13 @@ import net.runelite.client.plugins.cluescrolls.clues.MapClue;
import net.runelite.client.plugins.cluescrolls.clues.NpcClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.ObjectClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.TextClueScroll;
import net.runelite.client.plugins.cluescrolls.clues.ThreeStepCrypticClue;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.QueryRunner;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.ArrayUtils;
@PluginDescriptor(
name = "Clue Scroll",
@@ -179,9 +180,9 @@ public class ClueScrollPlugin extends Plugin
return;
}
if (clue instanceof LocationsClueScroll)
if (clue instanceof HotColdClue)
{
if (((LocationsClueScroll)clue).update(event.getMessage(), this))
if (((HotColdClue) clue).update(event.getMessage(), this))
{
worldMapPointsSet = false;
}
@@ -225,6 +226,12 @@ public class ClueScrollPlugin extends Plugin
resetClue();
}
}
// if three step clue check for clue scroll pieces
if (clue instanceof ThreeStepCrypticClue)
{
((ThreeStepCrypticClue) clue).checkForParts(client, event, itemManager);
}
}
@Subscribe
@@ -255,11 +262,41 @@ public class ClueScrollPlugin extends Plugin
if (clue instanceof LocationsClueScroll)
{
final List<WorldPoint> locations = ((LocationsClueScroll) clue).getLocations();
final WorldPoint[] locations = ((LocationsClueScroll) clue).getLocations();
if (!locations.isEmpty())
if (locations.length > 0)
{
addMapPoints(locations.toArray(new WorldPoint[locations.size()]));
addMapPoints(locations);
}
if (clue instanceof ObjectClueScroll)
{
int[] objectIds = ((ObjectClueScroll) clue).getObjectIds();
if (objectIds.length > 0)
{
for (WorldPoint location : locations)
{
final LocalPoint localLocation = LocalPoint.fromWorld(client, location);
if (localLocation != null)
{
final Scene scene = client.getScene();
final Tile[][][] tiles = scene.getTiles();
final Tile tile = tiles[client.getPlane()][localLocation.getSceneX()][localLocation.getSceneY()];
objectsToMark = Arrays.stream(tile.getGameObjects())
.filter(object -> object != null && ArrayUtils.contains(objectIds, object.getId()))
.toArray(GameObject[]::new);
// Set hint arrow to first object found as there can only be 1 hint arrow
if (config.displayHintArrows() && objectsToMark.length >= 1)
{
client.setHintArrow(objectsToMark[0].getWorldLocation());
}
}
}
}
}
}
@@ -282,11 +319,11 @@ public class ClueScrollPlugin extends Plugin
if (clue instanceof NpcClueScroll)
{
String npc = ((NpcClueScroll) clue).getNpc();
String[] npcs = ((NpcClueScroll) clue).getNpcs();
if (npc != null)
if (npcs.length > 0)
{
Query query = new NPCQuery().nameEquals(npc);
Query query = new NPCQuery().nameEquals(npcs);
npcsToMark = queryRunner.runQuery(query);
// Set hint arrow to first NPC found as there can only be 1 hint arrow
@@ -302,40 +339,6 @@ public class ClueScrollPlugin extends Plugin
}
}
if (clue instanceof ObjectClueScroll)
{
final ObjectClueScroll objectClueScroll = (ObjectClueScroll) clue;
int objectId = objectClueScroll.getObjectId();
if (objectId != -1)
{
// Match object with location every time
final WorldPoint location = objectClueScroll.getLocation();
if (location != null)
{
final LocalPoint localLocation = LocalPoint.fromWorld(client, location);
if (localLocation != null)
{
final Scene scene = client.getScene();
final Tile[][][] tiles = scene.getTiles();
final Tile tile = tiles[client.getPlane()][localLocation.getSceneX()][localLocation.getSceneY()];
objectsToMark = Arrays.stream(tile.getGameObjects())
.filter(object -> object != null && object.getId() == objectId)
.toArray(GameObject[]::new);
// Set hint arrow to first object found as there can only be 1 hint arrow
if (config.displayHintArrows() && objectsToMark.length >= 1)
{
client.setHintArrow(objectsToMark[0].getWorldLocation());
}
}
}
}
}
if (clue instanceof EmoteClue)
{
ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
@@ -510,6 +513,14 @@ public class ClueScrollPlugin extends Plugin
return hotColdClue;
}
// three step cryptic clues need unedited text to check which steps are already done
final ThreeStepCrypticClue threeStepCrypticClue = ThreeStepCrypticClue.forText(text, clueScrollText.getText());
if (threeStepCrypticClue != null)
{
return threeStepCrypticClue;
}
// We have unknown clue, reset
resetClue();
return null;

View File

@@ -219,4 +219,9 @@ public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueSc
return null;
}
public String[] getNpcs()
{
return new String[] {npc};
}
}

View File

@@ -128,4 +128,9 @@ public class CipherClue extends ClueScroll implements TextClueScroll, NpcClueScr
return null;
}
public String[] getNpcs()
{
return new String[] {npc};
}
}

View File

@@ -50,7 +50,7 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
@Getter
public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueScroll, ObjectClueScroll
{
private static final Set<CrypticClue> CLUES = ImmutableSet.of(
public static final Set<CrypticClue> CLUES = ImmutableSet.of(
new CrypticClue("Show this to Sherlock.", "Sherlock", new WorldPoint(2733, 3415, 0), "Sherlock is located to the east of the Sorcerer's tower in Seers' Village."),
new CrypticClue("Talk to the bartender of the Rusty Anchor in Port Sarim.", "Bartender", new WorldPoint(3045, 3256, 0), "The Rusty Anchor is located in the north of Port Sarim."),
new CrypticClue("The keeper of Melzars... Spare? Skeleton? Anar?", "Oziach", new WorldPoint(3068, 3516, 0), "Speak to Oziach in Edgeville"),
@@ -432,4 +432,14 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
return null;
}
public int[] getObjectIds()
{
return new int[] {objectId};
}
public String[] getNpcs()
{
return new String[] {npc};
}
}

View File

@@ -84,9 +84,9 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
}
@Override
public List<WorldPoint> getLocations()
public WorldPoint[] getLocations()
{
return Lists.transform(digLocations, HotColdLocation::getWorldPoint);
return Lists.transform(digLocations, HotColdLocation::getWorldPoint).toArray(new WorldPoint[0]);
}
@Override
@@ -232,7 +232,6 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
}
}
@Override
public boolean update(final String message, final ClueScrollPlugin plugin)
{
if (!message.startsWith("The device is"))
@@ -407,4 +406,9 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
this.location = wp;
reset();
}
public String[] getNpcs()
{
return new String[] {npc};
}
}

View File

@@ -24,15 +24,11 @@
*/
package net.runelite.client.plugins.cluescrolls.clues;
import java.util.List;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
public interface LocationsClueScroll
{
boolean update(String message, ClueScrollPlugin plugin);
void reset();
List<WorldPoint> getLocations();
WorldPoint[] getLocations();
}

View File

@@ -200,4 +200,9 @@ public class MapClue extends ClueScroll implements ObjectClueScroll
return null;
}
public int[] getObjectIds()
{
return new int[] {objectId};
}
}

View File

@@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues;
public interface NpcClueScroll
{
String getNpc();
String[] getNpcs();
}

View File

@@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues;
public interface ObjectClueScroll extends LocationClueScroll
{
int getObjectId();
int[] getObjectIds();
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (c) 2018, Eadgars Ruse <https://github.com/Eadgars-Ruse>
* 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 java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_1;
import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_2;
import static net.runelite.api.ItemID.TORN_CLUE_SCROLL_PART_3;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.client.game.ItemManager;
import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR;
import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.components.TitleComponent;
@Getter
public class ThreeStepCrypticClue extends ClueScroll implements TextClueScroll, ObjectClueScroll, NpcClueScroll, LocationsClueScroll
{
private static final int CLUE_STEPS = 3;
private List<Map.Entry<CrypticClue, Boolean>> clueSteps;
private int[] objectIds;
private WorldPoint[] locations;
private String[] npcs;
private String text;
private ThreeStepCrypticClue(List<Map.Entry<CrypticClue, Boolean>> steps, String text)
{
final int numClueSteps = steps.size();
this.clueSteps = steps;
this.text = text;
this.locations = new WorldPoint[numClueSteps];
this.npcs = new String[numClueSteps];
this.objectIds = new int[numClueSteps];
for (int i = 0; i < numClueSteps; i++)
{
CrypticClue c = clueSteps.get(i).getKey();
locations[i] = c.getLocation();
npcs[i] = c.getNpc();
objectIds[i] = c.getObjectId();
}
}
@Override
public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin)
{
panelComponent.setPreferredSize(new Dimension(200, 0));
for (int i = 0; i < CLUE_STEPS; i++)
{
Map.Entry<CrypticClue, Boolean> e = clueSteps.get(i);
if (!e.getValue())
{
CrypticClue c = e.getKey();
panelComponent.getChildren().add(TitleComponent.builder().text("Cryptic Clue #" + (i + 1)).build());
panelComponent.getChildren().add(LineComponent.builder().left("Solution:").build());
panelComponent.getChildren().add(LineComponent.builder()
.left(c.getSolution())
.leftColor(TITLED_CONTENT_COLOR)
.build());
}
}
}
@Override
public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin)
{
for (Map.Entry<CrypticClue, Boolean> e : clueSteps)
{
if (!e.getValue())
{
e.getKey().makeWorldOverlayHint(graphics, plugin);
}
}
}
public void checkForParts(Client client, final ItemContainerChanged event, ItemManager itemManager)
{
if (event.getItemContainer() == client.getItemContainer(InventoryID.INVENTORY))
{
checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_1, 0);
checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_2, 1);
checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_3, 2);
}
}
private void checkForPart(final ItemContainerChanged event, ItemManager itemManager, int clueScrollPart, int index)
{
final Stream<Item> items = Arrays.stream(event.getItemContainer().getItems());
// If we have the part then that step is done
if (items.anyMatch(item -> itemManager.getItemComposition(item.getId()).getId() == clueScrollPart))
{
clueSteps.get(index).setValue(true);
}
}
@Override
public void reset()
{
this.locations = new WorldPoint[] {};
}
public static ThreeStepCrypticClue forText(String plainText, String text)
{
List<Map.Entry<CrypticClue, Boolean>> steps = new ArrayList<>(CLUE_STEPS);
StringBuilder threeStepText = new StringBuilder(text.replaceAll("<br><br>", " ").toLowerCase());
boolean stepDone;
for (int i = 0; i < CLUE_STEPS; i++)
{
// if text has <str> - strikethrough - tag then it is already done
stepDone = threeStepText.toString().startsWith("<str>");
if (stepDone)
{
// chop off <str> tag
threeStepText.delete(0, 5);
}
for (CrypticClue clue : CrypticClue.CLUES)
{
if (threeStepText.toString().startsWith(clue.getText().toLowerCase()))
{
if (stepDone)
{
steps.add(new AbstractMap.SimpleEntry<>(clue, true));
// chop off </str> tag
threeStepText.delete(0, 6);
}
else
{
steps.add(new AbstractMap.SimpleEntry<>(clue, false));
}
if (i < CLUE_STEPS - 1)
{
// chop off the length of the clue text plus a space
threeStepText.delete(0, clue.getText().length() + 1);
}
break;
}
}
}
if (steps.isEmpty())
{
return null;
}
return new ThreeStepCrypticClue(steps, plainText);
}
@Override
public WorldPoint getLocation()
{
return null;
}
}