From 55a5eaa43dd5a601d0a6a4759f0da543a02a451b Mon Sep 17 00:00:00 2001 From: Kamiel <35824069+Kamielvf@users.noreply.github.com> Date: Mon, 19 Feb 2018 04:20:47 +0100 Subject: [PATCH] Raids scouting plugin --- .../main/java/net/runelite/api/Client.java | 2 + .../net/runelite/api/InstanceTemplates.java | 87 ++++ .../main/java/net/runelite/api/Setting.java | 4 +- .../net/runelite/api/widgets/WidgetID.java | 6 + .../net/runelite/api/widgets/WidgetInfo.java | 2 + .../runelite/client/plugins/raids/Raid.java | 112 +++++ .../client/plugins/raids/RaidRoom.java | 161 ++++++ .../client/plugins/raids/RaidsConfig.java | 87 ++++ .../client/plugins/raids/RaidsOverlay.java | 117 +++++ .../client/plugins/raids/RaidsPlugin.java | 472 ++++++++++++++++++ .../client/plugins/raids/RaidsTimer.java | 83 +++ .../client/plugins/raids/solver/Layout.java | 63 +++ .../plugins/raids/solver/LayoutSolver.java | 218 ++++++++ .../client/plugins/raids/solver/Room.java | 51 ++ .../plugins/raids/solver/RotationSolver.java | 126 +++++ .../client/plugins/raids/raids_icon.png | Bin 0 -> 3210 bytes .../java/net/runelite/rs/api/RSClient.java | 4 + 17 files changed, 1594 insertions(+), 1 deletion(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/InstanceTemplates.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/Raid.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidRoom.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Layout.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/LayoutSolver.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Room.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/RotationSolver.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/raids/raids_icon.png diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index f95abfa874..b8d4397495 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -124,6 +124,8 @@ public interface Client extends GameEngine int[] getMapRegions(); + int[][][] getInstanceTemplateChunks(); + int[][] getXteaKeys(); int[] getSettings(); diff --git a/runelite-api/src/main/java/net/runelite/api/InstanceTemplates.java b/runelite-api/src/main/java/net/runelite/api/InstanceTemplates.java new file mode 100644 index 0000000000..e1b4d75a22 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/InstanceTemplates.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum InstanceTemplates +{ + RAIDS_LOBBY(3264, 5184, 0, 96, 32), + RAIDS_START(3264, 5696, 0, 96, 32), + RAIDS_END(3264, 5152, 0, 64, 32), + RAIDS_SCAVENGERS(3264, 5216, 0, 96, 32), + RAIDS_SHAMANS(3264, 5248, 0, 96, 32), + RAIDS_VASA(3264, 5280, 0, 96, 32), + RAIDS_VANGUARDS(3264, 5312, 0, 96, 32), + RAIDS_ICE_DEMON(3264, 5344, 0, 96, 32), + RAIDS_THIEVING(3264, 5376, 0, 96, 32), + RAIDS_FARMING(3264, 5440, 0, 96, 32), + RAIDS_SCAVENGERS2(3264, 5216, 1, 96, 32), + RAIDS_MUTTADILES(3264, 5312, 1, 96, 32), + RAIDS_MYSTICS(3264, 5248, 1, 96, 32), + RAIDS_TEKTON(3264, 5280, 1, 96, 32), + RAIDS_TIGHTROPE(3264, 5344, 1, 96, 32), + RAIDS_FARMING2(3264, 5440, 1, 96, 32), + RAIDS_GUARDIANS(3264, 5248, 2, 96, 32), + RAIDS_VESPULA(3264, 5280, 2, 96, 32), + RAIDS_CRABS(3264, 5344, 2, 96, 32); + + @Getter + private final int baseX; + + @Getter + private final int baseY; + + @Getter + private final int plane; + + @Getter + private final int width; + + @Getter + private final int height; + + public static InstanceTemplates findMatch(int chunkData) + { + int rotation = chunkData >> 1 & 0x3; //unused, but shows us the rotation of the chunk + int y = (chunkData >> 3 & 0x7FF) * 8; + int x = (chunkData >> 14 & 0x3FF) * 8; + int plane = chunkData >> 24 & 0x3; + + for (InstanceTemplates template : InstanceTemplates.values()) + { + if (plane == template.getPlane() + && x >= template.getBaseX() && x < template.getBaseX() + template.getWidth() + && y >= template.getBaseY() && y < template.getBaseY() + template.getHeight()) + { + return template; + } + } + + return null; + } +} diff --git a/runelite-api/src/main/java/net/runelite/api/Setting.java b/runelite-api/src/main/java/net/runelite/api/Setting.java index 8d8a124cf5..f049024ac3 100644 --- a/runelite-api/src/main/java/net/runelite/api/Setting.java +++ b/runelite-api/src/main/java/net/runelite/api/Setting.java @@ -34,7 +34,9 @@ public enum Setting ATTACK_STYLE(43), SPECIAL_ATTACK_PERCENT(300), - SPECIAL_ATTACK_ENABLED(301); + SPECIAL_ATTACK_ENABLED(301), + + IN_RAID_PARTY(1427); private final int id; } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index ad56114ddd..4783c979cf 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -61,6 +61,7 @@ public class WidgetID public static final int QUEST_COMPLETED_GROUP_ID = 277; public static final int CLUE_SCROLL_REWARD_GROUP_ID = 73; public static final int BARROWS_REWARD_GROUP_ID = 155; + public static final int RAIDS_GROUP_ID = 513; public static final int MOTHERLODE_MINE_GROUP_ID = 382; public static final int EXPERIENCE_DROP_GROUP_ID = 122; public static final int PUZZLE_BOX_GROUP_ID = 306; @@ -272,6 +273,11 @@ public class WidgetID static final int NAME_TEXT = 2; } + static class Raids + { + static final int POINTS_INFOBOX = 3; + } + static class ExperienceDrop { static final int DROP_1 = 15; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 261031a4d7..2f8412ea81 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -175,6 +175,8 @@ public enum WidgetInfo NIGHTMARE_ZONE(WidgetID.NIGHTMARE_ZONE_GROUP_ID, 1), + RAIDS_POINTS_INFOBOX(WidgetID.RAIDS_GROUP_ID, WidgetID.Raids.POINTS_INFOBOX), + BLAST_FURNACE_COFFER(WidgetID.BLAST_FURNACE_GROUP_ID, 0); private final int groupId; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/Raid.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/Raid.java new file mode 100644 index 0000000000..f37699c826 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/Raid.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.util.ArrayList; +import java.util.List; +import lombok.Getter; +import net.runelite.client.plugins.raids.solver.Layout; +import net.runelite.client.plugins.raids.solver.Room; + +public class Raid +{ + @Getter + private final RaidRoom[] rooms = new RaidRoom[16]; + + @Getter + private Layout layout; + + public void updateLayout(Layout layout) + { + if (layout == null) + return; + + this.layout = layout; + + for (int i = 0; i < rooms.length; i++) + { + if (layout.getRoomAt(i) == null) + continue; + + RaidRoom room = rooms[i]; + + if (room == null) + { + RaidRoom.Type type = RaidRoom.Type.fromCode(layout.getRoomAt(i).getSymbol()); + room = new RaidRoom(null, type); + + if (type == RaidRoom.Type.COMBAT) + room.setBoss(RaidRoom.Boss.UNKNOWN); + + if (type == RaidRoom.Type.PUZZLE) + room.setPuzzle(RaidRoom.Puzzle.UNKNOWN); + + setRoom(room, i); + } + } + } + + public RaidRoom getRoom(int position) + { + return rooms[position]; + } + + public void setRoom(RaidRoom room, int position) + { + if (position < rooms.length) + rooms[position] = room; + } + + public RaidRoom[] getCombatRooms() + { + List combatRooms = new ArrayList<>(); + + for (Room room : layout.getRooms()) + { + if (room == null) + continue; + + if (rooms[room.getPosition()].getType() == RaidRoom.Type.COMBAT) + combatRooms.add(rooms[room.getPosition()]); + } + + return combatRooms.toArray(new RaidRoom[combatRooms.size()]); + } + + public String toCode() + { + StringBuilder builder = new StringBuilder(); + + for (RaidRoom room : rooms) + { + if (room != null) + builder.append(room.getType().getCode()); + else + builder.append(" "); + } + + return builder.toString(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidRoom.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidRoom.java new file mode 100644 index 0000000000..417df399c7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidRoom.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018, Kamiel + * 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; +import lombok.Setter; +import net.runelite.api.Tile; + +public class RaidRoom +{ + public static final int ROOM_MAX_SIZE = 32; + + @AllArgsConstructor + public enum Type + { + START("Start", "#"), + END("End", "¤"), + SCAVENGERS("Scavengers", "S"), + FARMING("Farming", "F"), + COMBAT("Combat", "C"), + PUZZLE("Puzzle", "P"), + EMPTY("Empty", " "); + + @Getter + private final String name; + + @Getter + private final String code; + + public static Type fromCode(char code) + { + for (Type type : Type.values()) + { + if (type.getCode().equalsIgnoreCase(String.valueOf(code))) + return type; + } + + return Type.EMPTY; + } + } + + @AllArgsConstructor + public enum Boss + { + TEKTON("Tekton"), + MUTTADILES("Muttadiles"), + GUARDIANS("Guardians"), + VESPULA("Vespula"), + SHAMANS("Shamans"), + VASA("Vasa"), + VANGUARDS("Vanguards"), + MYSTICS("Mystics"), + UNKNOWN("Unknown"); + + @Getter + private final String name; + + public static Boss fromString(String name) + { + for (Boss boss : Boss.values()) + { + if (boss.getName().equalsIgnoreCase(name)) + return boss; + } + + return null; + } + } + + @AllArgsConstructor + public enum Puzzle + { + CRABS("Crabs"), + ICE_DEMON("Ice Demon"), + TIGHTROPE("Tightrope"), + THIEVING("Thieving"), + UNKNOWN("Unknown"); + + @Getter + private final String name; + + public static Puzzle fromString(String name) + { + for (Puzzle puzzle : Puzzle.values()) + { + if (puzzle.getName().equalsIgnoreCase(name)) + return puzzle; + } + + return null; + } + } + + @Getter + private final Tile base; + + @Getter + @Setter + private Type type; + + @Getter + @Setter + private Boss boss; + + @Getter + @Setter + private Puzzle puzzle; + + @Getter + @Setter + private RaidRoom previousRoom; + + @Getter + @Setter + private RaidRoom nextRoom; + + public RaidRoom(Tile base, Type type) + { + this.base = base; + this.type = type; + } + + @Override + public String toString() + { + switch (type) + { + case COMBAT: + return "RaidRoom (type: " + type.getName() + ", " + boss.getName() + ")"; + + case PUZZLE: + return "RaidRoom (type: " + type.getName() + ", " + puzzle.getName() + ")"; + + default: + return "RaidRoom (type: " + type.getName() + ")"; + } + } +} 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 new file mode 100644 index 0000000000..8879806273 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsConfig.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018, Kamiel + * 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 net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "raids", + name = "Raids", + description = "Configuration for the raids plugin" +) +public interface RaidsConfig extends Config +{ + @ConfigItem( + keyName = "raidsTimer", + name = "Display elapsed raid time", + description = "Display elapsed raid time" + ) + default boolean raidsTimer() + { + return true; + } + + @ConfigItem( + keyName = "pointsMessage", + name = "Display points in chatbox after raid", + description = "Display a message with total points, individual points and percentage at the end of a raid" + ) + default boolean pointsMessage() + { + return true; + } + + @ConfigItem( + keyName = "scoutOverlay", + name = "Show scout overlay", + description = "Display an overlay that shows the current raid layout (when entering lobby)" + ) + default boolean scoutOverlay() + { + return true; + } + + @ConfigItem( + keyName = "scoutOverlayAtBank", + name = "Show scout overlay outside lobby", + description = "Keep the overlay active while at the raids area" + ) + default boolean scoutOverlayAtBank() + { + return true; + } + + @ConfigItem( + keyName = "blacklistedRooms", + name = "Blacklisted rooms", + description = "Display blacklisted rooms in red on the overlay. Separate with comma (full name)" + ) + default String blacklistedRooms() + { + return ""; + } +} 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 new file mode 100644 index 0000000000..48388e89be --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsOverlay.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import javax.inject.Inject; +import lombok.Setter; +import net.runelite.client.plugins.raids.solver.Room; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.ui.overlay.components.PanelComponent; + +public class RaidsOverlay extends Overlay +{ + private RaidsPlugin plugin; + private RaidsConfig config; + private final PanelComponent panelComponent = new PanelComponent(); + + @Setter + private boolean scoutOverlayShown = false; + + @Inject + public RaidsOverlay(RaidsPlugin plugin, RaidsConfig config) + { + setPosition(OverlayPosition.TOP_LEFT); + setPriority(OverlayPriority.LOW); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + if (!config.scoutOverlay() || !scoutOverlayShown) + { + return null; + } + + panelComponent.getLines().clear(); + + if (plugin.getRaid() == null || plugin.getRaid().getLayout() == null) + { + panelComponent.setTitleColor(Color.RED); + panelComponent.setTitle("Unable to scout this raid!"); + return panelComponent.render(graphics, parent); + } + + panelComponent.setTitleColor(Color.WHITE); + panelComponent.setTitle("Raid scouter"); + + for (Room layoutRoom : plugin.getRaid().getLayout().getRooms()) + { + int position = layoutRoom.getPosition(); + RaidRoom room = plugin.getRaid().getRoom(position); + + if (room == null) + { + continue; + } + + Color color = Color.WHITE; + + switch (room.getType()) + { + case COMBAT: + if (plugin.getBlacklist().contains(room.getBoss().getName().toLowerCase())) + { + color = Color.RED; + } + + panelComponent.getLines().add(new PanelComponent.Line( + room.getType().getName(), Color.WHITE, room.getBoss().getName(), color + )); + break; + + case PUZZLE: + if (plugin.getBlacklist().contains(room.getPuzzle().getName().toLowerCase())) + { + color = Color.RED; + } + + panelComponent.getLines().add(new PanelComponent.Line( + room.getType().getName(), Color.WHITE, room.getPuzzle().getName(), color + )); + break; + } + } + + return panelComponent.render(graphics, parent); + } +} 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 new file mode 100644 index 0000000000..3743050207 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2018, Kamiel + * 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 com.google.common.eventbus.Subscribe; +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.io.InputStream; +import java.text.DecimalFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import javax.imageio.ImageIO; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import static net.runelite.api.Perspective.SCENE_SIZE; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.chat.ChatColor; +import net.runelite.client.chat.ChatColorType; +import net.runelite.client.chat.ChatMessageBuilder; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.config.ConfigManager; +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.overlay.Overlay; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; + +@PluginDescriptor( + name = "Raids Plugin" +) +@Slf4j +public class RaidsPlugin extends Plugin +{ + private static final int LOBBY_PLANE = 3; + private static final String RAID_START_MESSAGE = "The raid has begun!"; + private static final String RAID_COMPLETE_MESSAGE = "Congratulations - your raid is complete!"; + private static final int TOTAL_POINTS = 0, PERSONAL_POINTS = 1, TEXT_CHILD = 4; + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###.##"); + + private BufferedImage raidsIcon; + private RaidsTimer timer; + private boolean inRaidChambers; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private InfoBoxManager infoBoxManager; + + @Inject + private Client client; + + @Inject + private RaidsConfig config; + + @Inject + private RaidsOverlay overlay; + + @Inject + private LayoutSolver layoutSolver; + + @Getter + private Raid raid; + + @Getter + private ArrayList blacklist = new ArrayList<>(); + + @Provides + RaidsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(RaidsConfig.class); + } + + @Override + public void configure(Binder binder) + { + binder.bind(RaidsOverlay.class); + } + + @Override + public Overlay getOverlay() + { + return overlay; + } + + @Override + protected void startUp() throws Exception + { + if (client.getGameState() == GameState.LOGGED_IN) + { + inRaidChambers = client.getSetting(Varbits.IN_RAID) == 1; + updateInfoBoxState(); + } + + updateBlacklist(); + } + + @Override + protected void shutDown() throws Exception + { + if (timer != null) + infoBoxManager.removeInfoBox(timer); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (config.pointsMessage()) + cacheColors(); + + if (event.getKey().equals("raidsTimer")) + updateInfoBoxState(); + + if (event.getKey().equals("blacklistedRooms")) + updateBlacklist(); + } + + @Subscribe + public void onVarbitChange(VarbitChanged event) + { + boolean setting = client.getSetting(Varbits.IN_RAID) == 1; + + if (inRaidChambers != setting) + { + inRaidChambers = setting; + updateInfoBoxState(); + + if (inRaidChambers) + { + raid = buildRaid(); + + if (raid == null) + { + log.debug("Failed to build raid"); + return; + } + + Layout layout = layoutSolver.findLayout(raid.toCode()); + + if (layout == null) + { + log.debug("Could not find layout match"); + return; + } + + raid.updateLayout(layout); + RotationSolver.solve(raid.getCombatRooms()); + overlay.setScoutOverlayShown(true); + } + else if (!config.scoutOverlayAtBank()) + { + overlay.setScoutOverlayShown(false); + raid = null; + } + } + + if (client.getSetting(Setting.IN_RAID_PARTY) == -1) + { + overlay.setScoutOverlayShown(false); + raid = null; + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) + { + if (inRaidChambers && event.getType() == ChatMessageType.CLANCHAT_INFO) + { + String message = event.getMessage().replaceAll("<[^>]*>", ""); + + if (config.raidsTimer() && message.startsWith(RAID_START_MESSAGE)) + { + timer = new RaidsTimer(getRaidsIcon(), Instant.now()); + infoBoxManager.addInfoBox(timer); + } + + if (message.startsWith(RAID_COMPLETE_MESSAGE)) + { + if (timer != null) + timer.setStopped(true); + + if (config.pointsMessage()) + { + Widget raidsWidget = client.getWidget(WidgetInfo.RAIDS_POINTS_INFOBOX).getChild(TEXT_CHILD); + String[] raidPoints = raidsWidget.getText().split("
"); + int totalPoints = Integer.parseInt(raidPoints[TOTAL_POINTS].replace(",", "")); + int personalPoints = Integer.parseInt(raidPoints[PERSONAL_POINTS].replace(",", "")); + + double percentage = personalPoints / (totalPoints / 100.0); + + String chatMessage = new ChatMessageBuilder() + .append(ChatColorType.NORMAL) + .append("Total points: ") + .append(ChatColorType.HIGHLIGHT) + .append(raidPoints[TOTAL_POINTS]) + .append(ChatColorType.NORMAL) + .append(", Personal points: ") + .append(ChatColorType.HIGHLIGHT) + .append(raidPoints[PERSONAL_POINTS]) + .append(ChatColorType.NORMAL) + .append(" (") + .append(ChatColorType.HIGHLIGHT) + .append(DECIMAL_FORMAT.format(percentage)) + .append(ChatColorType.NORMAL) + .append("%)") + .build(); + + chatMessageManager.queue(ChatMessageType.CLANCHAT_INFO, chatMessage); + } + } + } + } + + private void updateInfoBoxState() + { + if (timer != null) + { + if (inRaidChambers && config.raidsTimer()) + infoBoxManager.addInfoBox(timer); + else + infoBoxManager.removeInfoBox(timer); + + if (!inRaidChambers) + timer = null; + } + } + + private void updateBlacklist() + { + blacklist.clear(); + blacklist.addAll(Arrays.asList(config.blacklistedRooms().toLowerCase().split(", "))); + } + + private void cacheColors() + { + chatMessageManager.cacheColor(new ChatColor(ChatColorType.NORMAL, Color.BLACK, false), ChatMessageType.CLANCHAT_INFO) + .cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, Color.RED, false), ChatMessageType.CLANCHAT_INFO) + .cacheColor(new ChatColor(ChatColorType.NORMAL, Color.WHITE, true), ChatMessageType.CLANCHAT_INFO) + .cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, Color.RED, true), ChatMessageType.CLANCHAT_INFO) + .refreshAll(); + } + + private Point findLobbyBase() + { + Tile[][] tiles = client.getRegion().getTiles()[LOBBY_PLANE]; + + for (int x = 0; x < SCENE_SIZE; x++) + { + for (int y = 0; y < SCENE_SIZE; y++) + { + if (tiles[x][y] == null || tiles[x][y].getWallObject() == null) + continue; + + if (tiles[x][y].getWallObject().getId() == ObjectID.NULL_12231) + return tiles[x][y].getRegionLocation(); + } + } + + return null; + } + + private Raid buildRaid() + { + Point gridBase = findLobbyBase(); + + if (gridBase == null) + return null; + + Raid raid = new Raid(); + Tile[][] tiles; + int position, x, y, offsetX; + int startX = -2; + + for (int plane = 3; plane > 1; plane--) + { + tiles = client.getRegion().getTiles()[plane]; + + if (tiles[gridBase.getX() + RaidRoom.ROOM_MAX_SIZE][gridBase.getY()] == null) + position = 1; + else + position = 0; + + for (int i = 1; i > -2; i--) + { + y = gridBase.getY() + (i * RaidRoom.ROOM_MAX_SIZE); + + for (int j = startX; j < 4; j++) + { + x = gridBase.getX() + (j * RaidRoom.ROOM_MAX_SIZE); + offsetX = 0; + + if (x > SCENE_SIZE && position > 1 && position < 4) + position++; + + if (x < 0) + offsetX = Math.abs(x) + 1; //add 1 because the tile at x=0 will always be null + + if (x < SCENE_SIZE && y >= 0 && y < SCENE_SIZE) + { + if (tiles[x + offsetX][y] == null) + { + if (position == 4) + { + position++; + break; + } + + continue; + } + + if (position == 0 && startX != j) + startX = j; + + Tile base = tiles[offsetX > 0 ? 1 : x][y]; + RaidRoom room = determineRoom(base); + raid.setRoom(room, position + Math.abs((plane - 3) * 8)); + position++; + } + } + } + } + + return raid; + } + + private RaidRoom determineRoom(Tile base) + { + RaidRoom room = new RaidRoom(base, RaidRoom.Type.EMPTY); + int chunkData = client.getInstanceTemplateChunks()[base.getPlane()][(base.getRegionLocation().getX()) / 8][base.getRegionLocation().getY() / 8]; + InstanceTemplates template = InstanceTemplates.findMatch(chunkData); + + if (template == null) + return room; + + switch (template) + { + case RAIDS_LOBBY: + case RAIDS_START: + room.setType(RaidRoom.Type.START); + break; + + case RAIDS_END: + room.setType(RaidRoom.Type.END); + break; + + case RAIDS_SCAVENGERS: + case RAIDS_SCAVENGERS2: + room.setType(RaidRoom.Type.SCAVENGERS); + break; + + case RAIDS_SHAMANS: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.SHAMANS); + break; + + case RAIDS_VASA: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.VASA); + break; + + case RAIDS_VANGUARDS: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.VANGUARDS); + break; + + case RAIDS_ICE_DEMON: + room.setType(RaidRoom.Type.PUZZLE); + room.setPuzzle(RaidRoom.Puzzle.ICE_DEMON); + break; + + case RAIDS_THIEVING: + room.setType(RaidRoom.Type.PUZZLE); + room.setPuzzle(RaidRoom.Puzzle.THIEVING); + break; + + case RAIDS_FARMING: + case RAIDS_FARMING2: + room.setType(RaidRoom.Type.FARMING); + break; + + case RAIDS_MUTTADILES: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.MUTTADILES); + break; + + case RAIDS_MYSTICS: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.MYSTICS); + break; + + case RAIDS_TEKTON: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.TEKTON); + break; + + case RAIDS_TIGHTROPE: + room.setType(RaidRoom.Type.PUZZLE); + room.setPuzzle(RaidRoom.Puzzle.TIGHTROPE); + break; + + case RAIDS_GUARDIANS: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.GUARDIANS); + break; + + case RAIDS_CRABS: + room.setType(RaidRoom.Type.PUZZLE); + room.setPuzzle(RaidRoom.Puzzle.CRABS); + break; + + case RAIDS_VESPULA: + room.setType(RaidRoom.Type.COMBAT); + room.setBoss(RaidRoom.Boss.VESPULA); + break; + } + + return room; + } + + private BufferedImage getRaidsIcon() + { + if (raidsIcon != null) + return raidsIcon; + + InputStream in = RaidsPlugin.class.getResourceAsStream("raids_icon.png"); + + try + { + raidsIcon = ImageIO.read(in); + } + catch (IOException ex) + { + log.warn("Unable to load image", ex); + } + + return raidsIcon; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java new file mode 100644 index 0000000000..dc6e574f0a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.Color; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import lombok.Setter; +import net.runelite.client.ui.overlay.infobox.InfoBox; + +public class RaidsTimer extends InfoBox +{ + private final Instant startTime; + private LocalTime time; + + @Setter + private boolean stopped; + + public RaidsTimer(BufferedImage image, Instant startTime) + { + super(image); + this.startTime = startTime; + stopped = false; + } + + @Override + public String getText() + { + if (startTime == null) + return ""; + + if (!stopped) + { + Duration elapsed = Duration.between(startTime, Instant.now()); + time = LocalTime.ofSecondOfDay(elapsed.getSeconds()); + } + + if (time.getHour() > 0) + return time.format(DateTimeFormatter.ofPattern("HH:mm")); + + return time.format(DateTimeFormatter.ofPattern("mm:ss")); + } + + @Override + public Color getTextColor() + { + if (stopped) + return Color.GREEN; + + return Color.WHITE; + } + + @Override + public String getTooltip() + { + return "Elapsed raid time: " + time.format(DateTimeFormatter.ofPattern("HH:mm:ss")); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Layout.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Layout.java new file mode 100644 index 0000000000..6392f63bc3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Layout.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.solver; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; + +public class Layout +{ + @Getter + private final List rooms = new ArrayList<>(); + + public void add(Room room) + { + rooms.add(room); + } + + public Room getRoomAt(int position) + { + for (Room room : rooms) + { + if (room.getPosition() == position) + return room; + } + + return null; + } + + public String toCode() + { + StringBuilder builder = new StringBuilder(); + + for (Room room : rooms) + { + builder.append(room.getSymbol()); + } + + return builder.toString(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/LayoutSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/LayoutSolver.java new file mode 100644 index 0000000000..0a2410fb6c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/LayoutSolver.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.solver; + +import com.google.inject.Singleton; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/* + * Implementation of https://github.com/WooxSolo/raids-layout + * Copyright (c) 2017 WooxSolo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ +@Slf4j +@Singleton +public class LayoutSolver +{ + @Getter + private static final List layouts = new ArrayList<>(); + private static final Pattern regex = Pattern.compile("^([A-Z]*)\\.([A-Z]*) - #([A-Z]*)#([A-Z]*)$"); + private static final String[] codes = + { + "CFSCP.PCSCF - #ENWWWS#NEESEN", + "CFSCPC.PCSCPF - #WSEENES#WWWNEEE", + "CSFCP.CSCPF - #ENESEN#WWWSEE", + "CSPFC.CCSSF - #NEESEN#WSWWNE", + "FCPCC.PSCSF - #WWWSEE#ENWWSW", + "FSCCP.PCSCF - #WNWSWN#ESEENW", + "FSCCS.PCPSF - #WSEEEN#WSWNWS", + "FSCPC.CSCPF - #WNWWSE#EENWWW", + "PCSFC.PCSCF - #WNEEES#NWSWNW", + "SCCFC.PSCSF - #EEENWW#WSEEEN", + "SCCFP.CCSPF - #NESEEN#WSWNWS", + "SCFCP.CCSPF - #ESEENW#ESWWNW", + "SCFCP.CSCFS - #ENEESW#ENWWSW", + "SCFCPC.CSPCSF - #ESWWNWS#NESENES", + "SCFPC.CSPCF - #WSWWNE#WSEENE", + "SCFPC.PCCSF - #WSEENE#WWWSEE", + "SCFPC.SCPCF - #NESENE#WSWWNE", + "SCPFC.CCPSF - #NWWWSE#WNEESE", + "SCPFC.CSPCF - #NEEESW#WWNEEE", + "SCPFC.CSPSF - #WWSEEE#NWSWWN", + "SCSPF.CCSPF - #ESWWNW#ESENES", + "SFCCP.CSCPF - #WNEESE#NWSWWN", + "SFCCS.PCPSF - #ENWWSW#ENESEN", + "SPCFC.CSPCF - #WWNEEE#WSWNWS", + "SPCFC.SCCPF - #ESENES#WWWNEE", + "SPSFP.CCCSF - #NWSWWN#ESEENW", + }; + + public LayoutSolver() + { + build(); + } + + public Layout findLayout(String code) + { + Layout solution = null; + int matches = 0; + boolean match; + + for (Layout layout : layouts) + { + match = true; + + for (int i = 0; i < code.length(); i++) + { + Room room = layout.getRoomAt(i); + char c = code.charAt(i); + + if (room != null && c != ' ' && c != room.getSymbol()) + { + match = false; + break; + } + } + + if (match) + { + solution = layout; + matches++; + log.debug("Found matching layout: " + layout.toCode()); + } + } + + if (matches == 1) + return solution; + else + return null; + } + + private int calcStart(String directions) + { + int startPos = 0; + int position = 0; + + for (int i = 0; i < directions.length(); i++) + { + char c = directions.charAt(i); + int delta = dirToPosDelta(c); + position += delta; + + if (position < 0 || position >= 8 || (position == 3 && delta == -1) || (position == 4 && delta == 1)) + { + position -= delta; + startPos -= delta; + } + } + + return startPos; + } + + private int dirToPosDelta(char direction) + { + switch (String.valueOf(direction)) + { + case "N": + return -4; + + case "E": + return 1; + + case "S": + return 4; + + case "W": + return -1; + + default: + return 0; + } + } + + private void build() + { + for (String code : codes) + { + Matcher match = regex.matcher(code); + + if (!match.find()) + continue; + + String symbols, directions; + int position = calcStart(match.group(3)); + Layout layout = new Layout(); + Room lastRoom = null; + Room room; + + for (int floor = 0; floor < 2; floor++) + { + symbols = match.group(1 + floor); + directions = match.group(3 + floor); + + for (int i = 0; i < directions.length(); i++) + { + char symbol = (i == 0 ? '#' : symbols.charAt(i - 1)); + + room = new Room(position, symbol); + + if (lastRoom != null) + { + lastRoom.setNext(room); + room.setPrevious(lastRoom); + } + + layout.add(room); + lastRoom = room; + + int delta = dirToPosDelta(directions.charAt(i)); + position += delta; + } + + room = new Room(position, '¤'); + room.setPrevious(lastRoom); + lastRoom.setNext(room); + layout.add(room); + position += 8; + } + + layouts.add(layout); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Room.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Room.java new file mode 100644 index 0000000000..4ffa7efb60 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/Room.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.solver; + +import lombok.Getter; +import lombok.Setter; + +public class Room +{ + @Getter + private final int position; + + @Getter + private final char symbol; + + @Getter + @Setter + private Room next; + + @Getter + @Setter + private Room previous; + + Room(int position, char symbol) + { + this.position = position; + this.symbol = symbol; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/RotationSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/RotationSolver.java new file mode 100644 index 0000000000..e5654bc0fa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/solver/RotationSolver.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.solver; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import net.runelite.client.plugins.raids.RaidRoom; +import net.runelite.client.plugins.raids.RaidRoom.Boss; + +public class RotationSolver +{ + private static class Rotation extends ArrayList + { + Rotation(Collection bosses) + { + super(bosses); + } + + @Override + public E get(int index) + { + if (index < 0) + index = index + size(); + + return super.get(index % size()); + } + } + + private static final Rotation[] ROTATIONS = + { + new Rotation<>(Arrays.asList(Boss.TEKTON, Boss.VASA, Boss.GUARDIANS, Boss.MYSTICS, Boss.SHAMANS, Boss.MUTTADILES, Boss.VANGUARDS, Boss.VESPULA)), + new Rotation<>(Arrays.asList(Boss.TEKTON, Boss.MUTTADILES, Boss.GUARDIANS, Boss.VESPULA, Boss.SHAMANS, Boss.VASA, Boss.VANGUARDS, Boss.MYSTICS)), + new Rotation<>(Arrays.asList(Boss.VESPULA, Boss.VANGUARDS, Boss.MUTTADILES, Boss.SHAMANS, Boss.MYSTICS, Boss.GUARDIANS, Boss.VASA, Boss.TEKTON)), + new Rotation<>(Arrays.asList(Boss.MYSTICS, Boss.VANGUARDS, Boss.VASA, Boss.SHAMANS, Boss.VESPULA, Boss.GUARDIANS, Boss.MUTTADILES, Boss.TEKTON)) + }; + + public static boolean solve(RaidRoom[] rooms) + { + if (rooms == null) + return false; + + Rotation match = null; + Integer start = null; + Integer index = null; + int known = 0; + + for (int i = 0; i < rooms.length; i++) + { + if (rooms[i] == null || rooms[i].getBoss() == null || rooms[i].getBoss() == Boss.UNKNOWN) + continue; + + if (start == null) + start = i; + + known++; + } + + if (known < 2) + return false; + + if (known == rooms.length) + return true; + + for (Rotation rotation : ROTATIONS) + { + COMPARE: + for (int i = 0; i < rotation.size(); i++) + { + if (rooms[start].getBoss() == rotation.get(i)) + { + for (int j = start + 1; j < rooms.length; j++) + { + if (rooms[j].getBoss() == null || rooms[j].getBoss() == Boss.UNKNOWN) + continue; + + if (rooms[j].getBoss() != rotation.get(i + j - start)) + break COMPARE; + } + + if (match != null && match != rotation) + return false; + + index = i - start; + match = rotation; + } + } + } + + if (match == null) + return false; + + for (int i = 0; i < rooms.length; i++) + { + if (rooms[i] == null) + continue; + + if (rooms[i].getBoss() == null || rooms[i].getBoss() == Boss.UNKNOWN) + rooms[i].setBoss(match.get(index + i)); + } + + return true; + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/raids/raids_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/raids/raids_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a239a5f33a747dd8358110929ad300b5c981ad9 GIT binary patch literal 3210 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 literal 0 HcmV?d00001 diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 5c0f0011a6..4402dd7eb1 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -276,6 +276,10 @@ public interface RSClient extends RSGameEngine, Client @Override int[] getMapRegions(); + @Import("instanceTemplateChunks") + @Override + int[][][] getInstanceTemplateChunks(); + @Import("xteaKeys") @Override int[][] getXteaKeys();