Skip to content

Commit ec95d62

Browse files
authored
Merge branch 'main' into p3-timeline-more-updates
2 parents 82c5afe + c2e6d15 commit ec95d62

23 files changed

Lines changed: 3496 additions & 105 deletions

File tree

.github/actions/setup-js-env/action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ runs:
55
steps:
66
- uses: actions/setup-node@v6
77
with:
8-
node-version: '>=24.18.0'
8+
# 24.17 has a bug that breaks our workflows
9+
# 26 introduces changes that break our workflows
10+
# @TODO: remove upper restriction once dependencies no longer fail on 26
11+
node-version: '>=24.18.0 <25'
912
cache: 'npm'
1013
cache-dependency-path: 'package-lock.json'
1114

resources/util.ts

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,16 @@ const output16Dir: DirectionOutput16[] = [
264264
const outputCardinalDir: DirectionOutputCardinal[] = ['dirN', 'dirE', 'dirS', 'dirW'];
265265
const outputIntercardDir: DirectionOutputIntercard[] = ['dirNE', 'dirSE', 'dirSW', 'dirNW'];
266266

267-
const compareDirectionOutput = (a: DirectionOutput16, b: DirectionOutput16): number => {
268-
const getIndex = (n: DirectionOutput16) => {
269-
const index = output16Dir.indexOf(n);
270-
// Values outside of output16Dir (i.e. 'unknown') sort last
271-
if (index < 0)
272-
return output16Dir.length;
273-
return index;
274-
};
267+
const getDirectionIndex = (n: DirectionOutput16) => {
268+
const index = output16Dir.indexOf(n);
269+
// Values outside of output16Dir (i.e. 'unknown') sort last
270+
if (index < 0)
271+
return output16Dir.length;
272+
return index;
273+
};
275274

276-
return getIndex(a) - getIndex(b);
275+
const compareDirectionOutput = (a: DirectionOutput16, b: DirectionOutput16): number => {
276+
return getDirectionIndex(a) - getDirectionIndex(b);
277277
};
278278

279279
const outputStrings16Dir: OutputStrings = {
@@ -356,6 +356,12 @@ const xyTo4DirIntercardNum = (x: number, y: number, centerX: number, centerY: nu
356356
return Math.round(2 - 2 * ((Math.PI / 4) + Math.atan2(x, y)) / Math.PI) % 4;
357357
};
358358

359+
const xyToHeading = (x: number, y: number, centerX: number, centerY: number): number => {
360+
x = x - centerX;
361+
y = y - centerY;
362+
return Math.atan2(x, y);
363+
};
364+
359365
const hdgTo16DirNum = (heading: number): number => {
360366
// N = 0, NNE = 1, ..., NNW = 15
361367
return (Math.round(8 - 8 * heading / Math.PI) % 16 + 16) % 16;
@@ -371,6 +377,10 @@ const hdgTo4DirNum = (heading: number): number => {
371377
return (Math.round(2 - heading * 2 / Math.PI) % 4 + 4) % 4;
372378
};
373379

380+
const outputFrom16DirNum = (dirNum: number): DirectionOutput16 => {
381+
return output16Dir[dirNum] ?? 'unknown';
382+
};
383+
374384
const outputFrom8DirNum = (dirNum: number): DirectionOutput8 => {
375385
return output8Dir[dirNum] ?? 'unknown';
376386
};
@@ -383,6 +393,88 @@ const outputFromIntercardNum = (dirNum: number): DirectionOutputIntercard => {
383393
return outputIntercardDir[dirNum] ?? 'unknown';
384394
};
385395

396+
export type AnyDirection =
397+
| DirectionOutputCardinal
398+
| DirectionOutputIntercard
399+
| DirectionOutput8
400+
| DirectionOutput16;
401+
402+
/**
403+
* Get a function to pass to Array.sort to sort an array of DirectionOutput entries
404+
*
405+
* @example
406+
* const dirs: DirectionOutputCardinal[] = ['dirN', 'dirW'];
407+
*
408+
* dirs.sort(getSortDirectionsClockwiseFunction('dirE'));
409+
*
410+
* // `dirs` should equal `['dirW', 'dirN']`
411+
*
412+
* @param from The DirectionOutput to treat as the start point for sort comparison
413+
* @returns A function to pass to the Array.sort function
414+
*/
415+
export const getSortDirectionsClockwiseFunction = (
416+
from?: AnyDirection,
417+
): (left: AnyDirection, right: AnyDirection) => number => {
418+
// Default to dirN
419+
let offset = 0;
420+
if (from !== undefined && from !== 'unknown')
421+
offset = getDirectionIndex(from);
422+
423+
const count = output16Dir.length;
424+
425+
return (left: AnyDirection, right: AnyDirection) => {
426+
if (left === 'unknown' || right === 'unknown') {
427+
return left === right ? 0 : left === 'unknown' ? 1 : -1;
428+
}
429+
const rightIndex = (count + getDirectionIndex(right) - offset) % count;
430+
const leftIndex = (count + getDirectionIndex(left) - offset) % count;
431+
return leftIndex - rightIndex;
432+
};
433+
};
434+
435+
type Point = {
436+
x: number;
437+
y: number;
438+
};
439+
440+
/**
441+
* Get a function to pass to Array.sort to sort an array of objects with `x` and `y` properties
442+
*
443+
* @example
444+
* const points = [{ x: 101, y: 101 }, { x: 99, y: 99 }];
445+
*
446+
* points.sort(getSortPointsClockwiseFunction({x: 100, y: 100}, {x: 99, y: 101}));
447+
*
448+
* // `points` should now equal `[{ x: 99, y: 99 }, { x: 101, y: 101 }]`
449+
*
450+
* @param center The x/y point to treat as the center to calculate headings from
451+
* @param reference The heading or x/y point to treat as the start point for sort comparison
452+
* @returns A function to pass to the Array.sort function
453+
*/
454+
export const getSortPointsClockwiseFunction = <T extends Point>(
455+
center: T,
456+
reference: number | T = Math.PI, // Default to north
457+
): (left: T, right: T) => number => {
458+
// Convert point to heading if needed
459+
const offset = typeof reference === 'object'
460+
? xyToHeading(reference.x, reference.y, center.x, center.y)
461+
: reference;
462+
463+
const twoPI = Math.PI * 2;
464+
465+
return (left: T, right: T) => {
466+
// Get our base headings for the two points
467+
const rightHeading = xyToHeading(right.x, right.y, center.x, center.y);
468+
const leftHeading = xyToHeading(left.x, left.y, center.x, center.y);
469+
470+
// Adjust by reference offset
471+
const rightHeadingOffset = (twoPI + (offset - rightHeading)) % twoPI;
472+
const leftHeadingOffset = (twoPI + (offset - leftHeading)) % twoPI;
473+
474+
return leftHeadingOffset - rightHeadingOffset;
475+
};
476+
};
477+
386478
export const Directions = {
387479
output8Dir: output8Dir,
388480
output16Dir: output16Dir,
@@ -397,11 +489,14 @@ export const Directions = {
397489
xyTo16DirNum: xyTo16DirNum,
398490
xyTo8DirNum: xyTo8DirNum,
399491
xyTo4DirNum: xyTo4DirNum,
492+
xyToHeading: xyToHeading,
400493
hdgTo16DirNum: hdgTo16DirNum,
401494
hdgTo8DirNum: hdgTo8DirNum,
402495
hdgTo4DirNum: hdgTo4DirNum,
496+
outputFrom16DirNum: outputFrom16DirNum,
403497
outputFrom8DirNum: outputFrom8DirNum,
404498
outputFromCardinalNum: outputFromCardinalNum,
499+
outputFromIntercardNum: outputFromIntercardNum,
405500
combatantStatePosTo8Dir: (
406501
combatant: PluginCombatantState,
407502
centerX: number,
@@ -452,6 +547,10 @@ export const Directions = {
452547
const dirNum = hdgTo8DirNum(heading);
453548
return outputFrom8DirNum(dirNum);
454549
},
550+
xyTo16DirOutput: (x: number, y: number, centerX: number, centerY: number): DirectionOutput16 => {
551+
const dirNum = xyTo16DirNum(x, y, centerX, centerY);
552+
return outputFrom16DirNum(dirNum);
553+
},
455554
xyTo8DirOutput: (x: number, y: number, centerX: number, centerY: number): DirectionOutput8 => {
456555
const dirNum = xyTo8DirNum(x, y, centerX, centerY);
457556
return outputFrom8DirNum(dirNum);

test/unittests/compile_test.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,20 @@ describe('compile test', () => {
1515
it('npm package should compile successfully', async function() {
1616
// eslint-disable-next-line @typescript-eslint/no-invalid-this
1717
this.timeout(30000); // allow a 30s timeout
18-
let execError = false;
1918
let output = '';
19+
let exitCode = -1;
2020
try {
2121
process.chdir(projectRoot);
2222
fs.rmSync('dist', { recursive: true, force: true });
23-
await exec('npx tsc --declaration', [], {
23+
exitCode = await exec('npx tsc --declaration', [], {
2424
listeners: {
2525
stdout: (data) => output += data.toString(),
26-
stderr: (data) => {
27-
execError = true;
28-
output += data.toString();
29-
},
26+
stderr: (data) => output += data.toString(),
3027
},
3128
});
32-
if (execError)
33-
throw output;
3429
} catch (err) {
3530
console.error(err);
36-
execError = true;
3731
}
38-
assert(execError === false, output);
32+
assert(exitCode === 0, output);
3933
});
4034
});

test/unittests/util_test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { assert } from 'chai';
22

33
import Util, {
44
allJobs,
5+
AnyDirection,
56
casterDpsJobs,
67
craftingJobs,
78
gatheringJobs,
9+
getSortDirectionsClockwiseFunction,
10+
getSortPointsClockwiseFunction,
811
healerJobs,
912
limitedJobs,
1013
meleeDpsJobs,
@@ -93,4 +96,97 @@ describe('util tests', () => {
9396
assert(!Util.isLimitedJob(job))
9497
);
9598
});
99+
100+
it('sorts directions clockwise from a reference direction and undefined reference', () => {
101+
const dirs: AnyDirection[] = [
102+
'dirNE',
103+
'dirSW',
104+
'dirN',
105+
'dirNW',
106+
'dirS',
107+
'dirW',
108+
'dirSE',
109+
'dirE',
110+
];
111+
112+
let expected = ['dirNW', 'dirN', 'dirNE', 'dirE', 'dirSE', 'dirS', 'dirSW', 'dirW'];
113+
let sorted = dirs.sort(getSortDirectionsClockwiseFunction('dirNW'));
114+
assert.deepEqual(sorted, expected);
115+
116+
expected = ['dirN', 'dirNE', 'dirE', 'dirSE', 'dirS', 'dirSW', 'dirW', 'dirNW'];
117+
sorted = dirs.sort(getSortDirectionsClockwiseFunction());
118+
assert.deepEqual(sorted, expected);
119+
});
120+
121+
it('sorts directions and unknowns, putting unknowns at the end', () => {
122+
const dirs1: AnyDirection[] = [
123+
'dirNE',
124+
'unknown',
125+
'dirN',
126+
'unknown',
127+
'dirNW',
128+
'dirS',
129+
];
130+
131+
const expected1 = ['dirN', 'dirNE', 'dirS', 'dirNW', 'unknown', 'unknown'];
132+
assert.deepEqual(dirs1.sort(getSortDirectionsClockwiseFunction()), expected1);
133+
134+
const dirs2: AnyDirection[] = ['dirNE', 'unknown', 'dirN', 'dirNW'];
135+
const expected2 = ['dirNW', 'dirN', 'dirNE', 'unknown'];
136+
assert.deepEqual(
137+
dirs2.sort(getSortDirectionsClockwiseFunction('dirNW')),
138+
expected2,
139+
);
140+
});
141+
142+
it('sorts points clockwise from a reference point', () => {
143+
const getPoints = () => [
144+
{ id: 'NE', x: 1, y: -1 },
145+
{ id: 'SW', x: -1, y: 1 },
146+
{ id: 'N', x: 0, y: -1 },
147+
{ id: 'NW', x: -1, y: -1 },
148+
{ id: 'S', x: 0, y: 1 },
149+
{ id: 'W', x: -1, y: 0 },
150+
{ id: 'SE', x: 3, y: 3 },
151+
{ id: 'E', x: 2, y: 0 },
152+
];
153+
154+
const expected = ['NW', 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W'];
155+
156+
let [refX, refY] = [-1, -1]; // same as NW
157+
let sorted = getPoints().sort(
158+
getSortPointsClockwiseFunction({ x: 0, y: 0 }, { x: refX, y: refY }),
159+
);
160+
assert.deepEqual(sorted.map((point) => point.id), expected);
161+
162+
[refX, refY] = [-1.2, -0.8];
163+
sorted = getPoints().sort(getSortPointsClockwiseFunction({ x: 0, y: 0 }, { x: refX, y: refY }));
164+
assert.deepEqual(sorted.map((point) => point.id), expected);
165+
});
166+
167+
it('sorts points clockwise with numeric reference', () => {
168+
const points = [
169+
{ id: 'NE', x: 1, y: -1 },
170+
{ id: 'NW', x: -1, y: -1 },
171+
{ id: 'N', x: 0, y: -1 },
172+
];
173+
174+
const sorted = points.sort(
175+
getSortPointsClockwiseFunction({ x: 0, y: 0 }, 0), // reference is south
176+
);
177+
178+
assert.deepEqual(sorted.map((point) => point.id), ['NW', 'N', 'NE']);
179+
});
180+
181+
it('keeps points in input order when all angles are equal', () => {
182+
const points = [
183+
{ id: '1', x: 0, y: -3 },
184+
{ id: '2', x: 0, y: -4 },
185+
{ id: '3', x: 0, y: -1 },
186+
{ id: '4', x: 0, y: -2 },
187+
];
188+
189+
const sorted = points.sort(getSortPointsClockwiseFunction({ x: 0, y: 0 }));
190+
assert.deepEqual(sorted.map((point) => point.id), ['1', '2', '3', '4']);
191+
});
96192
});

ui/config/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export const kPrefixToCategory = {
149149
de: 'Benutzerdefinierte Entwickler Trigger',
150150
cn: '自定义开发者触发器',
151151
ko: '커스텀 개발자 트리거',
152+
tc: '自定義開發者觸發器',
152153
},
153154
'user': {
154155
en: 'User Triggers',

ui/raidboss/data/02-arr/dungeon/aurum_vale.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const triggerSet: TriggerSet<Data> = {
1313
de: 'Vor der 7.4 Überarbeitung',
1414
cn: '7.4改版前',
1515
ko: '7.4 개편 전',
16+
tc: '7.4改版前',
1617
},
1718
triggers: [
1819
{

ui/raidboss/data/04-sb/dungeon/shisui_of_the_violet_tides.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const triggerSet: TriggerSet<Data> = {
1414
de: 'Vor der 7.5 Überarbeitung',
1515
cn: '7.5改版前',
1616
ko: '7.5 개편 전',
17+
tc: '7.5改版前',
1718
},
1819
timelineFile: 'shisui_of_the_violet_tides.txt',
1920
triggers: [

ui/raidboss/data/04-sb/trial/shinryu-ex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const triggerSet: TriggerSet<Data> = {
9898
de: 'Eis: Sammeln + nicht bewegen',
9999
cn: '冰: 集合 + 不要动',
100100
ko: '얼음: 모이기 + 이동 멈추기',
101+
tc: '冰: 集合 + 不要動',
101102
},
102103
},
103104
},
@@ -760,7 +761,6 @@ const triggerSet: TriggerSet<Data> = {
760761
},
761762
{
762763
'locale': 'tc',
763-
'missingTranslations': true,
764764
'replaceSync': {
765765
'Cocoon': '光繭',
766766
'Icicle': '冰柱',

ui/raidboss/data/04-sb/ultimate/ultima_weapon_ultimate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ const triggerSet: TriggerSet<Data> = {
770770
// The two close nails are 45 degrees apart.
771771
if (next8Dir - this8Dir === 1 || this8Dir - next8Dir === 7) {
772772
const between16Dir = this8Dir * 2 + 1;
773-
const outputKey = Directions.output16Dir[between16Dir] ?? 'unknown';
773+
const outputKey = Directions.outputFrom16DirNum(between16Dir);
774774
return output.text!({ dir: output[outputKey]!() });
775775
}
776776
}

ui/raidboss/data/04-sb/ultimate/unending_coil_ultimate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1810,7 +1810,7 @@ const triggerSet: TriggerSet<Data> = {
18101810
const towerDir = towersMap[(wantedIdx + naelIdx) % 8];
18111811

18121812
const myTowerDir = towerDir !== undefined
1813-
? Directions.output16Dir[towerDir] ?? 'unknown'
1813+
? Directions.outputFrom16DirNum(towerDir)
18141814
: 'unknown';
18151815

18161816
return output.tower!({

0 commit comments

Comments
 (0)