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 2323a15a49..c54ac589ab 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 @@ -24,9 +24,13 @@ */ package net.runelite.client.plugins.raids; +import java.awt.Color; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Keybind; @ConfigGroup("raids") public interface RaidsConfig extends Config @@ -154,6 +158,127 @@ public interface RaidsConfig extends Config @ConfigItem( position = 11, + keyName = "showScavsFarms", + name = "Show scavengers and farming", + description = "Adds scavengers and farming to the room breakdown" + ) + default boolean showScavsFarms() + { + return false; + } + + @ConfigItem( + position = 12, + keyName = "enhanceScouterTitle", + name = "Enhance scouter title", + description = "Adds #combat and good puzzles to scouter title" + ) + default boolean enhanceScouterTitle() + { + return false; + } + + @ConfigItem( + position = 16, + keyName = "showRecommendedItems", + name = "Show recommended items", + description = "Adds overlay with recommended items to scouter" + ) + default boolean showRecommendedItems() + { + return false; + } + + @ConfigItem( + position = 17, + keyName = "recommendedItems", + name = "Recommended items", + description = "User-set recommended items in the form: [muttadiles,ice barrage,zamorak godsword],[tekton,elder maul], ..." + ) + default String recommendedItems() + { + return ""; + } + + @ConfigItem( + position = 18, + keyName = "scavsBeforeIce", + name = "Show last scavs for Ice Demon", + description = "Highlights final scavengers before Ice Demon" + ) + default boolean scavsBeforeIce() + { + return false; + } + + @ConfigItem( + position = 19, + keyName = "scavsBeforeOlm", + name = "Show last scavs for Olm", + description = "Highlights final scavengers before Olm" + ) + default boolean scavsBeforeOlm() + { + return false; + } + + @ConfigItem( + position = 20, + keyName = "scavPrepColor", + name = "Last scavs color", + description = "The color of the final scavs before Ice Demon/Olm" + ) + default Color scavPrepColor() + { + return new Color(130, 222, 255); //light blue + } + + @ConfigItem( + position = 21, + keyName = "alwaysShowWorldAndCC", + name = "Always show CC and World", + description = "The CC and World are not removed from being in the in-game scouter" + ) + default boolean alwaysShowWorldAndCC() + { + return false; + } + + @ConfigItem( + position = 22, + keyName = "colorTightrope", + name = "Color tightrope", + description = "Colors tightrope a separate color" + ) + default boolean colorTightrope() + { + return false; + } + + @ConfigItem( + position = 23, + keyName = "tightropeColor", + name = "Tightrope color", + description = "The color of tightropes" + ) + default Color tightropeColor() + { + return Color.MAGENTA; + } + + @ConfigItem( + position = 24, + keyName = "hideRopeless", + name = "Hide no Tightrope raids", + description = "Completely hides raids with no tightrope" + ) + default boolean hideRopeless() + { + return false; + } + + @ConfigItem( + position = 25, keyName = "layoutMessage", name = "Send raid layout message when entering raid", description = "Sends game message with raid layout on entering new raid" @@ -162,4 +287,15 @@ public interface RaidsConfig extends Config { return true; } + + @ConfigItem( + position = 26, + keyName = "displayFloorBreak", + name = "Layout floor break", + description = "Displays floor break in layout" + ) + default boolean displayFloorBreak() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java index 5ca7214b57..fc149415b1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java @@ -24,12 +24,23 @@ */ package net.runelite.client.plugins.raids; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.Getter; +import lombok.Setter; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import javax.inject.Inject; -import lombok.Setter; import net.runelite.api.Client; +import net.runelite.api.SpriteID; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.SpriteManager; import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; import net.runelite.client.plugins.raids.solver.Room; import net.runelite.client.ui.overlay.Overlay; @@ -37,24 +48,48 @@ 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.ImageComponent; import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.PanelComponent; import net.runelite.client.ui.overlay.components.TitleComponent; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.Text; public class RaidsOverlay extends Overlay { private static final int OLM_PLANE = 0; + private static final int BORDER_OFFSET = 2; + private static final int ICON_SIZE = 32; + private static final int SMALL_ICON_SIZE = 21; + //might need to edit these if they are not standard + private static final int TITLE_COMPONENT_HEIGHT = 20; + private static final int LINE_COMPONENT_HEIGHT = 16; private Client client; private RaidsPlugin plugin; private RaidsConfig config; private final PanelComponent panelComponent = new PanelComponent(); + private final ItemManager itemManager; + private final SpriteManager spriteManager; + private final PanelComponent panelImages = new PanelComponent(); + + @Setter + private boolean sharable = false; @Setter private boolean scoutOverlayShown = false; + @Getter + private boolean scouterActive = false; + + @Getter + private int width; + + @Getter + private int height; + @Inject - private RaidsOverlay(Client client, RaidsPlugin plugin, RaidsConfig config) + private RaidsOverlay(Client client, RaidsPlugin plugin, RaidsConfig config, ItemManager itemManager, SpriteManager spriteManager) { super(plugin); setPosition(OverlayPosition.TOP_LEFT); @@ -62,6 +97,8 @@ public class RaidsOverlay extends Overlay this.client = client; this.plugin = plugin; this.config = config; + this.itemManager = itemManager; + this.spriteManager = spriteManager; getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Raids overlay")); } @@ -73,6 +110,7 @@ public class RaidsOverlay extends Overlay return null; } + scouterActive = false; panelComponent.getChildren().clear(); if (plugin.getRaid() == null || plugin.getRaid().getLayout() == null) @@ -87,25 +125,133 @@ public class RaidsOverlay extends Overlay Color color = Color.WHITE; String layout = plugin.getRaid().getLayout().toCodeString(); + String displayLayout; + if (config.displayFloorBreak()) + { + displayLayout = plugin.getRaid().getLayout().toCode(); + displayLayout = displayLayout.substring(0, displayLayout.length() - 1).replaceAll("#", "").replaceFirst("ยค", " | "); + } + else + { + displayLayout = layout; + } if (config.enableLayoutWhitelist() && !plugin.getLayoutWhitelist().contains(layout.toLowerCase())) { color = Color.RED; } + int combatCount = 0; + int roomCount = 0; + List iceRooms = new ArrayList<>(); + List scavRooms = new ArrayList<>(); + List scavsBeforeIceRooms = new ArrayList<>(); + boolean crabs = false; + boolean iceDemon = false; + boolean tightrope = false; + boolean thieving = false; + String puzzles = ""; + if (config.enhanceScouterTitle() || config.scavsBeforeIce() || sharable) + { + for (Room layoutRoom : plugin.getRaid().getLayout().getRooms()) + { + int position = layoutRoom.getPosition(); + RaidRoom room = plugin.getRaid().getRoom(position); + + if (room == null) + { + continue; + } + + switch (room.getType()) + { + case COMBAT: + combatCount++; + break; + case PUZZLE: + String roomName = room.getPuzzle().getName(); + switch (RaidRoom.Puzzle.fromString(roomName)) + { + case CRABS: + crabs = true; + break; + case ICE_DEMON: + iceDemon = true; + iceRooms.add(roomCount); + break; + case THIEVING: + thieving = true; + break; + case TIGHTROPE: + tightrope = true; + break; + } + break; + case SCAVENGERS: + scavRooms.add(roomCount); + break; + } + roomCount++; + } + if (tightrope) + puzzles = crabs ? "cr" : iceDemon ? "ri" : thieving ? "tr" : "?r"; + else if (config.hideRopeless()) + { + panelComponent.getChildren().add(TitleComponent.builder() + .text("No Tightrope!") + .color(Color.RED) + .build()); + + return panelComponent.render(graphics); + } + + scouterActive = true; + displayLayout = (config.enhanceScouterTitle() ? "" + combatCount + "c " + puzzles + " " : "") + displayLayout; + + for (Integer i : iceRooms) + { + int prev = 0; + for (Integer s : scavRooms) + { + if (s > i) + break; + prev = s; + } + scavsBeforeIceRooms.add(prev); + } + } + int lastScavs = scavRooms.get(scavRooms.size() - 1); panelComponent.getChildren().add(TitleComponent.builder() - .text(layout) + .text(displayLayout) .color(color) .build()); + color = Color.ORANGE; + if (sharable || config.alwaysShowWorldAndCC()) + { + String clanOwner = Text.removeTags(client.getWidget(WidgetInfo.CLAN_CHAT_OWNER).getText()); + if (clanOwner.equals("None")) + { + clanOwner = "Open CC tab..."; + color = Color.RED; + } + panelComponent.getChildren().add(LineComponent.builder() + .left("W" + client.getWorld()) + .right("" + clanOwner) + .leftColor(Color.ORANGE) + .rightColor(color) + .build()); + } int bossMatches = 0; int bossCount = 0; + roomCount = 0; if (config.enableRotationWhitelist()) { bossMatches = plugin.getRotationMatches(); } + Set imageIds = new HashSet<>(); for (Room layoutRoom : plugin.getRaid().getLayout().getRooms()) { int position = layoutRoom.getPosition(); @@ -127,38 +273,144 @@ public class RaidsOverlay extends Overlay color = Color.GREEN; } else if (plugin.getRoomBlacklist().contains(room.getBoss().getName().toLowerCase()) - || config.enableRotationWhitelist() && bossCount > bossMatches) + || config.enableRotationWhitelist() && bossCount > bossMatches) { color = Color.RED; } + String bossName = room.getBoss().getName(); + String bossNameLC = bossName.toLowerCase(); + if (config.showRecommendedItems()) + { + if (plugin.getRecommendedItemsList().get(bossNameLC) != null) + imageIds.addAll(plugin.getRecommendedItemsList().get(bossNameLC)); + } + panelComponent.getChildren().add(LineComponent.builder() - .left(room.getType().getName()) - .right(room.getBoss().getName()) + .left(config.showRecommendedItems() ? "" : room.getType().getName()) + .right(bossName) .rightColor(color) .build()); break; case PUZZLE: - if (plugin.getRoomWhitelist().contains(room.getPuzzle().getName().toLowerCase())) + String puzzleName = room.getPuzzle().getName(); + String puzzleNameLC = puzzleName.toLowerCase(); + if (plugin.getRecommendedItemsList().get(puzzleNameLC) != null) + imageIds.addAll(plugin.getRecommendedItemsList().get(puzzleNameLC)); + if (plugin.getRoomWhitelist().contains(puzzleNameLC)) { color = Color.GREEN; } - else if (plugin.getRoomBlacklist().contains(room.getPuzzle().getName().toLowerCase())) + else if (plugin.getRoomBlacklist().contains(puzzleNameLC)) { color = Color.RED; } + if (config.colorTightrope() && puzzleNameLC.equals("tightrope")) + { + color = config.tightropeColor(); + } panelComponent.getChildren().add(LineComponent.builder() - .left(room.getType().getName()) - .right(room.getPuzzle().getName()) + .left(config.showRecommendedItems() ? "" : room.getType().getName()) + .right(puzzleName) .rightColor(color) .build()); break; + case FARMING: + if (config.showScavsFarms()) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("") + .right(room.getType().getName()) + .rightColor(new Color(181, 230, 29)) //yellow green + .build()); + } + break; + case SCAVENGERS: + if (config.scavsBeforeOlm() && roomCount == lastScavs) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(config.showRecommendedItems() ? "" : "OlmPrep") + .right("Scavs") + .rightColor(config.scavPrepColor()) + .build()); + } + else if (config.scavsBeforeIce() && scavsBeforeIceRooms.contains(roomCount)) + { + panelComponent.getChildren().add(LineComponent.builder() + .left(config.showRecommendedItems() ? "" : "IcePrep") + .right("Scavs") + .rightColor(config.scavPrepColor()) + .build()); + } + else if (config.showScavsFarms()) + { + panelComponent.getChildren().add(LineComponent.builder() + .left("") + .right("Scavs") + .rightColor(new Color(181, 230, 29)) //yellow green + .build()); + } + break; } + roomCount++; } - return panelComponent.render(graphics); + Dimension panelDims = panelComponent.render(graphics); + width = (int) panelDims.getWidth(); + height = (int) panelDims.getHeight(); + + //add recommended items + if (config.showRecommendedItems() && imageIds.size() > 0) + { + panelImages.getChildren().clear(); + Integer[] idArray = imageIds.toArray(new Integer[0]); + int imagesVerticalOffset = TITLE_COMPONENT_HEIGHT + (sharable || config.alwaysShowWorldAndCC() ? LINE_COMPONENT_HEIGHT : 0) - BORDER_OFFSET; + int imagesMaxHeight = height - 2 * BORDER_OFFSET - TITLE_COMPONENT_HEIGHT - (sharable || config.alwaysShowWorldAndCC() ? LINE_COMPONENT_HEIGHT : 0); + boolean smallImages = false; + + panelImages.setPreferredLocation(new Point(0, imagesVerticalOffset)); + panelImages.setBackgroundColor(null); + if (2 * (imagesMaxHeight / ICON_SIZE) >= idArray.length ) + { + panelImages.setWrapping(2); + } + else + { + panelImages.setWrapping(3); + smallImages = true; + } + + panelImages.setOrientation(PanelComponent.Orientation.HORIZONTAL); + for (Integer e : idArray) + { + final BufferedImage image = getImage(e, smallImages); + if (image != null) + { + panelImages.getChildren().add(new ImageComponent(image)); + } + } + + panelImages.render(graphics); + } + return panelDims; + } + + private BufferedImage getImage(int id, boolean small) + { + BufferedImage bim; + if (id != SpriteID.SPELL_ICE_BARRAGE) + bim = itemManager.getImage(id); + else + bim = spriteManager.getSprite(id, 0); + if (bim == null) + return null; + if (!small) + return ImageUtil.resizeCanvas(bim, ICON_SIZE, ICON_SIZE); + if (id != SpriteID.SPELL_ICE_BARRAGE) + return ImageUtil.resizeImage(bim, SMALL_ICON_SIZE, SMALL_ICON_SIZE); + return ImageUtil.resizeCanvas(bim, SMALL_ICON_SIZE, SMALL_ICON_SIZE); } } 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 34db989c71..d2b39880ec 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 @@ -26,10 +26,18 @@ package net.runelite.client.plugins.raids; import com.google.inject.Binder; import com.google.inject.Provides; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; import java.text.DecimalFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; @@ -39,9 +47,11 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.InstanceTemplates; +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.SpriteID; import static net.runelite.api.SpriteID.TAB_QUESTS_BROWN_RAIDING_PARTY; import net.runelite.api.Tile; import net.runelite.api.VarPlayer; @@ -55,21 +65,26 @@ import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ItemManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.SpriteManager; +import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.raids.solver.Layout; import net.runelite.client.plugins.raids.solver.LayoutSolver; import net.runelite.client.plugins.raids.solver.RotationSolver; +import net.runelite.client.ui.DrawManager; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.Text; +import net.runelite.client.util.HotkeyListener; @PluginDescriptor( name = "Chambers Of Xeric", description = "Show helpful information for the Chambers of Xeric raid", - tags = {"combat", "raid", "overlay", "pve", "pvm", "bosses"} + tags = {"combat", "raid", "overlay", "pve", "pvm", "bosses", "cox", "olm"} ) @Slf4j public class RaidsPlugin extends Plugin @@ -82,6 +97,10 @@ public class RaidsPlugin extends Plugin static final DecimalFormat POINTS_FORMAT = new DecimalFormat("#,###"); private static final String SPLIT_REGEX = "\\s*,\\s*"; private static final Pattern ROTATION_REGEX = Pattern.compile("\\[(.*?)]"); + private static final int LINE_COMPONENT_HEIGHT = 16; + + @Inject + private ItemManager itemManager; @Inject private ChatMessageManager chatMessageManager; @@ -92,6 +111,12 @@ public class RaidsPlugin extends Plugin @Inject private Client client; + @Inject + private DrawManager drawManager; + + @Inject + private ScheduledExecutorService executor; + @Inject private RaidsConfig config; @@ -110,6 +135,9 @@ public class RaidsPlugin extends Plugin @Inject private ClientThread clientThread; + @Inject + private KeyManager keyManager; + @Getter private final ArrayList roomWhitelist = new ArrayList<>(); @@ -122,6 +150,9 @@ public class RaidsPlugin extends Plugin @Getter private final ArrayList layoutWhitelist = new ArrayList<>(); + @Getter + private final Map> recommendedItemsList = new HashMap<>(); + @Getter private Raid raid; @@ -346,6 +377,41 @@ public class RaidsPlugin extends Plugin updateList(roomBlacklist, config.blacklistedRooms()); updateList(rotationWhitelist, config.whitelistedRotations()); updateList(layoutWhitelist, config.whitelistedLayouts()); + updateMap(recommendedItemsList, config.recommendedItems()); + } + + private void updateMap(Map> map, String input) + { + map.clear(); + + Matcher m = ROTATION_REGEX.matcher(input); + while (m.find()) + { + String everything = m.group(1).toLowerCase(); + int split = everything.indexOf(','); + if (split < 0) + continue; + String key = everything.substring(0, split); + if (key.length() < 1) + continue; + String[] itemNames = everything.substring(split).split(SPLIT_REGEX); + + map.computeIfAbsent(key, k -> new ArrayList<>()); + + for (String itemName : itemNames) + { + if (itemName.equals("")) + continue; + if (itemName.equals("ice barrage")) + map.get(key).add(SpriteID.SPELL_ICE_BARRAGE); + else if (itemName.startsWith("salve")) + map.get(key).add(ItemID.SALVE_AMULETEI); + else if (itemManager.search(itemName).size() > 0) + map.get(key).add(itemManager.search(itemName).get(0).getId()); + else + log.info("RaidsPlugin: Could not find an item ID for item: " + itemName); + } + } } private void updateList(ArrayList list, String input)