diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java index d69e974ad9..6911123fce 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java @@ -406,4 +406,15 @@ public interface RaidsConfig extends Config { return false; } + + @ConfigItem( + position = 30, + keyName = "partyDisplay", + name = "Party Info Display", + description = "Display an overlay that shows information about the current party" + ) + default boolean partyDisplay() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPartyOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPartyOverlay.java new file mode 100644 index 0000000000..a634f48c27 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPartyOverlay.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2019, Bjornenalfa + * 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.raids; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Color; +import java.util.Set; +import javax.inject.Inject; + +import net.runelite.api.*; + +import net.runelite.client.ui.overlay.Overlay; + +import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; + +import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.PanelComponent; + +public class RaidsPartyOverlay extends Overlay +{ + public static final String PARTY_OVERLAY_RESET = "Reset missing"; + public static final String PARTY_OVERLAY_REFRESH = "Refresh party"; + + @Inject + private Client client; + + @Inject + private RaidsPlugin plugin; + + private final PanelComponent panel = new PanelComponent(); + + @Inject + private RaidsPartyOverlay(RaidsPlugin plugin) + { + super(plugin); + setPosition(OverlayPosition.TOP_RIGHT); + setPriority(OverlayPriority.HIGH); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Raids party overlay")); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, PARTY_OVERLAY_RESET, "Raids party overlay")); + getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, PARTY_OVERLAY_REFRESH, "Raids party overlay")); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isInRaidChambers()) + { + return null; + } + + if (client.getClanChatCount() == 0) + { + // Player left clan chat + return null; + } + + boolean inLobby = client.getVar(VarPlayer.IN_RAID_PARTY) != -1; // -1 if raid started + + int partySize = client.getVar(Varbits.RAID_PARTY_SIZE); + + int playerCount = client.getPlayers().size(); + + String partyCountString; + + Color countColor = Color.WHITE; + if (inLobby) + { + partyCountString = String.format("%d/%d", playerCount, partySize); + // While we are in the lobby compare to players visible on the screen + if (partySize <= playerCount) + { + countColor = Color.GREEN; + } + else + { + countColor = Color.RED; + } + } + else + { + // If raid has started then we compare the current party size to what it was when we started + partyCountString = String.format("%d/%d", partySize, plugin.getStartPlayerCount()); + if (plugin.getMissingPartyMembers().size() > 0) + { + countColor = Color.RED; // Somebody is missing + } + } + + panel.getChildren().clear(); + + // Show amount of people currently in raid vs total in the party + panel.getChildren().add(LineComponent.builder() + .left("Party size:") + .right(partyCountString) + .rightColor(countColor) + .build()); + + if (inLobby) + { + int world = client.getWorld(); + int wrongWorldClanMembers = 0; + int clanMemberCount = 0; + for (ClanMember clanMember : client.getClanMembers()) + { + if (clanMember != null) + { + if (clanMember.getWorld() != world) + { + wrongWorldClanMembers++; + } + else + { + clanMemberCount++; + } + } + } + + // Show amount of people on the right world but not at the raids area + Color notInPartyColor = Color.GREEN; + int notInParty = clanMemberCount - partySize; + + if (notInParty > 0) + { + notInPartyColor = Color.WHITE; + } + + panel.getChildren().add(LineComponent.builder() + .left("Not at raids:") + .right(String.valueOf(notInParty)) + .rightColor(notInPartyColor) + .build()); + + // Show amount of clan members that are not in the right world. + Color wrongWorldColor; + if (wrongWorldClanMembers == 0) + { + wrongWorldColor = Color.GREEN; + } + else + { + wrongWorldColor = Color.WHITE; + } + + panel.getChildren().add(LineComponent.builder() + .left("Wrong world:") + .right(String.valueOf(wrongWorldClanMembers)) + .rightColor(wrongWorldColor) + .build()); + + } + else + { + Set missingPartyMembers = plugin.getMissingPartyMembers(); + if (missingPartyMembers.size() > 0) + { + panel.getChildren().add(LineComponent.builder() + .left("Missing players:") + .build()); + + for (String member : missingPartyMembers) + { + panel.getChildren().add(LineComponent.builder() + .left(member) + .leftColor(Color.RED) + .build()); + } + } + } + + return panel.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java index 0c8cb9cbae..3dd467af09 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.raids; +import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Provides; import java.awt.image.BufferedImage; @@ -49,11 +50,13 @@ import net.runelite.api.ItemID; import net.runelite.api.NullObjectID; import static net.runelite.api.Perspective.SCENE_SIZE; import net.runelite.api.Point; +import net.runelite.api.Player; import net.runelite.api.SpriteID; import static net.runelite.api.SpriteID.TAB_QUESTS_BROWN_RAIDING_PARTY; import net.runelite.api.Tile; import net.runelite.api.VarPlayer; import net.runelite.api.Varbits; +import net.runelite.api.MenuAction; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ClientTick; import net.runelite.api.events.ConfigChanged; @@ -69,6 +72,7 @@ import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; +import net.runelite.client.events.OverlayMenuClicked; import net.runelite.client.game.SpriteManager; import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; @@ -82,12 +86,15 @@ import net.runelite.client.ui.DrawManager; import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.WidgetOverlay; +import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.ui.overlay.tooltip.Tooltip; import net.runelite.client.ui.overlay.tooltip.TooltipManager; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.Text; import org.apache.commons.lang3.StringUtils; +import java.util.HashSet; +import java.util.Set; @PluginDescriptor( name = "Chambers Of Xeric", @@ -142,6 +149,9 @@ public class RaidsPlugin extends Plugin @Inject private RaidsPointsOverlay pointsOverlay; + @Inject + private RaidsPartyOverlay partyOverlay; + @Inject private LayoutSolver layoutSolver; @@ -191,6 +201,18 @@ public class RaidsPlugin extends Plugin private NavigationButton navButton; private RaidsTimer timer; + @Getter + private int startPlayerCount; + + @Getter + private List partyMembers = new ArrayList<>(); + + @Getter + private List startingPartyMembers = new ArrayList<>(); + + @Getter + private Set missingPartyMembers = new HashSet<>(); + @Provides RaidsConfig provideConfig(ConfigManager configManager) { @@ -208,6 +230,10 @@ public class RaidsPlugin extends Plugin { overlayManager.add(overlay); overlayManager.add(pointsOverlay); + if (config.partyDisplay()) + { + overlayManager.add(partyOverlay); + } updateLists(); clientThread.invokeLater(() -> checkRaidPresence(true)); widgetOverlay = overlayManager.getWidgetOverlay(WidgetInfo.RAIDS_POINTS_INFOBOX); @@ -229,6 +255,10 @@ public class RaidsPlugin extends Plugin overlayManager.remove(overlay); overlayManager.remove(pointsOverlay); clientToolbar.removeNavigation(navButton); + if (config.partyDisplay()) + { + overlayManager.remove(partyOverlay); + } infoBoxManager.removeInfoBox(timer); inRaidChambers = false; widgetOverlay = null; @@ -257,6 +287,18 @@ public class RaidsPlugin extends Plugin return; } + if (event.getKey().equals("partyDisplay")) + { + if (config.partyDisplay()) + { + overlayManager.add(partyOverlay); + } + else + { + overlayManager.remove(partyOverlay); + } + } + updateLists(); clientThread.invokeLater(() -> checkRaidPresence(true)); } @@ -281,6 +323,10 @@ public class RaidsPlugin extends Plugin public void onVarbitChanged(VarbitChanged event) { checkRaidPresence(false); + if (config.partyDisplay()) + { + updatePartyMembers(false); + } } @Subscribe @@ -291,10 +337,27 @@ public class RaidsPlugin extends Plugin String message = Text.removeTags(event.getMessage()); Matcher matcher; - if (config.raidsTimer() && message.startsWith(RAID_START_MESSAGE)) + if (message.startsWith(RAID_START_MESSAGE)) { - timer = new RaidsTimer(spriteManager.getSprite(TAB_QUESTS_BROWN_RAIDING_PARTY, 0), this, Instant.now()); - infoBoxManager.addInfoBox(timer); + if (config.raidsTimer()) + { + timer = new RaidsTimer(spriteManager.getSprite(TAB_QUESTS_BROWN_RAIDING_PARTY, 0), this, Instant.now()); + infoBoxManager.addInfoBox(timer); + } + if (config.partyDisplay()) + { + // Base this on visible players since party size shows people outside the lobby + // and they did not get to come on the raid + List players = client.getPlayers(); + startPlayerCount = players.size(); + + partyMembers.clear(); + startingPartyMembers.clear(); + missingPartyMembers.clear(); + + startingPartyMembers.addAll(Lists.transform(players, Player::getName)); + partyMembers.addAll(startingPartyMembers); + } } if (timer != null && message.contains(LEVEL_COMPLETE_MESSAGE)) @@ -415,7 +478,95 @@ public class RaidsPlugin extends Plugin } } - public void checkRaidPresence(boolean force) + + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked event) + { + OverlayMenuEntry entry = event.getEntry(); + if (entry.getMenuAction() == MenuAction.RUNELITE_OVERLAY && + entry.getTarget().equals("Raids party overlay")) + { + switch (entry.getOption()) + { + case RaidsPartyOverlay.PARTY_OVERLAY_RESET: + startingPartyMembers.clear(); + updatePartyMembers(true); + missingPartyMembers.clear(); + break; + case RaidsPartyOverlay.PARTY_OVERLAY_REFRESH: + updatePartyMembers(true); + break; + default: + break; + } + } + } + + private void updatePartyMembers(boolean force) + { + int partySize = client.getVar(Varbits.RAID_PARTY_SIZE); + if (partySize <= 0) + { + return; + } + + if (startingPartyMembers.size() == partySize && !force) + { + // Skip update if the part is as big as when we started + missingPartyMembers.clear(); // Clear missing members in case someone came back + return; + } + + // Only update while in raid + if (client.getVar(VarPlayer.IN_RAID_PARTY) == -1 || force) + { + Widget[] widgets; + try + { + widgets = client.getWidget(WidgetInfo.RAIDING_PARTY).getStaticChildren()[2].getStaticChildren()[3].getDynamicChildren(); + } + catch (NullPointerException e) + { + return; // Raid widget not loaded + } + + partyMembers.clear(); + for (int i = 0; i < widgets.length; i++) + { + if (widgets[i] != null) + { + // Party members names can be found as a color tagged string in every fourth(ish) of these children + String name = widgets[i].getName(); + if (name.length() > 1) + { + // Clean away tag + partyMembers.add(name.substring(name.indexOf('>') + 1, name.indexOf('<', 1))); + } + } + } + + // If we don't have any starting members, update starting members + if (startingPartyMembers.size() == 0 || force) + { + missingPartyMembers.clear(); + startingPartyMembers.clear(); + startingPartyMembers.addAll(partyMembers); + } + else + { + + // Check if anyone left + if (startingPartyMembers.size() > partyMembers.size()) + { + missingPartyMembers.clear(); + missingPartyMembers.addAll(startingPartyMembers); + missingPartyMembers.removeAll(partyMembers); + } + } + } + } + + private void checkRaidPresence(boolean force) { if (client.getGameState() != GameState.LOGGED_IN) {