Skip to content

Commit

Permalink
Add clan rank updates highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyborger1 committed Jun 5, 2021
1 parent 1d4b5e6 commit 000eb7f
Show file tree
Hide file tree
Showing 5 changed files with 383 additions and 4 deletions.
212 changes: 212 additions & 0 deletions src/main/java/com/botdetector/BotDetectorClanHighlighter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
* Copyright (c) 2021, Ferrariic, Seltzer Bro, Cyborger1
* 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 com.botdetector;

import com.botdetector.model.CaseInsensitiveString;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.clan.ClanMember;
import net.runelite.api.clan.ClanRank;
import net.runelite.api.clan.ClanSettings;
import net.runelite.api.clan.ClanTitle;
import net.runelite.api.widgets.Widget;
import net.runelite.client.util.Text;

public class BotDetectorClanHighlighter
{
private static final String CLAN_NAME = "Bot Detector";

private static final int HIGHLIGHT_COLOR = 0x00ff00;

private static final int NAME_OFFSET = 1;
private static final int SPRITE_OFFSET = 2;
private static final int WIDGETS_PER_NAME = 3;

private static final Pattern POPUP_TITLE_PLAYER_NAME_PATTERN = Pattern.compile("^Set rank for ([\\w\\-\\s]{1,12}):$");

private static final ImmutableSet<ClanRank> EXCLUDE_RANKS = ImmutableSet.of(
ClanRank.GUEST, ClanRank.OWNER, ClanRank.JMOD
);

@Inject
private Client client;

private Map<CaseInsensitiveString, ClanRank> toHighlight;

protected void startUp()
{
toHighlight = null;
}

protected void shutDown()
{
toHighlight = null;
}

/**
* Gets the left-hand side name list widgets from the clan members interface.
* @return An array of widgets, or {@code null} if the clan members interface is not currently loaded.
*/
private Widget[] getNameWidgets()
{
Widget members = client.getWidget(693, 10);
if (members == null)
{
return null;
}

Widget[] dyn = members.getDynamicChildren();
if (dyn.length % WIDGETS_PER_NAME != 0)
{
return null;
}

return dyn;
}

/**
* Gets the current ranks for the members in the clan. The caller must be in {@link #CLAN_NAME}.
* @return A map of clan member names and their current rank, or {@code null} if the caller is not currently in the correct clan.
*/
public ImmutableMap<CaseInsensitiveString, ClanRank> getClanMemberRanks()
{
ClanSettings cs = client.getClanSettings();
if (cs == null || !CLAN_NAME.equals(cs.getName()))
{
return null;
}

return cs.getMembers().stream().collect(ImmutableMap.toImmutableMap(
cm -> BotDetectorPlugin.normalizeAndWrapPlayerName(cm.getName()), ClanMember::getRank));
}

/**
* Sets {@link #toHighlight}, then calls {@link #updateHighlight()}.
* @param toHighlight The map of clan members to highlight and the rank they should be.
*/
public void setHighlight(Map<CaseInsensitiveString, ClanRank> toHighlight)
{
this.toHighlight = toHighlight;
updateHighlight();
}

/**
* Highlights the players that need their ranks changed according to {@link #toHighlight},
* assuming the clan members interface is currently loaded. If the rank changer popup is up,
* the correct rank to set will also be highlighted.
* The caller must be in {@link #CLAN_NAME}, also see {@link #setHighlight(Map)}.
*/
public void updateHighlight()
{
if (toHighlight == null)
{
return;
}

ClanSettings cs = client.getClanSettings();
if (cs == null || !CLAN_NAME.equals(cs.getName()))
{
return;
}

ImmutableMap<CaseInsensitiveString, ClanRank> currentRanks = getClanMemberRanks();
if (currentRanks == null)
{
return;
}

Widget[] nameWidgets = getNameWidgets();
if (nameWidgets == null)
{
return;
}

Map<CaseInsensitiveString, String> checkPopupNames = new HashMap<>();
for (int i = 0; i < nameWidgets.length; i += WIDGETS_PER_NAME)
{
Widget nameWidget = nameWidgets[i + NAME_OFFSET];
CaseInsensitiveString name = BotDetectorPlugin.normalizeAndWrapPlayerName(nameWidget.getText());
ClanRank newRank = toHighlight.get(name);
if (newRank == null || newRank == currentRanks.get(name) || EXCLUDE_RANKS.contains(newRank))
{
continue;
}

ClanTitle title = cs.titleForRank(newRank);
if (title == null)
{
continue;
}

nameWidget.setTextColor(HIGHLIGHT_COLOR);
checkPopupNames.put(name, title.getName());
}

// Highlight correct rank in popup if present
Widget popupTitle = client.getWidget(289, 4);
Widget popupRanks = client.getWidget(289, 6);
if (popupTitle == null || popupRanks == null)
{
return;
}

Widget[] popupRanksDyn = popupRanks.getDynamicChildren();
if (popupRanks.getDynamicChildren().length % WIDGETS_PER_NAME != 0)
{
return;
}

Matcher match = POPUP_TITLE_PLAYER_NAME_PATTERN.matcher(popupTitle.getChild(1).getText());
if (!match.matches())
{
return;
}

CaseInsensitiveString popupName = BotDetectorPlugin.normalizeAndWrapPlayerName(match.group(1));

String highlightRank = checkPopupNames.get(popupName);
if (highlightRank == null)
{
return;
}

for (int i = 0; i < popupRanksDyn.length; i += WIDGETS_PER_NAME)
{
Widget w = popupRanksDyn[i + NAME_OFFSET];
if (highlightRank.equals(Text.removeTags(w.getText())))
{
w.setTextColor(HIGHLIGHT_COLOR);
break;
}
}
}
}
72 changes: 72 additions & 0 deletions src/main/java/com/botdetector/BotDetectorPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import net.runelite.api.MenuEntry;
import net.runelite.api.Player;
import net.runelite.api.WorldType;
import net.runelite.api.clan.ClanRank;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.CommandExecuted;
Expand All @@ -82,9 +83,11 @@
import net.runelite.api.events.MenuOpened;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.PlayerSpawned;
import net.runelite.api.events.ScriptPostFired;
import net.runelite.api.events.WorldChanged;
import net.runelite.api.kit.KitType;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatCommandManager;
import net.runelite.client.chat.ChatMessageBuilder;
Expand Down Expand Up @@ -158,6 +161,7 @@ public class BotDetectorPlugin extends Plugin
private static final String CLEAR_AUTH_TOKEN_COMMAND = COMMAND_PREFIX + "ClearToken";
private static final String TOGGLE_SHOW_DISCORD_VERIFICATION_ERRORS_COMMAND = COMMAND_PREFIX + "ToggleShowDiscordVerificationErrors";
private static final String TOGGLE_SHOW_DISCORD_VERIFICATION_ERRORS_COMMAND_ALIAS = COMMAND_PREFIX + "ToggleDVE";
private static final String GET_CLAN_RANK_UPDATES_COMMAND = COMMAND_PREFIX + "GetRankUpdates";

