From fc1c0799bdc5a78db2f4b02f2100c313222b9c1e Mon Sep 17 00:00:00 2001 From: therealunull Date: Wed, 16 Dec 2020 14:49:04 -0500 Subject: [PATCH] banktags, cooking, xpdrop, fishing, friendnotes --- .../main/java/net/runelite/api/Client.java | 2 +- ...umDefinition.java => EnumComposition.java} | 2 +- ...{FriendRemoved.java => RemovedFriend.java} | 2 +- .../net/runelite/api/widgets/WidgetInfo.java | 17 +- .../plugins/banktags/BankTagsConfig.java | 113 ++ .../plugins/banktags/BankTagsPlugin.java | 598 ++++++++ .../client/plugins/banktags/TagManager.java | 231 ++++ .../plugins/banktags/tabs/MenuIndexes.java | 45 + .../plugins/banktags/tabs/TabInterface.java | 1198 +++++++++++++++++ .../plugins/banktags/tabs/TabManager.java | 167 +++ .../plugins/banktags/tabs/TabSprites.java | 47 + .../client/plugins/banktags/tabs/TagTab.java | 65 + .../chathistory/ChatHistoryPlugin.java | 8 +- .../client/plugins/cooking/CookingConfig.java | 58 + .../plugins/cooking/CookingOverlay.java | 119 ++ .../client/plugins/cooking/CookingPlugin.java | 203 +++ .../plugins/cooking/CookingSession.java | 60 + .../client/plugins/cooking/FermentTimer.java | 76 ++ .../plugins/devtools/ScriptInspector.java | 6 +- .../plugins/devtools/WidgetInspector.java | 10 +- .../client/plugins/examine/ExaminePlugin.java | 12 +- .../plugins/experiencedrop/PrayerType.java | 32 + .../plugins/experiencedrop/XpDropConfig.java | 92 ++ .../plugins/experiencedrop/XpDropPlugin.java | 226 ++++ .../plugins/experiencedrop/XpPrayer.java | 63 + .../client/plugins/fishing/FishingConfig.java | 168 +++ .../plugins/fishing/FishingOverlay.java | 146 ++ .../client/plugins/fishing/FishingPlugin.java | 468 +++++++ .../plugins/fishing/FishingSession.java | 36 + .../fishing/FishingSpotMinimapOverlay.java | 93 ++ .../plugins/fishing/FishingSpotOverlay.java | 195 +++ .../client/plugins/fishing/MinnowSpot.java | 38 + .../friendnotes/FriendNoteOverlay.java | 71 + .../friendnotes/FriendNotesConfig.java | 46 + .../friendnotes/FriendNotesPlugin.java | 434 ++++++ .../plugins/friendnotes/HoveredFriend.java | 36 + .../grandexchange/GrandExchangePlugin.java | 4 +- .../client/plugins/hiscore/HiscorePlugin.java | 2 +- .../plugins/itemprices/ItemPricesOverlay.java | 2 +- .../plugins/itemstats/ItemStatOverlay.java | 4 +- .../mousehighlight/MouseHighlightOverlay.java | 2 +- .../puzzlesolver/PuzzleSolverPlugin.java | 4 +- .../ScreenMarkerWidgetHighlightOverlay.java | 4 +- .../client/plugins/timers/TimersPlugin.java | 2 +- .../plugins/xptracker/XpTrackerPlugin.java | 6 +- .../client/ui/overlay/WidgetItemOverlay.java | 4 +- .../net/runelite/mixins/RSClientMixin.java | 18 +- ...Mixin.java => RSEnumCompositionMixin.java} | 6 +- .../runelite/mixins/RSFriendSystemMixin.java | 6 +- .../net/runelite/mixins/RSWidgetMixin.java | 20 +- .../java/net/runelite/rs/api/RSClient.java | 2 +- ...Definition.java => RSEnumComposition.java} | 4 +- runescape-client/src/main/java/Client.java | 16 +- ...umDefinition.java => EnumComposition.java} | 4 +- runescape-client/src/main/java/FontName.java | 10 +- runescape-client/src/main/java/ItemLayer.java | 6 +- .../src/main/java/MusicPatchNode.java | 10 +- runescape-client/src/main/java/Players.java | 6 +- runescape-client/src/main/java/Skeleton.java | 2 +- .../src/main/java/StructDefinition.java | 2 +- 60 files changed, 5217 insertions(+), 112 deletions(-) rename runelite-api/src/main/java/net/runelite/api/{EnumDefinition.java => EnumComposition.java} (97%) rename runelite-api/src/main/java/net/runelite/api/events/{FriendRemoved.java => RemovedFriend.java} (97%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/cooking/FermentTimer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/PrayerType.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpPrayer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSession.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotMinimapOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/fishing/MinnowSpot.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNoteOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/HoveredFriend.java rename runelite-mixins/src/main/java/net/runelite/mixins/{RSEnumDefinitionMixin.java => RSEnumCompositionMixin.java} (83%) rename runescape-api/src/main/java/net/runelite/rs/api/{RSEnumDefinition.java => RSEnumComposition.java} (74%) rename runescape-client/src/main/java/{EnumDefinition.java => EnumComposition.java} (98%) diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 8594d794d2..e5d177b37a 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -1814,7 +1814,7 @@ public interface Client extends GameShell */ SpritePixels[] getCrossSprites(); - EnumDefinition getEnum(int id); + EnumComposition getEnum(int id); /** * Draws a menu in the 2010 interface style. diff --git a/runelite-api/src/main/java/net/runelite/api/EnumDefinition.java b/runelite-api/src/main/java/net/runelite/api/EnumComposition.java similarity index 97% rename from runelite-api/src/main/java/net/runelite/api/EnumDefinition.java rename to runelite-api/src/main/java/net/runelite/api/EnumComposition.java index 57df54147f..00f8c30814 100644 --- a/runelite-api/src/main/java/net/runelite/api/EnumDefinition.java +++ b/runelite-api/src/main/java/net/runelite/api/EnumComposition.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -public interface EnumDefinition +public interface EnumComposition { int[] getKeys(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/FriendRemoved.java b/runelite-api/src/main/java/net/runelite/api/events/RemovedFriend.java similarity index 97% rename from runelite-api/src/main/java/net/runelite/api/events/FriendRemoved.java rename to runelite-api/src/main/java/net/runelite/api/events/RemovedFriend.java index 13b1ecea21..dc244b8ff8 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FriendRemoved.java +++ b/runelite-api/src/main/java/net/runelite/api/events/RemovedFriend.java @@ -30,7 +30,7 @@ import lombok.Value; * An event where a request to remove a friend is sent to the server. */ @Value -public class FriendRemoved implements Event +public class RemovedFriend implements Event { /** * The name of the removed friend. diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 3269a12bbd..b4181ac199 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -981,7 +981,7 @@ public enum WidgetInfo * @param id passed group-child ID * @return the group ID */ - public static int TO_GROUP(int id) + public static int getGroupFromID(int id) { return id >>> 16; } @@ -993,21 +993,8 @@ public enum WidgetInfo * @param id passed group-child ID * @return the child ID */ - public static int TO_CHILD(int id) + public static int getChildFromID(int id) { return id & 0xFFFF; } - - /** - * Packs the group and child IDs into a single integer. - * - * @param groupId the group ID - * @param childId the child ID - * @return the packed ID - */ - public static int PACK(int groupId, int childId) - { - return groupId << 16 | childId; - } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java new file mode 100644 index 0000000000..6efc69b613 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsConfig.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("banktags") +public interface BankTagsConfig extends Config +{ + @ConfigItem( + keyName = "useTabs", + name = "Use Tag Tabs", + description = "Enable the ability to add tabs to your bank which allow fast access to tags.", + position = 1 + ) + default boolean tabs() + { + return true; + } + + @ConfigItem( + keyName = "rememberTab", + name = "Remember last Tag Tab", + description = "Enable the ability to remember last Tag Tab when closing/opening the bank.", + position = 2 + ) + default boolean rememberTab() + { + return true; + } + + @ConfigItem( + keyName = "removeTabSeparators", + name = "Remove tab separators", + description = "Remove the tab separators normally present in tag tabs", + position = 3 + ) + default boolean removeSeparators() + { + return false; + } + + @ConfigItem( + keyName = "preventTagTabDrags", + name = "Prevent tag tab item dragging", + description = "Ignore dragged items to prevent unwanted bank item reordering", + position = 4 + ) + default boolean preventTagTabDrags() + { + return false; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "", + hidden = true + ) + default int position() + { + return 0; + } + + @ConfigItem( + keyName = "position", + name = "", + description = "" + ) + void position(int idx); + + @ConfigItem( + keyName = "tab", + name = "", + description = "", + hidden = true + ) + default String tab() + { + return ""; + } + + @ConfigItem( + keyName = "tab", + name = "", + description = "" + ) + void tab(String tab); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java new file mode 100644 index 0000000000..1cffe75379 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -0,0 +1,598 @@ +/* + * Copyright (c) 2018, Adam + * Copyright (c) 2018, Ron Young + * Copyright (c) 2018, Tomas Slusny + * 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.banktags; + +import com.google.common.collect.Lists; +import com.google.common.primitives.Shorts; +import com.google.inject.Provides; +import java.awt.event.MouseWheelEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.KeyCode; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.ScriptID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientStr; +import net.runelite.api.events.DraggingWidgetChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.GrandExchangeSearched; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.ScriptPreFired; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.Widget; +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.game.SpriteManager; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.input.MouseWheelListener; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDependency; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.banktags.tabs.TabInterface; +import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS; +import net.runelite.client.plugins.banktags.tabs.TabSprites; +import net.runelite.client.plugins.banktags.tabs.TagTab; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.util.Text; + +@PluginDescriptor( + name = "Bank Tags", + description = "Enable tagging of bank items and searching of bank tags", + tags = {"searching", "tagging"} +) +@PluginDependency(ClueScrollPlugin.class) +public class BankTagsPlugin extends Plugin implements MouseWheelListener +{ + public static final String CONFIG_GROUP = "banktags"; + public static final String TAG_SEARCH = "tag:"; + private static final String EDIT_TAGS_MENU_OPTION = "Edit-tags"; + public static final String ICON_SEARCH = "icon_"; + public static final String TAG_TABS_CONFIG = "tagtabs"; + public static final String VAR_TAG_SUFFIX = "*"; + private static final int ITEMS_PER_ROW = 8; + private static final int ITEM_VERTICAL_SPACING = 36; + private static final int ITEM_HORIZONTAL_SPACING = 48; + private static final int ITEM_ROW_START = 51; + + private static final int MAX_RESULT_COUNT = 250; + + private static final String SEARCH_BANK_INPUT_TEXT = + "Show items whose names or tags contain the following text:
" + + "(To show only tagged items, start your search with 'tag:')"; + private static final String SEARCH_BANK_INPUT_TEXT_FOUND = + "Show items whose names or tags contain the following text: (%d found)
" + + "(To show only tagged items, start your search with 'tag:')"; + + @Inject + private ItemManager itemManager; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private MouseManager mouseManager; + + @Inject + private BankTagsConfig config; + + @Inject + private TagManager tagManager; + + @Inject + private TabInterface tabInterface; + + @Inject + private SpriteManager spriteManager; + + @Inject + private ConfigManager configManager; + + @Provides + BankTagsConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(BankTagsConfig.class); + } + + @Override + public void resetConfiguration() + { + List extraKeys = Lists.newArrayList( + CONFIG_GROUP + "." + TagManager.ITEM_KEY_PREFIX, + CONFIG_GROUP + "." + ICON_SEARCH, + CONFIG_GROUP + "." + TAG_TABS_CONFIG + ); + + for (String prefix : extraKeys) + { + List keys = configManager.getConfigurationKeys(prefix); + for (String key : keys) + { + String[] str = key.split("\\.", 2); + if (str.length == 2) + { + configManager.unsetConfiguration(str[0], str[1]); + } + } + } + + clientThread.invokeLater(() -> + { + tabInterface.destroy(); + tabInterface.init(); + }); + } + + + @Override + public void startUp() + { + cleanConfig(); + mouseManager.registerMouseWheelListener(this); + clientThread.invokeLater(tabInterface::init); + spriteManager.addSpriteOverrides(TabSprites.values()); + } + + @Deprecated + private void cleanConfig() + { + removeInvalidTags("tagtabs"); + + List tags = configManager.getConfigurationKeys(CONFIG_GROUP + ".item_"); + tags.forEach(s -> + { + String[] split = s.split("\\.", 2); + removeInvalidTags(split[1]); + }); + + List icons = configManager.getConfigurationKeys(CONFIG_GROUP + ".icon_"); + icons.forEach(s -> + { + String[] split = s.split("\\.", 2); + String replaced = split[1].replaceAll("[<>/]", ""); + if (!split[1].equals(replaced)) + { + String value = configManager.getConfiguration(CONFIG_GROUP, split[1]); + configManager.unsetConfiguration(CONFIG_GROUP, split[1]); + if (replaced.length() > "icon_".length()) + { + configManager.setConfiguration(CONFIG_GROUP, replaced, value); + } + } + }); + } + + @Deprecated + private void removeInvalidTags(final String key) + { + final String value = configManager.getConfiguration(CONFIG_GROUP, key); + if (value == null) + { + return; + } + + String replaced = value.replaceAll("[<>:/]", ""); + if (!value.equals(replaced)) + { + replaced = Text.toCSV(Text.fromCSV(replaced)); + if (replaced.isEmpty()) + { + configManager.unsetConfiguration(CONFIG_GROUP, key); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, key, replaced); + } + } + } + + @Override + public void shutDown() + { + mouseManager.unregisterMouseWheelListener(this); + clientThread.invokeLater(tabInterface::destroy); + spriteManager.removeSpriteOverrides(TabSprites.values()); + } + + @Subscribe + public void onGrandExchangeSearched(GrandExchangeSearched event) + { + final String input = client.getVar(VarClientStr.INPUT_TEXT); + if (!input.startsWith(TAG_SEARCH)) + { + return; + } + + event.consume(); + + final String tag = input.substring(TAG_SEARCH.length()).trim(); + final Set ids = tagManager.getItemsForTag(tag) + .stream() + .mapToInt(Math::abs) + .mapToObj(ItemVariationMapping::getVariations) + .flatMap(Collection::stream) + .distinct() + .filter(i -> itemManager.getItemComposition(i).isTradeable()) + .limit(MAX_RESULT_COUNT) + .collect(Collectors.toCollection(TreeSet::new)); + + client.setGeSearchResultIndex(0); + client.setGeSearchResultCount(ids.size()); + client.setGeSearchResultIds(Shorts.toArray(ids)); + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + String eventName = event.getEventName(); + + int[] intStack = client.getIntStack(); + String[] stringStack = client.getStringStack(); + int intStackSize = client.getIntStackSize(); + int stringStackSize = client.getStringStackSize(); + + tabInterface.handleScriptEvent(event); + + switch (eventName) + { + case "setSearchBankInputText": + stringStack[stringStackSize - 1] = SEARCH_BANK_INPUT_TEXT; + break; + case "setSearchBankInputTextFound": + { + int matches = intStack[intStackSize - 1]; + stringStack[stringStackSize - 1] = String.format(SEARCH_BANK_INPUT_TEXT_FOUND, matches); + break; + } + case "bankSearchFilter": + final int itemId = intStack[intStackSize - 1]; + final String searchfilter = stringStack[stringStackSize - 1]; + + // This event only fires when the bank is in search mode. It will fire even if there is no search + // input. We prevent having a tag tab open while also performing a normal search, so if a tag tab + // is active here it must mean we have placed the bank into search mode. See onScriptPostFired(). + TagTab activeTab = tabInterface.getActiveTab(); + String search = activeTab != null ? TAG_SEARCH + activeTab.getTag() : searchfilter; + + if (search.isEmpty()) + { + return; + } + + boolean tagSearch = search.startsWith(TAG_SEARCH); + if (tagSearch) + { + search = search.substring(TAG_SEARCH.length()).trim(); + } + + if (tagManager.findTag(itemId, search)) + { + // return true + intStack[intStackSize - 2] = 1; + } + else if (tagSearch) + { + // if the item isn't tagged we return false to prevent the item matching if the item name happens + // to contain the tag name. + intStack[intStackSize - 2] = 0; + } + break; + case "getSearchingTagTab": + intStack[intStackSize - 1] = tabInterface.isActive() ? 1 : 0; + break; + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + MenuEntry[] entries = client.getMenuEntries(); + + if (event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + Widget container = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + Widget item = container.getChild(event.getActionParam0()); + int itemID = item.getItemId(); + String text = EDIT_TAGS_MENU_OPTION; + int tagCount = tagManager.getTags(itemID, false).size() + tagManager.getTags(itemID, true).size(); + + if (tagCount > 0) + { + text += " (" + tagCount + ")"; + } + + MenuEntry editTags = new MenuEntry(); + editTags.setParam0(event.getActionParam0()); + editTags.setParam1(event.getActionParam1()); + editTags.setTarget(event.getTarget()); + editTags.setOption(text); + editTags.setType(MenuAction.RUNELITE.getId()); + editTags.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = editTags; + client.setMenuEntries(entries); + } + + tabInterface.handleAdd(event); + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getMenuAction() == MenuAction.RUNELITE + && event.getMenuOption().startsWith(EDIT_TAGS_MENU_OPTION)) + { + event.consume(); + int inventoryIndex = event.getActionParam(); + ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + if (bankContainer == null) + { + return; + } + Item[] items = bankContainer.getItems(); + if (inventoryIndex < 0 || inventoryIndex >= items.length) + { + return; + } + Item item = bankContainer.getItems()[inventoryIndex]; + if (item == null) + { + return; + } + + int itemId = item.getId(); + ItemComposition itemComposition = itemManager.getItemComposition(itemId); + String name = itemComposition.getName(); + + // Get both tags and vartags and append * to end of vartags name + Collection tags = tagManager.getTags(itemId, false); + tagManager.getTags(itemId, true).stream() + .map(i -> i + "*") + .forEach(tags::add); + + String initialValue = Text.toCSV(tags); + + chatboxPanelManager.openTextInput(name + " tags:
(append " + VAR_TAG_SUFFIX + " for variation tag)") + .addCharValidator(FILTERED_CHARS) + .value(initialValue) + .onDone((Consumer) (newValue) -> + clientThread.invoke(() -> + { + // Split inputted tags to vartags (ending with *) and regular tags + final Collection newTags = new ArrayList<>(Text.fromCSV(newValue.toLowerCase())); + final Collection newVarTags = new ArrayList<>(newTags).stream().filter(s -> s.endsWith(VAR_TAG_SUFFIX)).map(s -> + { + newTags.remove(s); + return s.substring(0, s.length() - VAR_TAG_SUFFIX.length()); + }).collect(Collectors.toList()); + + // And save them + tagManager.setTagString(itemId, Text.toCSV(newTags), false); + tagManager.setTagString(itemId, Text.toCSV(newVarTags), true); + + // Check both previous and current tags in case the tag got removed in new tags or in case + // the tag got added in new tags + tabInterface.updateTabIfActive(Text.fromCSV(initialValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); + tabInterface.updateTabIfActive(Text.fromCSV(newValue.toLowerCase().replaceAll(Pattern.quote(VAR_TAG_SUFFIX), ""))); + })) + .build(); + } + else + { + tabInterface.handleClick(event); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (configChanged.getGroup().equals(CONFIG_GROUP) && configChanged.getKey().equals("useTabs")) + { + if (config.tabs()) + { + clientThread.invokeLater(tabInterface::init); + } + else + { + clientThread.invokeLater(tabInterface::destroy); + } + } + } + + @Subscribe + public void onScriptPreFired(ScriptPreFired event) + { + int scriptId = event.getScriptId(); + if (scriptId == ScriptID.BANKMAIN_FINISHBUILDING) + { + // Since we apply tag tab search filters even when the bank is not in search mode, + // bankkmain_build will reset the bank title to "The Bank of Gielinor". So apply our + // own title. + TagTab activeTab = tabInterface.getActiveTab(); + if (tabInterface.isTagTabActive()) + { + // Tag tab tab has its own title since it isn't a real tag + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + bankTitle.setText("Tag tab tab"); + } + else if (activeTab != null) + { + Widget bankTitle = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + bankTitle.setText("Tag tab " + activeTab.getTag() + ""); + } + } + else if (scriptId == ScriptID.BANKMAIN_SEARCH_TOGGLE) + { + tabInterface.handleSearch(); + } + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (event.getScriptId() == ScriptID.BANKMAIN_SEARCHING) + { + // The return value of bankmain_searching is on the stack. If we have a tag tab active + // make it return true to put the bank in a searching state. + if (tabInterface.getActiveTab() != null || tabInterface.isTagTabActive()) + { + client.getIntStack()[client.getIntStackSize() - 1] = 1; // true + } + return; + } + + if (event.getScriptId() != ScriptID.BANKMAIN_BUILD || !config.removeSeparators()) + { + return; + } + + if (!tabInterface.isActive()) + { + return; + } + + Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (itemContainer == null) + { + return; + } + + int items = 0; + + Widget[] containerChildren = itemContainer.getDynamicChildren(); + + // sort the child array as the items are not in the displayed order + Arrays.sort(containerChildren, Comparator.comparing(Widget::getOriginalY) + .thenComparing(Widget::getOriginalX)); + + for (Widget child : containerChildren) + { + if (child.getItemId() != -1 && !child.isHidden()) + { + // calculate correct item position as if this was a normal tab + int adjYOffset = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING; + int adjXOffset = (items % ITEMS_PER_ROW) * ITEM_HORIZONTAL_SPACING + ITEM_ROW_START; + + if (child.getOriginalY() != adjYOffset) + { + child.setOriginalY(adjYOffset); + child.revalidate(); + } + + if (child.getOriginalX() != adjXOffset) + { + child.setOriginalX(adjXOffset); + child.revalidate(); + } + + items++; + } + + // separator line or tab text + if (child.getSpriteId() == SpriteID.RESIZEABLE_MODE_SIDE_PANEL_BACKGROUND + || child.getText().contains("Tab")) + { + child.setHidden(true); + } + } + + final Widget bankItemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + int itemContainerHeight = bankItemContainer.getHeight(); + // add a second row of height here to allow users to scroll down when the last row is partially visible + int adjustedScrollHeight = (items / ITEMS_PER_ROW) * ITEM_VERTICAL_SPACING + ITEM_VERTICAL_SPACING; + itemContainer.setScrollHeight(Math.max(adjustedScrollHeight, itemContainerHeight)); + + final int itemContainerScroll = bankItemContainer.getScrollY(); + clientThread.invokeLater(() -> + client.runScript(ScriptID.UPDATE_SCROLLBAR, + WidgetInfo.BANK_SCROLLBAR.getId(), + WidgetInfo.BANK_ITEM_CONTAINER.getId(), + itemContainerScroll)); + + } + + @Subscribe + public void onGameTick(GameTick event) + { + tabInterface.update(); + } + + @Subscribe + public void onDraggingWidgetChanged(DraggingWidgetChanged event) + { + final boolean shiftPressed = client.isKeyPressed(KeyCode.KC_SHIFT); + tabInterface.handleDrag(event.isDraggingWidget(), shiftPressed); + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() == WidgetID.BANK_GROUP_ID) + { + tabInterface.init(); + } + } + + @Override + public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event) + { + tabInterface.handleWheel(event); + return event; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java new file mode 100644 index 0000000000..fe697034d0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags; + +import com.google.common.base.Strings; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemVariationMapping; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import net.runelite.client.plugins.cluescrolls.ClueScrollService; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue; +import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; +import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue; +import net.runelite.client.plugins.cluescrolls.clues.HotColdClue; +import net.runelite.client.plugins.cluescrolls.clues.MapClue; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import net.runelite.client.util.Text; + +@Singleton +public class TagManager +{ + static final String ITEM_KEY_PREFIX = "item_"; + private final ConfigManager configManager; + private final ItemManager itemManager; + private final ClueScrollService clueScrollService; + + @Inject + private TagManager( + final ItemManager itemManager, + final ConfigManager configManager, + final ClueScrollService clueScrollService) + { + this.itemManager = itemManager; + this.configManager = configManager; + this.clueScrollService = clueScrollService; + } + + String getTagString(int itemId, boolean variation) + { + itemId = getItemId(itemId, variation); + + String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + if (config == null) + { + return ""; + } + + return config; + } + + Collection getTags(int itemId, boolean variation) + { + return new LinkedHashSet<>(Text.fromCSV(getTagString(itemId, variation).toLowerCase())); + } + + void setTagString(int itemId, String tags, boolean variation) + { + itemId = getItemId(itemId, variation); + + if (Strings.isNullOrEmpty(tags)) + { + configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, tags); + } + } + + public void addTags(int itemId, final Collection t, boolean variation) + { + final Collection tags = getTags(itemId, variation); + if (tags.addAll(t)) + { + setTags(itemId, tags, variation); + } + } + + public void addTag(int itemId, String tag, boolean variation) + { + final Collection tags = getTags(itemId, variation); + if (tags.add(Text.standardize(tag))) + { + setTags(itemId, tags, variation); + } + } + + private void setTags(int itemId, Collection tags, boolean variation) + { + setTagString(itemId, Text.toCSV(tags), variation); + } + + boolean findTag(int itemId, String search) + { + if (search.equals("clue") && testClue(itemId)) + { + return true; + } + + Collection tags = getTags(itemId, false); + tags.addAll(getTags(itemId, true)); + return tags.stream().anyMatch(tag -> tag.startsWith(Text.standardize(search))); + } + + public List getItemsForTag(String tag) + { + final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; + return configManager.getConfigurationKeys(prefix).stream() + .map(item -> Integer.parseInt(item.replace(prefix, ""))) + .filter(item -> getTags(item, false).contains(tag) || getTags(item, true).contains(tag)) + .collect(Collectors.toList()); + } + + public void removeTag(String tag) + { + final String prefix = CONFIG_GROUP + "." + ITEM_KEY_PREFIX; + configManager.getConfigurationKeys(prefix).forEach(item -> + { + int id = Integer.parseInt(item.replace(prefix, "")); + removeTag(id, tag); + }); + } + + public void removeTag(int itemId, String tag) + { + Collection tags = getTags(itemId, false); + if (tags.remove(Text.standardize(tag))) + { + setTags(itemId, tags, false); + } + + tags = getTags(itemId, true); + if (tags.remove(Text.standardize(tag))) + { + setTags(itemId, tags, true); + } + } + + public void renameTag(String oldTag, String newTag) + { + List items = getItemsForTag(Text.standardize(oldTag)); + items.forEach(id -> + { + Collection tags = getTags(id, id < 0); + + tags.remove(Text.standardize(oldTag)); + tags.add(Text.standardize(newTag)); + + setTags(id, tags, id < 0); + }); + } + + private int getItemId(int itemId, boolean variation) + { + itemId = Math.abs(itemId); + itemId = itemManager.canonicalize(itemId); + + if (variation) + { + itemId = ItemVariationMapping.map(itemId) * -1; + } + + return itemId; + } + + private boolean testClue(int itemId) + { + ClueScroll c = clueScrollService.getClue(); + + if (c == null) + { + return false; + } + + if (c instanceof EmoteClue) + { + EmoteClue emote = (EmoteClue) c; + + for (ItemRequirement ir : emote.getItemRequirements()) + { + if (ir.fulfilledBy(itemId)) + { + return true; + } + } + } + else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue) + { + return itemId == ItemID.SPADE; + } + else if (c instanceof MapClue) + { + MapClue mapClue = (MapClue) c; + + return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE; + } + + return false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java new file mode 100644 index 0000000000..bcf03fd585 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +class MenuIndexes +{ + static class NewTab + { + static final int NEW_TAB = 2; + static final int IMPORT_TAB = 3; + static final int OPEN_TAB_MENU = 4; + } + + static class Tab + { + static final int OPEN_TAG = 2; + static final int CHANGE_ICON = 3; + static final int DELETE_TAB = 4; + static final int EXPORT_TAB = 5; + static final int RENAME_TAB = 6; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java new file mode 100644 index 0000000000..0564d7b38b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java @@ -0,0 +1,1198 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Runnables; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.MouseWheelEvent; +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.IntPredicate; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Point; +import net.runelite.api.ScriptEvent; +import net.runelite.api.ScriptID; +import net.runelite.api.SoundEffectID; +import net.runelite.api.SpriteID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; +import net.runelite.api.Varbits; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.widgets.ItemQuantityMode; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.Notifier; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.chatbox.ChatboxItemSearch; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.plugins.bank.BankSearch; +import net.runelite.client.plugins.banktags.BankTagsConfig; +import net.runelite.client.plugins.banktags.BankTagsPlugin; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX; +import net.runelite.client.plugins.banktags.TagManager; +import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.NewTab; +import static net.runelite.client.plugins.banktags.tabs.MenuIndexes.Tab; +import net.runelite.client.ui.JagexColors; +import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; + +@Singleton +public class TabInterface +{ + public static final IntPredicate FILTERED_CHARS = c -> ":".indexOf(c) == -1; + + private static final Color HILIGHT_COLOR = JagexColors.MENU_TARGET; + private static final String SCROLL_UP = "Scroll up"; + private static final String SCROLL_DOWN = "Scroll down"; + private static final String NEW_TAB = "New tag tab"; + private static final String REMOVE_TAB = "Delete tag tab"; + private static final String EXPORT_TAB = "Export tag tab"; + private static final String IMPORT_TAB = "Import tag tab"; + private static final String VIEW_TAB = "View tag tab"; + private static final String RENAME_TAB = "Rename tag tab"; + private static final String CHANGE_ICON = "Change icon"; + private static final String REMOVE_TAG = "Remove-tag"; + private static final String TAG_GEAR = "Tag-equipment"; + private static final String TAG_INVENTORY = "Tag-inventory"; + private static final String TAB_MENU_KEY = "tagtabs"; + private static final String OPEN_TAB_MENU = "View tag tabs"; + private static final String SHOW_WORN = "Show worn items"; + private static final String SHOW_SETTINGS = "Show menu"; + private static final String SHOW_TUTORIAL = "Show tutorial"; + private static final int TAB_HEIGHT = 40; + private static final int TAB_WIDTH = 39; + private static final int BUTTON_HEIGHT = 20; + private static final int MARGIN = 1; + private static final int SCROLL_TICK = 500; + private static final int INCINERATOR_WIDTH = 48; + private static final int INCINERATOR_HEIGHT = 39; + private static final int BANK_ITEM_WIDTH = 36; + private static final int BANK_ITEM_HEIGHT = 32; + private static final int BANK_ITEM_X_PADDING = 12; + private static final int BANK_ITEM_Y_PADDING = 4; + private static final int BANK_ITEMS_PER_ROW = 8; + private static final int BANK_ITEM_START_X = 51; + private static final int BANK_ITEM_START_Y = 0; + + private final Client client; + private final ClientThread clientThread; + private final ItemManager itemManager; + private final TagManager tagManager; + private final TabManager tabManager; + private final ChatboxPanelManager chatboxPanelManager; + private final BankTagsConfig config; + private final Notifier notifier; + private final BankSearch bankSearch; + private final ChatboxItemSearch searchProvider; + private final Rectangle bounds = new Rectangle(); + private final Rectangle canvasBounds = new Rectangle(); + + @Getter + private TagTab activeTab; + @Getter + private boolean tagTabActive; + private int maxTabs; + private int currentTabIndex; + private Instant startScroll = Instant.now(); + + @Getter + private Widget upButton; + + @Getter + private Widget downButton; + + @Getter + private Widget newTab; + + @Getter + private Widget parent; + + @Inject + private TabInterface( + final Client client, + final ClientThread clientThread, + final ItemManager itemManager, + final TagManager tagManager, + final TabManager tabManager, + final ChatboxPanelManager chatboxPanelManager, + final BankTagsConfig config, + final Notifier notifier, + final BankSearch bankSearch, + final ChatboxItemSearch searchProvider) + { + this.client = client; + this.clientThread = clientThread; + this.itemManager = itemManager; + this.tagManager = tagManager; + this.tabManager = tabManager; + this.chatboxPanelManager = chatboxPanelManager; + this.config = config; + this.notifier = notifier; + this.bankSearch = bankSearch; + this.searchProvider = searchProvider; + } + + public boolean isActive() + { + return activeTab != null; + } + + public void init() + { + if (isHidden()) + { + return; + } + + currentTabIndex = config.position(); + parent = client.getWidget(WidgetInfo.BANK_CONTENT_CONTAINER); + + updateBounds(); + + upButton = createGraphic("", TabSprites.UP_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + upButton.setAction(1, SCROLL_UP); + int clickmask = upButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + upButton.setClickMask(clickmask); + upButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(-1)); + + downButton = createGraphic("", TabSprites.DOWN_ARROW.getSpriteId(), -1, TAB_WIDTH, BUTTON_HEIGHT, bounds.x, 0, true); + downButton.setAction(1, SCROLL_DOWN); + clickmask = downButton.getClickMask(); + clickmask |= WidgetConfig.DRAG; + downButton.setClickMask(clickmask); + downButton.setOnOpListener((JavaScriptCallback) (event) -> scrollTab(1)); + + newTab = createGraphic("", TabSprites.NEW_TAB.getSpriteId(), -1, TAB_WIDTH, 39, bounds.x, 0, true); + newTab.setAction(1, NEW_TAB); + newTab.setAction(2, IMPORT_TAB); + newTab.setAction(3, OPEN_TAB_MENU); + newTab.setOnOpListener((JavaScriptCallback) this::handleNewTab); + + tabManager.clear(); + tabManager.getAllTabs().forEach(this::loadTab); + activateTab(null); + scrollTab(0); + + if (config.rememberTab() && !Strings.isNullOrEmpty(config.tab())) + { + // the server will resync the last opened vanilla tab when the bank is opened + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + openTag(config.tab()); + } + + Widget equipmentButton = client.getWidget(WidgetInfo.BANK_EQUIPMENT_BUTTON); + Widget titleBar = client.getWidget(WidgetInfo.BANK_TITLE_BAR); + if (equipmentButton == null || titleBar == null || titleBar.getOriginalX() > 0) + { + // don't keep moving widgets if they have already been moved + return; + } + + equipmentButton.setOriginalX(6); + equipmentButton.setOriginalY(4); + equipmentButton.revalidate(); + + // the bank item count is 3 widgets + for (int child = WidgetInfo.BANK_ITEM_COUNT_TOP.getChildId(); child <= WidgetInfo.BANK_ITEM_COUNT_BOTTOM.getChildId(); child++) + { + Widget widget = client.getWidget(WidgetID.BANK_GROUP_ID, child); + if (widget == null) + { + return; + } + + widget.setOriginalX(widget.getOriginalX() + equipmentButton.getWidth()); + widget.revalidate(); + } + + titleBar.setOriginalX(equipmentButton.getWidth() / 2); + titleBar.setOriginalWidth(titleBar.getWidth() - equipmentButton.getWidth()); + titleBar.revalidate(); + } + + private void handleDeposit(MenuOptionClicked event, Boolean inventory) + { + ItemContainer container = client.getItemContainer(inventory ? InventoryID.INVENTORY : InventoryID.EQUIPMENT); + + if (container == null) + { + return; + } + + List items = Arrays.stream(container.getItems()) + .filter(Objects::nonNull) + .map(Item::getId) + .filter(id -> id != -1) + .collect(Collectors.toList()); + + if (!Strings.isNullOrEmpty(event.getMenuTarget())) + { + if (activeTab != null && Text.removeTags(event.getMenuTarget()).equals(activeTab.getTag())) + { + for (Integer item : items) + { + tagManager.addTag(item, activeTab.getTag(), false); + } + + openTag(activeTab.getTag()); + } + + return; + } + + chatboxPanelManager.openTextInput((inventory ? "Inventory " : "Equipment ") + " tags:") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (newTags) -> + clientThread.invoke(() -> + { + final List tags = Text.fromCSV(newTags.toLowerCase()); + + for (Integer item : items) + { + tagManager.addTags(item, tags, false); + } + + updateTabIfActive(tags); + })) + .build(); + } + + private void handleNewTab(ScriptEvent event) + { + switch (event.getOp()) + { + case NewTab.NEW_TAB: + chatboxPanelManager.openTextInput("Tag name") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (tagName) -> clientThread.invoke(() -> + { + if (!Strings.isNullOrEmpty(tagName)) + { + loadTab(tagName); + tabManager.save(); + scrollTab(0); + } + })) + .build(); + break; + case NewTab.IMPORT_TAB: + try + { + final String dataString = Toolkit + .getDefaultToolkit() + .getSystemClipboard() + .getData(DataFlavor.stringFlavor) + .toString() + .trim(); + + final Iterator dataIter = Text.fromCSV(dataString).iterator(); + String name = dataIter.next(); + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) + { + if (FILTERED_CHARS.test(c)) + { + sb.append(c); + } + } + + if (sb.length() == 0) + { + notifier.notify("Failed to import tag tab from clipboard, invalid format."); + return; + } + + name = sb.toString(); + + final String icon = dataIter.next(); + tabManager.setIcon(name, icon); + + while (dataIter.hasNext()) + { + final int itemId = Integer.parseInt(dataIter.next()); + tagManager.addTag(itemId, name, itemId < 0); + } + + loadTab(name); + tabManager.save(); + scrollTab(0); + + if (activeTab != null && name.equals(activeTab.getTag())) + { + openTag(activeTab.getTag()); + } + + notifier.notify("Tag tab " + name + " has been imported from your clipboard!"); + } + catch (UnsupportedFlavorException | NoSuchElementException | IOException | NumberFormatException ex) + { + notifier.notify("Failed to import tag tab from clipboard, invalid format."); + } + break; + case NewTab.OPEN_TAB_MENU: + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + openTag(TAB_MENU_KEY); + break; + } + } + + private void handleTagTab(ScriptEvent event) + { + switch (event.getOp()) + { + case Tab.OPEN_TAG: + client.setVarbit(Varbits.CURRENT_BANK_TAB, 0); + Widget clicked = event.getSource(); + + TagTab tab = tabManager.find(Text.removeTags(clicked.getName())); + + if (tab.equals(activeTab)) + { + activateTab(null); + bankSearch.reset(true); + } + else + { + openTag(Text.removeTags(clicked.getName())); + // openTag will reset and relayout + } + + client.playSoundEffect(SoundEffectID.UI_BOOP); + break; + case Tab.CHANGE_ICON: + final String tag = Text.removeTags(event.getOpbase()); + searchProvider + .tooltipText(CHANGE_ICON + " (" + tag + ")") + .onItemSelected((itemId) -> + { + TagTab iconToSet = tabManager.find(tag); + if (iconToSet != null) + { + iconToSet.setIconItemId(itemId); + iconToSet.getIcon().setItemId(itemId); + iconToSet.getMenu().setItemId(itemId); + tabManager.setIcon(iconToSet.getTag(), itemId + ""); + } + }) + .build(); + break; + case Tab.DELETE_TAB: + String target = Text.standardize(event.getOpbase()); + chatboxPanelManager.openTextMenuInput("Delete " + target) + .option("1. Tab and tag from all items", () -> + clientThread.invoke(() -> + { + tagManager.removeTag(target); + deleteTab(target); + }) + ) + .option("2. Only tab", () -> clientThread.invoke(() -> deleteTab(target))) + .option("3. Cancel", Runnables::doNothing) + .build(); + break; + case Tab.EXPORT_TAB: + final List data = new ArrayList<>(); + final TagTab tagTab = tabManager.find(Text.removeTags(event.getOpbase())); + data.add(tagTab.getTag()); + data.add(String.valueOf(tagTab.getIconItemId())); + + for (Integer item : tagManager.getItemsForTag(tagTab.getTag())) + { + data.add(String.valueOf(item)); + } + + final StringSelection stringSelection = new StringSelection(Text.toCSV(data)); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!"); + break; + case Tab.RENAME_TAB: + String renameTarget = Text.standardize(event.getOpbase()); + renameTab(renameTarget); + break; + } + } + + public void destroy() + { + activeTab = null; + currentTabIndex = 0; + maxTabs = 0; + parent = null; + + if (upButton != null) + { + upButton.setHidden(true); + downButton.setHidden(true); + newTab.setHidden(true); + } + + tabManager.clear(); + } + + public void update() + { + if (isHidden()) + { + parent = null; + + saveTab(); + return; + } + + // Don't continue ticking if equipment menu or bank menu is open + if (parent.isSelfHidden()) + { + return; + } + + updateBounds(); + scrollTab(0); + } + + private void saveTab() + { + // If bank window was just hidden, update last active tab position + if (currentTabIndex != config.position()) + { + config.position(currentTabIndex); + } + + // Do the same for last active tab + if (config.rememberTab()) + { + if (activeTab == null && !Strings.isNullOrEmpty(config.tab())) + { + config.tab(""); + } + else if (activeTab != null && !activeTab.getTag().equals(config.tab())) + { + config.tab(activeTab.getTag()); + } + } + else if (!Strings.isNullOrEmpty(config.tab())) + { + config.tab(""); + } + } + + private void setTabMenuVisible(boolean visible) + { + for (TagTab t : tabManager.getTabs()) + { + t.getMenu().setHidden(!visible); + } + } + + private boolean isTabMenuActive() + { + return tagTabActive; + } + + public void handleScriptEvent(final ScriptCallbackEvent event) + { + String eventName = event.getEventName(); + + int[] intStack = client.getIntStack(); + int intStackSize = client.getIntStackSize(); + + switch (eventName) + { + case "setBankScroll": + if (!isTabMenuActive()) + { + setTabMenuVisible(false); + return; + } + + setTabMenuVisible(true); + + // scroll height + intStack[intStackSize - 3] = (((tabManager.getTabs().size() - 1) / BANK_ITEMS_PER_ROW) + 1) * (BANK_ITEM_HEIGHT + BANK_ITEM_Y_PADDING); + + // skip normal bank layout + intStack[intStackSize - 2] = 1; + break; + case "beforeBankLayout": + setTabMenuVisible(false); + break; + } + } + + public void handleWheel(final MouseWheelEvent event) + { + if (parent == null || !canvasBounds.contains(event.getPoint())) + { + return; + } + + event.consume(); + + clientThread.invoke(() -> + { + if (isHidden()) + { + return; + } + + scrollTab(event.getWheelRotation()); + }); + } + + public void handleAdd(MenuEntryAdded event) + { + if (isHidden()) + { + return; + } + + MenuEntry[] entries = client.getMenuEntries(); + + if (activeTab != null + && event.getActionParam1() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getOption().equals("Examine")) + { + entries = createMenuEntry(event, REMOVE_TAG + " (" + activeTab.getTag() + ")", event.getTarget(), entries); + client.setMenuEntries(entries); + } + else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() + && event.getOption().equals("Deposit inventory")) + { + entries = createMenuEntry(event, TAG_INVENTORY, event.getTarget(), entries); + + if (activeTab != null) + { + entries = createMenuEntry(event, TAG_INVENTORY, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); + } + + client.setMenuEntries(entries); + } + else if (event.getActionParam1() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() + && event.getOption().equals("Deposit worn items")) + { + entries = createMenuEntry(event, TAG_GEAR, event.getTarget(), entries); + + if (activeTab != null) + { + entries = createMenuEntry(event, TAG_GEAR, ColorUtil.wrapWithColorTag(activeTab.getTag(), HILIGHT_COLOR), entries); + } + + client.setMenuEntries(entries); + } + } + + public void handleClick(MenuOptionClicked event) + { + if (isHidden()) + { + return; + } + + if (chatboxPanelManager.getCurrentInput() != null + && event.getMenuAction() != MenuAction.CANCEL + && !event.getMenuOption().equals(SCROLL_UP) + && !event.getMenuOption().equals(SCROLL_DOWN)) + { + chatboxPanelManager.close(); + } + + if (activeTab != null + && (event.getMenuOption().startsWith("View tab") || event.getMenuOption().equals("View all items"))) + { + activateTab(null); + } + else if (activeTab != null + && event.getWidgetId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() + && event.getMenuAction() == MenuAction.RUNELITE + && event.getMenuOption().startsWith(REMOVE_TAG)) + { + // Add "remove" menu entry to all items in bank while tab is selected + event.consume(); + final ItemComposition item = getItem(event.getActionParam()); + final int itemId = item.getId(); + tagManager.removeTag(itemId, activeTab.getTag()); + bankSearch.layoutBank(); // re-layout to filter the removed item out + } + else if (event.getMenuAction() == MenuAction.RUNELITE + && ((event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId() && event.getMenuOption().equals(TAG_INVENTORY)) + || (event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_EQUIPMENT.getId() && event.getMenuOption().equals(TAG_GEAR)))) + { + handleDeposit(event, event.getWidgetId() == WidgetInfo.BANK_DEPOSIT_INVENTORY.getId()); + } + else if (activeTab != null && ((event.getWidgetId() == WidgetInfo.BANK_EQUIPMENT_BUTTON.getId() && event.getMenuOption().equals(SHOW_WORN)) + || (event.getWidgetId() == WidgetInfo.BANK_SETTINGS_BUTTON.getId() && event.getMenuOption().equals(SHOW_SETTINGS)) + || (event.getWidgetId() == WidgetInfo.BANK_TUTORIAL_BUTTON.getId() && event.getMenuOption().equals(SHOW_TUTORIAL)))) + { + saveTab(); + } + } + + public void handleSearch() + { + if (activeTab != null) + { + activateTab(null); + // This ensures that when clicking Search when tab is selected, the search input is opened rather + // than client trying to close it first + client.setVar(VarClientStr.INPUT_TEXT, ""); + client.setVar(VarClientInt.INPUT_TYPE, 0); + } + } + + public void updateTabIfActive(final Collection tags) + { + if (activeTab != null && tags.contains(activeTab.getTag())) + { + openTag(activeTab.getTag()); + } + } + + public void handleDrag(boolean isDragging, boolean shiftDown) + { + if (isHidden()) + { + return; + } + + Widget draggedOn = client.getDraggedOnWidget(); + Widget draggedWidget = client.getDraggedWidget(); + + // Returning early or nulling the drag release listener has no effect. Hence, we need to + // null the draggedOnWidget instead. + if (draggedWidget.getId() == WidgetInfo.BANK_ITEM_CONTAINER.getId() && isActive() + && config.preventTagTabDrags()) + { + client.setDraggedOnWidget(null); + } + + if (!isDragging || draggedOn == null) + { + return; + } + + // is dragging widget and mouse button released + if (client.getMouseCurrentButton() == 0) + { + if (!isTabMenuActive() && draggedWidget.getItemId() > 0 && draggedWidget.getId() != parent.getId()) + { + // Tag an item dragged on a tag tab + if (draggedOn.getId() == parent.getId()) + { + tagManager.addTag(draggedWidget.getItemId(), draggedOn.getName(), shiftDown); + updateTabIfActive(Lists.newArrayList(Text.standardize(draggedOn.getName()))); + } + } + else if ((isTabMenuActive() && draggedWidget.getId() == draggedOn.getId() && draggedOn.getId() != parent.getId()) + || (parent.getId() == draggedOn.getId() && parent.getId() == draggedWidget.getId())) + { + // Reorder tag tabs + moveTagTab(draggedWidget, draggedOn); + } + } + else if (draggedWidget.getItemId() > 0) + { + MenuEntry[] entries = client.getMenuEntries(); + + if (entries.length > 0) + { + MenuEntry entry = entries[entries.length - 1]; + + if (draggedWidget.getItemId() > 0 && entry.getOption().equals(VIEW_TAB) && draggedOn.getId() != draggedWidget.getId()) + { + entry.setOption(TAG_SEARCH + Text.removeTags(entry.getTarget()) + (shiftDown ? VAR_TAG_SUFFIX : "")); + entry.setTarget(draggedWidget.getName()); + client.setMenuEntries(entries); + } + + if (entry.getOption().equals(SCROLL_UP)) + { + scrollTick(-1); + } + else if (entry.getOption().equals(SCROLL_DOWN)) + { + scrollTick(1); + } + } + } + } + + private void moveTagTab(final Widget source, final Widget dest) + { + if (Strings.isNullOrEmpty(dest.getName())) + { + return; + } + + if (client.getVar(Varbits.BANK_REARRANGE_MODE) == 0) + { + tabManager.swap(source.getName(), dest.getName()); + } + else + { + tabManager.insert(source.getName(), dest.getName()); + } + + tabManager.save(); + updateTabs(); + } + + private boolean isHidden() + { + Widget widget = client.getWidget(WidgetInfo.BANK_CONTAINER); + return !config.tabs() || widget == null || widget.isHidden(); + } + + private void addTabActions(Widget w) + { + w.setAction(1, VIEW_TAB); + w.setAction(2, CHANGE_ICON); + w.setAction(3, REMOVE_TAB); + w.setAction(4, EXPORT_TAB); + w.setAction(5, RENAME_TAB); + w.setOnOpListener((JavaScriptCallback) this::handleTagTab); + } + + private void addTabOptions(Widget w) + { + int clickmask = w.getClickMask(); + clickmask |= WidgetConfig.DRAG; + clickmask |= WidgetConfig.DRAG_ON; + w.setClickMask(clickmask); + w.setDragDeadTime(5); + w.setDragDeadZone(5); + w.setItemQuantity(10000); + w.setItemQuantityMode(ItemQuantityMode.NEVER); + } + + private void loadTab(String tag) + { + TagTab tagTab = tabManager.load(tag); + + if (tagTab.getBackground() == null) + { + Widget btn = createGraphic(ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), TabSprites.TAB_BACKGROUND.getSpriteId(), -1, TAB_WIDTH, TAB_HEIGHT, bounds.x, 1, true); + addTabActions(btn); + tagTab.setBackground(btn); + } + + if (tagTab.getIcon() == null) + { + Widget icon = createGraphic( + ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), + -1, + tagTab.getIconItemId(), + Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT, + bounds.x + 3, 1, + false); + addTabOptions(icon); + tagTab.setIcon(icon); + } + + if (tagTab.getMenu() == null) + { + Widget menu = createGraphic( + client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER), + ColorUtil.wrapWithColorTag(tagTab.getTag(), HILIGHT_COLOR), + -1, + tagTab.getIconItemId(), + BANK_ITEM_WIDTH, BANK_ITEM_HEIGHT, + BANK_ITEM_START_X, BANK_ITEM_START_Y, + true); + addTabActions(menu); + addTabOptions(menu); + if (activeTab != null && activeTab.getTag().equals(TAB_MENU_KEY)) + { + menu.setHidden(false); + } + else + { + menu.setHidden(true); + } + tagTab.setMenu(menu); + } + + tabManager.add(tagTab); + } + + private void deleteTab(String tag) + { + if (activeTab != null && activeTab.getTag().equals(tag)) + { + activateTab(null); + bankSearch.reset(true); + } + + tabManager.remove(tag); + tabManager.save(); + + updateBounds(); + scrollTab(0); + } + + private void renameTab(String oldTag) + { + chatboxPanelManager.openTextInput("Enter new tag name for tag \"" + oldTag + "\":") + .addCharValidator(FILTERED_CHARS) + .onDone((Consumer) (newTag) -> clientThread.invoke(() -> + { + if (!Strings.isNullOrEmpty(newTag) && !newTag.equalsIgnoreCase(oldTag)) + { + if (tabManager.find(newTag) == null) + { + TagTab tagTab = tabManager.find(oldTag); + tagTab.setTag(newTag); + + final String coloredName = ColorUtil.wrapWithColorTag(newTag, HILIGHT_COLOR); + tagTab.getIcon().setName(coloredName); + tagTab.getBackground().setName(coloredName); + tagTab.getMenu().setName(coloredName); + + tabManager.removeIcon(oldTag); + tabManager.setIcon(newTag, tagTab.getIconItemId() + ""); + + tabManager.save(); + tagManager.renameTag(oldTag, newTag); + + if (activeTab != null && activeTab.equals(tagTab)) + { + openTag(newTag); + } + } + else + { + chatboxPanelManager.openTextMenuInput("The specified bank tag already exists.") + .option("1. Merge into existing tag \"" + newTag + "\".", () -> + clientThread.invoke(() -> + { + tagManager.renameTag(oldTag, newTag); + final String activeTag = activeTab != null ? activeTab.getTag() : ""; + deleteTab(oldTag); + + if (activeTag.equals(oldTag)) + { + openTag(newTag); + } + }) + ) + .option("2. Choose a different name.", () -> + clientThread.invoke(() -> + renameTab(oldTag)) + ) + .build(); + } + } + })) + .build(); + } + + private void scrollTick(int direction) + { + // This ensures that dragging on scroll buttons do not scrolls too fast + if (startScroll.until(Instant.now(), ChronoUnit.MILLIS) >= SCROLL_TICK) + { + startScroll = Instant.now(); + scrollTab(direction); + } + } + + private void scrollTab(int direction) + { + maxTabs = (bounds.height - BUTTON_HEIGHT * 2 - MARGIN * 2) / TAB_HEIGHT; + + // prevent running into the incinerator + while (bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT * 2 + MARGIN > bounds.y + bounds.height) + { + --maxTabs; + } + + int proposedIndex = currentTabIndex + direction; + int numTabs = tabManager.size(); + + if (proposedIndex >= numTabs || proposedIndex < 0) + { + currentTabIndex = 0; + } + else if (numTabs - proposedIndex >= maxTabs) + { + currentTabIndex = proposedIndex; + } + else if (maxTabs < numTabs && numTabs - proposedIndex < maxTabs) + { + // Edge case when only 1 tab displays instead of up to maxTabs when one is deleted at the end of the list + currentTabIndex = proposedIndex; + scrollTab(-1); + } + + updateTabs(); + } + + private void activateTab(TagTab tagTab) + { + if (activeTab != null && activeTab.equals(tagTab)) + { + return; + } + + if (activeTab != null) + { + Widget tab = activeTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND.getSpriteId()); + tab.revalidate(); + activeTab = null; + } + + if (tagTab != null) + { + Widget tab = tagTab.getBackground(); + tab.setSpriteId(TabSprites.TAB_BACKGROUND_ACTIVE.getSpriteId()); + tab.revalidate(); + activeTab = tagTab; + } + + tagTabActive = false; + } + + private void updateBounds() + { + Widget itemContainer = client.getWidget(WidgetInfo.BANK_ITEM_CONTAINER); + if (itemContainer == null) + { + return; + } + + int height = itemContainer.getHeight(); + + // If player isn't using normal bank tabs + if (itemContainer.getRelativeY() == 0) + { + height -= (TAB_HEIGHT + MARGIN); + } + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height); + bounds.setLocation(MARGIN, TAB_HEIGHT + MARGIN); + + Widget incinerator = client.getWidget(WidgetInfo.BANK_INCINERATOR); + + if (incinerator != null && !incinerator.isHidden()) + { + incinerator.setOriginalHeight(INCINERATOR_HEIGHT); + incinerator.setOriginalWidth(INCINERATOR_WIDTH); + incinerator.setOriginalY(INCINERATOR_HEIGHT); + + Widget child = incinerator.getChild(0); + child.setOriginalHeight(INCINERATOR_HEIGHT); + child.setOriginalWidth(INCINERATOR_WIDTH); + child.setWidthMode(WidgetSizeMode.ABSOLUTE); + child.setHeightMode(WidgetSizeMode.ABSOLUTE); + child.setType(WidgetType.GRAPHIC); + child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); + incinerator.revalidate(); + + bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); + } + + if (upButton != null) + { + Point p = upButton.getCanvasLocation(); + canvasBounds.setBounds(p.getX(), p.getY() + BUTTON_HEIGHT, bounds.width, maxTabs * TAB_HEIGHT + maxTabs * MARGIN); + } + } + + private void updateTabs() + { + int y = bounds.y + MARGIN + BUTTON_HEIGHT; + + if (maxTabs >= tabManager.size()) + { + currentTabIndex = 0; + } + else + { + y -= (currentTabIndex * TAB_HEIGHT + currentTabIndex * MARGIN); + } + + int itemX = BANK_ITEM_START_X; + int itemY = BANK_ITEM_START_Y; + int rowIndex = 0; + + for (TagTab tab : tabManager.getTabs()) + { + updateWidget(tab.getBackground(), y); + updateWidget(tab.getIcon(), y + 4); + + // Edge case where item icon is 1 pixel out of bounds + tab.getIcon().setHidden(tab.getBackground().isHidden()); + + // Keep item widget shown while drag scrolling + if (client.getDraggedWidget() == tab.getIcon()) + { + tab.getIcon().setHidden(false); + } + + y += TAB_HEIGHT + MARGIN; + + Widget item = tab.getMenu(); + item.setOriginalX(itemX); + item.setOriginalY(itemY); + item.revalidate(); + + rowIndex++; + if (rowIndex == BANK_ITEMS_PER_ROW) + { + itemX = BANK_ITEM_START_X; + itemY += BANK_ITEM_Y_PADDING + BANK_ITEM_HEIGHT; + rowIndex = 0; + } + else + { + itemX += BANK_ITEM_X_PADDING + BANK_ITEM_WIDTH; + } + } + + boolean hidden = !(tabManager.size() > 0); + + upButton.setHidden(hidden); + upButton.setOriginalY(bounds.y); + upButton.revalidate(); + + downButton.setHidden(hidden); + downButton.setOriginalY(bounds.y + maxTabs * TAB_HEIGHT + MARGIN * maxTabs + BUTTON_HEIGHT + MARGIN); + downButton.revalidate(); + } + + private Widget createGraphic(Widget container, String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) + { + Widget widget = container.createChild(-1, WidgetType.GRAPHIC); + widget.setOriginalWidth(width); + widget.setOriginalHeight(height); + widget.setOriginalX(x); + widget.setOriginalY(y); + + widget.setSpriteId(spriteId); + + if (itemId > -1) + { + widget.setItemId(itemId); + widget.setItemQuantity(-1); + widget.setBorderType(1); + } + + if (hasListener) + { + widget.setOnOpListener(ScriptID.NULL); + widget.setHasListener(true); + } + + widget.setName(name); + widget.revalidate(); + + return widget; + } + + private Widget createGraphic(String name, int spriteId, int itemId, int width, int height, int x, int y, boolean hasListener) + { + return createGraphic(parent, name, spriteId, itemId, width, height, x, y, hasListener); + } + + private void updateWidget(Widget t, int y) + { + t.setOriginalY(y); + t.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT)); + t.revalidate(); + } + + private ItemComposition getItem(int idx) + { + ItemContainer bankContainer = client.getItemContainer(InventoryID.BANK); + Item item = bankContainer.getItem(idx); + return itemManager.getItemComposition(item.getId()); + } + + private void openTag(final String tag) + { + activateTab(tabManager.find(tag)); + tagTabActive = BankTagsPlugin.TAG_TABS_CONFIG.equals(tag); + bankSearch.reset(true); // clear search dialog & relayout bank for new tab. + + // When searching the button has a script on timer to detect search end, that will set the background back + // and remove the timer. However since we are going from a bank search to our fake search this will not remove + // the timer but instead re-add it and reset the background. So remove the timer and the background. This is the + // same as bankmain_search_setbutton. + Widget searchButtonBackground = client.getWidget(WidgetInfo.BANK_SEARCH_BUTTON_BACKGROUND); + searchButtonBackground.setOnTimerListener((Object[]) null); + searchButtonBackground.setSpriteId(SpriteID.EQUIPMENT_SLOT_TILE); + } + + private static MenuEntry[] createMenuEntry(MenuEntryAdded event, String option, String target, MenuEntry[] entries) + { + final MenuEntry entry = new MenuEntry(); + entry.setParam0(event.getActionParam0()); + entry.setParam1(event.getActionParam1()); + entry.setTarget(target); + entry.setOption(option); + entry.setType(MenuAction.RUNELITE.getId()); + entry.setIdentifier(event.getIdentifier()); + entries = Arrays.copyOf(entries, entries.length + 1); + entries[entries.length - 1] = entry; + return entries; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java new file mode 100644 index 0000000000..f3feed8511 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import com.google.common.base.MoreObjects; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; +import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_TABS_CONFIG; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.math.NumberUtils; + +@Singleton +class TabManager +{ + @Getter + private final List tabs = new ArrayList<>(); + private final ConfigManager configManager; + + @Inject + private TabManager(ConfigManager configManager) + { + this.configManager = configManager; + } + + void add(TagTab tagTab) + { + if (!contains(tagTab.getTag())) + { + tabs.add(tagTab); + } + } + + void clear() + { + tabs.forEach(t -> t.setHidden(true)); + tabs.clear(); + } + + TagTab find(String tag) + { + Optional first = tabs.stream().filter(t -> t.getTag().equals(Text.standardize(tag))).findAny(); + return first.orElse(null); + } + + List getAllTabs() + { + return Text.fromCSV(MoreObjects.firstNonNull(configManager.getConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG), "")); + } + + TagTab load(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab == null) + { + tag = Text.standardize(tag); + String item = configManager.getConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); + int itemid = NumberUtils.toInt(item, ItemID.SPADE); + tagTab = new TagTab(itemid, tag); + } + + return tagTab; + } + + void swap(String tagToMove, String tagDestination) + { + tagToMove = Text.standardize(tagToMove); + tagDestination = Text.standardize(tagDestination); + + if (contains(tagToMove) && contains(tagDestination)) + { + Collections.swap(tabs, indexOf(tagToMove), indexOf(tagDestination)); + } + } + + void insert(String tagToMove, String tagDestination) + { + tagToMove = Text.standardize(tagToMove); + tagDestination = Text.standardize(tagDestination); + + if (contains(tagToMove) && contains(tagDestination)) + { + tabs.add(indexOf(tagDestination), tabs.remove(indexOf(tagToMove))); + } + } + + void remove(String tag) + { + TagTab tagTab = find(tag); + + if (tagTab != null) + { + tagTab.setHidden(true); + tabs.remove(tagTab); + removeIcon(tag); + } + } + + void save() + { + String tags = Text.toCSV(tabs.stream().map(TagTab::getTag).collect(Collectors.toList())); + configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); + } + + void removeIcon(final String tag) + { + configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag)); + } + + void setIcon(final String tag, final String icon) + { + configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon); + } + + int size() + { + return tabs.size(); + } + + private boolean contains(String tag) + { + return tabs.stream().anyMatch(t -> t.getTag().equals(tag)); + } + + private int indexOf(TagTab tagTab) + { + return tabs.indexOf(tagTab); + } + + private int indexOf(String tag) + { + return indexOf(find(tag)); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java new file mode 100644 index 0000000000..20f9d0dfb6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.client.game.SpriteOverride; + +@RequiredArgsConstructor +public enum TabSprites implements SpriteOverride +{ + INCINERATOR(-200, "incinerator.png"), + TAB_BACKGROUND(-201, "tag-tab.png"), + TAB_BACKGROUND_ACTIVE(-202, "tag-tab-active.png"), + UP_ARROW(-203, "up-arrow.png"), + DOWN_ARROW(-204, "down-arrow.png"), + NEW_TAB(-205, "new-tab.png"); + + @Getter + private final int spriteId; + + @Getter + private final String fileName; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java new file mode 100644 index 0000000000..88ef36537b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018, Ron Young + * 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.banktags.tabs; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.runelite.api.widgets.Widget; + +@Data +@EqualsAndHashCode(of = "tag") +public class TagTab +{ + private String tag; + private int iconItemId; + private Widget background; + private Widget icon; + private Widget menu; + + TagTab(int iconItemId, String tag) + { + this.iconItemId = iconItemId; + this.tag = tag; + } + + void setHidden(boolean hide) + { + if (background != null) + { + background.setHidden(hide); + } + + if (icon != null) + { + icon.setHidden(hide); + } + + if (menu != null) + { + menu.setHidden(hide); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java index 8305c40bde..9cc102e2b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -53,8 +53,8 @@ import net.runelite.api.events.MenuOptionClicked; 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.TO_CHILD; -import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getChildFromID; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; import net.runelite.client.callback.ClientThread; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; @@ -203,8 +203,8 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener return; } - final int groupId = TO_GROUP(entry.getParam1()); - final int childId = TO_CHILD(entry.getParam1()); + final int groupId = getGroupFromID(entry.getParam1()); + final int childId = getChildFromID(entry.getParam1()); if (groupId != WidgetInfo.CHATBOX.getGroupId()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingConfig.java new file mode 100644 index 0000000000..a1281a8e89 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018, Joris K + * Copyright (c) 2018, Lasse + * 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.cooking; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("cooking") +public interface CookingConfig extends Config +{ + @ConfigItem( + position = 1, + keyName = "statTimeout", + name = "Reset stats", + description = "Configures the time until the session resets and the overlay is hidden (0 = Disable feature)" + ) + @Units(Units.MINUTES) + default int statTimeout() + { + return 5; + } + + @ConfigItem( + position = 2, + keyName = "fermentTimer", + name = "Show wine ferment timer", + description = "Configures if the timer before wines are fermented is shown" + ) + default boolean fermentTimer() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java new file mode 100644 index 0000000000..6998e96eef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018, Joris K + * Copyright (c) 2018, Lasse + * 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.cooking; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.text.DecimalFormat; +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import static net.runelite.api.AnimationID.COOKING_FIRE; +import static net.runelite.api.AnimationID.COOKING_RANGE; +import net.runelite.api.Client; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.api.Skill; +import net.runelite.client.plugins.xptracker.XpTrackerService; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +class CookingOverlay extends OverlayPanel +{ + private static final int COOK_TIMEOUT = 3; + private static final DecimalFormat FORMAT = new DecimalFormat("#.#"); + static final String COOKING_RESET = "Reset"; + + private final Client client; + private final CookingPlugin plugin; + private final XpTrackerService xpTrackerService; + + @Inject + private CookingOverlay(Client client, CookingPlugin plugin, XpTrackerService xpTrackerService) + { + super(plugin); + setPosition(OverlayPosition.TOP_LEFT); + this.client = client; + this.plugin = plugin; + this.xpTrackerService = xpTrackerService; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Cooking overlay")); + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, COOKING_RESET, "Cooking overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + CookingSession session = plugin.getSession(); + if (session == null) + { + return null; + } + + if (isCooking() || Duration.between(session.getLastCookingAction(), Instant.now()).getSeconds() < COOK_TIMEOUT) + { + panelComponent.getChildren().add(TitleComponent.builder() + .text("Cooking") + .color(Color.GREEN) + .build()); + } + else + { + panelComponent.getChildren().add(TitleComponent.builder() + .text("NOT cooking") + .color(Color.RED) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Cooked:") + .right(session.getCookAmount() + (session.getCookAmount() >= 1 ? " (" + xpTrackerService.getActionsHr(Skill.COOKING) + "/hr)" : "")) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Burnt:") + .right(session.getBurnAmount() + (session.getBurnAmount() >= 1 ? " (" + FORMAT.format(session.getBurntPercentage()) + "%)" : "")) + .build()); + + return super.render(graphics); + } + + private boolean isCooking() + { + switch (client.getLocalPlayer().getAnimation()) + { + case COOKING_FIRE: + case COOKING_RANGE: + return true; + default: + return false; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingPlugin.java new file mode 100644 index 0000000000..a75aa8c2eb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingPlugin.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2018, Joris K + * Copyright (c) 2018, Lasse + * 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.cooking; + +import com.google.inject.Provides; +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GraphicID; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.Player; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.GraphicChanged; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDependency; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.xptracker.XpTrackerPlugin; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; + +@PluginDescriptor( + name = "Cooking", + description = "Show cooking statistics", + tags = {"overlay", "skilling", "cook"} +) +@PluginDependency(XpTrackerPlugin.class) +public class CookingPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private CookingConfig config; + + @Inject + private CookingOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private ItemManager itemManager; + + @Getter(AccessLevel.PACKAGE) + private CookingSession session; + + @Provides + CookingConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(CookingConfig.class); + } + + @Override + protected void startUp() throws Exception + { + session = null; + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + infoBoxManager.removeIf(FermentTimer.class::isInstance); + overlayManager.remove(overlay); + session = null; + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked) + { + OverlayMenuEntry overlayMenuEntry = overlayMenuClicked.getEntry(); + if (overlayMenuEntry.getMenuAction() == MenuAction.RUNELITE_OVERLAY + && overlayMenuClicked.getEntry().getOption().equals(CookingOverlay.COOKING_RESET) + && overlayMenuClicked.getOverlay() == overlay) + { + session = null; + } + } + + @Subscribe + public void onGameTick(GameTick gameTick) + { + if (session == null || config.statTimeout() == 0) + { + return; + } + + Duration statTimeout = Duration.ofMinutes(config.statTimeout()); + Duration sinceCut = Duration.between(session.getLastCookingAction(), Instant.now()); + + if (sinceCut.compareTo(statTimeout) >= 0) + { + session = null; + } + } + + @Subscribe + public void onGraphicChanged(GraphicChanged graphicChanged) + { + Player player = client.getLocalPlayer(); + + if (graphicChanged.getActor() != player) + { + return; + } + + if (player.getGraphic() == GraphicID.WINE_MAKE && config.fermentTimer()) + { + Optional fermentTimerOpt = infoBoxManager.getInfoBoxes().stream() + .filter(FermentTimer.class::isInstance) + .map(FermentTimer.class::cast) + .findAny(); + + if (fermentTimerOpt.isPresent()) + { + FermentTimer fermentTimer = fermentTimerOpt.get(); + fermentTimer.reset(); + } + else + { + FermentTimer fermentTimer = new FermentTimer(itemManager.getImage(ItemID.JUG_OF_WINE), this); + infoBoxManager.addInfoBox(fermentTimer); + } + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.SPAM) + { + return; + } + + final String message = event.getMessage(); + + if (message.startsWith("You successfully cook") + || message.startsWith("You successfully bake") + || message.startsWith("You manage to cook") + || message.startsWith("You roast a") + || message.startsWith("You cook") + || message.startsWith("You dry a piece of meat")) + { + if (session == null) + { + session = new CookingSession(); + } + + session.updateLastCookingAction(); + session.increaseCookAmount(); + + } + else if (message.startsWith("You accidentally burn") + || message.startsWith("You accidentally spoil")) + { + if (session == null) + { + session = new CookingSession(); + } + + session.updateLastCookingAction(); + session.increaseBurnAmount(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingSession.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingSession.java new file mode 100644 index 0000000000..292e4a3584 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingSession.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Joris K + * Copyright (c) 2018, Lasse + * 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.cooking; + +import java.time.Instant; +import lombok.AccessLevel; +import lombok.Getter; + +class CookingSession +{ + @Getter(AccessLevel.PACKAGE) + private Instant lastCookingAction; + @Getter(AccessLevel.PACKAGE) + private int cookAmount; + @Getter(AccessLevel.PACKAGE) + private int burnAmount; + + void updateLastCookingAction() + { + this.lastCookingAction = Instant.now(); + } + + void increaseCookAmount() + { + this.cookAmount++; + } + + void increaseBurnAmount() + { + this.burnAmount++; + } + + double getBurntPercentage() + { + return ((double) getBurnAmount() / (getCookAmount() + getBurnAmount())) * 100; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/FermentTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/FermentTimer.java new file mode 100644 index 0000000000..84d7033f57 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/FermentTimer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019, Adam + * 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.cooking; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.time.Instant; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.overlay.infobox.InfoBox; + +final class FermentTimer extends InfoBox +{ + private static final Duration FERMENT_TIME = Duration.ofMillis(13_800); + + private Instant fermentTime; + + FermentTimer(BufferedImage image, Plugin plugin) + { + super(image, plugin); + reset(); + } + + @Override + public String getText() + { + int seconds = timeUntilFerment(); + return Integer.toString(seconds); + } + + @Override + public Color getTextColor() + { + int seconds = timeUntilFerment(); + return seconds <= 3 ? Color.RED : Color.WHITE; + } + + @Override + public boolean cull() + { + int seconds = timeUntilFerment(); + return seconds <= 0; + } + + void reset() + { + fermentTime = Instant.now().plus(FERMENT_TIME); + } + + private int timeUntilFerment() + { + return (int) Duration.between(Instant.now(), fermentTime).getSeconds(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java index 5956baed72..678dd7a4f3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java @@ -63,8 +63,8 @@ import net.runelite.api.events.ScriptPostFired; 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.TO_CHILD; -import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getChildFromID; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; 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 += " - " + TO_GROUP(id) + "." + TO_CHILD(id); + output += " - " + getGroupFromID(id) + "." + getChildFromID(id); if (source.getIndex() != -1) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java index 986ebb996a..61953cc628 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java @@ -59,8 +59,8 @@ import net.runelite.api.Client; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.SpriteID; -import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; -import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getChildFromID; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; 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.TO_GROUP(picker.getId()), WidgetInfo.TO_CHILD(picker.getId()), picker.getIndex()); + log.info("Picker is {}.{} [{}]", WidgetInfo.getGroupFromID(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.TO_GROUP(entry.getParam1()) + "." + WidgetInfo.TO_CHILD(entry.getParam1()); + String name = WidgetInfo.getGroupFromID(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 = TO_GROUP(id) + "." + TO_CHILD(id); + String str = getGroupFromID(id) + "." + getChildFromID(id); if (widget.getIndex() != -1) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java index 976082dcd7..b31a7b2731 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/examine/ExaminePlugin.java @@ -45,8 +45,8 @@ import net.runelite.api.widgets.Widget; 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.TO_CHILD; -import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getChildFromID; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; import net.runelite.api.widgets.WidgetItem; import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatMessageBuilder; @@ -123,8 +123,8 @@ public class ExaminePlugin extends Plugin id = event.getId(); int widgetId = event.getWidgetId(); - int widgetGroup = TO_GROUP(widgetId); - int widgetChild = TO_CHILD(widgetId); + int widgetGroup = getGroupFromID(widgetId); + int widgetChild = getChildFromID(widgetId); Widget widget = client.getWidget(widgetGroup, widgetChild); WidgetItem widgetItem = widget.getWidgetItem(event.getActionParam()); quantity = widgetItem != null && widgetItem.getId() >= 0 ? widgetItem.getQuantity() : 1; @@ -266,8 +266,8 @@ public class ExaminePlugin extends Plugin private int[] findItemFromWidget(int widgetId, int actionParam) { - int widgetGroup = TO_GROUP(widgetId); - int widgetChild = TO_CHILD(widgetId); + int widgetGroup = getGroupFromID(widgetId); + int widgetChild = getChildFromID(widgetId); Widget widget = client.getWidget(widgetGroup, widgetChild); if (widget == null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/PrayerType.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/PrayerType.java new file mode 100644 index 0000000000..4e7efb793c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/PrayerType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018, Adam + * 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.experiencedrop; + +enum PrayerType +{ + MELEE, + RANGE, + MAGIC; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropConfig.java new file mode 100644 index 0000000000..568a656ae2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropConfig.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018, Cameron + * 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.experiencedrop; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("xpdrop") +public interface XpDropConfig extends Config +{ + @ConfigItem( + keyName = "hideSkillIcons", + name = "Hide skill icons", + description = "Configure if XP drops will show their respective skill icons", + position = 0 + ) + default boolean hideSkillIcons() + { + return false; + } + + @ConfigItem( + keyName = "meleePrayerColor", + name = "Melee Prayer Color", + description = "XP drop color when a melee prayer is active", + position = 1 + ) + default Color getMeleePrayerColor() + { + return new Color(0x15, 0x80, 0xAD); + } + + @ConfigItem( + keyName = "rangePrayerColor", + name = "Range Prayer Color", + description = "XP drop color when a range prayer is active", + position = 2 + ) + default Color getRangePrayerColor() + { + return new Color(0x15, 0x80, 0xAD); + } + + @ConfigItem( + keyName = "magePrayerColor", + name = "Mage Prayer Color", + description = "XP drop color when a mage prayer is active", + position = 3 + ) + default Color getMagePrayerColor() + { + return new Color(0x15, 0x80, 0xAD); + } + + @ConfigItem( + keyName = "fakeXpDropDelay", + name = "Fake Xp Drop delay", + description = "Configures how many ticks should pass between fake XP drops, 0 to disable", + position = 4 + ) + @Units(Units.TICKS) + default int fakeXpDropDelay() + { + return 0; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java new file mode 100644 index 0000000000..c3b9523f21 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpDropPlugin.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2018, Cameron , SoyChai + * 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.experiencedrop; + +import com.google.inject.Provides; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.EnumComposition; +import net.runelite.api.EnumID; +import static net.runelite.api.ScriptID.XPDROPS_SETDROPSIZE; +import static net.runelite.api.ScriptID.XPDROP_DISABLED; +import net.runelite.api.Skill; +import net.runelite.api.SpriteID; +import net.runelite.api.Varbits; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.ScriptPreFired; +import net.runelite.api.events.StatChanged; +import net.runelite.api.widgets.Widget; +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 = "XP Drop", + description = "Enable customization of the way XP drops are displayed", + tags = {"experience", "levels", "tick", "prayer", "xpdrop"} +) +public class XpDropPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private XpDropConfig config; + + private int tickCounter = 0; + private int previousExpGained; + private boolean hasDropped = false; + private boolean correctPrayer; + private Skill lastSkill = null; + private final Map previousSkillExpTable = new EnumMap<>(Skill.class); + + @Provides + XpDropConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(XpDropConfig.class); + } + + @Subscribe + public void onScriptPreFired(ScriptPreFired scriptPreFired) + { + if (scriptPreFired.getScriptId() == XPDROPS_SETDROPSIZE) + { + final int[] intStack = client.getIntStack(); + final int intStackSize = client.getIntStackSize(); + // This runs prior to the proc being invoked, so the arguments are still on the stack. + // Grab the first argument to the script. + final int widgetId = intStack[intStackSize - 4]; + processXpDrop(widgetId); + } + } + + private void processXpDrop(int widgetId) + { + final Widget xpdrop = client.getWidget(widgetId); + final Widget[] children = xpdrop.getChildren(); + // child 0 is the xpdrop text, everything else are sprite ids for skills + final Widget text = children[0]; + + PrayerType prayer = getActivePrayerType(); + if (prayer == null) + { + hideSkillIcons(xpdrop); + resetTextColor(text); + return; + } + + final IntStream spriteIDs = + Arrays.stream(children) + .skip(1) // skip text + .filter(Objects::nonNull) + .mapToInt(Widget::getSpriteId); + + int color = 0; + + switch (prayer) + { + case MELEE: + if (correctPrayer || spriteIDs.anyMatch(id -> + id == SpriteID.SKILL_ATTACK || id == SpriteID.SKILL_STRENGTH || id == SpriteID.SKILL_DEFENCE)) + { + color = config.getMeleePrayerColor().getRGB(); + correctPrayer = true; + } + break; + case RANGE: + if (correctPrayer || spriteIDs.anyMatch(id -> id == SpriteID.SKILL_RANGED)) + { + color = config.getRangePrayerColor().getRGB(); + correctPrayer = true; + } + break; + case MAGIC: + if (correctPrayer || spriteIDs.anyMatch(id -> id == SpriteID.SKILL_MAGIC)) + { + color = config.getMagePrayerColor().getRGB(); + correctPrayer = true; + } + break; + } + + if (color != 0) + { + text.setTextColor(color); + } + else + { + resetTextColor(text); + } + + hideSkillIcons(xpdrop); + } + + private void resetTextColor(Widget widget) + { + EnumComposition colorEnum = client.getEnum(EnumID.XPDROP_COLORS); + int defaultColorId = client.getVar(Varbits.EXPERIENCE_DROP_COLOR); + int color = colorEnum.getIntValue(defaultColorId); + widget.setTextColor(color); + } + + private void hideSkillIcons(Widget xpdrop) + { + if (config.hideSkillIcons()) + { + Widget[] children = xpdrop.getChildren(); + // keep only text + Arrays.fill(children, 1, children.length, null); + } + } + + private PrayerType getActivePrayerType() + { + for (XpPrayer prayer : XpPrayer.values()) + { + if (client.isPrayerActive(prayer.getPrayer())) + { + return prayer.getType(); + } + } + return null; + } + + @Subscribe + public void onGameTick(GameTick tick) + { + correctPrayer = false; + + final int fakeTickDelay = config.fakeXpDropDelay(); + + if (fakeTickDelay == 0 || lastSkill == null) + { + return; + } + + // If an xp drop was created this tick, reset the counter + if (hasDropped) + { + hasDropped = false; + tickCounter = 0; + return; + } + + if (++tickCounter % fakeTickDelay != 0) + { + return; + } + + client.runScript(XPDROP_DISABLED, lastSkill.ordinal(), previousExpGained); + } + + @Subscribe + public void onStatChanged(StatChanged statChanged) + { + final Skill skill = statChanged.getSkill(); + final int xp = statChanged.getXp(); + + lastSkill = skill; + + Integer previous = previousSkillExpTable.put(skill, xp); + if (previous != null) + { + previousExpGained = xp - previous; + hasDropped = true; + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpPrayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpPrayer.java new file mode 100644 index 0000000000..61ff0b088c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/experiencedrop/XpPrayer.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018, Adam + * 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.experiencedrop; + +import lombok.Getter; +import net.runelite.api.Prayer; +import static net.runelite.api.Prayer.*; +import static net.runelite.client.plugins.experiencedrop.PrayerType.MAGIC; +import static net.runelite.client.plugins.experiencedrop.PrayerType.MELEE; +import static net.runelite.client.plugins.experiencedrop.PrayerType.RANGE; + +enum XpPrayer +{ + XP_BURST_OF_STRENGTH(BURST_OF_STRENGTH, MELEE), + XP_CLARITY_OF_THOUGHT(CLARITY_OF_THOUGHT, MELEE), + XP_SHARP_EYE(SHARP_EYE, RANGE), + XP_MYSTIC_WILL(MYSTIC_WILL, MAGIC), + XP_SUPERHUMAN_STRENGTH(SUPERHUMAN_STRENGTH, MELEE), + XP_IMPROVED_REFLEXES(IMPROVED_REFLEXES, MELEE), + XP_HAWK_EYE(HAWK_EYE, RANGE), + XP_MYSTIC_LORE(MYSTIC_LORE, MAGIC), + XP_ULTIMATE_STRENGTH(ULTIMATE_STRENGTH, MELEE), + XP_INCREDIBLE_REFLEXES(INCREDIBLE_REFLEXES, MELEE), + XP_EAGLE_EYE(EAGLE_EYE, RANGE), + XP_MYSTIC_MIGHT(MYSTIC_MIGHT, MAGIC), + XP_CHIVALRY(CHIVALRY, MELEE), + XP_PIETY(PIETY, MELEE), + XP_RIGOUR(RIGOUR, RANGE), + XP_AUGURY(AUGURY, MAGIC); + + @Getter + private final Prayer prayer; + @Getter + private final PrayerType type; + + XpPrayer(Prayer prayer, PrayerType type) + { + this.prayer = prayer; + this.type = type; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java new file mode 100644 index 0000000000..4c9153f42a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2017, Seth + * 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.fishing; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Units; + +@ConfigGroup("fishing") +public interface FishingConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "onlyCurrent", + name = "Display only currently fished fish", + description = "Configures whether only current fished fish's fishing spots are displayed" + ) + default boolean onlyCurrentSpot() + { + return false; + } + + @ConfigItem( + position = 1, + keyName = "showTiles", + name = "Display spot tiles", + description = "Configures whether tiles for fishing spots are highlighted" + ) + default boolean showSpotTiles() + { + return true; + } + + @ConfigItem( + position = 2, + keyName = "showIcons", + name = "Display spot icons", + description = "Configures whether icons for fishing spots are displayed" + ) + default boolean showSpotIcons() + { + return true; + } + + @ConfigItem( + position = 3, + keyName = "showNames", + name = "Display spot names", + description = "Configures whether names for fishing spots are displayed" + ) + default boolean showSpotNames() + { + return false; + } + + @ConfigItem( + keyName = "overlayColor", + name = "Overlay Color", + description = "Color of overlays", + position = 4 + ) + default Color getOverlayColor() + { + return Color.CYAN; + } + + @ConfigItem( + keyName = "minnowsOverlayColor", + name = "Minnows Overlay Color", + description = "Color of overlays for Minnows", + position = 5 + ) + default Color getMinnowsOverlayColor() + { + return Color.RED; + } + + @ConfigItem( + keyName = "aerialOverlayColor", + name = "Aerial Overlay Color", + description = "Color of overlays when 1-tick aerial fishing", + position = 6 + ) + default Color getAerialOverlayColor() + { + return Color.GREEN; + } + + @ConfigItem( + position = 7, + keyName = "statTimeout", + name = "Reset stats", + description = "The time until fishing session data is reset in minutes." + ) + @Units(Units.MINUTES) + default int statTimeout() + { + return 5; + } + + @ConfigItem( + position = 8, + keyName = "showFishingStats", + name = "Show Fishing session stats", + description = "Display the fishing session stats." + ) + default boolean showFishingStats() + { + return true; + } + + @ConfigItem( + position = 9, + keyName = "showMinnowOverlay", + name = "Show Minnow Movement overlay", + description = "Display the minnow progress pie overlay." + ) + default boolean showMinnowOverlay() + { + return true; + } + + @ConfigItem( + position = 10, + keyName = "trawlerNotification", + name = "Trawler activity notification", + description = "Send a notification when fishing trawler activity drops below 15%." + ) + default boolean trawlerNotification() + { + return true; + } + + @ConfigItem( + position = 11, + keyName = "trawlerTimer", + name = "Trawler timer in MM:SS", + description = "Trawler Timer will display a more accurate timer in MM:SS format." + ) + default boolean trawlerTimer() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingOverlay.java new file mode 100644 index 0000000000..43853f95c7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingOverlay.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2017, Seth + * 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.fishing; + +import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.util.Set; +import javax.inject.Inject; +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.GraphicID; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; +import net.runelite.api.Skill; +import net.runelite.client.plugins.xptracker.XpTrackerService; +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +class FishingOverlay extends OverlayPanel +{ + private static final String FISHING_SPOT = "Fishing spot"; + static final String FISHING_RESET = "Reset"; + + private static final Set FISHING_ANIMATIONS = ImmutableSet.of( + AnimationID.FISHING_BARBTAIL_HARPOON, + AnimationID.FISHING_BAREHAND, + AnimationID.FISHING_BAREHAND_CAUGHT_SHARK_1, + AnimationID.FISHING_BAREHAND_CAUGHT_SHARK_2, + AnimationID.FISHING_BAREHAND_CAUGHT_SWORDFISH_1, + AnimationID.FISHING_BAREHAND_CAUGHT_SWORDFISH_2, + AnimationID.FISHING_BAREHAND_CAUGHT_TUNA_1, + AnimationID.FISHING_BAREHAND_CAUGHT_TUNA_2, + AnimationID.FISHING_BAREHAND_WINDUP_1, + AnimationID.FISHING_BAREHAND_WINDUP_2, + AnimationID.FISHING_BIG_NET, + AnimationID.FISHING_CAGE, + AnimationID.FISHING_CRYSTAL_HARPOON, + AnimationID.FISHING_DRAGON_HARPOON, + AnimationID.FISHING_HARPOON, + AnimationID.FISHING_INFERNAL_HARPOON, + AnimationID.FISHING_TRAILBLAZER_HARPOON, + AnimationID.FISHING_TRAILBLAZER_HARPOON_2, + AnimationID.FISHING_KARAMBWAN, + AnimationID.FISHING_NET, + AnimationID.FISHING_OILY_ROD, + AnimationID.FISHING_POLE_CAST, + AnimationID.FISHING_PEARL_ROD, + AnimationID.FISHING_PEARL_FLY_ROD, + AnimationID.FISHING_PEARL_BARBARIAN_ROD, + AnimationID.FISHING_PEARL_ROD_2, + AnimationID.FISHING_PEARL_FLY_ROD_2, + AnimationID.FISHING_PEARL_BARBARIAN_ROD_2, + AnimationID.FISHING_PEARL_OILY_ROD); + + private final Client client; + private final FishingPlugin plugin; + private final FishingConfig config; + private final XpTrackerService xpTrackerService; + + @Inject + public FishingOverlay(Client client, FishingPlugin plugin, FishingConfig config, XpTrackerService xpTrackerService) + { + super(plugin); + setPosition(OverlayPosition.TOP_LEFT); + this.client = client; + this.plugin = plugin; + this.config = config; + this.xpTrackerService = xpTrackerService; + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Fishing overlay")); + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, FISHING_RESET, "Fishing overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!config.showFishingStats() || plugin.getSession().getLastFishCaught() == null) + { + return null; + } + + if (client.getLocalPlayer().getInteracting() != null + && client.getLocalPlayer().getInteracting().getName().contains(FISHING_SPOT) + && client.getLocalPlayer().getInteracting().getGraphic() != GraphicID.FLYING_FISH + && FISHING_ANIMATIONS.contains(client.getLocalPlayer().getAnimation())) + { + panelComponent.getChildren().add(TitleComponent.builder() + .text("Fishing") + .color(Color.GREEN) + .build()); + } + else + { + panelComponent.getChildren().add(TitleComponent.builder() + .text("NOT fishing") + .color(Color.RED) + .build()); + } + + int actions = xpTrackerService.getActions(Skill.FISHING); + if (actions > 0) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Caught fish:") + .right(Integer.toString(actions)) + .build()); + + if (actions > 2) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("Fish/hr:") + .right(Integer.toString(xpTrackerService.getActionsHr(Skill.FISHING))) + .build()); + } + } + + return super.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java new file mode 100644 index 0000000000..65257adeb8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2017, Seth + * Copyright (c) 2018, Levi + * 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.fishing; + +import com.google.inject.Provides; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; +import net.runelite.api.NPC; +import net.runelite.api.Varbits; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.NpcSpawned; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.OverlayMenuClicked; +import net.runelite.client.game.FishingSpot; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDependency; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.xptracker.XpTrackerPlugin; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; + +@PluginDescriptor( + name = "Fishing", + description = "Show fishing stats and mark fishing spots", + tags = {"overlay", "skilling"} +) +@PluginDependency(XpTrackerPlugin.class) +@Singleton +@Slf4j +public class FishingPlugin extends Plugin +{ + private static final int TRAWLER_SHIP_REGION_NORMAL = 7499; + private static final int TRAWLER_SHIP_REGION_SINKING = 8011; + private static final int TRAWLER_TIME_LIMIT_IN_SECONDS = 614; + private static final int TRAWLER_ACTIVITY_THRESHOLD = Math.round(0.15f * 255); + + private Instant trawlerStartTime; + + @Getter(AccessLevel.PACKAGE) + private final FishingSession session = new FishingSession(); + + @Getter(AccessLevel.PACKAGE) + private final Map minnowSpots = new HashMap<>(); + + @Getter(AccessLevel.PACKAGE) + private final List fishingSpots = new ArrayList<>(); + + @Getter(AccessLevel.PACKAGE) + private FishingSpot currentSpot; + + @Inject + private Client client; + + @Inject + private Notifier notifier; + + @Inject + private OverlayManager overlayManager; + + @Inject + private FishingConfig config; + + @Inject + private FishingOverlay overlay; + + @Inject + private FishingSpotOverlay spotOverlay; + + @Inject + private FishingSpotMinimapOverlay fishingSpotMinimapOverlay; + + private boolean trawlerNotificationSent; + + @Provides + FishingConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(FishingConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + overlayManager.add(spotOverlay); + overlayManager.add(fishingSpotMinimapOverlay); + } + + @Override + protected void shutDown() throws Exception + { + spotOverlay.setHidden(true); + fishingSpotMinimapOverlay.setHidden(true); + overlayManager.remove(overlay); + overlayManager.remove(spotOverlay); + overlayManager.remove(fishingSpotMinimapOverlay); + fishingSpots.clear(); + minnowSpots.clear(); + trawlerNotificationSent = false; + currentSpot = null; + trawlerStartTime = null; + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + GameState gameState = gameStateChanged.getGameState(); + if (gameState == GameState.CONNECTION_LOST || gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING) + { + fishingSpots.clear(); + minnowSpots.clear(); + } + } + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked) + { + OverlayMenuEntry overlayMenuEntry = overlayMenuClicked.getEntry(); + if (overlayMenuEntry.getMenuAction() == MenuAction.RUNELITE_OVERLAY + && overlayMenuClicked.getEntry().getOption().equals(FishingOverlay.FISHING_RESET) + && overlayMenuClicked.getOverlay() == overlay) + { + session.setLastFishCaught(null); + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY) + && event.getItemContainer() != client.getItemContainer(InventoryID.EQUIPMENT)) + { + return; + } + + final boolean showOverlays = session.getLastFishCaught() != null + || canPlayerFish(client.getItemContainer(InventoryID.INVENTORY)) + || canPlayerFish(client.getItemContainer(InventoryID.EQUIPMENT)); + + if (!showOverlays) + { + currentSpot = null; + } + + spotOverlay.setHidden(!showOverlays); + fishingSpotMinimapOverlay.setHidden(!showOverlays); + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (event.getType() != ChatMessageType.SPAM) + { + return; + } + + if (event.getMessage().contains("You catch a") || event.getMessage().contains("You catch some") || + event.getMessage().equals("Your cormorant returns with its catch.")) + { + session.setLastFishCaught(Instant.now()); + spotOverlay.setHidden(false); + fishingSpotMinimapOverlay.setHidden(false); + } + } + + @Subscribe + public void onInteractingChanged(InteractingChanged event) + { + if (event.getSource() != client.getLocalPlayer()) + { + return; + } + + final Actor target = event.getTarget(); + + if (!(target instanceof NPC)) + { + return; + } + + final NPC npc = (NPC) target; + FishingSpot spot = FishingSpot.findSpot(npc.getId()); + + if (spot == null) + { + return; + } + + currentSpot = spot; + } + + private boolean canPlayerFish(final ItemContainer itemContainer) + { + if (itemContainer == null) + { + return false; + } + + for (Item item : itemContainer.getItems()) + { + switch (item.getId()) + { + case ItemID.DRAGON_HARPOON: + case ItemID.INFERNAL_HARPOON: + case ItemID.INFERNAL_HARPOON_UNCHARGED: + case ItemID.HARPOON: + case ItemID.BARBTAIL_HARPOON: + case ItemID.BIG_FISHING_NET: + case ItemID.SMALL_FISHING_NET: + case ItemID.SMALL_FISHING_NET_6209: + case ItemID.FISHING_ROD: + case ItemID.FLY_FISHING_ROD: + case ItemID.PEARL_BARBARIAN_ROD: + case ItemID.PEARL_FISHING_ROD: + case ItemID.PEARL_FLY_FISHING_ROD: + case ItemID.BARBARIAN_ROD: + case ItemID.OILY_FISHING_ROD: + case ItemID.LOBSTER_POT: + case ItemID.KARAMBWAN_VESSEL: + case ItemID.KARAMBWAN_VESSEL_3159: + case ItemID.CORMORANTS_GLOVE: + case ItemID.CORMORANTS_GLOVE_22817: + case ItemID.TRAILBLAZER_HARPOON: + case ItemID.TRAILBLAZER_HARPOON_25114: + case ItemID.CRYSTAL_HARPOON: + case ItemID.CRYSTAL_HARPOON_23864: + case ItemID.CRYSTAL_HARPOON_INACTIVE: + return true; + } + } + + return false; + } + + @Subscribe + public void onGameTick(GameTick event) + { + // Reset fishing session + if (session.getLastFishCaught() != null) + { + final Duration statTimeout = Duration.ofMinutes(config.statTimeout()); + final Duration sinceCaught = Duration.between(session.getLastFishCaught(), Instant.now()); + + if (sinceCaught.compareTo(statTimeout) >= 0) + { + currentSpot = null; + session.setLastFishCaught(null); + } + } + + inverseSortSpotDistanceFromPlayer(); + + for (NPC npc : fishingSpots) + { + if (FishingSpot.findSpot(npc.getId()) == FishingSpot.MINNOW && config.showMinnowOverlay()) + { + final int id = npc.getIndex(); + final MinnowSpot minnowSpot = minnowSpots.get(id); + + // create the minnow spot if it doesn't already exist + // or if it was moved, reset it + if (minnowSpot == null + || !minnowSpot.getLoc().equals(npc.getWorldLocation())) + { + minnowSpots.put(id, new MinnowSpot(npc.getWorldLocation(), Instant.now())); + } + } + } + + if (config.trawlerTimer()) + { + updateTrawlerTimer(); + } + } + + @Subscribe + public void onNpcSpawned(NpcSpawned event) + { + final NPC npc = event.getNpc(); + + if (FishingSpot.findSpot(npc.getId()) == null) + { + return; + } + + fishingSpots.add(npc); + inverseSortSpotDistanceFromPlayer(); + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + final NPC npc = npcDespawned.getNpc(); + + fishingSpots.remove(npc); + + MinnowSpot minnowSpot = minnowSpots.remove(npc.getIndex()); + if (minnowSpot != null) + { + log.debug("Minnow spot {} despawned", npc); + } + } + + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + if (!config.trawlerNotification() || client.getGameState() != GameState.LOGGED_IN) + { + return; + } + + int regionID = client.getLocalPlayer().getWorldLocation().getRegionID(); + + if ((regionID == TRAWLER_SHIP_REGION_NORMAL || regionID == TRAWLER_SHIP_REGION_SINKING) + && client.getVar(Varbits.FISHING_TRAWLER_ACTIVITY) <= TRAWLER_ACTIVITY_THRESHOLD) + { + if (!trawlerNotificationSent) + { + notifier.notify("[" + client.getLocalPlayer().getName() + "] has low Fishing Trawler activity!"); + trawlerNotificationSent = true; + } + } + else + { + trawlerNotificationSent = false; + } + } + + @Subscribe + public void onWidgetLoaded(WidgetLoaded event) + { + if (event.getGroupId() == WidgetID.FISHING_TRAWLER_GROUP_ID) + { + trawlerStartTime = Instant.now(); + } + } + + /** + * Changes the Fishing Trawler timer widget from minutes to minutes and seconds + */ + private void updateTrawlerTimer() + { + if (trawlerStartTime == null) + { + return; + } + + int regionID = client.getLocalPlayer().getWorldLocation().getRegionID(); + if (regionID != TRAWLER_SHIP_REGION_NORMAL && regionID != TRAWLER_SHIP_REGION_SINKING) + { + log.debug("Trawler session ended"); + trawlerStartTime = null; + return; + } + + Widget trawlerTimerWidget = client.getWidget(WidgetInfo.FISHING_TRAWLER_TIMER); + if (trawlerTimerWidget == null) + { + return; + } + + long timeLeft = TRAWLER_TIME_LIMIT_IN_SECONDS - Duration.between(trawlerStartTime, Instant.now()).getSeconds(); + if (timeLeft < 0) + { + timeLeft = 0; + } + + int minutes = (int) timeLeft / 60; + int seconds = (int) timeLeft % 60; + + final StringBuilder trawlerText = new StringBuilder(); + trawlerText.append("Time Left: "); + + if (minutes > 0) + { + trawlerText.append(minutes); + } + else + { + trawlerText.append("00"); + } + + trawlerText.append(':'); + + if (seconds < 10) + { + trawlerText.append("0"); + } + + trawlerText.append(seconds); + + trawlerTimerWidget.setText(trawlerText.toString()); + } + + private void inverseSortSpotDistanceFromPlayer() + { + if (fishingSpots.isEmpty()) + { + return; + } + + final LocalPoint cameraPoint = new LocalPoint(client.getCameraX(), client.getCameraY()); + fishingSpots.sort( + Comparator.comparing( + // Negate to have the furthest first + (NPC npc) -> -npc.getLocalLocation().distanceTo(cameraPoint)) + // Order by position + .thenComparing(NPC::getLocalLocation, Comparator.comparing(LocalPoint::getX) + .thenComparing(LocalPoint::getY)) + // And then by id + .thenComparing(NPC::getId) + ); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSession.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSession.java new file mode 100644 index 0000000000..ae1f387747 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSession.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, Adam + * 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.fishing; + +import java.time.Instant; +import lombok.Getter; +import lombok.Setter; + +class FishingSession +{ + @Getter + @Setter + private Instant lastFishCaught; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotMinimapOverlay.java new file mode 100644 index 0000000000..7b995a9945 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotMinimapOverlay.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, Adam + * 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.fishing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Setter; +import net.runelite.api.GraphicID; +import net.runelite.api.NPC; +import net.runelite.client.game.FishingSpot; +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; + +class FishingSpotMinimapOverlay extends Overlay +{ + private final FishingPlugin plugin; + private final FishingConfig config; + + @Setter(AccessLevel.PACKAGE) + private boolean hidden; + + @Inject + public FishingSpotMinimapOverlay(FishingPlugin plugin, FishingConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (hidden) + { + return null; + } + + for (NPC npc : plugin.getFishingSpots()) + { + FishingSpot spot = FishingSpot.findSpot(npc.getId()); + + if (spot == null) + { + continue; + } + + if (config.onlyCurrentSpot() && plugin.getCurrentSpot() != null && plugin.getCurrentSpot() != spot) + { + continue; + } + + Color color = npc.getGraphic() == GraphicID.FLYING_FISH + ? config.getMinnowsOverlayColor() + : config.getOverlayColor(); + + net.runelite.api.Point minimapLocation = npc.getMinimapLocation(); + if (minimapLocation != null) + { + OverlayUtil.renderMinimapLocation(graphics, minimapLocation, color.darker()); + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotOverlay.java new file mode 100644 index 0000000000..82c710a214 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingSpotOverlay.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2017, Seth + * 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.fishing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Setter; +import net.runelite.api.Client; +import net.runelite.api.GraphicID; +import net.runelite.api.NPC; +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.game.FishingSpot; +import net.runelite.client.game.ItemManager; +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.ui.overlay.components.ProgressPieComponent; +import net.runelite.client.util.ImageUtil; + +class FishingSpotOverlay extends Overlay +{ + private static final Duration MINNOW_MOVE = Duration.ofSeconds(15); + private static final Duration MINNOW_WARN = Duration.ofSeconds(3); + private static final int ONE_TICK_AERIAL_FISHING = 3; + + private final FishingPlugin plugin; + private final FishingConfig config; + private final Client client; + private final ItemManager itemManager; + + @Setter(AccessLevel.PACKAGE) + private boolean hidden; + + @Inject + private FishingSpotOverlay(FishingPlugin plugin, FishingConfig config, Client client, ItemManager itemManager) + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + this.plugin = plugin; + this.config = config; + this.client = client; + this.itemManager = itemManager; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (hidden) + { + return null; + } + + FishingSpot previousSpot = null; + WorldPoint previousLocation = null; + for (NPC npc : plugin.getFishingSpots()) + { + FishingSpot spot = FishingSpot.findSpot(npc.getId()); + + if (spot == null) + { + continue; + } + + if (config.onlyCurrentSpot() && plugin.getCurrentSpot() != null && plugin.getCurrentSpot() != spot) + { + continue; + } + + // This relies on the sort order to keep identical npcs on the same tile adjacent to each other + if (previousSpot == spot && previousLocation.equals(npc.getWorldLocation())) + { + continue; + } + + Color color; + if (npc.getGraphic() == GraphicID.FLYING_FISH) + { + color = config.getMinnowsOverlayColor(); + } + else if (spot == FishingSpot.COMMON_TENCH && npc.getWorldLocation().distanceTo2D(client.getLocalPlayer().getWorldLocation()) <= ONE_TICK_AERIAL_FISHING) + { + color = config.getAerialOverlayColor(); + } + else + { + color = config.getOverlayColor(); + } + + if (spot == FishingSpot.MINNOW && config.showMinnowOverlay()) + { + MinnowSpot minnowSpot = plugin.getMinnowSpots().get(npc.getIndex()); + if (minnowSpot != null) + { + long millisLeft = MINNOW_MOVE.toMillis() - Duration.between(minnowSpot.getTime(), Instant.now()).toMillis(); + if (millisLeft < MINNOW_WARN.toMillis()) + { + color = Color.ORANGE; + } + + LocalPoint localPoint = npc.getLocalLocation(); + Point location = Perspective.localToCanvas(client, localPoint, client.getPlane()); + + if (location != null) + { + ProgressPieComponent pie = new ProgressPieComponent(); + pie.setFill(color); + pie.setBorderColor(color); + pie.setPosition(location); + pie.setProgress((float) millisLeft / MINNOW_MOVE.toMillis()); + pie.render(graphics); + } + } + } + + if (config.showSpotTiles()) + { + Polygon poly = npc.getCanvasTilePoly(); + + if (poly != null) + { + OverlayUtil.renderPolygon(graphics, poly, color.darker()); + } + } + + if (config.showSpotIcons()) + { + BufferedImage fishImage = itemManager.getImage(spot.getFishSpriteId()); + + if (spot == FishingSpot.COMMON_TENCH + && npc.getWorldLocation().distanceTo2D(client.getLocalPlayer().getWorldLocation()) <= ONE_TICK_AERIAL_FISHING) + { + fishImage = ImageUtil.outlineImage(itemManager.getImage(spot.getFishSpriteId()), color); + } + + if (fishImage != null) + { + Point imageLocation = npc.getCanvasImageLocation(fishImage, npc.getLogicalHeight()); + if (imageLocation != null) + { + OverlayUtil.renderImageLocation(graphics, imageLocation, fishImage); + } + } + } + + if (config.showSpotNames()) + { + String text = spot.getName(); + Point textLocation = npc.getCanvasTextLocation(graphics, text, npc.getLogicalHeight() + 40); + + if (textLocation != null) + { + OverlayUtil.renderTextLocation(graphics, textLocation, text, color.darker()); + } + } + + previousSpot = spot; + previousLocation = npc.getWorldLocation(); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/MinnowSpot.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/MinnowSpot.java new file mode 100644 index 0000000000..b17270dfee --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/MinnowSpot.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Adam + * 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.fishing; + +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Value; +import net.runelite.api.coords.WorldPoint; + +@AllArgsConstructor +@Value +class MinnowSpot +{ + private final WorldPoint loc; + private final Instant time; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNoteOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNoteOverlay.java new file mode 100644 index 0000000000..554a823573 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNoteOverlay.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018, Rheon + * Copyright (c) 2018, Adam + * 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 HOLDER 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.friendnotes; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.tooltip.Tooltip; +import net.runelite.client.ui.overlay.tooltip.TooltipManager; + +class FriendNoteOverlay extends Overlay +{ + private final Client client; + private final FriendNotesPlugin plugin; + private final TooltipManager tooltipManager; + + @Inject + private FriendNoteOverlay(Client client, FriendNotesPlugin plugin, TooltipManager tooltipManager) + { + this.client = client; + this.plugin = plugin; + this.tooltipManager = tooltipManager; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (client.isMenuOpen()) + { + return null; + } + + // Add a friend note tooltip to a hovered friend list entry + final HoveredFriend hovered = plugin.getHoveredFriend(); + + if (hovered != null) // Will always have a friend note if non-null + { + final String content = hovered.getNote(); + tooltipManager.add(new Tooltip(content)); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesConfig.java new file mode 100644 index 0000000000..abaa5915f7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, ThatGamerBlue + * 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 HOLDER 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.friendnotes; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(FriendNotesPlugin.CONFIG_GROUP) +public interface FriendNotesConfig extends Config +{ + @ConfigItem( + keyName = "showIcons", + name = "Show Icons", + description = "Show icons on friend or ignore list", + position = 1 + ) + default boolean showIcons() + { + return true; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesPlugin.java new file mode 100644 index 0000000000..de0fe4b9dc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/FriendNotesPlugin.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2018, Rheon + * Copyright (c) 2018, Adam + * 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 HOLDER 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.friendnotes; + +import com.google.common.base.Strings; +import com.google.common.collect.ObjectArrays; +import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import javax.annotation.Nullable; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.Friend; +import net.runelite.api.GameState; +import net.runelite.api.Ignore; +import net.runelite.api.IndexedSprite; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Nameable; +import net.runelite.api.ScriptID; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.NameableNameChanged; +import net.runelite.api.events.RemovedFriend; +import net.runelite.api.events.ScriptCallbackEvent; +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.chatbox.ChatboxPanelManager; +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.ImageUtil; +import net.runelite.client.util.Text; + +@Slf4j +@PluginDescriptor( + name = "Friend Notes", + description = "Store notes about your friends" +) +public class FriendNotesPlugin extends Plugin +{ + static final String CONFIG_GROUP = "friendNotes"; + private static final int CHARACTER_LIMIT = 128; + private static final String KEY_PREFIX = "note_"; + private static final String ADD_NOTE = "Add Note"; + private static final String EDIT_NOTE = "Edit Note"; + private static final String NOTE_PROMPT_FORMAT = "%s's Notes
" + + ColorUtil.prependColorTag("(Limit %s Characters)", new Color(0, 0, 170)); + private static final int ICON_WIDTH = 14; + private static final int ICON_HEIGHT = 12; + + @Inject + private Client client; + + @Inject + private ConfigManager configManager; + + @Inject + private OverlayManager overlayManager; + + @Inject + private FriendNoteOverlay overlay; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private ClientThread clientThread; + + @Inject + private FriendNotesConfig config; + + @Getter + private HoveredFriend hoveredFriend = null; + + private int iconIdx = -1; + private String currentlyLayouting; + + @Provides + private FriendNotesConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(FriendNotesConfig.class); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + clientThread.invoke(this::loadIcon); + if (client.getGameState() == GameState.LOGGED_IN) + { + rebuildFriendsList(); + rebuildIgnoreList(); + } + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + if (client.getGameState() == GameState.LOGGED_IN) + { + rebuildFriendsList(); + rebuildIgnoreList(); + } + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() == GameState.LOGGED_IN) + { + loadIcon(); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals(CONFIG_GROUP)) + { + return; + } + + switch (event.getKey()) + { + case "showIcons": + if (client.getGameState() == GameState.LOGGED_IN) + { + rebuildFriendsList(); + rebuildIgnoreList(); + } + break; + } + } + + /** + * Set a friend note, or unset by passing a null/empty note. + */ + private void setFriendNote(String displayName, String note) + { + if (Strings.isNullOrEmpty(note)) + { + configManager.unsetConfiguration(CONFIG_GROUP, KEY_PREFIX + displayName); + } + else + { + configManager.setConfiguration(CONFIG_GROUP, KEY_PREFIX + displayName, note); + } + if (client.getGameState() == GameState.LOGGED_IN) + { + rebuildFriendsList(); + rebuildIgnoreList(); + } + } + + /** + * Get the friend note of a display name, or null if no friend note exists for it. + */ + @Nullable + private String getFriendNote(String displayName) + { + return configManager.getConfiguration(CONFIG_GROUP, KEY_PREFIX + displayName); + } + + /** + * Migrate a friend note to a new display name, and remove the previous one. + * If current name already has a note, or previous name had none, do nothing. + */ + private void migrateFriendNote(String currentDisplayName, String prevDisplayName) + { + final String currentNote = getFriendNote(currentDisplayName); + if (currentNote == null) + { + final String prevNote = getFriendNote(prevDisplayName); + if (prevNote != null) + { + log.debug("Update friend's username: '{}' -> '{}'", prevDisplayName, currentDisplayName); + setFriendNote(prevDisplayName, null); + setFriendNote(currentDisplayName, prevNote); + } + } + } + + /** + * Set the currently hovered display name, if a friend note exists for it. + */ + private void setHoveredFriend(String displayName) + { + hoveredFriend = null; + + if (!Strings.isNullOrEmpty(displayName)) + { + final String note = getFriendNote(displayName); + if (note != null) + { + hoveredFriend = new HoveredFriend(displayName, note); + } + } + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + final int groupId = WidgetInfo.getGroupFromID(event.getActionParam1()); + + // Look for "Message" on friends list + if ((groupId == WidgetInfo.FRIENDS_LIST.getGroupId() && event.getOption().equals("Message")) || + (groupId == WidgetInfo.IGNORE_LIST.getGroupId() && event.getOption().equals("Delete"))) + { + // Friends have color tags + setHoveredFriend(Text.toJagexName(Text.removeTags(event.getTarget()))); + + // Build "Add Note" or "Edit Note" menu entry + final MenuEntry addNote = new MenuEntry(); + addNote.setOption(hoveredFriend == null || hoveredFriend.getNote() == null ? ADD_NOTE : EDIT_NOTE); + addNote.setType(MenuAction.RUNELITE.getId()); + addNote.setTarget(event.getTarget()); //Preserve color codes here + addNote.setParam0(event.getActionParam0()); + addNote.setParam1(event.getActionParam1()); + + // Add menu entry + final MenuEntry[] menuEntries = ObjectArrays.concat(client.getMenuEntries(), addNote); + client.setMenuEntries(menuEntries); + } + else if (hoveredFriend != null) + { + hoveredFriend = null; + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + final int groupId = WidgetInfo.getGroupFromID(event.getWidgetId()); + + if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() || groupId == WidgetInfo.IGNORE_LIST.getGroupId()) + { + if (Strings.isNullOrEmpty(event.getMenuTarget())) + { + return; + } + + // Handle clicks on "Add Note" or "Edit Note" + if (event.getMenuOption().equals(ADD_NOTE) || event.getMenuOption().equals(EDIT_NOTE)) + { + event.consume(); + + //Friends have color tags + final String sanitizedTarget = Text.toJagexName(Text.removeTags(event.getMenuTarget())); + final String note = getFriendNote(sanitizedTarget); + + // Open the new chatbox input dialog + chatboxPanelManager.openTextInput(String.format(NOTE_PROMPT_FORMAT, sanitizedTarget, CHARACTER_LIMIT)) + .value(Strings.nullToEmpty(note)) + .onDone((content) -> + { + if (content == null) + { + return; + } + + content = Text.removeTags(content).trim(); + log.debug("Set note for '{}': '{}'", sanitizedTarget, content); + setFriendNote(sanitizedTarget, content); + }).build(); + } + } + + } + + @Subscribe + public void onNameableNameChanged(NameableNameChanged event) + { + final Nameable nameable = event.getNameable(); + + if (nameable instanceof Friend || nameable instanceof Ignore) + { + // Migrate a friend's note to their new display name + String name = nameable.getName(); + String prevName = nameable.getPrevName(); + + if (prevName != null) + { + migrateFriendNote( + Text.toJagexName(name), + Text.toJagexName(prevName) + ); + } + } + } + + @Subscribe + public void onRemovedFriend(RemovedFriend event) + { + // Delete a friend's note if they are removed + final String displayName = Text.toJagexName(event.getName()); + log.debug("Remove friend: '{}'", displayName); + setFriendNote(displayName, null); + } + + @Subscribe + public void onScriptCallbackEvent(ScriptCallbackEvent event) + { + if (!config.showIcons() || iconIdx == -1) + { + return; + } + + switch (event.getEventName()) + { + case "friend_cc_settext": + case "ignore_cc_settext": + String[] stringStack = client.getStringStack(); + int stringStackSize = client.getStringStackSize(); + final String rsn = stringStack[stringStackSize - 1]; + final String sanitized = Text.toJagexName(Text.removeTags(rsn)); + currentlyLayouting = sanitized; + if (getFriendNote(sanitized) != null) + { + stringStack[stringStackSize - 1] = rsn + " "; + } + break; + case "friend_cc_setposition": + case "ignore_cc_setposition": + if (currentlyLayouting == null || getFriendNote(currentlyLayouting) == null) + { + return; + } + + int[] intStack = client.getIntStack(); + int intStackSize = client.getIntStackSize(); + int xpos = intStack[intStackSize - 4]; + xpos += ICON_WIDTH + 1; + intStack[intStackSize - 4] = xpos; + break; + } + } + + private void rebuildFriendsList() + { + clientThread.invokeLater(() -> + { + log.debug("Rebuilding friends list"); + client.runScript( + ScriptID.FRIENDS_UPDATE, + WidgetInfo.FRIEND_LIST_FULL_CONTAINER.getPackedId(), + WidgetInfo.FRIEND_LIST_SORT_BY_NAME_BUTTON.getPackedId(), + WidgetInfo.FRIEND_LIST_SORT_BY_LAST_WORLD_CHANGE_BUTTON.getPackedId(), + WidgetInfo.FRIEND_LIST_SORT_BY_WORLD_BUTTON.getPackedId(), + WidgetInfo.FRIEND_LIST_LEGACY_SORT_BUTTON.getPackedId(), + WidgetInfo.FRIEND_LIST_NAMES_CONTAINER.getPackedId(), + WidgetInfo.FRIEND_LIST_SCROLL_BAR.getPackedId(), + WidgetInfo.FRIEND_LIST_LOADING_TEXT.getPackedId(), + WidgetInfo.FRIEND_LIST_PREVIOUS_NAME_HOLDER.getPackedId() + ); + }); + } + + private void rebuildIgnoreList() + { + clientThread.invokeLater(() -> + { + log.debug("Rebuilding ignore list"); + client.runScript( + ScriptID.IGNORE_UPDATE, + WidgetInfo.IGNORE_FULL_CONTAINER.getPackedId(), + WidgetInfo.IGNORE_SORT_BY_NAME_BUTTON.getPackedId(), + WidgetInfo.IGNORE_LEGACY_SORT_BUTTON.getPackedId(), + WidgetInfo.IGNORE_NAMES_CONTAINER.getPackedId(), + WidgetInfo.IGNORE_SCROLL_BAR.getPackedId(), + WidgetInfo.IGNORE_LOADING_TEXT.getPackedId(), + WidgetInfo.IGNORE_PREVIOUS_NAME_HOLDER.getPackedId() + ); + }); + } + + private void loadIcon() + { + final IndexedSprite[] modIcons = client.getModIcons(); + if (iconIdx != -1 || modIcons == null) + { + return; + } + + final BufferedImage iconImg = ImageUtil.getResourceStreamFromClass(getClass(), "note_icon.png"); + if (iconImg == null) + { + return; + } + + final BufferedImage resized = ImageUtil.resizeImage(iconImg, ICON_WIDTH, ICON_HEIGHT); + + final IndexedSprite[] newIcons = Arrays.copyOf(modIcons, modIcons.length + 1); + newIcons[newIcons.length - 1] = ImageUtil.getImageIndexedSprite(resized, client); + + iconIdx = newIcons.length - 1; + client.setModIcons(newIcons); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/HoveredFriend.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/HoveredFriend.java new file mode 100644 index 0000000000..9a049ec58e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendnotes/HoveredFriend.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Rheon + * 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 HOLDER 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.friendnotes; + +import lombok.Value; + +@Value +class HoveredFriend +{ + private String friendName; + private String note; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index 240ea3b8d4..8b62273426 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -557,13 +557,13 @@ 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.TO_GROUP(widgetId); + final int groupId = WidgetInfo.getGroupFromID(widgetId); switch (groupId) { case WidgetID.BANK_GROUP_ID: // Don't show for view tabs and such - if (WidgetInfo.TO_CHILD(widgetId) != WidgetInfo.BANK_ITEM_CONTAINER.getChildId()) + if (WidgetInfo.getChildFromID(widgetId) != WidgetInfo.BANK_ITEM_CONTAINER.getChildId()) { break; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java index 75e41f6018..7dcf181c99 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePlugin.java @@ -148,7 +148,7 @@ public class HiscorePlugin extends Plugin return; } - int groupId = WidgetInfo.TO_GROUP(event.getActionParam1()); + int groupId = WidgetInfo.getGroupFromID(event.getActionParam1()); String option = event.getOption(); if (groupId == WidgetInfo.FRIENDS_LIST.getGroupId() || groupId == WidgetInfo.FRIENDS_CHAT.getGroupId() || diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java index c614665401..dac17ee36e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java @@ -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.TO_GROUP(widgetId); + final int groupId = WidgetInfo.getGroupFromID(widgetId); final boolean isAlching = menuEntry.getOption().equals("Cast") && menuEntry.getTarget().contains("High Level Alchemy"); // Tooltip action type handling diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java index ff3922fd92..bfcbb8e58d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java @@ -92,8 +92,8 @@ public class ItemStatOverlay extends Overlay } final MenuEntry entry = menu[menuSize - 1]; - final int group = WidgetInfo.TO_GROUP(entry.getParam1()); - final int child = WidgetInfo.TO_CHILD(entry.getParam1()); + final int group = WidgetInfo.getGroupFromID(entry.getParam1()); + final int child = WidgetInfo.getChildFromID(entry.getParam1()); final Widget widget = client.getWidget(group, child); if (widget == null diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java index 07b11ccf50..8fb198ecb5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/mousehighlight/MouseHighlightOverlay.java @@ -136,7 +136,7 @@ class MouseHighlightOverlay extends Overlay if (WIDGET_MENU_ACTIONS.contains(type)) { final int widgetId = menuEntry.getParam1(); - final int groupId = WidgetInfo.TO_GROUP(widgetId); + final int groupId = WidgetInfo.getGroupFromID(widgetId); if (!config.uiTooltip()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java index a2dfb8577b..4051d5e509 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/puzzlesolver/PuzzleSolverPlugin.java @@ -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.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; 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 (TO_GROUP(widgetId) != WidgetID.LIGHT_BOX_GROUP_ID) + if (getGroupFromID(widgetId) != WidgetID.LIGHT_BOX_GROUP_ID) { return; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerWidgetHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerWidgetHighlightOverlay.java index 718ea93903..e58c7644f7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerWidgetHighlightOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerWidgetHighlightOverlay.java @@ -74,8 +74,8 @@ 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.TO_GROUP(widgetId); - final int componentId = WidgetInfo.TO_CHILD(widgetId); + final int groupId = WidgetInfo.getGroupFromID(widgetId); + final int componentId = WidgetInfo.getChildFromID(widgetId); final Widget widget = client.getWidget(groupId, componentId); if (widget == null) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java index ec45039942..3b005bfb21 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java @@ -279,7 +279,7 @@ public class TimersPlugin extends Plugin { Widget widget = event.getWidget(); if (WorldType.isPvpWorld(client.getWorldType()) - && WidgetInfo.TO_GROUP(widget.getId()) == WidgetID.PVP_GROUP_ID) + && WidgetInfo.getGroupFromID(widget.getId()) == WidgetID.PVP_GROUP_ID) { widgetHiddenChangedOnPvpWorld = true; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java index 9ae532a8db..6f72d10e5a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java @@ -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.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; 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 (TO_GROUP(widgetID) != WidgetID.SKILLS_GROUP_ID + if (getGroupFromID(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() - || TO_GROUP(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID) + || getGroupFromID(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID) { return; } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java index 295c1d618b..f3a1abd1e5 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/WidgetItemOverlay.java @@ -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.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; 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 = TO_GROUP(widget.getId()); + int interfaceGroup = getGroupFromID(widget.getId()); // Don't draw if this widget isn't one of the allowed nor in tag tab/item tab if (!interfaceGroups.contains(interfaceGroup) || diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 85d1a5c495..b8f417e334 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -39,7 +39,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Named; import net.runelite.api.ChatMessageType; -import net.runelite.api.EnumDefinition; +import net.runelite.api.EnumComposition; import net.runelite.api.Friend; import net.runelite.api.GameState; import net.runelite.api.GrandExchangeOffer; @@ -124,7 +124,7 @@ import net.runelite.api.widgets.WidgetType; import net.runelite.rs.api.RSAbstractArchive; import net.runelite.rs.api.RSChatChannel; import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSEnumDefinition; +import net.runelite.rs.api.RSEnumComposition; import net.runelite.rs.api.RSFriendSystem; import net.runelite.rs.api.RSIndexedSprite; import net.runelite.rs.api.RSItemContainer; @@ -206,7 +206,7 @@ public abstract class RSClientMixin implements RSClient static int skyboxColor; @Inject - private final Cache enumCache = CacheBuilder.newBuilder() + private final Cache enumCache = CacheBuilder.newBuilder() .maximumSize(64) .build(); @@ -594,13 +594,7 @@ public abstract class RSClientMixin implements RSClient @Override public Widget getWidget(int id) { - for (WidgetInfo widgetInfo : WidgetInfo.values()) - { - if (widgetInfo.getId() == id) - return getWidget(widgetInfo); - } - - return null; + return getWidget(WidgetInfo.getGroupFromID(id), WidgetInfo.getChildFromID(id)); } @Inject @@ -1747,11 +1741,11 @@ public abstract class RSClientMixin implements RSClient @Inject @Override - public EnumDefinition getEnum(int id) + public EnumComposition getEnum(int id) { assert isClientThread() : "getEnum must be called on client thread"; - RSEnumDefinition rsEnumDefinition = enumCache.getIfPresent(id); + RSEnumComposition rsEnumDefinition = enumCache.getIfPresent(id); if (rsEnumDefinition != null) { return rsEnumDefinition; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumDefinitionMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumCompositionMixin.java similarity index 83% rename from runelite-mixins/src/main/java/net/runelite/mixins/RSEnumDefinitionMixin.java rename to runelite-mixins/src/main/java/net/runelite/mixins/RSEnumCompositionMixin.java index 05424a8b4e..58856130ec 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumDefinitionMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumCompositionMixin.java @@ -2,10 +2,10 @@ package net.runelite.mixins; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; -import net.runelite.rs.api.RSEnumDefinition; +import net.runelite.rs.api.RSEnumComposition; -@Mixin(RSEnumDefinition.class) -public abstract class RSEnumDefinitionMixin implements RSEnumDefinition +@Mixin(RSEnumComposition.class) +public abstract class RSEnumCompositionMixin implements RSEnumComposition { @Inject @Override diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSFriendSystemMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSFriendSystemMixin.java index d273a3bf33..06e7325fae 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSFriendSystemMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSFriendSystemMixin.java @@ -1,7 +1,7 @@ package net.runelite.mixins; import net.runelite.api.events.FriendAdded; -import net.runelite.api.events.FriendRemoved; +import net.runelite.api.events.RemovedFriend; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.MethodHook; import net.runelite.api.mixins.Mixin; @@ -19,8 +19,8 @@ public abstract class RSFriendSystemMixin implements RSFriendSystem @Inject public void rl$removeFriend(String friendName) { - FriendRemoved friendRemoved = new FriendRemoved(friendName); - client.getCallbacks().post(friendRemoved); + RemovedFriend removedFriend = new RemovedFriend(friendName); + client.getCallbacks().post(removedFriend); } @MethodHook("addFriend") diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java index d95e5c94dc..3652bc6449 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java @@ -50,8 +50,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; -import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import static net.runelite.api.widgets.WidgetInfo.getChildFromID; +import static net.runelite.api.widgets.WidgetInfo.getGroupFromID; @Mixin(RSWidget.class) public abstract class RSWidgetMixin implements RSWidget @@ -107,7 +107,7 @@ public abstract class RSWidgetMixin implements RSWidget return null; } - return client.getWidget(TO_GROUP(id), TO_CHILD(id)); + return client.getWidget(getGroupFromID(id), getChildFromID(id)); } @Inject @@ -123,7 +123,7 @@ public abstract class RSWidgetMixin implements RSWidget } final int id = getId(); - if (TO_GROUP(id) == client.getWidgetRoot()) + if (getGroupFromID(id) == client.getWidgetRoot()) { // this is a root widget return -1; @@ -140,7 +140,7 @@ public abstract class RSWidgetMixin implements RSWidget // check the parent in the component table @SuppressWarnings("unchecked") HashTable componentTable = client.getComponentTable(); WidgetNode widgetNode = componentTable.get(parentId); - if (widgetNode == null || widgetNode.getId() != TO_GROUP(id)) + if (widgetNode == null || widgetNode.getId() != getGroupFromID(id)) { // invalidate parent rl$parentId = -1; @@ -152,7 +152,7 @@ public abstract class RSWidgetMixin implements RSWidget } // also the widget may not have been drawn, yet - int groupId = TO_GROUP(getId()); + int groupId = getGroupFromID(getId()); RSNodeHashTable componentTable = client.getComponentTable(); RSNode[] buckets = componentTable.getBuckets(); for (RSNode node : buckets) @@ -220,7 +220,7 @@ public abstract class RSWidgetMixin implements RSWidget // If the parent is hidden, this widget is also hidden. // Widget has no parent and is not the root widget (which is always visible), // so it's not visible. - return parent == null ? TO_GROUP(getId()) != client.getWidgetRoot() : parent.isHidden(); + return parent == null ? getGroupFromID(getId()) != client.getWidgetRoot() : parent.isHidden(); } @Inject @@ -360,7 +360,7 @@ public abstract class RSWidgetMixin implements RSWidget } List widgets = new ArrayList(); - for (RSWidget widget : client.getGroup(TO_GROUP(getId()))) + for (RSWidget widget : client.getGroup(getGroupFromID(getId()))) { if (widget != null && widget.getRSParentId() == getId()) { @@ -475,7 +475,7 @@ public abstract class RSWidgetMixin implements RSWidget return; } } - else if (TO_GROUP(id) != client.getWidgetRoot()) + else if (getGroupFromID(id) != client.getWidgetRoot()) { return; } @@ -576,7 +576,7 @@ public abstract class RSWidgetMixin implements RSWidget assert client.isClientThread(); client.revalidateWidget(this); - client.revalidateWidgetScroll(client.getWidgets()[TO_GROUP(this.getId())], this, false); + client.revalidateWidgetScroll(client.getWidgets()[getGroupFromID(this.getId())], this, false); } @Inject diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 92676e0e9e..f1513802cb 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -997,7 +997,7 @@ public interface RSClient extends RSGameShell, Client void setSpellSelected(boolean selected); @Import("getEnum") - RSEnumDefinition getRsEnum(int id); + RSEnumComposition getRsEnum(int id); @Import("menuX") int getMenuX(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSEnumDefinition.java b/runescape-api/src/main/java/net/runelite/rs/api/RSEnumComposition.java similarity index 74% rename from runescape-api/src/main/java/net/runelite/rs/api/RSEnumDefinition.java rename to runescape-api/src/main/java/net/runelite/rs/api/RSEnumComposition.java index 494cdfb111..fbe8e8603c 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSEnumDefinition.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSEnumComposition.java @@ -1,9 +1,9 @@ package net.runelite.rs.api; -import net.runelite.api.EnumDefinition; +import net.runelite.api.EnumComposition; import net.runelite.mapping.Import; -public interface RSEnumDefinition extends EnumDefinition, RSDualNode +public interface RSEnumComposition extends EnumComposition, RSDualNode { @Import("keys") @Override diff --git a/runescape-client/src/main/java/Client.java b/runescape-client/src/main/java/Client.java index 6211da49fc..b8f9c3639e 100644 --- a/runescape-client/src/main/java/Client.java +++ b/runescape-client/src/main/java/Client.java @@ -3613,9 +3613,9 @@ public final class Client extends GameShell implements Usernamed { } } - Widget var35 = EnumDefinition.mousedOverWidgetIf1; // L: 3201 + Widget var35 = EnumComposition.mousedOverWidgetIf1; // L: 3201 Widget var37 = CollisionMap.field2092; // L: 3202 - EnumDefinition.mousedOverWidgetIf1 = null; // L: 3203 + EnumComposition.mousedOverWidgetIf1 = null; // L: 3203 CollisionMap.field2092 = null; // L: 3204 draggedOnWidget = null; // L: 3205 field829 = false; // L: 3206 @@ -3775,13 +3775,13 @@ public final class Client extends GameShell implements Usernamed { destinationY = var5; // L: 3387 } - if (var35 != EnumDefinition.mousedOverWidgetIf1) { // L: 3389 + if (var35 != EnumComposition.mousedOverWidgetIf1) { // L: 3389 if (var35 != null) { // L: 3390 CollisionMap.invalidateWidget(var35); } - if (EnumDefinition.mousedOverWidgetIf1 != null) { // L: 3391 - CollisionMap.invalidateWidget(EnumDefinition.mousedOverWidgetIf1); + if (EnumComposition.mousedOverWidgetIf1 != null) { // L: 3391 + CollisionMap.invalidateWidget(EnumComposition.mousedOverWidgetIf1); } } @@ -4036,7 +4036,7 @@ public final class Client extends GameShell implements Usernamed { } var4 = WorldMapData_0.field112 * 128 + 64; // L: 3552 - var5 = EnumDefinition.field3340 * 128 + 64; // L: 3553 + var5 = EnumComposition.field3340 * 128 + 64; // L: 3553 var6 = SecureRandomFuture.getTileHeight(var4, var5, GameObject.Client_plane) - class25.field128; // L: 3554 var7 = var4 - Player.cameraX; // L: 3555 var8 = var6 - KeyHandler.cameraY; // L: 3556 @@ -5347,13 +5347,13 @@ public final class Client extends GameShell implements Usernamed { if (ServerPacket.field2163 == var1.serverPacket) { // L: 6415 isCameraLocked = true; // L: 6416 WorldMapData_0.field112 = var3.readUnsignedByte(); // L: 6417 - EnumDefinition.field3340 = var3.readUnsignedByte(); // L: 6418 + EnumComposition.field3340 = var3.readUnsignedByte(); // L: 6418 class25.field128 = var3.readUnsignedShort(); // L: 6419 Players.field1267 = var3.readUnsignedByte(); // L: 6420 ModeWhere.field2405 = var3.readUnsignedByte(); // L: 6421 if (ModeWhere.field2405 >= 100) { // L: 6422 var16 = WorldMapData_0.field112 * 128 + 64; // L: 6423 - var5 = EnumDefinition.field3340 * 128 + 64; // L: 6424 + var5 = EnumComposition.field3340 * 128 + 64; // L: 6424 var18 = SecureRandomFuture.getTileHeight(var16, var5, GameObject.Client_plane) - class25.field128; // L: 6425 var7 = var16 - Player.cameraX; // L: 6426 var19 = var18 - KeyHandler.cameraY; // L: 6427 diff --git a/runescape-client/src/main/java/EnumDefinition.java b/runescape-client/src/main/java/EnumComposition.java similarity index 98% rename from runescape-client/src/main/java/EnumDefinition.java rename to runescape-client/src/main/java/EnumComposition.java index 4cbf9e0ead..814d22e1d6 100644 --- a/runescape-client/src/main/java/EnumDefinition.java +++ b/runescape-client/src/main/java/EnumComposition.java @@ -6,7 +6,7 @@ import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("jx") @Implements("EnumDefinition") -public class EnumDefinition extends DualNode { +public class EnumComposition extends DualNode { @ObfuscatedName("rk") @ObfuscatedGetter( intValue = 2141868731 @@ -65,7 +65,7 @@ public class EnumDefinition extends DualNode { EnumDefinition_cached = new EvictingDualNodeHashTable(64); // L: 12 } - EnumDefinition() { + EnumComposition() { this.defaultStr = "null"; // L: 15 this.outputCount = 0; // L: 17 } // L: 22 diff --git a/runescape-client/src/main/java/FontName.java b/runescape-client/src/main/java/FontName.java index d3197fb39f..d136a76b55 100644 --- a/runescape-client/src/main/java/FontName.java +++ b/runescape-client/src/main/java/FontName.java @@ -287,7 +287,7 @@ public class FontName { } if (var10.type == 0) { // L: 9058 - if (!var10.isIf3 && DevicePcmPlayerProvider.isComponentHidden(var10) && var10 != EnumDefinition.mousedOverWidgetIf1) { // L: 9059 + if (!var10.isIf3 && DevicePcmPlayerProvider.isComponentHidden(var10) && var10 != EnumComposition.mousedOverWidgetIf1) { // L: 9059 continue; } @@ -426,12 +426,12 @@ public class FontName { } else if (var10.type == 3) { // L: 9145 if (class8.runCs1(var10)) { // L: 9147 var19 = var10.color2; // L: 9148 - if (var10 == EnumDefinition.mousedOverWidgetIf1 && var10.mouseOverColor2 != 0) { // L: 9149 + if (var10 == EnumComposition.mousedOverWidgetIf1 && var10.mouseOverColor2 != 0) { // L: 9149 var19 = var10.mouseOverColor2; } } else { var19 = var10.color; // L: 9152 - if (var10 == EnumDefinition.mousedOverWidgetIf1 && var10.mouseOverColor != 0) { // L: 9153 + if (var10 == EnumComposition.mousedOverWidgetIf1 && var10.mouseOverColor != 0) { // L: 9153 var19 = var10.mouseOverColor; } } @@ -468,7 +468,7 @@ public class FontName { String var44 = var10.text; // L: 9182 if (class8.runCs1(var10)) { // L: 9183 var20 = var10.color2; // L: 9184 - if (var10 == EnumDefinition.mousedOverWidgetIf1 && var10.mouseOverColor2 != 0) { // L: 9185 + if (var10 == EnumComposition.mousedOverWidgetIf1 && var10.mouseOverColor2 != 0) { // L: 9185 var20 = var10.mouseOverColor2; } @@ -477,7 +477,7 @@ public class FontName { } } else { var20 = var10.color; // L: 9189 - if (var10 == EnumDefinition.mousedOverWidgetIf1 && var10.mouseOverColor != 0) { // L: 9190 + if (var10 == EnumComposition.mousedOverWidgetIf1 && var10.mouseOverColor != 0) { // L: 9190 var20 = var10.mouseOverColor; } } diff --git a/runescape-client/src/main/java/ItemLayer.java b/runescape-client/src/main/java/ItemLayer.java index 7ec12270a7..814aea29e1 100644 --- a/runescape-client/src/main/java/ItemLayer.java +++ b/runescape-client/src/main/java/ItemLayer.java @@ -86,7 +86,7 @@ public final class ItemLayer VarcInt.Interpreter_intStackSize -= 2; // L: 1920 var3 = Interpreter.Interpreter_intStack[VarcInt.Interpreter_intStackSize]; // L: 1921 var9 = Interpreter.Interpreter_intStack[VarcInt.Interpreter_intStackSize + 1]; // L: 1922 - EnumDefinition var10 = MusicPatchNode.getEnum(var3); // L: 1923 + EnumComposition var10 = MusicPatchNode.getEnum(var3); // L: 1923 if (var10.outputType != 's') { // L: 1924 } @@ -106,7 +106,7 @@ public final class ItemLayer } else if (var0 != ScriptOpcodes.ENUM) { // L: 1935 if (var0 == ScriptOpcodes.ENUM_GETOUTPUTCOUNT) { // L: 1961 var3 = Interpreter.Interpreter_intStack[--VarcInt.Interpreter_intStackSize]; // L: 1962 - EnumDefinition var4 = MusicPatchNode.getEnum(var3); // L: 1963 + EnumComposition var4 = MusicPatchNode.getEnum(var3); // L: 1963 Interpreter.Interpreter_intStack[++VarcInt.Interpreter_intStackSize - 1] = var4.size(); // L: 1964 return 1; // L: 1965 } else { @@ -118,7 +118,7 @@ public final class ItemLayer var9 = Interpreter.Interpreter_intStack[VarcInt.Interpreter_intStackSize + 1]; // L: 1938 int var5 = Interpreter.Interpreter_intStack[VarcInt.Interpreter_intStackSize + 2]; // L: 1939 var6 = Interpreter.Interpreter_intStack[VarcInt.Interpreter_intStackSize + 3]; // L: 1940 - EnumDefinition var7 = MusicPatchNode.getEnum(var5); // L: 1941 + EnumComposition var7 = MusicPatchNode.getEnum(var5); // L: 1941 if (var3 == var7.inputType && var9 == var7.outputType) { // L: 1942 for (int var8 = 0; var8 < var7.outputCount; ++var8) { // L: 1947 if (var6 == var7.keys[var8]) { // L: 1948 diff --git a/runescape-client/src/main/java/MusicPatchNode.java b/runescape-client/src/main/java/MusicPatchNode.java index b5c035aaa3..f5db1cdb49 100644 --- a/runescape-client/src/main/java/MusicPatchNode.java +++ b/runescape-client/src/main/java/MusicPatchNode.java @@ -137,18 +137,18 @@ public class MusicPatchNode extends Node { garbageValue = "-621121394" ) @Export("getEnum") - public static EnumDefinition getEnum(int var0) { - EnumDefinition var1 = (EnumDefinition)EnumDefinition.EnumDefinition_cached.get((long)var0); // L: 29 + public static EnumComposition getEnum(int var0) { + EnumComposition var1 = (EnumComposition) EnumComposition.EnumDefinition_cached.get((long)var0); // L: 29 if (var1 != null) { // L: 30 return var1; } else { - byte[] var2 = EnumDefinition.EnumDefinition_archive.takeFile(8, var0); // L: 31 - var1 = new EnumDefinition(); // L: 32 + byte[] var2 = EnumComposition.EnumDefinition_archive.takeFile(8, var0); // L: 31 + var1 = new EnumComposition(); // L: 32 if (var2 != null) { // L: 33 var1.decode(new Buffer(var2)); } - EnumDefinition.EnumDefinition_cached.put(var1, (long)var0); // L: 34 + EnumComposition.EnumDefinition_cached.put(var1, (long)var0); // L: 34 return var1; // L: 35 } } diff --git a/runescape-client/src/main/java/Players.java b/runescape-client/src/main/java/Players.java index 364d4f4b66..cf1c1c56bb 100644 --- a/runescape-client/src/main/java/Players.java +++ b/runescape-client/src/main/java/Players.java @@ -263,7 +263,7 @@ public class Players { if (DevicePcmPlayerProvider.isComponentHidden(var9)) { // L: 9916 continue; } - } else if (var9.type == 0 && var9 != EnumDefinition.mousedOverWidgetIf1 && DevicePcmPlayerProvider.isComponentHidden(var9)) { // L: 9919 + } else if (var9.type == 0 && var9 != EnumComposition.mousedOverWidgetIf1 && DevicePcmPlayerProvider.isComponentHidden(var9)) { // L: 9919 continue; } @@ -749,9 +749,9 @@ public class Players { if ((var9.mouseOverRedirect >= 0 || var9.mouseOverColor != 0) && MouseHandler.MouseHandler_x >= var12 && MouseHandler.MouseHandler_y >= var13 && MouseHandler.MouseHandler_x < var14 && MouseHandler.MouseHandler_y < var15) { // L: 10328 if (var9.mouseOverRedirect >= 0) { // L: 10329 - EnumDefinition.mousedOverWidgetIf1 = var0[var9.mouseOverRedirect]; + EnumComposition.mousedOverWidgetIf1 = var0[var9.mouseOverRedirect]; } else { - EnumDefinition.mousedOverWidgetIf1 = var9; // L: 10330 + EnumComposition.mousedOverWidgetIf1 = var9; // L: 10330 } } diff --git a/runescape-client/src/main/java/Skeleton.java b/runescape-client/src/main/java/Skeleton.java index 66e7b665b8..fbb05c8ac4 100644 --- a/runescape-client/src/main/java/Skeleton.java +++ b/runescape-client/src/main/java/Skeleton.java @@ -109,7 +109,7 @@ public class Skeleton extends Node { Widget var3 = var0[var2]; // L: 10597 if (var3 != null && var3.parentId == var1 && (!var3.isIf3 || !DevicePcmPlayerProvider.isComponentHidden(var3))) { // L: 10598 10599 10600 if (var3.type == 0) { // L: 10601 - if (!var3.isIf3 && DevicePcmPlayerProvider.isComponentHidden(var3) && var3 != EnumDefinition.mousedOverWidgetIf1) { // L: 10602 + if (!var3.isIf3 && DevicePcmPlayerProvider.isComponentHidden(var3) && var3 != EnumComposition.mousedOverWidgetIf1) { // L: 10602 continue; } diff --git a/runescape-client/src/main/java/StructDefinition.java b/runescape-client/src/main/java/StructDefinition.java index ae5985bb0d..6ca0347a2b 100644 --- a/runescape-client/src/main/java/StructDefinition.java +++ b/runescape-client/src/main/java/StructDefinition.java @@ -123,6 +123,6 @@ public class StructDefinition extends DualNode { garbageValue = "-890013246" ) public static void method4545(AbstractArchive var0) { - EnumDefinition.EnumDefinition_archive = var0; // L: 25 + EnumComposition.EnumDefinition_archive = var0; // L: 25 } // L: 26 }