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 edd309e661..cdfc5e943f 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 @@ -33,7 +33,6 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -180,20 +179,18 @@ public class RaidsOverlay extends Overlay { case COMBAT: combatCount++; - roomName = room.getBoss().getName(); - switch (Objects.requireNonNull(RaidRoom.Boss.fromString(roomName))) + switch (room) { case VANGUARDS: vanguards = true; break; - case UNKNOWN: + case UNKNOWN_COMBAT: unknownCombat = true; break; } break; case PUZZLE: - roomName = room.getPuzzle().getName(); - switch (Objects.requireNonNull(RaidRoom.Puzzle.fromString(roomName))) + switch (room) { case CRABS: crabs = true; @@ -316,17 +313,17 @@ public class RaidsOverlay extends Overlay { case COMBAT: bossCount++; - if (plugin.getRoomWhitelist().contains(room.getBoss().getName().toLowerCase())) + if (plugin.getRoomWhitelist().contains(room.getName().toLowerCase())) { color = Color.GREEN; } - else if (plugin.getRoomBlacklist().contains(room.getBoss().getName().toLowerCase()) + else if (plugin.getRoomBlacklist().contains(room.getName().toLowerCase()) || plugin.isEnableRotationWhitelist() && bossCount > bossMatches) { color = Color.RED; } - String bossName = room.getBoss().getName(); + String bossName = room.getName(); String bossNameLC = bossName.toLowerCase(); if (plugin.isShowRecommendedItems() && plugin.getRecommendedItemsList().get(bossNameLC) != null) { @@ -338,7 +335,7 @@ public class RaidsOverlay extends Overlay break; case PUZZLE: - String puzzleName = room.getPuzzle().getName(); + String puzzleName = room.getName(); String puzzleNameLC = puzzleName.toLowerCase(); if (plugin.getRecommendedItemsList().get(puzzleNameLC) != null) { 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 864e1712bf..4f8f0b10db 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,20 +25,24 @@ */ package net.runelite.client.plugins.raids; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Provides; import java.awt.Color; import java.awt.image.BufferedImage; +import java.io.IOException; import java.text.DecimalFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; @@ -46,18 +50,19 @@ import javax.inject.Singleton; import lombok.AccessLevel; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import static net.runelite.api.Perspective.SCENE_SIZE; +import static net.runelite.api.SpriteID.TAB_QUESTS_BROWN_RAIDING_PARTY; 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.MenuOpcode; +import net.runelite.api.MessageNode; import net.runelite.api.NullObjectID; -import static net.runelite.api.Perspective.SCENE_SIZE; import net.runelite.api.Player; 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; import net.runelite.api.Varbits; @@ -70,12 +75,14 @@ import net.runelite.api.widgets.Widget; 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; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ChatInput; import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.OverlayMenuClicked; import net.runelite.client.game.ItemManager; @@ -98,8 +105,11 @@ import net.runelite.client.util.ImageUtil; import net.runelite.client.ws.PartyMember; import net.runelite.client.ws.PartyService; import net.runelite.client.ws.WSClient; +import net.runelite.http.api.chat.ChatClient; +import net.runelite.http.api.chat.LayoutRoom; import net.runelite.http.api.ws.messages.party.PartyChatMessage; import org.apache.commons.lang3.StringUtils; +import static net.runelite.api.util.Text.sanitize; import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; @PluginDescriptor( @@ -121,6 +131,7 @@ public class RaidsPlugin extends Plugin private static final String RAID_COMPLETE_MESSAGE = "Congratulations - your raid is complete!"; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###.##"); private static final Pattern ROTATION_REGEX = Pattern.compile("\\[(.*?)]"); + private static final String LAYOUT_COMMAND = "!layout"; private static final Pattern RAID_COMPLETE_REGEX = Pattern.compile("Congratulations - your raid is complete! Duration: ([0-9:]+)"); private static final ImmutableSet GOOD_CRABS_FIRST = ImmutableSet.of( "FSCCP.PCSCF - #WNWSWN#ESEENW", //both good crabs @@ -226,6 +237,15 @@ public class RaidsPlugin extends Plugin @Inject private WSClient ws; + @Inject + private ChatCommandManager chatCommandManager; + + @Inject + private ChatClient chatClient; + + @Inject + private ScheduledExecutorService scheduledExecutorService; + @Getter private final List roomWhitelist = new ArrayList<>(); @@ -316,6 +336,7 @@ public class RaidsPlugin extends Plugin } updateLists(); clientThread.invokeLater(() -> checkRaidPresence(true)); + chatCommandManager.registerCommandAsync(LAYOUT_COMMAND, this::lookupRaid, this::submitRaid); widgetOverlay = overlayManager.getWidgetOverlay(WidgetInfo.RAIDS_POINTS_INFOBOX); RaidsPanel panel = injector.getInstance(RaidsPanel.class); panel.init(); @@ -329,9 +350,93 @@ public class RaidsPlugin extends Plugin clientToolbar.addNavigation(navButton); } + private void lookupRaid(ChatMessage chatMessage, String s) + { + ChatMessageType type = chatMessage.getType(); + + final String player; + if (type.equals(ChatMessageType.PRIVATECHATOUT)) + { + player = client.getLocalPlayer().getName(); + } + else + { + player = sanitize(chatMessage.getName()); + } + + LayoutRoom[] layout; + try + { + layout = chatClient.getLayout(player); + } + catch (IOException ex) + { + log.debug("unable to lookup layout", ex); + return; + } + + if (layout == null || layout.length == 0) + { + return; + } + + String layoutMessage = Joiner.on(", ").join(Arrays.stream(layout) + .map(l -> RaidRoom.valueOf(l.name())) + .filter(room -> room.getType() == RoomType.COMBAT || room.getType() == RoomType.PUZZLE) + .map(RaidRoom::getName) + .toArray()); + + String response = new ChatMessageBuilder() + .append(ChatColorType.HIGHLIGHT) + .append("Layout: ") + .append(ChatColorType.NORMAL) + .append(layoutMessage) + .build(); + + log.debug("Setting response {}", response); + final MessageNode messageNode = chatMessage.getMessageNode(); + messageNode.setRuneLiteFormatMessage(response); + chatMessageManager.update(messageNode); + client.refreshChat(); + } + + private boolean submitRaid(ChatInput chatInput, String s) + { + if (raid == null) + { + return false; + } + + final String playerName = client.getLocalPlayer().getName(); + RaidRoom[] rooms = raid.getRooms(); + + LayoutRoom[] layoutRooms = Arrays.stream(rooms) + .map(room -> LayoutRoom.valueOf(room.name())) + .toArray(LayoutRoom[]::new); + + scheduledExecutorService.execute(() -> + { + try + { + chatClient.submitLayout(playerName, layoutRooms); + } + catch (Exception ex) + { + log.warn("unable to submit layout", ex); + } + finally + { + chatInput.resume(); + } + }); + + return true; + } + @Override protected void shutDown() { + chatCommandManager.unregisterCommand(LAYOUT_COMMAND); overlayManager.remove(overlay); overlayManager.remove(pointsOverlay); clientToolbar.removeNavigation(navButton); @@ -1016,98 +1121,71 @@ public class RaidsPlugin extends Plugin private RaidRoom determineRoom(Tile base) { - RaidRoom room = new RaidRoom(base, RaidRoom.Type.EMPTY); int chunkData = client.getInstanceTemplateChunks()[base.getPlane()][(base.getSceneLocation().getX()) / 8][base.getSceneLocation().getY() / 8]; InstanceTemplates template = InstanceTemplates.findMatch(chunkData); if (template == null) { - return room; + return RaidRoom.EMPTY; } switch (template) { case RAIDS_LOBBY: case RAIDS_START: - room.setType(RaidRoom.Type.START); - break; + return RaidRoom.START; case RAIDS_END: - room.setType(RaidRoom.Type.END); - break; + return RaidRoom.END; case RAIDS_SCAVENGERS: case RAIDS_SCAVENGERS2: - room.setType(RaidRoom.Type.SCAVENGERS); - break; + return RaidRoom.SCAVENGERS; case RAIDS_SHAMANS: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.SHAMANS); - break; + return RaidRoom.SHAMANS; case RAIDS_VASA: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.VASA); - break; + return RaidRoom.VASA; case RAIDS_VANGUARDS: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.VANGUARDS); - break; + return RaidRoom.VANGUARDS; case RAIDS_ICE_DEMON: - room.setType(RaidRoom.Type.PUZZLE); - room.setPuzzle(RaidRoom.Puzzle.ICE_DEMON); - break; + return RaidRoom.ICE_DEMON; case RAIDS_THIEVING: - room.setType(RaidRoom.Type.PUZZLE); - room.setPuzzle(RaidRoom.Puzzle.THIEVING); - break; + return RaidRoom.THIEVING; case RAIDS_FARMING: case RAIDS_FARMING2: - room.setType(RaidRoom.Type.FARMING); - break; + return RaidRoom.FARMING; case RAIDS_MUTTADILES: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.MUTTADILES); - break; + return RaidRoom.MUTTADILES; case RAIDS_MYSTICS: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.MYSTICS); - break; + return RaidRoom.MYSTICS; case RAIDS_TEKTON: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.TEKTON); - break; + return RaidRoom.TEKTON; case RAIDS_TIGHTROPE: - room.setType(RaidRoom.Type.PUZZLE); - room.setPuzzle(RaidRoom.Puzzle.TIGHTROPE); - break; + return RaidRoom.TIGHTROPE; case RAIDS_GUARDIANS: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.GUARDIANS); - break; + return RaidRoom.GUARDIANS; case RAIDS_CRABS: - room.setType(RaidRoom.Type.PUZZLE); - room.setPuzzle(RaidRoom.Puzzle.CRABS); - break; + return RaidRoom.CRABS; case RAIDS_VESPULA: - room.setType(RaidRoom.Type.COMBAT); - room.setBoss(RaidRoom.Boss.VESPULA); - break; + return RaidRoom.VESPULA; + + default: + return RaidRoom.EMPTY; } - return room; } private void reset() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RoomType.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RoomType.java new file mode 100644 index 0000000000..a76b3cef82 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RoomType.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018, Kamiel + * Copyright (c) 2019, Adam + * 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 lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum RoomType +{ + START("Start", '#'), + END("End", 'ยค'), + SCAVENGERS("Scavengers", 'S'), + FARMING("Farming", 'F'), + EMPTY("Empty", ' '), + COMBAT("Combat", 'C'), + PUZZLE("Puzzle", 'P'); + + private final String name; + private final char code; + + RaidRoom getUnsolvedRoom() + { + switch (this) + { + case START: + return RaidRoom.START; + case END: + return RaidRoom.END; + case SCAVENGERS: + return RaidRoom.SCAVENGERS; + case FARMING: + return RaidRoom.FARMING; + case COMBAT: + return RaidRoom.UNKNOWN_COMBAT; + case PUZZLE: + return RaidRoom.UNKNOWN_PUZZLE; + case EMPTY: + default: + return RaidRoom.EMPTY; + } + } + + static RoomType fromCode(char code) + { + for (RoomType type : values()) + { + if (type.getCode() == code) + { + return type; + } + } + + return EMPTY; + } +} \ No newline at end of file