/** Command to method map to be used in {@link #onCommandExecuted(CommandExecuted)}. **/
private final ImmutableMap<CaseInsensitiveString, Consumer<String[]>> commandConsumerMap =
Expand All @@ -171,6 +175,7 @@ public class BotDetectorPlugin extends Plugin
.put(wrap(CLEAR_AUTH_TOKEN_COMMAND), s -> clearAuthTokenCommand())
.put(wrap(TOGGLE_SHOW_DISCORD_VERIFICATION_ERRORS_COMMAND), s -> toggleShowDiscordVerificationErrors())
.put(wrap(TOGGLE_SHOW_DISCORD_VERIFICATION_ERRORS_COMMAND_ALIAS), s -> toggleShowDiscordVerificationErrors())
.put(wrap(GET_CLAN_RANK_UPDATES_COMMAND), s -> getClanRankUpdatesCommand())
.build();

private static final int MANUAL_FLUSH_COOLDOWN_SECONDS = 60;
Expand All @@ -184,6 +189,9 @@ public class BotDetectorPlugin extends Plugin
@Inject
private Client client;

@Inject
private ClientThread clientThread;

@Inject
private MenuManager menuManager;

Expand All @@ -208,6 +216,9 @@ public class BotDetectorPlugin extends Plugin
@Inject
private BotDetectorClient detectorClient;

@Inject
private BotDetectorClanHighlighter clanHighlighter;

private BotDetectorPanel panel;
private NavigationButton navButton;

Expand Down Expand Up @@ -336,6 +347,8 @@ protected void startUp()
previousTwoGameStates.offer(client.getGameState());

chatCommandManager.registerCommand(VERIFY_DISCORD_COMMAND, this::verifyDiscord);

clanHighlighter.startUp();
}

@Override
Expand Down Expand Up @@ -366,6 +379,8 @@ protected void shutDown()
previousTwoGameStates.clear();

chatCommandManager.unregisterCommand(VERIFY_DISCORD_COMMAND);

clanHighlighter.shutDown();
}

/**
Expand Down Expand Up @@ -881,6 +896,17 @@ private void onWorldChanged(WorldChanged event)
processCurrentWorld();
}

@Subscribe
private void onScriptPostFired(ScriptPostFired event)
{
// If clan names list updates (4253)
// or clan rank selection pops up (4316)
if (event.getScriptId() == 4253 || event.getScriptId() == 4316)
{
clanHighlighter.updateHighlight();
}
}

/**
* Opens the plugin panel and sends over {@code playerName} to {@link BotDetectorPanel#predictPlayer(String)} for prediction.
* @param playerName The player name to predict.
Expand Down Expand Up @@ -1182,6 +1208,52 @@ private void toggleShowDiscordVerificationErrors()
}
}

/**
* Gets the current clan members and their ranks, sends them over to {@link BotDetectorClient#requestClanRankUpdates(String, Map)}
* to get players that need their ranks updated and then highlights them on the clan members interface using {@link #clanHighlighter}.
*/
private void getClanRankUpdatesCommand()
{
if (!authToken.getTokenType().getPermissions().contains(AuthTokenPermission.GET_CLAN_RANK_UPDATES))
{
sendChatStatusMessage("The currently set auth token does not permit this command.", true);
return;
}

Map<CaseInsensitiveString, ClanRank> ranks = clanHighlighter.getClanMemberRanks();
if (ranks == null || ranks.size() == 0)
{
sendChatStatusMessage("Could not get members/rank list from clan settings.", true);
return;
}

detectorClient.requestClanRankUpdates(authToken.getToken(), ranks).whenComplete((newRanks, ex) ->
{
if (ex == null)
{
int size = newRanks != null ? newRanks.size() : 0;
if (size == 0)
{
sendChatStatusMessage("No clan ranks to update.", true);
clientThread.invokeLater(() -> clanHighlighter.setHighlight(null));
}
else
{
sendChatStatusMessage("Received " + size + " clan rank updates from the API.", true);
clientThread.invokeLater(() -> clanHighlighter.setHighlight(newRanks));
}
}
else if (ex instanceof UnauthorizedTokenException)
{
sendChatStatusMessage("Invalid token for getting clan rank updates.", true);
}
else
{
sendChatStatusMessage("Error getting clan rank updates from the API.", true);
}
});
}

//endregion


Expand Down
Loading

0 comments on commit 000eb7f

Please sign in to comment.