Add clan ranks to clan chat

- Extend API to include support for getting clan members and
informations about them
- Add API for manipulation with modIcons
- Display clan ranks in clan chat (add to ClanChat plugin)
- Add new hook for clan members changed

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
This commit is contained in:
Tomas Slusny
2017-12-04 19:28:57 -05:00
committed by Adam
parent e1623f239a
commit 454fd4cfc1
24 changed files with 556 additions and 7 deletions

View File

@@ -157,6 +157,12 @@ public class Hooks
eventBus.post(varbitChanged);
break;
}
case "clanMembersChanged":
{
ClanMembersChanged clanMembersChanged = new ClanMembersChanged();
eventBus.post(clanMembersChanged);
break;
}
case "resizeChanged":
{
//maybe couple with varbitChanged. resizeable may not be a varbit but it would fit with the other client settings.

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.events;
import lombok.Data;
@Data
public class ClanMembersChanged
{
}

View File

@@ -45,9 +45,9 @@ import net.runelite.api.ItemComposition;
import net.runelite.api.MessageNode;
import net.runelite.api.Varbits;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.events.SetMessage;
import net.runelite.client.events.ResizeableChanged;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.ResizeableChanged;
import net.runelite.client.events.SetMessage;
import net.runelite.client.events.VarbitChanged;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.clanchat;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup(
keyName = "clanchat",
name = "Clan Chat",
description = "Configuration for clan chat"
)
public interface ClanChatConfig extends Config
{
@ConfigItem(
keyName = "clanRank",
name = "Show Clan Ranks Icon",
description = "Configures whether the clan ranks icons are shown next to name in chat"
)
default boolean clanRank()
{
return true;
}
}

View File

@@ -24,13 +24,37 @@
*/
package net.runelite.client.plugins.clanchat;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.ClanMember;
import net.runelite.api.ClanMemberRank;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.IndexedSprite;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.events.ClanMembersChanged;
import net.runelite.client.events.GameStateChanged;
import net.runelite.client.events.SetMessage;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.task.Schedule;
@@ -38,12 +62,43 @@ import net.runelite.client.task.Schedule;
@PluginDescriptor(
name = "Clan chat plugin"
)
@Slf4j
public class ClanChatPlugin extends Plugin
{
private static final String[] CLANCHAT_IMAGES =
{
"Friend_clan_rank.png", "Recruit_clan_rank.png",
"Corporal_clan_rank.png", "Sergeant_clan_rank.png",
"Lieutenant_clan_rank.png", "Captain_clan_rank.png",
"General_clan_rank.png", "Owner_clan_rank.png"
};
private final Map<String, ClanMemberRank> clanRanksCache = new HashMap<>();
private int modIconsLength;
@Inject
@Nullable
Client client;
@Inject
ClanChatConfig config;
@Provides
ClanChatConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(ClanChatConfig.class);
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN)
{
// this is after "Loading sprites" so we can modify modicons now
loadClanChatIcons();
}
}
@Schedule(
period = 600,
unit = ChronoUnit.MILLIS
@@ -61,4 +116,168 @@ public class ClanChatPlugin extends Plugin
clanChatTitleWidget.setText("Clan Chat (" + client.getClanChatCount() + "/100)");
}
}
@Schedule(
period = 2,
unit = ChronoUnit.MINUTES
)
public void cacheClanMemberRanks()
{
if (client.getGameState() != GameState.LOGGED_IN || !config.clanRank())
{
return;
}
clanRanksCache.clear();
final ClanMember[] clanMembersArr = client.getClanMembers();
if (clanMembersArr == null || clanMembersArr.length == 0)
{
return;
}
final Set<ClanMember> clanMembers = Arrays.stream(clanMembersArr)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
final int clanMembersSize = clanMembers.size();
if (clanMembersSize == 0)
{
return;
}
log.debug("Caching clan members...");
for (ClanMember clanMember : clanMembers)
{
final String name = sanitize(clanMember.getUsername());
final ClanMemberRank rank = clanMember.getRank();
if (rank != null)
{
clanRanksCache.put(name, rank);
}
}
}
@Subscribe
public void onClanMembersChanged(ClanMembersChanged event)
{
cacheClanMemberRanks();
}
@Subscribe
public void onSetMessage(SetMessage setMessage)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return;
}
if (config.clanRank() && setMessage.getType() == ChatMessageType.CLANCHAT)
{
insertClanRankIcon(setMessage);
}
}
private void loadClanChatIcons()
{
try
{
final IndexedSprite[] modIcons = client.getModIcons();
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + CLANCHAT_IMAGES.length);
int curPosition = newModIcons.length - CLANCHAT_IMAGES.length;
for (String resource : CLANCHAT_IMAGES)
{
IndexedSprite sprite = createIndexedSprite(resource);
newModIcons[curPosition++] = sprite;
}
client.setModIcons(newModIcons);
modIconsLength = newModIcons.length;
}
catch (IOException e)
{
log.warn("Failed loading of clan chat icons", e);
modIconsLength = 0;
}
}
private IndexedSprite createIndexedSprite(final String imagePath) throws IOException
{
final BufferedImage bufferedImage = rgbaToIndexedBufferedImage(ImageIO
.read(this.getClass().getResource(imagePath)));
final IndexColorModel indexedCM = (IndexColorModel) bufferedImage.getColorModel();
final int width = bufferedImage.getWidth();
final int height = bufferedImage.getHeight();
final byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
final int[] palette = new int[indexedCM.getMapSize()];
indexedCM.getRGBs(palette);
final IndexedSprite newIndexedSprite = client.createIndexedSprite();
newIndexedSprite.setPixels(pixels);
newIndexedSprite.setPalette(palette);
newIndexedSprite.setWidth(width);
newIndexedSprite.setHeight(height);
newIndexedSprite.setOriginalWidth(width);
newIndexedSprite.setOriginalHeight(height);
newIndexedSprite.setOffsetX(0);
newIndexedSprite.setOffsetY(0);
return newIndexedSprite;
}
private static BufferedImage rgbaToIndexedBufferedImage(final BufferedImage sourceBufferedImage)
{
final BufferedImage indexedImage = new BufferedImage(
sourceBufferedImage.getWidth(),
sourceBufferedImage.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED);
final ColorModel cm = indexedImage.getColorModel();
final IndexColorModel icm = (IndexColorModel) cm;
final int size = icm.getMapSize();
final byte[] reds = new byte[size];
final byte[] greens = new byte[size];
final byte[] blues = new byte[size];
icm.getReds(reds);
icm.getGreens(greens);
icm.getBlues(blues);
final WritableRaster raster = indexedImage.getRaster();
final int pixel = raster.getSample(0, 0, 0);
final IndexColorModel resultIcm = new IndexColorModel(8, size, reds, greens, blues, pixel);
final BufferedImage resultIndexedImage = new BufferedImage(resultIcm, raster, sourceBufferedImage.isAlphaPremultiplied(), null);
resultIndexedImage.getGraphics().drawImage(sourceBufferedImage, 0, 0, null);
return resultIndexedImage;
}
private void insertClanRankIcon(final SetMessage message)
{
final String playerName = sanitize(message.getName());
final ClanMemberRank rank = clanRanksCache.get(playerName);
if (rank != null && rank != ClanMemberRank.UNRANKED)
{
int iconNumber = getIconNumber(rank);
message.getMessageNode()
.setSender(message.getMessageNode().getSender() + " <img=" + iconNumber + ">");
client.refreshChat();
}
}
private int getIconNumber(final ClanMemberRank clanMemberRank)
{
return modIconsLength - CLANCHAT_IMAGES.length + clanMemberRank.getValue();
}
private static String sanitize(String lookup)
{
final String cleaned = lookup.contains("<img") ? lookup.substring(lookup.lastIndexOf('>') + 1) : lookup;
return cleaned.replace('\u00A0', ' ');
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B