From 1c216ec6d9056d870c9c72b8abb27e8007ea0b5a Mon Sep 17 00:00:00 2001 From: valarnin Date: Thu, 1 Jan 2026 01:07:09 -0500 Subject: [PATCH] raidboss: Add new properties to `data` --- test/helper/test_trigger.ts | 7 +++++ types/data.d.ts | 7 +++++ ui/raidboss/data/07-dt/raid/r10n.ts | 2 +- ui/raidboss/data/07-dt/trial/doomtrain.ts | 2 +- ui/raidboss/data/07-dt/trial/necron-ex.ts | 4 +-- .../overrides/RaidEmulatorTimeline.ts | 10 +++++++ ui/raidboss/popup-text.ts | 9 +++++++ ui/raidboss/raidboss_config.ts | 7 +++++ ui/raidboss/timeline.ts | 27 +++++++++++++++++++ ui/raidboss/timeline_parser.ts | 3 ++- 10 files changed, 72 insertions(+), 6 deletions(-) diff --git a/test/helper/test_trigger.ts b/test/helper/test_trigger.ts index 780f6e50df5..3a50f23c249 100644 --- a/test/helper/test_trigger.ts +++ b/test/helper/test_trigger.ts @@ -40,6 +40,9 @@ const emptyPartyTracker = new PartyTracker(raidbossOptions); const getFakeRaidbossData = (triggerSet?: LooseTriggerSet): RaidbossData => { return { me: '', + meId: '10001234', + zoneName: '', + zoneId: -1, job: 'NONE', role: 'none', party: emptyPartyTracker, @@ -50,6 +53,10 @@ const getFakeRaidbossData = (triggerSet?: LooseTriggerSet): RaidbossData => { options: raidbossOptions, inCombat: true, triggerSetConfig: {}, + timeline: { + currentTime: () => 0, + jumpTo: (_label) => 0, + }, ShortName: (x: string | undefined) => x ?? '', StopCombat: (): void => {/* noop */}, ParseLocaleFloat: () => 0, diff --git a/types/data.d.ts b/types/data.d.ts index b69c24a8241..e86a455888c 100644 --- a/types/data.d.ts +++ b/types/data.d.ts @@ -24,6 +24,9 @@ export interface BaseOptions { export interface RaidbossData { job: Job; me: string; + meId: string; + zoneName: string; + zoneId: number; role: Role; party: PartyTracker; lang: Lang; @@ -33,6 +36,10 @@ export interface RaidbossData { options: BaseOptions; inCombat: boolean; triggerSetConfig: { [key: string]: ConfigValue }; + timeline: { + jumpTo: (label: string) => void; + currentTime: () => number; + }; /** @deprecated Use data.party.member instead */ ShortName: (x?: string) => string; StopCombat: () => void; diff --git a/ui/raidboss/data/07-dt/raid/r10n.ts b/ui/raidboss/data/07-dt/raid/r10n.ts index 36abd67aa29..b3e8ae332d3 100644 --- a/ui/raidboss/data/07-dt/raid/r10n.ts +++ b/ui/raidboss/data/07-dt/raid/r10n.ts @@ -308,7 +308,7 @@ const triggerSet: TriggerSet = { data0: '1[0-9A-F]{7}', capture: true, }, - condition: (data, matches) => data.me === data.party?.idToName_?.[matches.data0], + condition: (data, matches) => data.meId === matches.data0, infoText: (_data, _matches, output) => output.text!(), outputStrings: { text: { diff --git a/ui/raidboss/data/07-dt/trial/doomtrain.ts b/ui/raidboss/data/07-dt/trial/doomtrain.ts index 753956161e8..f1685bda9c7 100644 --- a/ui/raidboss/data/07-dt/trial/doomtrain.ts +++ b/ui/raidboss/data/07-dt/trial/doomtrain.ts @@ -214,7 +214,7 @@ const triggerSet: TriggerSet = { id: 'Doomtrain Add Train Direction Predictor', type: 'HeadMarker', netRegex: { id: '0282', data0: '1[0-9A-F]{7}', capture: true }, - condition: (data, matches) => data.me === data.party?.idToName_?.[matches.data0], + condition: (data, matches) => data.meId === matches.data0, durationSeconds: 7.6, countdownSeconds: 7.6, alertText: (data, matches, output) => { diff --git a/ui/raidboss/data/07-dt/trial/necron-ex.ts b/ui/raidboss/data/07-dt/trial/necron-ex.ts index e0eaab682ec..7c620fe8b61 100644 --- a/ui/raidboss/data/07-dt/trial/necron-ex.ts +++ b/ui/raidboss/data/07-dt/trial/necron-ex.ts @@ -80,10 +80,8 @@ const triggerSet: TriggerSet = { id: 'NecronEx Blue Shockwave', type: 'HeadMarker', netRegex: { id: '0267', capture: true }, - // Annoyingly, the "target" of this headmarker is the boss, and the actual player ID is stored - // in `data0`. So we need to map back to party info to determine if target is self or another condition: (data, matches) => { - if (data.me === data.party?.idToName_?.[matches.data0]) + if (data.meId === matches.data0) return true; return data.role === 'tank'; }, diff --git a/ui/raidboss/emulator/overrides/RaidEmulatorTimeline.ts b/ui/raidboss/emulator/overrides/RaidEmulatorTimeline.ts index 64300125948..46365f4e1ba 100644 --- a/ui/raidboss/emulator/overrides/RaidEmulatorTimeline.ts +++ b/ui/raidboss/emulator/overrides/RaidEmulatorTimeline.ts @@ -59,6 +59,16 @@ export default class RaidEmulatorTimeline extends Timeline { super._OnUpdateTimer(currentTime); } + public override JumpTo(label: string, currentTime: number): void { + // Override JumpTo to use the emulated timestamp, same logic as _OnUpdateTimer + const lastLogTimestamp = this.emulator?.currentEncounter?.encounter + .logLines.slice(-1)[0]?.timestamp; + if (lastLogTimestamp && currentTime > lastLogTimestamp) + currentTime = this.emulator?.currentLogTime ?? currentTime; + + super.JumpTo(label, currentTime); + } + override _ScheduleUpdate(_fightNow: number): void { // Override } diff --git a/ui/raidboss/popup-text.ts b/ui/raidboss/popup-text.ts index fe1a5506eee..67354aade9d 100644 --- a/ui/raidboss/popup-text.ts +++ b/ui/raidboss/popup-text.ts @@ -581,6 +581,7 @@ export class PopupText { protected readonly kMaxRowsOfText = 2; protected data: RaidbossData; protected me = ''; + protected meId = ''; protected job: Job = 'NONE'; protected role: Role = 'none'; protected triggerSets: ProcessedTriggerSet[] = []; @@ -1027,6 +1028,7 @@ export class PopupText { OnJobChange(e: PlayerChangedDetail): void { this.me = e.detail.name; + this.meId = e.detail.id.toString(16).toUpperCase(); this.job = e.detail.job; this.role = Util.jobToRole(this.job); this.ReloadTimelines(); @@ -1732,6 +1734,9 @@ export class PopupText { // make all this style consistent, sorry. const data: RaidbossData = { me: this.me, + meId: this.meId, + zoneName: this.zoneName, + zoneId: this.zoneId, job: this.job, role: this.role, party: this.partyTracker, @@ -1742,6 +1747,10 @@ export class PopupText { options: this.options, inCombat: this.inCombat, triggerSetConfig: this.triggerSetConfig, + timeline: { + currentTime: () => this.timelineLoader.CurrentTime(), + jumpTo: (label: string) => this.timelineLoader.JumpTo(label, Date.now()), + }, ShortName: (name?: string) => Util.shortName(name, this.options.PlayerNicks), StopCombat: () => this.SetInCombat(false), ParseLocaleFloat: parseFloat, diff --git a/ui/raidboss/raidboss_config.ts b/ui/raidboss/raidboss_config.ts index a8e09009596..e694a609f7e 100644 --- a/ui/raidboss/raidboss_config.ts +++ b/ui/raidboss/raidboss_config.ts @@ -1316,6 +1316,9 @@ class RaidbossConfigurator { const baseFakeData: RaidbossData = { me: '', + meId: '10001234', + zoneName: '', + zoneId: -1, job: 'NONE', role: 'none', party: new PartyTracker(raidbossOptions), @@ -1324,6 +1327,10 @@ class RaidbossConfigurator { options: this.base.configOptions, inCombat: true, triggerSetConfig: {}, + timeline: { + currentTime: () => 0, + jumpTo: (_label) => 0, + }, ShortName: (x?: string) => x ?? '???', StopCombat: () => {/* noop */}, ParseLocaleFloat: parseFloat, diff --git a/ui/raidboss/timeline.ts b/ui/raidboss/timeline.ts index 1dbd9a91b3d..cf1e647bd3d 100644 --- a/ui/raidboss/timeline.ts +++ b/ui/raidboss/timeline.ts @@ -152,6 +152,7 @@ export class Timeline { public syncStarts: Sync[]; public syncEnds: Sync[]; public forceJumps: Sync[]; + public labelToTime: { [name: string]: number }; public timebase = 0; @@ -203,6 +204,8 @@ export class Timeline { this.syncEnds = []; // Sorted by event occurrence time. this.forceJumps = []; + // A set of label names to their sync times + this.labelToTime = {}; this.LoadFile(text, triggers, styles); this.Stop(); @@ -223,6 +226,7 @@ export class Timeline { this.syncStarts = parsed.syncStarts; this.syncEnds = parsed.syncEnds; this.forceJumps = parsed.forceJumps; + this.labelToTime = parsed.labelToTime; } public Stop(): void { @@ -340,6 +344,15 @@ export class Timeline { } } + public JumpTo(label: string, currentTime: number): void { + const time = this.labelToTime[label]; + + if (time === undefined) + return; + + this.SyncTo(time, currentTime); + } + private _AdvanceTimeTo(fightNow: number): void { // This function advances time to fightNow without processing any events. let event = this.events[this.nextEventState.index]; @@ -798,6 +811,13 @@ export class TimelineController { public IsReady(): boolean { return this.timelines !== null; } + + public CurrentTime(): number { + return this.activeTimeline?.timebase ?? 0; + } + public JumpTo(label: string, currentTime: number): void { + this.activeTimeline?.JumpTo(label, currentTime); + } } export class TimelineLoader { @@ -830,4 +850,11 @@ export class TimelineLoader { public StopCombat(): void { this.timelineController.SetInCombat(false); } + + public CurrentTime(): number { + return this.timelineController.CurrentTime(); + } + public JumpTo(label: string, currentTime: number): void { + this.timelineController.JumpTo(label, currentTime); + } } diff --git a/ui/raidboss/timeline_parser.ts b/ui/raidboss/timeline_parser.ts index 2d878bd1594..f677e6c1c6a 100644 --- a/ui/raidboss/timeline_parser.ts +++ b/ui/raidboss/timeline_parser.ts @@ -141,6 +141,7 @@ export type Sync = { lineNumber: number; event: Event; jump?: number; + label?: string; // TODO: could consider "maybe" jumps here to say "Ability?". // TODO: also it'd be nice to be able to `forcejump` with out a `sync //` jumpType?: 'force' | 'normal'; @@ -213,7 +214,7 @@ export class TimelineParser { // Sorted by line. public errors: Error[] = []; // Map of encountered label names to their time. - private labelToTime: { [name: string]: number } = {}; + public labelToTime: { [name: string]: number } = {}; // Map of encountered syncs to the label they are jumping to. private labelToSync: { [name: string]: Sync[] } = {};