Merge branch 'master' of https://github.com/runelite/runelite into runelite-master

This commit is contained in:
zeruth
2019-06-21 03:32:14 -04:00
72 changed files with 2852 additions and 491 deletions

View File

@@ -46,11 +46,13 @@ import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.GameState;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.FlashNotification;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.util.OSType;
@@ -68,7 +70,8 @@ public class Notifier
// Notifier properties
private static final Color FLASH_COLOR = new Color(255, 0, 0, 70);
private static final int FLASH_DURATION = 2000;
private static final int MINIMUM_FLASH_DURATION_MILLIS = 2000;
private static final int MINIMUM_FLASH_DURATION_TICKS = MINIMUM_FLASH_DURATION_MILLIS / Constants.CLIENT_TICK_LENGTH;
private final Client client;
private final String appName;
@@ -79,6 +82,7 @@ public class Notifier
private final Path notifyIconPath;
private final boolean terminalNotifierAvailable;
private Instant flashStart;
private long mouseLastPressedMillis;
@Inject
private Notifier(
@@ -146,9 +150,10 @@ public class Notifier
.build());
}
if (runeLiteConfig.enableFlashNotification())
if (runeLiteConfig.flashNotification() != FlashNotification.DISABLED)
{
flashStart = Instant.now();
mouseLastPressedMillis = client.getMouseLastPressedMillis();
}
log.debug(message);
@@ -156,24 +161,48 @@ public class Notifier
public void processFlash(final Graphics2D graphics)
{
if (flashStart == null || client.getGameCycle() % 40 >= 20)
{
return;
}
else if (client.getGameState() != GameState.LOGGED_IN)
if (flashStart == null || client.getGameState() != GameState.LOGGED_IN)
{
flashStart = null;
return;
}
FlashNotification flashNotification = runeLiteConfig.flashNotification();
if (client.getGameCycle() % 40 >= 20
// For solid colour, fall through every time.
&& (flashNotification == FlashNotification.FLASH_TWO_SECONDS
|| flashNotification == FlashNotification.FLASH_UNTIL_CANCELLED))
{
return;
}
final Color color = graphics.getColor();
graphics.setColor(FLASH_COLOR);
graphics.fill(new Rectangle(client.getCanvas().getSize()));
graphics.setColor(color);
if (Instant.now().minusMillis(FLASH_DURATION).isAfter(flashStart))
if (!Instant.now().minusMillis(MINIMUM_FLASH_DURATION_MILLIS).isAfter(flashStart))
{
flashStart = null;
return;
}
switch (flashNotification)
{
case FLASH_TWO_SECONDS:
case SOLID_TWO_SECONDS:
flashStart = null;
break;
case SOLID_UNTIL_CANCELLED:
case FLASH_UNTIL_CANCELLED:
// Any interaction with the client since the notification started will cancel it after the minimum duration
if (client.getMouseIdleTicks() < MINIMUM_FLASH_DURATION_TICKS
|| client.getKeyboardIdleTicks() < MINIMUM_FLASH_DURATION_TICKS
|| client.getMouseLastPressedMillis() > mouseLastPressedMillis)
{
flashStart = null;
}
break;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2019, Twiglet1022 <https://github.com/Twiglet1022>
* 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.config;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum FlashNotification
{
DISABLED("Off"),
FLASH_TWO_SECONDS("Flash for 2 seconds"),
SOLID_TWO_SECONDS("Solid for 2 seconds"),
FLASH_UNTIL_CANCELLED("Flash until cancelled"),
SOLID_UNTIL_CANCELLED("Solid until cancelled");
private final String type;
@Override
public String toString()
{
return type;
}
}

View File

@@ -191,9 +191,9 @@ public interface RuneLiteConfig extends Config
description = "Flashes the game frame as a notification",
position = 24
)
default boolean enableFlashNotification()
default FlashNotification flashNotification()
{
return false;
return FlashNotification.DISABLED;
}
@ConfigItem(

View File

@@ -44,6 +44,7 @@ import javax.inject.Singleton;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import static net.runelite.api.Constants.CLIENT_DEFAULT_ZOOM;
import static net.runelite.api.Constants.HIGH_ALCHEMY_CONSTANT;
import net.runelite.api.GameState;
@@ -521,7 +522,7 @@ public class ItemManager
*/
private AsyncBufferedImage loadImage(int itemId, int quantity, boolean stackable)
{
AsyncBufferedImage img = new AsyncBufferedImage(36, 32, BufferedImage.TYPE_INT_ARGB);
AsyncBufferedImage img = new AsyncBufferedImage(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
clientThread.invoke(() ->
{
if (client.getGameState().ordinal() < GameState.LOGIN_SCREEN.ordinal())

View File

@@ -144,10 +144,12 @@ public enum ItemMapping
// Bounty hunter
ITEM_GRANITE_MAUL(GRANITE_MAUL, GRANITE_MAUL_12848),
ITEM_MAGIC_SHORTBOW(MAGIC_SHORTBOW, MAGIC_SHORTBOW_I),
ITEM_MAGIC_SHORTBOW_SCROLL(MAGIC_SHORTBOW_SCROLL, MAGIC_SHORTBOW_I),
ITEM_SARADOMINS_BLESSED_SWORD(SARADOMINS_TEAR, SARADOMINS_BLESSED_SWORD),
// Jewellery with charges
ITEM_RING_OF_WEALTH(RING_OF_WEALTH, RING_OF_WEALTH_I, RING_OF_WEALTH_1, RING_OF_WEALTH_I1, RING_OF_WEALTH_2, RING_OF_WEALTH_I2, RING_OF_WEALTH_3, RING_OF_WEALTH_I3, RING_OF_WEALTH_4, RING_OF_WEALTH_I4, RING_OF_WEALTH_I5),
ITEM_RING_OF_WEALTH_SCROLL(RING_OF_WEALTH_SCROLL, RING_OF_WEALTH_I, RING_OF_WEALTH_I1, RING_OF_WEALTH_I2, RING_OF_WEALTH_I3, RING_OF_WEALTH_I4, RING_OF_WEALTH_I5),
ITEM_AMULET_OF_GLORY(AMULET_OF_GLORY, AMULET_OF_GLORY1, AMULET_OF_GLORY2, AMULET_OF_GLORY3, AMULET_OF_GLORY5),
ITEM_AMULET_OF_GLORY_T(AMULET_OF_GLORY_T, AMULET_OF_GLORY_T1, AMULET_OF_GLORY_T2, AMULET_OF_GLORY_T3, AMULET_OF_GLORY_T5),
ITEM_SKILLS_NECKLACE(SKILLS_NECKLACE, SKILLS_NECKLACE1, SKILLS_NECKLACE2, SKILLS_NECKLACE3, SKILLS_NECKLACE5),

View File

@@ -109,7 +109,7 @@ public class LootManager
case NpcID.LIZARD:
case NpcID.ZYGOMITE:
case NpcID.ZYGOMITE_474:
case NpcID.ZYGOMITE_1024:
case NpcID.ANCIENT_ZYGOMITE:
// these monsters die with >0 hp, so we just look for coincident

View File

@@ -34,6 +34,7 @@ import javax.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemContainer;

View File

@@ -50,6 +50,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemDefinition;
@@ -730,7 +731,13 @@ public class TabInterface
if (tagTab.getIcon() == null)
{
Widget icon = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), -1, tagTab.getIconItemId(), 36, 32, bounds.x + 3, 1, false);
Widget icon = createGraphic(
ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR),
-1,
tagTab.getIconItemId(),
Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT,
bounds.x + 3, 1,
false);
int clickmask = icon.getClickMask();
clickmask |= WidgetConfig.DRAG;
clickmask |= WidgetConfig.DRAG_ON;

View File

@@ -35,7 +35,7 @@ public enum CannonSpots
BLOODVELDS(new WorldPoint(2439, 9821, 0), new WorldPoint(2448, 9821, 0), new WorldPoint(2472, 9833, 0), new WorldPoint(2453, 9817, 0)),
FIRE_GIANTS(new WorldPoint(2393, 9782, 0), new WorldPoint(2412, 9776, 0), new WorldPoint(2401, 9780, 0)),
ABBERANT_SPECTRES(new WorldPoint(2456, 9791, 0)),
ABERRANT_SPECTRES(new WorldPoint(2456, 9791, 0)),
HELLHOUNDS(new WorldPoint(2431, 9776, 0), new WorldPoint(2413, 9786, 0), new WorldPoint(2783, 9686, 0), new WorldPoint(3198, 10071, 0)),
BLACK_DEMONS(new WorldPoint(2859, 9778, 0), new WorldPoint(2841, 9791, 0)),
ELVES(new WorldPoint(2044, 4635, 0)),

View File

@@ -37,6 +37,7 @@ import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.Experience;
import net.runelite.api.IconID;
import net.runelite.api.ItemDefinition;

View File

@@ -129,9 +129,12 @@ public class ChatNotificationsPlugin extends Plugin
{
List<String> items = Text.fromCSV(config.highlightWordsString());
String joined = items.stream()
.map(Text::escapeJagex) // we compare these strings to the raw Jagex ones
.map(Pattern::quote)
.collect(Collectors.joining("|"));
highlightMatcher = Pattern.compile("\\b(" + joined + ")\\b", Pattern.CASE_INSENSITIVE);
// To match <word> \b doesn't work due to <> not being in \w,
// so match \b or \s
highlightMatcher = Pattern.compile("(?:\\b|(?<=\\s))(" + joined + ")(?:\\b|(?=\\s))", Pattern.CASE_INSENSITIVE);
}
}
@@ -139,7 +142,6 @@ public class ChatNotificationsPlugin extends Plugin
public void onChatMessage(ChatMessage chatMessage)
{
MessageNode messageNode = chatMessage.getMessageNode();
String nodeValue = Text.removeTags(messageNode.getValue());
boolean update = false;
switch (chatMessage.getType())
@@ -202,6 +204,7 @@ public class ChatNotificationsPlugin extends Plugin
if (highlightMatcher != null)
{
String nodeValue = messageNode.getValue();
Matcher matcher = highlightMatcher.matcher(nodeValue);
boolean found = false;
StringBuffer stringBuffer = new StringBuffer();

View File

@@ -259,7 +259,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
new CrypticClue("Search the drawers in Catherby's Archery shop.", DRAWERS_350, new WorldPoint(2825, 3442, 0), "Hickton's Archery Emporium in Catherby."),
new CrypticClue("The hand ain't listening.", "The Face", new WorldPoint(3019, 3232, 0), "Talk to The Face located by the manhole just north of the Port Sarim fishing shop."),
new CrypticClue("Search the chest in the left-hand tower of Camelot Castle.", CLOSED_CHEST_25592, new WorldPoint(2748, 3495, 2), "Located on the second floor of the western tower of Camelot."),
new CrypticClue("Kill the spiritual, magic and godly whilst representing their own god", null, "Kill a spiritual mage while wearing a corresponding god item."),
new CrypticClue("Kill the spiritual, magic and godly whilst representing their own god.", null, "Kill a spiritual mage while wearing a corresponding god item."),
new CrypticClue("Anger those who adhere to Saradomin's edicts to prevent travel.", "Monk of Entrana", new WorldPoint(3042, 3236, 0), "Port Sarim Docks, try to charter a ship to Entrana with armour or weapons equipped."),
new CrypticClue("South of a river in a town surrounded by the undead, what lies beneath the furnace?", new WorldPoint(2857, 2966, 0), "Dig in front of the Shilo Village furnace."),
new CrypticClue("Talk to the Squire in the White Knights' castle in Falador.", "Squire", new WorldPoint(2977, 3343, 0), "The squire is located in the courtyard of the White Knights' Castle."),

View File

@@ -87,11 +87,11 @@ public class FaloTheBardClue extends ClueScroll implements TextClueScroll, NpcCl
new FaloTheBardClue("A shiny helmet of flight, to obtain this with melee, struggle you might.", item(ARMADYL_HELMET)),
// The wiki doesn't specify whether the trimmed dragon defender will work so I've assumed that it doesn't
new FaloTheBardClue("A sword held in the other hand, red its colour, Cyclops strength you must withstand.", item(DRAGON_DEFENDER)),
new FaloTheBardClue("A token used to kill mythical beasts, in hope of a blade or just for an xp feast.", item(WARRIOR_GUILD_TOKEN)),
new FaloTheBardClue("A token used to kill mythical beasts, in hopes of a blade or just for an xp feast.", item(WARRIOR_GUILD_TOKEN)),
new FaloTheBardClue("Green is my favorite, mature ale I do love, this takes your herblore above.", item(GREENMANS_ALEM)),
new FaloTheBardClue("It can hold down a boat or crush a goat, this object, you see, is quite heavy.", item(BARRELCHEST_ANCHOR)),
new FaloTheBardClue("It comes from the ground, underneath the snowy plain. Trolls aplenty, with what looks like a mane.", item(BASALT)),
new FaloTheBardClue("No attack to wield, only strength is required, made of obsidian but with no room for a shield.", item(TZHAARKETOM)),
new FaloTheBardClue("No attack to wield, only strength is required, made of obsidian, but with no room for a shield.", item(TZHAARKETOM)),
new FaloTheBardClue("Penance healers runners and more, obtaining this body often gives much deplore.", item(FIGHTER_TORSO)),
new FaloTheBardClue("Strangely found in a chest, many believe these gloves are the best.", item(BARROWS_GLOVES)),
new FaloTheBardClue("These gloves of white won't help you fight, but aid in cooking, they just might.", item(COOKING_GAUNTLETS)),

View File

@@ -26,6 +26,8 @@ package net.runelite.client.plugins.emojis;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.inject.Inject;
import joptsimple.internal.Strings;
@@ -52,6 +54,8 @@ import net.runelite.client.util.ImageUtil;
@Slf4j
public class EmojiPlugin extends Plugin
{
private static final Pattern TAG_REGEXP = Pattern.compile("<[^>]*>");
@Inject
private Client client;
@@ -128,7 +132,8 @@ public class EmojiPlugin extends Plugin
return;
}
final String message = chatMessage.getMessage();
final MessageNode messageNode = chatMessage.getMessageNode();
final String message = messageNode.getValue();
final String updatedMessage = updateMessage(message);
if (updatedMessage == null)
@@ -136,7 +141,6 @@ public class EmojiPlugin extends Plugin
return;
}
final MessageNode messageNode = chatMessage.getMessageNode();
messageNode.setRuneLiteFormatMessage(updatedMessage);
chatMessageManager.update(messageNode);
client.refreshChat();
@@ -169,7 +173,9 @@ public class EmojiPlugin extends Plugin
boolean editedMessage = false;
for (int i = 0; i < messageWords.length; i++)
{
final Emoji emoji = Emoji.getEmoji(messageWords[i]);
// Remove tags except for <lt> and <gt>
final String trigger = removeTags(messageWords[i]);
final Emoji emoji = Emoji.getEmoji(trigger);
if (emoji == null)
{
@@ -178,7 +184,7 @@ public class EmojiPlugin extends Plugin
final int emojiId = modIconsStart + emoji.ordinal();
messageWords[i] = "<img=" + emojiId + ">";
messageWords[i] = messageWords[i].replace(trigger, "<img=" + emojiId + ">");
editedMessage = true;
}
@@ -190,4 +196,29 @@ public class EmojiPlugin extends Plugin
return Strings.join(messageWords, " ");
}
/**
* Remove tags, except for &lt;lt&gt; and &lt;gt&gt;
*
* @return
*/
private static String removeTags(String str)
{
StringBuffer stringBuffer = new StringBuffer();
Matcher matcher = TAG_REGEXP.matcher(str);
while (matcher.find())
{
matcher.appendReplacement(stringBuffer, "");
String match = matcher.group(0);
switch (match)
{
case "<lt>":
case "<gt>":
stringBuffer.append(match);
break;
}
}
matcher.appendTail(stringBuffer);
return stringBuffer.toString();
}
}

View File

@@ -36,6 +36,7 @@ import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.ItemDefinition;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameStateChanged;
@@ -199,26 +200,26 @@ public class ExaminePlugin extends Plugin
log.debug("Got examine for {} {}: {}", pendingExamine.getType(), pendingExamine.getId(), event.getMessage());
// If it is an item, show the price of it
final ItemDefinition itemComposition;
final ItemDefinition Itemdefinition;
if (pendingExamine.getType() == ExamineType.ITEM || pendingExamine.getType() == ExamineType.ITEM_BANK_EQ)
{
final int itemId = pendingExamine.getId();
final int itemQuantity = pendingExamine.getQuantity();
itemComposition = itemManager.getItemDefinition(itemId);
Itemdefinition = itemManager.getItemDefinition(itemId);
if (itemComposition != null)
if (Itemdefinition != null)
{
final int id = itemManager.canonicalize(itemComposition.getId());
executor.submit(() -> getItemPrice(id, itemComposition, itemQuantity));
final int id = itemManager.canonicalize(Itemdefinition.getId());
executor.submit(() -> getItemPrice(id, Itemdefinition, itemQuantity));
}
}
else
{
itemComposition = null;
Itemdefinition = null;
}
// Don't submit examine info for tradeable items, which we already have from the RS item api
if (itemComposition != null && itemComposition.isTradeable())
if (Itemdefinition != null && Itemdefinition.isTradeable())
{
return;
}

View File

@@ -49,6 +49,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.GameState;
import net.runelite.api.Item;
import net.runelite.api.ItemDefinition;

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2019 Hydrox6 <ikada@protonmail.ch>
* 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.interfacestyles;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import static net.runelite.api.SpriteID.*;
import net.runelite.client.game.SpriteOverride;
@RequiredArgsConstructor
enum HealthbarOverride implements SpriteOverride
{
BACK_30PX(HEALTHBAR_DEFAULT_BACK_30PX, "back_30px.png"),
BACK_50PX(HEALTHBAR_DEFAULT_BACK_50PX, "back_30px.png"),
BACK_60PX(HEALTHBAR_DEFAULT_BACK_60PX, "back_30px.png"),
BACK_80PX(HEALTHBAR_DEFAULT_BACK_80PX, "back_90px.png"),
BACK_100PX(HEALTHBAR_DEFAULT_BACK_100PX, "back_90px.png"),
BACK_120PX(HEALTHBAR_DEFAULT_BACK_120PX, "back_90px.png"),
BACK_140PX(HEALTHBAR_DEFAULT_BACK_140PX, "back_90px.png"),
BACK_160PX(HEALTHBAR_DEFAULT_BACK_160PX, "back_90px.png"),
FRONT_30PX(HEALTHBAR_DEFAULT_FRONT_30PX, "front_30px.png"),
FRONT_50PX(HEALTHBAR_DEFAULT_FRONT_50PX, "front_30px.png"),
FRONT_60PX(HEALTHBAR_DEFAULT_FRONT_60PX, "front_30px.png"),
FRONT_80PX(HEALTHBAR_DEFAULT_FRONT_80PX, "front_90px.png"),
FRONT_100PX(HEALTHBAR_DEFAULT_FRONT_100PX, "front_90px.png"),
FRONT_120PX(HEALTHBAR_DEFAULT_FRONT_120PX, "front_90px.png"),
FRONT_140PX(HEALTHBAR_DEFAULT_FRONT_140PX, "front_90px.png"),
FRONT_160PX(HEALTHBAR_DEFAULT_FRONT_160PX, "front_90px.png");
@Getter
private final int spriteId;
private final String fileName;
@Getter
private int padding = 1;
private static final Map<Integer, HealthbarOverride> MAP;
static
{
ImmutableMap.Builder<Integer, HealthbarOverride> builder = new ImmutableMap.Builder<>();
for (HealthbarOverride override : values())
{
builder.put(override.spriteId, override);
}
MAP = builder.build();
}
static HealthbarOverride get(int spriteID)
{
return MAP.get(spriteID);
}
@Override
public String getFileName()
{
return Skin.AROUND_2010.toString() + "/healthbar/" + this.fileName;
}
}

View File

@@ -32,8 +32,6 @@ import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.HealthBar;
import net.runelite.api.HealthBarOverride;
import net.runelite.api.NodeCache;
import net.runelite.api.SpriteID;
import net.runelite.api.Sprite;
import net.runelite.api.events.BeforeMenuRender;
@@ -71,8 +69,6 @@ public class InterfaceStylesPlugin extends Plugin
@Inject
private SpriteManager spriteManager;
private HealthBarOverride healthBarOverride;
@Provides
InterfaceStylesConfig provideConfig(ConfigManager configManager)
{
@@ -92,10 +88,7 @@ public class InterfaceStylesPlugin extends Plugin
{
restoreWidgetDimensions();
removeGameframe();
healthBarOverride = null;
client.setHealthBarOverride(null);
NodeCache heathBarCache = client.getHealthBarCache();
heathBarCache.reset(); // invalidate healthbar cache so padding resets
restoreHealthBars();
});
}
@@ -117,19 +110,19 @@ public class InterfaceStylesPlugin extends Plugin
@Subscribe
public void onPostHealthBar(PostHealthBar postHealthBar)
{
if (healthBarOverride == null || !config.hdHealthBars())
if (!config.hdHealthBars())
{
return;
}
HealthBar healthBar = postHealthBar.getHealthBar();
Sprite frontSprite = healthBar.getHealthBarFrontSprite();
HealthbarOverride override = HealthbarOverride.get(healthBar.getHealthBarFrontSpriteId());
// Check if this is the health bar we are replacing
if (frontSprite == healthBarOverride.getFrontSprite() || frontSprite == healthBarOverride.getFrontSpriteLarge())
if (override != null)
{
// Increase padding to show some more green at very low hp percentages
healthBar.setPadding(1);
healthBar.setPadding(override.getPadding());
}
}
@@ -162,7 +155,7 @@ public class InterfaceStylesPlugin extends Plugin
if (skin == config.skin())
{
String file = config.skin().toString() + "/" + spriteOverride.getSpriteID() + ".png";
Sprite spritePixels = getFileSprite(file);
Sprite spritePixels = getFileSpritePixels(file);
if (spriteOverride.getSpriteID() == SpriteID.COMPASS_TEXTURE)
{
@@ -194,7 +187,7 @@ public class InterfaceStylesPlugin extends Plugin
if (widgetOverride.getSkin() == config.skin())
{
String file = config.skin().toString() + "/widget/" + widgetOverride.getName() + ".png";
Sprite spritePixels = getFileSprite(file);
Sprite spritePixels = getFileSpritePixels(file);
if (spritePixels != null)
{
@@ -218,7 +211,7 @@ public class InterfaceStylesPlugin extends Plugin
}
}
private Sprite getFileSprite(String file)
private Sprite getFileSpritePixels(String file)
{
try
{
@@ -272,31 +265,24 @@ public class InterfaceStylesPlugin extends Plugin
private void overrideHealthBars()
{
// Reset health bar cache to reset applied padding
NodeCache healthBarCache = client.getHealthBarCache();
healthBarCache.reset();
if (config.hdHealthBars())
{
String fileBase = Skin.AROUND_2010.toString() + "/healthbar/";
Sprite frontSprite = getFileSprite(fileBase + "front.png");
Sprite backSprite = getFileSprite(fileBase + "back.png");
Sprite frontSpriteLarge = getFileSprite(fileBase + "front_large.png");
Sprite backSpriteLarge = getFileSprite(fileBase + "back_large.png");
HealthBarOverride override = new HealthBarOverride(frontSprite, backSprite, frontSpriteLarge, backSpriteLarge);
healthBarOverride = override;
client.setHealthBarOverride(override);
spriteManager.addSpriteOverrides(HealthbarOverride.values());
// Reset health bar caches to apply the override
clientThread.invokeLater(client::resetHealthBarCaches);
}
else
{
healthBarOverride = null;
client.setHealthBarOverride(null);
restoreHealthBars();
}
}
private void restoreHealthBars()
{
spriteManager.removeSpriteOverrides(HealthbarOverride.values());
clientThread.invokeLater(client::resetHealthBarCaches);
}
private void restoreWidgetDimensions()
{
for (WidgetOffset widgetOffset : WidgetOffset.values())

View File

@@ -27,6 +27,7 @@ package net.runelite.client.plugins.inventorygrid;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Range;
@ConfigGroup("inventorygrid")
public interface InventoryGridConfig extends Config
@@ -60,4 +61,15 @@ public interface InventoryGridConfig extends Config
{
return true;
}
@ConfigItem(
keyName = "dragDelay",
name = "Drag Delay",
description = "Time in ms to wait after item press before showing grid"
)
@Range(min = 100)
default int dragDelay()
{
return 100;
}
}

View File

@@ -34,6 +34,7 @@ import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetItem;
@@ -45,7 +46,6 @@ import net.runelite.client.ui.overlay.OverlayPosition;
class InventoryGridOverlay extends Overlay
{
private static final int INVENTORY_SIZE = 28;
private static final int DRAG_DELAY = 5;
private static final Color HIGHLIGHT = new Color(0, 255, 0, 45);
private static final Color GRID = new Color(255, 255, 255, 45);
@@ -72,7 +72,7 @@ class InventoryGridOverlay extends Overlay
final Widget inventoryWidget = client.getWidget(WidgetInfo.INVENTORY);
if (if1DraggingWidget == null || if1DraggingWidget != inventoryWidget
|| client.getItemPressedDuration() < DRAG_DELAY)
|| client.getItemPressedDuration() < config.dragDelay() / Constants.CLIENT_TICK_LENGTH)
{
return null;
}

View File

@@ -34,6 +34,7 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemDefinition;
@@ -51,9 +52,8 @@ import net.runelite.client.ui.overlay.components.TitleComponent;
class InventoryViewerOverlay extends Overlay
{
private static final int INVENTORY_SIZE = 28;
private static final int PLACEHOLDER_WIDTH = 36;
private static final int PLACEHOLDER_HEIGHT = 32;
private static final ImageComponent PLACEHOLDER_IMAGE = new ImageComponent(new BufferedImage(PLACEHOLDER_WIDTH, PLACEHOLDER_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR));
private static final ImageComponent PLACEHOLDER_IMAGE = new ImageComponent(
new BufferedImage(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR));
private final Client client;
private final ItemManager itemManager;

View File

@@ -29,6 +29,7 @@ import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemDefinition;
@@ -194,6 +195,7 @@ class ItemPricesOverlay extends Overlay
int gePrice = 0;
int haPrice = 0;
int haProfit = 0;
final int itemHaPrice = Math.round(itemDef.getPrice() * Constants.HIGH_ALCHEMY_MULTIPLIER);
if (config.showGEPrice())
{
@@ -203,9 +205,9 @@ class ItemPricesOverlay extends Overlay
{
haPrice = itemManager.getAlchValue(id);
}
if (gePrice > 0 && haPrice > 0 && config.showAlchProfit())
if (gePrice > 0 && itemHaPrice > 0 && config.showAlchProfit())
{
haProfit = calculateHAProfit(haPrice, gePrice);
haProfit = calculateHAProfit(itemHaPrice, gePrice);
}
if (gePrice > 0 || haPrice > 0)

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.itemskeptondeath;
import com.google.common.collect.ImmutableMap;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.ItemID;
/**
* Certain Items receive a white outline by Jagex as they are always lost on death. This is sometimes incorrectly
* added to Items by Jagex as the item is actually kept in non-pvp areas of the game, such as the Rune Pouch.
*
* The white outline will be added to these items when they are lost on death.
*/
@AllArgsConstructor
@Getter
enum AlwaysLostItem
{
RUNE_POUCH(ItemID.RUNE_POUCH, true),
LOOTING_BAG(ItemID.LOOTING_BAG, false),
CLUE_BOX(ItemID.CLUE_BOX, false);
private final int itemID;
private final boolean keptOutsideOfWilderness;
private static final ImmutableMap<Integer, AlwaysLostItem> ID_MAP;
static
{
final ImmutableMap.Builder<Integer, AlwaysLostItem> map = ImmutableMap.builder();
for (final AlwaysLostItem p : values())
{
map.put(p.itemID, p);
}
ID_MAP = map.build();
}
static AlwaysLostItem getByItemID(final int itemID)
{
return ID_MAP.get(itemID);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.itemskeptondeath;
import com.google.common.collect.ImmutableSet;
import lombok.AllArgsConstructor;
import net.runelite.api.ItemID;
/**
* Some non tradeable items are kept on death inside low level wilderness (1-20) but are turned into a broken variant.
*
* The non-broken variant will be shown inside the interface.
*/
@AllArgsConstructor
enum BrokenOnDeathItem
{
// Capes
FIRE_CAPE(ItemID.FIRE_CAPE),
FIRE_MAX_CAPE(ItemID.FIRE_MAX_CAPE),
INFERNAL_CAPE(ItemID.INFERNAL_CAPE),
INFERNAL_MAX_CAPE(ItemID.INFERNAL_MAX_CAPE),
AVAS_ASSEMBLER(ItemID.AVAS_ASSEMBLER),
ASSEMBLER_MAX_CAPE(ItemID.ASSEMBLER_MAX_CAPE),
// Defenders
BRONZE_DEFENDER(ItemID.BRONZE_DEFENDER),
IRON_DEFENDER(ItemID.IRON_DEFENDER),
STEEL_DEFENDER(ItemID.STEEL_DEFENDER),
BLACK_DEFENDER(ItemID.BLACK_DEFENDER),
MITHRIL_DEFENDER(ItemID.MITHRIL_DEFENDER),
ADAMANT_DEFENDER(ItemID.ADAMANT_DEFENDER),
RUNE_DEFENDER(ItemID.RUNE_DEFENDER),
DRAGON_DEFENDER(ItemID.DRAGON_DEFENDER),
AVERNIC_DEFENDER(ItemID.AVERNIC_DEFENDER),
// Void
VOID_MAGE_HELM(ItemID.VOID_MAGE_HELM),
VOID_RANGER_HELM(ItemID.VOID_RANGER_HELM),
VOID_MELEE_HELM(ItemID.VOID_MELEE_HELM),
VOID_KNIGHT_TOP(ItemID.VOID_KNIGHT_TOP),
VOID_KNIGHT_ROBE(ItemID.VOID_KNIGHT_ROBE),
VOID_KNIGHT_GLOVES(ItemID.VOID_KNIGHT_GLOVES),
VOID_KNIGHT_MACE(ItemID.VOID_KNIGHT_MACE),
ELITE_VOID_TOP(ItemID.ELITE_VOID_TOP),
ELITE_VOID_ROBE(ItemID.ELITE_VOID_ROBE),
// Barb Assault
FIGHTER_HAT(ItemID.FIGHTER_HAT),
RANGER_HAT(ItemID.RANGER_HAT),
HEALER_HAT(ItemID.HEALER_HAT),
FIGHTER_TORSO(ItemID.FIGHTER_TORSO),
PENANCE_SKIRT(ItemID.PENANCE_SKIRT),
// Castle Wars
SARADOMIN_HALO(ItemID.SARADOMIN_HALO),
ZAMORAK_HALO(ItemID.ZAMORAK_HALO),
GUTHIX_HALO(ItemID.GUTHIX_HALO),
DECORATIVE_MAGIC_HAT(ItemID.DECORATIVE_ARMOUR_11898),
DECORATIVE_MAGIC_ROBE_TOP(ItemID.DECORATIVE_ARMOUR_11896),
DECORATIVE_MAGIC_ROBE_LEGS(ItemID.DECORATIVE_ARMOUR_11897),
DECORATIVE_RANGE_TOP(ItemID.DECORATIVE_ARMOUR_11899),
DECORATIVE_RANGE_BOTTOM(ItemID.DECORATIVE_ARMOUR_11900),
DECORATIVE_RANGE_QUIVER(ItemID.DECORATIVE_ARMOUR_11901),
GOLD_DECORATIVE_HELM(ItemID.DECORATIVE_HELM_4511),
GOLD_DECORATIVE_BODY(ItemID.DECORATIVE_ARMOUR_4509),
GOLD_DECORATIVE_LEGS(ItemID.DECORATIVE_ARMOUR_4510),
GOLD_DECORATIVE_SKIRT(ItemID.DECORATIVE_ARMOUR_11895),
GOLD_DECORATIVE_SHIELD(ItemID.DECORATIVE_SHIELD_4512),
GOLD_DECORATIVE_SWORD(ItemID.DECORATIVE_SWORD_4508);
private final int itemID;
private static final ImmutableSet<Integer> ID_SET;
static
{
final ImmutableSet.Builder<Integer> set = new ImmutableSet.Builder<>();
for (final BrokenOnDeathItem p : values())
{
set.add(p.itemID);
}
ID_SET = set.build();
}
static boolean isBrokenOnDeath(final int itemID)
{
return ID_SET.contains(itemID);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* Copyright (c) 2019, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.itemskeptondeath;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.api.ItemID;
/**
* Some items have a fixed price that is added to its default value when calculating death prices.
* These are typically imbued items, such as Berserker ring (i), to help it protect over the non-imbued variants.
*/
@AllArgsConstructor
@Getter
enum FixedPriceItem
{
IMBUED_BLACK_MASK_I(ItemID.BLACK_MASK_I, 5000),
IMBUED_BLACK_MASK_1_I(ItemID.BLACK_MASK_1_I, 5000),
IMBUED_BLACK_MASK_2_I(ItemID.BLACK_MASK_2_I, 5000),
IMBUED_BLACK_MASK_3_I(ItemID.BLACK_MASK_3_I, 5000),
IMBUED_BLACK_MASK_4_I(ItemID.BLACK_MASK_4_I, 5000),
IMBUED_BLACK_MASK_5_I(ItemID.BLACK_MASK_5_I, 5000),
IMBUED_BLACK_MASK_6_I(ItemID.BLACK_MASK_6_I, 5000),
IMBUED_BLACK_MASK_7_I(ItemID.BLACK_MASK_7_I, 5000),
IMBUED_BLACK_MASK_8_I(ItemID.BLACK_MASK_8_I, 5000),
IMBUED_BLACK_MASK_9_I(ItemID.BLACK_MASK_9_I, 5000),
IMBUED_BLACK_MASK_10_I(ItemID.BLACK_MASK_10_I, 5000),
IMBUED_SLAYER_HELMET_I(ItemID.SLAYER_HELMET_I, 1000),
IMBUED_BLACK_SLAYER_HELMET_I(ItemID.BLACK_SLAYER_HELMET_I, 1000),
IMBUED_PURPLE_SLAYER_HELMET_I(ItemID.PURPLE_SLAYER_HELMET_I, 1000),
IMBUED_RED_SLAYER_HELMET_I(ItemID.RED_SLAYER_HELMET_I, 1000),
IMBUED_GREEN_SLAYER_HELMET_I(ItemID.GREEN_SLAYER_HELMET_I, 1000),
IMBUED_TURQUOISE_SLAYER_HELMET_I(ItemID.TURQUOISE_SLAYER_HELMET_I, 1000),
IMBUED_HYDRA_SLAYER_HELMET_I(ItemID.HYDRA_SLAYER_HELMET_I, 1000),
IMBUED_ARCHERS_RING_I(ItemID.ARCHERS_RING_I, 2000),
IMBUED_BERSERKER_RING_I(ItemID.BERSERKER_RING_I, 2000),
IMBUED_SEERS_RING_I(ItemID.SEERS_RING_I, 2000),
IMBUED_RING_OF_THE_GODS_I(ItemID.RING_OF_THE_GODS_I, 2000),
IMBUED_TREASONOUS_RING_I(ItemID.TREASONOUS_RING_I, 2000),
IMBUED_TYRANNICAL_RING_I(ItemID.TYRANNICAL_RING_I, 2000);
private final int itemId;
private final int offset;
private static final Map<Integer, FixedPriceItem> FIXED_ITEMS;
static
{
final ImmutableMap.Builder<Integer, FixedPriceItem> map = ImmutableMap.builder();
for (final FixedPriceItem p : values())
{
map.put(p.itemId, p);
}
FIXED_ITEMS = map.build();
}
@Nullable
static FixedPriceItem find(int itemId)
{
return FIXED_ITEMS.get(itemId);
}
}

View File

@@ -0,0 +1,610 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* 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.itemskeptondeath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.FontID;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemComposition;
import net.runelite.api.ItemContainer;
import net.runelite.api.ItemID;
import net.runelite.api.ScriptID;
import net.runelite.api.SkullIcon;
import net.runelite.api.SpriteID;
import net.runelite.api.Varbits;
import net.runelite.api.WorldType;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.vars.AccountType;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.StackFormatter;
@PluginDescriptor(
name = "Items Kept on Death",
description = "Updates the Items Kept on Death interface to be more accurate",
enabledByDefault = false
)
@Slf4j
public class ItemsKeptOnDeathPlugin extends Plugin
{
private static final int DEEP_WILDY = 20;
private static final Pattern WILDERNESS_LEVEL_PATTERN = Pattern.compile("^Level: (\\d+).*");
// Item Container helpers
private static final int MAX_ROW_ITEMS = 8;
private static final int ITEM_X_OFFSET = 5;
private static final int ITEM_Y_OFFSET = 25;
private static final int ITEM_X_STRIDE = 38;
private static final int ITEM_Y_STRIDE = 38;
private static final int ORIGINAL_LOST_HEIGHT = 209;
private static final int ORIGINAL_LOST_Y = 107;
// Information panel text helpers
private static final String LINE_BREAK = "<br>";
private static final int INFORMATION_CONTAINER_HEIGHT = 183;
private static final int FONT_COLOR = 0xFF981F;
// Button Images
private static final int PROTECT_ITEM_SPRITE_ID = SpriteID.PRAYER_PROTECT_ITEM;
private static final int SKULL_SPRITE_ID = SpriteID.PLAYER_KILLER_SKULL_523;
private static final int SWORD_SPRITE_ID = SpriteID.MULTI_COMBAT_ZONE_CROSSED_SWORDS;
private static final int SKULL_2_SPRITE_ID = SpriteID.FIGHT_PITS_WINNER_SKULL_RED;
@Inject
private Client client;
@Inject
private ItemManager itemManager;
private WidgetButton deepWildyButton;
private WidgetButton lowWildyButton;
private boolean isSkulled;
private boolean protectingItem;
private int wildyLevel;
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent event)
{
if (event.getEventName().equals("itemsKeptOnDeath"))
{
// The script in charge of building the Items Kept on Death interface has finished running.
// Make all necessary changes now.
// Players inside Safe Areas (POH/Clan Wars) or playing DMM see the default interface
if (isInSafeArea() || client.getWorldType().contains(WorldType.DEADMAN))
{
return;
}
syncSettings();
createWidgetButtons();
rebuildItemsKeptOnDeathInterface();
final Widget keptText = client.getWidget(WidgetInfo.ITEMS_KEPT_ON_DEATH_TEXT);
keptText.setText("Items you will keep on death:");
final Widget lostText = client.getWidget(WidgetInfo.ITEMS_LOST_ON_DEATH_TEXT);
lostText.setText("Items you will lose on death:");
}
}
// Sync user settings
private void syncSettings()
{
final SkullIcon s = client.getLocalPlayer().getSkullIcon();
// Ultimate iron men deaths are treated like they are always skulled
isSkulled = s == SkullIcon.SKULL || isUltimateIronman();
protectingItem = client.getVar(Varbits.PRAYER_PROTECT_ITEM) == 1;
syncWildernessLevel();
}
private void syncWildernessLevel()
{
if (client.getVar(Varbits.IN_WILDERNESS) != 1)
{
// if they are in a PvP world and not in a safe zone act like in lvl 1 wildy
if (isInPvpWorld() && !isInPvPSafeZone())
{
wildyLevel = 1;
return;
}
wildyLevel = -1;
return;
}
final Widget wildernessLevelWidget = client.getWidget(WidgetInfo.PVP_WILDERNESS_LEVEL);
if (wildernessLevelWidget == null)
{
wildyLevel = -1;
return;
}
final String wildernessLevelText = wildernessLevelWidget.getText();
final Matcher m = WILDERNESS_LEVEL_PATTERN.matcher(wildernessLevelText);
if (!m.matches())
{
wildyLevel = -1;
return;
}
wildyLevel = Integer.parseInt(m.group(1));
}
private boolean isInPvpWorld()
{
final EnumSet<WorldType> world = client.getWorldType();
return world.contains(WorldType.PVP);
}
private boolean isProtectItemAllowed()
{
return !client.getWorldType().contains(WorldType.HIGH_RISK)
&& !isUltimateIronman();
}
private boolean isInPvPSafeZone()
{
final Widget w = client.getWidget(WidgetInfo.PVP_WORLD_SAFE_ZONE);
return w != null && !w.isHidden();
}
private boolean isInSafeArea()
{
final Widget w = client.getWidget(WidgetInfo.ITEMS_KEPT_SAFE_ZONE_CONTAINER);
return w != null && !w.isHidden();
}
private boolean isUltimateIronman()
{
return client.getAccountType() == AccountType.ULTIMATE_IRONMAN;
}
private int getDefaultItemsKept()
{
final int count = isSkulled ? 0 : 3;
return count + (protectingItem ? 1 : 0);
}
private void rebuildItemsKeptOnDeathInterface()
{
final Widget lost = client.getWidget(WidgetInfo.ITEMS_LOST_ON_DEATH_CONTAINER);
final Widget kept = client.getWidget(WidgetInfo.ITEMS_KEPT_ON_DEATH_CONTAINER);
if (lost == null || kept == null)
{
return;
}
lost.deleteAllChildren();
kept.deleteAllChildren();
// Grab all items on player
final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY);
final Item[] inv = inventory == null ? new Item[0] : inventory.getItems();
final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT);
final Item[] equip = equipment == null ? new Item[0] : equipment.getItems();
final List<Item> items = new ArrayList<>();
Collections.addAll(items, inv);
Collections.addAll(items, equip);
// Sort by item price
items.sort(Comparator.comparing(this::getDeathPrice).reversed());
boolean hasAlwaysLost = false;
int keepCount = getDefaultItemsKept();
final List<Widget> keptItems = new ArrayList<>();
final List<Widget> lostItems = new ArrayList<>();
for (final Item i : items)
{
final int id = i.getId();
int itemQuantity = i.getQuantity();
if (id == -1)
{
continue;
}
final ItemComposition c = itemManager.getItemComposition(i.getId());
// Bonds are always kept and do not count towards the limit.
if (id == ItemID.OLD_SCHOOL_BOND || id == ItemID.OLD_SCHOOL_BOND_UNTRADEABLE)
{
final Widget itemWidget = createItemWidget(kept, itemQuantity, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName());
keptItems.add(itemWidget);
continue;
}
// Certain items are always lost on death and have a white outline which we need to add
final AlwaysLostItem alwaysLostItem = AlwaysLostItem.getByItemID(i.getId());
if (alwaysLostItem != null)
{
// Some of these items are kept on death (outside wildy), like the Rune pouch. Ignore them
if (!alwaysLostItem.isKeptOutsideOfWilderness() || wildyLevel > 0)
{
final Widget itemWidget = createItemWidget(lost, itemQuantity, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 0, itemQuantity, c.getName());
itemWidget.setBorderType(2); // white outline
lostItems.add(itemWidget);
hasAlwaysLost = true;
continue;
}
// the rune pouch is "always lost" but its kept outside of pvp, and does not count towards your keep count
}
else if (keepCount > 0)
{
// Keep most valuable items regardless of trade-ability.
if (i.getQuantity() > keepCount)
{
final Widget itemWidget = createItemWidget(kept, keepCount, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, keepCount, c.getName());
keptItems.add(itemWidget);
itemQuantity -= keepCount;
keepCount = 0;
// Fall through to below to drop the rest of the stack
}
else
{
final Widget itemWidget = createItemWidget(kept, itemQuantity, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName());
keptItems.add(itemWidget);
keepCount -= i.getQuantity();
continue;
}
}
// Items are kept if:
// 1) is not tradeable
// 2) is under the deep wilderness line
// 3) is outside of the wilderness, or item has a broken form
if (!Pets.isPet(id)
&& !isTradeable(c) && wildyLevel <= DEEP_WILDY
&& (wildyLevel <= 0 || BrokenOnDeathItem.isBrokenOnDeath(i.getId())))
{
final Widget itemWidget = createItemWidget(kept, itemQuantity, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 1, itemQuantity, c.getName());
keptItems.add(itemWidget);
}
else
{
// Otherwise, the item is lost
final Widget itemWidget = createItemWidget(lost, itemQuantity, c);
itemWidget.setOnOpListener(ScriptID.DEATH_KEEP_ITEM_EXAMINE, 0, itemQuantity, c.getName());
lostItems.add(itemWidget);
}
}
int rows = (keptItems.size() + MAX_ROW_ITEMS - 1) / MAX_ROW_ITEMS;
// Show an empty row if there isn't anything
if (rows > 0)
{
// ORIGINAL_LOST_Y/HEIGHT includes a row already
rows--;
}
// Adjust items lost container position if new rows were added to kept items container
lost.setOriginalY(ORIGINAL_LOST_Y + (rows * ITEM_Y_STRIDE));
lost.setOriginalHeight(ORIGINAL_LOST_HEIGHT - (rows * ITEM_Y_STRIDE));
positionWidgetItems(kept, keptItems);
positionWidgetItems(lost, lostItems);
updateKeptWidgetInfoText(hasAlwaysLost, keptItems, lostItems);
}
/**
* Get the price of an item
* @param item
* @return
*/
private int getDeathPrice(Item item)
{
int itemId = item.getId();
// Unnote/unplaceholder item
int canonicalizedItemId = itemManager.canonicalize(itemId);
int exchangePrice = itemManager.getItemPrice(canonicalizedItemId);
if (exchangePrice == 0)
{
final ItemComposition c1 = itemManager.getItemComposition(canonicalizedItemId);
exchangePrice = c1.getPrice();
}
else
{
// Some items have artifically applied death prices - such as ring imbues
// which are +2k over the non imbues. Check if the item has a fixed price.
FixedPriceItem fixedPrice = FixedPriceItem.find(canonicalizedItemId);
if (fixedPrice != null)
{
// Apply fixed price offset
exchangePrice += fixedPrice.getOffset();
}
}
return exchangePrice;
}
/**
* Position a list of widget items in the parent container
*/
private static void positionWidgetItems(final Widget parent, final List<Widget> widgets)
{
int startingIndex = 0;
for (final Widget w : widgets)
{
final int originalX = ITEM_X_OFFSET + ((startingIndex % MAX_ROW_ITEMS) * ITEM_X_STRIDE);
final int originalY = ITEM_Y_OFFSET + ((startingIndex / MAX_ROW_ITEMS) * ITEM_Y_STRIDE);
w.setOriginalX(originalX);
w.setOriginalY(originalY);
w.revalidate();
++startingIndex;
}
parent.revalidate();
}
/**
* Creates the text to be displayed in the right side of the interface based on current selections
*/
private String getInfoText(final boolean hasAlwaysLost)
{
final StringBuilder sb = new StringBuilder();
if (isUltimateIronman())
{
sb.append("You are an <col=FFFFFF>UIM<col=FF981F> which means <col=FFFFFF>0<col=FF981F> items are protected by default");
}
else
{
sb.append("<col=FFFFFF>3<col=FF981F> items protected by default");
if (isSkulled)
{
sb.append(LINE_BREAK)
.append("<col=ff3333>PK skull<col=ff981f> -3");
}
if (protectingItem)
{
sb.append(LINE_BREAK)
.append("<col=ff3333>Protect Item prayer<col=ff981f> +1");
}
sb.append(LINE_BREAK)
.append(String.format("Actually protecting <col=FFFFFF>%s<col=FF981F> items", getDefaultItemsKept()));
}
if (wildyLevel < 1)
{
sb.append(LINE_BREAK)
.append(LINE_BREAK)
.append("You will have 1 hour to retrieve your lost items.");
}
if (hasAlwaysLost)
{
sb.append(LINE_BREAK)
.append(LINE_BREAK)
.append("Items with a <col=ffffff>white outline<col=ff981f> will always be lost.");
}
sb.append(LINE_BREAK)
.append(LINE_BREAK)
.append("Untradeable items are kept on death in non-pvp scenarios.");
return sb.toString();
}
/**
* Updates the information panel based on the item containers
*/
private void updateKeptWidgetInfoText(final boolean hasAlwaysLost, final List<Widget> keptItems, final List<Widget> lostItems)
{
// Add Information text widget
final Widget textWidget = findOrCreateInfoText();
textWidget.setText(getInfoText(hasAlwaysLost));
textWidget.revalidate();
// Update Items lost total value
long total = 0;
for (final Widget w : lostItems)
{
int cid = itemManager.canonicalize(w.getItemId());
int price = itemManager.getItemPrice(cid);
if (price == 0)
{
// Default to alch price
price = (int) (itemManager.getItemComposition(cid).getPrice() * Constants.HIGH_ALCHEMY_MULTIPLIER);
}
total += (long) price * w.getItemQuantity();
}
final Widget lostValue = client.getWidget(WidgetInfo.ITEMS_LOST_VALUE);
lostValue.setText(StackFormatter.quantityToStackSize(total) + " gp");
// Update Max items kept
final Widget max = client.getWidget(WidgetInfo.ITEMS_KEPT_MAX);
final int keptQty = keptItems.stream().mapToInt(Widget::getItemQuantity).sum();
max.setText(String.format("<col=ffcc33>Max items kept on death:<br><br><col=ffcc33>~ %d ~", keptQty));
}
/**
* Check if an item is tradeable to another player
*
* @param c The item
* @return
*/
private static boolean isTradeable(final ItemComposition c)
{
// ItemComposition:: isTradeable checks if they are traded on the grand exchange, some items are trade-able but not via GE
if (c.getNote() != -1
|| c.getLinkedNoteId() != -1
|| c.isTradeable())
{
return true;
}
final int id = c.getId();
switch (id)
{
case ItemID.COINS_995:
case ItemID.PLATINUM_TOKEN:
return true;
default:
return false;
}
}
private Widget findOrCreateInfoText()
{
// The text was on the ITEMS_KEPT_INFORMATION_CONTAINER widget - but now that it is a layer,
// we need to create a child widget to hold the text
final Widget parent = client.getWidget(WidgetInfo.ITEMS_KEPT_INFORMATION_CONTAINER);
// Use the text TEXT widget if it already exists. It should be the last child of the parent
final Widget[] children = parent.getChildren();
if (children != null && children.length > 0)
{
final Widget w = parent.getChild(children.length - 1);
if (w != null && w.getType() == WidgetType.TEXT)
{
log.debug("Reusing old text widget");
return w;
}
}
log.debug("Creating new text widget");
final Widget w = parent.createChild(-1, WidgetType.TEXT);
// Position under buttons taking remaining space
w.setOriginalWidth(parent.getOriginalWidth());
w.setOriginalHeight(INFORMATION_CONTAINER_HEIGHT - parent.getOriginalHeight());
w.setOriginalY(parent.getOriginalHeight());
w.setFontId(FontID.PLAIN_11);
w.setTextShadowed(true);
w.setTextColor(FONT_COLOR);
// Need to adjust parent height so text is visible
parent.setOriginalHeight(INFORMATION_CONTAINER_HEIGHT);
parent.revalidate();
return w;
}
private void createWidgetButtons()
{
final Widget parent = client.getWidget(WidgetInfo.ITEMS_KEPT_INFORMATION_CONTAINER);
// Change the information container from a text widget to a layer
parent.setType(WidgetType.LAYER);
parent.deleteAllChildren();
// Ultimate Iron men are always skulled and can't use the protect item prayer
WidgetButton protectItemButton = isProtectItemAllowed()
? new WidgetButton(parent, "Protect Item Prayer", PROTECT_ITEM_SPRITE_ID, protectingItem, selected ->
{
protectingItem = selected;
rebuildItemsKeptOnDeathInterface();
}) : null;
WidgetButton skulledButton = !isUltimateIronman()
? new WidgetButton(parent, "Skulled", SKULL_SPRITE_ID, isSkulled, selected ->
{
isSkulled = selected;
rebuildItemsKeptOnDeathInterface();
}) : null;
lowWildyButton = new WidgetButton(parent, "Low Wildy (1-20)", SWORD_SPRITE_ID, wildyLevel > 0 && wildyLevel <= DEEP_WILDY, selected ->
{
if (!selected)
{
syncWildernessLevel();
}
else
{
wildyLevel = 1;
deepWildyButton.setSelected(false);
}
rebuildItemsKeptOnDeathInterface();
});
deepWildyButton = new WidgetButton(parent, "Deep Wildy (21+)", SKULL_2_SPRITE_ID, wildyLevel > DEEP_WILDY, selected ->
{
if (!selected)
{
syncWildernessLevel();
}
else
{
wildyLevel = DEEP_WILDY + 1;
lowWildyButton.setSelected(false);
}
rebuildItemsKeptOnDeathInterface();
});
parent.revalidate();
WidgetButton.layoutButtonsToContainer(parent, protectItemButton, skulledButton, lowWildyButton, deepWildyButton);
}
/**
* Creates an Item Widget for use inside the Kept on Death Interface
*
* @param qty Amount of item
* @param c Items Composition
* @return
*/
private static Widget createItemWidget(final Widget parent, final int qty, final ItemComposition c)
{
final Widget itemWidget = parent.createChild(-1, WidgetType.GRAPHIC);
itemWidget.setItemId(c.getId());
itemWidget.setItemQuantity(qty);
itemWidget.setHasListener(true);
itemWidget.setOriginalWidth(Constants.ITEM_SPRITE_WIDTH);
itemWidget.setOriginalHeight(Constants.ITEM_SPRITE_HEIGHT);
itemWidget.setBorderType(1);
itemWidget.setAction(1, String.format("Item: <col=ff981f>%s", c.getName()));
return itemWidget;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2018 Abex
* 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.itemskeptondeath;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import static net.runelite.api.ItemID.*;
final class Pets
{
private Pets()
{
}
private static final Set<Integer> PETS = ImmutableSet.of(
BABY_MOLE,
PRINCE_BLACK_DRAGON,
PET_CORPOREAL_CRITTER, PET_DARK_CORE,
JALNIBREK, TZREKZUK,
KALPHITE_PRINCESS, KALPHITE_PRINCESS_12654,
LIL_ZIK,
SKOTOS,
PET_SNAKELING, PET_SNAKELING_12939, PET_SNAKELING_12940,
TZREKJAD,
VORKI,
OLMLET, PUPPADILE, TEKTINY, VANGUARD, VASA_MINIRIO, VESPINA,
PET_DAGANNOTH_PRIME, PET_DAGANNOTH_REX, PET_DAGANNOTH_SUPREME,
PET_GENERAL_GRAARDOR, PET_KRIL_TSUTSAROTH, PET_KREEARRA, PET_ZILYANA,
ABYSSAL_ORPHAN,
HELLPUPPY,
PET_KRAKEN,
MIDNIGHT, NOON,
PET_SMOKE_DEVIL, PET_SMOKE_DEVIL_22663,
IKKLE_HYDRA, IKKLE_HYDRA_22748, IKKLE_HYDRA_22750, IKKLE_HYDRA_22752,
CALLISTO_CUB,
PET_CHAOS_ELEMENTAL,
SCORPIAS_OFFSPRING,
VENENATIS_SPIDERLING,
VETION_JR, VETION_JR_13180,
BABY_CHINCHOMPA, BABY_CHINCHOMPA_13324, BABY_CHINCHOMPA_13325, BABY_CHINCHOMPA_13326,
BEAVER,
GIANT_SQUIRREL,
HERON,
RIFT_GUARDIAN, RIFT_GUARDIAN_20667, RIFT_GUARDIAN_20669, RIFT_GUARDIAN_20671, RIFT_GUARDIAN_20673, RIFT_GUARDIAN_20675,
RIFT_GUARDIAN_20677, RIFT_GUARDIAN_20679, RIFT_GUARDIAN_20681, RIFT_GUARDIAN_20683, RIFT_GUARDIAN_20685, RIFT_GUARDIAN_20687,
RIFT_GUARDIAN_20689, RIFT_GUARDIAN_20691, RIFT_GUARDIAN_21990,
ROCK_GOLEM, ROCK_GOLEM_21187, ROCK_GOLEM_21188, ROCK_GOLEM_21189, ROCK_GOLEM_21190, ROCK_GOLEM_21191, ROCK_GOLEM_21192,
ROCK_GOLEM_21193, ROCK_GOLEM_21194, ROCK_GOLEM_21195, ROCK_GOLEM_21196, ROCK_GOLEM_21197, ROCK_GOLEM_21340, ROCK_GOLEM_21358,
ROCK_GOLEM_21359, ROCK_GOLEM_21360,
ROCKY,
TANGLEROOT,
PET_KITTEN, PET_KITTEN_1556, PET_KITTEN_1557, PET_KITTEN_1558, PET_KITTEN_1559, PET_KITTEN_1560,
PET_CAT, PET_CAT_1562, PET_CAT_1563, PET_CAT_1564, PET_CAT_1565, PET_CAT_1566, PET_CAT_1567, PET_CAT_1568, PET_CAT_1569,
PET_CAT_1570, PET_CAT_1571, PET_CAT_1572,
LAZY_CAT, LAZY_CAT_6550, LAZY_CAT_6551, LAZY_CAT_6552, LAZY_CAT_6553, LAZY_CAT_6554,
WILY_CAT, WILY_CAT_6556, WILY_CAT_6557, WILY_CAT_6558, WILY_CAT_6559, WILY_CAT_6560,
OVERGROWN_HELLCAT, HELL_CAT, HELLKITTEN, LAZY_HELL_CAT, WILY_HELLCAT,
BLOODHOUND,
CHOMPY_CHICK,
HERBI,
PET_PENANCE_QUEEN,
PHOENIX
);
public static boolean isPet(int id)
{
return PETS.contains(id);
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.itemskeptondeath;
import net.runelite.api.ScriptEvent;
import net.runelite.api.SpriteID;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetType;
class WidgetButton
{
private static final int ICON_HEIGHT = 26;
private static final int ICON_WIDTH = 26;
private static final int BACKGROUND_HEIGHT = 32;
private static final int BACKGROUND_WIDTH = 32;
private static final int PADDING = 5;
private static final int ICON_PADDING = (BACKGROUND_HEIGHT - ICON_HEIGHT) / 2;
private static final int BACKGROUND_SPRITE_ID = SpriteID.EQUIPMENT_SLOT_TILE;
private static final int SELECTED_BACKGROUND_SPRITE_ID = SpriteID.EQUIPMENT_SLOT_SELECTED;
@FunctionalInterface
public interface WidgetButtonCallback
{
void run(boolean newState);
}
private final Widget parent;
private final String name;
private final int spriteID;
private boolean selected;
private final WidgetButtonCallback callback;
private Widget icon;
private Widget background;
WidgetButton(
final Widget parent,
final String name,
final int spriteID,
final boolean selectedStartState,
final WidgetButtonCallback callback)
{
this.parent = parent;
this.name = name;
this.spriteID = spriteID;
this.selected = selectedStartState;
this.callback = callback;
createBackgroundWidget();
createIconWidget();
}
private void createBackgroundWidget()
{
background = createWidget();
background.setOriginalWidth(BACKGROUND_WIDTH);
background.setOriginalHeight(BACKGROUND_HEIGHT);
syncBackgroundSprite();
}
private void createIconWidget()
{
icon = createWidget();
icon.setAction(1, "Toggle:");
icon.setOnOpListener((JavaScriptCallback) this::onButtonClicked);
icon.setOnMouseRepeatListener((JavaScriptCallback) e -> e.getSource().setOpacity(120));
icon.setOnMouseLeaveListener((JavaScriptCallback) e -> e.getSource().setOpacity(0));
icon.setHasListener(true);
icon.setSpriteId(spriteID);
}
private Widget createWidget()
{
final Widget w = parent.createChild(-1, WidgetType.GRAPHIC);
w.setOriginalWidth(ICON_WIDTH);
w.setOriginalHeight(ICON_HEIGHT);
w.setName("<col=ff981f>" + this.name);
return w;
}
public void setSelected(boolean selected)
{
this.selected = selected;
syncBackgroundSprite();
}
private void syncBackgroundSprite()
{
background.setSpriteId(selected ? SELECTED_BACKGROUND_SPRITE_ID : BACKGROUND_SPRITE_ID);
}
/**
* Adds the collection of WidgetButtons to the container overriding any existing children.
*
* @param container Widget to add buttons too
* @param buttons buttons to add
*/
static void layoutButtonsToContainer(final Widget container, final WidgetButton... buttons)
{
// Each button has two widgets, Icon and Background
final int xIncrement = BACKGROUND_WIDTH + PADDING;
final int yIncrement = BACKGROUND_HEIGHT + PADDING;
int maxRowItems = container.getWidth() / xIncrement;
// Ensure at least 1 button per row
maxRowItems = maxRowItems < 1 ? 1 : maxRowItems;
int index = 0;
for (final WidgetButton w : buttons)
{
if (w == null)
{
continue;
}
final int originalX = ((index % maxRowItems) * xIncrement);
final int originalY = ((index / maxRowItems) * yIncrement);
w.background.setOriginalX(originalX);
w.background.setOriginalY(originalY);
w.background.revalidate();
// Icon must be padded to center inside image
w.icon.setOriginalX(originalX + ICON_PADDING);
w.icon.setOriginalY(originalY + ICON_PADDING);
w.icon.revalidate();
index++;
}
final int numButtons = index;
final int rows = 1 + (numButtons > maxRowItems ? numButtons / maxRowItems : 0);
container.setOriginalHeight(yIncrement * rows);
container.revalidate();
}
private void onButtonClicked(ScriptEvent scriptEvent)
{
setSelected(!selected);
callback.run(selected);
}
}

View File

@@ -92,6 +92,16 @@ public interface ItemStatConfig extends Config
return false;
}
@ConfigItem(
keyName = "showWeight",
name = "Show Weight",
description = "Show weight in tooltip"
)
default boolean showWeight()
{
return true;
}
@ConfigItem(
keyName = "colorBetterUncapped",
name = "Better (Uncapped)",

View File

@@ -189,7 +189,10 @@ public class ItemStatOverlay extends Overlay
private String buildStatBonusString(ItemStats s)
{
final StringBuilder b = new StringBuilder();
b.append(getChangeString("Weight", s.getWeight(), true, false));
if (config.showWeight())
{
b.append(getChangeString("Weight", s.getWeight(), true, false));
}
ItemStats other = null;
final ItemEquipmentStats currentEquipment = s.getEquipment();

View File

@@ -35,6 +35,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.FontID;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
@@ -232,8 +233,8 @@ public class ItemStatPlugin extends Plugin
Widget icon = invContainer.createChild(-1, WidgetType.GRAPHIC);
icon.setOriginalX(8);
icon.setOriginalY(yPos);
icon.setOriginalWidth(36);
icon.setOriginalHeight(32);
icon.setOriginalWidth(Constants.ITEM_SPRITE_WIDTH);
icon.setOriginalHeight(Constants.ITEM_SPRITE_HEIGHT);
icon.setItemId(id);
icon.setItemQuantityMode(0);
icon.setBorderType(1);

View File

@@ -47,7 +47,7 @@ class MiningOverlay extends Overlay
{
// Range of Motherlode vein respawn time not 100% confirmed but based on observation
@Getter(AccessLevel.PACKAGE)
private static final int ORE_VEIN_MAX_RESPAWN_TIME = 123;
public static final int ORE_VEIN_MAX_RESPAWN_TIME = 123;
private static final int ORE_VEIN_MIN_RESPAWN_TIME = 90;
private static final float ORE_VEIN_RANDOM_PERCENT_THRESHOLD = (float) ORE_VEIN_MIN_RESPAWN_TIME / ORE_VEIN_MAX_RESPAWN_TIME;
private static final Color DARK_GREEN = new Color(0, 100, 0);

View File

@@ -168,6 +168,7 @@ public class MiningPlugin extends Plugin
case ORE_VEIN_26663: // Motherlode vein
case ORE_VEIN_26664: // Motherlode vein
{
// If the vein respawns before the timer is up, remove it
final WorldPoint point = object.getWorldLocation();
respawns.removeIf(rockRespawn -> rockRespawn.getWorldPoint().equals(point));
break;

View File

@@ -49,6 +49,7 @@ import static net.runelite.api.ObjectID.ROCKS_11376;
import static net.runelite.api.ObjectID.ROCKS_11377;
import static net.runelite.api.ObjectID.ROCKS_11386;
import static net.runelite.api.ObjectID.ROCKS_11387;
import static net.runelite.api.ObjectID.ASH_PILE;
enum Rock
{
@@ -98,8 +99,9 @@ enum Rock
return inMiningGuild ? Duration.ofMinutes(6) : super.respawnTime;
}
},
ORE_VEIN(Duration.ofSeconds(MiningOverlay.getORE_VEIN_MAX_RESPAWN_TIME()), 150),
AMETHYST(Duration.ofSeconds(75), 120);
ORE_VEIN(Duration.ofSeconds(MiningOverlay.ORE_VEIN_MAX_RESPAWN_TIME), 150),
AMETHYST(Duration.ofSeconds(75), 120),
ASH_VEIN(Duration.ofSeconds(30), 0, ASH_PILE);
private static final Map<Integer, Rock> ROCKS;

View File

@@ -784,7 +784,7 @@ public class SlayerPlugin extends Plugin
private boolean doubleTroubleExtraKill()
{
return WorldPoint.fromLocalInstance(client, client.getLocalPlayer().getLocalLocation()).getRegionID() == GROTESQUE_GUARDIANS_REGION &&
SlayerUnlock.GROTESQUE_GARDIAN_DOUBLE_COUNT.isEnabled(client);
SlayerUnlock.GROTESQUE_GUARDIAN_DOUBLE_COUNT.isEnabled(client);
}
// checks if any contiguous subsequence of seq0 exactly matches the String toMatch

View File

@@ -126,13 +126,13 @@ public class TimersPlugin extends Plugin
private static final String CANNON_REPAIR_MESSAGE = "You repair your cannon, restoring it to working order.";
private static final String CHARGE_EXPIRED_MESSAGE = "<col=ef1020>Your magical charge fades away.</col>";
private static final String CHARGE_MESSAGE = "<col=ef1020>You feel charged with magic power.</col>";
private static final String DEADMAN_HALF_TELEBLOCK_MESSAGE = "<col=4f006f>A teleblock spell has been cast on you. It will expire in 1 minute, 15 seconds.</col>";
private static final String DEADMAN_HALF_TELEBLOCK_MESSAGE = "<col=4f006f>A Tele Block spell has been cast on you. It will expire in 1 minute, 15 seconds.</col>";
private static final String EXTENDED_ANTIFIRE_DRINK_MESSAGE = "You drink some of your extended antifire potion.";
private static final String EXTENDED_SUPER_ANTIFIRE_DRINK_MESSAGE = "You drink some of your extended super antifire potion.";
private static final String FROZEN_MESSAGE = "<col=ef1020>You have been frozen!</col>";
private static final String FULL_TELEBLOCK_MESSAGE = "<col=4f006f>A teleblock spell has been cast on you. It will expire in 5 minutes, 0 seconds.</col>";
private static final String FULL_TELEBLOCK_MESSAGE = "<col=4f006f>A Tele Block spell has been cast on you. It will expire in 5 minutes, 0 seconds.</col>";
private static final String GOD_WARS_ALTAR_MESSAGE = "you recharge your prayer.";
private static final String HALF_TELEBLOCK_MESSAGE = "<col=4f006f>A teleblock spell has been cast on you. It will expire in 2 minutes, 30 seconds.</col>";
private static final String HALF_TELEBLOCK_MESSAGE = "<col=4f006f>A Tele Block spell has been cast on you. It will expire in 2 minutes, 30 seconds.</col>";
private static final String IMBUED_HEART_READY_MESSAGE = "<col=ef1020>Your imbued heart has regained its magical power.</col>";
private static final String IMBUED_HEART_NOTREADY_MESSAGE = "The heart is still drained of its power.";
private static final String MAGIC_IMBUE_EXPIRED_MESSAGE = "Your Magic Imbue charge has ended.";

View File

@@ -35,6 +35,7 @@ import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import net.runelite.api.Constants;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
@@ -60,7 +61,7 @@ class OverviewItemPanel extends JPanel
setBorder(new EmptyBorder(7, 7, 7, 7));
JLabel iconLabel = new JLabel();
iconLabel.setMinimumSize(new Dimension(36, 32));
iconLabel.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
itemManager.getImage(tab.getItemID()).addTo(iconLabel);
add(iconLabel, BorderLayout.WEST);

View File

@@ -33,6 +33,7 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import net.runelite.api.Constants;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.components.ThinProgressBar;
@@ -58,7 +59,7 @@ public class TimeablePanel<T> extends JPanel
topContainer.setLayout(new BorderLayout());
topContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
icon.setMinimumSize(new Dimension(36, 32));
icon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
JPanel infoPanel = new JPanel();
infoPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);

View File

@@ -27,151 +27,159 @@ package net.runelite.client.plugins.worldmap;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.Quest;
// Some quests are in the same spot, but they are done in order. If multiple
// quests start in the same location, an array of quests is expected.
enum QuestStartLocation
{
//Free Quests
COOKS_ASSISTANT_RFD("Cook's Assistant", new WorldPoint(3211, 3216, 0)),
THE_CORSAIR_CURSE("The Corsair Curse", new WorldPoint(3029, 3273, 0)),
DEMON_SLAYER("Demon Slayer", new WorldPoint(3204, 3424, 0)),
DORICS_QUEST("Doric's Quest", new WorldPoint(2952, 3450, 0)),
DRAGON_SLAYER("Dragon Slayer", new WorldPoint(3190, 3362, 0)),
ERNEST_THE_CHICKEN("Ernest the Chicken", new WorldPoint(3109, 3330, 0)),
GOBLIN_DIPLOMACY("Goblin Diplomacy", new WorldPoint(2957, 3509, 0)),
IMP_CATCHER("Imp Catcher", new WorldPoint(3108, 3160, 0)),
THE_KNIGHTS_SWORD("The Knight's Sword", new WorldPoint(2976, 3342, 0)),
MISTHALIN_MYSTERY("Misthalin Mystery", new WorldPoint(3234, 3155, 0)),
PIRATES_TREASURE("Pirate's Treasure", new WorldPoint(3051, 3252, 0)),
PRINCE_ALI_RESCUE("Prince Ali Rescue", new WorldPoint(3301, 3163, 0)),
THE_RESTLESS_GHOST("The Restless Ghost", new WorldPoint(3240, 3210, 0)),
RUNE_MYSTERIES("Rune Mysteries", new WorldPoint(3210, 3220, 0)),
SHEEP_SHEARER("Sheep Shearer", new WorldPoint(3190, 3272, 0)),
SHIELD_OF_ARRAV_PHOENIX_GANG("Shield of Arrav (Phoenix Gang)", new WorldPoint(3208, 3495, 0)),
SHIELD_OF_ARRAV_BLACK_ARM_GANG("Shield of Arrav (Black Arm Gang)", new WorldPoint(3208, 3392, 0)),
VAMPIRE_SLAYER("Vampire Slayer", new WorldPoint(3096, 3266, 0)),
WITCHS_POTION("Witch's Potion", new WorldPoint(2967, 3203, 0)),
X_MARKS_THE_SPOT("X Marks the Spot", new WorldPoint(3227, 3242, 0)),
COOKS_ASSISTANT_RFD(Quest.COOKS_ASSISTANT, new WorldPoint(3211, 3216, 0)),
THE_CORSAIR_CURSE(Quest.THE_CORSAIR_CURSE, new WorldPoint(3029, 3273, 0)),
DEMON_SLAYER(Quest.DEMON_SLAYER, new WorldPoint(3204, 3424, 0)),
DORICS_QUEST(Quest.DORICS_QUEST, new WorldPoint(2952, 3450, 0)),
DRAGON_SLAYER(Quest.DRAGON_SLAYER, new WorldPoint(3190, 3362, 0)),
ERNEST_THE_CHICKEN(Quest.ERNEST_THE_CHICKEN, new WorldPoint(3109, 3330, 0)),
GOBLIN_DIPLOMACY(Quest.GOBLIN_DIPLOMACY, new WorldPoint(2957, 3509, 0)),
IMP_CATCHER(Quest.IMP_CATCHER, new WorldPoint(3108, 3160, 0)),
THE_KNIGHTS_SWORD(Quest.THE_KNIGHTS_SWORD, new WorldPoint(2976, 3342, 0)),
MISTHALIN_MYSTERY(Quest.MISTHALIN_MYSTERY, new WorldPoint(3234, 3155, 0)),
PIRATES_TREASURE(Quest.PIRATES_TREASURE, new WorldPoint(3051, 3252, 0)),
PRINCE_ALI_RESCUE(Quest.PRINCE_ALI_RESCUE, new WorldPoint(3301, 3163, 0)),
THE_RESTLESS_GHOST(Quest.THE_RESTLESS_GHOST, new WorldPoint(3240, 3210, 0)),
RUNE_MYSTERIES(Quest.RUNE_MYSTERIES, new WorldPoint(3210, 3220, 0)),
SHEEP_SHEARER(Quest.SHEEP_SHEARER, new WorldPoint(3190, 3272, 0)),
SHIELD_OF_ARRAV(Quest.SHIELD_OF_ARRAV, new WorldPoint(3208, 3495, 0)),
VAMPIRE_SLAYER(Quest.VAMPIRE_SLAYER, new WorldPoint(3096, 3266, 0)),
WITCHS_POTION(Quest.WITCHS_POTION, new WorldPoint(2967, 3203, 0)),
X_MARKS_THE_SPOT(Quest.X_MARKS_THE_SPOT, new WorldPoint(3227, 3242, 0)),
//Members' Quests
ANIMAL_MAGNETISM("Animal Magnetism", new WorldPoint(3094, 3360, 0)),
ANOTHER_SLICE_OF_HAM("Another Slice of H.A.M.", new WorldPoint(2799, 5428, 0)),
THE_ASCENT_OF_ARCEUUS("The Ascent of Arceuus", new WorldPoint(1700, 3742, 0)),
BETWEEN_A_ROCK("Between a Rock...", new WorldPoint(2823, 10168, 0)),
BIG_CHOMPY_BIRD_HUNTING("Big Chompy Bird Hunting", new WorldPoint(2629, 2981, 0)),
BIOHAZARD("Biohazard", new WorldPoint(2591, 3335, 0)),
BONE_VOYAGE("Bone Voyage", new WorldPoint(3259, 3450, 0)),
CABIN_FEVER("Cabin Fever", new WorldPoint(3674, 3496, 0)),
CLIENT_OF_KOUREND("Client of Kourend", new WorldPoint(1823, 3690, 0)),
CLOCK_TOWER("Clock Tower", new WorldPoint(2568, 3249, 0)),
COLD_WAR("Cold War", new WorldPoint(2593, 3265, 0)),
CONTACT("Contact!", new WorldPoint(3280, 2770, 0)),
CREATURE_OF_FENKENSTRAIN("Creature of Fenkenstrain", new WorldPoint(3487, 3485, 0)),
DARKNESS_OF_HALLOWVALE("Darkness of Hallowvale", new WorldPoint(3494, 9628, 0)),
DEATH_PLATEAU_TROLL_STRONGHOLD("Death Plateau & Troll Stronghold", new WorldPoint(2895, 3528, 0)),
DEATH_TO_THE_DORGESHUUN("Death to the Dorgeshuun", new WorldPoint(3316, 9613, 0)),
THE_DEPTHS_OF_DESPAIR("The Depths of Despair", new WorldPoint(1846, 3556, 0)),
DESERT_TREASURE("Desert Treasure", new WorldPoint(3177, 3043, 0)),
DEVIOUS_MINDS("Devious Minds", new WorldPoint(3405, 3492, 0)),
THE_DIG_SITE("The Dig Site", new WorldPoint(3363, 3337, 0)),
DRAGON_SLAYER_II("Dragon Slayer II", new WorldPoint(2456, 2868, 0)),
DREAM_MENTOR("Dream Mentor", new WorldPoint(2144, 10346, 0)),
DRUIDIC_RITUAL("Druidic Ritual", new WorldPoint(2916, 3484, 0)),
DWARF_CANNON("Dwarf Cannon", new WorldPoint(2566, 3461, 0)),
EADGARS_RUSE("Eadgar's Ruse", new WorldPoint(2896, 3426, 0)),
EAGLES_PEAK("Eagles' Peak", new WorldPoint(2605, 3264, 0)),
ELEMENTAL_WORKSHOP("Elemental Workshop I & II", new WorldPoint(2714, 3482, 0)),
ENAKHRAS_LAMENT("Enakhra's Lament", new WorldPoint(3190, 2926, 0)),
ENLIGHTENED_JOURNEY("Enlightened Journey", new WorldPoint(2809, 3356, 0)),
THE_EYES_OF_GLOUPHRIE("The Eyes of Glouphrie", new WorldPoint(2400, 3419, 0)),
FAIRYTALE("Fairytale I & II", new WorldPoint(3077, 3258, 0)),
FAMILY_CREST("Family Crest", new WorldPoint(3278, 3404, 0)),
THE_FEUD("The Feud", new WorldPoint(3301, 3211, 0)),
FIGHT_ARENA("Fight Arena", new WorldPoint(2565, 3199, 0)),
FISHING_CONTEST_1("Fishing Contest", new WorldPoint(2875, 3483, 0)),
FISHING_CONTEST_2("Fishing Contest", new WorldPoint(2820, 3487, 0)),
FORGETTABLE_TALE("Forgettable Tale...", new WorldPoint(2826, 10215, 0)),
THE_FORSAKEN_TOWER("The Forsaken Tower", new WorldPoint(1484, 3747, 0)),
THE_FREMENNIK_ISLES("The Fremennik Isles", new WorldPoint(2645, 3711, 0)),
THE_FREMENNIK_TRIALS("The Fremennik Trials", new WorldPoint(2657, 3669, 0)),
GARDEN_OF_TRANQUILLITY("Garden of Tranquillity", new WorldPoint(3227, 3477, 0)),
GERTRUDES_CAT_RATCATCHERS("Gertrude's Cat & Ratcatchers", new WorldPoint(3150, 3411, 0)),
GHOSTS_AHOY("Ghosts Ahoy", new WorldPoint(3677, 3510, 0)),
THE_GIANT_DWARF("The Giant Dwarf", new WorldPoint(2841, 10129, 0)),
THE_GOLEM("The Golem", new WorldPoint(3487, 3089, 0)),
THE_GRAND_TREE_MONKEY_MADNESS("The Grand Tree & Monkey Madness I & II", new WorldPoint(2466, 3497, 0)),
THE_GREAT_BRAIN_ROBBERY("The Great Brain Robbery", new WorldPoint(3681, 2963, 0)),
GRIM_TALES("Grim Tales", new WorldPoint(2890, 3454, 0)),
THE_HAND_IN_THE_SAND("The Hand in the Sand", new WorldPoint(2552, 3101, 0)),
HAUNTED_MINE("Haunted Mine", new WorldPoint(3443, 3258, 0)),
HAZEEL_CULT("Hazeel Cult", new WorldPoint(2565, 3271, 0)),
HEROES_QUEST("Heroes' Quest", new WorldPoint(2903, 3511, 0)),
HOLY_GRAIL("Holy Grail & Merlin's Crystal", new WorldPoint(2763, 3515, 0)),
HORROR_FROM_THE_DEEP("Horror from the Deep", new WorldPoint(2507, 3635, 0)),
ICTHLARINS_LITTLE_HELPER("Icthlarin's Little Helper", new WorldPoint(3314, 2849, 0)),
IN_SEARCH_OF_THE_MYREQUE("In Search of the Myreque", new WorldPoint(3502, 3477, 0)),
JUNGLE_POTION("Jungle Potion", new WorldPoint(2809, 3086, 0)),
KINGS_RANSOM("King's Ransom", new WorldPoint(2741, 3554, 0)),
LEGENDS_QUEST("Legends' Quest", new WorldPoint(2725, 3367, 0)),
LOST_CITY("Lost City", new WorldPoint(3149, 3205, 0)),
THE_LOST_TRIBE("The Lost Tribe", new WorldPoint(3211, 3224, 0)),
LUNAR_DIPLOMACY("Lunar Diplomacy", new WorldPoint(2619, 3689, 0)),
MAKING_FRIENDS_WITH_MY_ARM("Making Friends with My Arm", new WorldPoint(2904, 10092, 0)),
MAKING_HISTORY("Making History", new WorldPoint(2435, 3346, 0)),
MONKS_FRIEND("Monk's Friend", new WorldPoint(2605, 3209, 0)),
MOUNTAIN_DAUGHTER("Mountain Daughter", new WorldPoint(2810, 3672, 0)),
MOURNINGS_ENDS_PART_I("Mourning's Ends Part I", new WorldPoint(2289, 3149, 0)),
MOURNINGS_ENDS_PART_II("Mourning's Ends Part II", new WorldPoint(2352, 3172, 0)),
MURDER_MYSTERY("Murder Mystery", new WorldPoint(2740, 3562, 0)),
MY_ARMS_BIG_ADVENTURE("My Arm's Big Adventure", new WorldPoint(2908, 10088, 0)),
NATURE_SPIRIT("Nature Spirit", new WorldPoint(3440, 9894, 0)),
OBSERVATORY_QUEST("Observatory Quest", new WorldPoint(2438, 3185, 0)),
OLAFS_QUEST("Olaf's Quest", new WorldPoint(2723, 3729, 0)),
ONE_SMALL_FAVOUR("One Small Favour", new WorldPoint(2834, 2985, 0)),
PLAGUE_CITY("Plague City", new WorldPoint(2567, 3334, 0)),
PRIEST_IN_PERIL("Priest in Peril", new WorldPoint(3219, 3473, 0)),
THE_QUEEN_OF_THIEVES("The Queen of Thieves", new WorldPoint(1795, 3782, 0)),
RAG_AND_BONE_MAN("Rag and Bone Man I & II", new WorldPoint(3359, 3504, 0)),
RECRUITMENT_DRIVE_BLACK_KNIGHTS_FORTRESS("Recruitment Drive & Black Knights' Fortress", new WorldPoint(2959, 3336, 0)),
ROVING_ELVES("Roving Elves", new WorldPoint(2289, 3146, 0)),
RUM_DEAL("Rum Deal", new WorldPoint(3679, 3535, 0)),
SCORPION_CATCHER("Scorpion Catcher", new WorldPoint(2701, 3399, 0)),
SEA_SLUG("Sea Slug", new WorldPoint(2715, 3302, 0)),
SHADES_OF_MORTTON("Shades of Mort'ton", new WorldPoint(3463, 3308, 0)),
SHADOW_OF_THE_STORM("Shadow of the Storm", new WorldPoint(3270, 3159, 0)),
SHEEP_HERDER("Sheep Herder", new WorldPoint(2616, 3299, 0)),
SHILO_VILLAGE("Shilo Village", new WorldPoint(2882, 2951, 0)),
A_SOULS_BANE("A Soul's Bane", new WorldPoint(3307, 3454, 0)),
SPIRITS_OF_THE_ELID("Spirits of the Elid", new WorldPoint(3441, 2911, 0)),
SWAN_SONG("Swan Song", new WorldPoint(2345, 3652, 0)),
TAI_BWO_WANNAI_TRIO("Tai Bwo Wannai Trio", new WorldPoint(2779, 3087, 0)),
A_TAIL_OF_TWO_CATS("A Tail of Two Cats", new WorldPoint(2917, 3557, 0)),
TALE_OF_THE_RIGHTEOUS("Tale of the Righteous", new WorldPoint(1511, 3631, 0)),
A_TASTE_OF_HOPE("A Taste of Hope", new WorldPoint(3668, 3216, 0)),
TEARS_OF_GUTHIX("Tears of Guthix", new WorldPoint(3251, 9517, 0)),
TEMPLE_OF_IKOV("Temple of Ikov", new WorldPoint(2574, 3320, 0)),
THRONE_OF_MISCELLANIA_ROYAL_TROUBLE("Throne of Miscellania & Royal Trouble", new WorldPoint(2497, 3859, 0)),
THE_TOURIST_TRAP("The Tourist Trap", new WorldPoint(3302, 3113, 0)),
TOWER_OF_LIFE("Tower of Life", new WorldPoint(2640, 3218, 0)),
TREE_GNOME_VILLAGE("Tree Gnome Village", new WorldPoint(2541, 3169, 0)),
TRIBAL_TOTEM("Tribal Totem", new WorldPoint(2790, 3182, 0)),
TROLL_ROMANCE("Troll Romance", new WorldPoint(2890, 10097, 0)),
UNDERGROUND_PASS_REGICIDE("Underground Pass & Regicide", new WorldPoint(2575, 3293, 0)),
WANTED_SLUG_MENACE("Wanted! & The Slug Menace", new WorldPoint(2996, 3373, 0)),
WATCHTOWER("Watchtower", new WorldPoint(2545, 3112, 0)),
WATERFALL_QUEST("Waterfall Quest", new WorldPoint(2521, 3498, 0)),
WHAT_LIES_BELOW("What Lies Below", new WorldPoint(3265, 3333, 0)),
WITCHS_HOUSE("Witch's House", new WorldPoint(2927, 3456, 0)),
ZOGRE_FLESH_EATERS("Zogre Flesh Eaters", new WorldPoint(2442, 3051, 0));
@Getter
private final String tooltip;
ANIMAL_MAGNETISM(Quest.ANIMAL_MAGNETISM, new WorldPoint(3094, 3360, 0)),
ANOTHER_SLICE_OF_HAM(Quest.ANOTHER_SLICE_OF_HAM, new WorldPoint(2799, 5428, 0)),
THE_ASCENT_OF_ARCEUUS(Quest.THE_ASCENT_OF_ARCEUUS, new WorldPoint(1700, 3742, 0)),
BETWEEN_A_ROCK(Quest.BETWEEN_A_ROCK, new WorldPoint(2823, 10168, 0)),
BIG_CHOMPY_BIRD_HUNTING(Quest.BIG_CHOMPY_BIRD_HUNTING, new WorldPoint(2629, 2981, 0)),
BIOHAZARD(Quest.BIOHAZARD, new WorldPoint(2591, 3335, 0)),
BONE_VOYAGE(Quest.BONE_VOYAGE, new WorldPoint(3259, 3450, 0)),
CABIN_FEVER(Quest.CABIN_FEVER, new WorldPoint(3674, 3496, 0)),
CLIENT_OF_KOUREND(Quest.CLIENT_OF_KOUREND, new WorldPoint(1823, 3690, 0)),
CLOCK_TOWER(Quest.CLOCK_TOWER, new WorldPoint(2568, 3249, 0)),
COLD_WAR(Quest.COLD_WAR, new WorldPoint(2593, 3265, 0)),
CONTACT(Quest.CONTACT, new WorldPoint(3280, 2770, 0)),
CREATURE_OF_FENKENSTRAIN(Quest.CREATURE_OF_FENKENSTRAIN, new WorldPoint(3487, 3485, 0)),
DARKNESS_OF_HALLOWVALE(Quest.DARKNESS_OF_HALLOWVALE, new WorldPoint(3494, 9628, 0)),
DEATH_PLATEAU_TROLL_STRONGHOLD(new Quest[]{Quest.DEATH_PLATEAU, Quest.TROLL_STRONGHOLD}, new WorldPoint(2895, 3528, 0)),
DEATH_TO_THE_DORGESHUUN(Quest.DEATH_TO_THE_DORGESHUUN, new WorldPoint(3316, 9613, 0)),
THE_DEPTHS_OF_DESPAIR(Quest.THE_DEPTHS_OF_DESPAIR, new WorldPoint(1846, 3556, 0)),
DESERT_TREASURE(Quest.DESERT_TREASURE, new WorldPoint(3177, 3043, 0)),
DEVIOUS_MINDS(Quest.DEVIOUS_MINDS, new WorldPoint(3405, 3492, 0)),
THE_DIG_SITE(Quest.THE_DIG_SITE, new WorldPoint(3363, 3337, 0)),
DRAGON_SLAYER_II(Quest.DRAGON_SLAYER_II, new WorldPoint(2456, 2868, 0)),
DREAM_MENTOR(Quest.DREAM_MENTOR, new WorldPoint(2144, 10346, 0)),
DRUIDIC_RITUAL(Quest.DRUIDIC_RITUAL, new WorldPoint(2916, 3484, 0)),
DWARF_CANNON(Quest.DWARF_CANNON, new WorldPoint(2566, 3461, 0)),
EADGARS_RUSE(Quest.EADGARS_RUSE, new WorldPoint(2896, 3426, 0)),
EAGLES_PEAK(Quest.EAGLES_PEAK, new WorldPoint(2605, 3264, 0)),
ELEMENTAL_WORKSHOP(new Quest[]{Quest.ELEMENTAL_WORKSHOP_I, Quest.ELEMENTAL_WORKSHOP_II}, new WorldPoint(2714, 3482, 0)),
ENAKHRAS_LAMENT(Quest.ENAKHRAS_LAMENT, new WorldPoint(3190, 2926, 0)),
ENLIGHTENED_JOURNEY(Quest.ENLIGHTENED_JOURNEY, new WorldPoint(2809, 3356, 0)),
THE_EYES_OF_GLOUPHRIE(Quest.THE_EYES_OF_GLOUPHRIE, new WorldPoint(2400, 3419, 0)),
FAIRYTALE(new Quest[]{Quest.FAIRYTALE_I__GROWING_PAINS, Quest.FAIRYTALE_II__CURE_A_QUEEN}, new WorldPoint(3077, 3258, 0)),
FAMILY_CREST(Quest.FAMILY_CREST, new WorldPoint(3278, 3404, 0)),
THE_FEUD(Quest.THE_FEUD, new WorldPoint(3301, 3211, 0)),
FIGHT_ARENA(Quest.FIGHT_ARENA, new WorldPoint(2565, 3199, 0)),
FISHING_CONTEST_1(Quest.FISHING_CONTEST, new WorldPoint(2875, 3483, 0)),
FISHING_CONTEST_2(Quest.FISHING_CONTEST, new WorldPoint(2820, 3487, 0)),
FORGETTABLE_TALE(Quest.FORGETTABLE_TALE, new WorldPoint(2826, 10215, 0)),
THE_FORSAKEN_TOWER(Quest.THE_FORSAKEN_TOWER, new WorldPoint(1484, 3747, 0)),
THE_FREMENNIK_ISLES(Quest.THE_FREMENNIK_ISLES, new WorldPoint(2645, 3711, 0)),
THE_FREMENNIK_TRIALS(Quest.THE_FREMENNIK_TRIALS, new WorldPoint(2657, 3669, 0)),
GARDEN_OF_TRANQUILLITY(Quest.GARDEN_OF_TRANQUILLITY, new WorldPoint(3227, 3477, 0)),
GERTRUDES_CAT_RATCATCHERS(Quest.GERTRUDES_CAT, new WorldPoint(3150, 3411, 0)),
GHOSTS_AHOY(Quest.GHOSTS_AHOY, new WorldPoint(3677, 3510, 0)),
THE_GIANT_DWARF(Quest.THE_GIANT_DWARF, new WorldPoint(2841, 10129, 0)),
THE_GOLEM(Quest.THE_GOLEM, new WorldPoint(3487, 3089, 0)),
THE_GRAND_TREE_MONKEY_MADNESS(new Quest[]{Quest.THE_GRAND_TREE, Quest.MONKEY_MADNESS_I, Quest.MONKEY_MADNESS_II}, new WorldPoint(2466, 3497, 0)),
THE_GREAT_BRAIN_ROBBERY(Quest.THE_GREAT_BRAIN_ROBBERY, new WorldPoint(3681, 2963, 0)),
GRIM_TALES(Quest.GRIM_TALES, new WorldPoint(2890, 3454, 0)),
THE_HAND_IN_THE_SAND(Quest.THE_HAND_IN_THE_SAND, new WorldPoint(2552, 3101, 0)),
HAUNTED_MINE(Quest.HAUNTED_MINE, new WorldPoint(3443, 3258, 0)),
HAZEEL_CULT(Quest.HAZEEL_CULT, new WorldPoint(2565, 3271, 0)),
HEROES_QUEST(Quest.HEROES_QUEST, new WorldPoint(2903, 3511, 0)),
HOLY_GRAIL(new Quest[]{Quest.MERLINS_CRYSTAL, Quest.HOLY_GRAIL}, new WorldPoint(2763, 3515, 0)),
HORROR_FROM_THE_DEEP(Quest.HORROR_FROM_THE_DEEP, new WorldPoint(2507, 3635, 0)),
ICTHLARINS_LITTLE_HELPER(Quest.ICTHLARINS_LITTLE_HELPER, new WorldPoint(3314, 2849, 0)),
IN_SEARCH_OF_THE_MYREQUE(Quest.IN_SEARCH_OF_THE_MYREQUE, new WorldPoint(3502, 3477, 0)),
JUNGLE_POTION(Quest.JUNGLE_POTION, new WorldPoint(2809, 3086, 0)),
KINGS_RANSOM(Quest.KINGS_RANSOM, new WorldPoint(2741, 3554, 0)),
LEGENDS_QUEST(Quest.LEGENDS_QUEST, new WorldPoint(2725, 3367, 0)),
LOST_CITY(Quest.LOST_CITY, new WorldPoint(3149, 3205, 0)),
THE_LOST_TRIBE(Quest.THE_LOST_TRIBE, new WorldPoint(3211, 3224, 0)),
LUNAR_DIPLOMACY(Quest.LUNAR_DIPLOMACY, new WorldPoint(2619, 3689, 0)),
MAKING_FRIENDS_WITH_MY_ARM(Quest.MAKING_FRIENDS_WITH_MY_ARM, new WorldPoint(2904, 10092, 0)),
MAKING_HISTORY(Quest.MAKING_HISTORY, new WorldPoint(2435, 3346, 0)),
MONKS_FRIEND(Quest.MONKS_FRIEND, new WorldPoint(2605, 3209, 0)),
MOUNTAIN_DAUGHTER(Quest.MOUNTAIN_DAUGHTER, new WorldPoint(2810, 3672, 0)),
MOURNINGS_ENDS_PART_I(Quest.MOURNINGS_ENDS_PART_I, new WorldPoint(2289, 3149, 0)),
MOURNINGS_ENDS_PART_II(Quest.MONKEY_MADNESS_II, new WorldPoint(2352, 3172, 0)),
MURDER_MYSTERY(Quest.MURDER_MYSTERY, new WorldPoint(2740, 3562, 0)),
MY_ARMS_BIG_ADVENTURE(Quest.MY_ARMS_BIG_ADVENTURE, new WorldPoint(2908, 10088, 0)),
NATURE_SPIRIT(Quest.NATURE_SPIRIT, new WorldPoint(3440, 9894, 0)),
OBSERVATORY_QUEST(Quest.OBSERVATORY_QUEST, new WorldPoint(2438, 3185, 0)),
OLAFS_QUEST(Quest.OLAFS_QUEST, new WorldPoint(2723, 3729, 0)),
ONE_SMALL_FAVOUR(Quest.ONE_SMALL_FAVOUR, new WorldPoint(2834, 2985, 0)),
PLAGUE_CITY(Quest.PLAGUE_CITY, new WorldPoint(2567, 3334, 0)),
PRIEST_IN_PERIL(Quest.PRIEST_IN_PERIL, new WorldPoint(3219, 3473, 0)),
THE_QUEEN_OF_THIEVES(Quest.THE_QUEEN_OF_THIEVES, new WorldPoint(1795, 3782, 0)),
RAG_AND_BONE_MAN(new Quest[]{Quest.RAG_AND_BONE_MAN, Quest.RAG_AND_BONE_MAN_II}, new WorldPoint(3359, 3504, 0)),
RECRUITMENT_DRIVE_BLACK_KNIGHTS_FORTRESS(new Quest[]{Quest.BLACK_KNIGHTS_FORTRESS, Quest.RECRUITMENT_DRIVE}, new WorldPoint(2959, 3336, 0)),
ROVING_ELVES(Quest.ROVING_ELVES, new WorldPoint(2289, 3146, 0)),
RUM_DEAL(Quest.RUM_DEAL, new WorldPoint(3679, 3535, 0)),
SCORPION_CATCHER(Quest.SCORPION_CATCHER, new WorldPoint(2701, 3399, 0)),
SEA_SLUG(Quest.SEA_SLUG, new WorldPoint(2715, 3302, 0)),
SHADES_OF_MORTTON(Quest.SHADES_OF_MORTTON, new WorldPoint(3463, 3308, 0)),
SHADOW_OF_THE_STORM(Quest.SHADES_OF_MORTTON, new WorldPoint(3270, 3159, 0)),
SHEEP_HERDER(Quest.SHEEP_HERDER, new WorldPoint(2616, 3299, 0)),
SHILO_VILLAGE(Quest.SHILO_VILLAGE, new WorldPoint(2882, 2951, 0)),
A_SOULS_BANE(Quest.A_SOULS_BANE, new WorldPoint(3307, 3454, 0)),
SPIRITS_OF_THE_ELID(Quest.SPIRITS_OF_THE_ELID, new WorldPoint(3441, 2911, 0)),
SWAN_SONG(Quest.SWAN_SONG, new WorldPoint(2345, 3652, 0)),
TAI_BWO_WANNAI_TRIO(Quest.TAI_BWO_WANNAI_TRIO, new WorldPoint(2779, 3087, 0)),
A_TAIL_OF_TWO_CATS(Quest.A_TAIL_OF_TWO_CATS, new WorldPoint(2917, 3557, 0)),
TALE_OF_THE_RIGHTEOUS(Quest.TALE_OF_THE_RIGHTEOUS, new WorldPoint(1511, 3631, 0)),
A_TASTE_OF_HOPE(Quest.A_TASTE_OF_HOPE, new WorldPoint(3668, 3216, 0)),
TEARS_OF_GUTHIX(Quest.TEARS_OF_GUTHIX, new WorldPoint(3251, 9517, 0)),
TEMPLE_OF_IKOV(Quest.TEMPLE_OF_IKOV, new WorldPoint(2574, 3320, 0)),
THRONE_OF_MISCELLANIA_ROYAL_TROUBLE(new Quest[]{Quest.THRONE_OF_MISCELLANIA, Quest.ROYAL_TROUBLE}, new WorldPoint(2497, 3859, 0)),
THE_TOURIST_TRAP(Quest.THE_TOURIST_TRAP, new WorldPoint(3302, 3113, 0)),
TOWER_OF_LIFE(Quest.TOWER_OF_LIFE, new WorldPoint(2640, 3218, 0)),
TREE_GNOME_VILLAGE(Quest.TREE_GNOME_VILLAGE, new WorldPoint(2541, 3169, 0)),
TRIBAL_TOTEM(Quest.TRIBAL_TOTEM, new WorldPoint(2790, 3182, 0)),
TROLL_ROMANCE(Quest.TROLL_ROMANCE, new WorldPoint(2890, 10097, 0)),
UNDERGROUND_PASS_REGICIDE(new Quest[]{Quest.REGICIDE, Quest.UNDERGROUND_PASS}, new WorldPoint(2575, 3293, 0)),
WANTED_SLUG_MENACE(new Quest[]{Quest.WANTED, Quest.THE_SLUG_MENACE}, new WorldPoint(2996, 3373, 0)),
WATCHTOWER(Quest.WATCHTOWER, new WorldPoint(2545, 3112, 0)),
WATERFALL_QUEST(Quest.WATERFALL_QUEST, new WorldPoint(2521, 3498, 0)),
WHAT_LIES_BELOW(Quest.WHAT_LIES_BELOW, new WorldPoint(3265, 3333, 0)),
WITCHS_HOUSE(Quest.WITCHS_HOUSE, new WorldPoint(2927, 3456, 0)),
ZOGRE_FLESH_EATERS(Quest.ZOGRE_FLESH_EATERS, new WorldPoint(2442, 3051, 0));
@Getter
private final WorldPoint location;
QuestStartLocation(String description, WorldPoint location)
@Getter
private final Quest[] quests;
QuestStartLocation(Quest[] quests, WorldPoint location)
{
this.tooltip = "Quest Start - " + description;
this.location = location;
this.quests = quests;
}
QuestStartLocation(Quest quest, WorldPoint location)
{
this.location = location;
this.quests = new Quest[]{quest};
}
}

View File

@@ -25,15 +25,15 @@
*/
package net.runelite.client.plugins.worldmap;
import net.runelite.api.coords.WorldPoint;
import java.awt.image.BufferedImage;
import net.runelite.client.ui.overlay.worldmap.WorldMapPoint;
class QuestStartPoint extends WorldMapPoint
{
QuestStartPoint(QuestStartLocation data, BufferedImage icon)
QuestStartPoint(WorldPoint location, BufferedImage icon, String tooltip)
{
super(data.getLocation(), icon);
setTooltip(data.getTooltip());
super(location, icon);
setTooltip(tooltip);
}
}

View File

@@ -166,8 +166,8 @@ public interface WorldMapConfig extends Config
@ConfigItem(
keyName = WorldMapPlugin.CONFIG_KEY_QUEST_START_TOOLTIPS,
name = "Show quest names",
description = "Indicates the names of quests and highlights incomplete ones",
name = "Show quest names and status",
description = "Indicates the names of quests and shows completion status",
position = 13
)
default boolean questStartTooltips()

View File

@@ -31,9 +31,15 @@ import java.awt.image.BufferedImage;
import java.util.Arrays;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.GameState;
import net.runelite.api.Skill;
import net.runelite.api.Quest;
import net.runelite.api.QuestState;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.ExperienceChanged;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.WidgetID;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.AgilityShortcut;
@@ -52,6 +58,9 @@ public class WorldMapPlugin extends Plugin
static final BufferedImage BLANK_ICON;
private static final BufferedImage FAIRY_TRAVEL_ICON;
private static final BufferedImage NOPE_ICON;
private static final BufferedImage NOT_STARTED_ICON;
private static final BufferedImage STARTED_ICON;
private static final BufferedImage FINISHED_ICON;
static final String CONFIG_KEY = "worldmap";
static final String CONFIG_KEY_FAIRY_RING_TOOLTIPS = "fairyRingTooltips";
@@ -77,6 +86,9 @@ public class WorldMapPlugin extends Plugin
//A size of 17 gives us a buffer when triggering tooltips
final int iconBufferSize = 17;
//Quest icons are a bit bigger.
final int questIconBufferSize = 22;
BLANK_ICON = new BufferedImage(iconBufferSize, iconBufferSize, BufferedImage.TYPE_INT_ARGB);
FAIRY_TRAVEL_ICON = new BufferedImage(iconBufferSize, iconBufferSize, BufferedImage.TYPE_INT_ARGB);
@@ -86,11 +98,26 @@ public class WorldMapPlugin extends Plugin
NOPE_ICON = new BufferedImage(iconBufferSize, iconBufferSize, BufferedImage.TYPE_INT_ARGB);
final BufferedImage nopeImage = ImageUtil.getResourceStreamFromClass(WorldMapPlugin.class, "nope_icon.png");
NOPE_ICON.getGraphics().drawImage(nopeImage, 1, 1, null);
NOT_STARTED_ICON = new BufferedImage(questIconBufferSize, questIconBufferSize, BufferedImage.TYPE_INT_ARGB);
final BufferedImage notStartedIcon = ImageUtil.getResourceStreamFromClass(WorldMapPlugin.class, "quest_not_started_icon.png");
NOT_STARTED_ICON.getGraphics().drawImage(notStartedIcon, 4, 4, null);
STARTED_ICON = new BufferedImage(questIconBufferSize, questIconBufferSize, BufferedImage.TYPE_INT_ARGB);
final BufferedImage startedIcon = ImageUtil.getResourceStreamFromClass(WorldMapPlugin.class, "quest_started_icon.png");
STARTED_ICON.getGraphics().drawImage(startedIcon, 4, 4, null);
FINISHED_ICON = new BufferedImage(questIconBufferSize, questIconBufferSize, BufferedImage.TYPE_INT_ARGB);
final BufferedImage finishedIcon = ImageUtil.getResourceStreamFromClass(WorldMapPlugin.class, "quest_completed_icon.png");
FINISHED_ICON.getGraphics().drawImage(finishedIcon, 4, 4, null);
}
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private WorldMapConfig config;
@@ -164,6 +191,17 @@ public class WorldMapPlugin extends Plugin
}
}
@Subscribe
public void onWidgetLoaded(WidgetLoaded widgetLoaded)
{
if (widgetLoaded.getGroupId() == WidgetID.WORLD_MAP_GROUP_ID)
{
// Quest icons are per-account due to showing quest status,
// so we recreate them each time the map is loaded
updateQuestStartPointIcons();
}
}
private void updateAgilityIcons()
{
worldMapPointManager.removeIf(AgilityShortcutPoint.class::isInstance);
@@ -200,6 +238,7 @@ public class WorldMapPlugin extends Plugin
{
updateAgilityIcons();
updateRareTreeIcons();
updateQuestStartPointIcons();
worldMapPointManager.removeIf(FairyRingPoint.class::isInstance);
if (config.fairyRingIcon() || config.fairyRingTooltips())
@@ -219,14 +258,6 @@ public class WorldMapPlugin extends Plugin
.forEach(worldMapPointManager::add);
}
worldMapPointManager.removeIf(QuestStartPoint.class::isInstance);
if (config.questStartTooltips())
{
Arrays.stream(QuestStartLocation.values())
.map(value -> new QuestStartPoint(value, BLANK_ICON))
.forEach(worldMapPointManager::add);
}
worldMapPointManager.removeIf(TransportationPoint.class::isInstance);
if (config.transportationTeleportTooltips())
{
@@ -271,4 +302,72 @@ public class WorldMapPlugin extends Plugin
}).map(TeleportPoint::new)
.forEach(worldMapPointManager::add);
}
private void updateQuestStartPointIcons()
{
worldMapPointManager.removeIf(QuestStartPoint.class::isInstance);
if (!config.questStartTooltips())
{
return;
}
// Must setup the quest icons on the client thread, after the player has logged in.
clientThread.invokeLater(() ->
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return false;
}
Arrays.stream(QuestStartLocation.values())
.map(this::createQuestStartPoint)
.forEach(worldMapPointManager::add);
return true;
});
}
private QuestStartPoint createQuestStartPoint(QuestStartLocation data)
{
Quest[] quests = data.getQuests();
// Get first uncompleted quest. Else, return the last quest.
Quest quest = null;
for (int i = 0; i < quests.length; i++)
{
if (quests[i].getState(client) != QuestState.FINISHED)
{
quest = quests[i];
break;
}
}
if (quest == null)
{
quest = quests[quests.length - 1];
}
BufferedImage icon = BLANK_ICON;
String tooltip = "";
if (quest != null)
{
tooltip = quest.getName();
switch (quest.getState(client))
{
case FINISHED:
icon = FINISHED_ICON;
tooltip += " - Finished";
break;
case IN_PROGRESS:
icon = STARTED_ICON;
tooltip += " - Started";
break;
case NOT_STARTED:
icon = NOT_STARTED_ICON;
tooltip += " - Not Started";
break;
}
}
return new QuestStartPoint(data.getLocation(), icon, tooltip);
}
}