Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Run tests
run: |
./gradlew --version
./gradlew test jacocoTestReport --no-daemon --stacktrace
./gradlew test jacocoTestReport --no-daemon --stacktrace --info
shell: bash
- name: Upload JUnit XML results
if: always()
Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ plugins {
}

repositories {
mavenLocal()
maven { url = 'https://repo.runelite.net' }
mavenCentral()
}
Expand Down
347 changes: 347 additions & 0 deletions missing.md

Large diffs are not rendered by default.

1,109 changes: 569 additions & 540 deletions src/main/resources/transports/agility_shortcuts.tsv

Large diffs are not rendered by default.

93 changes: 81 additions & 12 deletions src/main/resources/transports/transports.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3201,18 +3201,35 @@
3074 3653 0 3197 10056 0 Enter Cavern 31555 2
3075 3653 0 3197 10056 0 Enter Cavern 31555 2
3076 3653 0 3197 10056 0 Enter Cavern 31555 2
3069 3740 0 3187 10128 0 Jump-down Crevice 40386 2
3069 3741 0 3187 10128 0 Jump-down Crevice 40386 2
3069 3742 0 3187 10128 0 Jump-down Crevice 40386 2
3069 3743 0 3187 10128 0 Jump-down Crevice 40386 2
3066 3740 0 3187 10128 0 Jump-down Crevice 40386 2
3066 3741 0 3187 10128 0 Jump-down Crevice 40386 2
3066 3742 0 3187 10128 0 Jump-down Crevice 40386 2
3066 3743 0 3187 10128 0 Jump-down Crevice 40386 2
3067 3739 0 3187 10128 0 Jump-down Crevice 40386 2
3068 3739 0 3187 10128 0 Jump-down Crevice 40386 2
3067 3744 0 3187 10128 0 Jump-down Crevice 40386 2
3068 3744 0 3187 10128 0 Jump-down Crevice 40386 2

# Revenant Caves

3069 3740 0 3187 10127 0 Jump-down Crevice 40386 2
3069 3741 0 3187 10127 0 Jump-down Crevice 40386 2
3069 3742 0 3187 10127 0 Jump-down Crevice 40386 2
3069 3743 0 3187 10127 0 Jump-down Crevice 40386 2
3066 3740 0 3187 10127 0 Jump-down Crevice 40386 2
3066 3741 0 3187 10127 0 Jump-down Crevice 40386 2
3066 3742 0 3187 10127 0 Jump-down Crevice 40386 2
3066 3743 0 3187 10127 0 Jump-down Crevice 40386 2
3067 3739 0 3187 10127 0 Jump-down Crevice 40386 2
3068 3739 0 3187 10127 0 Jump-down Crevice 40386 2
3067 3744 0 3187 10127 0 Jump-down Crevice 40386 2
3068 3744 0 3187 10127 0 Jump-down Crevice 40386 2

3220 10088 0 3220 10084 0 Jump-to Pillar 31561 65 Agility 4
3220 10084 0 3220 10088 0 Jump-to Pillar 31561 65 Agility 4

3198 10136 0 3202 10132 0 Jump-to Pillar 31562 4
3202 10132 0 3198 10136 0 Jump-to Pillar 31562 4

3200 10196 0 3204 10196 0 Jump-to Pillar 31561 4
3204 10196 0 3200 10196 0 Jump-to Pillar 31561 4

3180 10211 0 3180 10207 0 Jump-to Pillar 31561 4
3180 10207 0 3180 10211 0 Jump-to Pillar 31561 4


3126 3831 0 3241 10233 0 Enter Cavern 31556 2
3126 3832 0 3241 10233 0 Enter Cavern 31556 2
3126 3833 0 3241 10233 0 Enter Cavern 31556 2
Expand Down Expand Up @@ -5280,3 +5297,55 @@

# Arzinian mine
2824 10169 0 2615 4966 0 Talk-to Dondakan the Dwarf 4891 4567=1 Between a Rock... 4

# Wintertodt

1633 4023 0 1631 4023 0 Jump Gap 29326 60 Agility 2
1631 4023 0 1629 4023 0 Jump Gap 29326 60 Agility 2
1629 4023 0 1627 4023 0 Jump Gap 29326 60 Agility 2

1631 4023 0 1633 4023 0 Jump Gap 29326 60 Agility 2
1629 4023 0 1631 4023 0 Jump Gap 29326 60 Agility 2
1627 4023 0 1629 4023 0 Jump Gap 29326 60 Agility 2



