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:
committed by
Jordan Atwood
parent
597c7ef225
commit
0c6fd36ecb
@@ -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;
|
||||
|
||||
@@ -219,4 +219,9 @@ public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueSc
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String[] getNpcs()
|
||||
{
|
||||
return new String[] {npc};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +128,9 @@ public class CipherClue extends ClueScroll implements TextClueScroll, NpcClueScr
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String[] getNpcs()
|
||||
{
|
||||
return new String[] {npc};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -200,4 +200,9 @@ public class MapClue extends ClueScroll implements ObjectClueScroll
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int[] getObjectIds()
|
||||
{
|
||||
return new int[] {objectId};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues;
|
||||
|
||||
public interface NpcClueScroll
|
||||
{
|
||||
String getNpc();
|
||||
String[] getNpcs();
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues;
|
||||
|
||||
public interface ObjectClueScroll extends LocationClueScroll
|
||||
{
|
||||
int getObjectId();
|
||||
int[] getObjectIds();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user