Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raidboss: add named timeline jump labels #5760

Merged
merged 2 commits into from
Sep 3, 2023
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
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