Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.blert.util.Location;
import io.blert.util.Tick;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -99,6 +100,14 @@ public boolean hasLogged() {
return hasLogged;
}

@Override
public Optional<OverallTicks> getOverallTicks() {
if (hasLogged || getState().isInactive() || challengeStartTick == -1) {
return Optional.empty();
}
return Optional.of(OverallTicks.local(recordedDurationTicks()));
}

@Override
public boolean containsLocation(WorldPoint worldPoint) {
return worldPoint.getRegionID() == INFERNO_REGION_ID
Expand Down
98 changes: 56 additions & 42 deletions src/main/java/io/blert/challenges/tfa/ArchiveChallenge.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,21 @@
import io.blert.util.Tick;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import joptsimple.internal.Strings;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.events.*;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.util.Text;
import org.apache.commons.lang3.tuple.Pair;
Expand All @@ -63,6 +64,7 @@
public class ArchiveChallenge extends RecordableChallenge {
private static final int TFA_PARTY_VARBIT = 16386;
private static final int TFA_PARTY_LIST_COMPONENT_ID = 63897605;
private static final int TFA_RAID_TIMER_SCRIPT_ID = 10036;

private static final int TFA_VARBIT_NO_PARTY = 0;
private static final int TFA_VARBIT_IN_PARTY = 1;
Expand All @@ -73,8 +75,6 @@ public class ArchiveChallenge extends RecordableChallenge {
Pattern.compile("(.+) has entered the Fractured Archive\\. Step inside to join (her|him|them)\\.\\.\\.");
private static final String RAID_WIPE_MESSAGE = "You failed to survive the Fractured Archive.";
private static final String RAID_ABANDON_MESSAGE = "You abandon the raid and leave the Fractured Archive.";
private static final Pattern BOSS_COMPLETION_TOTAL_REGEX = Pattern.compile("Challenge complete: .+?\\. Duration: "
+ Tick.TIME_STRING_REGEX + "\\. Total: (?<total>" + Tick.TIME_STRING_REGEX + ")");
private static final Pattern RAID_COMPLETION_REGEX =
Pattern.compile("The Fractured Archive: Total completion time: (" + Tick.TIME_STRING_REGEX + ").*");

Expand All @@ -86,8 +86,16 @@ public class ArchiveChallenge extends RecordableChallenge {

private @Nullable DeferredTask deferredTask = null;

// Ticks since Guardian start, from the latest boss completion message.
private int reportedOverallTime = -1;
@AllArgsConstructor
private static class RaidTimer {
private final int clientTick;
private final int ticks;
private final boolean stopped;
}

// The raid timer as of the latest run of its script.
@Nullable
private RaidTimer raidTimer = null;

public ArchiveChallenge(Client client, ClientThread clientThread) {
super(Challenge.TFA, client, clientThread);
Expand All @@ -105,37 +113,13 @@ public boolean containsLocation(WorldPoint worldPoint) {
}

@Override
protected void onInitialize() {
// TODO(frolv): Temporary, for comparing TFA lifecycle against captures. Remove once verified.
addEventHandler((clientTick, event) -> {
switch (event.getType()) {
case CHALLENGE_START:
case CHALLENGE_UPDATE:
case CHALLENGE_END:
case PLAYER_DEATH:
log.warn("TFA-VERIFY tick={} state={} {}", clientTick, getState(), event);
break;
case STAGE_UPDATE:
StageUpdateEvent update = (StageUpdateEvent) event;
log.warn(
"TFA-VERIFY tick={} state={} {} inGameTicks={} accurate={} overall={}",
clientTick,
getState(),
event,
update.getInGameTicks().orElse(-1),
update.isAccurate(),
reportedOverallTime);
break;
default:
break;
}
});
}
protected void onInitialize() {}

@Override
protected void onTerminate() {
clearBossDataTracker();
resetParty();
raidTimer = null;

// The same instance records the next raid, so drop anything still
// pending from this one.
Expand Down Expand Up @@ -204,6 +188,21 @@ protected Stage getStage() {
return bossDataTracker != null ? bossDataTracker.getStage() : null;
}

/**
* Returns the raid's overall time in ticks, from the start of Guardian, if known.
*/
@Override
public Optional<OverallTicks> getOverallTicks() {
if (raidTimer == null) {
return Optional.empty();
}
// The raid timer is always precise.
if (raidTimer.stopped) {
return Optional.of(OverallTicks.server(raidTimer.ticks, true));
}
return Optional.of(OverallTicks.server(raidTimer.ticks + client.getTickCount() - raidTimer.clientTick, true));
}

/**
* Invokes a callback for each username displayed in the TFA orb list.
*
Expand All @@ -224,7 +223,6 @@ public void forEachOrb(BiConsumer<Integer, String> callback) {
private void queueRaidStart(boolean reinitializeParty) {
log.info("Starting new TFA raid");
setState(ChallengeState.PREPARING);
reportedOverallTime = -1;

if (reinitializeParty) {
// The in-raid party list is written 7 ticks after the player's entry message;
Expand Down Expand Up @@ -276,6 +274,7 @@ private void queueRaidEnd(ChallengeState state, int overallTime) {
clearBossDataTracker();
clearPendingEvents();
resetParty();
raidTimer = null;
deferredTask = null;
log.info("TFA raid ended before it started");
return;
Expand All @@ -294,6 +293,7 @@ private void endRaid(ChallengeState state, int overallTime) {
clearBossDataTracker();
setState(state);
resetParty();
raidTimer = null;

getClientThread().invokeAtTickEnd(() -> dispatchEvent(new ChallengeEndEvent(-1, overallTime, soft)));
}
Expand Down Expand Up @@ -370,6 +370,27 @@ public void onVarbitChanged(VarbitChanged varbit) {
super.onVarbitChanged(varbit);
}

@Override
public void onScriptPreFired(ScriptPreFired event) {
if (event.getScriptId() == TFA_RAID_TIMER_SCRIPT_ID) {
// The script runs every tick with the raid's overall time in ticks,
// and whether the timer is stopped.
Object[] args = event.getScriptEvent().getArguments();
raidTimer = new RaidTimer(client.getTickCount(), (int) args[1], (int) args[2] == 1);
}

super.onScriptPreFired(event);
}

@Override
public void onGameStateChanged(GameStateChanged event) {
if (event.getGameState() == GameState.LOGIN_SCREEN) {
raidTimer = null;
}

super.onGameStateChanged(event);
}

@Override
public void onChatMessage(ChatMessage message) {
updateLocation();
Expand All @@ -396,13 +417,6 @@ public void onChatMessage(ChatMessage message) {
return;
}

Matcher matcher = BOSS_COMPLETION_TOTAL_REGEX.matcher(stripped);
if (matcher.find()) {
reportedOverallTime = Tick.fromTimeString(matcher.group("total"))
.map(Pair::getLeft)
.orElse(-1);
}

if (getState() == ChallengeState.ACTIVE
&& (stripped.equals(RAID_WIPE_MESSAGE) || stripped.equals(RAID_ABANDON_MESSAGE))) {
if (bossDataTracker != null) {
Expand All @@ -413,7 +427,7 @@ public void onChatMessage(ChatMessage message) {
queueRaidEnd(ChallengeState.COMPLETE);
}

matcher = RAID_COMPLETION_REGEX.matcher(stripped);
Matcher matcher = RAID_COMPLETION_REGEX.matcher(stripped);
if (getState() == ChallengeState.ACTIVE && matcher.matches()) {
int overallTime =
Tick.fromTimeString(matcher.group(1)).map(Pair::getLeft).orElse(-1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public abstract class BossDataTracker extends DataTracker {
private static final int TFA_ORB1_VARBIT = 16387;

private static final Pattern BOSS_START_REGEX = Pattern.compile("Challenge started: (.+)\\.");
private static final Pattern BOSS_END_REGEX =
Pattern.compile("Challenge complete: (.+?)\\. Duration: (" + Tick.TIME_STRING_REGEX + ")");
private static final Pattern BOSS_END_REGEX = Pattern.compile("Challenge complete: (.+?)\\. Duration: (?<duration>"
+ Tick.TIME_STRING_REGEX + ")(\\. Total: (?<total>" + Tick.TIME_STRING_REGEX + "))?");

// Boss names as they appear in the start and completion messages.
private static final Map<Stage, String> BOSS_NAMES = Map.of(
Expand Down Expand Up @@ -145,7 +145,7 @@ protected final void onMessage(ChatMessage chatMessage) {
matcher = BOSS_END_REGEX.matcher(stripped);
if (matcher.find() && isBossStartMessage(matcher.group(1))) {
completionReceived = true;
finish(matcher.group(2));
finish(matcher.group("duration"), matcher.group("total"));
}
}

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/io/blert/client/WebSocketEventHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.blert.BlertPluginPanel;
import io.blert.core.Challenge;
import io.blert.core.ChallengeMode;
import io.blert.core.OverallTicks;
import io.blert.core.RecordableChallenge;
import io.blert.core.Stage;
import io.blert.events.*;
Expand Down Expand Up @@ -416,6 +417,7 @@ void updateChallenge(@Nullable ChallengeUpdateEvent challenge, @Nullable StageUp
stageUpdate.recordedTicks = stage.getTick();
stageUpdate.gameTicksPrecise = stage.isGameTicksPrecise();
stage.getInGameTicks().ifPresent(t -> stageUpdate.gameServerTicks = t);
stage.getOverallTicks().ifPresent(t -> stageUpdate.overallTicks = translateOverallTicks(t));
challengeUpdate.stageUpdate = stageUpdate;
}

Expand Down Expand Up @@ -1072,4 +1074,16 @@ private static int translateStageStatus(StageUpdateEvent.Status status) {
throw new NotImplementedException("Stage status translation not implemented for " + status);
}
}

private static ChallengeUpdate.OverallTicks translateOverallTicks(OverallTicks overallTicks) {
ChallengeUpdate.OverallTicks json = new ChallengeUpdate.OverallTicks();
if (overallTicks instanceof OverallTicks.Server) {
json.server = new ChallengeUpdate.OverallTicks.Server();
json.server.ticks = overallTicks.getTicks();
json.server.precise = ((OverallTicks.Server) overallTicks).isPrecise();
} else {
json.local = overallTicks.getTicks();
}
return json;
}
}
Loading
Loading