1627 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1628 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1629 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1630 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1631 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1632 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1
1633 3963 0 1630 3979 0 Enter Door of Dinh 29322 50 Firemaking 1


1627 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1628 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1629 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1630 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1631 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1632 3963 0 1630 3958 0 Enter Door of Dinh 29322 1
1633 3963 0 1630 3958 0 Enter Door of Dinh 29322 1


# Temple of the eye

3633 9503 0 3637 9503 0 Climb Rubble 43724 5
3637 9503 0 3633 9503 0 Climb Rubble 43726 5

3613 9482 0 3613 9484 0 Quick-pass Barrier 43700 5
3614 9482 0 3614 9484 0 Quick-pass Barrier 43700 5
3615 9482 0 3615 9484 0 Quick-pass Barrier 43700 5
3616 9482 0 3616 9484 0 Quick-pass Barrier 43700 5
3617 9482 0 3617 9484 0 Quick-pass Barrier 43700 5
3613 9484 0 3613 9482 0 Quick-pass Barrier 43700 5
3614 9484 0 3614 9482 0 Quick-pass Barrier 43700 5
3615 9484 0 3615 9482 0 Quick-pass Barrier 43700 5
3616 9484 0 3616 9482 0 Quick-pass Barrier 43700 5
3617 9484 0 3617 9482 0 Quick-pass Barrier 43700 5

2775 10003 0 2773 10003 0 Jump-over Strange floor 16544 43 Agility 10
2773 10003 0 2775 10003 0 Jump-over Strange floor 16544 43 Agility 10

2770 10002 0 2768 10002 0 Jump-over Strange floor 16544 43 Agility 10
2768 10002 0 2770 10002 0 Jump-over Strange floor 16544 43 Agility 10
223 changes: 223 additions & 0 deletions src/test/java/shortestpath/AgilityShortcutTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package shortestpath;

