mes, metronome, npchighlight, npcunaggroarea, objectindicators
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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() ||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
Reference in New Issue
Block a user