mes, metronome, npchighlight, npcunaggroarea, objectindicators

This commit is contained in:
therealunull
2020-12-16 15:28:24 -05:00
parent fc1c0799bd
commit 5d7bf6aa54
52 changed files with 4885 additions and 59 deletions

View File

@@ -54,7 +54,7 @@ import net.runelite.api.vars.InputType;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.api.widgets.WidgetInfo.getChildFromID;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
@@ -203,7 +203,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener
return;
}
final int groupId = getGroupFromID(entry.getParam1());
final int groupId = TO_GROUP(entry.getParam1());
final int childId = getChildFromID(entry.getParam1());
if (groupId != WidgetInfo.CHATBOX.getGroupId())

View File

@@ -64,7 +64,7 @@ import net.runelite.api.events.ScriptPreFired;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.api.widgets.WidgetInfo.getChildFromID;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
@@ -120,7 +120,7 @@ public class ScriptInspector extends JFrame
if (source != null)
{
int id = source.getId();
output += " - " + getGroupFromID(id) + "." + getChildFromID(id);
output += " - " + TO_GROUP(id) + "." + getChildFromID(id);
if (source.getIndex() != -1)
{

View File

@@ -60,7 +60,7 @@ import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.SpriteID;
import static net.runelite.api.widgets.WidgetInfo.getChildFromID;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked;
@@ -464,7 +464,7 @@ class WidgetInspector extends JFrame
picker = parent.createChild(-1, WidgetType.GRAPHIC);
log.info("Picker is {}.{} [{}]", WidgetInfo.getGroupFromID(picker.getId()), WidgetInfo.getChildFromID(picker.getId()), picker.getIndex());
log.info("Picker is {}.{} [{}]", WidgetInfo.TO_GROUP(picker.getId()), WidgetInfo.getChildFromID(picker.getId()), picker.getIndex());
picker.setSpriteId(SpriteID.MOBILE_FINGER_ON_INTERFACE);
picker.setOriginalWidth(15);
@@ -538,7 +538,7 @@ class WidgetInspector extends JFrame
{
continue;
}
String name = WidgetInfo.getGroupFromID(entry.getParam1()) + "." + WidgetInfo.getChildFromID(entry.getParam1());
String name = WidgetInfo.TO_GROUP(entry.getParam1()) + "." + WidgetInfo.getChildFromID(entry.getParam1());
if (entry.getParam0() != -1)
{
@@ -584,7 +584,7 @@ class WidgetInspector extends JFrame
public static String getWidgetIdentifier(Widget widget)
{
int id = widget.getId();
String str = getGroupFromID(id) + "." + getChildFromID(id);
String str = TO_GROUP(id) + "." + getChildFromID(id);
if (widget.getIndex() != -1)
{

View File

@@ -46,7 +46,7 @@ import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import static net.runelite.api.widgets.WidgetInfo.SEED_VAULT_ITEM_CONTAINER;
import static net.runelite.api.widgets.WidgetInfo.getChildFromID;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
@@ -123,7 +123,7 @@ public class ExaminePlugin extends Plugin
id = event.getId();
int widgetId = event.getWidgetId();
int widgetGroup = getGroupFromID(widgetId);
int widgetGroup = TO_GROUP(widgetId);
int widgetChild = getChildFromID(widgetId);
Widget widget = client.getWidget(widgetGroup, widgetChild);
WidgetItem widgetItem = widget.getWidgetItem(event.getActionParam());
@@ -266,7 +266,7 @@ public class ExaminePlugin extends Plugin
private int[] findItemFromWidget(int widgetId, int actionParam)
{
int widgetGroup = getGroupFromID(widgetId);
int widgetGroup = TO_GROUP(widgetId);
int widgetChild = getChildFromID(widgetId);
Widget widget = client.getWidget(widgetGroup, widgetChild);

View File

@@ -235,7 +235,7 @@ public class FriendNotesPlugin extends Plugin
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded event)
{
final int groupId = WidgetInfo.getGroupFromID(event.getActionParam1());
final int groupId = WidgetInfo.TO_GROUP(event.getActionParam1());
// Look for "Message" on friends list
if ((groupId == WidgetInfo.FRIENDS_LIST.getGroupId() && event.getOption().equals("Message")) ||
@@ -265,7 +265,7 @@ public class FriendNotesPlugin extends Plugin
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
final int groupId = WidgetInfo.getGroupFromID(event.getWidgetId());
final int groupId = WidgetInfo.TO_GROUP(event.getWidgetId());
if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() || groupId == WidgetInfo.IGNORE_LIST.getGroupId())
{

View File

@@ -557,7 +557,7 @@ public class GrandExchangePlugin extends Plugin
final MenuEntry[] entries = client.getMenuEntries();
final MenuEntry menuEntry = entries[entries.length - 1];
final int widgetId = menuEntry.getParam1();
final int groupId = WidgetInfo.getGroupFromID(widgetId);
final int groupId = WidgetInfo.TO_GROUP(widgetId);
switch (groupId)
{

View File

@@ -148,7 +148,7 @@ public class HiscorePlugin extends Plugin
return;
}
int groupId = WidgetInfo.getGroupFromID(event.getActionParam1());
int groupId = WidgetInfo.TO_GROUP(event.getActionParam1());
String option = event.getOption();
if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() || groupId == WidgetInfo.FRIENDS_CHAT.getGroupId() ||

View File

@@ -91,7 +91,7 @@ class ItemPricesOverlay extends Overlay
final MenuEntry menuEntry = menuEntries[last];
final MenuAction action = MenuAction.of(menuEntry.getType());
final int widgetId = menuEntry.getParam1();
final int groupId = WidgetInfo.getGroupFromID(widgetId);
final int groupId = WidgetInfo.TO_GROUP(widgetId);
final boolean isAlching = menuEntry.getOption().equals("Cast") && menuEntry.getTarget().contains("High Level Alchemy");
// Tooltip action type handling

View File

@@ -92,7 +92,7 @@ public class ItemStatOverlay extends Overlay
}
final MenuEntry entry = menu[menuSize - 1];
final int group = WidgetInfo.getGroupFromID(entry.getParam1());
final int group = WidgetInfo.TO_GROUP(entry.getParam1());
final int child = WidgetInfo.getChildFromID(entry.getParam1());
final Widget widget = client.getWidget(group, child);

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020, Sam <dasistkeinnamen@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.menuentryswapper;
public enum BuyMode
{
OFF,
BUY_1,
BUY_5,
BUY_10,
BUY_50;
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2018, Ethan <http://github.com/shmeeps>
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum FairyRingMode
{
ZANARIS("Zanaris"),
LAST_DESTINATION("Last-Destination"),
CONFIGURE("Configure"),
OFF("Off");
private final String name;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019, Rami <https://github.com/Rami-J>
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum GEItemCollectMode
{
DEFAULT("Default"),
ITEMS("Collect-items"),
NOTES("Collect-notes"),
BANK("Bank");
private final String name;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum HouseAdvertisementMode
{
VIEW("View"),
ADD_HOUSE("Add-House"),
VISIT_LAST("Visit-Last");
private final String name;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2018, Snakk <http://github.com/SnakkSnokk>
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum HouseMode
{
ENTER("Enter"),
HOME("Home"),
BUILD_MODE("Build mode"),
FRIENDS_HOUSE("Friend's House");
private final String name;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,609 @@
/*
* Copyright (c) 2018, 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.menuentryswapper;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup(MenuEntrySwapperConfig.GROUP)
public interface MenuEntrySwapperConfig extends Config
{
String GROUP = "menuentryswapper";
@ConfigSection(
name = "Item Swaps",
description = "All options that swap item menu entries",
position = 0,
closedByDefault = true
)
String itemSection = "items";
@ConfigSection(
name = "NPC Swaps",
description = "All options that swap NPC menu entries",
position = 1,
closedByDefault = true
)
String npcSection = "npcs";
@ConfigSection(
name = "Object Swaps",
description = "All options that swap object menu entries",
position = 2,
closedByDefault = true
)
String objectSection = "objects";
@ConfigSection(
name = "UI Swaps",
description = "All options that swap entries in the UI (except Items)",
position = 3,
closedByDefault = true
)
String uiSection = "ui";
@ConfigItem(
position = -2,
keyName = "shiftClickCustomization",
name = "Customizable shift-click",
description = "Allows customization of shift-clicks on items",
section = itemSection
)
default boolean shiftClickCustomization()
{
return true;
}
@ConfigItem(
keyName = "swapAdmire",
name = "Admire",
description = "Swap Admire with Teleport, Spellbook and Perks (max cape) for mounted skill capes.",
section = objectSection
)
default boolean swapAdmire()
{
return true;
}
@ConfigItem(
keyName = "swapAssignment",
name = "Assignment",
description = "Swap Talk-to with Assignment for Slayer Masters. This will take priority over swapping Trade.",
section = npcSection
)
default boolean swapAssignment()
{
return true;
}
@ConfigItem(
keyName = "swapBanker",
name = "Bank",
description = "Swap Talk-to with Bank on Bank NPC<br>Example: Banker",
section = npcSection
)
default boolean swapBank()
{
return true;
}
@ConfigItem(
keyName = "swapBirdhouseEmpty",
name = "Birdhouse",
description = "Swap Interact with Empty for birdhouses on Fossil Island",
section = objectSection
)
default boolean swapBirdhouseEmpty()
{
return true;
}
@ConfigItem(
keyName = "swapBones",
name = "Bury",
description = "Swap Bury with Use on Bones",
section = itemSection
)
default boolean swapBones()
{
return false;
}
@ConfigItem(
keyName = "swapHerbs",
name = "Clean",
description = "Swap Clean with Use on Herbs",
section = itemSection
)
default boolean swapHerbs()
{
return false;
}
@ConfigItem(
keyName = "swapPrayerBook",
name = "Recite-Prayer",
description = "Swap Read with Recite-prayer on the Prayer Book from The Great Brain Robbery quest",
section = itemSection
)
default boolean swapPrayerBook()
{
return false;
}
@ConfigItem(
keyName = "swapContract",
name = "Contract",
description = "Swap Talk-to with Contract on Guildmaster Jane",
section = npcSection
)
default boolean swapContract()
{
return true;
}
@ConfigItem(
keyName = "swapChase",
name = "Chase",
description = "Allows to left click your cat to chase",
section = npcSection
)
default boolean swapChase()
{
return true;
}
@ConfigItem(
keyName = "claimSlime",
name = "Claim Slime",
description = "Swap Talk-to with Claim Slime from Morytania diaries",
section = npcSection
)
default boolean claimSlime()
{
return true;
}
@ConfigItem(
keyName = "swapDarkMage",
name = "Repairs",
description = "Swap Talk-to with Repairs for Dark Mage",
section = npcSection
)
default boolean swapDarkMage()
{
return true;
}
@ConfigItem(
keyName = "swapCaptainKhaled",
name = "Task",
description = "Swap Talk-to with Task for Captain Khaled in Port Piscarilius",
section = npcSection
)
default boolean swapCaptainKhaled()
{
return false;
}
@ConfigItem(
keyName = "swapDecant",
name = "Decant",
description = "Swap Talk-to with Decant for Bob Barter and Murky Matt at the Grand Exchange.",
section = npcSection
)
default boolean swapDecant()
{
return false;
}
@ConfigItem(
keyName = "swapExchange",
name = "Exchange",
description = "Swap Talk-to with Exchange on NPC<br>Example: Grand Exchange Clerk, Tool Leprechaun, Void Knight",
section = npcSection
)
default boolean swapExchange()
{
return true;
}
@ConfigItem(
keyName = "swapFairyRing",
name = "Fairy ring",
description = "Swap Zanaris with Last-destination or Configure on Fairy rings",
section = objectSection
)
default FairyRingMode swapFairyRing()
{
return FairyRingMode.LAST_DESTINATION;
}
@ConfigItem(
keyName = "swapHardWoodGrove",
name = "Hardwood Grove Quick-Pay",
description = "Swap Quick-Pay(100) at the Hardwood Grove",
section = objectSection
)
default boolean swapHardWoodGrove()
{
return true;
}
@ConfigItem(
keyName = "swapHardWoodGroveParcel",
name = "Hardwood Grove Send-Parcel",
description = "Swap Send-Parcel at the Hardwood Grove",
section = npcSection
)
default boolean swapHardWoodGroveParcel()
{
return true;
}
@ConfigItem(
keyName = "swapHarpoon",
name = "Harpoon",
description = "Swap Cage, Big Net with Harpoon on Fishing spot",
section = objectSection
)
default boolean swapHarpoon()
{
return false;
}
@ConfigItem(
keyName = "swapHelp",
name = "Help",
description = "Swap Talk-to with Help on Arceuus library customers",
section = npcSection
)
default boolean swapHelp()
{
return true;
}
@ConfigItem(
keyName = "swapHomePortal",
name = "Home",
description = "Swap Enter with Home or Build or Friend's house on Portal",
section = objectSection
)
default HouseMode swapHomePortal()
{
return HouseMode.HOME;
}
@ConfigItem(
keyName = "swapHouseAdvertisement",
name = "House Advertisement",
description = "Swap View with Add-House or Visit-Last on House Advertisement board",
section = objectSection
)
default HouseAdvertisementMode swapHouseAdvertisement()
{
return HouseAdvertisementMode.VIEW;
}
@ConfigItem(
keyName = "swapPay",
name = "Pay",
description = "Swap Talk-to with Pay on NPC<br>Example: Elstan, Heskel, Fayeth",
section = npcSection
)
default boolean swapPay()
{
return true;
}
@ConfigItem(
keyName = "swapJewelleryBox",
name = "Jewellery Box",
description = "Swap Teleport Menu with previous destination on Jewellery Box",
section = objectSection
)
default boolean swapJewelleryBox()
{
return false;
}
@ConfigItem(
keyName = "swapPrivate",
name = "Private",
description = "Swap Shared with Private on the Chambers of Xeric storage units.",
section = objectSection
)
default boolean swapPrivate()
{
return false;
}
@ConfigItem(
keyName = "swapPick",
name = "Pick",
description = "Swap Pick with Pick-lots of the Gourd tree in the Chambers of Xeric",
section = objectSection
)
default boolean swapPick()
{
return false;
}
@ConfigItem(
keyName = "swapQuick",
name = "Quick Pass/Open/Start/Travel",
description = "Swap Pass with Quick-Pass, Open with Quick-Open, Ring with Quick-Start and Talk-to with Quick-Travel",
section = objectSection
)
default boolean swapQuick()
{
return true;
}
@ConfigItem(
keyName = "swapBoxTrap",
name = "Reset",
description = "Swap Check with Reset on box trap",
section = objectSection
)
default boolean swapBoxTrap()
{
return true;
}
@ConfigItem(
keyName = "swapTeleportItem",
name = "Teleport item",
description = "Swap Wear, Wield with Rub, Teleport on teleport item<br>Example: Amulet of glory, Explorer's ring, Chronicle",
section = itemSection
)
default boolean swapTeleportItem()
{
return false;
}
@ConfigItem(
keyName = "swapAbyssTeleport",
name = "Teleport to Abyss",
description = "Swap Talk-to with Teleport for the Mage of Zamorak",
section = npcSection
)
default boolean swapAbyssTeleport()
{
return true;
}
@ConfigItem(
keyName = "swapTrade",
name = "Trade",
description = "Swap Talk-to with Trade on NPC<br>Example: Shop keeper, Shop assistant",
section = npcSection
)
default boolean swapTrade()
{
return true;
}
@ConfigItem(
keyName = "swapTravel",
name = "Travel",
description = "Swap Talk-to with Travel, Take-boat, Pay-fare, Charter on NPC<br>Example: Squire, Monk of Entrana, Customs officer, Trader Crewmember",
section = npcSection
)
default boolean swapTravel()
{
return true;
}
@ConfigItem(
keyName = "swapEnchant",
name = "Enchant",
description = "Swap Talk-to with Enchant for Eluned",
section = npcSection
)
default boolean swapEnchant()
{
return true;
}
@ConfigItem(
keyName = "swapTeleportSpell",
name = "Shift-click teleport spells",
description = "Swap teleport spells that have a second destination on shift",
section = uiSection
)
default boolean swapTeleportSpell()
{
return false;
}
@ConfigItem(
keyName = "swapStartMinigame",
name = "Pyramid Plunder Start-minigame",
description = "Swap Talk-to with Start-minigame at the Guardian Mummy",
section = npcSection
)
default boolean swapStartMinigame()
{
return true;
}
@ConfigItem(
keyName = "swapQuickleave",
name = "Quick-Leave",
description = "Swap Leave Tomb with Quick-Leave at Pyramid Plunder",
section = objectSection
)
default boolean swapQuickLeave()
{
return false;
}
@ConfigItem(
keyName = "swapGEItemCollect",
name = "GE Item Collect",
description = "Swap Collect-notes, Collect-items, or Bank options from GE offer",
section = uiSection
)
default GEItemCollectMode swapGEItemCollect()
{
return GEItemCollectMode.DEFAULT;
}
@ConfigItem(
keyName = "swapGEAbort",
name = "GE Abort",
description = "Swap abort offer on Grand Exchange offers when shift-clicking"
,
section = uiSection
)
default boolean swapGEAbort()
{
return false;
}
@ConfigItem(
keyName = "swapNpcContact",
name = "NPC Contact",
description = "Swap NPC Contact with last contacted NPC when shift-clicking",
section = uiSection
)
default boolean swapNpcContact()
{
return false;
}
@ConfigItem(
keyName = "bankWithdrawShiftClick",
name = "Bank Withdraw Shift-Click",
description = "Swaps the behavior of shift-click when withdrawing from bank.",
section = itemSection
)
default ShiftWithdrawMode bankWithdrawShiftClick()
{
return ShiftWithdrawMode.OFF;
}
@ConfigItem(
keyName = "bankDepositShiftClick",
name = "Bank Deposit Shift-Click",
description = "Swaps the behavior of shift-click when depositing to bank.",
section = itemSection
)
default ShiftDepositMode bankDepositShiftClick()
{
return ShiftDepositMode.OFF;
}
@ConfigItem(
keyName = "shopBuy",
name = "Shop Buy Shift-Click",
description = "Swaps the Buy options with Value on items in shops when shift is held.",
section = uiSection
)
default BuyMode shopBuy()
{
return BuyMode.OFF;
}
@ConfigItem(
keyName = "shopSell",
name = "Shop Sell Shift-Click",
description = "Swaps the Sell options with Value on items in your inventory when selling to shops when shift is held.",
section = uiSection
)
default SellMode shopSell()
{
return SellMode.OFF;
}
@ConfigItem(
keyName = "swapEssenceMineTeleport",
name = "Essence Mine Teleport",
description = "Swaps Talk-To with Teleport for NPCs which teleport you to the essence mine",
section = npcSection
)
default boolean swapEssenceMineTeleport()
{
return false;
}
@ConfigItem(
keyName = "swapNets",
name = "Nets",
description = "Swap Talk-to with Nets on Annette",
section = npcSection
)
default boolean swapNets()
{
return true;
}
@ConfigItem(
keyName = "swapGauntlet",
name = "Corrupted Gauntlet",
description = "Swap Enter with Enter-corrupted when entering The Gauntlet",
section = objectSection
)
default boolean swapGauntlet()
{
return false;
}
@ConfigItem(
keyName = "swapTan",
name = "Tan",
description = "Swap Tan 1 with Tan All",
section = uiSection
)
default boolean swapTan()
{
return false;
}
@ConfigItem(
keyName = "swapCollectMiscellania",
name = "Miscellania",
description = "Swap Talk-to with Collect for Advisor Ghrim",
section = npcSection
)
default boolean swapCollectMiscellania()
{
return false;
}
@ConfigItem(
keyName = "swapDepositItems",
name = "Deposit Items",
description = "Swap Talk-to with Deposit-items",
section = npcSection
)
default boolean swapDepositItems()
{
return false;
}
}

View File

@@ -0,0 +1,851 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Kamiel
* Copyright (c) 2019, Rami <https://github.com/Rami-J>
* 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.menuentryswapper;
import com.google.common.annotations.VisibleForTesting;
import static com.google.common.base.Predicates.alwaysTrue;
import static com.google.common.base.Predicates.equalTo;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.Provides;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.ItemComposition;
import net.runelite.api.KeyCode;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.events.ClientTick;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOpened;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.PostItemComposition;
import net.runelite.api.events.WidgetMenuOptionClicked;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.ItemVariationMapping;
import net.runelite.client.menus.MenuManager;
import net.runelite.client.menus.WidgetMenuOption;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.ArrayUtils;
@PluginDescriptor(
name = "Menu Entry Swapper",
description = "Change the default option that is displayed when hovering over objects",
tags = {"npcs", "inventory", "items", "objects"},
enabledByDefault = false
)
public class MenuEntrySwapperPlugin extends Plugin
{
private static final String CONFIGURE = "Configure";
private static final String SAVE = "Save";
private static final String RESET = "Reset";
private static final String MENU_TARGET = "Shift-click";
private static final String SHIFTCLICK_CONFIG_GROUP = "shiftclick";
private static final String ITEM_KEY_PREFIX = "item_";
private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE,
MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB);
private static final WidgetMenuOption FIXED_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE,
MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB);
private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE,
MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB);
private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE,
MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB);
private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE,
MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB);
private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE,
MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB);
private static final Set<MenuAction> NPC_MENU_TYPES = ImmutableSet.of(
MenuAction.NPC_FIRST_OPTION,
MenuAction.NPC_SECOND_OPTION,
MenuAction.NPC_THIRD_OPTION,
MenuAction.NPC_FOURTH_OPTION,
MenuAction.NPC_FIFTH_OPTION,
MenuAction.EXAMINE_NPC);
private static final Set<String> ESSENCE_MINE_NPCS = ImmutableSet.of(
"aubury",
"wizard sedridor",
"wizard distentor",
"wizard cromperty",
"brimstail"
);
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private MenuEntrySwapperConfig config;
@Inject
private ConfigManager configManager;
@Inject
private MenuManager menuManager;
@Inject
private ItemManager itemManager;
@Getter
private boolean configuringShiftClick = false;
private final Multimap<String, Swap> swaps = LinkedHashMultimap.create();
private final ArrayListMultimap<String, Integer> optionIndexes = ArrayListMultimap.create();
@Provides
MenuEntrySwapperConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(MenuEntrySwapperConfig.class);
}
@Override
public void startUp()
{
if (config.shiftClickCustomization())
{
enableCustomization();
}
setupSwaps();
}
@Override
public void shutDown()
{
disableCustomization();
swaps.clear();
}
@VisibleForTesting
void setupSwaps()
{
swap("talk-to", "mage of zamorak", "teleport", config::swapAbyssTeleport);
swap("talk-to", "rionasta", "send-parcel", config::swapHardWoodGroveParcel);
swap("talk-to", "captain khaled", "task", config::swapCaptainKhaled);
swap("talk-to", "bank", config::swapBank);
swap("talk-to", "contract", config::swapContract);
swap("talk-to", "exchange", config::swapExchange);
swap("talk-to", "help", config::swapHelp);
swap("talk-to", "nets", config::swapNets);
swap("talk-to", "repairs", config::swapDarkMage);
// make sure assignment swap is higher priority than trade swap for slayer masters
swap("talk-to", "assignment", config::swapAssignment);
swap("talk-to", "trade", config::swapTrade);
swap("talk-to", "trade-with", config::swapTrade);
swap("talk-to", "shop", config::swapTrade);
swap("talk-to", "robin", "claim-slime", config::claimSlime);
swap("talk-to", "travel", config::swapTravel);
swap("talk-to", "pay-fare", config::swapTravel);
swap("talk-to", "charter", config::swapTravel);
swap("talk-to", "take-boat", config::swapTravel);
swap("talk-to", "fly", config::swapTravel);
swap("talk-to", "jatizso", config::swapTravel);
swap("talk-to", "neitiznot", config::swapTravel);
swap("talk-to", "rellekka", config::swapTravel);
swap("talk-to", "ungael", config::swapTravel);
swap("talk-to", "pirate's cove", config::swapTravel);
swap("talk-to", "waterbirth island", config::swapTravel);
swap("talk-to", "island of stone", config::swapTravel);
swap("talk-to", "miscellania", config::swapTravel);
swap("talk-to", "follow", config::swapTravel);
swap("talk-to", "transport", config::swapTravel);
swap("talk-to", "pay", config::swapPay);
swapContains("talk-to", alwaysTrue(), "pay (", config::swapPay);
swap("talk-to", "decant", config::swapDecant);
swap("talk-to", "quick-travel", config::swapQuick);
swap("talk-to", "enchant", config::swapEnchant);
swap("talk-to", "start-minigame", config::swapStartMinigame);
swap("talk-to", ESSENCE_MINE_NPCS::contains, "teleport", config::swapEssenceMineTeleport);
swap("talk-to", "collect", config::swapCollectMiscellania);
swap("talk-to", "deposit-items", config::swapDepositItems);
swap("leave tomb", "quick-leave", config::swapQuickLeave);
swap("tomb door", "quick-leave", config::swapQuickLeave);
swap("pass", "energy barrier", "pay-toll(2-ecto)", config::swapTravel);
swap("open", "gate", "pay-toll(10gp)", config::swapTravel);
swap("open", "hardwood grove doors", "quick-pay(100)", config::swapHardWoodGrove);
swap("inspect", "trapdoor", "travel", config::swapTravel);
swap("board", "travel cart", "pay-fare", config::swapTravel);
swap("cage", "harpoon", config::swapHarpoon);
swap("big net", "harpoon", config::swapHarpoon);
swap("net", "harpoon", config::swapHarpoon);
swap("enter", "portal", "home", () -> config.swapHomePortal() == HouseMode.HOME);
swap("enter", "portal", "build mode", () -> config.swapHomePortal() == HouseMode.BUILD_MODE);
swap("enter", "portal", "friend's house", () -> config.swapHomePortal() == HouseMode.FRIENDS_HOUSE);
swap("view", "add-house", () -> config.swapHouseAdvertisement() == HouseAdvertisementMode.ADD_HOUSE);
swap("view", "visit-last", () -> config.swapHouseAdvertisement() == HouseAdvertisementMode.VISIT_LAST);
for (String option : new String[]{"zanaris", "tree"})
{
swapContains(option, alwaysTrue(), "last-destination", () -> config.swapFairyRing() == FairyRingMode.LAST_DESTINATION);
swapContains(option, alwaysTrue(), "configure", () -> config.swapFairyRing() == FairyRingMode.CONFIGURE);
}
swapContains("configure", alwaysTrue(), "last-destination", () ->
config.swapFairyRing() == FairyRingMode.LAST_DESTINATION || config.swapFairyRing() == FairyRingMode.ZANARIS);
swapContains("tree", alwaysTrue(), "zanaris", () -> config.swapFairyRing() == FairyRingMode.ZANARIS);
swap("check", "reset", config::swapBoxTrap);
swap("dismantle", "reset", config::swapBoxTrap);
swap("take", "lay", config::swapBoxTrap);
swap("pick-up", "chase", config::swapChase);
swap("interact", target -> target.endsWith("birdhouse"), "empty", config::swapBirdhouseEmpty);
swap("enter", "the gauntlet", "enter-corrupted", config::swapGauntlet);
swap("enter", "quick-enter", config::swapQuick);
swap("ring", "quick-start", config::swapQuick);
swap("pass", "quick-pass", config::swapQuick);
swap("pass", "quick pass", config::swapQuick);
swap("open", "quick-open", config::swapQuick);
swap("climb-down", "quick-start", config::swapQuick);
swap("climb-down", "pay", config::swapQuick);
swap("admire", "teleport", config::swapAdmire);
swap("admire", "spellbook", config::swapAdmire);
swap("admire", "perks", config::swapAdmire);
swap("teleport menu", "duel arena", config::swapJewelleryBox);
swap("teleport menu", "castle wars", config::swapJewelleryBox);
swap("teleport menu", "ferox enclave", config::swapJewelleryBox);
swap("teleport menu", "burthorpe", config::swapJewelleryBox);
swap("teleport menu", "barbarian outpost", config::swapJewelleryBox);
swap("teleport menu", "corporeal beast", config::swapJewelleryBox);
swap("teleport menu", "tears of guthix", config::swapJewelleryBox);
swap("teleport menu", "wintertodt camp", config::swapJewelleryBox);
swap("teleport menu", "warriors' guild", config::swapJewelleryBox);
swap("teleport menu", "champions' guild", config::swapJewelleryBox);
swap("teleport menu", "monastery", config::swapJewelleryBox);
swap("teleport menu", "ranging guild", config::swapJewelleryBox);
swap("teleport menu", "fishing guild", config::swapJewelleryBox);
swap("teleport menu", "mining guild", config::swapJewelleryBox);
swap("teleport menu", "crafting guild", config::swapJewelleryBox);
swap("teleport menu", "cooking guild", config::swapJewelleryBox);
swap("teleport menu", "woodcutting guild", config::swapJewelleryBox);
swap("teleport menu", "farming guild", config::swapJewelleryBox);
swap("teleport menu", "miscellania", config::swapJewelleryBox);
swap("teleport menu", "grand exchange", config::swapJewelleryBox);
swap("teleport menu", "falador park", config::swapJewelleryBox);
swap("teleport menu", "dondakan's rock", config::swapJewelleryBox);
swap("teleport menu", "edgeville", config::swapJewelleryBox);
swap("teleport menu", "karamja", config::swapJewelleryBox);
swap("teleport menu", "draynor village", config::swapJewelleryBox);
swap("teleport menu", "al kharid", config::swapJewelleryBox);
swap("shared", "private", config::swapPrivate);
swap("pick", "pick-lots", config::swapPick);
swap("view offer", "abort offer", () -> shiftModifier() && config.swapGEAbort());
Arrays.asList(
"honest jimmy", "bert the sandman", "advisor ghrim", "dark mage", "lanthus", "turael", "mazchna", "vannaka",
"chaeldar", "nieve", "steve", "duradel", "krystilia", "konar", "murphy", "cyrisus", "smoggy", "ginea", "watson",
"barbarian guard", "amy", "random"
).forEach(npc -> swap("cast", "npc contact", npc, () -> shiftModifier() && config.swapNpcContact()));
swap("value", "buy 1", () -> shiftModifier() && config.shopBuy() == BuyMode.BUY_1);
swap("value", "buy 5", () -> shiftModifier() && config.shopBuy() == BuyMode.BUY_5);
swap("value", "buy 10", () -> shiftModifier() && config.shopBuy() == BuyMode.BUY_10);
swap("value", "buy 50", () -> shiftModifier() && config.shopBuy() == BuyMode.BUY_50);
swap("value", "sell 1", () -> shiftModifier() && config.shopSell() == SellMode.SELL_1);
swap("value", "sell 5", () -> shiftModifier() && config.shopSell() == SellMode.SELL_5);
swap("value", "sell 10", () -> shiftModifier() && config.shopSell() == SellMode.SELL_10);
swap("value", "sell 50", () -> shiftModifier() && config.shopSell() == SellMode.SELL_50);
swap("wear", "rub", config::swapTeleportItem);
swap("wear", "teleport", config::swapTeleportItem);
swap("wield", "teleport", config::swapTeleportItem);
swap("bury", "use", config::swapBones);
swap("clean", "use", config::swapHerbs);
swap("read", "recite-prayer", config::swapPrayerBook);
swap("collect-note", "collect-item", () -> config.swapGEItemCollect() == GEItemCollectMode.ITEMS);
swap("collect-notes", "collect-items", () -> config.swapGEItemCollect() == GEItemCollectMode.ITEMS);
swap("collect-item", "collect-note", () -> config.swapGEItemCollect() == GEItemCollectMode.NOTES);
swap("collect-items", "collect-notes", () -> config.swapGEItemCollect() == GEItemCollectMode.NOTES);
swap("collect to inventory", "collect to bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("collect", "bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("collect-note", "bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("collect-notes", "bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("collect-item", "bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("collect-items", "bank", () -> config.swapGEItemCollect() == GEItemCollectMode.BANK);
swap("tan 1", "tan all", config::swapTan);
swapTeleport("varrock teleport", "grand exchange");
swapTeleport("camelot teleport", "seers'");
swapTeleport("watchtower teleport", "yanille");
swapTeleport("teleport to house", "outside");
}
private void swap(String option, String swappedOption, Supplier<Boolean> enabled)
{
swap(option, alwaysTrue(), swappedOption, enabled);
}
private void swap(String option, String target, String swappedOption, Supplier<Boolean> enabled)
{
swap(option, equalTo(target), swappedOption, enabled);
}
private void swap(String option, Predicate<String> targetPredicate, String swappedOption, Supplier<Boolean> enabled)
{
swaps.put(option, new Swap(alwaysTrue(), targetPredicate, swappedOption, enabled, true));
}
private void swapContains(String option, Predicate<String> targetPredicate, String swappedOption, Supplier<Boolean> enabled)
{
swaps.put(option, new Swap(alwaysTrue(), targetPredicate, swappedOption, enabled, false));
}
private void swapTeleport(String option, String swappedOption)
{
swap("cast", option, swappedOption, () -> shiftModifier() && config.swapTeleportSpell());
swap(swappedOption, option, "cast", () -> shiftModifier() && config.swapTeleportSpell());
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals(MenuEntrySwapperConfig.GROUP) && event.getKey().equals("shiftClickCustomization"))
{
if (config.shiftClickCustomization())
{
enableCustomization();
}
else
{
disableCustomization();
}
}
else if (event.getGroup().equals(SHIFTCLICK_CONFIG_GROUP) && event.getKey().startsWith(ITEM_KEY_PREFIX))
{
clientThread.invoke(this::resetItemCompositionCache);
}
}
private void resetItemCompositionCache()
{
itemManager.invalidateItemCompositionCache();
client.getItemCompositionCache().reset();
}
private Integer getSwapConfig(int itemId)
{
itemId = ItemVariationMapping.map(itemId);
String config = configManager.getConfiguration(SHIFTCLICK_CONFIG_GROUP, ITEM_KEY_PREFIX + itemId);
if (config == null || config.isEmpty())
{
return null;
}
return Integer.parseInt(config);
}
private void setSwapConfig(int itemId, int index)
{
itemId = ItemVariationMapping.map(itemId);
configManager.setConfiguration(SHIFTCLICK_CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, index);
}
private void unsetSwapConfig(int itemId)
{
itemId = ItemVariationMapping.map(itemId);
configManager.unsetConfiguration(SHIFTCLICK_CONFIG_GROUP, ITEM_KEY_PREFIX + itemId);
}
private void enableCustomization()
{
refreshShiftClickCustomizationMenus();
// set shift click action index on the item compositions
clientThread.invoke(this::resetItemCompositionCache);
}
private void disableCustomization()
{
removeShiftClickCustomizationMenus();
configuringShiftClick = false;
// flush item compositions to reset the shift click action index
clientThread.invoke(this::resetItemCompositionCache);
}
@Subscribe
public void onWidgetMenuOptionClicked(WidgetMenuOptionClicked event)
{
if (event.getWidget() == WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB
|| event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB
|| event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB)
{
configuringShiftClick = event.getMenuOption().equals(CONFIGURE) && Text.removeTags(event.getMenuTarget()).equals(MENU_TARGET);
refreshShiftClickCustomizationMenus();
}
}
@Subscribe
public void onMenuOpened(MenuOpened event)
{
if (!configuringShiftClick)
{
return;
}
MenuEntry firstEntry = event.getFirstEntry();
if (firstEntry == null)
{
return;
}
int widgetId = firstEntry.getParam1();
if (widgetId != WidgetInfo.INVENTORY.getId())
{
return;
}
int itemId = firstEntry.getIdentifier();
if (itemId == -1)
{
return;
}
ItemComposition itemComposition = client.getItemDefinition(itemId);
String itemName = itemComposition.getName();
String option = "Use";
int shiftClickActionIndex = itemComposition.getShiftClickActionIndex();
String[] inventoryActions = itemComposition.getInventoryActions();
if (shiftClickActionIndex >= 0 && shiftClickActionIndex < inventoryActions.length)
{
option = inventoryActions[shiftClickActionIndex];
}
MenuEntry[] entries = event.getMenuEntries();
for (MenuEntry entry : entries)
{
if (itemName.equals(Text.removeTags(entry.getTarget())))
{
entry.setType(MenuAction.RUNELITE.getId());
if (option.equals(entry.getOption()))
{
entry.setOption("* " + option);
}
}
}
final MenuEntry resetShiftClickEntry = new MenuEntry();
resetShiftClickEntry.setOption(RESET);
resetShiftClickEntry.setTarget(MENU_TARGET);
resetShiftClickEntry.setIdentifier(itemId);
resetShiftClickEntry.setParam1(widgetId);
resetShiftClickEntry.setType(MenuAction.RUNELITE.getId());
client.setMenuEntries(ArrayUtils.addAll(entries, resetShiftClickEntry));
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
{
// This swap needs to happen prior to drag start on click, which happens during
// widget ticking and prior to our client tick event. This is because drag start
// is what builds the context menu row which is what the eventual click will use
// Swap to shift-click deposit behavior
// Deposit- op 1 is the current withdraw amount 1/5/10/x for deposit box interface and chambers of xeric storage unit.
// Deposit- op 2 is the current withdraw amount 1/5/10/x for bank interface
if (shiftModifier() && config.bankDepositShiftClick() != ShiftDepositMode.OFF
&& menuEntryAdded.getType() == MenuAction.CC_OP.getId()
&& (menuEntryAdded.getIdentifier() == 2 || menuEntryAdded.getIdentifier() == 1)
&& (menuEntryAdded.getOption().startsWith("Deposit-") || menuEntryAdded.getOption().startsWith("Store") || menuEntryAdded.getOption().startsWith("Donate")))
{
ShiftDepositMode shiftDepositMode = config.bankDepositShiftClick();
final int widgetGroupId = WidgetInfo.TO_GROUP(menuEntryAdded.getActionParam1());
final int opId = widgetGroupId == WidgetID.DEPOSIT_BOX_GROUP_ID ? shiftDepositMode.getIdentifierDepositBox()
: widgetGroupId == WidgetID.CHAMBERS_OF_XERIC_STORAGE_UNIT_INVENTORY_GROUP_ID ? shiftDepositMode.getIdentifierChambersStorageUnit()
: shiftDepositMode.getIdentifier();
final int actionId = opId >= 6 ? MenuAction.CC_OP_LOW_PRIORITY.getId() : MenuAction.CC_OP.getId();
bankModeSwap(actionId, opId);
}
// Swap to shift-click withdraw behavior
// Deposit- op 1 is the current withdraw amount 1/5/10/x
if (shiftModifier() && config.bankWithdrawShiftClick() != ShiftWithdrawMode.OFF
&& menuEntryAdded.getType() == MenuAction.CC_OP.getId() && menuEntryAdded.getIdentifier() == 1
&& menuEntryAdded.getOption().startsWith("Withdraw"))
{
ShiftWithdrawMode shiftWithdrawMode = config.bankWithdrawShiftClick();
final int widgetGroupId = WidgetInfo.TO_GROUP(menuEntryAdded.getActionParam1());
final int actionId, opId;
if (widgetGroupId == WidgetID.CHAMBERS_OF_XERIC_STORAGE_UNIT_PRIVATE_GROUP_ID || widgetGroupId == WidgetID.CHAMBERS_OF_XERIC_STORAGE_UNIT_SHARED_GROUP_ID)
{
actionId = MenuAction.CC_OP.getId();
opId = shiftWithdrawMode.getIdentifierChambersStorageUnit();
}
else
{
actionId = shiftWithdrawMode.getMenuAction().getId();
opId = shiftWithdrawMode.getIdentifier();
}
bankModeSwap(actionId, opId);
}
}
private void bankModeSwap(int entryTypeId, int entryIdentifier)
{
MenuEntry[] menuEntries = client.getMenuEntries();
for (int i = menuEntries.length - 1; i >= 0; --i)
{
MenuEntry entry = menuEntries[i];
if (entry.getType() == entryTypeId && entry.getIdentifier() == entryIdentifier)
{
// Raise the priority of the op so it doesn't get sorted later
entry.setType(MenuAction.CC_OP.getId());
menuEntries[i] = menuEntries[menuEntries.length - 1];
menuEntries[menuEntries.length - 1] = entry;
client.setMenuEntries(menuEntries);
break;
}
}
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
if (event.getMenuAction() != MenuAction.RUNELITE || event.getWidgetId() != WidgetInfo.INVENTORY.getId())
{
return;
}
int itemId = event.getId();
if (itemId == -1)
{
return;
}
String option = event.getMenuOption();
String target = event.getMenuTarget();
ItemComposition itemComposition = client.getItemDefinition(itemId);
if (option.equals(RESET) && target.equals(MENU_TARGET))
{
unsetSwapConfig(itemId);
return;
}
if (!itemComposition.getName().equals(Text.removeTags(target)))
{
return;
}
if (option.equals("Use")) //because "Use" is not in inventoryActions
{
setSwapConfig(itemId, -1);
}
else
{
String[] inventoryActions = itemComposition.getInventoryActions();
for (int index = 0; index < inventoryActions.length; index++)
{
if (option.equals(inventoryActions[index]))
{
setSwapConfig(itemId, index);
break;
}
}
}
}
private void swapMenuEntry(int index, MenuEntry menuEntry)
{
final int eventId = menuEntry.getIdentifier();
final MenuAction menuAction = MenuAction.of(menuEntry.getType());
final String option = Text.removeTags(menuEntry.getOption()).toLowerCase();
final String target = Text.removeTags(menuEntry.getTarget()).toLowerCase();
final NPC hintArrowNpc = client.getHintArrowNpc();
if (hintArrowNpc != null
&& hintArrowNpc.getIndex() == eventId
&& NPC_MENU_TYPES.contains(menuAction))
{
return;
}
if (shiftModifier() && (menuAction == MenuAction.ITEM_FIRST_OPTION
|| menuAction == MenuAction.ITEM_SECOND_OPTION
|| menuAction == MenuAction.ITEM_THIRD_OPTION
|| menuAction == MenuAction.ITEM_FOURTH_OPTION
|| menuAction == MenuAction.ITEM_FIFTH_OPTION
|| menuAction == MenuAction.ITEM_USE))
{
// Special case use shift click due to items not actually containing a "Use" option, making
// the client unable to perform the swap itself.
if (config.shiftClickCustomization() && !option.equals("use"))
{
Integer customOption = getSwapConfig(eventId);
if (customOption != null && customOption == -1)
{
swap("use", target, index, true);
}
}
// don't perform swaps on items when shift is held; instead prefer the client menu swap, which
// we may have overwrote
return;
}
Collection<Swap> swaps = this.swaps.get(option);
for (Swap swap : swaps)
{
if (swap.getTargetPredicate().test(target) && swap.getEnabled().get())
{
if (swap(swap.getSwappedOption(), target, index, swap.isStrict()))
{
break;
}
}
}
}
@Subscribe
public void onClientTick(ClientTick clientTick)
{
// The menu is not rebuilt when it is open, so don't swap or else it will
// repeatedly swap entries
if (client.getGameState() != GameState.LOGGED_IN || client.isMenuOpen())
{
return;
}
MenuEntry[] menuEntries = client.getMenuEntries();
// Build option map for quick lookup in findIndex
int idx = 0;
optionIndexes.clear();
for (MenuEntry entry : menuEntries)
{
String option = Text.removeTags(entry.getOption()).toLowerCase();
optionIndexes.put(option, idx++);
}
// Perform swaps
idx = 0;
for (MenuEntry entry : menuEntries)
{
swapMenuEntry(idx++, entry);
}
}
@Subscribe
public void onPostItemComposition(PostItemComposition event)
{
if (!config.shiftClickCustomization())
{
// since shift-click is done by the client we have to check if our shift click customization is on
// prior to altering the item shift click action index.
return;
}
ItemComposition itemComposition = event.getItemComposition();
Integer option = getSwapConfig(itemComposition.getId());
if (option != null)
{
itemComposition.setShiftClickActionIndex(option);
}
}
private boolean swap(String option, String target, int index, boolean strict)
{
MenuEntry[] menuEntries = client.getMenuEntries();
// find option to swap with
int optionIdx = findIndex(menuEntries, index, option, target, strict);
if (optionIdx >= 0)
{
swap(optionIndexes, menuEntries, optionIdx, index);
return true;
}
return false;
}
private int findIndex(MenuEntry[] entries, int limit, String option, String target, boolean strict)
{
if (strict)
{
List<Integer> indexes = optionIndexes.get(option);
// We want the last index which matches the target, as that is what is top-most
// on the menu
for (int i = indexes.size() - 1; i >= 0; --i)
{
int idx = indexes.get(i);
MenuEntry entry = entries[idx];
String entryTarget = Text.removeTags(entry.getTarget()).toLowerCase();
// Limit to the last index which is prior to the current entry
if (idx < limit && entryTarget.equals(target))
{
return idx;
}
}
}
else
{
// Without strict matching we have to iterate all entries up to the current limit...
for (int i = limit - 1; i >= 0; i--)
{
MenuEntry entry = entries[i];
String entryOption = Text.removeTags(entry.getOption()).toLowerCase();
String entryTarget = Text.removeTags(entry.getTarget()).toLowerCase();
if (entryOption.contains(option.toLowerCase()) && entryTarget.equals(target))
{
return i;
}
}
}
return -1;
}
private void swap(ArrayListMultimap<String, Integer> optionIndexes, MenuEntry[] entries, int index1, int index2)
{
MenuEntry entry1 = entries[index1],
entry2 = entries[index2];
entries[index1] = entry2;
entries[index2] = entry1;
client.setMenuEntries(entries);
// Update optionIndexes
String option1 = Text.removeTags(entry1.getOption()).toLowerCase(),
option2 = Text.removeTags(entry2.getOption()).toLowerCase();
List<Integer> list1 = optionIndexes.get(option1),
list2 = optionIndexes.get(option2);
// call remove(Object) instead of remove(int)
list1.remove((Integer) index1);
list2.remove((Integer) index2);
sortedInsert(list1, index2);
sortedInsert(list2, index1);
}
private static <T extends Comparable<? super T>> void sortedInsert(List<T> list, T value) // NOPMD: UnusedPrivateMethod: false positive
{
int idx = Collections.binarySearch(list, value);
list.add(idx < 0 ? -idx - 1 : idx, value);
}
private void removeShiftClickCustomizationMenus()
{
menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE);
menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE);
menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE);
menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE);
menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE);
menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE);
}
private void refreshShiftClickCustomizationMenus()
{
removeShiftClickCustomizationMenus();
if (configuringShiftClick)
{
menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE);
menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE);
menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE);
}
else
{
menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE);
menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE);
menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE);
}
}
private boolean shiftModifier()
{
return client.isKeyPressed(KeyCode.KC_SHIFT);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020, Sam <dasistkeinnamen@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.menuentryswapper;
public enum SellMode
{
OFF,
SELL_1,
SELL_5,
SELL_10,
SELL_50;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2020, Zach <https://github.com/zacharydwaller>
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ShiftDepositMode
{
DEPOSIT_1("Deposit-1", 3, 2, 1),
DEPOSIT_5("Deposit-5", 4, 3, 2),
DEPOSIT_10("Deposit-10", 5, 4, 3),
DEPOSIT_X("Deposit-X", 6, 6, 5),
DEPOSIT_ALL("Deposit-All", 8, 5, 4),
EXTRA_OP("Eat/Wield/Etc.", 9, 0, 0),
OFF("Off", 0, 0, 0);
private final String name;
private final int identifier;
private final int identifierDepositBox;
private final int identifierChambersStorageUnit;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2020, Zach <https://github.com/zacharydwaller>
* 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.menuentryswapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.MenuAction;
@Getter
@RequiredArgsConstructor
public enum ShiftWithdrawMode
{
WITHDRAW_1("Withdraw-1", MenuAction.CC_OP, 2, 1),
WITHDRAW_5("Withdraw-5", MenuAction.CC_OP, 3, 2),
WITHDRAW_10("Withdraw-10", MenuAction.CC_OP, 4, 3),
WITHDRAW_X("Withdraw-X", MenuAction.CC_OP, 5, 5),
WITHDRAW_ALL("Withdraw-All", MenuAction.CC_OP_LOW_PRIORITY, 7, 4),
// chambers of xeric storage units do not have an "all-but-1" option, so this option will choose "Withdraw-all"
// instead when using the storage unit.
WITHDRAW_ALL_BUT_1("Withdraw-All-But-1", MenuAction.CC_OP_LOW_PRIORITY, 8, 4),
OFF("Off", MenuAction.UNKNOWN, 0, 0);
private final String name;
private final MenuAction menuAction;
private final int identifier;
private final int identifierChambersStorageUnit;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, 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.menuentryswapper;
import java.util.function.Predicate;
import java.util.function.Supplier;
import lombok.Value;
@Value
class Swap
{
private Predicate<String> optionPredicate;
private Predicate<String> targetPredicate;
private String swappedOption;
private Supplier<Boolean> enabled;
private boolean strict;
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018, SomeoneWithAnInternetConnection
* Copyright (c) 2018, oplosthee <https://github.com/oplosthee>
* 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.metronome;
import com.google.inject.Provides;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Preferences;
import net.runelite.api.SoundEffectID;
import net.runelite.api.events.GameTick;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@PluginDescriptor(
name = "Metronome",
description = "Play a sound on a specified tick to aid in efficient skilling",
tags = {"skilling", "tick", "timers"},
enabledByDefault = false
)
public class MetronomePlugin extends Plugin
{
@Inject
private Client client;
@Inject
private MetronomePluginConfiguration config;
private int tickCounter = 0;
private boolean shouldTock = false;
@Provides
MetronomePluginConfiguration provideConfig(ConfigManager configManager)
{
return configManager.getConfig(MetronomePluginConfiguration.class);
}
@Override
protected void shutDown()
{
tickCounter = 0;
shouldTock = false;
}
@Subscribe
public void onGameTick(GameTick tick)
{
if (config.tickCount() == 0)
{
return;
}
if (++tickCounter % config.tickCount() == 0)
{
// As playSoundEffect only uses the volume argument when the in-game volume isn't muted, sound effect volume
// needs to be set to the value desired for ticks or tocks and afterwards reset to the previous value.
Preferences preferences = client.getPreferences();
int previousVolume = preferences.getSoundEffectVolume();
if (shouldTock && config.tockVolume() > 0)
{
preferences.setSoundEffectVolume(config.tockVolume());
client.playSoundEffect(SoundEffectID.GE_DECREMENT_PLOP, config.tockVolume());
}
else if (config.tickVolume() > 0)
{
preferences.setSoundEffectVolume(config.tickVolume());
client.playSoundEffect(SoundEffectID.GE_INCREMENT_PLOP, config.tickVolume());
}
preferences.setSoundEffectVolume(previousVolume);
shouldTock = !shouldTock;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2018, SomeoneWithAnInternetConnection
* Copyright (c) 2018, oplosthee <https://github.com/oplosthee>
* 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.metronome;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Range;
import net.runelite.api.SoundEffectVolume;
@ConfigGroup("metronome")
public interface MetronomePluginConfiguration extends Config
{
int VOLUME_MAX = SoundEffectVolume.HIGH;
@ConfigItem(
keyName = "tickCount",
name = "Tick count",
description = "Configures the tick on which a sound will be played."
)
default int tickCount()
{
return 1;
}
@Range(
max = VOLUME_MAX
)
@ConfigItem(
keyName = "tickVolume",
name = "Tick volume",
description = "Configures the volume of the tick sound. A value of 0 will disable tick sounds."
)
default int tickVolume()
{
return SoundEffectVolume.MEDIUM_HIGH;
}
@Range(
max = VOLUME_MAX
)
@ConfigItem(
keyName = "tockVolume",
name = "Tock volume",
description = "Configures the volume of the tock sound. A value of 0 will disable tock sounds."
)
default int tockVolume()
{
return SoundEffectVolume.MUTED;
}
}

View File

@@ -136,7 +136,7 @@ class MouseHighlightOverlay extends Overlay
if (WIDGET_MENU_ACTIONS.contains(type))
{
final int widgetId = menuEntry.getParam1();
final int groupId = WidgetInfo.getGroupFromID(widgetId);
final int groupId = WidgetInfo.TO_GROUP(widgetId);
if (!config.uiTooltip())
{

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npchighlight;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.coords.WorldPoint;
class MemorizedNpc
{
@Getter
private int npcIndex;
@Getter
private String npcName;
@Getter
private int npcSize;
/**
* The time the npc died at, in game ticks, relative to the tick counter
*/
@Getter
@Setter
private int diedOnTick;
/**
* The time it takes for the npc to respawn, in game ticks
*/
@Getter
@Setter
private int respawnTime;
@Getter
@Setter
private List<WorldPoint> possibleRespawnLocations;
MemorizedNpc(NPC npc)
{
this.npcName = npc.getName();
this.npcIndex = npc.getIndex();
this.possibleRespawnLocations = new ArrayList<>();
this.respawnTime = -1;
this.diedOnTick = -1;
final NPCComposition composition = npc.getTransformedComposition();
if (composition != null)
{
this.npcSize = composition.getSize();
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.npchighlight;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup("npcindicators")
public interface NpcIndicatorsConfig extends Config
{
@ConfigSection(
name = "Render style",
description = "The render style of NPC highlighting",
position = 0
)
String renderStyleSection = "renderStyleSection";
@ConfigItem(
position = 0,
keyName = "highlightHull",
name = "Highlight hull",
description = "Configures whether or not NPC should be highlighted by hull",
section = renderStyleSection
)
default boolean highlightHull()
{
return true;
}
@ConfigItem(
position = 1,
keyName = "highlightTile",
name = "Highlight tile",
description = "Configures whether or not NPC should be highlighted by tile",
section = renderStyleSection
)
default boolean highlightTile()
{
return false;
}
@ConfigItem(
position = 2,
keyName = "highlightSouthWestTile",
name = "Highlight south west tile",
description = "Configures whether or not NPC should be highlighted by south western tile",
section = renderStyleSection
)
default boolean highlightSouthWestTile()
{
return false;
}
@ConfigItem(
position = 3,
keyName = "npcToHighlight",
name = "NPCs to Highlight",
description = "List of NPC names to highlight"
)
default String getNpcToHighlight()
{
return "";
}
@ConfigItem(
keyName = "npcToHighlight",
name = "",
description = ""
)
void setNpcToHighlight(String npcsToHighlight);
@ConfigItem(
position = 4,
keyName = "npcColor",
name = "Highlight Color",
description = "Color of the NPC highlight"
)
@Alpha
default Color getHighlightColor()
{
return Color.CYAN;
}
@ConfigItem(
position = 5,
keyName = "drawNames",
name = "Draw names above NPC",
description = "Configures whether or not NPC names should be drawn above the NPC"
)
default boolean drawNames()
{
return false;
}
@ConfigItem(
position = 6,
keyName = "drawMinimapNames",
name = "Draw names on minimap",
description = "Configures whether or not NPC names should be drawn on the minimap"
)
default boolean drawMinimapNames()
{
return false;
}
@ConfigItem(
position = 7,
keyName = "highlightMenuNames",
name = "Highlight menu names",
description = "Highlight NPC names in right click menu"
)
default boolean highlightMenuNames()
{
return false;
}
@ConfigItem(
position = 8,
keyName = "ignoreDeadNpcs",
name = "Ignore dead NPCs",
description = "Prevents highlighting NPCs after they are dead"
)
default boolean ignoreDeadNpcs()
{
return true;
}
@ConfigItem(
position = 9,
keyName = "deadNpcMenuColor",
name = "Dead NPC menu color",
description = "Color of the NPC menus for dead NPCs"
)
Color deadNpcMenuColor();
@ConfigItem(
position = 10,
keyName = "showRespawnTimer",
name = "Show respawn timer",
description = "Show respawn timer of tagged NPCs")
default boolean showRespawnTimer()
{
return false;
}
}

View File

@@ -0,0 +1,662 @@
/*
* Copyright (c) 2018, James Swindle <wilingua@gmail.com>
* Copyright (c) 2018, 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.npchighlight;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.awt.Color;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.GraphicID;
import net.runelite.api.GraphicsObject;
import net.runelite.api.KeyCode;
import net.runelite.api.MenuAction;
import static net.runelite.api.MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.GraphicsObjectCreated;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.Text;
import net.runelite.client.util.WildcardMatcher;
@PluginDescriptor(
name = "NPC Indicators",
description = "Highlight NPCs on-screen and/or on the minimap",
tags = {"highlight", "minimap", "npcs", "overlay", "respawn", "tags"}
)
@Slf4j
public class NpcIndicatorsPlugin extends Plugin
{
private static final int MAX_ACTOR_VIEW_RANGE = 15;
// Option added to NPC menu
private static final String TAG = "Tag";
private static final String UNTAG = "Un-tag";
private static final String TAG_ALL = "Tag-All";
private static final String UNTAG_ALL = "Un-tag-All";
private static final Set<MenuAction> NPC_MENU_ACTIONS = ImmutableSet.of(MenuAction.NPC_FIRST_OPTION, MenuAction.NPC_SECOND_OPTION,
MenuAction.NPC_THIRD_OPTION, MenuAction.NPC_FOURTH_OPTION, MenuAction.NPC_FIFTH_OPTION, MenuAction.SPELL_CAST_ON_NPC,
MenuAction.ITEM_USE_ON_NPC);
@Inject
private Client client;
@Inject
private NpcIndicatorsConfig config;
@Inject
private OverlayManager overlayManager;
@Inject
private NpcSceneOverlay npcSceneOverlay;
@Inject
private NpcMinimapOverlay npcMinimapOverlay;
@Inject
private ClientThread clientThread;
/**
* NPCs to highlight
*/
@Getter(AccessLevel.PACKAGE)
private final Set<NPC> highlightedNpcs = new HashSet<>();
/**
* Dead NPCs that should be displayed with a respawn indicator if the config is on.
*/
@Getter(AccessLevel.PACKAGE)
private final Map<Integer, MemorizedNpc> deadNpcsToDisplay = new HashMap<>();
/**
* The time when the last game tick event ran.
*/
@Getter(AccessLevel.PACKAGE)
private Instant lastTickUpdate;
/**
* Tagged NPCs that have died at some point, which are memorized to
* remember when and where they will respawn
*/
private final Map<Integer, MemorizedNpc> memorizedNpcs = new HashMap<>();
/**
* Highlight strings from the configuration
*/
private List<String> highlights = new ArrayList<>();
/**
* NPC ids marked with the Tag option
*/
private final Set<Integer> npcTags = new HashSet<>();
/**
* Tagged NPCs that spawned this tick, which need to be verified that
* they actually spawned and didn't just walk into view range.
*/
private final List<NPC> spawnedNpcsThisTick = new ArrayList<>();
/**
* Tagged NPCs that despawned this tick, which need to be verified that
* they actually spawned and didn't just walk into view range.
*/
private final List<NPC> despawnedNpcsThisTick = new ArrayList<>();
/**
* World locations of graphics object which indicate that an
* NPC teleported that were played this tick.
*/
private final Set<WorldPoint> teleportGraphicsObjectSpawnedThisTick = new HashSet<>();
/**
* The players location on the last game tick.
*/
private WorldPoint lastPlayerLocation;
/**
* When hopping worlds, NPCs can spawn without them actually respawning,
* so we would not want to mark it as a real spawn in those cases.
*/
private boolean skipNextSpawnCheck = false;
@Provides
NpcIndicatorsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(NpcIndicatorsConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(npcSceneOverlay);
overlayManager.add(npcMinimapOverlay);
clientThread.invoke(() ->
{
skipNextSpawnCheck = true;
rebuildAllNpcs();
});
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(npcSceneOverlay);
overlayManager.remove(npcMinimapOverlay);
clientThread.invoke(() ->
{
deadNpcsToDisplay.clear();
memorizedNpcs.clear();
spawnedNpcsThisTick.clear();
despawnedNpcsThisTick.clear();
teleportGraphicsObjectSpawnedThisTick.clear();
npcTags.clear();
highlightedNpcs.clear();
});
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOGIN_SCREEN ||
event.getGameState() == GameState.HOPPING)
{
highlightedNpcs.clear();
deadNpcsToDisplay.clear();
memorizedNpcs.forEach((id, npc) -> npc.setDiedOnTick(-1));
lastPlayerLocation = null;
skipNextSpawnCheck = true;
}
}
@Subscribe
public void onConfigChanged(ConfigChanged configChanged)
{
if (!configChanged.getGroup().equals("npcindicators"))
{
return;
}
clientThread.invoke(this::rebuildAllNpcs);
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded event)
{
int type = event.getType();
if (type >= MENU_ACTION_DEPRIORITIZE_OFFSET)
{
type -= MENU_ACTION_DEPRIORITIZE_OFFSET;
}
final MenuAction menuAction = MenuAction.of(type);
if (NPC_MENU_ACTIONS.contains(menuAction))
{
NPC npc = client.getCachedNPCs()[event.getIdentifier()];
Color color = null;
if (npc.isDead())
{
color = config.deadNpcMenuColor();
}
if (color == null && highlightedNpcs.contains(npc) && config.highlightMenuNames() && (!npc.isDead() || !config.ignoreDeadNpcs()))
{
color = config.getHighlightColor();
}
if (color != null)
{
MenuEntry[] menuEntries = client.getMenuEntries();
final MenuEntry menuEntry = menuEntries[menuEntries.length - 1];
final String target = ColorUtil.prependColorTag(Text.removeTags(event.getTarget()), color);
menuEntry.setTarget(target);
client.setMenuEntries(menuEntries);
}
}
else if (menuAction == MenuAction.EXAMINE_NPC && client.isKeyPressed(KeyCode.KC_SHIFT))
{
// Add tag and tag-all options
final int id = event.getIdentifier();
final NPC[] cachedNPCs = client.getCachedNPCs();
final NPC npc = cachedNPCs[id];
if (npc == null || npc.getName() == null)
{
return;
}
final String npcName = npc.getName();
boolean matchesList = highlights.stream()
.filter(highlight -> !highlight.equalsIgnoreCase(npcName))
.anyMatch(highlight -> WildcardMatcher.matches(highlight, npcName));
MenuEntry[] menuEntries = client.getMenuEntries();
// Only add Untag-All option to npcs not highlighted by a wildcard entry, because untag-all will not remove wildcards
if (!matchesList)
{
menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 2);
final MenuEntry tagAllEntry = menuEntries[menuEntries.length - 2] = new MenuEntry();
tagAllEntry.setOption(highlights.stream().anyMatch(npcName::equalsIgnoreCase) ? UNTAG_ALL : TAG_ALL);
tagAllEntry.setTarget(event.getTarget());
tagAllEntry.setParam0(event.getActionParam0());
tagAllEntry.setParam1(event.getActionParam1());
tagAllEntry.setIdentifier(event.getIdentifier());
tagAllEntry.setType(MenuAction.RUNELITE.getId());
}
else
{
menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1);
}
final MenuEntry tagEntry = menuEntries[menuEntries.length - 1] = new MenuEntry();
tagEntry.setOption(npcTags.contains(npc.getIndex()) ? UNTAG : TAG);
tagEntry.setTarget(event.getTarget());
tagEntry.setParam0(event.getActionParam0());
tagEntry.setParam1(event.getActionParam1());
tagEntry.setIdentifier(event.getIdentifier());
tagEntry.setType(MenuAction.RUNELITE.getId());
client.setMenuEntries(menuEntries);
}
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked click)
{
if (click.getMenuAction() != MenuAction.RUNELITE ||
!(click.getMenuOption().equals(TAG) || click.getMenuOption().equals(UNTAG) ||
click.getMenuOption().equals(TAG_ALL) || click.getMenuOption().equals(UNTAG_ALL)))
{
return;
}
final int id = click.getId();
final NPC[] cachedNPCs = client.getCachedNPCs();
final NPC npc = cachedNPCs[id];
if (npc == null || npc.getName() == null)
{
return;
}
if (click.getMenuOption().equals(TAG) || click.getMenuOption().equals(UNTAG))
{
final boolean removed = npcTags.remove(id);
if (removed)
{
if (!highlightMatchesNPCName(npc.getName()))
{
highlightedNpcs.remove(npc);
memorizedNpcs.remove(npc.getIndex());
}
}
else
{
if (!client.isInInstancedRegion())
{
memorizeNpc(npc);
npcTags.add(id);
}
highlightedNpcs.add(npc);
}
}
else
{
final String name = npc.getName();
updateNpcsToHighlight(name);
}
click.consume();
}
@Subscribe
public void onNpcSpawned(NpcSpawned npcSpawned)
{
final NPC npc = npcSpawned.getNpc();
final String npcName = npc.getName();
if (npcName == null)
{
return;
}
if (npcTags.contains(npc.getIndex()))
{
memorizeNpc(npc);
highlightedNpcs.add(npc);
spawnedNpcsThisTick.add(npc);
return;
}
if (highlightMatchesNPCName(npcName))
{
highlightedNpcs.add(npc);
if (!client.isInInstancedRegion())
{
memorizeNpc(npc);
spawnedNpcsThisTick.add(npc);
}
}
}
@Subscribe
public void onNpcDespawned(NpcDespawned npcDespawned)
{
final NPC npc = npcDespawned.getNpc();
if (memorizedNpcs.containsKey(npc.getIndex()))
{
despawnedNpcsThisTick.add(npc);
}
highlightedNpcs.remove(npc);
}
@Subscribe
public void onGraphicsObjectCreated(GraphicsObjectCreated event)
{
final GraphicsObject go = event.getGraphicsObject();
if (go.getId() == GraphicID.GREY_BUBBLE_TELEPORT)
{
teleportGraphicsObjectSpawnedThisTick.add(WorldPoint.fromLocal(client, go.getLocation()));
}
}
@Subscribe
public void onGameTick(GameTick event)
{
removeOldHighlightedRespawns();
validateSpawnedNpcs();
lastTickUpdate = Instant.now();
lastPlayerLocation = client.getLocalPlayer().getWorldLocation();
}
private void updateNpcsToHighlight(String npc)
{
final List<String> highlightedNpcs = new ArrayList<>(highlights);
if (!highlightedNpcs.removeIf(npc::equalsIgnoreCase))
{
highlightedNpcs.add(npc);
}
// this triggers the config change event and rebuilds npcs
config.setNpcToHighlight(Text.toCSV(highlightedNpcs));
}
private static boolean isInViewRange(WorldPoint wp1, WorldPoint wp2)
{
int distance = wp1.distanceTo(wp2);
return distance < MAX_ACTOR_VIEW_RANGE;
}
private static WorldPoint getWorldLocationBehind(NPC npc)
{
final int orientation = npc.getOrientation() / 256;
int dx = 0, dy = 0;
switch (orientation)
{
case 0: // South
dy = -1;
break;
case 1: // Southwest
dx = -1;
dy = -1;
break;
case 2: // West
dx = -1;
break;
case 3: // Northwest
dx = -1;
dy = 1;
break;
case 4: // North
dy = 1;
break;
case 5: // Northeast
dx = 1;
dy = 1;
break;
case 6: // East
dx = 1;
break;
case 7: // Southeast
dx = 1;
dy = -1;
break;
}
final WorldPoint currWP = npc.getWorldLocation();
return new WorldPoint(currWP.getX() - dx, currWP.getY() - dy, currWP.getPlane());
}
private void memorizeNpc(NPC npc)
{
final int npcIndex = npc.getIndex();
memorizedNpcs.putIfAbsent(npcIndex, new MemorizedNpc(npc));
}
private void removeOldHighlightedRespawns()
{
deadNpcsToDisplay.values().removeIf(x -> x.getDiedOnTick() + x.getRespawnTime() <= client.getTickCount() + 1);
}
@VisibleForTesting
List<String> getHighlights()
{
final String configNpcs = config.getNpcToHighlight();
if (configNpcs.isEmpty())
{
return Collections.emptyList();
}
return Text.fromCSV(configNpcs);
}
@VisibleForTesting
void rebuildAllNpcs()
{
highlights = getHighlights();
highlightedNpcs.clear();
if (client.getGameState() != GameState.LOGGED_IN &&
client.getGameState() != GameState.LOADING)
{
// NPCs are still in the client after logging out,
// but we don't want to highlight those.
return;
}
for (NPC npc : client.getNpcs())
{
final String npcName = npc.getName();
if (npcName == null)
{
continue;
}
if (npcTags.contains(npc.getIndex()))
{
highlightedNpcs.add(npc);
continue;
}
if (highlightMatchesNPCName(npcName))
{
if (!client.isInInstancedRegion())
{
memorizeNpc(npc);
}
highlightedNpcs.add(npc);
continue;
}
// NPC is not highlighted
memorizedNpcs.remove(npc.getIndex());
}
}
private boolean highlightMatchesNPCName(String npcName)
{
for (String highlight : highlights)
{
if (WildcardMatcher.matches(highlight, npcName))
{
return true;
}
}
return false;
}
private void validateSpawnedNpcs()
{
if (skipNextSpawnCheck)
{
skipNextSpawnCheck = false;
}
else
{
for (NPC npc : despawnedNpcsThisTick)
{
if (!teleportGraphicsObjectSpawnedThisTick.isEmpty())
{
if (teleportGraphicsObjectSpawnedThisTick.contains(npc.getWorldLocation()))
{
// NPC teleported away, so we don't want to add the respawn timer
continue;
}
}
if (isInViewRange(client.getLocalPlayer().getWorldLocation(), npc.getWorldLocation()))
{
final MemorizedNpc mn = memorizedNpcs.get(npc.getIndex());
if (mn != null)
{
mn.setDiedOnTick(client.getTickCount() + 1); // This runs before tickCounter updates, so we add 1
if (!mn.getPossibleRespawnLocations().isEmpty())
{
log.debug("Starting {} tick countdown for {}", mn.getRespawnTime(), mn.getNpcName());
deadNpcsToDisplay.put(mn.getNpcIndex(), mn);
}
}
}
}
for (NPC npc : spawnedNpcsThisTick)
{
if (!teleportGraphicsObjectSpawnedThisTick.isEmpty())
{
if (teleportGraphicsObjectSpawnedThisTick.contains(npc.getWorldLocation()) ||
teleportGraphicsObjectSpawnedThisTick.contains(getWorldLocationBehind(npc)))
{
// NPC teleported here, so we don't want to update the respawn timer
continue;
}
}
if (lastPlayerLocation != null && isInViewRange(lastPlayerLocation, npc.getWorldLocation()))
{
final MemorizedNpc mn = memorizedNpcs.get(npc.getIndex());
if (mn.getDiedOnTick() != -1)
{
final int respawnTime = client.getTickCount() + 1 - mn.getDiedOnTick();
// By killing a monster and leaving the area before seeing it again, an erroneously lengthy
// respawn time can be recorded. Thus, if the respawn time is already set and is greater than
// the observed time, assume that the lower observed respawn time is correct.
if (mn.getRespawnTime() == -1 || respawnTime < mn.getRespawnTime())
{
mn.setRespawnTime(respawnTime);
}
mn.setDiedOnTick(-1);
}
final WorldPoint npcLocation = npc.getWorldLocation();
// An NPC can move in the same tick as it spawns, so we also have
// to consider whatever tile is behind the npc
final WorldPoint possibleOtherNpcLocation = getWorldLocationBehind(npc);
mn.getPossibleRespawnLocations().removeIf(x ->
x.distanceTo(npcLocation) != 0 && x.distanceTo(possibleOtherNpcLocation) != 0);
if (mn.getPossibleRespawnLocations().isEmpty())
{
mn.getPossibleRespawnLocations().add(npcLocation);
mn.getPossibleRespawnLocations().add(possibleOtherNpcLocation);
}
}
}
}
spawnedNpcsThisTick.clear();
despawnedNpcsThisTick.clear();
teleportGraphicsObjectSpawnedThisTick.clear();
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2018, James Swindle <wilingua@gmail.com>
* Copyright (c) 2018, 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.npchighlight;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.Point;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
public class NpcMinimapOverlay extends Overlay
{
private final NpcIndicatorsConfig config;
private final NpcIndicatorsPlugin plugin;
@Inject
NpcMinimapOverlay(NpcIndicatorsConfig config, NpcIndicatorsPlugin plugin)
{
this.config = config;
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
}
@Override
public Dimension render(Graphics2D graphics)
{
for (NPC npc : plugin.getHighlightedNpcs())
{
renderNpcOverlay(graphics, npc, npc.getName(), config.getHighlightColor());
}
return null;
}
private void renderNpcOverlay(Graphics2D graphics, NPC actor, String name, Color color)
{
NPCComposition npcComposition = actor.getTransformedComposition();
if (npcComposition == null || !npcComposition.isInteractible()
|| (actor.isDead() && config.ignoreDeadNpcs()))
{
return;
}
Point minimapLocation = actor.getMinimapLocation();
if (minimapLocation != null)
{
OverlayUtil.renderMinimapLocation(graphics, minimapLocation, color.darker());
if (config.drawMinimapNames())
{
OverlayUtil.renderTextLocation(graphics, minimapLocation, name, color);
}
}
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2018, James Swindle <wilingua@gmail.com>
* Copyright (c) 2018, 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.npchighlight;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.Instant;
import java.util.Locale;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.util.Text;
public class NpcSceneOverlay extends Overlay
{
// Anything but white text is quite hard to see since it is drawn on
// a dark background
private static final Color TEXT_COLOR = Color.WHITE;
private static final NumberFormat TIME_LEFT_FORMATTER = DecimalFormat.getInstance(Locale.US);
static
{
((DecimalFormat)TIME_LEFT_FORMATTER).applyPattern("#0.0");
}
private final Client client;
private final NpcIndicatorsConfig config;
private final NpcIndicatorsPlugin plugin;
@Inject
NpcSceneOverlay(Client client, NpcIndicatorsConfig config, NpcIndicatorsPlugin plugin)
{
this.client = client;
this.config = config;
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (config.showRespawnTimer())
{
plugin.getDeadNpcsToDisplay().forEach((id, npc) -> renderNpcRespawn(npc, graphics));
}
for (NPC npc : plugin.getHighlightedNpcs())
{
renderNpcOverlay(graphics, npc, config.getHighlightColor());
}
return null;
}
private void renderNpcRespawn(final MemorizedNpc npc, final Graphics2D graphics)
{
if (npc.getPossibleRespawnLocations().isEmpty())
{
return;
}
final WorldPoint respawnLocation = npc.getPossibleRespawnLocations().get(0);
final LocalPoint lp = LocalPoint.fromWorld(client, respawnLocation.getX(), respawnLocation.getY());
if (lp == null)
{
return;
}
final Color color = config.getHighlightColor();
final LocalPoint centerLp = new LocalPoint(
lp.getX() + Perspective.LOCAL_TILE_SIZE * (npc.getNpcSize() - 1) / 2,
lp.getY() + Perspective.LOCAL_TILE_SIZE * (npc.getNpcSize() - 1) / 2);
final Polygon poly = Perspective.getCanvasTileAreaPoly(client, centerLp, npc.getNpcSize());
if (poly != null)
{
OverlayUtil.renderPolygon(graphics, poly, color);
}
final Instant now = Instant.now();
final double baseTick = ((npc.getDiedOnTick() + npc.getRespawnTime()) - client.getTickCount()) * (Constants.GAME_TICK_LENGTH / 1000.0);
final double sinceLast = (now.toEpochMilli() - plugin.getLastTickUpdate().toEpochMilli()) / 1000.0;
final double timeLeft = Math.max(0.0, baseTick - sinceLast);
final String timeLeftStr = TIME_LEFT_FORMATTER.format(timeLeft);
final int textWidth = graphics.getFontMetrics().stringWidth(timeLeftStr);
final int textHeight = graphics.getFontMetrics().getAscent();
final Point canvasPoint = Perspective
.localToCanvas(client, centerLp, respawnLocation.getPlane());
if (canvasPoint != null)
{
final Point canvasCenterPoint = new Point(
canvasPoint.getX() - textWidth / 2,
canvasPoint.getY() + textHeight / 2);
OverlayUtil.renderTextLocation(graphics, canvasCenterPoint, timeLeftStr, TEXT_COLOR);
}
}
private void renderNpcOverlay(Graphics2D graphics, NPC actor, Color color)
{
NPCComposition npcComposition = actor.getTransformedComposition();
if (npcComposition == null || !npcComposition.isInteractible()
|| (actor.isDead() && config.ignoreDeadNpcs()))
{
return;
}
if (config.highlightHull())
{
Shape objectClickbox = actor.getConvexHull();
renderPoly(graphics, color, objectClickbox);
}
if (config.highlightTile())
{
int size = npcComposition.getSize();
LocalPoint lp = actor.getLocalLocation();
Polygon tilePoly = Perspective.getCanvasTileAreaPoly(client, lp, size);
renderPoly(graphics, color, tilePoly);
}
if (config.highlightSouthWestTile())
{
int size = npcComposition.getSize();
LocalPoint lp = actor.getLocalLocation();
int x = lp.getX() - ((size - 1) * Perspective.LOCAL_TILE_SIZE / 2);
int y = lp.getY() - ((size - 1) * Perspective.LOCAL_TILE_SIZE / 2);
Polygon southWestTilePoly = Perspective.getCanvasTilePoly(client, new LocalPoint(x, y));
renderPoly(graphics, color, southWestTilePoly);
}
if (config.drawNames() && actor.getName() != null)
{
String npcName = Text.removeTags(actor.getName());
Point textLocation = actor.getCanvasTextLocation(graphics, npcName, actor.getLogicalHeight() + 40);
if (textLocation != null)
{
OverlayUtil.renderTextLocation(graphics, textLocation, npcName, color);
}
}
}
private void renderPoly(Graphics2D graphics, Color color, Shape polygon)
{
if (polygon != null)
{
graphics.setColor(color);
graphics.setStroke(new BasicStroke(2));
graphics.draw(polygon);
graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
graphics.fill(polygon);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npcunaggroarea;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import lombok.Getter;
import lombok.Setter;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Timer;
class AggressionTimer extends Timer
{
@Getter
@Setter
private boolean visible;
AggressionTimer(Duration duration, BufferedImage image, Plugin plugin, boolean visible)
{
super(duration.toMillis(), ChronoUnit.MILLIS, image, plugin);
setTooltip("Time until NPCs become unaggressive");
this.visible = visible;
}
@Override
public Color getTextColor()
{
Duration timeLeft = Duration.between(Instant.now(), getEndTime());
if (timeLeft.getSeconds() < 60)
{
return Color.RED.brighter();
}
return Color.WHITE;
}
@Override
public boolean render()
{
return visible && super.render();
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npcunaggroarea;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("npcUnaggroArea")
public interface NpcAggroAreaConfig extends Config
{
String CONFIG_GROUP = "npcUnaggroArea";
String CONFIG_CENTER1 = "center1";
String CONFIG_CENTER2 = "center2";
String CONFIG_LOCATION = "location";
String CONFIG_DURATION = "duration";
@ConfigItem(
keyName = "npcUnaggroAlwaysActive",
name = "Always active",
description = "Always show this plugins overlays<br>Otherwise, they will only be shown when any NPC name matches the list",
position = 1
)
default boolean alwaysActive()
{
return false;
}
@ConfigItem(
keyName = "npcUnaggroNames",
name = "NPC names",
description = "Enter names of NPCs where you wish to use this plugin",
position = 2
)
default String npcNamePatterns()
{
return "";
}
@ConfigItem(
keyName = "npcUnaggroShowTimer",
name = "Show timer",
description = "Display a timer until NPCs become unaggressive",
position = 3
)
default boolean showTimer()
{
return true;
}
@ConfigItem(
keyName = "npcUnaggroShowAreaLines",
name = "Show area lines",
description = "Display lines, when walked past, the unaggressive timer resets",
position = 4
)
default boolean showAreaLines()
{
return false;
}
@ConfigItem(
keyName = "npcAggroAreaColor",
name = "Aggressive colour",
description = "Choose colour to use for marking NPC unaggressive area when NPCs are aggressive",
position = 5
)
@Alpha
default Color aggroAreaColor()
{
return new Color(0x64FFFF00, true);
}
@ConfigItem(
keyName = "npcUnaggroAreaColor",
name = "Unaggressive colour",
description = "Choose colour to use for marking NPC unaggressive area after NPCs have lost aggression",
position = 6
)
@Alpha
default Color unaggroAreaColor()
{
return new Color(0xFFFF00);
}
@ConfigItem(
keyName = "notifyExpire",
name = "Notify Expiration",
description = "Send a notifcation when the unaggressive timer expires",
position = 7
)
default boolean notifyExpire()
{
return false;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npcunaggroarea;
import com.google.inject.Inject;
import java.awt.Dimension;
import java.awt.Graphics2D;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.components.LineComponent;
class NpcAggroAreaNotWorkingOverlay extends OverlayPanel
{
private final NpcAggroAreaPlugin plugin;
@Inject
private NpcAggroAreaNotWorkingOverlay(NpcAggroAreaPlugin plugin)
{
this.plugin = plugin;
panelComponent.getChildren().add(LineComponent.builder()
.left("Unaggressive NPC timers will start working when you teleport far away or enter a dungeon.")
.build());
setPriority(OverlayPriority.LOW);
setPosition(OverlayPosition.TOP_LEFT);
setClearChildren(false);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.isActive() || plugin.getSafeCenters()[1] != null)
{
return null;
}
return super.render(graphics);
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npcunaggroarea;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
import java.time.Instant;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.geometry.Geometry;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
class NpcAggroAreaOverlay extends Overlay
{
private static final int MAX_LOCAL_DRAW_LENGTH = 20 * Perspective.LOCAL_TILE_SIZE;
private final Client client;
private final NpcAggroAreaConfig config;
private final NpcAggroAreaPlugin plugin;
@Inject
private NpcAggroAreaOverlay(Client client, NpcAggroAreaConfig config, NpcAggroAreaPlugin plugin)
{
this.client = client;
this.config = config;
this.plugin = plugin;
setLayer(OverlayLayer.ABOVE_SCENE);
setPriority(OverlayPriority.LOW);
setPosition(OverlayPosition.DYNAMIC);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.isActive() || plugin.getSafeCenters()[1] == null)
{
return null;
}
GeneralPath lines = plugin.getLinesToDisplay()[client.getPlane()];
if (lines == null)
{
return null;
}
Color outlineColor = config.unaggroAreaColor();
AggressionTimer timer = plugin.getCurrentTimer();
if (outlineColor == null || timer == null || Instant.now().compareTo(timer.getEndTime()) < 0)
{
outlineColor = config.aggroAreaColor();
}
renderPath(graphics, lines, outlineColor);
return null;
}
private void renderPath(Graphics2D graphics, GeneralPath path, Color color)
{
LocalPoint playerLp = client.getLocalPlayer().getLocalLocation();
Rectangle viewArea = new Rectangle(
playerLp.getX() - MAX_LOCAL_DRAW_LENGTH,
playerLp.getY() - MAX_LOCAL_DRAW_LENGTH,
MAX_LOCAL_DRAW_LENGTH * 2,
MAX_LOCAL_DRAW_LENGTH * 2);
graphics.setColor(color);
graphics.setStroke(new BasicStroke(1));
path = Geometry.clipPath(path, viewArea);
path = Geometry.filterPath(path, (p1, p2) ->
Perspective.localToCanvas(client, new LocalPoint((int)p1[0], (int)p1[1]), client.getPlane()) != null &&
Perspective.localToCanvas(client, new LocalPoint((int)p2[0], (int)p2[1]), client.getPlane()) != null);
path = Geometry.transformPath(path, coords ->
{
Point point = Perspective.localToCanvas(client, new LocalPoint((int)coords[0], (int)coords[1]), client.getPlane());
coords[0] = point.getX();
coords[1] = point.getY();
});
graphics.draw(path);
}
}

View File

@@ -0,0 +1,492 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.npcunaggroarea;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.inject.Provides;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.ItemID;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.Perspective;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldArea;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.geometry.Geometry;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
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.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.WildcardMatcher;
@PluginDescriptor(
name = "NPC Aggression Timer",
description = "Highlights the unaggressive area of NPCs nearby and timer until it becomes active",
tags = {"highlight", "lines", "unaggro", "aggro", "aggressive", "npcs", "area", "slayer"},
enabledByDefault = false
)
public class NpcAggroAreaPlugin extends Plugin
{
/*
How it works: The game remembers 2 tiles. When the player goes >10 steps
away from both tiles, the oldest one is moved to under the player and the
NPC aggression timer resets.
So to first figure out where the 2 tiles are, we wait until the player teleports
a long enough distance. At that point it's very likely that the player
moved out of the radius of both tiles, which resets one of them. The other
should reset shortly after as the player starts moving around.
*/
private static final int SAFE_AREA_RADIUS = 10;
private static final int UNKNOWN_AREA_RADIUS = SAFE_AREA_RADIUS * 2;
private static final int AGGRESSIVE_TIME_SECONDS = 600;
private static final Splitter NAME_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
private static final WorldArea WILDERNESS_ABOVE_GROUND = new WorldArea(2944, 3523, 448, 448, 0);
private static final WorldArea WILDERNESS_UNDERGROUND = new WorldArea(2944, 9918, 320, 442, 0);
@Inject
private Client client;
@Inject
private NpcAggroAreaConfig config;
@Inject
private NpcAggroAreaOverlay overlay;
@Inject
private NpcAggroAreaNotWorkingOverlay notWorkingOverlay;
@Inject
private OverlayManager overlayManager;
@Inject
private ItemManager itemManager;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ConfigManager configManager;
@Inject
private Notifier notifier;
@Getter
private final WorldPoint[] safeCenters = new WorldPoint[2];
@Getter
private final GeneralPath[] linesToDisplay = new GeneralPath[Constants.MAX_Z];
@Getter
private boolean active;
@Getter
private AggressionTimer currentTimer;
private WorldPoint lastPlayerLocation;
private WorldPoint previousUnknownCenter;
private boolean loggingIn;
private boolean notifyOnce;
private List<String> npcNamePatterns;
@Provides
NpcAggroAreaConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(NpcAggroAreaConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(overlay);
overlayManager.add(notWorkingOverlay);
npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns());
recheckActive();
}
@Override
protected void shutDown() throws Exception
{
removeTimer();
overlayManager.remove(overlay);
overlayManager.remove(notWorkingOverlay);
Arrays.fill(safeCenters, null);
lastPlayerLocation = null;
currentTimer = null;
loggingIn = false;
npcNamePatterns = null;
active = false;
Arrays.fill(linesToDisplay, null);
}
private Area generateSafeArea()
{
final Area area = new Area();
for (WorldPoint wp : safeCenters)
{
if (wp == null)
{
continue;
}
Polygon poly = new Polygon();
poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() - SAFE_AREA_RADIUS);
poly.addPoint(wp.getX() - SAFE_AREA_RADIUS, wp.getY() + SAFE_AREA_RADIUS + 1);
poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() + SAFE_AREA_RADIUS + 1);
poly.addPoint(wp.getX() + SAFE_AREA_RADIUS + 1, wp.getY() - SAFE_AREA_RADIUS);
area.add(new Area(poly));
}
return area;
}
private void transformWorldToLocal(float[] coords)
{
final LocalPoint lp = LocalPoint.fromWorld(client, (int)coords[0], (int)coords[1]);
coords[0] = lp.getX() - Perspective.LOCAL_TILE_SIZE / 2f;
coords[1] = lp.getY() - Perspective.LOCAL_TILE_SIZE / 2f;
}
private void reevaluateActive()
{
if (currentTimer != null)
{
currentTimer.setVisible(active && config.showTimer());
}
calculateLinesToDisplay();
}
private void calculateLinesToDisplay()
{
if (!active || !config.showAreaLines())
{
Arrays.fill(linesToDisplay, null);
return;
}
Rectangle sceneRect = new Rectangle(
client.getBaseX() + 1, client.getBaseY() + 1,
Constants.SCENE_SIZE - 2, Constants.SCENE_SIZE - 2);
for (int i = 0; i < linesToDisplay.length; i++)
{
GeneralPath lines = new GeneralPath(generateSafeArea());
lines = Geometry.clipPath(lines, sceneRect);
lines = Geometry.splitIntoSegments(lines, 1);
lines = Geometry.transformPath(lines, this::transformWorldToLocal);
linesToDisplay[i] = lines;
}
}
private void removeTimer()
{
infoBoxManager.removeInfoBox(currentTimer);
currentTimer = null;
notifyOnce = false;
}
private void createTimer(Duration duration)
{
removeTimer();
BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD);
currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer());
infoBoxManager.addInfoBox(currentTimer);
notifyOnce = true;
}
private void resetTimer()
{
createTimer(Duration.ofSeconds(AGGRESSIVE_TIME_SECONDS));
}
private static boolean isInWilderness(WorldPoint location)
{
return WILDERNESS_ABOVE_GROUND.distanceTo2D(location) == 0 || WILDERNESS_UNDERGROUND.distanceTo2D(location) == 0;
}
private boolean isNpcMatch(NPC npc)
{
NPCComposition composition = npc.getTransformedComposition();
if (composition == null)
{
return false;
}
if (Strings.isNullOrEmpty(composition.getName()))
{
return false;
}
// Most NPCs stop aggroing when the player has more than double
// its combat level.
int playerLvl = client.getLocalPlayer().getCombatLevel();
int npcLvl = composition.getCombatLevel();
String npcName = composition.getName().toLowerCase();
if (npcLvl > 0 && playerLvl > npcLvl * 2 && !isInWilderness(npc.getWorldLocation()))
{
return false;
}
for (String pattern : npcNamePatterns)
{
if (WildcardMatcher.matches(pattern, npcName))
{
return true;
}
}
return false;
}
private void checkAreaNpcs(final NPC... npcs)
{
for (NPC npc : npcs)
{
if (npc == null)
{
continue;
}
if (isNpcMatch(npc))
{
active = true;
break;
}
}
reevaluateActive();
}
private void recheckActive()
{
active = config.alwaysActive();
checkAreaNpcs(client.getCachedNPCs());
}
@Subscribe
public void onNpcSpawned(NpcSpawned event)
{
if (config.alwaysActive())
{
return;
}
checkAreaNpcs(event.getNpc());
}
@Subscribe
public void onGameTick(GameTick event)
{
WorldPoint newLocation = client.getLocalPlayer().getWorldLocation();
if (active && currentTimer != null && currentTimer.cull() && notifyOnce)
{
if (config.notifyExpire())
{
notifier.notify("NPC aggression has expired!");
}
notifyOnce = false;
}
if (lastPlayerLocation != null)
{
if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4)
{
safeCenters[0] = null;
safeCenters[1] = newLocation;
resetTimer();
calculateLinesToDisplay();
// We don't know where the previous area was, so if the player e.g.
// entered a dungeon and then goes back out, he/she may enter the previous
// area which is unknown and would make the plugin inaccurate
previousUnknownCenter = lastPlayerLocation;
}
}
if (safeCenters[0] == null && previousUnknownCenter != null &&
previousUnknownCenter.distanceTo2D(newLocation) <= UNKNOWN_AREA_RADIUS)
{
// Player went back to their previous unknown area before the 2nd
// center point was found, which means we don't know where it is again.
safeCenters[1] = null;
removeTimer();
calculateLinesToDisplay();
}
if (safeCenters[1] != null)
{
if (Arrays.stream(safeCenters).noneMatch(
x -> x != null && x.distanceTo2D(newLocation) <= SAFE_AREA_RADIUS))
{
safeCenters[0] = safeCenters[1];
safeCenters[1] = newLocation;
resetTimer();
calculateLinesToDisplay();
previousUnknownCenter = null;
}
}
lastPlayerLocation = newLocation;
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
String key = event.getKey();
switch (key)
{
case "npcUnaggroAlwaysActive":
recheckActive();
break;
case "npcUnaggroShowTimer":
if (currentTimer != null)
{
currentTimer.setVisible(active && config.showTimer());
}
break;
case "npcUnaggroCollisionDetection":
case "npcUnaggroShowAreaLines":
calculateLinesToDisplay();
break;
case "npcUnaggroNames":
npcNamePatterns = NAME_SPLITTER.splitToList(config.npcNamePatterns());
recheckActive();
break;
}
}
private void loadConfig()
{
safeCenters[0] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, WorldPoint.class);
safeCenters[1] = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, WorldPoint.class);
lastPlayerLocation = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, WorldPoint.class);
Duration timeLeft = configManager.getConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.class);
if (timeLeft != null && !timeLeft.isNegative())
{
createTimer(timeLeft);
}
}
private void resetConfig()
{
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1);
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2);
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION);
configManager.unsetConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION);
}
private void saveConfig()
{
if (safeCenters[0] == null || safeCenters[1] == null || lastPlayerLocation == null || currentTimer == null)
{
resetConfig();
}
else
{
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER1, safeCenters[0]);
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_CENTER2, safeCenters[1]);
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_LOCATION, lastPlayerLocation);
configManager.setConfiguration(NpcAggroAreaConfig.CONFIG_GROUP, NpcAggroAreaConfig.CONFIG_DURATION, Duration.between(Instant.now(), currentTimer.getEndTime()));
}
}
private void onLogin()
{
loadConfig();
resetConfig();
WorldPoint newLocation = client.getLocalPlayer().getWorldLocation();
assert newLocation != null;
// If the player isn't at the location he/she logged out at,
// the safe unaggro area probably changed, and should be disposed.
if (lastPlayerLocation == null || newLocation.distanceTo(lastPlayerLocation) != 0)
{
safeCenters[0] = null;
safeCenters[1] = null;
lastPlayerLocation = newLocation;
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case LOGGED_IN:
if (loggingIn)
{
loggingIn = false;
onLogin();
}
recheckActive();
break;
case LOGGING_IN:
loggingIn = true;
break;
case LOGIN_SCREEN:
if (lastPlayerLocation != null)
{
saveConfig();
}
safeCenters[0] = null;
safeCenters[1] = null;
lastPlayerLocation = null;
break;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020, dekvall <https://github.com/dekvall>
* 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.objectindicators;
import java.awt.Color;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import net.runelite.api.TileObject;
/**
* Used to denote marked objects and their colors.
* Note: This is not used for serialization of object indicators; see {@link ObjectPoint}
*/
@Value
@RequiredArgsConstructor
class ColorTileObject
{
private final TileObject tileObject;
private final Color color;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.objectindicators;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("objectindicators")
public interface ObjectIndicatorsConfig extends Config
{
@Alpha
@ConfigItem(
keyName = "markerColor",
name = "Marker color",
description = "Configures the color of object marker"
)
default Color markerColor()
{
return Color.YELLOW;
}
@ConfigItem(
keyName = "rememberObjectColors",
name = "Remember color per object",
description = "Color objects using the color from time of marking"
)
default boolean rememberObjectColors()
{
return false;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.objectindicators;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Shape;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.DecorativeObject;
import net.runelite.api.GameObject;
import net.runelite.api.GroundObject;
import net.runelite.api.TileObject;
import net.runelite.api.WallObject;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.OverlayUtil;
class ObjectIndicatorsOverlay extends Overlay
{
private final Client client;
private final ObjectIndicatorsConfig config;
private final ObjectIndicatorsPlugin plugin;
@Inject
private ObjectIndicatorsOverlay(Client client, ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin)
{
this.client = client;
this.config = config;
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setPriority(OverlayPriority.LOW);
setLayer(OverlayLayer.ABOVE_SCENE);
}
@Override
public Dimension render(Graphics2D graphics)
{
for (ColorTileObject colorTileObject : plugin.getObjects())
{
TileObject object = colorTileObject.getTileObject();
Color color = colorTileObject.getColor();
if (object.getPlane() != client.getPlane())
{
continue;
}
if (color == null || !config.rememberObjectColors())
{
// Fallback to the current config if the object is marked before the addition of multiple colors
color = config.markerColor();
}
final Shape polygon;
Shape polygon2 = null;
if (object instanceof GameObject)
{
polygon = ((GameObject) object).getConvexHull();
}
else if (object instanceof WallObject)
{
polygon = ((WallObject) object).getConvexHull();
polygon2 = ((WallObject) object).getConvexHull2();
}
else if (object instanceof DecorativeObject)
{
polygon = ((DecorativeObject) object).getConvexHull();
polygon2 = ((DecorativeObject) object).getConvexHull2();
}
else if (object instanceof GroundObject)
{
polygon = ((GroundObject) object).getConvexHull();
}
else
{
polygon = object.getCanvasTilePoly();
}
if (polygon != null)
{
OverlayUtil.renderPolygon(graphics, polygon, color);
}
if (polygon2 != null)
{
OverlayUtil.renderPolygon(graphics, polygon2, color);
}
}
return null;
}
}

View File

@@ -0,0 +1,462 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2018, 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.objectindicators;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Provides;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.DecorativeObject;
import net.runelite.api.GameObject;
import net.runelite.api.GameState;
import net.runelite.api.GroundObject;
import net.runelite.api.KeyCode;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.ObjectComposition;
import net.runelite.api.Scene;
import net.runelite.api.Tile;
import net.runelite.api.TileObject;
import net.runelite.api.WallObject;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.DecorativeObjectDespawned;
import net.runelite.api.events.DecorativeObjectSpawned;
import net.runelite.api.events.GameObjectDespawned;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GroundObjectDespawned;
import net.runelite.api.events.GroundObjectSpawned;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.WallObjectChanged;
import net.runelite.api.events.WallObjectDespawned;
import net.runelite.api.events.WallObjectSpawned;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "Object Markers",
description = "Enable marking of objects using the Shift key",
tags = {"overlay", "objects", "mark", "marker"},
enabledByDefault = false
)
@Slf4j
public class ObjectIndicatorsPlugin extends Plugin
{
private static final String CONFIG_GROUP = "objectindicators";
private static final String MARK = "Mark object";
private static final String UNMARK = "Unmark object";
private final Gson GSON = new Gson();
@Getter(AccessLevel.PACKAGE)
private final List<ColorTileObject> objects = new ArrayList<>();
private final Map<Integer, Set<ObjectPoint>> points = new HashMap<>();
@Inject
private Client client;
@Inject
private ConfigManager configManager;
@Inject
private OverlayManager overlayManager;
@Inject
private ObjectIndicatorsOverlay overlay;
@Inject
private ObjectIndicatorsConfig config;
@Provides
ObjectIndicatorsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(ObjectIndicatorsConfig.class);
}
@Override
protected void startUp()
{
overlayManager.add(overlay);
}
@Override
protected void shutDown()
{
overlayManager.remove(overlay);
points.clear();
objects.clear();
}
@Subscribe
public void onWallObjectSpawned(WallObjectSpawned event)
{
checkObjectPoints(event.getWallObject());
}
@Subscribe
public void onWallObjectChanged(WallObjectChanged event)
{
WallObject previous = event.getPrevious();
WallObject wallObject = event.getWallObject();
objects.removeIf(o -> o.getTileObject() == previous);
checkObjectPoints(wallObject);
}
@Subscribe
public void onWallObjectDespawned(WallObjectDespawned event)
{
objects.removeIf(o -> o.getTileObject() == event.getWallObject());
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
checkObjectPoints(event.getGameObject());
}
@Subscribe
public void onDecorativeObjectSpawned(DecorativeObjectSpawned event)
{
checkObjectPoints(event.getDecorativeObject());
}
@Subscribe
public void onGameObjectDespawned(GameObjectDespawned event)
{
objects.removeIf(o -> o.getTileObject() == event.getGameObject());
}
@Subscribe
public void onDecorativeObjectDespawned(DecorativeObjectDespawned event)
{
objects.removeIf(o -> o.getTileObject() == event.getDecorativeObject());
}
@Subscribe
public void onGroundObjectSpawned(GroundObjectSpawned event)
{
checkObjectPoints(event.getGroundObject());
}
@Subscribe
public void onGroundObjectDespawned(GroundObjectDespawned event)
{
objects.removeIf(o -> o.getTileObject() == event.getGroundObject());
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
GameState gameState = gameStateChanged.getGameState();
if (gameState == GameState.LOADING)
{
// Reload points with new map regions
points.clear();
for (int regionId : client.getMapRegions())
{
// load points for region
final Set<ObjectPoint> regionPoints = loadPoints(regionId);
if (regionPoints != null)
{
points.put(regionId, regionPoints);
}
}
}
if (gameStateChanged.getGameState() != GameState.LOGGED_IN)
{
objects.clear();
}
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded event)
{
if (event.getType() != MenuAction.EXAMINE_OBJECT.getId() || !client.isKeyPressed(KeyCode.KC_SHIFT))
{
return;
}
final Tile tile = client.getScene().getTiles()[client.getPlane()][event.getActionParam0()][event.getActionParam1()];
final TileObject tileObject = findTileObject(tile, event.getIdentifier());
if (tileObject == null)
{
return;
}
MenuEntry[] menuEntries = client.getMenuEntries();
menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1);
MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry();
menuEntry.setOption(objects.stream().anyMatch(o -> o.getTileObject() == tileObject) ? UNMARK : MARK);
menuEntry.setTarget(event.getTarget());
menuEntry.setParam0(event.getActionParam0());
menuEntry.setParam1(event.getActionParam1());
menuEntry.setIdentifier(event.getIdentifier());
menuEntry.setType(MenuAction.RUNELITE.getId());
client.setMenuEntries(menuEntries);
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
if (event.getMenuAction() != MenuAction.RUNELITE
|| !(event.getMenuOption().equals(MARK) || event.getMenuOption().equals(UNMARK)))
{
return;
}
Scene scene = client.getScene();
Tile[][][] tiles = scene.getTiles();
final int x = event.getActionParam();
final int y = event.getWidgetId();
final int z = client.getPlane();
final Tile tile = tiles[z][x][y];
TileObject object = findTileObject(tile, event.getId());
if (object == null)
{
return;
}
// object.getId() is always the base object id, getObjectComposition transforms it to
// the correct object we see
ObjectComposition objectDefinition = getObjectComposition(object.getId());
String name = objectDefinition.getName();
// Name is probably never "null" - however prevent adding it if it is, as it will
// become ambiguous as objects with no name are assigned name "null"
if (Strings.isNullOrEmpty(name) || name.equals("null"))
{
return;
}
markObject(objectDefinition, name, object);
}
private void checkObjectPoints(TileObject object)
{
final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation(), object.getPlane());
final Set<ObjectPoint> objectPoints = points.get(worldPoint.getRegionID());
if (objectPoints == null)
{
return;
}
for (ObjectPoint objectPoint : objectPoints)
{
if (worldPoint.getRegionX() == objectPoint.getRegionX()
&& worldPoint.getRegionY() == objectPoint.getRegionY()
&& worldPoint.getPlane() == objectPoint.getZ())
{
// Transform object to get the name which matches against what we've stored
ObjectComposition composition = getObjectComposition(object.getId());
if (composition != null && objectPoint.getName().equals(composition.getName()))
{
log.debug("Marking object {} due to matching {}", object, objectPoint);
objects.add(new ColorTileObject(object, objectPoint.getColor()));
break;
}
}
}
}
private TileObject findTileObject(Tile tile, int id)
{
if (tile == null)
{
return null;
}
final GameObject[] tileGameObjects = tile.getGameObjects();
final DecorativeObject tileDecorativeObject = tile.getDecorativeObject();
final WallObject tileWallObject = tile.getWallObject();
final GroundObject groundObject = tile.getGroundObject();
if (objectIdEquals(tileWallObject, id))
{
return tileWallObject;
}
if (objectIdEquals(tileDecorativeObject, id))
{
return tileDecorativeObject;
}
if (objectIdEquals(groundObject, id))
{
return groundObject;
}
for (GameObject object : tileGameObjects)
{
if (objectIdEquals(object, id))
{
return object;
}
}
return null;
}
private boolean objectIdEquals(TileObject tileObject, int id)
{
if (tileObject == null)
{
return false;
}
if (tileObject.getId() == id)
{
return true;
}
// Menu action EXAMINE_OBJECT sends the transformed object id, not the base id, unlike
// all of the GAME_OBJECT_OPTION actions, so check the id against the impostor ids
final ObjectComposition comp = client.getObjectDefinition(tileObject.getId());
if (comp.getImpostorIds() != null)
{
for (int impostorId : comp.getImpostorIds())
{
if (impostorId == id)
{
return true;
}
}
}
return false;
}
/** mark or unmark an object
*
* @param objectComposition transformed composition of object based on vars
* @param name name of objectComposition
* @param object tile object, for multilocs object.getId() is the base id
*/
private void markObject(ObjectComposition objectComposition, String name, final TileObject object)
{
final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation());
final int regionId = worldPoint.getRegionID();
final Color color = config.markerColor();
final ObjectPoint point = new ObjectPoint(
object.getId(),
name,
regionId,
worldPoint.getRegionX(),
worldPoint.getRegionY(),
worldPoint.getPlane(),
color);
Set<ObjectPoint> objectPoints = points.computeIfAbsent(regionId, k -> new HashSet<>());
if (objects.removeIf(o -> o.getTileObject() == object))
{
// Find the object point that caused this object to be marked, there are two cases:
// 1) object is a multiloc, the name may have changed since marking - match from base id
// 2) not a multiloc, but an object has spawned with an identical name and a different
// id as what was originally marked
if (!objectPoints.removeIf(op -> ((op.getId() == -1 || op.getId() == object.getId()) || op.getName().equals(objectComposition.getName()))
&& op.getRegionX() == worldPoint.getRegionX()
&& op.getRegionY() == worldPoint.getRegionY()
&& op.getZ() == worldPoint.getPlane()))
{
log.warn("unable to find object point for unmarked object {}", object.getId());
}
log.debug("Unmarking object: {}", point);
}
else
{
objectPoints.add(point);
objects.add(new ColorTileObject(object, color));
log.debug("Marking object: {}", point);
}
savePoints(regionId, objectPoints);
}
private void savePoints(final int id, final Set<ObjectPoint> points)
{
if (points.isEmpty())
{
configManager.unsetConfiguration(CONFIG_GROUP, "region_" + id);
}
else
{
final String json = GSON.toJson(points);
configManager.setConfiguration(CONFIG_GROUP, "region_" + id, json);
}
}
private Set<ObjectPoint> loadPoints(final int id)
{
final String json = configManager.getConfiguration(CONFIG_GROUP, "region_" + id);
if (Strings.isNullOrEmpty(json))
{
return null;
}
Set<ObjectPoint> points = GSON.fromJson(json, new TypeToken<Set<ObjectPoint>>()
{
}.getType());
// Prior to multiloc support the plugin would mark objects named "null", which breaks
// in most cases due to the specific object being identified being ambiguous, so remove
// them
return points.stream()
.filter(point -> !point.getName().equals("null"))
.collect(Collectors.toSet());
}
@Nullable
private ObjectComposition getObjectComposition(int id)
{
ObjectComposition objectComposition = client.getObjectDefinition(id);
return objectComposition.getImpostorIds() == null ? objectComposition : objectComposition.getImpostor();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.objectindicators;
import java.awt.Color;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
class ObjectPoint
{
private int id = -1;
private String name;
private int regionId;
private int regionX;
private int regionY;
private int z;
private Color color;
}

View File

@@ -45,7 +45,7 @@ import static net.runelite.api.widgets.WidgetInfo.LIGHT_BOX_BUTTON_E;
import static net.runelite.api.widgets.WidgetInfo.LIGHT_BOX_BUTTON_F;
import static net.runelite.api.widgets.WidgetInfo.LIGHT_BOX_BUTTON_G;
import static net.runelite.api.widgets.WidgetInfo.LIGHT_BOX_BUTTON_H;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
@@ -139,7 +139,7 @@ public class PuzzleSolverPlugin extends Plugin
public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked)
{
int widgetId = menuOptionClicked.getWidgetId();
if (getGroupFromID(widgetId) != WidgetID.LIGHT_BOX_GROUP_ID)
if (TO_GROUP(widgetId) != WidgetID.LIGHT_BOX_GROUP_ID)
{
return;
}

View File

@@ -74,7 +74,7 @@ class ScreenMarkerWidgetHighlightOverlay extends Overlay
final MenuEntry menuEntry = menuEntries[menuEntries.length - 1];
final int childIdx = menuEntry.getParam0();
final int widgetId = menuEntry.getParam1();
final int groupId = WidgetInfo.getGroupFromID(widgetId);
final int groupId = WidgetInfo.TO_GROUP(widgetId);
final int componentId = WidgetInfo.getChildFromID(widgetId);
final Widget widget = client.getWidget(groupId, componentId);

View File

@@ -279,7 +279,7 @@ public class TimersPlugin extends Plugin
{
Widget widget = event.getWidget();
if (WorldType.isPvpWorld(client.getWorldType())
&& WidgetInfo.getGroupFromID(widget.getId()) == WidgetID.PVP_GROUP_ID)
&& WidgetInfo.TO_GROUP(widget.getId()) == WidgetID.PVP_GROUP_ID)
{
widgetHiddenChangedOnPvpWorld = true;
}

View File

@@ -58,7 +58,7 @@ import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.StatChanged;
import net.runelite.api.widgets.WidgetID;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.NPCManager;
@@ -483,7 +483,7 @@ public class XpTrackerPlugin extends Plugin
{
int widgetID = event.getActionParam1();
if (getGroupFromID(widgetID) != WidgetID.SKILLS_GROUP_ID
if (TO_GROUP(widgetID) != WidgetID.SKILLS_GROUP_ID
|| !event.getOption().startsWith("View")
|| !xpTrackerConfig.skillTabOverlayMenuOptions())
{
@@ -511,7 +511,7 @@ public class XpTrackerPlugin extends Plugin
public void onMenuOptionClicked(MenuOptionClicked event)
{
if (event.getMenuAction().getId() != MenuAction.RUNELITE.getId()
|| getGroupFromID(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID)
|| TO_GROUP(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID)
{
return;
}

View File

@@ -50,7 +50,7 @@ import static net.runelite.api.widgets.WidgetID.PLAYER_TRADE_SCREEN_GROUP_ID;
import static net.runelite.api.widgets.WidgetID.PLAYER_TRADE_INVENTORY_GROUP_ID;
import static net.runelite.api.widgets.WidgetInfo.BANK_CONTENT_CONTAINER;
import static net.runelite.api.widgets.WidgetInfo.BANK_TAB_CONTAINER;
import static net.runelite.api.widgets.WidgetInfo.getGroupFromID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.api.widgets.WidgetItem;
public abstract class WidgetItemOverlay extends Overlay
@@ -80,7 +80,7 @@ public abstract class WidgetItemOverlay extends Overlay
for (WidgetItem widgetItem : itemWidgets)
{
Widget widget = widgetItem.getWidget();
int interfaceGroup = getGroupFromID(widget.getId());
int interfaceGroup = TO_GROUP(widget.getId());
// Don't draw if this widget isn't one of the allowed nor in tag tab/item tab
if (!interfaceGroups.contains(interfaceGroup) ||