import net.runelite.client.game.AgilityShortcut;
import org.junit.Assert;
import org.junit.Test;
import shortestpath.transport.Transport;
import shortestpath.transport.TransportLoader;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This test checks every AgilityShortcut enum constant against the list of all transports
* in the resource files, to ensure that our list is complete.
* <p>
* This test should only fail when a new AgilityShortcut enum constant is added
* without a corresponding entry in the TSV file.
* <p>
* There are some known exceptions where an AgilityShortcut cannot be matched to a TSV entry,
* for example when the Object ID changes as the result of "unlocking" the shortcut via a quest.
* These exceptions can be added to the {@link #createExcludedIds()} method. Use this sparingly.
*/
public class AgilityShortcutTest {
private static final Pattern OBJECT_ID_PATTERN = Pattern.compile("\\d+");

/**
* IDs that are known exceptions and cannot be matched to TSV entries.
* These typically represent shortcuts that change based on quest completion or other game state.
*/
private static final Set<Integer> EXCLUDED_OBJECT_IDS = createExcludedIds();

private static Set<Integer> createExcludedIds() {
Set<Integer> excluded = new HashSet<>();

// These two IDs represent the same shortcut before and after a step in the 'Making Friends with My Arm' quest
// See: https://oldschool.runescape.wiki/w/Broken_fence_(Weiss)
excluded.add(46815);
excluded.add(46817);

// somehow not found in tsv
excluded.add(23644);

// somehow not found in tsv
excluded.add(11948);

// Somehow not found in tsv
excluded.add(23568);
excluded.add(23569);

// The rocks in the rock cab cave is not found because the coordinate is one off. but this is where you land
excluded.add(31697);

return excluded;
}

@Test
public void everyEnumHasTsvEntry() {
Map<Integer, Set<Transport>> allTransports = TransportLoader.loadAllFromResources();
TransportData transportData = extractTransportData(allTransports);

List<String> missingShortcuts = findMissingShortcuts(transportData);

if (!missingShortcuts.isEmpty()) {
String errorMessage = String.join("\n", missingShortcuts);
System.out.println("Missing TSV entries for the following AgilityShortcut enum constants:\n" + errorMessage);
Assert.fail("Missing TSV entries for AgilityShortcut enum constants (see stdout for list)");
}
}

/**
* Extracts coordinate and object ID data from all transports.
*/
private TransportData extractTransportData(Map<Integer, Set<Transport>> allTransports) {
Set<String> coordinates = new HashSet<>();
Set<Integer> objectIds = new HashSet<>();

for (Set<Transport> transportSet : allTransports.values()) {
for (Transport transport : transportSet) {
addCoordinatesFromTransport(transport, coordinates);
addObjectIdsFromTransport(transport, objectIds);
}
}

return new TransportData(coordinates, objectIds);
}

/**
* Adds origin and destination coordinates from a transport to the coordinate set.
*/
private void addCoordinatesFromTransport(Transport transport, Set<String> coordinates) {
addCoordinateIfValid(transport.getOrigin(), coordinates);
addCoordinateIfValid(transport.getDestination(), coordinates);
}

/**
* Adds a coordinate to the set if it's valid (not undefined or a permutation).
*/
private void addCoordinateIfValid(int packedCoordinate, Set<String> coordinates) {
if (packedCoordinate != Transport.UNDEFINED_ORIGIN &&
packedCoordinate != Transport.UNDEFINED_DESTINATION &&
packedCoordinate != Transport.LOCATION_PERMUTATION) {

String coordinate = formatCoordinate(packedCoordinate);
coordinates.add(coordinate);
}
}

/**
* Formats a packed coordinate as "x y plane".
*/
private String formatCoordinate(int packed) {
int x = WorldPointUtil.unpackWorldX(packed);
int y = WorldPointUtil.unpackWorldY(packed);
int plane = WorldPointUtil.unpackWorldPlane(packed);
return x + " " + y + " " + plane;
}

/**
* Extracts object IDs from the transport's object info string.
*/
private void addObjectIdsFromTransport(Transport transport, Set<Integer> objectIds) {
String objectInfo = transport.getObjectInfo();
if (objectInfo == null || objectInfo.isEmpty()) {
return;
}

Matcher matcher = OBJECT_ID_PATTERN.matcher(objectInfo);
while (matcher.find()) {
try {
objectIds.add(Integer.parseInt(matcher.group()));
} catch (NumberFormatException ignored) {
// Skip invalid numbers
}
}
}

/**
* Finds all AgilityShortcut enum constants that don't have matching TSV entries.
*/
private List<String> findMissingShortcuts(TransportData transportData) {
List<String> missing = new ArrayList<>();

for (AgilityShortcut shortcut : AgilityShortcut.values()) {
if (!isShortcutMatched(shortcut, transportData)) {
missing.add(shortcut.name() + " - " + shortcut.getLevel() + " - " + shortcut.getWorldLocation());
}
}

return missing;
}

/**
* Checks if an AgilityShortcut matches any entry in the transport data.
*/
private boolean isShortcutMatched(AgilityShortcut shortcut, TransportData transportData) {
// Check by world location
if (isMatchedByLocation(shortcut, transportData.coordinates)) {
return true;
}

// Check by obstacle IDs
return isMatchedByObstacleIds(shortcut, transportData.objectIds);
}

/**
* Checks if the shortcut's world location matches any coordinate in the transport data.
*/
private boolean isMatchedByLocation(AgilityShortcut shortcut, Set<String> coordinates) {
if (shortcut.getWorldLocation() == null) {
return false;
}

String coordinate = shortcut.getWorldLocation().getX() + " "
+ shortcut.getWorldLocation().getY() + " "
+ shortcut.getWorldLocation().getPlane();

return coordinates.contains(coordinate);
}

/**
* Checks if any of the shortcut's obstacle IDs match object IDs in the transport data.
* Returns true if any non-excluded ID matches, or if all IDs are excluded.
*/
private boolean isMatchedByObstacleIds(AgilityShortcut shortcut, Set<Integer> objectIds) {
if (shortcut.getObstacleIds() == null) {
return false;
}

boolean hasExcludedId = false;
for (int obstacleId : shortcut.getObstacleIds()) {
if (EXCLUDED_OBJECT_IDS.contains(obstacleId)) {
System.out.println("Skipping excluded ID " + obstacleId + " for " + shortcut.name());
hasExcludedId = true;
continue;
}

if (objectIds.contains(obstacleId)) {
return true;
}
}

// If all IDs were excluded, consider it matched to avoid false failures
return hasExcludedId;
}

/**
* Holds coordinate and object ID data extracted from transports.
*/
private static class TransportData {
final Set<String> coordinates;
final Set<Integer> objectIds;

TransportData(Set<String> coordinates, Set<Integer> objectIds) {
this.coordinates = coordinates;
this.objectIds = objectIds;
}
}
}
Loading
Loading