fixes cluescroll plugin

This commit is contained in:
Zeruth
2019-07-02 19:22:20 -04:00
parent 236ccaf33f
commit c2710ada6b
14 changed files with 499 additions and 232 deletions

View File

@@ -36,8 +36,10 @@ import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import javax.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@@ -98,7 +100,6 @@ import net.runelite.client.ui.overlay.OverlayUtil;
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.ItemUtil;
import net.runelite.client.util.Text;
@PluginDescriptor(
@@ -225,9 +226,9 @@ public class ClueScrollPlugin extends Plugin
@Subscribe
public void onMenuOptionClicked(final MenuOptionClicked event)
{
if (event.getOption() != null && event.getOption().equals("Read"))
if (event.getMenuAction() != null && event.getMenuAction().equals("Read"))
{
final ItemDefinition itemComposition = itemManager.getItemDefinition(event.getIdentifier());
final ItemDefinition itemComposition = itemManager.getItemDefinition(event.hashCode());
if (itemComposition != null && itemComposition.getName().startsWith("Clue scroll"))
{
@@ -256,8 +257,10 @@ public class ClueScrollPlugin extends Plugin
// Check if item was removed from inventory
if (clue != null && clueItemId != null)
{
final Stream<Item> items = Arrays.stream(event.getItemContainer().getItems());
// Check if clue was removed from inventory
if (!ItemUtil.containsItemId(event.getItemContainer().getItems(), clueItemId))
if (items.noneMatch(item -> itemManager.getItemDefinition(item.getId()).getId() == clueItemId))
{
resetClue(true);
}
@@ -761,7 +764,7 @@ public class ClueScrollPlugin extends Plugin
textComponent.render(graphics);
}
void scrollToWidget(WidgetInfo list, WidgetInfo scrollbar, Widget... toHighlight)
void scrollToWidget(WidgetInfo list, WidgetInfo scrollbar, Widget ... toHighlight)
{
final Widget parent = client.getWidget(list);
int averageCentralY = 0;

View File

@@ -267,12 +267,12 @@ public class AnagramClue extends ClueScroll implements TextClueScroll, NpcClueSc
@Override
public String[] getNpcs()
{
return new String[]{npc};
return new String[] {npc};
}
@Override
public int[] getObjectIds()
{
return new int[]{objectId};
return new int[] {objectId};
}
}

View File

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

View File

@@ -80,7 +80,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
new CrypticClue("Speak to the bartender of the Blue Moon Inn in Varrock.", "Bartender", new WorldPoint(3226, 3399, 0), "Talk to the bartender in Blue Moon Inn in Varrock."),
new CrypticClue("This aviator is at the peak of his profession.", "Captain Bleemadge", new WorldPoint(2846, 1749, 0), "Captain Bleemadge, the gnome glider pilot, is found at the top of White Wolf Mountain."),
new CrypticClue("Search the crates in the shed just north of East Ardougne.", CRATE_355, new WorldPoint(2617, 3347, 0), "The crates in the shed north of the northern Ardougne bank."),
new CrypticClue("I wouldn't wear this jean on my legs.", "Father Jean", new WorldPoint(1697, 3574, 0), "Talk to father Jean in the Hosidius church"),
new CrypticClue("I wouldn't wear this jean on my legs.", "Father Jean", new WorldPoint(1734, 3576, 0), "Talk to father Jean in the Hosidius church"),
new CrypticClue("Search the crate in the Toad and Chicken pub.", CRATE_354, new WorldPoint(2913, 3536, 0), "The Toad and Chicken pub is located in Burthorpe."),
new CrypticClue("Search chests found in the upstairs of shops in Port Sarim.", CLOSED_CHEST_375, new WorldPoint(3016, 3205, 1), "Search the chest in the upstairs of Wydin's Food Store, on the east wall."),
new CrypticClue("Right on the blessed border, cursed by the evil ones. On the spot inaccessible by both; I will be waiting. The bugs' imminent possession holds the answer.", new WorldPoint(3410, 3324, 0), "B I P. Dig right under the fairy ring."),
@@ -116,7 +116,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
new CrypticClue("Probably filled with wizards socks.", "Wizard", DRAWERS_350, new WorldPoint(3116, 9562, 0), "Search the drawers in the basement of the Wizard's Tower south of Draynor Village. Kill one of the Wizards for the key. Fairy ring DIS"),
new CrypticClue("Even the seers say this clue goes right over their heads.", CRATE_14934, new WorldPoint(2707, 3488, 2), "Search the crate on the Seers Agility Course in Seers Village"),
new CrypticClue("Speak to a Wyse man.", "Wyson the gardener", new WorldPoint(3026, 3378, 0), "Talk to Wyson the gardener at Falador Park."),
new CrypticClue("You'll need to look for a town with a central fountain. Look for a locked chest in the town's chapel.", "Monk", CLOSED_CHEST_5108, new WorldPoint(3256, 3487, 0), "Search the chest by the stairs in the Varrock church. Kill a Monk in Ardougne Monastery to obtain the key."),
new CrypticClue("You'll need to look for a town with a central fountain. Look for a locked chest in the town's chapel.", "Monk" , CLOSED_CHEST_5108, new WorldPoint(3256, 3487, 0), "Search the chest by the stairs in the Varrock church. Kill a Monk in Ardougne Monastery to obtain the key."),
new CrypticClue("Talk to Ambassador Spanfipple in the White Knights Castle.", "Ambassador Spanfipple", new WorldPoint(2979, 3340, 0), "Ambassador Spanfipple can be found roaming on the first floor of the White Knights Castle."),
new CrypticClue("Mine was the strangest birth under the sun. I left the crimson sack, yet life had not begun. Entered the world, and yet was seen by none.", new WorldPoint(2832, 9586, 0), "Inside Karamja Volcano, dig directly underneath the Red spiders' eggs respawn."),
new CrypticClue("Search for a crate in Varrock Castle.", CRATE_5113, new WorldPoint(3224, 3492, 0), "Search the crate in the corner of the kitchen in Varrock Castle."),
@@ -202,7 +202,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
new CrypticClue("Search the drawers on the first floor of a building overlooking Ardougne's Market.", DRAWERS_352, new WorldPoint(2657, 3322, 1), "Climb the ladder in the house north of the market."),
new CrypticClue("'A bag belt only?', he asked his balding brothers.", "Abbot Langley", new WorldPoint(3058, 3487, 0), "Talk-to Abbot Langley in Monastery west of Edgeville"),
new CrypticClue("Search the drawers upstairs in Falador's shield shop.", DRAWERS, new WorldPoint(2971, 3386, 1), "Cassie's Shield Shop at the northern Falador entrance."),
new CrypticClue("Go to this building to be illuminated, and check the drawers while you are there.", "Market Guard", DRAWERS_350, new WorldPoint(2512, 3641, 1), "Search the drawers in the first floor of the Lighthouse. Kill a Rellekka marketplace guard to obtain the key."),
new CrypticClue("Go to this building to be illuminated, and check the drawers while you are there.", "Market Guard", DRAWERS_350 , new WorldPoint(2512, 3641, 1), "Search the drawers in the first floor of the Lighthouse. Kill a Rellekka marketplace guard to obtain the key."),
new CrypticClue("Dig near some giant mushrooms, behind the Grand Tree.", new WorldPoint(2458, 3504, 0), "Dig near the red mushrooms northwest of the Grand Tree."),
new CrypticClue("Pentagrams and demons, burnt bones and remains, I wonder what the blood contains.", new WorldPoint(3297, 3890, 0), "Dig under the blood rune spawn next the the Demonic Ruins."),
new CrypticClue("Search the drawers above Varrock's shops.", DRAWERS_7194, new WorldPoint(3206, 3419, 1), "Located upstairs in Thessalia's Fine Clothes shop in Varrock."),
@@ -428,7 +428,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
for (TileObject gameObject : plugin.getObjectsToMark())
{
OverlayUtil.renderHoverableArea(graphics, gameObject.getClickbox(), mousePosition,
CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR);
CLICKBOX_FILL_COLOR, CLICKBOX_BORDER_COLOR, CLICKBOX_HOVER_BORDER_COLOR);
OverlayUtil.renderImageLocation(plugin.getClient(), graphics, gameObject.getLocalLocation(), plugin.getClueScrollImage(), IMAGE_Z_OFFSET);
}
@@ -452,12 +452,12 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
@Override
public int[] getObjectIds()
{
return new int[]{objectId};
return new int[] {objectId};
}
@Override
public String[] getNpcs()
{
return new String[]{npc};
return new String[] {npc};
}
}

View File

@@ -184,7 +184,7 @@ public class FaloTheBardClue extends ClueScroll implements TextClueScroll, NpcCl
@Override
public String[] getNpcs()
{
return new String[]{FALO_THE_BARD};
return new String[] {FALO_THE_BARD};
}
public static FaloTheBardClue forText(String text)

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 2018, Eadgars Ruse <https://github.com/Eadgars-Ruse>
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -24,21 +25,19 @@
*/
package net.runelite.client.plugins.cluescrolls.clues;
import com.google.common.collect.Lists;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.NPC;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
@@ -47,35 +46,45 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin;
import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET;
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdArea;
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdLocation;
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdSolver;
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperature;
import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdTemperatureChange;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.components.TitleComponent;
@EqualsAndHashCode(callSuper = false, exclude = { "hotColdSolver", "location" })
@Getter
@Slf4j
public class HotColdClue extends ClueScroll implements LocationClueScroll, LocationsClueScroll, TextClueScroll, NpcClueScroll
{
private static final Pattern INITIAL_STRANGE_DEVICE_MESSAGE = Pattern.compile("The device is (.*)");
private static final Pattern STRANGE_DEVICE_MESSAGE = Pattern.compile("The device is (.*), (.*) last time\\.");
private static final Pattern FINAL_STRANGE_DEVICE_MESSAGE = Pattern.compile("The device is visibly shaking.*");
private static final HotColdClue CLUE =
new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Jorral may have a clue.",
"Jorral",
"Speak to Jorral to receive a strange device.");
private static final int HOT_COLD_PANEL_WIDTH = 200;
private static final HotColdClue BEGINNER_CLUE = new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Reldo may have a clue.",
"Reldo",
"Speak to Reldo to receive a strange device.");
private static final HotColdClue MASTER_CLUE = new HotColdClue("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Jorral may have a clue.",
"Jorral",
"Speak to Jorral to receive a strange device.");
// list of potential places to dig
private List<HotColdLocation> digLocations = new ArrayList<>();
private final String text;
private final String npc;
private final String solution;
@Nullable
private HotColdSolver hotColdSolver;
private WorldPoint location;
private WorldPoint lastWorldPoint;
public static HotColdClue forText(String text)
{
if (CLUE.text.equalsIgnoreCase(text))
if (BEGINNER_CLUE.text.equalsIgnoreCase(text))
{
return CLUE;
BEGINNER_CLUE.reset();
return BEGINNER_CLUE;
}
else if (MASTER_CLUE.text.equalsIgnoreCase(text))
{
MASTER_CLUE.reset();
return MASTER_CLUE;
}
return null;
@@ -87,24 +96,35 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
this.npc = npc;
this.solution = solution;
setRequiresSpade(true);
initializeSolver();
}
@Override
public WorldPoint[] getLocations()
{
return Lists.transform(digLocations, HotColdLocation::getWorldPoint).toArray(new WorldPoint[0]);
if (hotColdSolver == null)
{
return new WorldPoint[0];
}
return hotColdSolver.getPossibleLocations().stream().map(HotColdLocation::getWorldPoint).toArray(WorldPoint[]::new);
}
@Override
public void makeOverlayHint(PanelComponent panelComponent, ClueScrollPlugin plugin)
{
if (hotColdSolver == null)
{
return;
}
panelComponent.getChildren().add(TitleComponent.builder()
.text("Hot/Cold Clue")
.build());
panelComponent.setPreferredSize(new Dimension(200, 0));
panelComponent.setPreferredSize(new Dimension(HOT_COLD_PANEL_WIDTH, 0));
// strange device has not been tested yet, show how to get it
if (lastWorldPoint == null && location == null)
if (hotColdSolver.getLastWorldPoint() == null && location == null)
{
if (getNpc() != null)
{
@@ -131,7 +151,9 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
panelComponent.getChildren().add(LineComponent.builder()
.left("Possible areas:")
.build());
Map<HotColdArea, Integer> locationCounts = new HashMap<>();
final Map<HotColdArea, Integer> locationCounts = new EnumMap<>(HotColdArea.class);
final Collection<HotColdLocation> digLocations = hotColdSolver.getPossibleLocations();
for (HotColdLocation hotColdLocation : digLocations)
{
@@ -159,17 +181,16 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
}
else
{
for (HotColdArea s : locationCounts.keySet())
for (HotColdArea area : locationCounts.keySet())
{
panelComponent.getChildren().add(LineComponent.builder()
.left(s.getName() + ":")
.left(area.getName() + ':')
.build());
for (HotColdLocation hotColdLocation : digLocations)
{
if (hotColdLocation.getHotColdArea() == s)
if (hotColdLocation.getHotColdArea() == area)
{
Rectangle2D r = hotColdLocation.getRect();
panelComponent.getChildren().add(LineComponent.builder()
.left("- " + hotColdLocation.getArea())
.leftColor(Color.LIGHT_GRAY)
@@ -184,8 +205,13 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
@Override
public void makeWorldOverlayHint(Graphics2D graphics, ClueScrollPlugin plugin)
{
if (hotColdSolver == null)
{
return;
}
// when final location has been found
if (this.location != null)
if (location != null)
{
LocalPoint localLocation = LocalPoint.fromWorld(plugin.getClient(), getLocation());
@@ -197,20 +223,17 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
return;
}
// when strange device hasn't been activated yet, show Jorral
if (lastWorldPoint == null)
// when strange device hasn't been activated yet, show npc who gives you the strange device
if (hotColdSolver.getLastWorldPoint() == null && plugin.getNpcsToMark() != null)
{
// Mark NPC
if (plugin.getNpcsToMark() != null)
for (NPC npcToMark : plugin.getNpcsToMark())
{
for (NPC npc : plugin.getNpcsToMark())
{
OverlayUtil.renderActorOverlayImage(graphics, npc, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET);
}
OverlayUtil.renderActorOverlayImage(graphics, npcToMark, plugin.getClueScrollImage(), Color.ORANGE, IMAGE_Z_OFFSET);
}
}
// once the number of possible dig locations is below 10, show the dig spots
final Collection<HotColdLocation> digLocations = hotColdSolver.getPossibleLocations();
if (digLocations.size() < 10)
{
// Mark potential dig locations
@@ -231,170 +254,86 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
public boolean update(final String message, final ClueScrollPlugin plugin)
{
if (!message.startsWith("The device is"))
if (hotColdSolver == null)
{
return false;
}
Matcher m1 = FINAL_STRANGE_DEVICE_MESSAGE.matcher(message);
Matcher m2 = STRANGE_DEVICE_MESSAGE.matcher(message);
Matcher m3 = INITIAL_STRANGE_DEVICE_MESSAGE.matcher(message);
final Set<HotColdTemperature> temperatureSet;
// the order that these pattern matchers are checked is important
if (m1.find())
if (this.equals(BEGINNER_CLUE))
{
// final location for hot cold clue has been found
WorldPoint localWorld = plugin.getClient().getLocalPlayer().getWorldLocation();
if (localWorld != null)
{
markFinalSpot(localWorld);
return true;
}
temperatureSet = HotColdTemperature.BEGINNER_HOT_COLD_TEMPERATURES;
}
else if (m2.find())
else if (this.equals(MASTER_CLUE))
{
String temperature = m2.group(1);
String difference = m2.group(2);
WorldPoint localWorld = plugin.getClient().getLocalPlayer().getWorldLocation();
if (localWorld != null)
{
updatePossibleArea(localWorld, temperature, difference);
return true;
}
temperatureSet = HotColdTemperature.MASTER_HOT_COLD_TEMPERATURES;
}
else if (m3.find())
else
{
String temperature = m3.group(1);
WorldPoint localWorld = plugin.getClient().getLocalPlayer().getWorldLocation();
if (localWorld != null)
{
updatePossibleArea(localWorld, temperature, "");
return true;
}
temperatureSet = null;
}
return false;
final HotColdTemperature temperature = HotColdTemperature.getFromTemperatureSet(temperatureSet, message);
if (temperature == null)
{
return false;
}
final WorldPoint localWorld = plugin.getClient().getLocalPlayer().getWorldLocation();
if (localWorld == null)
{
return false;
}
if ((this.equals(BEGINNER_CLUE) && temperature == HotColdTemperature.BEGINNER_VISIBLY_SHAKING)
|| (this.equals(MASTER_CLUE) && temperature == HotColdTemperature.MASTER_VISIBLY_SHAKING))
{
markFinalSpot(localWorld);
}
else
{
location = null;
final HotColdTemperatureChange temperatureChange = HotColdTemperatureChange.of(message);
hotColdSolver.signal(localWorld, temperature, temperatureChange);
}
return true;
}
@Override
public void reset()
{
this.lastWorldPoint = null;
digLocations.clear();
initializeSolver();
}
private void updatePossibleArea(WorldPoint currentWp, String temperature, String difference)
private void initializeSolver()
{
this.location = null;
if (digLocations.isEmpty())
{
digLocations.addAll(Arrays.asList(HotColdLocation.values()));
}
int maxSquaresAway = 5000;
int minSquaresAway = 0;
switch (temperature)
{
// when the strange device reads a temperature, that means that the center of the final dig location
// is a range of squares away from the player's current location (Chebyshev AKA Chess-board distance)
case "ice cold":
maxSquaresAway = 5000;
minSquaresAway = 500;
break;
case "very cold":
maxSquaresAway = 499;
minSquaresAway = 200;
break;
case "cold":
maxSquaresAway = 199;
minSquaresAway = 150;
break;
case "warm":
maxSquaresAway = 149;
minSquaresAway = 100;
break;
case "hot":
maxSquaresAway = 99;
minSquaresAway = 70;
break;
case "very hot":
maxSquaresAway = 69;
minSquaresAway = 30;
break;
case "incredibly hot":
maxSquaresAway = 29;
minSquaresAway = 5;
break;
}
// rectangle r1 encompasses all of the points that are within the max possible distance from the player
Point p1 = new Point(currentWp.getX() - maxSquaresAway, currentWp.getY() - maxSquaresAway);
Rectangle r1 = new Rectangle((int) p1.getX(), (int) p1.getY(), 2 * maxSquaresAway + 1, 2 * maxSquaresAway + 1);
// rectangle r2 encompasses all of the points that are within the min possible distance from the player
Point p2 = new Point(currentWp.getX() - minSquaresAway, currentWp.getY() - minSquaresAway);
Rectangle r2 = new Rectangle((int) p2.getX(), (int) p2.getY(), 2 * minSquaresAway + 1, 2 * minSquaresAway + 1);
// eliminate from consideration dig spots that lie entirely within the min range or entirely outside of the max range
digLocations.removeIf(entry -> r2.contains(entry.getRect()) || !r1.intersects(entry.getRect()));
// if a previous world point has been recorded, we can consider the warmer/colder result from the strange device
if (lastWorldPoint != null)
{
switch (difference)
{
case "but colder than":
// eliminate spots that are absolutely warmer
digLocations.removeIf(entry -> isFirstPointCloserRect(currentWp, lastWorldPoint, entry.getRect()));
break;
case "and warmer than":
// eliminate spots that are absolutely colder
digLocations.removeIf(entry -> isFirstPointCloserRect(lastWorldPoint, currentWp, entry.getRect()));
break;
case "and the same temperature as":
// I couldn't figure out a clean implementation for this case
// not necessary for quickly determining final location
}
}
lastWorldPoint = currentWp;
}
private boolean isFirstPointCloserRect(WorldPoint firstWp, WorldPoint secondWp, Rectangle2D r)
{
final boolean isBeginner;
WorldPoint p1 = new WorldPoint((int) r.getMaxX(), (int) r.getMaxY(), 0);
if (!isFirstPointCloser(firstWp, secondWp, p1))
if (this.equals(BEGINNER_CLUE))
{
return false;
isBeginner = true;
}
else if (this.equals(MASTER_CLUE))
{
isBeginner = false;
}
else
{
log.warn("Hot cold solver could not be initialized, clue type is unknown; text: {}, npc: {}, solution: {}",
text, npc, solution);
hotColdSolver = null;
return;
}
WorldPoint p2 = new WorldPoint((int) r.getMaxX(), (int) r.getMinY(), 0);
if (!isFirstPointCloser(firstWp, secondWp, p2))
{
return false;
}
else if (!isFirstPointCloser(firstWp, secondWp, p3))
{
return false;
}
WorldPoint p4 = new WorldPoint((int) r.getMinX(), (int) r.getMinY(), 0);
return (isFirstPointCloser(firstWp, secondWp, p4));
}
private boolean isFirstPointCloser(WorldPoint firstWp, WorldPoint secondWp, WorldPoint wp)
{
int firstDistance = firstWp.distanceTo2D(wp);
int secondDistance = secondWp.distanceTo2D(wp);
return (firstDistance < secondDistance);
final Set<HotColdLocation> locations = Arrays.stream(HotColdLocation.values())
.filter(l -> l.isBeginnerClue() == isBeginner)
.collect(Collectors.toSet());
hotColdSolver = new HotColdSolver(locations);
}
private void markFinalSpot(WorldPoint wp)
@@ -405,6 +344,6 @@ public class HotColdClue extends ClueScroll implements LocationClueScroll, Locat
public String[] getNpcs()
{
return new String[]{npc};
return new String[] {npc};
}
}
}

View File

@@ -204,6 +204,6 @@ public class MapClue extends ClueScroll implements ObjectClueScroll
public int[] getObjectIds()
{
return new int[]{objectId};
return new int[] {objectId};
}
}

View File

@@ -91,7 +91,7 @@ public class MusicClue extends ClueScroll implements NpcClueScroll
@Override
public String[] getNpcs()
{
return new String[]{CECILIA};
return new String[] {CECILIA};
}
public static MusicClue forText(String text)

View File

@@ -28,12 +28,15 @@ 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 lombok.RequiredArgsConstructor;
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;
@@ -45,7 +48,6 @@ 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;
import net.runelite.client.util.ItemUtil;
import net.runelite.client.util.Text;
@Getter
@@ -125,19 +127,21 @@ public class ThreeStepCrypticClue extends ClueScroll implements TextClueScroll,
if (event.getItemContainer() == client.getItemContainer(InventoryID.INVENTORY))
{
boolean success = false;
success |= checkForPart(event, TORN_CLUE_SCROLL_PART_1, 0);
success |= checkForPart(event, TORN_CLUE_SCROLL_PART_2, 1);
success |= checkForPart(event, TORN_CLUE_SCROLL_PART_3, 2);
success |= checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_1, 0);
success |= checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_2, 1);
success |= checkForPart(event, itemManager, TORN_CLUE_SCROLL_PART_3, 2);
return success;
}
return false;
}
private boolean checkForPart(final ItemContainerChanged event, int clueScrollPart, int index)
private boolean 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 (ItemUtil.containsItemId(event.getItemContainer().getItems(), clueScrollPart))
if (items.anyMatch(item -> itemManager.getItemDefinition(item.getId()).getId() == clueScrollPart))
{
final Map.Entry<CrypticClue, Boolean> entry = clueSteps.get(index);

View File

@@ -25,33 +25,7 @@
package net.runelite.client.plugins.cluescrolls.clues.emote;
import lombok.Getter;
import static net.runelite.api.SpriteID.EMOTE_ANGRY;
import static net.runelite.api.SpriteID.EMOTE_BECKON;
import static net.runelite.api.SpriteID.EMOTE_BLOW_KISS;
import static net.runelite.api.SpriteID.EMOTE_BOW;
import static net.runelite.api.SpriteID.EMOTE_CHEER;
import static net.runelite.api.SpriteID.EMOTE_CLAP;
import static net.runelite.api.SpriteID.EMOTE_CRY;
import static net.runelite.api.SpriteID.EMOTE_DANCE;
import static net.runelite.api.SpriteID.EMOTE_FLAP;
import static net.runelite.api.SpriteID.EMOTE_GOBLIN_SALUTE;
import static net.runelite.api.SpriteID.EMOTE_HEADBANG;
import static net.runelite.api.SpriteID.EMOTE_JIG;
import static net.runelite.api.SpriteID.EMOTE_JUMP_FOR_JOY;
import static net.runelite.api.SpriteID.EMOTE_LAUGH;
import static net.runelite.api.SpriteID.EMOTE_NO;
import static net.runelite.api.SpriteID.EMOTE_PANIC;
import static net.runelite.api.SpriteID.EMOTE_PUSH_UP;
import static net.runelite.api.SpriteID.EMOTE_RASPBERRY;
import static net.runelite.api.SpriteID.EMOTE_SALUTE;
import static net.runelite.api.SpriteID.EMOTE_SHRUG;
import static net.runelite.api.SpriteID.EMOTE_SLAP_HEAD;
import static net.runelite.api.SpriteID.EMOTE_SPIN;
import static net.runelite.api.SpriteID.EMOTE_STOMP;
import static net.runelite.api.SpriteID.EMOTE_THINK;
import static net.runelite.api.SpriteID.EMOTE_WAVE;
import static net.runelite.api.SpriteID.EMOTE_YAWN;
import static net.runelite.api.SpriteID.EMOTE_YES;
import static net.runelite.api.SpriteID.*;
@Getter
public enum Emote

View File

@@ -1,6 +1,7 @@
/*
* Copyright (c) 2018, Eadgars Ruse <https://github.com/Eadgars-Ruse>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -26,7 +27,6 @@
package net.runelite.client.plugins.cluescrolls.clues.hotcold;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
@@ -69,6 +69,8 @@ public enum HotColdLocation
DESERT_POLLNIVNEACH(new WorldPoint(3287, 2975, 0), DESERT, "West of Pollnivneach."),
DESERT_MTA(new WorldPoint(3350, 3293, 0), DESERT, "Next to Mage Training Arena."),
DESERT_SHANTY(new WorldPoint(3294, 3106, 0), DESERT, "South-west of Shantay Pass."),
DRAYNOR_MANOR_MUSHROOMS(true, new WorldPoint(3096, 3379, 0), MISTHALIN, "Patch of mushrooms just northwest of Draynor Manor"),
DRAYNOR_WHEAT_FIELD(true, new WorldPoint(3120, 3282, 0), MISTHALIN, "Inside the wheat field next to Draynor Village"),
FELDIP_HILLS_JIGGIG(new WorldPoint(2413, 3055, 0), FELDIP_HILLS, "West of Jiggig, east of the fairy ring bkp."),
FELDIP_HILLS_SW(new WorldPoint(2582, 2895, 0), FELDIP_HILLS, "West of the southeasternmost lake in Feldip Hills."),
FELDIP_HILLS_GNOME_GLITER(new WorldPoint(2553, 2972, 0), FELDIP_HILLS, "East of the gnome glider (Lemantolly Undri)."),
@@ -91,6 +93,7 @@ public enum HotColdLocation
FREMENNIK_PROVINCE_ASTRAL_ALTER(new WorldPoint(2147, 3862, 0), FREMENNIK_PROVINCE, "Astral altar"),
FREMENNIK_PROVINCE_LUNAR_VILLAGE(new WorldPoint(2087, 3915, 0), FREMENNIK_PROVINCE, "Lunar Isle, inside the village."),
FREMENNIK_PROVINCE_LUNAR_NORTH(new WorldPoint(2106, 3949, 0), FREMENNIK_PROVINCE, "Lunar Isle, north of the village."),
ICE_MOUNTAIN(true, new WorldPoint(3007, 3475, 0), MISTHALIN, "Atop Ice Mountain"),
KANDARIN_SINCLAR_MANSION(new WorldPoint(2726, 3588, 0), KANDARIN, "North-west of the Sinclair Mansion, near the log balance shortcut."),
KANDARIN_CATHERBY(new WorldPoint(2774, 3433, 0), KANDARIN, "Catherby, between the bank and the beehives, near small rock formation."),
KANDARIN_GRAND_TREE(new WorldPoint(2444, 3503, 0), KANDARIN, "Grand Tree, just east of the terrorchick gnome enclosure."),
@@ -115,6 +118,7 @@ public enum HotColdLocation
KARAMJA_KHARAZI_NE(new WorldPoint(2904, 2925, 0), KARAMJA, "North-eastern part of Kharazi Jungle."),
KARAMJA_KHARAZI_SW(new WorldPoint(2783, 2898, 0), KARAMJA, "South-western part of Kharazi Jungle."),
KARAMJA_CRASH_ISLAND(new WorldPoint(2910, 2737, 0), KARAMJA, "Northern part of Crash Island."),
LUMBRIDGE_COW_FIELD(true, new WorldPoint(3174, 3336, 0), MISTHALIN, "Cow field north of Lumbridge"),
MISTHALIN_VARROCK_STONE_CIRCLE(new WorldPoint(3225, 3355, 0), MISTHALIN, "South of the stone circle near Varrock's entrance."),
MISTHALIN_LUMBRIDGE(new WorldPoint(3238, 3169, 0), MISTHALIN, "Just north-west of the Lumbridge Fishing tutor."),
MISTHALIN_LUMBRIDGE_2(new WorldPoint(3170, 3278, 0), MISTHALIN, "North of the pond between Lumbridge and Draynor Village."),
@@ -131,6 +135,7 @@ public enum HotColdLocation
MORYTANIA_MOS_LES_HARMLESS_BAR(new WorldPoint(3670, 2974, 0), MORYTANIA, "Near Mos Le'Harmless southern bar."),
MORYTANIA_DRAGONTOOTH_NORTH(new WorldPoint(3813, 3567, 0), MORYTANIA, "Northern part of Dragontooth Island."),
MORYTANIA_DRAGONTOOTH_SOUTH(new WorldPoint(3803, 3532, 0), MORYTANIA, "Southern part of Dragontooth Island."),
NORTHEAST_OF_AL_KHARID_MINE(true, new WorldPoint(3332, 3313, 0), MISTHALIN, "Northeast of Al Kharid Mine"),
WESTERN_PROVINCE_EAGLES_PEAK(new WorldPoint(2297, 3530, 0), WESTERN_PROVINCE, "North-west of Eagles' Peak."),
WESTERN_PROVINCE_PISCATORIS(new WorldPoint(2337, 3689, 0), WESTERN_PROVINCE, "Piscatoris Fishing Colony"),
WESTERN_PROVINCE_PISCATORIS_HUNTER_AREA(new WorldPoint(2361, 3566, 0), WESTERN_PROVINCE, "Eastern part of Piscatoris Hunter area, south-west of the Falconry."),
@@ -176,12 +181,20 @@ public enum HotColdLocation
ZEAH_DAIRY_COW(new WorldPoint(1320, 3718, 0), ZEAH, "North-east of the Kebos Lowlands, east of the dairy cow."),
ZEAH_CRIMSON_SWIFTS(new WorldPoint(1186, 3583, 0), ZEAH, "South-west of the Kebos Swamp, below the crimson swifts.");
private final boolean beginnerClue;
private final WorldPoint worldPoint;
private final HotColdArea hotColdArea;
private final String area;
public Rectangle2D getRect()
HotColdLocation(WorldPoint worldPoint, HotColdArea hotColdArea, String areaDescription)
{
return new Rectangle(worldPoint.getX() - 4, worldPoint.getY() - 4, 9, 9);
this(false, worldPoint, hotColdArea, areaDescription);
}
public Rectangle getRect()
{
final int digRadius = beginnerClue ? HotColdTemperature.BEGINNER_VISIBLY_SHAKING.getMaxDistance() :
HotColdTemperature.MASTER_VISIBLY_SHAKING.getMaxDistance();
return new Rectangle(worldPoint.getX() - digRadius, worldPoint.getY() - digRadius, digRadius * 2 + 1, digRadius * 2 + 1);
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) 2018, Eadgars Ruse <https://github.com/Eadgars-Ruse>
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.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.hotcold;
import com.google.common.annotations.VisibleForTesting;
import java.awt.Rectangle;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
/**
* Solution finder for hot-cold style puzzles.
* <p>
* These puzzles are established by having some way to test the distance from the solution via "warmth", where being
* colder means one is farther away from the target, and being warmer means one is closer to it, with the goal being to
* reach the most warm value to discover the solution point. Hot-cold puzzles in Old School Runescape are implemented
* with specific set of solution points, so this solver will filter from a provided set of possible solutions as new
* signals of temperatures and temperature changes are provided.
*/
@Getter
public class HotColdSolver
{
private final Set<HotColdLocation> possibleLocations;
@Nullable
private WorldPoint lastWorldPoint;
public HotColdSolver(Set<HotColdLocation> possibleLocations)
{
this.possibleLocations = possibleLocations;
}
/**
* Process a hot-cold update given a {@link WorldPoint} where a check occurred and the resulting temperature and
* temperature change discovered at that point. This will filter the set of possible locations which can be the
* solution.
*
* @param worldPoint The point where a hot-cold check occurred
* @param temperature The temperature of the checked point
* @param temperatureChange The change of temperature of the checked point compared to the previously-checked point
* @return A set of {@link HotColdLocation}s which are still possible after the filtering occurs. This return value
* is the same as would be returned by {@code getPossibleLocations()}.
*/
public Set<HotColdLocation> signal(@Nonnull final WorldPoint worldPoint, @Nonnull final HotColdTemperature temperature, @Nullable final HotColdTemperatureChange temperatureChange)
{
// when the strange device reads a temperature, that means that the center of the final dig location
// is a range of squares away from the player's current location (Chebyshev AKA Chess-board distance)
int maxSquaresAway = temperature.getMaxDistance();
int minSquaresAway = temperature.getMinDistance();
// maxDistanceArea encompasses all of the points that are within the max possible distance from the player
final Rectangle maxDistanceArea = new Rectangle(
worldPoint.getX() - maxSquaresAway,
worldPoint.getY() - maxSquaresAway,
2 * maxSquaresAway + 1,
2 * maxSquaresAway + 1);
// minDistanceArea encompasses all of the points that are within the min possible distance from the player
final Rectangle minDistanceArea = new Rectangle(
worldPoint.getX() - minSquaresAway,
worldPoint.getY() - minSquaresAway,
2 * minSquaresAway + 1,
2 * minSquaresAway + 1);
// eliminate from consideration dig spots that lie entirely within the min range or entirely outside of the max range
possibleLocations.removeIf(entry -> minDistanceArea.contains(entry.getRect()) || !maxDistanceArea.intersects(entry.getRect()));
// if a previous world point has been recorded, we can consider the warmer/colder result from the strange device
if (lastWorldPoint != null && temperatureChange != null)
{
switch (temperatureChange)
{
case COLDER:
// eliminate spots that are absolutely warmer
possibleLocations.removeIf(entry -> isFirstPointCloserRect(worldPoint, lastWorldPoint, entry.getRect()));
break;
case WARMER:
// eliminate spots that are absolutely colder
possibleLocations.removeIf(entry -> isFirstPointCloserRect(lastWorldPoint, worldPoint, entry.getRect()));
break;
case SAME:
// I couldn't figure out a clean implementation for this case
// not necessary for quickly determining final location
}
}
lastWorldPoint = worldPoint;
return getPossibleLocations();
}
/**
* Determines whether the first point passed is closer to each corner of the given rectangle than the second point.
*
* @param firstPoint First point to test. Return result will be relating to this point's location.
* @param secondPoint Second point to test
* @param rect Rectangle, whose corner points will be compared to the first and second points passed
* @return {@code true} if {@code firstPoint} is closer to each of {@code rect}'s four corner points than
* {@code secondPoint}, {@code false} otherwise.
* @see WorldPoint#distanceTo2D
*/
@VisibleForTesting
static boolean isFirstPointCloserRect(final WorldPoint firstPoint, final WorldPoint secondPoint, final Rectangle rect)
{
final WorldPoint nePoint = new WorldPoint((rect.x + rect.width), (rect.y + rect.height), 0);
if (!isFirstPointCloser(firstPoint, secondPoint, nePoint))
{
return false;
}
final WorldPoint sePoint = new WorldPoint((rect.x + rect.width), rect.y, 0);
if (!isFirstPointCloser(firstPoint, secondPoint, sePoint))
{
return false;
}
final WorldPoint nwPoint = new WorldPoint(rect.x, (rect.y + rect.height), 0);
if (!isFirstPointCloser(firstPoint, secondPoint, nwPoint))
{
return false;
}
final WorldPoint swPoint = new WorldPoint(rect.x, rect.y, 0);
return (isFirstPointCloser(firstPoint, secondPoint, swPoint));
}
/**
* Determines whether the first point passed is closer to the given point of comparison than the second point.
*
* @param firstPoint First point to test. Return result will be relating to this point's location.
* @param secondPoint Second point to test
* @param worldPoint Point to compare to the first and second points passed
* @return {@code true} if {@code firstPoint} is closer to {@code worldPoint} than {@code secondPoint},
* {@code false} otherwise.
* @see WorldPoint#distanceTo2D
*/
@VisibleForTesting
static boolean isFirstPointCloser(final WorldPoint firstPoint, final WorldPoint secondPoint, final WorldPoint worldPoint)
{
return firstPoint.distanceTo2D(worldPoint) < secondPoint.distanceTo2D(worldPoint);
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.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.hotcold;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum HotColdTemperature
{
ICE_COLD("ice cold", 500, 5000),
VERY_COLD("very cold", 200, 499),
COLD("cold", 150, 199),
WARM("warm", 100, 149),
HOT("hot", 70, 99),
VERY_HOT("very hot", 30, 69),
BEGINNER_INCREDIBLY_HOT("incredibly hot", 4, 29),
BEGINNER_VISIBLY_SHAKING("visibly shaking", 0, 3),
MASTER_INCREDIBLY_HOT("incredibly hot", 5, 29),
MASTER_VISIBLY_SHAKING("visibly shaking", 0, 4);
public static final Set<HotColdTemperature> BEGINNER_HOT_COLD_TEMPERATURES = Sets.immutableEnumSet(
ICE_COLD,
VERY_COLD,
COLD,
WARM,
HOT,
VERY_HOT,
BEGINNER_INCREDIBLY_HOT,
BEGINNER_VISIBLY_SHAKING
);
public static final Set<HotColdTemperature> MASTER_HOT_COLD_TEMPERATURES = Sets.immutableEnumSet(
ICE_COLD,
VERY_COLD,
COLD,
WARM,
HOT,
VERY_HOT,
MASTER_INCREDIBLY_HOT,
MASTER_VISIBLY_SHAKING
);
private final String text;
private final int minDistance;
private final int maxDistance;
private static final String DEVICE_USED_START_TEXT = "The device is ";
/**
* Gets the temperature from a set of temperatures corresponding to the passed string.
*
* @param temperatureSet A set of temperature values to select from
* @param message A string containing a temperature value
* @return The corresponding enum from the given temperature set.
* <p>
* Note that in cases where two temperature values in the given set are equally likely to be the given
* temperature (say, two temperatures with identical text values), the behavior is undefined.
*/
@Nullable
public static HotColdTemperature getFromTemperatureSet(final Set<HotColdTemperature> temperatureSet, final String message)
{
if (!message.startsWith(DEVICE_USED_START_TEXT) || temperatureSet == null)
{
return null;
}
final List<HotColdTemperature> possibleTemperatures = new ArrayList<>();
for (final HotColdTemperature temperature : temperatureSet)
{
if (message.contains(temperature.getText()))
{
possibleTemperatures.add(temperature);
}
}
return possibleTemperatures.stream()
// For messages such as "The device is very cold", this will choose the Enum with text of greatest length so
// that VERY_COLD would be selected over COLD, though both Enums have matching text for this message.
.max(Comparator.comparingInt(x -> (x.getText()).length()))
.orElse(null);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2019, Jordan Atwood <nightfirecat@protonmail.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.hotcold;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum HotColdTemperatureChange
{
WARMER("and warmer than"),
SAME("and the same temperature as"),
COLDER("but colder than");
private final String text;
public static HotColdTemperatureChange of(final String message)
{
if (!message.endsWith(" last time."))
{
return null;
}
for (final HotColdTemperatureChange change : values())
{
if (message.contains(change.text))
{
return change;
}
}
return null;
}
}