diff --git a/src/main/java/io/blert/challenges/chambers/CoxChallenge.java b/src/main/java/io/blert/challenges/chambers/CoxChallenge.java index 413e45c..232d472 100644 --- a/src/main/java/io/blert/challenges/chambers/CoxChallenge.java +++ b/src/main/java/io/blert/challenges/chambers/CoxChallenge.java @@ -18,12 +18,16 @@ import io.blert.events.ChallengeEndEvent; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.Point; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameObjectSpawned; import net.runelite.client.callback.ClientThread; import net.runelite.client.util.Text; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,12 +41,6 @@ public class CoxChallenge extends RecordableChallenge { Pattern.compile("The raid has begun!.*"); private static final Pattern RAID_COMPLETION_REGEX = Pattern.compile("Congratulations - your raid is complete!.*"); - // private static final Pattern RAID_COMPLETION_REGEX = - // Pattern.compile("(Combat room|Puzzle) `.*` complete! Duration: .*"); - private static final Pattern ROOM_COMPLETE_REGEX = - Pattern.compile("(Combat room|Puzzle) `.*` complete! Duration: .*"); - // private static final Pattern ROOM_COMPLETE_REGEX = - // Pattern.compile("Congratulations - your raid is complete!.*"); private static final Pattern FLOOR_COMPLETE_REGEX = Pattern.compile(".* level complete! Duration: .*"); private static final Pattern MAP_LAYOUT_REGEX = @@ -59,6 +57,12 @@ public class CoxChallenge extends RecordableChallenge { private int maxCombatLevel = -1; // Cached max combat level for the party private int avgMiningLevel = -1; // Cached average mining level for the party + // Obstacle tracking for collision-flag room completion detection (dey0 methodology). + // Indexed by CoxRoomUtil room type constant; obstacleP[i] == -1 means not registered. + private final int[] obstacleP = new int[16]; + private final int[] obstacleX = new int[16]; + private final int[] obstacleY = new int[16]; + private static final List COX_ROOM_ORDER = List.of( // Stage.COX_THIEVING, // Stage.COX_GUARDIANS, @@ -132,18 +136,28 @@ protected void onTerminate() { for (CoxNpc npc : CoxNpc.values()) { npc.resetScaledHitpoints(); } + Arrays.fill(obstacleP, -1); + Arrays.fill(obstacleX, -1); + Arrays.fill(obstacleY, -1); + currentLocation = null; + enteredLobby = false; setState(ChallengeState.INACTIVE); } @Override protected void onTick() { + // Update current location + if (client.getLocalPlayer() != null) { + updateCurrentLocation(client.getLocalPlayer().getWorldLocation()); + } + // Only use instance check for inRaid logic (like CoxTimersPlugin) boolean inInstance = client.getTopLevelWorldView() != null && client.getTopLevelWorldView().isInstance(); if (inRaid && !inInstance) { log.info("Detected raid exit: inInstance={}", inInstance); if (getState() == ChallengeState.ACTIVE) { - endRaid(ChallengeState.INACTIVE); + endRaid(ChallengeState.COMPLETE); } inRaid = false; } else if (!inRaid && inInstance) { @@ -156,6 +170,40 @@ protected void onTick() { if (roomDataTracker != null) { roomDataTracker.tick(); } + + // Poll collision flags to detect room completion (dey0 methodology). + if (getState() == ChallengeState.ACTIVE && roomDataTracker != null) { + Stage currentStage = roomDataTracker.getStage(); + for (int i = 0; i < 16; i++) { + if (obstacleP[i] == -1) continue; + if (CoxRoomUtil.roomTypeToStage(i) != currentStage) continue; + int p = obstacleP[i]; + int sceneX = obstacleX[i] - client.getTopLevelWorldView().getBaseX(); + int sceneY = obstacleY[i] - client.getTopLevelWorldView().getBaseY(); + if (p != client.getTopLevelWorldView().getPlane() || sceneX < 0 || sceneX >= 104 || sceneY < 0 || sceneY >= 104) { + obstacleP[i] = -1; + continue; + } + var collisionMaps = client.getTopLevelWorldView().getCollisionMaps(); + if (collisionMaps == null) { + continue; + } + int flags = collisionMaps[p].getFlags()[sceneX][sceneY]; + if ((flags & 0x100) == 0) { + int completionTick = getRelativeTick(); + log.info("Room complete via collision flag: stage={}, tick={}", currentStage, completionTick); + obstacleP[i] = -1; + final RoomDataTracker tracker = roomDataTracker; + if (tracker == null) { + log.warn("Room completion detected but roomDataTracker is null"); + return; + } + tracker.finishRoom(completionTick); + advanceToNextRoom(tracker, completionTick); + break; + } + } + } } @Nullable @@ -164,6 +212,43 @@ protected Stage getStage() { return roomDataTracker != null ? roomDataTracker.getStage() : null; } + @Override + public void onGameObjectSpawned(GameObjectSpawned event) { + if (getState() == ChallengeState.ACTIVE && roomDataTracker != null) { + final RoomDataTracker tracker = roomDataTracker; + GameObject go = event.getGameObject(); + switch (go.getId()) { + case 26209: // shamans / thieving / guardians + case 29741: // mystics + case 29749: // tightrope + case 29753: case 29754: case 29755: case 29756: case 29757: // crabs + case 29876: // ice demon + case 30016: // vasa + case 30017: // tekton / vanguards + case 30018: // muttadiles + case 30070: // vespula + Point pt = go.getSceneMinLocation(); + int p = go.getPlane(); + int sceneX = pt.getX(); + int sceneY = pt.getY(); + int template = client.getTopLevelWorldView().getInstanceTemplateChunks()[p][sceneX / 8][sceneY / 8]; + int roomType = CoxRoomUtil.getRoomType(template); + if (roomType < 16) { + Stage expectedStage = CoxRoomUtil.roomTypeToStage(roomType); + if (expectedStage != null && expectedStage == tracker.getStage()) { + obstacleP[roomType] = p; + obstacleX[roomType] = sceneX + client.getTopLevelWorldView().getBaseX(); + obstacleY[roomType] = sceneY + client.getTopLevelWorldView().getBaseY(); + log.debug("Registered obstacle for room type {} (stage {}) at world ({},{})", + roomType, expectedStage, obstacleX[roomType], obstacleY[roomType]); + } + } + break; + } + } + super.onGameObjectSpawned(event); + } + @Override public void onChatMessage(ChatMessage message) { String stripped = Text.removeTags(message.getMessage()); @@ -201,6 +286,10 @@ public void onChatMessage(ChatMessage message) { // endRaid must be delayed with its own invokeLater so it runs AFTER that // StageUpdateEvent reaches the WebSocketEventHandler, otherwise CHALLENGE_END // is sent to the server before the final stage update. + if (tracker == null) { + log.warn("Raid completion detected but roomDataTracker is null"); + return; + } tracker.finishLastRoom(currentTick); // Clean up the old tracker properly @@ -214,54 +303,20 @@ public void onChatMessage(ChatMessage message) { return; } - // Floor complete (dispatch floor event) - Matcher floorMatcher = FLOOR_COMPLETE_REGEX.matcher(stripped); - if (floorMatcher.find() && getState() == ChallengeState.ACTIVE) { - int currentTick = getRelativeTick(); - final RoomDataTracker tracker = roomDataTracker; // Capture non-null value - tracker.finishRoom(currentTick); - log.info("Finished floor at tick {}", currentTick); - removeEventHandler(tracker); - roomDataTracker = null; - // Delay starting the next room to ensure the finish event is dispatched first - Stage nextStage = getNextStage(tracker.getStage()); - if (nextStage != null) { - final int tickForNextRoom = currentTick; // Capture tick for lambda - getClientThread().invokeLater(() -> { - roomDataTracker = createRoomDataTracker(nextStage); - final RoomDataTracker newTracker = roomDataTracker; // Capture new non-null value - if (newTracker != null) { - newTracker.startRoom(tickForNextRoom); - log.info("Started tracking next room {} at tick {}", nextStage, tickForNextRoom); - } - }); + // Floor completion detection (like dey0's plugin) + Matcher floorCompleteMatcher = FLOOR_COMPLETE_REGEX.matcher(stripped); + if (floorCompleteMatcher.matches() && getState() == ChallengeState.ACTIVE) { + final RoomDataTracker tracker = roomDataTracker; + if (tracker == null) { + log.warn("Floor completion detected but roomDataTracker is null"); + return; } - } - - // Room complete (dispatch room event) - Matcher roomMatcher = ROOM_COMPLETE_REGEX.matcher(stripped); - if (roomMatcher.find() && roomDataTracker != null) { + int currentTick = getRelativeTick(); - final RoomDataTracker tracker = roomDataTracker; // Capture non-null value tracker.finishRoom(currentTick); - - // Clean up the old tracker properly - removeEventHandler(tracker); - roomDataTracker = null; - - // Delay starting the next room to ensure the finish event is dispatched first - Stage nextStage = getNextStage(tracker.getStage()); - if (nextStage != null) { - final int tickForNextRoom = currentTick; // Capture tick for lambda - getClientThread().invokeLater(() -> { - roomDataTracker = createRoomDataTracker(nextStage); - final RoomDataTracker newTracker = roomDataTracker; // Capture new non-null value - if (newTracker != null) { - newTracker.startRoom(tickForNextRoom); - log.info("Started tracking next room {} at tick {}", nextStage, tickForNextRoom); - } - }); - } + log.info("Finished floor at tick {} (detected via chat message)", currentTick); + advanceToNextRoom(tracker, currentTick); + return; } } @@ -275,12 +330,31 @@ private int getRelativeTick() { return client.getTickCount() - raidStartTick; } + private void advanceToNextRoom(RoomDataTracker finishedTracker, int currentTick) { + removeEventHandler(finishedTracker); + roomDataTracker = null; + Stage nextStage = getNextStage(finishedTracker.getStage()); + if (nextStage != null) { + getClientThread().invokeLater(() -> { + roomDataTracker = createRoomDataTracker(nextStage); + final RoomDataTracker newTracker = roomDataTracker; + if (newTracker != null) { + newTracker.startRoom(currentTick); + log.info("Started tracking next room {} at tick {}", nextStage, currentTick); + } + }); + } + } + private void startRaid() { inRaid = true; setState(ChallengeState.ACTIVE); raidStartTick = client.getTickCount(); startTick = 0; // relative to raid start - + Arrays.fill(obstacleP, -1); + Arrays.fill(obstacleX, -1); + Arrays.fill(obstacleY, -1); + // Add the local player to the party to ensure scale is at least 1 addRaider(new Raider(client.getLocalPlayer(), true)); @@ -306,11 +380,12 @@ private void startRaid() { private void endRaid(ChallengeState completionState) { inRaid = false; setState(ChallengeState.ENDING); - endTick = getRelativeTick(); + endTick = getRelativeTick() - 1; // Adjust end tick to be the last tick of the raid, not the tick after completion message int overallTime = endTick - startTick; log.info("Raid end detected at tick {} (end/relative tick: {}). Start Tick: {} ticks", getTick(), endTick, startTick); // Use parsed completion time if available, otherwise fall back to measured overall time. int challengeTime = reportedChallengeTime > 0 ? reportedChallengeTime : overallTime; + log.info("Chambers of Xeric raid ended: challenge={}, overall={} ticks", challengeTime, overallTime); dispatchEvent(new ChallengeEndEvent(overallTime, overallTime)); log.info("Chambers of Xeric raid ended: challenge={}, overall={} ticks", challengeTime, overallTime); onTerminate(); @@ -588,17 +663,57 @@ private RoomDataTracker createRoomDataTracker(Stage stage) { // Add methods for party management, room tracking, etc. as needed. private static final int COX_LOBBY_REGION_ID = 4919; - // COX lobby area coordinates (center point from Mount Quidamortem bank) + // Fallback coordinates (center point from Mount Quidamortem bank) private static final int COX_LOBBY_X = 1232; private static final int COX_LOBBY_Y = 3572; private static final int COX_LOBBY_Z = 0; private static final int COX_LOBBY_RADIUS = 5; // Tiles from center point private boolean enteredLobby = false; + + @Nullable + private CoxLocation currentLocation = null; + + /** + * Gets the current location in the raid. + * + * @return The current CoxLocation, or null if not in a valid COX location + */ + @Nullable + public CoxLocation getCurrentLocation() { + return currentLocation; + } + + /** + * Updates the current location based on the player's world point. + * Called automatically on tick. + * + * @param worldPoint The player's current world point + */ + private void updateCurrentLocation(net.runelite.api.coords.WorldPoint worldPoint) { + CoxLocation newLocation = CoxLocation.fromWorldPoint(client, worldPoint); + if (newLocation != currentLocation) { + CoxLocation oldLocation = currentLocation; + currentLocation = newLocation; + + if (currentLocation != null && oldLocation != null) { + log.debug("Location changed: {} -> {}", oldLocation, currentLocation); + } + } + } @Override public boolean containsLocation(net.runelite.api.coords.WorldPoint worldPoint) { if (!enteredLobby) { - // Check if player is in the COX lobby area + // Use CoxLocation for precise instance template chunk detection + CoxLocation location = CoxLocation.fromWorldPoint(client, worldPoint); + if (location != null && location != CoxLocation.UNKNOWN) { + enteredLobby = true; + currentLocation = location; + log.debug("Entered COX raid at location: {}", location); + return true; + } + + // Fallback to region-based detection for non-instance areas (outside lobby) int regionId = worldPoint.getRegionID(); if (regionId == COX_LOBBY_REGION_ID) { int dx = Math.abs(worldPoint.getX() - COX_LOBBY_X); @@ -607,6 +722,8 @@ public boolean containsLocation(net.runelite.api.coords.WorldPoint worldPoint) { if (dz == 0 && dx <= COX_LOBBY_RADIUS && dy <= COX_LOBBY_RADIUS) { enteredLobby = true; + currentLocation = CoxLocation.LOBBY; + log.debug("Entered COX raid at lobby (fallback detection)"); return true; } } diff --git a/src/main/java/io/blert/challenges/chambers/CoxLocation.java b/src/main/java/io/blert/challenges/chambers/CoxLocation.java index 7bce4c9..9cd37be 100644 --- a/src/main/java/io/blert/challenges/chambers/CoxLocation.java +++ b/src/main/java/io/blert/challenges/chambers/CoxLocation.java @@ -1,32 +1,218 @@ package io.blert.challenges.chambers; -import net.runelite.api.coords.WorldArea; +import io.blert.core.Stage; +import net.runelite.api.Client; import net.runelite.api.coords.WorldPoint; +import javax.annotation.Nullable; + +/** + * Represents different locations within the Chambers of Xeric raid. + * Uses instance template chunk detection for precise location identification. + */ public enum CoxLocation { LOBBY, - ROOM_1, - ROOM_2, - ROOM_3, - ROOM_4, - ROOM_5; - public static final WorldArea LOBBY_AREA = new WorldArea(12686, 11971, 1, 1, 3); - public static CoxLocation fromWorldPoint(WorldPoint point) { - switch (point.getRegionID()) { - case 12345: // Replace with actual region IDs - return LOBBY_AREA.contains(point) ? LOBBY : null; - case 12346: - return ROOM_1; - case 12347: - return ROOM_2; - case 12348: - return ROOM_3; - case 12349: - return ROOM_4; - case 12350: - return ROOM_5; + FLOOR_END, + SCAVENGERS, + FARMING, + TEKTON, + CRABS, + ICE_DEMON, + SHAMANS, + VANGUARDS, + THIEVING, + VESPULA, + TIGHTROPE, + GUARDIANS, + VASA, + MYSTICS, + MUTTADILES, + OLM, + UNKNOWN; + + /** + * Determines the COX location from a world point using instance template chunk detection. + * + * @param client The game client + * @param worldPoint The world point to check + * @return The COX location, or null if not in a COX instance + */ + @Nullable + public static CoxLocation fromWorldPoint(Client client, WorldPoint worldPoint) { + if (client == null || worldPoint == null) { + return null; + } + + var topLevelWorldView = client.getTopLevelWorldView(); + if (topLevelWorldView == null || !topLevelWorldView.isInstance()) { + return null; + } + + var templateChunks = topLevelWorldView.getInstanceTemplateChunks(); + if (templateChunks == null) { + return null; + } + + int plane = worldPoint.getPlane(); + int sceneX = worldPoint.getX() - topLevelWorldView.getBaseX(); + int sceneY = worldPoint.getY() - topLevelWorldView.getBaseY(); + + // Check bounds before accessing array + if (plane < 0 || plane >= templateChunks.length || + sceneX < 0 || sceneX >= 104 || sceneY < 0 || sceneY >= 104) { + return null; + } + + int chunkX = sceneX / 8; + int chunkY = sceneY / 8; + + if (chunkX >= templateChunks[plane].length || + chunkY >= templateChunks[plane][chunkX].length) { + return null; + } + + int template = templateChunks[plane][chunkX][chunkY]; + int roomType = CoxRoomUtil.getRoomType(template); + + return fromRoomType(roomType); + } + + /** + * Maps a CoxRoomUtil room type constant to a CoxLocation. + * + * @param roomType The room type constant from CoxRoomUtil + * @return The corresponding CoxLocation + */ + public static CoxLocation fromRoomType(int roomType) { + switch (roomType) { + case CoxRoomUtil.FL_START: + return LOBBY; + case CoxRoomUtil.FL_END: + return FLOOR_END; + case CoxRoomUtil.SCAVENGERS: + return SCAVENGERS; + case CoxRoomUtil.FARMING: + return FARMING; + case CoxRoomUtil.TEKTON: + return TEKTON; + case CoxRoomUtil.CRABS: + return CRABS; + case CoxRoomUtil.ICE_DEMON: + return ICE_DEMON; + case CoxRoomUtil.SHAMANS: + return SHAMANS; + case CoxRoomUtil.VANGUARDS: + return VANGUARDS; + case CoxRoomUtil.THIEVING: + return THIEVING; + case CoxRoomUtil.VESPULA: + return VESPULA; + case CoxRoomUtil.TIGHTROPE: + return TIGHTROPE; + case CoxRoomUtil.GUARDIANS: + return GUARDIANS; + case CoxRoomUtil.VASA: + return VASA; + case CoxRoomUtil.MYSTICS: + return MYSTICS; + case CoxRoomUtil.MUTTADILES: + return MUTTADILES; + case CoxRoomUtil.OLM: + return OLM; + default: + return UNKNOWN; + } + } + + /** + * Checks if this location is a combat room. + * + * @return true if this is a combat room (not lobby, floor end, scavengers, or farming) + */ + public boolean isCombatRoom() { + switch (this) { + case TEKTON: + case CRABS: + case ICE_DEMON: + case SHAMANS: + case VANGUARDS: + case THIEVING: + case VESPULA: + case TIGHTROPE: + case GUARDIANS: + case VASA: + case MYSTICS: + case MUTTADILES: + case OLM: + return true; default: - return null; + return false; + } + } + + /** + * Checks if this location is a puzzle room. + * + * @return true if this is a puzzle room (thieving or tightrope) + */ + public boolean isPuzzleRoom() { + return this == THIEVING || this == TIGHTROPE; + } + + /** + * Converts this location to the corresponding blert Stage. + * + * @return The corresponding Stage, or null if no mapping exists + */ + @Nullable + public Stage toStage() { + switch (this) { + case TEKTON: return Stage.COX_TEKTON; + case CRABS: return Stage.COX_CRABS; + case ICE_DEMON: return Stage.COX_ICE_DEMON; + case SHAMANS: return Stage.COX_SHAMANS; + case VANGUARDS: return Stage.COX_VANGUARDS; + case THIEVING: return Stage.COX_THIEVING; + case VESPULA: return Stage.COX_VESPULA; + case TIGHTROPE: return Stage.COX_TIGHTROPE; + case GUARDIANS: return Stage.COX_GUARDIANS; + case VASA: return Stage.COX_VASA; + case MYSTICS: return Stage.COX_MYSTICS; + case MUTTADILES: return Stage.COX_MUTTADILE; + case OLM: return Stage.COX_OLM; + default: return null; + } + } + + /** + * Converts a blert Stage to the corresponding CoxLocation. + * + * @param stage The Stage to convert + * @return The corresponding CoxLocation, or null if no mapping exists + */ + @Nullable + public static CoxLocation fromStage(Stage stage) { + if (stage == null) { + return null; + } + switch (stage) { + case COX_TEKTON: return TEKTON; + case COX_CRABS: return CRABS; + case COX_ICE_DEMON: return ICE_DEMON; + case COX_SHAMANS: return SHAMANS; + case COX_VANGUARDS: return VANGUARDS; + case COX_THIEVING: return THIEVING; + case COX_VESPULA: return VESPULA; + case COX_TIGHTROPE: return TIGHTROPE; + case COX_GUARDIANS: return GUARDIANS; + case COX_VASA: return VASA; + case COX_MYSTICS: return MYSTICS; + case COX_MUTTADILE: return MUTTADILES; + case COX_OLM: return OLM; + case COX_FLOOR_1: + case COX_FLOOR_2: + case COX_FLOOR_3: return FLOOR_END; + default: return null; } } } \ No newline at end of file diff --git a/src/main/java/io/blert/challenges/chambers/CoxRoomUtil.java b/src/main/java/io/blert/challenges/chambers/CoxRoomUtil.java new file mode 100644 index 0000000..03f7ade --- /dev/null +++ b/src/main/java/io/blert/challenges/chambers/CoxRoomUtil.java @@ -0,0 +1,190 @@ +package io.blert.challenges.chambers; + +import io.blert.core.Stage; + +/** + * Utility for identifying CoX room types from instance template chunk codes. + * Logic ported from dey0's CoxTimers plugin (de0.util.CoxUtil). + */ +public class CoxRoomUtil { + + // Template chunk encoding: pp_xxxxxxxxxx_yyyyyyyyyy_rr0 + private static final int COX_ROOM_MASK = 0b11_1111111100_11111111100_00_0; + + private static final int FL_END1 = 0 << 24 | 102 << 16 | 160 << 5; + private static final int FL_END2 = 0 << 24 | 102 << 16 | 161 << 5; + private static final int FL_END3 = 0 << 24 | 103 << 16 | 161 << 5; + + private static final int LOBBY_CCW = 0 << 24 | 102 << 16 | 162 << 5; + private static final int LOBBY_THRU = 0 << 24 | 103 << 16 | 162 << 5; + private static final int LOBBY_CW = 0 << 24 | 104 << 16 | 162 << 5; + + private static final int SCAVS_SM_CCW = 0 << 24 | 102 << 16 | 163 << 5; + private static final int SCAVS_SM_THRU = 0 << 24 | 103 << 16 | 163 << 5; + private static final int SCAVS_SM_CW = 0 << 24 | 104 << 16 | 163 << 5; + + private static final int SHAMANS_CCW = 0 << 24 | 102 << 16 | 164 << 5; + private static final int SHAMANS_THRU = 0 << 24 | 103 << 16 | 164 << 5; + private static final int SHAMANS_CW = 0 << 24 | 104 << 16 | 164 << 5; + + private static final int VASA_CCW = 0 << 24 | 102 << 16 | 165 << 5; + private static final int VASA_THRU = 0 << 24 | 103 << 16 | 165 << 5; + private static final int VASA_CW = 0 << 24 | 104 << 16 | 165 << 5; + + private static final int VANGUARDS_CCW = 0 << 24 | 102 << 16 | 166 << 5; + private static final int VANGUARDS_THRU = 0 << 24 | 103 << 16 | 166 << 5; + private static final int VANGUARDS_CW = 0 << 24 | 104 << 16 | 166 << 5; + + private static final int ICE_DEMON_CCW = 0 << 24 | 102 << 16 | 167 << 5; + private static final int ICE_DEMON_THRU = 0 << 24 | 103 << 16 | 167 << 5; + private static final int ICE_DEMON_CW = 0 << 24 | 104 << 16 | 167 << 5; + + private static final int THIEVING_CCW = 0 << 24 | 102 << 16 | 168 << 5; + private static final int THIEVING_THRU = 0 << 24 | 103 << 16 | 168 << 5; + private static final int THIEVING_CW = 0 << 24 | 104 << 16 | 168 << 5; + + private static final int FARM_FISH_CCW = 0 << 24 | 102 << 16 | 170 << 5; + private static final int FARM_FISH_THRU = 0 << 24 | 103 << 16 | 170 << 5; + private static final int FARM_FISH_CW = 0 << 24 | 104 << 16 | 170 << 5; + + private static final int FL_START1_CCW = 0 << 24 | 102 << 16 | 178 << 5; + private static final int FL_START1_THRU = 0 << 24 | 103 << 16 | 178 << 5; + private static final int FL_START1_CW = 0 << 24 | 104 << 16 | 178 << 5; + + private static final int FL_START2_CCW = 0 << 24 | 102 << 16 | 179 << 5; + private static final int FL_START2_THRU = 0 << 24 | 103 << 16 | 179 << 5; + private static final int FL_START2_CW = 0 << 24 | 104 << 16 | 179 << 5; + + private static final int SCAVS_LG_CCW = 1 << 24 | 102 << 16 | 163 << 5; + private static final int SCAVS_LG_THRU = 1 << 24 | 103 << 16 | 163 << 5; + private static final int SCAVS_LG_CW = 1 << 24 | 104 << 16 | 163 << 5; + + private static final int MYSTICS_CCW = 1 << 24 | 102 << 16 | 164 << 5; + private static final int MYSTICS_THRU = 1 << 24 | 103 << 16 | 164 << 5; + private static final int MYSTICS_CW = 1 << 24 | 104 << 16 | 164 << 5; + + private static final int TEKTON_CCW = 1 << 24 | 102 << 16 | 165 << 5; + private static final int TEKTON_THRU = 1 << 24 | 103 << 16 | 165 << 5; + private static final int TEKTON_CW = 1 << 24 | 104 << 16 | 165 << 5; + + private static final int MUTTADILES_CCW = 1 << 24 | 102 << 16 | 166 << 5; + private static final int MUTTADILES_THRU = 1 << 24 | 103 << 16 | 166 << 5; + private static final int MUTTADILES_CW = 1 << 24 | 104 << 16 | 166 << 5; + + private static final int TIGHTROPE_CCW = 1 << 24 | 102 << 16 | 167 << 5; + private static final int TIGHTROPE_THRU = 1 << 24 | 103 << 16 | 167 << 5; + private static final int TIGHTROPE_CW = 1 << 24 | 104 << 16 | 167 << 5; + + private static final int FARM_BATS_CCW = 1 << 24 | 102 << 16 | 170 << 5; + private static final int FARM_BATS_THRU = 1 << 24 | 103 << 16 | 170 << 5; + private static final int FARM_BATS_CW = 1 << 24 | 104 << 16 | 170 << 5; + + private static final int GUARDIANS_CCW = 2 << 24 | 102 << 16 | 164 << 5; + private static final int GUARDIANS_THRU = 2 << 24 | 103 << 16 | 164 << 5; + private static final int GUARDIANS_CW = 2 << 24 | 104 << 16 | 164 << 5; + + private static final int VESPULA_CCW = 2 << 24 | 102 << 16 | 165 << 5; + private static final int VESPULA_THRU = 2 << 24 | 103 << 16 | 165 << 5; + private static final int VESPULA_CW = 2 << 24 | 104 << 16 | 165 << 5; + + private static final int CRABS_CCW = 2 << 24 | 102 << 16 | 167 << 5; + private static final int CRABS_THRU = 2 << 24 | 103 << 16 | 167 << 5; + private static final int CRABS_CW = 2 << 24 | 104 << 16 | 167 << 5; + + private static final int OLM_ROOM_MASK = 0b11_1111111000_11111111000_00_0; + private static final int OLM_ = 0 << 24 | 50 << 17 | 89 << 6; + + // Room type constants + public static final int FL_START = 0; + public static final int FL_END = 1; + public static final int SCAVENGERS = 2; + public static final int FARMING = 3; + public static final int SHAMANS = 4; + public static final int VASA = 5; + public static final int VANGUARDS = 6; + public static final int MYSTICS = 7; + public static final int TEKTON = 8; + public static final int MUTTADILES = 9; + public static final int GUARDIANS = 10; + public static final int VESPULA = 11; + public static final int ICE_DEMON = 12; + public static final int THIEVING = 13; + public static final int TIGHTROPE = 14; + public static final int CRABS = 15; + public static final int OLM = 16; + public static final int UNKNOWN = 17; + + /** + * Resolves a template chunk value to a room type constant. + * Ported verbatim from de0.util.CoxUtil.getroom_type(). + */ + public static int getRoomType(int zonecode) { + switch (zonecode & COX_ROOM_MASK) { + case LOBBY_CCW: case LOBBY_THRU: case LOBBY_CW: + case FL_START1_CCW: case FL_START1_THRU: case FL_START1_CW: + case FL_START2_CCW: case FL_START2_THRU: case FL_START2_CW: + return FL_START; + case FL_END1: case FL_END2: case FL_END3: + return FL_END; + case SCAVS_SM_CCW: case SCAVS_SM_THRU: case SCAVS_SM_CW: + case SCAVS_LG_CCW: case SCAVS_LG_THRU: case SCAVS_LG_CW: + return SCAVENGERS; + case FARM_FISH_CCW: case FARM_FISH_THRU: case FARM_FISH_CW: + case FARM_BATS_CCW: case FARM_BATS_THRU: case FARM_BATS_CW: + return FARMING; + case SHAMANS_CCW: case SHAMANS_THRU: case SHAMANS_CW: + return SHAMANS; + case VASA_CCW: case VASA_THRU: case VASA_CW: + return VASA; + case VANGUARDS_CCW: case VANGUARDS_THRU: case VANGUARDS_CW: + return VANGUARDS; + case MYSTICS_CCW: case MYSTICS_THRU: case MYSTICS_CW: + return MYSTICS; + case TEKTON_CCW: case TEKTON_THRU: case TEKTON_CW: + return TEKTON; + case MUTTADILES_CCW: case MUTTADILES_THRU: case MUTTADILES_CW: + return MUTTADILES; + case GUARDIANS_CCW: case GUARDIANS_THRU: case GUARDIANS_CW: + return GUARDIANS; + case VESPULA_CCW: case VESPULA_THRU: case VESPULA_CW: + return VESPULA; + case ICE_DEMON_CCW: case ICE_DEMON_THRU: case ICE_DEMON_CW: + return ICE_DEMON; + case THIEVING_CCW: case THIEVING_THRU: case THIEVING_CW: + return THIEVING; + case TIGHTROPE_CCW: case TIGHTROPE_THRU: case TIGHTROPE_CW: + return TIGHTROPE; + case CRABS_CCW: case CRABS_THRU: case CRABS_CW: + return CRABS; + } + if ((zonecode & OLM_ROOM_MASK) == OLM_) { + return OLM; + } + return UNKNOWN; + } + + /** + * Maps a room type constant to the corresponding blert {@link Stage}, + * or {@code null} if there is no direct mapping (e.g. FL_START, SCAVENGERS). + */ + public static Stage roomTypeToStage(int roomType) { + switch (roomType) { + case TEKTON: return Stage.COX_TEKTON; + case CRABS: return Stage.COX_CRABS; + case ICE_DEMON: return Stage.COX_ICE_DEMON; + case SHAMANS: return Stage.COX_SHAMANS; + case VANGUARDS: return Stage.COX_VANGUARDS; + case THIEVING: return Stage.COX_THIEVING; + case VESPULA: return Stage.COX_VESPULA; + case TIGHTROPE: return Stage.COX_TIGHTROPE; + case GUARDIANS: return Stage.COX_GUARDIANS; + case VASA: return Stage.COX_VASA; + case MYSTICS: return Stage.COX_MYSTICS; + case MUTTADILES: return Stage.COX_MUTTADILE; + case OLM: return Stage.COX_OLM; + default: return null; + } + } + + private CoxRoomUtil() {} +} diff --git a/src/main/java/io/blert/challenges/chambers/RoomDataTracker.java b/src/main/java/io/blert/challenges/chambers/RoomDataTracker.java index 682ff07..56f923d 100644 --- a/src/main/java/io/blert/challenges/chambers/RoomDataTracker.java +++ b/src/main/java/io/blert/challenges/chambers/RoomDataTracker.java @@ -15,11 +15,6 @@ // import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.GameStateChanged; -import net.runelite.client.util.Text; - -// import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Tracks Chambers of Xeric room times and dispatches events to the Blert event handler. @@ -27,7 +22,6 @@ */ @Slf4j public abstract class RoomDataTracker extends DataTracker implements EventHandler { - private final Pattern roomEndRegex; private final Stage stage; private boolean started = false; private int startTick = 0; @@ -40,8 +34,6 @@ public abstract class RoomDataTracker extends DataTracker implements EventHandle public RoomDataTracker(RecordableChallenge challenge, Stage stage, Client client) { super(challenge, client, stage); this.stage = stage; - this.roomEndRegex = Pattern.compile("(Combat room|Puzzle) `.*` complete! Duration: .*"); - // this.roomEndRegex = Pattern.compile("Congratulations - your raid is complete!.*"); } /** @@ -122,11 +114,8 @@ protected int getStartTick() { @Override protected void onMessage(ChatMessage chatMessage) { - String stripped = Text.removeTags(chatMessage.getMessage()); - Matcher matcher = roomEndRegex.matcher(stripped); - if (matcher.find()) { - finishRoom(getStartTick() + getTick()); - } + // Room completion is now detected via collision flags in CoxChallenge.onTick() + // No chat message handling needed here } @Override