diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
index 1fb85b0145..494489fadc 100644
--- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java
@@ -233,4 +233,36 @@ public final class ScriptID
*/
@ScriptArguments(integer = 2)
public static final int TOPLEVEL_RESIZE = 909;
+
+ /**
+ * Called when the friends list is updated
+ *
+ * - int (WidgetID) Friends list "full container"
+ * - int (WidgetID) Friends list sort by name button
+ * - int (WidgetID) Friends list sort by last world change button
+ * - int (WidgetID) Friends list sort by world button
+ * - int (WidgetID) Friends list legacy sort button
+ * - int (WidgetID) Friends list names container
+ * - int (WidgetID) Friends list scroll bar
+ * - int (WidgetID) Friends list "loading please wait" text
+ * - int (WidgetID) Friends list player previous name holder
+ *
+ */
+ @ScriptArguments(integer = 9)
+ public static final int FRIENDS_UPDATE = 631;
+
+ /**
+ * Called when the ignore list is updated
+ *
+ * - int (WidgetID) Ignore list "full container"
+ * - int (WidgetID) Ignore list sort by name button
+ * - int (WidgetID) Ignore list legacy sort button
+ * - int (WidgetID) Ignore list names container
+ * - int (WidgetID) Ignore list scroll bar
+ * - int (WidgetID) Ignore list "loading please wait" text
+ * - int (WidgetID) Ignore list player previous name holder
+ *
+ */
+ @ScriptArguments(integer = 7)
+ public static final int IGNORE_UPDATE = 630;
}
diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
index 859ea90bda..d957b3f8d5 100644
--- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
+++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java
@@ -215,11 +215,27 @@ public class WidgetID
static class FriendList
{
static final int TITLE = 3;
+ static final int FULL_CONTAINER = 5;
+ static final int SORT_BY_NAME_BUTTON = 7;
+ static final int SORT_BY_LAST_WORLD_CHANGE_BUTTON = 8;
+ static final int SORT_BY_WORLD_BUTTON = 9;
+ static final int LEGACY_SORT_BUTTON = 10;
+ static final int NAMES_CONTAINER = 11;
+ static final int SCROLL_BAR = 12;
+ static final int LOADING_TEXT = 13;
+ static final int PREVIOUS_NAME_HOLDER = 18;
}
static class IgnoreList
{
static final int TITLE = 3;
+ static final int FULL_CONTAINER = 5;
+ static final int SORT_BY_NAME_BUTTON = 7;
+ static final int LEGACY_SORT_BUTTON = 8;
+ static final int NAMES_CONTAINER = 9;
+ static final int SCROLL_BAR = 10;
+ static final int LOADING_TEXT = 11;
+ static final int PREVIOUS_NAME_HOLDER = 16;
}
static class ClanChat
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 dec5f89cf6..ea559f8d37 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
@@ -104,8 +104,24 @@ public enum WidgetInfo
VOLCANIC_MINE_VENT_C_STATUS(WidgetID.VOLCANIC_MINE_GROUP_ID, WidgetID.VolcanicMine.VENT_C_STATUS),
FRIEND_CHAT_TITLE(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.TITLE),
+ FRIEND_LIST_FULL_CONTAINER(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.FULL_CONTAINER),
+ FRIEND_LIST_SORT_BY_NAME_BUTTON(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.SORT_BY_NAME_BUTTON),
+ FRIEND_LIST_SORT_BY_LAST_WORLD_CHANGE_BUTTON(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.SORT_BY_LAST_WORLD_CHANGE_BUTTON),
+ FRIEND_LIST_SORT_BY_WORLD_BUTTON(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.SORT_BY_WORLD_BUTTON),
+ FRIEND_LIST_LEGACY_SORT_BUTTON(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.LEGACY_SORT_BUTTON),
+ FRIEND_LIST_NAMES_CONTAINER(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.NAMES_CONTAINER),
+ FRIEND_LIST_SCROLL_BAR(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.SCROLL_BAR),
+ FRIEND_LIST_LOADING_TEXT(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.LOADING_TEXT),
+ FRIEND_LIST_PREVIOUS_NAME_HOLDER(WidgetID.FRIENDS_LIST_GROUP_ID, WidgetID.FriendList.PREVIOUS_NAME_HOLDER),
IGNORE_TITLE(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.TITLE),
+ IGNORE_FULL_CONTAINER(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.FULL_CONTAINER),
+ IGNORE_SORT_BY_NAME_BUTTON(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.SORT_BY_NAME_BUTTON),
+ IGNORE_LEGACY_SORT_BUTTON(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.LEGACY_SORT_BUTTON),
+ IGNORE_NAMES_CONTAINER(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.NAMES_CONTAINER),
+ IGNORE_SCROLL_BAR(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.SCROLL_BAR),
+ IGNORE_LOADING_TEXT(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.LOADING_TEXT),
+ IGNORE_PREVIOUS_NAME_HOLDER(WidgetID.IGNORE_LIST_GROUP_ID, WidgetID.IgnoreList.PREVIOUS_NAME_HOLDER),
EXPLORERS_RING_ALCH_INVENTORY(WidgetID.EXPLORERS_RING_ALCH_GROUP_ID, WidgetID.ExplorersRing.INVENTORY),
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
index ba0d85cff7..94fe830cc1 100644
--- 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
@@ -29,29 +29,40 @@ 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
@@ -61,13 +72,15 @@ import net.runelite.client.util.Text;
)
public class FriendNotesPlugin extends Plugin
{
- private static final String CONFIG_GROUP = "friendNotes";
+ 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;
@@ -84,19 +97,74 @@ public class FriendNotesPlugin extends Plugin
@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;
+ }
}
/**
@@ -112,6 +180,11 @@ public class FriendNotesPlugin extends Plugin
{
configManager.setConfiguration(CONFIG_GROUP, KEY_PREFIX + displayName, note);
}
+ if (client.getGameState() == GameState.LOGGED_IN)
+ {
+ rebuildFriendsList();
+ rebuildIgnoreList();
+ }
}
/**
@@ -258,4 +331,104 @@ public class FriendNotesPlugin extends Plugin
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/resources/net/runelite/client/plugins/friendnotes/note_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/friendnotes/note_icon.png
new file mode 100644
index 0000000000..9b2625aeb7
Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/friendnotes/note_icon.png differ
diff --git a/runelite-client/src/main/scripts/FriendUpdate.rs2asm b/runelite-client/src/main/scripts/FriendUpdate.rs2asm
index e2d838bd66..69cb250b31 100644
--- a/runelite-client/src/main/scripts/FriendUpdate.rs2asm
+++ b/runelite-client/src/main/scripts/FriendUpdate.rs2asm
@@ -3,6 +3,12 @@
.string_stack_count 0
.int_var_count 16
.string_var_count 2
+; callback "friend_cc_settext"
+; Fired just before the client pops the name off the stack
+; Modified by the friendnotes plugin to show the icon
+; callback "friend_cc_setposition"
+; Fired just before the client sets the position of "friend changed their name" icon
+; Modified by the friendnotes plugin to offset the name changed icon
iload 1
iconst 2
iconst 3
@@ -276,6 +282,8 @@ LABEL218:
add
istore 10
sload 0
+ sconst "friend_cc_settext"
+ runelite_callback
cc_settext
iconst 0
iload 13
@@ -356,6 +364,8 @@ LABEL277:
add
iconst 0
iconst 0
+ sconst "friend_cc_setposition"
+ runelite_callback
cc_setposition 1
iconst 1093
cc_setgraphic 1
diff --git a/runelite-client/src/main/scripts/IgnoreUpdate.rs2asm b/runelite-client/src/main/scripts/IgnoreUpdate.rs2asm
index c50a07f093..a99a72a2f1 100644
--- a/runelite-client/src/main/scripts/IgnoreUpdate.rs2asm
+++ b/runelite-client/src/main/scripts/IgnoreUpdate.rs2asm
@@ -3,6 +3,12 @@
.string_stack_count 0
.int_var_count 13
.string_var_count 2
+; callback "ignore_cc_settext"
+; Fired just before the client pops the name off the stack
+; Modified by the friendnotes plugin to show the icon
+; callback "ignore_cc_setposition"
+; Fired just before the client sets the position of "ignored person changed their name" icon
+; Modified by the friendnotes plugin to offset the name changed icon
iload 1
iconst 2
iconst 3
@@ -133,6 +139,8 @@ LABEL101:
add
istore 8
sload 0
+ sconst "ignore_cc_settext"
+ runelite_callback
cc_settext
iconst 0
iload 10
@@ -190,6 +198,8 @@ LABEL101:
add
iconst 0
iconst 0
+ sconst "ignore_cc_setposition"
+ runelite_callback
cc_setposition 1
iconst 1093
cc_setgraphic 1