From 53570013e1000656a0fae18e20d36865fcfaa083 Mon Sep 17 00:00:00 2001 From: valarnin Date: Sat, 16 Nov 2024 21:53:55 -0500 Subject: [PATCH] ex3 triggers --- .../data/07-dt/trial/queen-eternal-ex.ts | 466 ++++++++++++++++-- 1 file changed, 437 insertions(+), 29 deletions(-) diff --git a/ui/raidboss/data/07-dt/trial/queen-eternal-ex.ts b/ui/raidboss/data/07-dt/trial/queen-eternal-ex.ts index eaea6d3ee5..a9b950cf14 100644 --- a/ui/raidboss/data/07-dt/trial/queen-eternal-ex.ts +++ b/ui/raidboss/data/07-dt/trial/queen-eternal-ex.ts @@ -1,18 +1,24 @@ import Conditions from '../../../../../resources/conditions'; import Outputs from '../../../../../resources/outputs'; +import { callOverlayHandler } from '../../../../../resources/overlay_plugin_api'; import { Responses } from '../../../../../resources/responses'; import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; // TODO: -// Wind phase triggers for chain break and knockback direction -// Earth phase triggers -// More ice phase triggers -// Phase two triggers +// Earth phase boulders, track the amount that hit platform and specify off for second set if 8 have already hit? +// Coronation followup trigger for the memorized lines, not sure how to word this +// Ice phase, tethers + line stack, might need a strat option for this export interface Data extends RaidbossData { + actorPositions: { [id: string]: { x: number; y: number } }; + windKnockbackDir?: 'left' | 'right'; + phase: 'p1' | 'wind' | 'earth' | 'ice' | 'p2'; + gravitationalEmpireMech: 'tower' | 'spread' | 'cone'; absoluteAuthorityDebuff: 'stack' | 'spread'; + radicalShiftCWPlatform?: 'wind' | 'earth' | 'ice'; + radicalShiftCCWPlatform?: 'wind' | 'earth' | 'ice'; } const triggerSet: TriggerSet = { @@ -21,48 +27,74 @@ const triggerSet: TriggerSet = { timelineFile: 'queen-eternal-ex.txt', initData: () => ({ absoluteAuthorityDebuff: 'stack', + gravitationalEmpireMech: 'tower', + phase: 'p1', + actorPositions: {}, }), triggers: [ + // Phase trackers { - id: 'QueenEternal Ex Virtual Shift', + id: 'QueenEternal Ex Phase Tracker Elemental', type: 'StartsUsing', - netRegex: { id: ['A019', 'A01A', 'A01B'], source: 'Queen Eternal', capture: false }, - response: Responses.aoe(), + netRegex: { id: ['A019', 'A01A', 'A01B'], source: 'Queen Eternal', capture: true }, + run: (data, matches) => { + switch (matches.id) { + case 'A019': + data.phase = 'wind'; + break; + case 'A01A': + data.phase = 'earth'; + break; + case 'A01B': + data.phase = 'ice'; + break; + } + }, }, { - id: 'QueenEternal Ex Aeroquell', + id: 'QueenEternal Ex Phase Tracker P1', type: 'StartsUsing', - netRegex: { id: 'A025', source: 'Queen Eternal', capture: false }, - suppressSeconds: 1, - infoText: (_data, _matches, output) => output.stacks!(), - outputStrings: { - stacks: Outputs.healerGroups, + netRegex: { id: 'A01C', source: 'Queen Eternal', capture: false }, + run: (data) => data.phase = 'p1', + }, + { + id: 'QueenEternal Ex Phase Tracker P2', + type: 'Ability', + netRegex: { id: 'A04B', source: 'Queen Eternal', capture: false }, + run: (data) => data.phase = 'p2', + }, + // General triggers + { + id: 'QueenEternal Ex General ActorSetPos Tracker', + type: 'ActorSetPos', + netRegex: { id: '4[0-9A-F]{7}', capture: true }, + run: (data, matches) => { + data.actorPositions[matches.id] = { + x: parseFloat(matches.x), + y: parseFloat(matches.y), + }; }, }, { - id: 'QueenEternal Ex Legitimate Force East Safe First', + id: 'QueenEternal Ex General Legitimate Force East Safe First', type: 'StartsUsing', netRegex: { id: 'A01E', source: 'Queen Eternal', capture: false }, + condition: (data) => ['p1', 'earth', 'ice'].includes(data.phase), response: Responses.goRightThenLeft(), }, { - id: 'QueenEternal Ex Legitimate Force West Safe First', + id: 'QueenEternal Ex General Legitimate Force West Safe First', type: 'StartsUsing', netRegex: { id: 'A020', source: 'Queen Eternal', capture: false }, + condition: (data) => ['p1', 'earth', 'ice'].includes(data.phase), response: Responses.goLeftThenRight(), }, { id: 'QueenEternal Ex World Shatter', type: 'StartsUsing', - netRegex: { id: 'A01C', source: 'Queen Eternal', capture: false }, + netRegex: { id: ['7692', 'A01C'], source: 'Queen Eternal', capture: false }, response: Responses.aoe(), }, - { - id: 'QueenEternal Ex Divide and Conquer', - type: 'StartsUsing', - netRegex: { id: 'A017', source: 'Queen Eternal', capture: false }, - response: Responses.spread(), - }, { id: 'QueenEternal Ex Prosecution of War', type: 'StartsUsing', @@ -76,7 +108,223 @@ const triggerSet: TriggerSet = { response: Responses.aoe(), }, { - id: 'QueenEternal Ex Weighty Blow', + id: 'QueenEternal Ex Virtual Shift', + type: 'StartsUsing', + netRegex: { id: ['A019', 'A01A', 'A01B'], source: 'Queen Eternal', capture: false }, + response: Responses.bigAoe(), + }, + + // Before wind + { + id: 'QueenEternal Ex Aethertithe Safe Parties', + type: 'MapEffect', + netRegex: { flags: ['04000100', '08000100', '10000100'], location: '00', capture: true }, + infoText: (_data, matches, output) => { + const dirMap: { [flag: string]: 'west' | 'middle' | 'east' } = { + '04000100': 'west', + '08000100': 'middle', + '10000100': 'east', + }; + const dirs: ('east' | 'middle' | 'west')[] = Object.entries(dirMap).filter((entry) => + entry[0] !== matches.flags + ).map((entry) => entry[1]); + + const [dir1, dir2] = dirs; + + if (dirs.length !== 2 || dir1 === undefined || dir2 === undefined) { + return output.unknownCombo!({ + unk: output.unknown!(), + groups: output.healerGroups!(), + }); + } + + return output.combo!({ + dir1: output[dir1]!(), + dir2: output[dir2]!(), + groups: output.healerGroups!(), + }); + }, + outputStrings: { + east: Outputs.east, + middle: Outputs.middle, + west: Outputs.west, + healerGroups: Outputs.healerGroups, + combo: { + en: '${dir1}/${dir2}, ${groups}', + de: '${dir1}/${dir2}, ${groups}', + fr: '${dir1}/${dir2}, ${groups}', + ja: '${dir1}/${dir2}, ${groups}', + cn: '${dir1}/${dir2}, ${groups}', + ko: '${dir1}/${dir2}, ${groups}', + }, + unknown: Outputs.unknown, + unknownCombo: { + en: '${unk} => ${groups}', + de: '${unk} => ${groups}', + fr: '${unk} => ${groups}', + ja: '${unk} => ${groups}', + cn: '${unk} => ${groups}', + ko: '${unk} => ${groups}', + }, + }, + }, + + // Wind phase + { + id: 'QueenEternal Ex Wind Phase Aeroquell', + type: 'StartsUsing', + netRegex: { id: 'A025', source: 'Queen Eternal', capture: false }, + suppressSeconds: 1, + infoText: (_data, _matches, output) => output.stacks!(), + outputStrings: { + stacks: Outputs.healerGroups, + }, + }, + { + id: 'QueenEternal Ex Wind Phase Debuff Collector', + type: 'GainsEffect', + netRegex: { effectId: ['105D', '105E'], capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => + data.windKnockbackDir = matches.effectId === '105E' ? 'right' : 'left', + }, + { + id: 'QueenEternal Ex Wind Phase Legitimate Force', + type: 'StartsUsing', + netRegex: { id: ['A01E', 'A020'], source: 'Queen Eternal', capture: true }, + condition: (data) => data.phase === 'wind', + delaySeconds: 0.5, + durationSeconds: 13.3, + infoText: (data, matches, output) => { + const safeDir: 'leftRight' | 'rightLeft' = matches.id === 'A01E' + ? 'rightLeft' + : 'leftRight'; + const kbDir = data.windKnockbackDir; + + if (kbDir === undefined) { + return output.comboUnknown!({ + break: output.break!(), + safe: output[safeDir]!(), + unk: output.unknown!(), + }); + } + + return output.combo!({ + break: output.break!(), + safe: output[safeDir]!(), + kbDir: output[kbDir]!(), + }); + }, + outputStrings: { + leftRight: Outputs.leftThenRight, + rightLeft: Outputs.rightThenLeft, + left: { + en: 'Knockback Left', + }, + right: { + en: 'Knockback Right', + }, + break: Outputs.breakChains, + unknown: Outputs.unknown, + combo: { + en: '${break} => ${safe} => ${kbDir}', + de: '${break} => ${safe} => ${kbDir}', + fr: '${break} => ${safe} => ${kbDir}', + ja: '${break} => ${safe} => ${kbDir}', + cn: '${break} => ${safe} => ${kbDir}', + ko: '${break} => ${safe} => ${kbDir}', + }, + comboUnknown: { + en: '${break} => ${safe} => ${unk}', + de: '${break} => ${safe} => ${unk}', + fr: '${break} => ${safe} => ${unk}', + ja: '${break} => ${safe} => ${unk}', + cn: '${break} => ${safe} => ${unk}', + ko: '${break} => ${safe} => ${unk}', + }, + }, + }, + + // After wind + { + id: 'QueenEternal Ex Divide and Conquer', + type: 'StartsUsing', + netRegex: { id: 'A017', source: 'Queen Eternal', capture: false }, + response: Responses.spread(), + }, + + // Earth phase + { + id: 'QueenEternal Ex Earth Phase Initial Up', + type: 'Ability', + netRegex: { id: 'A01A', capture: false }, + suppressSeconds: 1, + infoText: (_data, _matches, output) => output.up!(), + outputStrings: { + up: { + en: 'Up', + }, + }, + }, + { + id: 'QueenEternal Ex Earth Phase First Towers', + type: 'Ability', + netRegex: { id: 'A028', capture: false }, + delaySeconds: 14.3, + infoText: (_data, _matches, output) => output.downSoak!(), + outputStrings: { + downSoak: { + en: 'Down, soak tower', + }, + }, + }, + { + id: 'QueenEternal Ex Earth Phase Gravitational Empire Pillar Collector', + type: 'StartsUsing', + netRegex: { id: 'A02C', capture: true }, + condition: Conditions.targetIsYou(), + response: Responses.spread(), + run: (data) => data.gravitationalEmpireMech = 'spread', + }, + { + id: 'QueenEternal Ex Earth Phase Gravitational Empire Ray Collector', + type: 'Tether', + netRegex: { id: '0011', capture: true }, + condition: (data, matches) => matches.source === data.me, + infoText: (_data, _matches, output) => output.cone!(), + run: (data) => data.gravitationalEmpireMech = 'cone', + outputStrings: { + cone: { + en: 'Cone on YOU', + }, + }, + }, + { + id: 'QueenEternal Ex Earth Phase Gravitational Empire Towers', + type: 'StartsUsing', + netRegex: { id: 'A02B', capture: false }, + delaySeconds: 0.5, + infoText: (data, _matches, output) => { + if (data.gravitationalEmpireMech !== 'tower') + return; + + return output.downSoak!(); + }, + outputStrings: { + downSoak: { + en: 'Down, soak tower', + }, + }, + }, + { + id: 'QueenEternal Ex Earth Phase Boulder', + type: 'HeadMarker', + netRegex: { id: '00DB', capture: false }, + suppressSeconds: 1, + response: Responses.spread(), + }, + { + id: 'QueenEternal Ex Earth Phase Weighty Blow', type: 'StartsUsing', netRegex: { id: 'A033', source: 'Queen Eternal', capture: false }, infoText: (_data, _matches, output) => output.text!(), @@ -86,6 +334,8 @@ const triggerSet: TriggerSet = { }, }, }, + + // After earth { id: 'QueenEternal Ex Coronation', type: 'StartsUsing', @@ -141,15 +391,173 @@ const triggerSet: TriggerSet = { }, }, }, + + // Ice phase + { + id: 'QueenEternal Ex Ice Phase Motion Headmarker', + type: 'HeadMarker', + netRegex: { id: '022A', capture: false }, + suppressSeconds: 1, + response: Responses.moveAround(), + }, + { + id: 'QueenEternal Ex Ice Phase Icecicles', + type: 'Tether', + netRegex: { id: '0039', capture: true }, + condition: Conditions.targetIsYou(), + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })); + + if (combatants === null) { + console.error(`Ice Phase Icecicles: null data`); + return; + } + if (combatants.combatants.length !== 1) { + console.error(`Ice Phase Icecicles: expected 1, got ${combatants.combatants.length}`); + return; + } + + const icecicle = combatants.combatants[0]; + if (!icecicle) + return; + + data.actorPositions[matches.sourceId] = { + x: icecicle.PosX, + y: icecicle.PosY, + }; + }, + infoText: (data, matches, output) => { + const iceciclePos = data.actorPositions[matches.sourceId]; + + if (iceciclePos === undefined) { + return output.unknown!(); + } + + if (iceciclePos.x < 100.0) { + return output.east!(); + } + + return output.west!(); + }, + outputStrings: { + unknown: { + en: 'Spread ???, stretch tethers', + }, + west: { + en: 'Spread West, stretch tethers', + }, + east: { + en: 'Spread East, stretch tethers', + }, + }, + }, + + // Phase two + { + id: 'QueenEternal Ex Platform Tracker', + type: 'MapEffect', + netRegex: { location: ['09', '0A', '0B'], capture: true }, + run: (data, matches) => { + const flags: { [flag: string]: 'cw' | 'ccw' } = { + '00200010': 'ccw', + '00020001': 'cw', + }; + + const slots: { [slot: string]: 'wind' | 'earth' | 'ice' } = { + '09': 'wind', + '0A': 'earth', + '0B': 'ice', + }; + + const dir = flags[matches.flags]; + const element = slots[matches.location]; + + if (dir === undefined || element === undefined) { + return; + } + + if (dir === 'cw') { + data.radicalShiftCWPlatform = element; + } else { + data.radicalShiftCCWPlatform = element; + } + }, + }, + { + id: 'QueenEternal Ex Rotation Direction + Spread', + type: 'MapEffect', + netRegex: { flags: ['08000400', '01000080'], location: '0C', capture: true }, + infoText: (data, matches, output) => { + const dir = matches.flags === '08000400' ? 'cw' : 'ccw'; + let elem = data.radicalShiftCWPlatform; + + if (dir === 'ccw') { + elem = data.radicalShiftCCWPlatform; + } + + if (elem === undefined) { + return output.combo!({ + elem: output.unknown!(), + spread: output.spread!(), + }); + } + + return output.combo!({ + elem: output[elem]!(), + spread: output.spread!(), + }); + }, + outputStrings: { + spread: Outputs.spread, + unknown: Outputs.unknown, + wind: { + en: 'Wind/Green', + }, + earth: { + en: 'Earth/Yellow', + }, + ice: { + en: 'Ice/Blue', + }, + combo: { + en: '${elem} => ${spread}', + de: '${elem} => ${spread}', + fr: '${elem} => ${spread}', + ja: '${elem} => ${spread}', + cn: '${elem} => ${spread}', + ko: '${elem} => ${spread}', + }, + }, + }, { - id: 'QueenEternal Ex Rush', + id: 'QueenEternal Ex Radical Shift', type: 'StartsUsing', - netRegex: { id: ['A037', 'A038'], source: 'Ice Pillar', capture: false }, - suppressSeconds: 5, - infoText: (_data, _matches, output) => output.text!(), + netRegex: { id: 'A04F', source: 'Queen Eternal', capture: false }, + response: Responses.bigAoe(), + }, + { + id: 'QueenEternal Ex Dying Memory', + type: 'StartsUsing', + netRegex: { id: 'A059', source: 'Queen Eternal', capture: false }, + response: Responses.aoe(), + }, + { + id: 'QueenEternal Ex Royal Banishment', + type: 'StartsUsing', + netRegex: { id: 'A05A', source: 'Queen Eternal', capture: false }, + response: Responses.aoe(), + }, + { + id: 'QueenEternal Ex Tyranny\'s Grasp', + type: 'StartsUsing', + netRegex: { id: 'A055', source: 'Queen Eternal', capture: false }, + infoText: (_data, _matches, output) => output.back!(), outputStrings: { - text: { - en: 'Spread, stretch tethers', + back: { + en: 'Back, Tank Towers => AoE', }, }, },