Skip to content

Commit

Permalink
client: add chat icons api
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam- committed Aug 3, 2023
1 parent ee8b354 commit 5ec9961
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,25 @@
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;
import net.runelite.api.FriendsChatRank;
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
Expand All @@ -49,29 +56,90 @@ public class ChatIconManager

private final Client client;
private final SpriteManager spriteManager;
private final ClientThread clientThread;

private BufferedImage[] friendsChatRankImages;
private BufferedImage[] clanRankImages;

private int friendsChatOffset = -1;
private int clanOffset = -1;

private final List<ChatIcon> 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)
{
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
});
}

Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
}
Expand Down Expand Up @@ -182,9 +155,8 @@ String updateMessage(final String message)
continue;
}

final int emojiId = modIconsStart + emoji.ordinal();

messageWords[i] = messageWords[i].replace(trigger, "<img=" + emojiId + ">");
final int emojiId = iconIds[emoji.ordinal()];
messageWords[i] = messageWords[i].replace(trigger, "<img=" + chatIconManager.chatIconIndex(emojiId) + ">");
editedMessage = true;
}

Expand Down
Loading

0 comments on commit 5ec9961

Please sign in to comment.