From 0c6fd36ecb7fa263e4b40e732a5ed6d16bdc3983 Mon Sep 17 00:00:00 2001 From: Eadgars-Ruse <38920911+Eadgars-Ruse@users.noreply.github.com> Date: Fri, 21 Sep 2018 14:46:34 -0700 Subject: [PATCH] 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. --- .../plugins/cluescrolls/ClueScrollPlugin.java | 97 +++++---- .../cluescrolls/clues/AnagramClue.java | 5 + .../plugins/cluescrolls/clues/CipherClue.java | 5 + .../cluescrolls/clues/CrypticClue.java | 12 +- .../cluescrolls/clues/HotColdClue.java | 10 +- .../clues/LocationsClueScroll.java | 6 +- .../plugins/cluescrolls/clues/MapClue.java | 5 + .../cluescrolls/clues/NpcClueScroll.java | 2 +- .../cluescrolls/clues/ObjectClueScroll.java | 2 +- .../clues/ThreeStepCrypticClue.java | 199 ++++++++++++++++++ 10 files changed, 289 insertions(+), 54 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java index c81ae85777..bc1b7448fc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java @@ -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 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; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java index 294907dcfe..0e6d3fdb15 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/AnagramClue.java @@ -219,4 +219,9 @@ public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueSc return null; } + + public String[] getNpcs() + { + return new String[] {npc}; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java index 11544fb822..e77e3b7fe5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CipherClue.java @@ -128,4 +128,9 @@ public class CipherClue extends ClueScroll implements TextClueScroll, NpcClueScr return null; } + + public String[] getNpcs() + { + return new String[] {npc}; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java index 6d935c7f9b..dc415b55c4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -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 CLUES = ImmutableSet.of( + public static final Set 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}; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java index f7c71b3f3d..fd66bc2848 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/HotColdClue.java @@ -84,9 +84,9 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat } @Override - public List 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}; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java index a85db3cb43..eff9d9efd7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/LocationsClueScroll.java @@ -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 getLocations(); + WorldPoint[] getLocations(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java index fd2b65f874..315447d8b7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/MapClue.java @@ -200,4 +200,9 @@ public class MapClue extends ClueScroll implements ObjectClueScroll return null; } + + public int[] getObjectIds() + { + return new int[] {objectId}; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java index df8d468817..d343693e0a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/NpcClueScroll.java @@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues; public interface NpcClueScroll { - String getNpc(); + String[] getNpcs(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java index 605fab1042..c832cb23ae 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ObjectClueScroll.java @@ -26,5 +26,5 @@ package net.runelite.client.plugins.cluescrolls.clues; public interface ObjectClueScroll extends LocationClueScroll { - int getObjectId(); + int[] getObjectIds(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java new file mode 100644 index 0000000000..3858f6c68a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/ThreeStepCrypticClue.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2018, 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> clueSteps; + private int[] objectIds; + private WorldPoint[] locations; + private String[] npcs; + private String text; + + private ThreeStepCrypticClue(List> 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 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 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 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> steps = new ArrayList<>(CLUE_STEPS); + StringBuilder threeStepText = new StringBuilder(text.replaceAll("

", " ").toLowerCase()); + boolean stepDone; + + for (int i = 0; i < CLUE_STEPS; i++) + { + // if text has - strikethrough - tag then it is already done + stepDone = threeStepText.toString().startsWith(""); + + if (stepDone) + { + // chop off 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 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; + } +}