Skip to content

Commit

Permalink
raidboss: add named timeline jump labels (#5760)
Browse files Browse the repository at this point in the history
Mentioned in #5619.

---------

Co-authored-by: Jonathan Garber <linkthevaliant@gmail.com>
  • Loading branch information
quisquous and JLGarber authored Sep 3, 2023
1 parent 39eb350 commit 3f729c4
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 31 deletions.
9 changes: 7 additions & 2 deletions docs/TimelineGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,19 @@ Every timeline entry begins with the ability time and the ability name.

`Number "String" (duration Number)`

`Number "String" sync /Regex/ (window Number,Number) (jump Number) (duration Number)`
`Number "String" sync /Regex/ (window Number,Number) (jump NumberOrLabel) (duration Number)`

`Number "String" sync /Regex/ (window Number,Number) (forcejump Number) (duration Number)`
`Number "String" sync /Regex/ (window Number,Number) (forcejump NumberOrLabel) (duration Number)`

`Number label "String"`

(The parentheses here indicate optionality and are not literal parentheses.)

**Number** can be an integer, e.g. `34`, or a float, e.g. `84.381`.

**NumberOrLabel** can be a **Number** (e.g. `42` or `12.8`)
or a label name with double quotes (e.g. `"loop"` or `"branch2"`).

**String** is a character string, e.g. `"Liftoff"` or `"Double Attack"`

**Regex** is a [Javascript regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions).
Expand Down
6 changes: 4 additions & 2 deletions ui/raidboss/data/00-misc/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ hideall "--sync--"
30 "Dummy Stands Still"
40 "Death"

50 "--sync--" sync /:test sync2:/ window 100,1 forcejump 100
50 "--sync--" sync /:test sync2:/ window 100,1 forcejump "loop"

# Loop test!
100 label "loop"
102 "Two"
103 label "three"
103 "Three"
104 "Four"
106 "Six"
110 "Ten" #duration 100
115 "Fifteen"
118 "Force Jump Three" sync /:test sync3:/ window 10,10 forcejump 103
118 "Force Jump Three" sync /:test sync3:/ window 10,10 forcejump "three"
120 "Invisible" sync /:test sync4:/ forcejump 1000
84 changes: 57 additions & 27 deletions ui/raidboss/timeline_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,24 @@ export class TimelineParser {
protected replacements: TimelineReplacement[];
private timelineConfig: TimelineConfig;

public ignores: { [ignoreId: string]: boolean };
public events: Event[];
public texts: Text[];
public syncStarts: Sync[];
public syncEnds: Sync[];
public forceJumps: Sync[];
public errors: Error[];
// A set of names which will not be notified about.
public ignores: { [ignoreId: string]: boolean } = {};
// Sorted by event occurrence time.
public events: Event[] = [];
// Sorted by event occurrence time.
public texts: Text[] = [];
// Sorted by sync.start time.
public syncStarts: Sync[] = [];
// Sorted by sync.end time.
public syncEnds: Sync[] = [];
// Sorted by event occurrence time.
public forceJumps: Sync[] = [];
// Sorted by line.
public errors: Error[] = [];
// Map of encountered label names to their time.
private labelToTime: { [name: string]: number } = {};
// Map of encountered syncs to the label they are jumping to.
private labelToSync: { [name: string]: Sync[] } = {};

constructor(
text: string,
Expand All @@ -99,21 +110,6 @@ export class TimelineParser {
this.perTriggerAutoConfig = this.options.PerTriggerAutoConfig;
this.replacements = replacements;

// A set of names which will not be notified about.
this.ignores = {};
// Sorted by event occurrence time.
this.events = [];
// Sorted by event occurrence time.
this.texts = [];
// Sorted by sync.start time.
this.syncStarts = [];
// Sorted by sync.end time.
this.syncEnds = [];
// Sorted by event occurrence time.
this.forceJumps = [];
// Sorted by line.
this.errors = [];

this.timelineConfig = typeof zoneId === 'number'
? this.options.PerZoneTimelineConfig[zoneId] ?? {}
: {};
Expand Down Expand Up @@ -150,7 +146,8 @@ export class TimelineParser {
durationCommand: /(?:[^#]*?\s)?(?<text>duration\s+(?<seconds>[0-9]+(?:\.[0-9]+)?))(\s.*)?$/,
ignore: /^hideall\s+\"(?<id>[^"]+)\"(?:\s*#.*)?$/,
jumpCommand:
/(?:[^#]*?\s)?(?<text>(?<command>(?:force|)jump)\s+(?<seconds>[0-9]+(?:\.[0-9]+)?))(?:\s.*)?$/,
/(?:[^#]*?\s)?(?<text>(?<command>(?:force|)jump)\s+(?:"(?<label>\w*)"|(?<seconds>[0-9]+(?:\.[0-9]+)?)))(?:\s.*)?$/,
label: /^(?<time>[0-9]+(?:\.[0-9]+)?)\s+(?<text>label\s+"(?<label>\w*)")\s*$/,
line: /^(?<text>(?<time>[0-9]+(?:\.[0-9]+)?)\s+"(?<name>.*?)")(\s+(.*))?/,
popupText:
/^(?<type>info|alert|alarm)text\s+\"(?<id>[^"]+)\"\s+before\s+(?<beforeSeconds>-?[0-9]+(?:\.[0-9]+)?)(?:\s+\"(?<text>[^"]+)\")?$/,
Expand Down Expand Up @@ -229,14 +226,25 @@ export class TimelineParser {
});
continue;
}

match = regexes.label.exec(line);
if (match && match['groups']) {
const parsedLine = match['groups'];
if (parsedLine.time === undefined || parsedLine.label === undefined)
throw new UnreachableCode();
const seconds = parseFloat(parsedLine.time);
const label = parsedLine.label;
this.labelToTime[label] = seconds;
continue;
}

match = regexes.line.exec(line);
if (!(match && match['groups'])) {
this.errors.push({
lineNumber: lineNumber,
line: originalLine,
error: 'Invalid format',
});
console.log('Unknown timeline: ' + originalLine);
continue;
}
const parsedLine = match['groups'];
Expand Down Expand Up @@ -304,10 +312,16 @@ export class TimelineParser {
argMatch = regexes.jumpCommand.exec(syncCommand.args);
if (argMatch && argMatch['groups']) {
const jumpCommand = argMatch['groups'];
if (jumpCommand.text === undefined || jumpCommand.seconds === undefined)
if (jumpCommand.text === undefined)
throw new UnreachableCode();
line = line.replace(jumpCommand.text, '').trim();
sync.jump = parseFloat(jumpCommand.seconds);

if (jumpCommand.seconds !== undefined)
sync.jump = parseFloat(jumpCommand.seconds);
else if (jumpCommand.label !== undefined)
(this.labelToSync[jumpCommand.label] ??= []).push(sync);
else
throw new UnreachableCode();
if (jumpCommand.command === 'forcejump')
sync.jumpType = 'force';
else
Expand All @@ -322,7 +336,6 @@ export class TimelineParser {
}
// If there's text left that isn't a comment then we didn't parse that text so report it.
if (line && !regexes.comment.exec(line)) {
console.log(`Unknown content '${line}' in timeline: ${originalLine}`);
this.errors.push({
lineNumber: lineNumber,
line: originalLine,
Expand Down Expand Up @@ -352,6 +365,23 @@ export class TimelineParser {
}
}

// Validate that all the jumps go to labels that exist.
for (const [label, syncs] of Object.entries(this.labelToSync)) {
const destination = this.labelToTime[label];
if (destination === undefined) {
const text = `No label named ${label} found to jump to`;
for (const sync of syncs) {
this.errors.push({
error: text,
lineNumber: sync.lineNumber,
});
}
continue;
}
for (const sync of syncs)
sync.jump = destination;
}

for (const e of this.events) {
for (const matchedTextEvent of texts[e.name] ?? []) {
const type = matchedTextEvent.type;
Expand Down

0 comments on commit 3f729c4

Please sign in to comment.