From 5ec9961f37536b064a2eeac7ae7d0a8f26fd8504 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 3 Aug 2023 18:22:52 -0400 Subject: [PATCH] client: add chat icons api --- .../runelite/client/game/ChatIconManager.java | 111 +++++++++++++++++- .../chatcommands/ChatCommandsPlugin.java | 49 ++++---- .../client/plugins/emojis/EmojiPlugin.java | 52 ++------ .../friendnotes/FriendNotesPlugin.java | 35 ++---- 4 files changed, 158 insertions(+), 89 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/game/ChatIconManager.java b/runelite-client/src/main/java/net/runelite/client/game/ChatIconManager.java index 2e3fb850e22..11e876c1445 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ChatIconManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ChatIconManager.java @@ -27,10 +27,13 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.image.BufferedImage; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; +import lombok.AllArgsConstructor; import net.runelite.api.Client; import net.runelite.api.EnumComposition; import net.runelite.api.EnumID; @@ -38,7 +41,11 @@ import net.runelite.api.GameState; import net.runelite.api.IndexedSprite; import net.runelite.api.clan.ClanTitle; +import net.runelite.api.events.GameStateChanged; import net.runelite.client.callback.ClientThread; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.util.AsyncBufferedImage; import net.runelite.client.util.ImageUtil; @Singleton @@ -49,6 +56,7 @@ public class ChatIconManager private final Client client; private final SpriteManager spriteManager; + private final ClientThread clientThread; private BufferedImage[] friendsChatRankImages; private BufferedImage[] clanRankImages; @@ -56,22 +64,82 @@ public class ChatIconManager private int friendsChatOffset = -1; private int clanOffset = -1; + private final List icons = new ArrayList<>(); + @Inject - private ChatIconManager(Client client, SpriteManager spriteManager, ClientThread clientThread) + private ChatIconManager(Client client, SpriteManager spriteManager, ClientThread clientThread, EventBus eventBus) { this.client = client; this.spriteManager = spriteManager; + this.clientThread = clientThread; + eventBus.register(this); clientThread.invokeLater(() -> { + // if the client is not booted yet, this will be picked up by the game state change handler below instead if (client.getGameState().getState() >= GameState.LOGIN_SCREEN.getState()) { - loadRankIcons(); - return true; + if (friendsChatOffset == -1) + { + loadRankIcons(); + } + refreshIcons(); } - return false; }); } + @AllArgsConstructor + private static class ChatIcon + { + int idx; + IndexedSprite sprite; + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + var state = gameStateChanged.getGameState(); + if (state == GameState.STARTING) + { + friendsChatOffset = clanOffset = -1; + synchronized (this) + { + for (var icon : icons) + { + icon.idx = -1; + } + } + } + else if (state == GameState.LOGIN_SCREEN) + { + if (friendsChatOffset == -1) + { + loadRankIcons(); + } + refreshIcons(); + } + } + + public synchronized int registerChatIcon(BufferedImage image) + { + if (image == null || image instanceof AsyncBufferedImage) + { + throw new IllegalArgumentException("invalid image"); + } + var i = ImageUtil.getImageIndexedSprite(image, client); + icons.add(new ChatIcon(-1, i)); + clientThread.invokeLater(this::refreshIcons); + return icons.size() - 1; + } + + public int chatIconIndex(int iconId) + { + if (iconId < 0 || iconId >= icons.size()) + { + return -1; + } + return icons.get(iconId).idx; + } + @Nullable public BufferedImage getRankImage(final FriendsChatRank friendsChatRank) { @@ -102,6 +170,41 @@ public int getIconNumber(final ClanTitle clanTitle) return clanOffset == -1 ? -1 : clanOffset + clanRankToIdx(rank); } + private synchronized void refreshIcons() + { + var chatIcons = client.getModIcons(); + final var offset = chatIcons.length; + + int newIcons = 0; + for (var icon : icons) + { + assert icon.idx < offset; + if (icon.idx == -1) + { + ++newIcons; + } + } + + if (newIcons == 0) + { + return; + } + + var newChatIcons = Arrays.copyOf(chatIcons, chatIcons.length + newIcons); + + newIcons = 0; + for (var icon : icons) + { + if (icon.idx == -1) + { + icon.idx = offset + newIcons++; + newChatIcons[icon.idx] = icon.sprite; + } + } + + client.setModIcons(newChatIcons); + } + private void loadRankIcons() { final EnumComposition friendsChatIcons = client.getEnum(EnumID.FRIENDS_CHAT_RANK_ICONS); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index c16ef5a4951..4e6f57e8f63 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -160,7 +160,7 @@ public class ChatCommandsPlugin extends Plugin private int lastBossTime = -1; private double lastPb = -1; private String lastTeamSize; - private int modIconIdx = -1; + private int petsIconIdx = -1; private int[] pets; @Inject @@ -226,22 +226,13 @@ public void startUp() clientThread.invoke(() -> { - // enum config must be loaded for building pet icons - if (client.getModIcons() == null || client.getGameState().getState() < GameState.LOGIN_SCREEN.getState()) + if (client.getGameState().getState() >= GameState.LOGIN_SCREEN.getState()) { - return false; - } - - // !pets requires off thread pets access, so we just store a copy at startup - EnumComposition petsEnum = client.getEnum(EnumID.PETS); - pets = new int[petsEnum.size()]; - for (int i = 0; i < petsEnum.size(); ++i) - { - pets[i] = petsEnum.getIntValue(i); + if (petsIconIdx == -1) + { + loadPets(); + } } - - loadPetIcons(); - return true; }); } @@ -310,18 +301,24 @@ private double getPb(String boss) return personalBest == null ? 0 : personalBest; } - private void loadPetIcons() + private void loadPets() { - if (modIconIdx != -1) + assert client.isClientThread(); + assert petsIconIdx == -1; + + // !pets requires off thread pets access, so we just store a copy + EnumComposition petsEnum = client.getEnum(EnumID.PETS); + pets = new int[petsEnum.size()]; + for (int i = 0; i < petsEnum.size(); ++i) { - return; + pets[i] = petsEnum.getIntValue(i); } final IndexedSprite[] modIcons = client.getModIcons(); assert modIcons != null; final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + pets.length); - modIconIdx = modIcons.length; + petsIconIdx = modIcons.length; client.setModIcons(newModIcons); @@ -330,7 +327,7 @@ private void loadPetIcons() final int petId = pets[i]; final AsyncBufferedImage abi = itemManager.getImage(petId); - final int idx = modIconIdx + i; + final int idx = petsIconIdx + i; Runnable r = () -> { final BufferedImage image = ImageUtil.resizeImage(abi, 18, 16); @@ -864,6 +861,16 @@ public void onGameStateChanged(GameStateChanged event) case HOPPING: pohOwner = null; break; + case STARTING: + petsIconIdx = -1; + pets = null; + break; + case LOGIN_SCREEN: + if (petsIconIdx == -1) + { + loadPets(); + } + break; } } @@ -1323,7 +1330,7 @@ private void petListLookup(ChatMessage chatMessage, String message) final int petId = pets[petIdx]; if (playerPetList.contains(petId)) { - responseBuilder.append(" ").img(modIconIdx + petIdx); + responseBuilder.append(" ").img(petsIconIdx + petIdx); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java index 7b72f1483d1..078efe6fa45 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java @@ -25,24 +25,20 @@ package net.runelite.client.plugins.emojis; import java.awt.image.BufferedImage; -import java.util.Arrays; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.inject.Inject; import joptsimple.internal.Strings; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.IndexedSprite; import net.runelite.api.MessageNode; import net.runelite.api.Player; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.OverheadTextChanged; -import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ChatIconManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.util.ImageUtil; import net.runelite.client.util.Text; @PluginDescriptor( @@ -59,61 +55,38 @@ public class EmojiPlugin extends Plugin private Client client; @Inject - private ClientThread clientThread; + private ChatIconManager chatIconManager; - private int modIconsStart = -1; + private int[] iconIds; @Override protected void startUp() { - clientThread.invoke(() -> - { - if (client.getModIcons() == null) - { - return false; - } - loadEmojiIcons(); - return true; - }); + loadEmojiIcons(); } private void loadEmojiIcons() { - if (modIconsStart != -1) + if (iconIds != null) { return; } - final Emoji[] emojis = Emoji.values(); - final IndexedSprite[] modIcons = client.getModIcons(); - assert modIcons != null; - final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + emojis.length); - modIconsStart = modIcons.length; + var emojis = Emoji.values(); + iconIds = new int[emojis.length]; for (int i = 0; i < emojis.length; i++) { final Emoji emoji = emojis[i]; - - try - { - final BufferedImage image = emoji.loadImage(); - final IndexedSprite sprite = ImageUtil.getImageIndexedSprite(image, client); - newModIcons[modIconsStart + i] = sprite; - } - catch (Exception ex) - { - log.warn("Failed to load the sprite for emoji " + emoji, ex); - } + final BufferedImage image = emoji.loadImage(); + iconIds[i] = chatIconManager.registerChatIcon(image); } - - log.debug("Adding emoji icons"); - client.setModIcons(newModIcons); } @Subscribe public void onChatMessage(ChatMessage chatMessage) { - if (client.getGameState() != GameState.LOGGED_IN || modIconsStart == -1) + if (iconIds == null) { return; } @@ -182,9 +155,8 @@ String updateMessage(final String message) continue; } - final int emojiId = modIconsStart + emoji.ordinal(); - - messageWords[i] = messageWords[i].replace(trigger, ""); + final int emojiId = iconIds[emoji.ordinal()]; + messageWords[i] = messageWords[i].replace(trigger, ""); editedMessage = 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 b8032f9e1e0..f57b235667e 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 @@ -31,7 +31,6 @@ 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; @@ -40,7 +39,6 @@ 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.Nameable; import net.runelite.api.ScriptID; @@ -53,6 +51,7 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.ChatIconManager; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -99,10 +98,13 @@ public class FriendNotesPlugin extends Plugin @Inject private FriendNotesConfig config; + @Inject + private ChatIconManager chatIconManager; + @Getter private HoveredFriend hoveredFriend = null; - private int iconIdx = -1; + private int iconId = -1; private String currentlyLayouting; @Provides @@ -115,15 +117,7 @@ private FriendNotesConfig getConfig(ConfigManager configManager) protected void startUp() throws Exception { overlayManager.add(overlay); - clientThread.invoke(() -> - { - if (client.getModIcons() == null) - { - return false; - } - loadIcon(); - return true; - }); + loadIcon(); if (client.getGameState() == GameState.LOGGED_IN) { rebuildFriendsList(); @@ -305,7 +299,7 @@ public void onRemovedFriend(RemovedFriend event) @Subscribe public void onScriptCallbackEvent(ScriptCallbackEvent event) { - if (!config.showIcons() || iconIdx == -1) + if (!config.showIcons() || iconId == -1) { return; } @@ -320,7 +314,7 @@ public void onScriptCallbackEvent(ScriptCallbackEvent event) currentlyLayouting = sanitized; if (getFriendNote(sanitized) != null) { - stringStack[stringStackSize - 1] = rsn + " "; + stringStack[stringStackSize - 1] = rsn + " "; } break; case "friendsChatSetPosition": @@ -378,7 +372,7 @@ private void rebuildIgnoreList() private void loadIcon() { - if (iconIdx != -1) + if (iconId != -1) { return; } @@ -386,18 +380,11 @@ private void loadIcon() final BufferedImage iconImg = ImageUtil.loadImageResource(getClass(), "note_icon.png"); if (iconImg == null) { - return; + throw new RuntimeException("unable to load icon"); } final BufferedImage resized = ImageUtil.resizeImage(iconImg, ICON_WIDTH, ICON_HEIGHT); - - final IndexedSprite[] modIcons = client.getModIcons(); - assert modIcons != null; - final IndexedSprite[] newIcons = Arrays.copyOf(modIcons, modIcons.length + 1); - newIcons[newIcons.length - 1] = ImageUtil.getImageIndexedSprite(resized, client); - - iconIdx = newIcons.length - 1; - client.setModIcons(newIcons); + iconId = chatIconManager.registerChatIcon(resized); } }