diff --git a/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island-savage.ts b/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island-savage.ts new file mode 100644 index 0000000000..a7aa13a2a1 --- /dev/null +++ b/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island-savage.ts @@ -0,0 +1,182 @@ +// This file was autogenerated from running ts-node util/sync_files.ts. +// DO NOT EDIT THIS FILE DIRECTLY. +// Edit the source file below and then run `npm run sync-files` +// Source: ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island.ts + +import NetRegexes from '../../../../../resources/netregexes'; +import ZoneId from '../../../../../resources/zone_id'; +import { OopsyData } from '../../../../../types/data'; +import { OopsyMistakeType, OopsyTrigger, OopsyTriggerSet } from '../../../../../types/oopsy'; +import { LocaleText } from '../../../../../types/trigger'; +import { playerDamageFields } from '../../../oopsy_common'; + +// TODO: people who missed their 8AE2 Burst tower +// TODO: failing 8BEB Radiance orb damage during Analysis +// TODO: failing 8CE1 Targeted Light during Analysis +// TODO: people who failed Subtractive Suppressor Alpha + Beta +// TODO: walking over 8BF2 Arcane Combustion when you don't have Suppressor +// TODO: taking extra 8BEA Inferno Divide squares during Spatial Tactics +// TODO: 01F7(success) and 01F8(fail) check and x markers? +// TODO: players not in Trapshooting stack 8977 +// TODO: players not in Present Box / Pinwheeling Dartboard two person stack + +const renameMistake = ( + triggerId: string, + abilityId: string | string[], + type: OopsyMistakeType, + text: LocaleText, +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + mistake: (_data, matches) => { + return { + type: type, + blame: matches.target, + reportId: matches.targetId, + text: text, + }; + }, + }; +}; + +export type Data = OopsyData; + +// TODO: we could probably move these helpers to some oopsy util. +const pushedIntoWall = ( + triggerId: string, + abilityId: string | string[], +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + deathReason: (_data, matches) => { + return { + id: matches.targetId, + name: matches.target, + text: { + en: 'Pushed into wall', + de: 'Rückstoß in die Wand', + fr: 'Poussé(e) dans le mur', + ja: '壁へノックバック', + cn: '击退至墙', + ko: '넉백', + }, + }; + }, + }; +}; + +const nonzeroDamageMistake = ( + triggerId: string, + abilityId: string | string[], + type: OopsyMistakeType, +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + mistake: (_data, matches) => { + return { + type: type, + blame: matches.target, + reportId: matches.targetId, + text: matches.ability, + }; + }, + }; +}; + +const triggerSet: OopsyTriggerSet = { + zoneId: ZoneId.AnotherAloaloIslandSavage, + damageWarn: { + // Trash 1 + 'AAIS Twister': '8BCF', // Twister tornados + 'AAIS Kiwakin Tail Screw': '8BC9', // baited circle + 'AAIS Snipper Bubble Shower': '8BCA', // front conal + 'AAIS Snipper Crab Dribble': '8BCB', // fast back conal after Bubble Shower + 'AAIS Ray Hydrocannon': '8C4B', // line aoe + 'AAIS Ray Expulsion': '8BCE', // "get out" + 'AAIS Ray Electric Whorl': '8BCD', // "get in" + + // Ketuduke + 'AAIS Spring Crystal Saturate 1': '8ADB', // orb circle + 'AAIS Spring Crystal Saturate 2': '8ADC', // rupee line laser + 'AAIS Sphere Shatter': '8AE0', // moving arches + 'AAIS Receding Twintides': '8AE7', // initial out during out->in + 'AAIS Near Tide': '8AE8', // second out during in->out with 8AE9 Encroaching Twintides + 'AAIS Encroaching Twintides': '8AE9', // initial in during in->out + 'AAIS Far Tide': '8AEA', // second in during out->in with 8AE7 Receding Twintides + 'AAIS Hydrobomb': '8AEB', // 3x puddles duruing 8ABD Blowing Bubbles + + // Trash 2 + 'AAIS Wood Golem Ovation': '8BD4', // front line aoe + 'AAIS Islekeeper Isle Drop': '8C3C', // front circle + + // Lala + 'AAIS Arcane Blight': '8BE6', // 270 degree rotating cleave + 'AAIS Bright Pulse 1': '8BE8', // initial blue square + 'AAIS Bright Pulse 2': '8BE9', // moving blue square + 'AAIS Arcane Mine': '8BF1', // initial Arcane Mine squares + 'AAIS Golem Aero II': '8BFB', // line damage from Aloalo Golem during Symmetric Surge + 'AAIS Telluric Theorem': '8C00', // puddles from Explosive Theorem spreads + + // Statice + 'AAIS Trigger Happy': '8969', // limit cut dart board + 'AAIS Bomb Burst': '897A', // bomb explosion + 'AAIS Uncommon Ground': '8CC3', // people who are on the same dartboard color with Bull's-eye + 'AAIS Faerie Ring': '8973', // donut rings during Present Box + 'AAIS Fire Spread 1': '896F', // initial rotating fire (from Ball of Fire) + 'AAIS Fire Spread 2': '89FB', // ongoing rotating fire damage (from Statice) + }, + damageFail: { + 'AAIS Big Burst': '8AE3', // tower failure damage + 'AAIS Massive Explosion 1': '8BF3', // failing to resolve Subractive Suppressor Alpha + 'AAIS Massive Explosion 2': '8BF4', // failing to resolve Subractive Suppressor Beta + 'AAIS Burning Chains': '8CC1', // damage from not breaking chains + 'AAIS Surprising Missile Burst': '8974', // running into Surprising Missile tethered add + 'AAIS Surprising Claw Death by Claw': '8975', // running into Surprising Claw tethered add + }, + gainsEffectFail: { + // C03 = 9999 duration, ??? = 15s duration + 'AAIS Dropsy': 'C03', // standing outside Ketuduke + // C05 = 9999 duration, C06 = 15s duration + 'AAIS Bleeding': 'C05', // standing in blue square during Lala + // BF9 = 9999 duration, BFA??? = 15s duration + 'AAIS Burns': 'BF9', // standing outside Lala + }, + shareWarn: { + 'AAIS Hydrobullet': '8ADF', // spread debuffs + 'AAIS Wood Golem Tornado': '8BD3', // headmarker -> bind and heavy aoe + 'AAIS Powerful Light': '8BFD', // spread marker during Symmetric Surge that turns squares blue + 'AAIS Explosive Theorem': '8BFF', // large spreads with Telluric Theorem puddles + 'AAIS Trapshooting Spread': '8978', // spread damage from Trick Reload + 'AAIS Firewords Spread': '897D', // spread damage during Present Box / Pinwheeling Dartboard + }, + soloWarn: { + 'AAIS Snipper Water III': '8BCC', // Snipper stack marker + 'AAIS Islekeeper Gravity Force': '8C3A', // stack + 'AAIS Trapshooting Stack': '8977', // stack damage from Trick Reload + }, + soloFail: { + 'AAIS Hydrofall': '8ADE', // partner stack debuffs + 'AAIS Symmetric Surge': '8BF5', // two person stack that gives magic vuln up + 'AAIS Fireworks Stack': '897C', // two person stack damage during Present Box / Pinwheeling Dartboard + }, + triggers: [ + renameMistake('AAIS Tornado', '8BCF', 'fail', { + // running into a tornado in the initial trash section + en: 'Tornado', + }), + pushedIntoWall('AAIS Angry Seas', '8AE1'), + pushedIntoWall('AAIS Pop', '896B'), + nonzeroDamageMistake('AAIS Hundred Lashings', ['8AE5', '8AE6'], 'warn'), + ], +}; + +export default triggerSet; diff --git a/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island.ts b/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island.ts new file mode 100644 index 0000000000..5a6d654c2f --- /dev/null +++ b/ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island.ts @@ -0,0 +1,177 @@ +import NetRegexes from '../../../../../resources/netregexes'; +import ZoneId from '../../../../../resources/zone_id'; +import { OopsyData } from '../../../../../types/data'; +import { OopsyMistakeType, OopsyTrigger, OopsyTriggerSet } from '../../../../../types/oopsy'; +import { LocaleText } from '../../../../../types/trigger'; +import { playerDamageFields } from '../../../oopsy_common'; + +// TODO: people who missed their 8AC2 Burst tower +// TODO: failing 8894 Radiance orb damage during Analysis +// TODO: failing 8CDF Targeted Light during Analysis +// TODO: people who failed Subtractive Suppressor Alpha + Beta +// TODO: walking over 889B Arcane Combustion when you don't have Suppressor +// TODO: taking extra 8893 Inferno Divide squares during Spatial Tactics +// TODO: 01F7(success) and 01F8(fail) check and x markers? +// TODO: players not in Trapshooting stack 895A +// TODO: players not in Present Box / Pinwheeling Dartboard two person stack + +const renameMistake = ( + triggerId: string, + abilityId: string | string[], + type: OopsyMistakeType, + text: LocaleText, +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + mistake: (_data, matches) => { + return { + type: type, + blame: matches.target, + reportId: matches.targetId, + text: text, + }; + }, + }; +}; + +export type Data = OopsyData; + +// TODO: we could probably move these helpers to some oopsy util. +const pushedIntoWall = ( + triggerId: string, + abilityId: string | string[], +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + deathReason: (_data, matches) => { + return { + id: matches.targetId, + name: matches.target, + text: { + en: 'Pushed into wall', + de: 'Rückstoß in die Wand', + fr: 'Poussé(e) dans le mur', + ja: '壁へノックバック', + cn: '击退至墙', + ko: '넉백', + }, + }; + }, + }; +}; + +const nonzeroDamageMistake = ( + triggerId: string, + abilityId: string | string[], + type: OopsyMistakeType, +): OopsyTrigger => { + return { + id: triggerId, + type: 'Ability', + netRegex: NetRegexes.ability({ id: abilityId, ...playerDamageFields }), + condition: (data, matches) => data.DamageFromMatches(matches) > 0, + mistake: (_data, matches) => { + return { + type: type, + blame: matches.target, + reportId: matches.targetId, + text: matches.ability, + }; + }, + }; +}; + +const triggerSet: OopsyTriggerSet = { + zoneId: ZoneId.AnotherAloaloIsland, + damageWarn: { + // Trash 1 + 'AAI Twister': '8BC0', // Twister tornados + 'AAI Kiwakin Tail Screw': '8BB8', // baited circle + 'AAI Snipper Bubble Shower': '8BB9', // front conal + 'AAI Snipper Crab Dribble': '8BBA', // fast back conal after Bubble Shower + 'AAI Ray Hydrocannon': '8BBD', // line aoe + 'AAI Ray Expulsion': '8BBF', // "get out" + 'AAI Ray Electric Whorl': '8BBE', // "get in" + + // Ketuduke + 'AAI Spring Crystal Saturate 1': '8AAB', // orb circle + 'AAI Spring Crystal Saturate 2': '8AAC', // rupee line laser + 'AAI Sphere Shatter': '8ABC', // moving arches + 'AAI Receding Twintides': '8ACC', // initial out during out->in + 'AAI Near Tide': '8ACD', // second out during in->out with 8ACE Encroaching Twintides + 'AAI Encroaching Twintides': '8ACE', // initial in during in->out + 'AAI Far Tide': '8ACF', // second in during out->in with 8ACC Receding Twintides + 'AAI Hydrobomb': '8AD1', // 3x puddles duruing 8ABD Blowing Bubbles + + // Trash 2 + 'AAI Wood Golem Ovation': '8BC1', // front line aoe + 'AAI Islekeeper Isle Drop': '8C6F', // front circle + + // Lala + 'AAI Arcane Blight': '888F', // 270 degree rotating cleave + 'AAI Bright Pulse 1': '8891', // initial blue square + 'AAI Bright Pulse 2': '8892', // moving blue square + 'AAI Arcane Mine': '889A', // initial Arcane Mine squares + 'AAI Golem Aero II': '88A4', // line damage from Aloalo Golem during Symmetric Surge + 'AAI Telluric Theorem': '88A9', // puddles from Explosive Theorem spreads + + // Statice + 'AAI Trigger Happy': '894C', // limit cut dart board + 'AAI Bomb Burst': '895D', // bomb explosion + 'AAI Uncommon Ground': '8CC2', // people who are on the same dartboard color with Bull's-eye + 'AAI Faerie Ring': '8956', // donut rings during Present Box + 'AAI Fire Spread 1': '8982', // initial rotating fire (from Ball of Fire) + 'AAI Fire Spread 2': '89F9', // ongoing rotating fire damage (from Statice) + }, + damageFail: { + 'AAI Big Burst': '8AC3', // tower failure damage + 'AAI Massive Explosion 1': '889C', // failing to resolve Subractive Suppressor Alpha + 'AAI Massive Explosion 2': '889D', // failing to resolve Subractive Suppressor Beta + 'AAI Burning Chains': '8CBE', // damage from not breaking chains + 'AAI Surprising Missile Burst': '8957', // running into Surprising Missile tethered add + 'AAI Surprising Claw Death by Claw': '8958', // running into Surprising Claw tethered add + }, + gainsEffectFail: { + // C03 = 9999 duration, ??? = 15s duration + 'AAI Dropsy': 'C03', // standing outside Ketuduke + // C05 = 9999 duration, C06 = 15s duration + 'AAI Bleeding': 'C05', // standing in blue square during Lala + // BF9 = 9999 duration, BFA??? = 15s duration + 'AAI Burns': 'BF9', // standing outside Lala + }, + shareWarn: { + 'AAI Hydrobullet': '8ABA', // spread debuffs + 'AAI Wood Golem Tornado': '8C4D', // headmarker -> bind and heavy aoe + 'AAI Powerful Light': '88A6', // spread marker during Symmetric Surge that turns squares blue + 'AAI Explosive Theorem': '88A8', // large spreads with Telluric Theorem puddles + 'AAI Trapshooting Spread': '895B', // spread damage from Trick Reload + 'AAI Firewords Spread': '8960', // spread damage during Present Box / Pinwheeling Dartboard + }, + soloWarn: { + 'AAI Snipper Water III': '8C64', // Snipper stack marker + 'AAI Islekeeper Gravity Force': '8BC5', // stack + 'AAI Trapshooting Stack': '895A', // stack damage from Trick Reload + }, + soloFail: { + 'AAI Hydrofall': '8AB7', // partner stack debuffs + 'AAI Symmetric Surge': '889E', // two person stack that gives magic vuln up + 'AAI Fireworks Stack': '895F', // two person stack damage during Present Box / Pinwheeling Dartboard + }, + triggers: [ + renameMistake('AAI Tornado', '8BC0', 'fail', { + // running into a tornado in the initial trash section + en: 'Tornado', + }), + pushedIntoWall('AAI Angry Seas', '8AC1'), + pushedIntoWall('AAI Pop', '894E'), + nonzeroDamageMistake('AAI Hundred Lashings', ['8AC9', '8ACB'], 'warn'), + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.ts b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.ts new file mode 100644 index 0000000000..722a4cd4be --- /dev/null +++ b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.ts @@ -0,0 +1,1529 @@ +// This file was autogenerated from running ts-node util/sync_files.ts. +// DO NOT EDIT THIS FILE DIRECTLY. +// Edit the source file below and then run `npm run sync-files` +// Source: ui/raidboss/data/06-ew/dungeon/another_aloalo_island.ts + +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { PluginCombatantState } from '../../../../../types/event'; +import { NetMatches } from '../../../../../types/net_matches'; +import { TriggerSet } from '../../../../../types/trigger'; + +// TODO: add callout for Monk Hydroshot target +// TODO: sc3 should say which bubble to take to the other side (for everyone) +// TODO: figure out directions for Lala for Radiance orbs +// TODO: map effects for Lala +// TODO: Lala Planar Tactics could add config strats and tell you who to stack with +// TODO: Statice colors could add config strats for role-based colors + melee flex + +export interface Data extends RaidbossData { + combatantData: PluginCombatantState[]; + ketuSpringCrystalCount: number; + ketuCrystalAdd: NetMatches['AddedCombatant'][]; + ketuHydroBuffCount: number; + ketuHydroBuffIsSpreadFirst?: boolean; + ketuHydroBuffIsRoleStacks?: boolean; + ketuBuff?: 'bubble' | 'fetters'; + ketuBuffPartner?: string; + ketuBuffCollect: NetMatches['GainsEffect'][]; + ketuStackTargets: string[]; + ketuTwintidesNext?: 'out' | 'in'; + lalaBossRotation?: 'clock' | 'counter'; + lalaBossTimes?: 3 | 5; + lalaBossInitialSafe?: 'north' | 'east' | 'south' | 'west'; + lalaUnseen?: 'front' | 'left' | 'right' | 'back'; + lalaPlayerTimes?: 3 | 5; + lalaPlayerRotation?: 'clock' | 'counter'; + lalaSubAlpha: NetMatches['GainsEffect'][]; + staticeBullet: NetMatches['Ability'][]; + staticeTriggerHappy?: number; + staticePopTriggerHappyNum?: number; + staticeTrapshooting: ('stack' | 'spread' | undefined)[]; + staticeDart: NetMatches['GainsEffect'][]; + staticePresentBoxCount: number; + staticeMissileCollect: NetMatches['AddedCombatant'][]; + staticeMissileIdToDir: { [id: string]: number }; + staticeMissileTether: NetMatches['Tether'][]; + staticeClawTether: NetMatches['Tether'][]; + staticeIsPinwheelingDartboard?: boolean; + staticeHomingColor?: 'blue' | 'yellow' | 'red'; + staticeDartboardTether: NetMatches['HeadMarker'][]; +} + +// Horizontal crystals have a heading of 0, vertical crystals are -pi/2. +const isHorizontalCrystal = (line: NetMatches['AddedCombatant']) => { + const epsilon = 0.1; + return Math.abs(parseFloat(line.heading)) < epsilon; +}; + +const headmarkerIds = { + tethers: '0061', + enumeration: '015B', +} as const; + +// TODO: this maybe should be a method on party? +const isStandardLightParty = (data: Data): boolean => { + const supports = [...data.party.healerNames, ...data.party.tankNames]; + const dps = data.party.dpsNames; + return supports.length === 2 && dps.length === 2; +}; + +const triggerSet: TriggerSet = { + id: 'AnotherAloaloIslandSavage', + zoneId: ZoneId.AnotherAloaloIslandSavage, + timelineFile: 'another_aloalo_island-savage.txt', + initData: () => { + return { + combatantData: [], + ketuSpringCrystalCount: 0, + ketuCrystalAdd: [], + ketuHydroBuffCount: 0, + ketuBuffCollect: [], + ketuStackTargets: [], + lalaSubAlpha: [], + staticeBullet: [], + staticeTrapshooting: [], + staticeDart: [], + staticePresentBoxCount: 0, + staticeMissileCollect: [], + staticeMissileIdToDir: {}, + staticeMissileTether: [], + staticeClawTether: [], + staticeDartboardTether: [], + }; + }, + timelineTriggers: [ + { + id: 'AAIS Lala Radiance', + regex: /^Radiance \d/, + beforeSeconds: 4, + alertText: (data, _matches, output) => { + // TODO: could figure out directions here and say "Point left at NW Orb" + const dir = data.lalaUnseen; + if (dir === undefined) + return output.orbGeneral!(); + return { + front: output.orbDirFront!(), + back: output.orbDirBack!(), + left: output.orbDirLeft!(), + right: output.orbDirRight!(), + }[dir]; + }, + outputStrings: { + orbDirFront: { + en: 'Face Towards Orb', + }, + orbDirBack: { + en: 'Face Away from Orb', + }, + orbDirLeft: { + en: 'Point Left at Orb', + }, + orbDirRight: { + en: 'Point Right at Orb', + }, + orbGeneral: { + en: 'Point opening at Orb', + }, + }, + }, + ], + triggers: [ + // ---------------- first trash ---------------- + { + id: 'AAIS Kiwakin Lead Hook', + type: 'StartsUsing', + netRegex: { id: '8BC7', source: 'Aloalo Kiwakin' }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankBusterOnYou: { + en: '3x Tankbuster on YOU', + }, + tankBusterOnPlayer: { + en: '3x Tankbuster on ${player}', + }, + }; + + if (matches.target === data.me) + return { alertText: output.tankBusterOnYou!() }; + const target = data.party.member(matches.target); + return { infoText: output.tankBusterOnPlayer!({ player: target }) }; + }, + }, + { + id: 'AAIS Kiwakin Sharp Strike', + type: 'StartsUsing', + netRegex: { id: '8BC8', source: 'Aloalo Kiwakin' }, + response: Responses.tankBuster(), + }, + { + id: 'AAIS Kiwakin Tail Screw', + type: 'StartsUsing', + // This is a baited targeted circle. + netRegex: { id: '8BC9', source: 'Aloalo Kiwakin', capture: false }, + response: Responses.moveAway(), + }, + { + id: 'AAIS Snipper Water III', + type: 'StartsUsing', + netRegex: { id: '8BCC', source: 'Aloalo Snipper' }, + response: Responses.stackMarkerOn(), + }, + { + id: 'AAIS Snipper Bubble Shower', + type: 'StartsUsing', + netRegex: { id: '8BCA', source: 'Aloalo Snipper', capture: false }, + response: Responses.getBackThenFront(), + }, + { + id: 'AAIS Snipper Crab Dribble', + type: 'Ability', + // Crab Dribble 8BCB has a fast cast, so trigger on Bubble Shower ability + netRegex: { id: '8BCA', source: 'Aloalo Snipper', capture: false }, + suppressSeconds: 5, + response: Responses.goFront('info'), + }, + { + id: 'AAIS Ray Hydrocannon', + type: 'StartsUsing', + netRegex: { id: '8C4B', source: 'Aloalo Ray', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'AAIS Ray Expulsion', + type: 'StartsUsing', + netRegex: { id: '8BCE', source: 'Aloalo Ray', capture: false }, + response: Responses.getOut(), + }, + { + id: 'AAIS Ray Electric Whorl', + type: 'StartsUsing', + netRegex: { id: '8BCD', source: 'Aloalo Ray', capture: false }, + response: Responses.getUnder(), + }, + { + id: 'AAIS Monk Hydroshot', + type: 'StartsUsing', + netRegex: { id: '8BCD', source: 'Aloalo Monk' }, + condition: Conditions.targetIsYou(), + response: Responses.knockbackOn(), + }, + { + id: 'AAIS Monk Cross Attack', + type: 'StartsUsing', + netRegex: { id: '8C4F', source: 'Aloalo Monk' }, + response: Responses.tankBuster(), + }, + // ---------------- Ketuduke ---------------- + { + id: 'AAIS Ketuduke Tidal Roar', + type: 'StartsUsing', + netRegex: { id: '8AD4', source: 'Ketuduke', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'AAIS Ketuduke Spring Crystals', + type: 'StartsUsing', + netRegex: { id: '8AA8', source: 'Ketuduke', capture: false }, + run: (data) => { + data.ketuSpringCrystalCount++; + data.ketuCrystalAdd = []; + }, + }, + { + id: 'AAIS Ketuduke Spring Crystal Collect', + type: 'AddedCombatant', + netRegex: { npcNameId: '12607' }, + run: (data, matches) => data.ketuCrystalAdd.push(matches), + }, + { + id: 'AAIS Ketuduke Bubble Net', + type: 'StartsUsing', + netRegex: { id: ['8AC5', '8AAD'], source: 'Ketuduke', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAIS Ketuduke Foamy Fetters Bubble Weave', + type: 'GainsEffect', + // ECC = Foamy Fetters + // E9F = Bubble Weave + netRegex: { effectId: ['ECC', 'E9F'] }, + delaySeconds: (data, matches) => { + data.ketuBuffCollect.push(matches); + return data.ketuBuffCollect.length === 4 ? 0 : 0.5; + }, + alertText: (data, _matches, output) => { + if (data.ketuBuffCollect.length === 0) + return; + + const myBuff = data.ketuBuffCollect.find((x) => x.target === data.me)?.effectId; + if (myBuff === undefined) + return; + data.ketuBuff = myBuff === 'ECC' ? 'fetters' : 'bubble'; + data.ketuBuffPartner = data.ketuBuffCollect.find((x) => { + return x.target !== data.me && x.effectId === myBuff; + })?.target; + const player = data.party.member(data.ketuBuffPartner); + + // To avoid too many calls, we'll call this out later for the Fluke Gale + // versions of this. + if (data.ketuSpringCrystalCount === 1 || data.ketuSpringCrystalCount === 4) + return; + + if (data.ketuBuff === 'fetters') + return output.fetters!({ player: player }); + return output.bubble!({ player: player }); + }, + run: (data) => data.ketuBuffCollect = [], + outputStrings: { + fetters: { + en: 'Fetters (w/${player})', + }, + bubble: { + en: 'Bubble (w/${player})', + }, + }, + }, + { + id: 'AAIS Ketuduke Hydro Buff Counter', + type: 'StartsUsing', + // 8AB8 = Hydrobullet (spread) + // 8AB4 = Hydrofall (stack) + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke', capture: false }, + run: (data) => { + data.ketuHydroBuffCount++; + delete data.ketuHydroBuffIsSpreadFirst; + delete data.ketuHydroBuffIsRoleStacks; + }, + }, + { + id: 'AAIS Ketuduke Hydro Buff 1', + comment: { + en: `These directions assume that you always pick a square in the same + quadrant as the crystal specified. + For brevity, "next to" always means horizontal east/west of something. + The number in parentheses is the limit cut wind you should be on. + See trigger source for diagrams in comments.`, + }, + type: 'StartsUsing', + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke' }, + condition: (data) => data.ketuHydroBuffCount === 1 || data.ketuHydroBuffCount === 6, + durationSeconds: 8, + alertText: (data, matches, output) => { + if (data.ketuBuff === undefined) + return; + + const isPlayerDPS = data.party.isDPS(data.me); + const isPartnerDPS = data.ketuBuffPartner !== undefined + ? data.party.isDPS(data.ketuBuffPartner) + : undefined; + const isBubbleNetPartnerSameRole = isPlayerDPS === isPartnerDPS && + isStandardLightParty(data); + + // Simplify callout in vast majority of cases where there's a normal light party setup + // and you and the two dps and two supports get the same debuff, so no need to list + // your partner. + // + // Otherwise, if you're doing this nonstandard for some reason or somebody is dead + // you can know if you need to flex. + const isSpread = matches.id === '8AB8'; + const bubbleStr = data.ketuBuff === 'bubble' ? output.bubbleBuff!() : output.fettersBuff!(); + // We don't know about role stacks at this point, as this is just the initial cast bar. + const stackStr = isSpread ? output.spread!() : output.stacks!(); + if (isBubbleNetPartnerSameRole || data.ketuBuffPartner === undefined) + return output.bubbleNetMech!({ fettersBubble: bubbleStr, spreadStack: stackStr }); + return output.bubbleNetMechPartner!({ + fettersBubble: bubbleStr, + spreadStack: stackStr, + player: data.party.member(data.ketuBuffPartner), + }); + }, + infoText: (data, matches, output) => { + // If somebody died and missed a debuff, good luck. + if (data.ketuBuff === undefined) + return; + + // Bubble always does the same thing. + if (data.ketuBuff === 'bubble') + return output.bubbleAnything!(); + + // Two layouts, one with each crystal in its own column ("split") + // and one with two columns that have an H and a V in that same column ("columns"). + // Wind doesn't matter, as "1" will always be on the horizontal crystals. + // These can be flipped somewhat, but the solution is always the same. + // + // STACK FETTERS COLUMNS (kitty to horizontal) + // 2 2 + // + - - - - + + - - - - + + // | V f | 1 | H f | 1 + // | H b | => | V | + // | H b | | V | + // 1 | f V | 1 | H f | + // + - - - - + + - - - - + + // 2 2 + // + // STACK FETTERS SPLIT (on horizontal) + // 2 2 + // + - - - - + + - - - - + + // 1 | V | 1 | V | + // | H b | => | f H | + // | V | | V | + // | b H | 1 | H f | 1 + // + - - - - + + - - - - + + // 2 2 + // + // SPREAD FETTERS COLUMNS (adjacent to vertical) + // 2 2 + // + - - - - + + - - - - + + // | V f | 1 | f H b | 1 + // | H b | => | V | + // | H b | | V | + // 1 | V f | 1 | H b f | + // + - - - - + + - - - - + + // 2 2 + // + // SPREAD FETTERS SPLIT (kitty to vertical) + // 2 2 + // + - - - - + + - - - - + + // | V | 1 | V | 1 + // | f b H | => | f H b | + // | V | | V | + // 1 | H b f | 1 | b H f | + // + - - - - + + - - - - + + // 2 2 + + const isSpread = matches.id === '8AB8'; + const horizontal = data.ketuCrystalAdd.filter((x) => isHorizontalCrystal(x)); + const vertical = data.ketuCrystalAdd.filter((x) => !isHorizontalCrystal(x)); + + const [firstHorizontal] = horizontal; + if (horizontal.length !== 2 || vertical.length !== 2 || firstHorizontal === undefined) + return; + const firstHorizX = parseFloat(firstHorizontal.x); + // It's split if no vertical is in the same column as either horizontal. + const isSplitLayout = + vertical.find((line) => Math.abs(parseFloat(line.x) - firstHorizX) < 1) === undefined; + + if (isSpread) + return isSplitLayout ? output.fettersSpreadSplit!() : output.fettersSpreadColumn!(); + return isSplitLayout ? output.fettersStackSplit!() : output.fettersStackColumn!(); + }, + outputStrings: { + bubbleNetMech: { + en: '${fettersBubble} + ${spreadStack}', + }, + bubbleNetMechPartner: { + en: '${fettersBubble} + ${spreadStack} (w/${player})', + }, + bubbleBuff: { + en: 'Bubble', + }, + fettersBuff: { + en: 'Fetters', + }, + spread: Outputs.spread, + stacks: { + en: 'Stacks', + }, + bubbleAnything: { + en: 'Next to Horizontal (1)', + }, + fettersSpreadSplit: { + en: 'Diagonal of Vertical (2)', + }, + fettersSpreadColumn: { + en: 'Next to Vertical (2)', + }, + fettersStackSplit: { + en: 'On Horizontal (1)', + }, + fettersStackColumn: { + en: 'Diagonal of Horizontal (1)', + }, + }, + }, + { + id: 'AAIS Ketuduke Hydro Buff Double', + type: 'StartsUsing', + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke' }, + condition: (data) => data.ketuHydroBuffCount === 2 || data.ketuHydroBuffCount === 5, + alertText: (data, matches, output) => { + data.ketuHydroBuffIsSpreadFirst = matches.id === '8AB8'; + return data.ketuHydroBuffIsSpreadFirst ? output.spread!() : output.stacks!(); + }, + outputStrings: { + spread: { + en: 'Spread => Stacks', + }, + stacks: { + en: 'Stacks => Spread', + }, + }, + }, + { + id: 'AAIS Ketuduke Hydro Buff Double Followup', + type: 'Ability', + netRegex: { id: ['8ADF', '8ADE'], source: 'Ketuduke' }, + suppressSeconds: 10, + infoText: (data, matches, output) => { + const wasSpread = matches.id === '8ADF'; + if (wasSpread && data.ketuHydroBuffIsSpreadFirst === true) { + if (data.ketuHydroBuffIsRoleStacks) + return output.roleStacks!(); + return output.stacks!(); + } else if (!wasSpread && data.ketuHydroBuffIsSpreadFirst === false) { + return output.spread!(); + } + }, + outputStrings: { + spread: Outputs.spread, + stacks: { + en: 'Stacks', + }, + roleStacks: { + en: 'Role Stacks', + }, + }, + }, + { + id: 'AAIS Ketuduke Hydrofall Role Stack Warning', + type: 'GainsEffect', + netRegex: { effectId: 'EA3' }, + delaySeconds: (data, matches) => { + data.ketuStackTargets.push(matches.target); + return data.ketuStackTargets.length === 2 ? 0 : 0.5; + }, + alarmText: (data, _matches, output) => { + const [stack1, stack2] = data.ketuStackTargets; + if (data.ketuStackTargets.length !== 2 || stack1 === undefined || stack2 === undefined) + return; + + // Sorry, non-standard light party comps. + if (!isStandardLightParty(data)) + return; + + const isStack1DPS = data.party.isDPS(stack1); + const isStack2DPS = data.party.isDPS(stack2); + + // If both stacks are on dps or neither stack is on a dps, then you have + // standard "partner" stacks of one support and one dps. If one is on a dps + // and one is on a support (which can happen if somebody dies), then + // you (probably) need to have role stacks. + if (isStack1DPS === isStack2DPS) + return; + + data.ketuHydroBuffIsRoleStacks = true; + + // Handle Blowing Bubbles/Angry Seas spread+stack combo. + if (data.ketuHydroBuffIsSpreadFirst === true) + return output.spreadThenRoleStacks!(); + else if (data.ketuHydroBuffIsSpreadFirst === false) + return output.roleStacksThenSpread!(); + return output.roleStacks!(); + }, + run: (data) => data.ketuStackTargets = [], + outputStrings: { + roleStacks: { + en: 'Role Stacks', + }, + spreadThenRoleStacks: { + en: 'Spread => Role Stacks', + }, + roleStacksThenSpread: { + en: 'Role Stacks => Spread', + }, + }, + }, + { + id: 'AAIS Ketuduke Receding Twintides', + type: 'StartsUsing', + netRegex: { id: '8AE7', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsRoleStacks) + return output.outInRoleStacks!(); + return output.outInStacks!(); + }, + run: (data) => data.ketuTwintidesNext = 'in', + outputStrings: { + outInStacks: { + en: 'Out => In + Stacks', + }, + outInRoleStacks: { + en: 'Out => In + Role Stacks', + }, + }, + }, + { + id: 'AAIS Ketuduke Encroaching Twintides', + type: 'StartsUsing', + netRegex: { id: '8AE9', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsRoleStacks) + return output.inOutRoleStacks!(); + return output.inOutStacks!(); + }, + run: (data) => data.ketuTwintidesNext = 'out', + outputStrings: { + inOutStacks: { + en: 'In => Out + Stacks', + }, + inOutRoleStacks: { + en: 'In => Out + Role Stacks', + }, + }, + }, + { + id: 'AAIS Ketuduke Twintides Followup', + type: 'Ability', + // 8AE0 = Sphere Shatter, which happens slightly after the Twintides hit. + // You can technically start moving along the safe Sphere Shatter side 0.5s earlier + // after the initial out/in, but this is hard to explain. + netRegex: { id: '8AE0', source: 'Ketuduke', capture: false }, + suppressSeconds: 5, + infoText: (data, _matches, output) => { + const mech = data.ketuTwintidesNext; + if (mech === undefined) + return; + const mechStr = output[mech]!(); + const stackStr = data.ketuHydroBuffIsRoleStacks ? output.roleStacks!() : output.stack!(); + return output.text!({ inOut: mechStr, stack: stackStr }); + }, + run: (data) => delete data.ketuTwintidesNext, + outputStrings: { + text: { + en: '${inOut} + ${stack}', + }, + in: Outputs.in, + out: Outputs.out, + stack: { + en: 'Stacks', + }, + roleStacks: { + en: 'Role Stacks', + }, + }, + }, + { + id: 'AAIS Ketuduke Spring Crystals 2', + type: 'AddedCombatant', + netRegex: { npcNameId: '12607', capture: false }, + condition: (data) => data.ketuSpringCrystalCount === 2 && data.ketuCrystalAdd.length === 4, + // We could call this absurdly early, but knowing this doesn't help with anything + // until you know what your debuff is, so move it later both so it is less absurd + // futuresight and so you don't have to remember it as long. + delaySeconds: 5, + alertText: (data, _matches, output) => { + const horizontal = data.ketuCrystalAdd.filter((x) => isHorizontalCrystal(x)); + const vertical = data.ketuCrystalAdd.filter((x) => !isHorizontalCrystal(x)); + if (horizontal.length !== 2 || vertical.length !== 2) + return; + + // Crystal positions are always -15, -5, 5, 15. + + // Check if any verticals are on the outer vertical edges. + for (const line of vertical) { + const y = parseFloat(line.y); + if (y < -10 || y > 10) + return output.eastWestSafe!(); + } + + // Check if any horizontals are on the outer horizontal edges. + for (const line of horizontal) { + const x = parseFloat(line.x); + if (x < -10 || x > 10) + return output.northSouthSafe!(); + } + + return output.cornersSafe!(); + }, + outputStrings: { + northSouthSafe: { + en: 'North/South', + }, + eastWestSafe: { + en: 'East/West', + }, + cornersSafe: { + en: 'Corners', + }, + }, + }, + { + id: 'AAIS Ketuduke Angry Seas', + type: 'StartsUsing', + netRegex: { id: '8AE1', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsSpreadFirst) + return output.knockbackSpread!(); + if (data.ketuHydroBuffIsRoleStacks) + return output.knockbackRoleStacks!(); + return output.knockbackStacks!(); + }, + outputStrings: { + knockbackSpread: { + en: 'Knockback => Spread', + }, + knockbackStacks: { + en: 'Knockback => Stacks', + }, + knockbackRoleStacks: { + en: 'Knockback => Role Stacks', + }, + }, + }, + // ---------------- second trash ---------------- + { + id: 'AAIS Wood Golem Ancient Aero III', + type: 'StartsUsing', + netRegex: { id: '8BD2', source: 'Aloalo Wood Golem' }, + condition: (data) => data.CanSilence(), + response: Responses.interrupt('alarm'), + }, + { + id: 'AAIS Wood Golem Tornado', + type: 'StartsUsing', + netRegex: { id: '8BD3', source: 'Aloalo Wood Golem' }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tornadoOn: { + en: 'Away from ${player}', + }, + tornadoOnYou: { + en: 'Tornado on YOU', + }, + }; + + if (data.me === matches.target) + return { alertText: output.tornadoOnYou!() }; + return { infoText: output.tornadoOn!({ player: data.party.member(matches.target) }) }; + }, + }, + { + id: 'AAIS Wood Golem Tornado Bind', + type: 'GainsEffect', + netRegex: { effectId: 'EC0' }, + condition: (data) => data.CanCleanse(), + infoText: (data, matches, output) => { + return output.text!({ player: data.party.member(matches.target) }); + }, + outputStrings: { + text: { + en: 'Cleanse ${player}', + }, + }, + }, + { + id: 'AAIS Wood Golem Ovation', + type: 'StartsUsing', + netRegex: { id: '8BD4', source: 'Aloalo Wood Golem', capture: false }, + response: Responses.getBehind('info'), + }, + { + id: 'AAIS Islekeeper Gravity Force', + type: 'StartsUsing', + netRegex: { id: '8C3A', source: 'Aloalo Islekeeper' }, + response: Responses.stackMarkerOn(), + }, + { + id: 'AAIS Islekeeper Isle Drop', + type: 'StartsUsing', + netRegex: { id: '8C3C', source: 'Aloalo Islekeeper', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Behind + Out', + }, + }, + }, + { + id: 'AAIS Islekeeper Ancient Quaga', + type: 'StartsUsing', + netRegex: { id: '8C39', source: 'Aloalo Islekeeper', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'AAIS Islekeeper Ancient Quaga Enrage', + type: 'StartsUsing', + netRegex: { id: 'TODO', source: 'Aloalo Islekeeper', capture: false }, + alarmText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Kill Islekeeper!', + }, + }, + }, + // ---------------- Lala ---------------- + { + id: 'AAIS Lala Inferno Theorem', + type: 'StartsUsing', + netRegex: { id: '8C05', source: 'Lala', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAIS Lala Rotation Tracker', + type: 'HeadMarker', + netRegex: { id: ['01E4', '01E5'], target: 'Lala' }, + run: (data, matches) => data.lalaBossRotation = matches.id === '01E4' ? 'clock' : 'counter', + }, + { + id: 'AAIS Lala Angular Addition Tracker', + type: 'Ability', + netRegex: { id: ['8BE0', '8D2F'], source: 'Lala' }, + run: (data, matches) => data.lalaBossTimes = matches.id === '8BE0' ? 3 : 5, + }, + { + id: 'AAIS Lala Arcane Blight', + type: 'StartsUsing', + netRegex: { id: ['8BE2', '8BE3', '8BE4', '8BE5'], source: 'Lala' }, + alertText: (data, matches, output) => { + const initialDir = { + '8BE2': 2, // initial back safe + '8BE3': 0, // initial front safe + '8BE4': 1, // initial right safe + '8BE5': 3, // initial left safe + }[matches.id]; + if (initialDir === undefined) + return; + if (data.lalaBossTimes === undefined) + return; + if (data.lalaBossRotation === undefined) + return; + const rotationFactor = data.lalaBossRotation === 'clock' ? 1 : -1; + const finalDir = (initialDir + rotationFactor * data.lalaBossTimes + 8) % 4; + + const diff = (finalDir - initialDir + 4) % 4; + if (diff !== 1 && diff !== 3) + return; + return { + 0: output.front!(), + 1: output.right!(), + 2: output.back!(), + 3: output.left!(), + }[finalDir]; + }, + run: (data) => { + delete data.lalaBossTimes; + delete data.lalaBossRotation; + }, + outputStrings: { + front: Outputs.front, + back: Outputs.back, + left: Outputs.left, + right: Outputs.right, + }, + }, + { + id: 'AAIS Lala Analysis Collect', + type: 'GainsEffect', + netRegex: { effectId: ['E8E', 'E8F', 'E90', 'E91'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const effectMap: { [effectId: string]: typeof data.lalaUnseen } = { + 'E8E': 'front', + 'E8F': 'back', + 'E90': 'right', + 'E91': 'left', + } as const; + data.lalaUnseen = effectMap[matches.effectId]; + }, + }, + { + id: 'AAIS Lala Times Collect', + type: 'GainsEffect', + netRegex: { effectId: ['E89', 'ECE'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const effectMap: { [effectId: string]: typeof data.lalaPlayerTimes } = { + 'E89': 3, + 'ECE': 5, + } as const; + data.lalaPlayerTimes = effectMap[matches.effectId]; + }, + }, + { + id: 'AAIS Lala Player Rotation Collect', + type: 'HeadMarker', + netRegex: { id: ['01ED', '01EE'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const idMap: { [id: string]: typeof data.lalaPlayerRotation } = { + '01ED': 'counter', + '01EE': 'clock', + } as const; + data.lalaPlayerRotation = idMap[matches.id]; + }, + }, + { + id: 'AAIS Lala Targeted Light', + type: 'StartsUsing', + netRegex: { id: '8CE0', source: 'Lala', capture: false }, + alertText: (data, _matches, output) => { + const initialUnseen = data.lalaUnseen; + if (initialUnseen === undefined) + return; + + const initialDir = { + front: 0, + right: 1, + back: 2, + left: 3, + }[initialUnseen]; + + const rotation = data.lalaPlayerRotation; + if (rotation === undefined) + return; + const times = data.lalaPlayerTimes; + if (times === undefined) + return; + + // The safe spot rotates, so the player counter-rotates. + const rotationFactor = rotation === 'clock' ? -1 : 1; + const finalDir = (initialDir + rotationFactor * times + 8) % 4; + + return { + 0: output.front!(), + 1: output.right!(), + 2: output.back!(), + 3: output.left!(), + }[finalDir]; + }, + run: (data) => { + delete data.lalaUnseen; + delete data.lalaPlayerTimes; + }, + outputStrings: { + front: { + en: 'Face Towards Lala', + }, + back: { + en: 'Look Away from Lala', + }, + left: { + en: 'Left Flank towards Lala', + }, + right: { + en: 'Right Flank towards Lala', + }, + }, + }, + { + id: 'AAIS Lala Strategic Strike', + type: 'StartsUsing', + netRegex: { id: '8C04', source: 'Lala' }, + response: Responses.tankBuster(), + }, + { + id: 'AAIS Lala Planar Tactics', + type: 'GainsEffect', + // E8B = Surge Vector + // E8C = Subtractive Suppressor Alpha + netRegex: { effectId: ['E8C', 'E8B'] }, + condition: (data, matches) => { + data.lalaSubAlpha.push(matches); + return data.lalaSubAlpha.length === 6; + }, + durationSeconds: 7, + // Only run once, as Surge Vector is used again. + suppressSeconds: 9999999, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + one: { + en: 'One', + }, + bigTwo: { + en: 'Two (stack with three)', + }, + smallTwo: { + en: 'Two (stack with one)', + }, + eitherTwo: { + en: 'Either Two (w/${player})', + }, + three: { + en: 'Three', + }, + // This is just a raidcall so you can direct your friends. + smallTwoOn: { + en: '(Two with one: ${players})', + }, + unknownNum: { + en: '${num}', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + }; + + // For brevity, this code calls "small two" the two that stacks with one + // and the "big two" the two that stacks with three. + const stacks = data.lalaSubAlpha.filter((x) => x.effectId === 'E8B').map((x) => x.target); + const nums = data.lalaSubAlpha.filter((x) => x.effectId === 'E8C'); + const myNumberStr = nums.find((x) => x.target === data.me)?.count; + if (myNumberStr === undefined) + return; + const myNumber = parseInt(myNumberStr); + if (myNumber < 1 || myNumber > 4) + return; + + const defaultOutput = { + alertText: output.unknownNum!({ num: output[`num${myNumber}`]!() }), + } as const; + + if (stacks.length !== 2 || nums.length !== 4) + return defaultOutput; + + const one = nums.find((x) => parseInt(x.count) === 1)?.target; + if (one === undefined) + return defaultOutput; + const isOneStack = stacks.includes(one); + const twos = nums.filter((x) => parseInt(x.count) === 2).map((x) => x.target); + + const smallTwos: string[] = []; + for (const thisTwo of twos) { + // can this two stack with the one? + const isThisTwoStack = stacks.includes(thisTwo); + if (isThisTwoStack && !isOneStack || !isThisTwoStack && isOneStack) + smallTwos.push(thisTwo); + } + + const [smallTwo1, smallTwo2] = smallTwos; + if (smallTwos.length === 0 || smallTwo1 === undefined) + return defaultOutput; + + const isPlayerSmallTwo = smallTwos.includes(data.me); + + // Worst case adjust + if (isPlayerSmallTwo && smallTwo2 !== undefined) { + const otherPlayer = smallTwo1 === data.me ? smallTwo2 : smallTwo1; + return { alarmText: output.eitherTwo!({ player: data.party.member(otherPlayer) }) }; + } + + let playerRole: string; + if (one === data.me) { + playerRole = output.one!(); + } else if (twos.includes(data.me)) { + playerRole = isPlayerSmallTwo ? output.smallTwo!() : output.bigTwo!(); + } else { + playerRole = output.three!(); + } + + if (isPlayerSmallTwo) + return { alertText: playerRole }; + + return { + alertText: playerRole, + infoText: output.smallTwoOn!({ players: smallTwos.map((x) => data.party.member(x)) }), + }; + }, + }, + { + id: 'AAIS Lala Forward March', + type: 'GainsEffect', + // E83 = Forward March + netRegex: { effectId: 'E83' }, + condition: Conditions.targetIsYou(), + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 8, + durationSeconds: 4, + alertText: (data, _matches, output) => { + const rotation = data.lalaPlayerRotation; + if (rotation === undefined) + return; + const times = data.lalaPlayerTimes; + if (times === undefined) + return; + + const rotationFactor = rotation === 'clock' ? 1 : -1; + const finalDir = (rotationFactor * times + 8) % 4; + if (finalDir === 1) + return output.left!(); + if (finalDir === 3) + return output.right!(); + }, + run: (data) => { + delete data.lalaPlayerRotation; + delete data.lalaPlayerTimes; + }, + outputStrings: { + left: { + en: 'Leftward March', + }, + right: { + en: 'Rightward March', + }, + }, + }, + { + id: 'AAIS Lala Spatial Tactics', + type: 'GainsEffect', + // E8D = Subtractive Suppressor Beta + netRegex: { effectId: 'E8D' }, + condition: Conditions.targetIsYou(), + suppressSeconds: 999999, + alertText: (_data, matches, output) => { + const num = parseInt(matches.count); + if (num < 1 || num > 4) + return; + return output[`num${num}`]!(); + }, + outputStrings: { + num1: { + en: 'One (avoid all)', + }, + num2: { + en: 'Two (stay middle)', + }, + num3: { + en: 'Three (adjacent to middle)', + }, + num4: { + en: 'Four', + }, + }, + }, + // ---------------- Statice ---------------- + { + id: 'AAIS Statice Aero IV', + type: 'StartsUsing', + netRegex: { id: '8966', source: 'Statice', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAIS Statice Trick Reload', + type: 'Ability', + // 8925 = Locked and Loaded + // 8926 = Misload + netRegex: { id: ['8925', '8926'], source: 'Statice' }, + preRun: (data, matches) => data.staticeBullet.push(matches), + alertText: (data, _matches, output) => { + // Statice loads 8 bullets, two are duds. + // The first and the last are always opposite, and one of them is a dud. + // The first/ last bullets are for Trapshooting and the middle six are for Trigger Happy. + const [bullet] = data.staticeBullet; + if (data.staticeBullet.length !== 1 || bullet === undefined) + return; + const isStack = bullet.id === '8926'; + data.staticeTrapshooting = isStack ? ['stack', 'spread'] : ['spread', 'stack']; + return isStack ? output.stackThenSpread!() : output.spreadThenStack!(); + }, + infoText: (data, _matches, output) => { + const lastBullet = data.staticeBullet[data.staticeBullet.length - 1]; + if (data.staticeBullet.length < 2 || data.staticeBullet.length > 7) + return; + if (lastBullet?.id !== '8926') + return; + data.staticeTriggerHappy = data.staticeBullet.length - 1; + return output.numSafeLater!({ num: output[`num${data.staticeTriggerHappy}`]!() }); + }, + run: (data) => { + if (data.staticeBullet.length === 8) + data.staticeBullet = []; + }, + outputStrings: { + stackThenSpread: Outputs.stackThenSpread, + spreadThenStack: Outputs.spreadThenStack, + numSafeLater: { + en: '(${num} safe later)', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAIS Statice Trapshooting', + type: 'StartsUsing', + netRegex: { id: ['8D1C', '8976'], source: 'Statice', capture: false }, + alertText: (data, _matches, output) => { + const mech = data.staticeTrapshooting.shift(); + if (mech === undefined) + return; + return output[mech]!(); + }, + outputStrings: { + spread: Outputs.spread, + stack: Outputs.stackMarker, + }, + }, + { + id: 'AAIS Statice Trigger Happy', + type: 'StartsUsing', + netRegex: { id: '8968', source: 'Statice', capture: false }, + alertText: (data, _matches, output) => { + const num = data.staticeTriggerHappy; + if (num === undefined) + return; + return output[`num${num}`]!(); + }, + run: (data) => delete data.staticeTriggerHappy, + outputStrings: { + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAIS Statice Bull\'s-eye', + type: 'GainsEffect', + netRegex: { effectId: 'E9E' }, + delaySeconds: (data, matches) => { + // Note: this collects for the pinwheeling dartboard version too. + data.staticeDart.push(matches); + return data.staticeDart.length === 3 ? 0 : 0.5; + }, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + dartOnYou: { + en: 'Dart on YOU', + }, + noDartOnYou: { + en: 'No Dart', + }, + flexCall: { + en: '(${player} unmarked)', + }, + }; + + if (data.staticeIsPinwheelingDartboard) + return; + + if (data.staticeDart.length === 0) + return; + + const dartTargets = data.staticeDart.map((x) => x.target); + + if (!dartTargets.includes(data.me)) + return { alertText: output.noDartOnYou!() }; + + const partyNames = data.party.partyNames; + + const flexers = partyNames.filter((x) => !dartTargets.includes(x)); + const [flex] = flexers; + const flexPlayer = flexers.length === 1 ? data.party.member(flex) : undefined; + + return { + alertText: output.dartOnYou!(), + infoText: output.flexCall!({ player: flexPlayer }), + }; + }, + run: (data) => data.staticeDart = [], + }, + { + id: 'AAIS Statice Surprise Balloon Reminder', + // This is an early reminder for the following Trigger Happy with knockback. + // However, because there's a tight window to immuune both knockbacks, + // call this ~15s early (in case anybody forgot). + type: 'StartsUsing', + netRegex: { id: '8927', source: 'Statice', capture: false }, + infoText: (data, _matches, output) => { + const num = data.staticeTriggerHappy; + if (num === undefined) + return; + // We'll re-call this out with the knockback warning. + // However, also clear `data.staticeTriggerHappy` to avoid double callouts. + data.staticePopTriggerHappyNum = num; + return output.numSafeSoon!({ num: output[`num${num}`]!() }); + }, + run: (data) => delete data.staticeTriggerHappy, + outputStrings: { + numSafeSoon: { + en: '(${num} safe soon)', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAIS Statice Pop', + type: 'StartsUsing', + // TODO: this might need a slight delay + netRegex: { id: '896B', source: 'Statice', capture: false }, + suppressSeconds: 20, + alertText: (data, _matches, output) => { + const num = data.staticePopTriggerHappyNum; + if (num === undefined) + return output.knockback!(); + + const numStr = output[`num${num}`]!(); + return output.knockbackToNum!({ num: numStr }); + }, + run: (data) => delete data.staticePopTriggerHappyNum, + outputStrings: { + knockbackToNum: { + en: 'Knockback => ${num}', + }, + knockback: Outputs.knockback, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAIS Statice Face', + type: 'GainsEffect', + // DD2 = Forward March + // DD3 = About Face + // DD4 = Left Face + // DD5 = Right Face + netRegex: { effectId: ['DD2', 'DD3', 'DD4', 'DD5'] }, + condition: Conditions.targetIsYou(), + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 7, + durationSeconds: 5, + alertText: (data, matches, output) => { + let mech = output.unknown!(); + + const num = data.staticeTriggerHappy; + if (num !== undefined) { + mech = output[`num${num}`]!(); + delete data.staticeTriggerHappy; + } else { + const mechName = data.staticeTrapshooting.shift(); + mech = mechName === undefined ? output.unknown!() : output[mechName]!(); + } + + return { + 'DD2': output.forward!({ mech: mech }), + 'DD3': output.backward!({ mech: mech }), + 'DD4': output.left!({ mech: mech }), + 'DD5': output.right!({ mech: mech }), + }[matches.effectId]; + }, + outputStrings: { + forward: { + en: 'Forward March => ${mech}', + }, + backward: { + en: 'Backward March => ${mech}', + }, + left: { + en: 'Left March => ${mech}', + }, + right: { + en: 'Right March => ${mech}', + }, + spread: Outputs.spread, + stack: Outputs.stackMarker, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + unknown: Outputs.unknown, + }, + }, + { + id: 'AAIS Statice Present Box Counter', + // This happens ~1s prior to ActorControlExtra on bomb. + type: 'StartsUsing', + netRegex: { id: '8972', source: 'Statice', capture: false }, + run: (data) => data.staticePresentBoxCount++, + }, + { + id: 'AAIS Statice Present Box Missile', + type: 'Tether', + netRegex: { source: 'Surprising Missile', id: '0011' }, + delaySeconds: (data, matches) => { + data.staticeMissileTether.push(matches); + return data.staticeMissileTether.length === 2 ? 0 : 0.5; + }, + durationSeconds: 7, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + missileOnYou: { + en: 'Bait Tethers => Missile Spread', + }, + }; + + if (data.staticeMissileTether.length !== 2) + return; + + const missileTether = data.staticeMissileTether.find((x) => x.target === data.me); + if (missileTether === undefined) + return; + + return { alertText: output.missileOnYou!() }; + }, + run: (data) => data.staticeMissileTether = [], + }, + { + id: 'AAIS Statice Present Box Claw', + type: 'Tether', + netRegex: { source: 'Surprising Claw', id: '0011' }, + delaySeconds: (data, matches) => { + data.staticeClawTether.push(matches); + return data.staticeClawTether.length === 2 ? 0 : 0.5; + }, + durationSeconds: 7, + alertText: (data, _matches, output) => { + if (data.staticeClawTether.length !== 2) + return; + if (!data.staticeClawTether.map((x) => x.target).includes(data.me)) + return; + return output.stack!(); + }, + run: (data) => data.staticeClawTether = [], + outputStrings: { + stack: { + en: 'Juke Claw => Stack', + }, + }, + }, + { + id: 'AAIS Statice Burning Chains', + type: 'GainsEffect', + netRegex: { effectId: '301' }, + condition: Conditions.targetIsYou(), + // TODO: add a strategy for dart colors and say where to go here + // for the Pinwheeling Dartboard if you have a dart. + response: Responses.breakChains(), + }, + { + id: 'AAIS Statice Shocking Abandon', + type: 'StartsUsing', + netRegex: { id: '8965', source: 'Statice' }, + response: Responses.tankBuster(), + }, + { + id: 'AAIS Statice Pinwheeling Dartboard Tracker', + type: 'StartsUsing', + netRegex: { id: '8CBF', source: 'Statice', capture: false }, + run: (data) => data.staticeIsPinwheelingDartboard = true, + }, + { + id: 'AAIS Statice Pinwheeling Dartboard Color', + type: 'AddedCombatant', + netRegex: { npcNameId: '12507' }, + durationSeconds: 6, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + dartOnYou: { + en: 'Dart (w/${player})', + }, + noDartOnYou: { + en: 'No Dart', + }, + blue: { + en: 'Avoid Blue', + }, + red: { + en: 'Avoid Red', + }, + yellow: { + en: 'Avoid Yellow', + }, + }; + + let infoText: string | undefined; + + const centerX = -200; + const centerY = 0; + const x = parseFloat(matches.x) - centerX; + const y = parseFloat(matches.y) - centerY; + + // 12 pie slices, the edge of the first one is directly north. + // It goes in B R Y order repeating 4 times. + // The 0.5 subtraction (12 - 0.5 = 11.5) is because the Homing Pattern + // lands directly in the middle of a slice. + const dir12 = Math.round(6 - 6 * Math.atan2(x, y) / Math.PI + 11.5) % 12; + + const colorOffset = dir12 % 3; + const colorMap: { [offset: number]: typeof data.staticeHomingColor } = { + 0: 'blue', + 1: 'red', + 2: 'yellow', + } as const; + + data.staticeHomingColor = colorMap[colorOffset]; + if (data.staticeHomingColor !== undefined) + infoText = output[data.staticeHomingColor]!(); + + if (data.staticeDart.length !== 2) + return { infoText }; + + const dartTargets = data.staticeDart.map((x) => x.target); + if (!dartTargets.includes(data.me)) + return { alertText: output.noDartOnYou!(), infoText: infoText }; + + const [target1, target2] = dartTargets; + if (target1 === undefined || target2 === undefined) + return { infoText }; + const otherTarget = data.party.member(target1 === data.me ? target2 : target1); + return { alertText: output.dartOnYou!({ player: otherTarget }), infoText: infoText }; + }, + }, + { + id: 'AAIS Statice Pinwheeling Dartboard Mech', + type: 'HeadMarker', + netRegex: { id: headmarkerIds.tethers }, + condition: (data) => data.staticeIsPinwheelingDartboard, + delaySeconds: (data, matches) => { + data.staticeDartboardTether.push(matches); + return data.staticeDartboardTether.length === 2 ? 0 : 0.5; + }, + alertText: (data, _matches, output) => { + if (data.staticeDartboardTether.length !== 2) + return; + + const tethers = data.staticeDartboardTether.map((x) => x.target); + + if (tethers.includes(data.me)) { + const [tether1, tether2] = tethers; + const other = data.party.member(tether1 === data.me ? tether2 : tether1); + return output.tether!({ player: other }); + } + + const partyNames = data.party.partyNames; + const nonTethers = partyNames.filter((x) => !tethers.includes(x)); + const [stack1, stack2] = nonTethers; + const other = data.party.member(stack1 === data.me ? stack2 : stack1); + return output.stack!({ player: other }); + }, + run: (data) => data.staticeDartboardTether = [], + outputStrings: { + // TODO: maybe this should remind you of dart color + tether: { + en: 'Tether w/${player}', + }, + stack: { + en: 'Stack w/${player}', + }, + }, + }, + ], + timelineReplace: [ + { + locale: 'en', + replaceText: { + 'Hydrobullet/Hydrofall': 'Hydrobullet/fall', + 'Hydrofall/Hydrobullet': 'Hydrofall/bullet', + 'Receding Twintides/Encroaching Twintides': 'Receding/Encroaching Twintides', + 'Far Tide/Near Tide': 'Far/Near Tide', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.txt b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.txt new file mode 100644 index 0000000000..dc01e052af --- /dev/null +++ b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.txt @@ -0,0 +1,429 @@ +# This file was autogenerated from running ts-node util/sync_files.ts. +# DO NOT EDIT THIS FILE DIRECTLY. +# Edit the source file below and then run `npm run sync-files` +# Source: ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt + +### ANOTHER ALOALO ISLAND +# ZoneId: 49B + +hideall "--Reset--" +hideall "--sync--" + +# .*is no longer sealed +0.0 "--Reset--" SystemLogMessage { id: "7DE" } window 100000 jump 0 + +#~~~~~~~~~~# +# KETUDUKE # +#~~~~~~~~~~# + +# -p 8AD4:1015.2 +# -ii 8AA7 8AED 8ADD 8AB9 8AB6 8AEB 8ABE 8ABF 8AE5 8AE6 8AC0 8ADA 8AD9 8AE4 8AC8 8AE3 8A82 8A83 + +# The Dawn Trial will be sealed off +1000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1146" } window 10000,0 +1010.2 "--sync--" StartsUsing { id: "8AD4", source: "Ketuduke" } window 20,20 +1015.2 "Tidal Roar" Ability { id: "8AD4", source: "Ketuduke" } + +1022.0 "--middle--" Ability { id: "8A77", source: "Ketuduke" } +1027.0 "Spring Crystals 1" Ability { id: "8AA8", source: "Ketuduke" } +1034.0 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1041.0 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8AB8|8AB4)", source: "Ketuduke" } +1050.0 "Fluke Gale (cast)" Ability { id: "8AB1", source: "Ketuduke" } +1060.0 "Fluke Gale 1" Ability { id: "8AB2", source: "Ketuduke" } +1062.0 "Fluke Gale 2" Ability { id: "8AB3", source: "Ketuduke" } +1065.0 "Saturate" Ability { id: "8ADC", source: "Spring Crystal" } +1065.1 "Hydrobullet/Hydrofall" Ability { id: "(8ADF|8ADE)", source: "Ketuduke" } + +1074.0 "Hydrofall/Hydrobullet (buff)" Ability { id: "(8AB4|8AB8)", source: "Ketuduke" } +1077.1 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8C6D|8AB5)", source: "Ketuduke" } +1084.3 "Blowing Bubbles" Ability { id: "8ABD", source: "Ketuduke" } +1091.4 "Hydrobomb x3" Ability { id: "8AD0", source: "Ketuduke" } +1096.3 "Hydrofall/Hydrobullet" Ability { id: "(8ADE|8ADF)", source: "Ketuduke" } +1099.6 "Hydrobomb x3" Ability { id: "8AD0", source: "Ketuduke" } +1102.5 "Hydrobullet/Hydrobullet" Ability { id: "(8ADF|8ADE)", source: "Ketuduke" } + +1113.6 "Hydrofall (buff)" Ability { id: "8AB4", source: "Ketuduke" } +1118.9 "Strewn Bubbles" Ability { id: "8ABB", source: "Ketuduke" } +1130.3 "Receding Twintides/Encroaching Twintides" Ability { id: "(8AE7|8AE9)", source: "Ketuduke" } +1130.9 "Sphere Shatter 1" Ability { id: "8AE0", source: "Ketuduke" } +1133.4 "Far Tide/Near Tide" Ability { id: "(8AEA|8AE8)", source: "Ketuduke" } +1134.0 "Sphere Shatter 2" Ability { id: "8AE0", source: "Ketuduke" } +1134.0 "Hydrofall" Ability { id: "8ADE", source: "Ketuduke" } + +1143.7 "Hydrobullet (buff)" Ability { id: "8AB8", source: "Ketuduke" } +1149.7 "Roar" Ability { id: "8AC4", source: "Ketuduke" } +1154.6 "Spring Crystals 2" Ability { id: "8AA8", source: "Ketuduke" } +1164.6 "Bubble Net" Ability { id: "8AC5", source: "Ketuduke" } +1177.0 "Hydrobullet" Ability { id: "8ADF", source: "Ketuduke" } +1177.6 "Saturate" Ability { id: "8ADC", source: "Spring Crystal" } +1182.7 "Updraft" Ability { id: "8AC7", source: "Ketuduke" } +1184.1 "Hundred Lashings" Ability { id: "8ACA", source: "Aloalo Zaratan" } + +1190.7 "--middle--" Ability { id: "8A77", source: "Ketuduke" } +1199.4 "Hydrofall/Hydrobullet (buff)" Ability { id: "(8AB4|8AB8)", source: "Ketuduke" } +1202.4 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8C6D|8AB5)", source: "Ketuduke" } +1210.4 "Angry Seas" Ability { id: "8AE1", source: "Ketuduke" } +1211.6 "Hydrofall/Hydrobullet" Ability { id: "(8ADE|8ADF)", source: "Ketuduke" } +1212.2 "Hydrobullet/Hydrobullet" Ability { id: "(8ADF|8ADE)" } # this sometimes has the wrong source +1214.8 "Spring Crystals 3" Ability { id: "8AA8", source: "Ketuduke" } +1216.8 "Hydrobullet" Ability { id: "8ADF", source: "Ketuduke" } +1221.8 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1227.8 "Fluke Typhoon (cast)" Ability { id: "8AAF", source: "Ketuduke" } +1233.8 "Fluke Typhoon" Ability { id: "8AB0", source: "Ketuduke" } +1236.4 "Saturate" Ability { id: "8ADB", source: "Spring Crystal" } +1238.8 "Burst x4" Ability { id: "8AE2", source: "Ketuduke" } + +1246.7 "Spring Crystals 4" Ability { id: "8AA8", source: "Ketuduke" } +1253.7 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1260.7 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8AB8|8AB4)", source: "Ketuduke" } +1269.8 "Fluke Gale (cast)" Ability { id: "8AB1", source: "Ketuduke" } +1279.9 "Fluke Gale 1" Ability { id: "8AB2", source: "Ketuduke" } +1281.9 "Fluke Gale 2" Ability { id: "8AB3", source: "Ketuduke" } +1284.9 "Saturate" Ability { id: "8ADC", source: "Spring Crystal" } +1285.0 "Hydrobullet/Hydrofall" Ability { id: "(8ADF|8ADE)", source: "Ketuduke" } + +1293.9 "Hydrofall (buff)" Ability { id: "8AB4", source: "Ketuduke" } +1299.2 "Strewn Bubbles" Ability { id: "8ABB", source: "Ketuduke" } +1310.6 "Receding Twintides/Encroaching Twintides" Ability { id: "(8AE7|8AE9)", source: "Ketuduke" } +1311.2 "Sphere Shatter 1" Ability { id: "8AE0", source: "Ketuduke" } +1313.7 "Far Tide/Near Tide" Ability { id: "(8AEA|8AE8)", source: "Ketuduke" } +1314.3 "Sphere Shatter 2" Ability { id: "8AE0", source: "Ketuduke" } +1314.3 "Hydrofall" Ability { id: "8ADE", source: "Ketuduke" } +1325.0 "Tidal Roar" Ability { id: "8AD4", source: "Ketuduke" } + +1331.8 "--sync--" StartsUsing { id: "TODO", source: "Ketuduke" } +1341.8 "Tidal Roar Enrage" Ability { id: "TODO", source: "Ketuduke" } + +# ALL ENCOUNTER ABILITIES +# 8A77 --sync-- Ketuduke repositioning +# 8A82 Riptide ability on players from Angry Seas Airy Bubble when you step in one +# 8A83 Fetters ability on players from Angry Seas Airy Bubble when you step in one after 8A82 Riptide +# 8AA7 --sync-- auto damage from Ketuduke +# 8AA8 Spring Crystals cast and ability to summon Spring Crystal adds (all flavors) +# 8AD9 衝撃 self-targeted ability from Spring Crystal orbs +# 8ADA 衝撃 self-targeted ability from Spring Crystal rupees +# 8ADB Saturate cast and damage from Spring Crystal orb circle +# 8ADC Saturate cast and damage from Spring Crystal rupee line laser +# 8AAD Bubble Net self-targeted cast before Bubbles along with 8ADD during Spring Crystals 1 +# 8ADD Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 1 +# 8AAF Fluke Typhoon self-targeted cast before 8AB0 knockback during Spring Crystals 3 +# 8AB0 Fluke Typhoon cast and knockback ability on Spring Crystal and players during Spring Crystals 3 +# 8AB1 Fluke Gale self-targeted cast that adds limit cut winds +# 8AB2 Fluke Gale cast and ability for limit cut 1 wind +# 8AB3 Fluke Gale cast and ability for limit cut 2 wind +# 8AB4 Hydrofall self-targeted cast that adds stack markers +# 8AB5 Hydrofall self-targeted "stack second" ability before Blowing Bubbles +# 8AB6 Hydrofall ability on players that adds stack debuffs +# 8ADE Hydrofall damage from stack debuffs +# 8AB8 Hydrobullet self-targeted cast that adds stack markers +# 8AB9 Hydrobullet ability on players that adds spread debuffs +# 8ADF Hydrobullet damage from spread debuffs +# 8ABB Strewn Bubbles self-targeted cast before 8AE0 Sphere Shatter moving arches +# 8AE0 Sphere Shatter damage from moving arches +# 8ABD Blowing Bubbles self-targeted cast that adds Airy Bubble Adds +# 8ABE Riptide ability on players from Blowing Bubbles Airy Bubble when you step in one +# 8ABF Fetters ability on players from Blowing Bubbles Airy Bubble when you step in one after 8ABE Riptide +# 8AC0 Angry Seas self-targeted cast for 8AE1 red line knockback +# 8AE1 Angry Seas cast and knockback damage from red line +# 8AE2 Burst tower damage +# 8AE3 Big Burst tower failure damage +# 8AC4 Roar self-targeted cast that summons Zaratan adds +# 8AC5 Bubble Net self-targeted cast before Bubbles along with 8AE4 during Spring Crystals 2 +# 8AE4 Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 2 +# 8AC7 Updraft self-targeted cast to boost adds and players into the air +# 8AC8 Updraft ability on players for 8AC7 Updraft +# 8AE5 Hundred Lashings cast and damage for non-bubbled Zaratan 180 cleave (no damage on bubbled players) +# 8ACA Hundred Lashings self-targeted cast for bubbled Zaratan adds +# 8AE6 Hundred Lashings cast and damage for bubbled Zaratan 180 cleave (no damage on non-bubbled players) +# 8AE7 Receding Twintides cast and damage for initial out during out->in +# 8AE8 Near Tide fast cast and damage for second out during in->out with 8AE9 Encroaching Twintides +# 8AE9 Encroaching Twintides cast and damage for initial in during in->out +# 8AEA Far Tide fast cast and damage for second in during out->in with 8AE7 Receding Twintides +# 8AD0 Hydrobomb self-targeted cast for 8AEB puddles +# 8AEB Hydrobomb cast and damage for 3x puddles duruing 8ABD Blowing Bubbles +# 8AD4 Tidal Roar self-targeted cast for raidwide aoe +# 8AED Tidal Roar damage from 8AD4 +# TODO Tidal Roar cast and enrage damage +# 8C6D Hydrobullet self-targeted "spread second" ability before Blowing Bubbles + + +#~~~~~~# +# LALA # +#~~~~~~# + +# -p 8C05:2011.1 +# -ii 8BE6 8BE9 8CE1 8BF1 8BF2 8BFF 8BF3 8BF4 + +# The Dusk Trial will be sealed off +2000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1147" } window 10000,0 +2006.1 "--sync--" StartsUsing { id: "8C05", source: "Lala" } window 20,20 +2011.1 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } + +2013.2 "--middle--" Ability { id: "8874", source: "Lala" } +2018.3 "Angular Addition" Ability { id: "(8BE0|8D2F)", source: "Lala" } +2026.4 "Arcane Blight" Ability { id: "(8BE2|8BE3|8BE4|8BE5)", source: "Lala" } + +2029.6 "--middle--" Ability { id: "8874", source: "Lala" } +2034.7 "Analysis" Ability { id: "8BEC", source: "Lala" } +2039.8 "Arcane Array 1" Ability { id: "8BE7", source: "Lala" } +2044.9 "Angular Addition" Ability { id: "(8BE0|8D2F)", source: "Lala" } +2045.6 "Bright Pulse" Ability { id: "8BE8", source: "Lala" } +2046.9 "Radiance 1" Ability { id: "8BEB", source: "Arcane Globe" } +2053.0 "Arcane Blight" Ability { id: "(8BE2|8BE3|8BE4|8BE5)", source: "Lala" } +# This can be +1.2s if it's on the final square instead of penultimate. +2054.1 "Radiance 2" #Ability { id: "8BEB", source: "Arcane Globe" } +2061.2 "Targeted Light" Ability { id: "8CE0", source: "Lala" } + +2072.5 "Strategic Strike" Ability { id: "8C04", source: "Lala" } + +2085.6 "Planar Tactics" Ability { id: "8BEF", source: "Lala" } +2100.7 "Arcane Mine" Ability { id: "8BF0", source: "Lala" } +2108.6 "Symmetric Surge x2" Ability { id: "8BF5", source: "Lala" } + +2112.8 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } +2122.9 "Strategic Strike" Ability { id: "8C04", source: "Lala" } + +2131.1 "--middle--" Ability { id: "8874", source: "Lala" } +2138.3 "Spatial Tactics" Ability { id: "8BF7", source: "Lala" } +2143.4 "Arcane Array 2" Ability { id: "8BF6", source: "Lala" } +2149.2 "Bright Pulse" Ability { id: "8BE8", source: "Lala" } +2150.5 "Inferno Divide 1" Ability { id: "8BEA", source: "Arcane Font" } window 1,1 +2150.5 "Radiance" Ability { id: "8D20", source: "Arcane Globe" } +2152.9 "Inferno Divide 2" Ability { id: "8BEA", source: "Arcane Font" } window 1,1 +2156.6 "Inferno Divide 3" Ability { id: "8BEA", source: "Arcane Font" } window 1,1 +2157.6 "Angular Addition" Ability { id: "(8BE0|8D2F)", source: "Lala" } +2157.8 "Inferno Divide 4" Ability { id: "8BEA", source: "Arcane Font" } window 1,1 +2160.2 "Inferno Divide 5" Ability { id: "8BEA", source: "Arcane Font" } window 1,1 +2165.6 "Arcane Blight" Ability { id: "(8BE2|8BE3|8BE4|8BE5)", source: "Lala" } + +2173.7 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } +2184.8 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } + +2196.9 "Symmetric Surge" Ability { id: "8BF8", source: "Lala" } +2202.0 "Constructive Figure" Ability { id: "8BFA", source: "Lala" } +2207.1 "Arcane Plot" Ability { id: "8BF9", source: "Lala" } +2212.9 "Bright Pulse" Ability { id: "8BE8", source: "Lala" } +2221.2 "Arcane Point" Ability { id: "8BFC", source: "Lala" } +2221.8 "Aero II" Ability { id: "8BFB", source: "Aloalo Golem" } +2222.0 "Powerful Light" Ability { id: "8BFD", source: "Lala" } +2230.3 "Explosive Theorem" Ability { id: "8BFE", source: "Lala" } +2234.7 "Symmetric Surge x2" Ability { id: "8BF5", source: "Lala" } +2235.4 "Telluric Theorem" Ability { id: "8C00", source: "Lala" } + +2248.4 "Strategic Strike" Ability { id: "8C04", source: "Lala" } +2256.5 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } + +2263.6 "--middle--" Ability { id: "8874", source: "Lala" } +2269.0 "Analysis" Ability { id: "8BEC", source: "Lala" } +2274.1 "Arcane Array 3" Ability { id: "8BE7", source: "Lala" } +2279.2 "Angular Addition" Ability { id: "(8BE0|8D2F)", source: "Lala" } +2279.9 "Bright Pulse" Ability { id: "8BE8", source: "Lala" } +2281.2 "Radiance 1" Ability { id: "8BEB", source: "Arcane Globe" } +2287.3 "Arcane Blight" Ability { id: "(8BE2|8BE3|8BE4|8BE5)", source: "Lala" } +2290.9 "Radiance 2" Ability { id: "8BEB", source: "Arcane Globe" } +2295.6 "Targeted Light" Ability { id: "8CE0", source: "Lala" } +2306.7 "Strategic Strike" Ability { id: "8C04", source: "Lala" } + +2316.8 "Inferno Theorem" Ability { id: "8C05", source: "Lala" } + +2319.9 "--sync--" StartsUsing { id: "TODO", source: "Lala" } window 20,20 +2329.9 "Inferno Theorem Enrage" Ability { id: "TODO", source: "Lala" } + + +# ALL ENCOUNTER ABILITIES +# 368 attack auto damage from Lala +# 8874 --sync-- repositioning for Lala +# 8BE0 Angular Addition self-targeted ability to give boss III +# 8BE2 Arcane Blight self-targeted cast for initial back-safe 270 degree rotating cleave +# 8BE3 Arcane Blight self-targeted cast for initial front-safe 270 degree rotating cleave +# 8BE4 Arcane Blight self-targeted cast for initial east-safe 270 degree rotating cleave +# 8BE5 Arcane Blight self-targeted cast for initial west-safe 270 degree rotating cleave +# 8BE6 Arcane Blight cast and damage from 270 degree rotating cleave +# 8BE7 Arcane Array self-targeted cast to summon moving blue squares (#1) +# 8BE8 Bright Pulse cast and damage for initial blue square +# 8BE9 Bright Pulse damage from moving blue square +# 8BEA Inferno Divide orange square cross explosion damage during Spatial Tactics +# 8BEB Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #1, #3) +# 8BEC Analysis self-targeted cast before giving players +# 8BEF Planar Tactics self-targeted cast before Arcane Mines +# 8BF0 Arcane Mine self-targeted cast to create 8 Arcane Mine squares +# 8BF1 Arcane Mine cast and damage for initial Arcane Mine squares +# 8BF2 Arcane Combustion damage from walking over an Arcane Mine +# 8BF3 Massive Explosion damage from failing to resolve Subractive Suppressor Alpha +# 8BF4 Massive Explosion damage from failing to resolve Subractive Suppressor Beta +# 8BF5 Symmetric Surge damage from two person stack that gives magic vuln up +# 8BF6 Arcane Array self-targeted cast to summon moving blue squares (#2) +# 8BF7 Spatial Tactics self-targeted cast prior to Arcane Array 2 +# 8BF8 Symmetric Surge self-targeted cast before this mechanic +# 8BF9 Arcane Plot self-targeted cast to summon blue squares for Symmetric Surge +# 8BFA Constructive Figure self-targeted cast that summons Aloalo Golem on edge +# 8BFB Aero II cast and line damage from Aloalo Golem during Symmetric Surge +# 8BFC Arcane Point self-targeted cast that gives players 8BFD Powerful Light spreads +# 8BFD Powerful Light spread damage on players that turn the squares they are on blue +# 8BFE Explosive Theorem self-targeted cast for very large spreads +# 8BFF Explosive Theorem cast and damage on players for spreads with Telluric Theorem puddles +# 8C00 Telluric Theorem cast and damage for large puddles from Explosive Theorem +# 8C04 Strategic Strike cast and damage for non-cleaving 3x tankbuster +# 8C05 Inferno Theorem cast and raidwide damage +# TODO Inferno Theorem cast and enrage damage +# 8CE0 Targeted Light self-targeted cast for weak spot boss tether +# 8CE1 Targeted Light cast and damage on players for 8CE0 +# 8D20 Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #2) +# 8D2F Angular Addition self-targeted ability to give boss V + + +#~~~~~~~~~~~~~~~~~~~~# +# STATICE WITH A GUN # +#~~~~~~~~~~~~~~~~~~~~# + +# -p 8966:3013.8 +# -ii 8964 8925 8926 8977 8978 8969 8988 8A6A 897C 897D 8CC3 896F 8CC1 89FB TODO 8974 8975 +# -it Statice + +# Midnight Trial will be sealed off +3000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1148" } window 10000,0 +3008.8 "--sync--" StartsUsing { id: "8966", source: "Statice" } window 20,20 +3013.8 "Aero IV" Ability { id: "8966", source: "Statice" } + +3018.0 "--middle--" Ability { id: "8927", source: "Statice" } +3023.4 "Trick Reload" Ability { id: "8967", source: "Statice" } +3038.2 "Trapshooting 1" Ability { id: "8D1C", source: "Statice" } +3048.6 "Trigger Happy" Ability { id: "8968", source: "Statice" } +3056.5 "Ring a Ring o' Explosions" Ability { id: "8979", source: "Statice" } +3070.6 "Trapshooting 2" Ability { id: "8976", source: "Statice" } +3074.6 "Burst" Ability { id: "897A", source: "Bomb" } + +3081.8 "--middle--" Ability { id: "8927", source: "Statice" } +3087.4 "Trick Reload" Ability { id: "8967", source: "Statice" } +3101.2 "Ring a Ring o' Explosions" Ability { id: "8979", source: "Statice" } +3106.3 "Dartboard of Dancing Explosives " Ability { id: "8CC0", source: "Statice" } +3120.1 "Trapshooting 1" Ability { id: "8976", source: "Statice" } +3122.0 "Burst" Ability { id: "897A", source: "Bomb" } +3125.3 "Uncommon Ground" Ability { id: "8971", source: "Statice" } + +3132.3 "--middle--" Ability { id: "8927", source: "Statice" } +3137.7 "Surprise Balloon" Ability { id: "8927", source: "Statice" } +3144.8 "Beguiling Glitter" Ability { id: "8980", source: "Statice" } +3149.8 "Surprise Needle 1" #Ability { id: "896C", source: "Needle" } +3150.7 "Pop 1" Ability { id: "896B", source: "Statice" } +3151.5 "Surprise Needle 2" #Ability { id: "896C", source: "Needle" } +3152.4 "Trigger Happy" Ability { id: "8968", source: "Statice" } +3153.2 "Surprise Needle 3" #Ability { id: "896C", source: "Needle" } +3154.9 "Surprise Needle 4" #Ability { id: "896C", source: "Needle" } +3155.6 "Pop 2" Ability { id: "896B", source: "Statice" } +3160.4 "Trapshooting 2" Ability { id: "8976", source: "Statice" } +3172.5 "Aero IV" Ability { id: "8966", source: "Statice" } + +3179.7 "--untargetable--" +3179.7 "--middle--" Ability { id: "8927", source: "Statice" } +3184.1 "Ring a Ring o' Explosions" Ability { id: "8979", source: "Statice" } +3189.2 "Present Box 1" Ability { id: "8972", source: "Statice" } +3194.3 "Fireworks (cast)" Ability { id: "897B", source: "Statice" } +3204.0 "Fireworks" Ability { id: "897C", source: "Statice" } +3204.2 "Burst" Ability { id: "897A", source: "Bomb" } +3204.3 "Faerie Ring" Ability { id: "8973", source: "Surprising Staff" } + +3206.3 "--targetable--" +3212.5 "Shocking Abandon" Ability { id: "8965", source: "Statice" } + +3222.7 "Pinwheeling Dartboard" Ability { id: "8CBF", source: "Statice" } +3231.4 "Fireworks (cast)" Ability { id: "897B", source: "Statice" } +3233.4 "Fire Spread" Ability { id: "8952", source: "Statice" } duration 11 +3241.3 "Fireworks" Ability { id: "897C", source: "Statice" } +3241.6 "Uncommon Ground" Ability { id: "8971", source: "Statice" } +3253.6 "Aero IV" Ability { id: "8966", source: "Statice" } + +3260.8 "--middle--" Ability { id: "8927", source: "Statice" } +3266.5 "Beguiling Glitter" Ability { id: "8980", source: "Statice" } +3273.6 "Trick Reload" Ability { id: "8967", source: "Statice" } +3288.4 "Trapshooting 1" Ability { id: "8D1C", source: "Statice" } +3295.5 "Present Box 2" Ability { id: "8972", source: "Statice" } +3302.2 "Ring a Ring o' Explosions" Ability { id: "8979", source: "Statice" } +3309.6 "Trigger Happy" Ability { id: "8968", source: "Statice" } +3310.2 "Faerie Ring" Ability { id: "8973", source: "Surprising Staff" } +3316.5 "Trapshooting 2" Ability { id: "8976", source: "Statice" } +3320.4 "Burst" Ability { id: "897A", source: "Bomb" } + +3327.6 "Aero IV" Ability { id: "8966", source: "Statice" } +3335.7 "Aero IV" Ability { id: "8966", source: "Statice" } + +3338.8 "--sync--" StartsUsing { id: "TODO", source: "Statice" } +3348.8 "Aero IV Enrage" Ability { id: "TODO", source: "Statice" } + + +# ALL ENCOUNTER ABILITIES +# 8925 Locked and Loaded ability during 8967 Trick Reload when a bullet is in the gun +# 8926 Misload ability during 8967 Trick Reload when a bullet missed the gun oops +# 8927 --sync-- repositioning from Statice +# 8964 --sync-- auto damage from Statice +# 8965 Shocking Abandon cast and tankbuster damage +# 8966 Aero IV cast and raidwide damage +# 8967 Trick Reload self-targeted cast to load gun with 8925/8926 +# 8968 Trigger Happy self-targeted cast for limit cut dart board +# 8969 Trigger Happy cast and damage for limit cut dart board (filled pie slice) +# 8927 Surprise Balloon self-targeted cast +# 896B Pop knockback from Surprise Balloon being popped +# 896C Surprise Needle short cast and ability blue line aoe from needle adds that pop balloons +# 8971 Uncommon Ground light damage on people who are not on a dartboard color with Bull's-eye +# 8972 Present Box self-targeted cast for bombs/donuts/missiles/hands +# 8973 Faerie Ring cast and damage for donut rings during Present Box +# 8974 Burst high damage from running into Surprising Missile tethered add +# 8975 Death by Claw high damage from running into Surprising Claw tethered add +# 8976 Trapshooting self-targeted cast after Trick Reload (some instances are 8D1C) +# 8977 Trapshooting stack damage from Trick Reload +# 8978 Trapshooting spread damage from Trick Reload +# 8979 Ring a Ring o' Explosions self-targeted cast for rotating bombs +# 897A Burst cast and damage from bomb explosion +# 897B Fireworks self-targeted cast +# 897C Fireworks two person stack damage during Present Box / Pinwheeling Dartboard +# 897D Fireworks spread damage during Present Box / Pinwheeling Dartboard +# 8980 Beguiling Glitter self-targeted cast to give players Face debuffs +# 896F Fire Spread self-targeted damage for initial rotating fire (from Ball of Fire) +# 8988 Trigger Happy cast and zero damage for limit cut dart board (empty pie slice) +# 89FB Fire Spread ongoing rotating fire damage (from Statice) +# 8A6A --sync-- ability on Bomb when rotating +# TODO Aero IV cast and enrage damage +# TODO Aero IV post-enrage follow-up damage just in case +# 8CBF Pinwheeling Dartboard self-targeted cast to summon dartboard with rotating fire +# 8CC0 Dartboard of Dancing Explosives self-targeted cast for colored dartboard +# 8CC1 Burning Chains damage from not breaking chains +# 8CC3 Uncommon Ground heavy damage on people who are on the same dartboard color with Bull's-eye +# 8D1C Trapshooting self-targeted cast after Trick Reload (some instances are 8976) + + +#~~~~~~~~~# +# TRASH 1 # +#~~~~~~~~~# + +# ALL ENCOUNTER ABILITIES +# 7A56 --sync-- various auto damage (trash 1) +# 8BCF --sync-- damage from Twister tornados +# 8BC7 Lead Hook casted damage from Kiwakin 3x tankbuster +# 8BC6 Lead Hook damage from hit 2 +# 8BC4 Lead Hook damage from hit 3 +# 8BCC Water III casted damage from Snipper stack marker +# 8BC8 Sharp Strike casted damage from Kiwakin tank buster with a concussion dot +# 8BC9 Tail Screw casted damage from Kiwakin baited circle +# 8BCA Bubble Shower casted damage from Snipper front conal +# 8BCB Crab Dribble fast casted damage from Snipper back conal after Bubble Shower 8BCA +# 8C4B Hydrocannon casted damage from Ray front line +# 8BCE Expulsion casted damage from Ray "get out" +# 8BCD Electric Whorl casted damage from Ray "get in" +# 8BD1 Hydroshot casted damage from Monk knockback line with a dot +# 8C4F Cross Attack casted damage from Monk tankbuster + +#~~~~~~~~~# +# TRASH 2 # +#~~~~~~~~~# + +# TODO: does Wood Golem have an enrage?? + +# ALL ENCOUNTER ABILITIES +# 7A58 --sync-- various auto damage (trash 2) +# 8BD4 Ovation cast and damage from Wood Golem front line aoe +# 8C3A Gravity Force cast and stack damage from Islekeeper +# TODO Ancient Quaga cast and damage for Islekeeper raidwide enrage +# 8BD2 Ancient Aero III interruptable cast and damage for Wood Golem raidwide +# 8BD3 Tornado cast and damage from Wood Golem that binds the initial target and heavies all targets +# 8C39 Ancient Quaga cast and damage for Islekeeper raidwide +# 8C3C Isle Drop cast and damage for Islekeeper front circle diff --git a/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.ts b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.ts new file mode 100644 index 0000000000..2198ee6605 --- /dev/null +++ b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.ts @@ -0,0 +1,1524 @@ +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { PluginCombatantState } from '../../../../../types/event'; +import { NetMatches } from '../../../../../types/net_matches'; +import { TriggerSet } from '../../../../../types/trigger'; + +// TODO: add callout for Monk Hydroshot target +// TODO: sc3 should say which bubble to take to the other side (for everyone) +// TODO: figure out directions for Lala for Radiance orbs +// TODO: map effects for Lala +// TODO: Lala Planar Tactics could add config strats and tell you who to stack with +// TODO: Statice colors could add config strats for role-based colors + melee flex + +export interface Data extends RaidbossData { + combatantData: PluginCombatantState[]; + ketuSpringCrystalCount: number; + ketuCrystalAdd: NetMatches['AddedCombatant'][]; + ketuHydroBuffCount: number; + ketuHydroBuffIsSpreadFirst?: boolean; + ketuHydroBuffIsRoleStacks?: boolean; + ketuBuff?: 'bubble' | 'fetters'; + ketuBuffPartner?: string; + ketuBuffCollect: NetMatches['GainsEffect'][]; + ketuStackTargets: string[]; + ketuTwintidesNext?: 'out' | 'in'; + lalaBossRotation?: 'clock' | 'counter'; + lalaBossTimes?: 3 | 5; + lalaBossInitialSafe?: 'north' | 'east' | 'south' | 'west'; + lalaUnseen?: 'front' | 'left' | 'right' | 'back'; + lalaPlayerTimes?: 3 | 5; + lalaPlayerRotation?: 'clock' | 'counter'; + lalaSubAlpha: NetMatches['GainsEffect'][]; + staticeBullet: NetMatches['Ability'][]; + staticeTriggerHappy?: number; + staticePopTriggerHappyNum?: number; + staticeTrapshooting: ('stack' | 'spread' | undefined)[]; + staticeDart: NetMatches['GainsEffect'][]; + staticePresentBoxCount: number; + staticeMissileCollect: NetMatches['AddedCombatant'][]; + staticeMissileIdToDir: { [id: string]: number }; + staticeMissileTether: NetMatches['Tether'][]; + staticeClawTether: NetMatches['Tether'][]; + staticeIsPinwheelingDartboard?: boolean; + staticeHomingColor?: 'blue' | 'yellow' | 'red'; + staticeDartboardTether: NetMatches['HeadMarker'][]; +} + +// Horizontal crystals have a heading of 0, vertical crystals are -pi/2. +const isHorizontalCrystal = (line: NetMatches['AddedCombatant']) => { + const epsilon = 0.1; + return Math.abs(parseFloat(line.heading)) < epsilon; +}; + +const headmarkerIds = { + tethers: '0061', + enumeration: '015B', +} as const; + +// TODO: this maybe should be a method on party? +const isStandardLightParty = (data: Data): boolean => { + const supports = [...data.party.healerNames, ...data.party.tankNames]; + const dps = data.party.dpsNames; + return supports.length === 2 && dps.length === 2; +}; + +const triggerSet: TriggerSet = { + id: 'AnotherAloaloIsland', + zoneId: ZoneId.AnotherAloaloIsland, + timelineFile: 'another_aloalo_island.txt', + initData: () => { + return { + combatantData: [], + ketuSpringCrystalCount: 0, + ketuCrystalAdd: [], + ketuHydroBuffCount: 0, + ketuBuffCollect: [], + ketuStackTargets: [], + lalaSubAlpha: [], + staticeBullet: [], + staticeTrapshooting: [], + staticeDart: [], + staticePresentBoxCount: 0, + staticeMissileCollect: [], + staticeMissileIdToDir: {}, + staticeMissileTether: [], + staticeClawTether: [], + staticeDartboardTether: [], + }; + }, + timelineTriggers: [ + { + id: 'AAI Lala Radiance', + regex: /^Radiance \d/, + beforeSeconds: 4, + alertText: (data, _matches, output) => { + // TODO: could figure out directions here and say "Point left at NW Orb" + const dir = data.lalaUnseen; + if (dir === undefined) + return output.orbGeneral!(); + return { + front: output.orbDirFront!(), + back: output.orbDirBack!(), + left: output.orbDirLeft!(), + right: output.orbDirRight!(), + }[dir]; + }, + outputStrings: { + orbDirFront: { + en: 'Face Towards Orb', + }, + orbDirBack: { + en: 'Face Away from Orb', + }, + orbDirLeft: { + en: 'Point Left at Orb', + }, + orbDirRight: { + en: 'Point Right at Orb', + }, + orbGeneral: { + en: 'Point opening at Orb', + }, + }, + }, + ], + triggers: [ + // ---------------- first trash ---------------- + { + id: 'AAI Kiwakin Lead Hook', + type: 'StartsUsing', + netRegex: { id: '8C6E', source: 'Aloalo Kiwakin' }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankBusterOnYou: { + en: '3x Tankbuster on YOU', + }, + tankBusterOnPlayer: { + en: '3x Tankbuster on ${player}', + }, + }; + + if (matches.target === data.me) + return { alertText: output.tankBusterOnYou!() }; + const target = data.party.member(matches.target); + return { infoText: output.tankBusterOnPlayer!({ player: target }) }; + }, + }, + { + id: 'AAI Kiwakin Sharp Strike', + type: 'StartsUsing', + netRegex: { id: '8C63', source: 'Aloalo Kiwakin' }, + response: Responses.tankBuster(), + }, + { + id: 'AAI Kiwakin Tail Screw', + type: 'StartsUsing', + // This is a baited targeted circle. + netRegex: { id: '8BB8', source: 'Aloalo Kiwakin', capture: false }, + response: Responses.moveAway(), + }, + { + id: 'AAI Snipper Water III', + type: 'StartsUsing', + netRegex: { id: '8C64', source: 'Aloalo Snipper' }, + response: Responses.stackMarkerOn(), + }, + { + id: 'AAI Snipper Bubble Shower', + type: 'StartsUsing', + netRegex: { id: '8BB9', source: 'Aloalo Snipper', capture: false }, + response: Responses.getBackThenFront(), + }, + { + id: 'AAI Snipper Crab Dribble', + type: 'Ability', + // Crab Dribble 8BBA has a fast cast, so trigger on Bubble Shower ability + netRegex: { id: '8BB9', source: 'Aloalo Snipper', capture: false }, + suppressSeconds: 5, + response: Responses.goFront('info'), + }, + { + id: 'AAI Ray Hydrocannon', + type: 'StartsUsing', + netRegex: { id: '8BBD', source: 'Aloalo Ray', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'AAI Ray Expulsion', + type: 'StartsUsing', + netRegex: { id: '8BBF', source: 'Aloalo Ray', capture: false }, + response: Responses.getOut(), + }, + { + id: 'AAI Ray Electric Whorl', + type: 'StartsUsing', + netRegex: { id: '8BBE', source: 'Aloalo Ray', capture: false }, + response: Responses.getUnder(), + }, + { + id: 'AAI Monk Hydroshot', + type: 'StartsUsing', + netRegex: { id: '8BBE', source: 'Aloalo Monk' }, + condition: Conditions.targetIsYou(), + response: Responses.knockbackOn(), + }, + { + id: 'AAI Monk Cross Attack', + type: 'StartsUsing', + netRegex: { id: '8BBB', source: 'Aloalo Monk' }, + response: Responses.tankBuster(), + }, + // ---------------- Ketuduke ---------------- + { + id: 'AAI Ketuduke Tidal Roar', + type: 'StartsUsing', + netRegex: { id: '8AD4', source: 'Ketuduke', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'AAI Ketuduke Spring Crystals', + type: 'StartsUsing', + netRegex: { id: '8AA8', source: 'Ketuduke', capture: false }, + run: (data) => { + data.ketuSpringCrystalCount++; + data.ketuCrystalAdd = []; + }, + }, + { + id: 'AAI Ketuduke Spring Crystal Collect', + type: 'AddedCombatant', + netRegex: { npcNameId: '12607' }, + run: (data, matches) => data.ketuCrystalAdd.push(matches), + }, + { + id: 'AAI Ketuduke Bubble Net', + type: 'StartsUsing', + netRegex: { id: ['8AC5', '8AAD'], source: 'Ketuduke', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAI Ketuduke Foamy Fetters Bubble Weave', + type: 'GainsEffect', + // ECC = Foamy Fetters + // E9F = Bubble Weave + netRegex: { effectId: ['ECC', 'E9F'] }, + delaySeconds: (data, matches) => { + data.ketuBuffCollect.push(matches); + return data.ketuBuffCollect.length === 4 ? 0 : 0.5; + }, + alertText: (data, _matches, output) => { + if (data.ketuBuffCollect.length === 0) + return; + + const myBuff = data.ketuBuffCollect.find((x) => x.target === data.me)?.effectId; + if (myBuff === undefined) + return; + data.ketuBuff = myBuff === 'ECC' ? 'fetters' : 'bubble'; + data.ketuBuffPartner = data.ketuBuffCollect.find((x) => { + return x.target !== data.me && x.effectId === myBuff; + })?.target; + const player = data.party.member(data.ketuBuffPartner); + + // To avoid too many calls, we'll call this out later for the Fluke Gale + // versions of this. + if (data.ketuSpringCrystalCount === 1 || data.ketuSpringCrystalCount === 4) + return; + + if (data.ketuBuff === 'fetters') + return output.fetters!({ player: player }); + return output.bubble!({ player: player }); + }, + run: (data) => data.ketuBuffCollect = [], + outputStrings: { + fetters: { + en: 'Fetters (w/${player})', + }, + bubble: { + en: 'Bubble (w/${player})', + }, + }, + }, + { + id: 'AAI Ketuduke Hydro Buff Counter', + type: 'StartsUsing', + // 8AB8 = Hydrobullet (spread) + // 8AB4 = Hydrofall (stack) + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke', capture: false }, + run: (data) => { + data.ketuHydroBuffCount++; + delete data.ketuHydroBuffIsSpreadFirst; + delete data.ketuHydroBuffIsRoleStacks; + }, + }, + { + id: 'AAI Ketuduke Hydro Buff 1', + comment: { + en: `These directions assume that you always pick a square in the same + quadrant as the crystal specified. + For brevity, "next to" always means horizontal east/west of something. + The number in parentheses is the limit cut wind you should be on. + See trigger source for diagrams in comments.`, + }, + type: 'StartsUsing', + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke' }, + condition: (data) => data.ketuHydroBuffCount === 1 || data.ketuHydroBuffCount === 6, + durationSeconds: 8, + alertText: (data, matches, output) => { + if (data.ketuBuff === undefined) + return; + + const isPlayerDPS = data.party.isDPS(data.me); + const isPartnerDPS = data.ketuBuffPartner !== undefined + ? data.party.isDPS(data.ketuBuffPartner) + : undefined; + const isBubbleNetPartnerSameRole = isPlayerDPS === isPartnerDPS && + isStandardLightParty(data); + + // Simplify callout in vast majority of cases where there's a normal light party setup + // and you and the two dps and two supports get the same debuff, so no need to list + // your partner. + // + // Otherwise, if you're doing this nonstandard for some reason or somebody is dead + // you can know if you need to flex. + const isSpread = matches.id === '8AB8'; + const bubbleStr = data.ketuBuff === 'bubble' ? output.bubbleBuff!() : output.fettersBuff!(); + // We don't know about role stacks at this point, as this is just the initial cast bar. + const stackStr = isSpread ? output.spread!() : output.stacks!(); + if (isBubbleNetPartnerSameRole || data.ketuBuffPartner === undefined) + return output.bubbleNetMech!({ fettersBubble: bubbleStr, spreadStack: stackStr }); + return output.bubbleNetMechPartner!({ + fettersBubble: bubbleStr, + spreadStack: stackStr, + player: data.party.member(data.ketuBuffPartner), + }); + }, + infoText: (data, matches, output) => { + // If somebody died and missed a debuff, good luck. + if (data.ketuBuff === undefined) + return; + + // Bubble always does the same thing. + if (data.ketuBuff === 'bubble') + return output.bubbleAnything!(); + + // Two layouts, one with each crystal in its own column ("split") + // and one with two columns that have an H and a V in that same column ("columns"). + // Wind doesn't matter, as "1" will always be on the horizontal crystals. + // These can be flipped somewhat, but the solution is always the same. + // + // STACK FETTERS COLUMNS (kitty to horizontal) + // 2 2 + // + - - - - + + - - - - + + // | V f | 1 | H f | 1 + // | H b | => | V | + // | H b | | V | + // 1 | f V | 1 | H f | + // + - - - - + + - - - - + + // 2 2 + // + // STACK FETTERS SPLIT (on horizontal) + // 2 2 + // + - - - - + + - - - - + + // 1 | V | 1 | V | + // | H b | => | f H | + // | V | | V | + // | b H | 1 | H f | 1 + // + - - - - + + - - - - + + // 2 2 + // + // SPREAD FETTERS COLUMNS (adjacent to vertical) + // 2 2 + // + - - - - + + - - - - + + // | V f | 1 | f H b | 1 + // | H b | => | V | + // | H b | | V | + // 1 | V f | 1 | H b f | + // + - - - - + + - - - - + + // 2 2 + // + // SPREAD FETTERS SPLIT (kitty to vertical) + // 2 2 + // + - - - - + + - - - - + + // | V | 1 | V | 1 + // | f b H | => | f H b | + // | V | | V | + // 1 | H b f | 1 | b H f | + // + - - - - + + - - - - + + // 2 2 + + const isSpread = matches.id === '8AB8'; + const horizontal = data.ketuCrystalAdd.filter((x) => isHorizontalCrystal(x)); + const vertical = data.ketuCrystalAdd.filter((x) => !isHorizontalCrystal(x)); + + const [firstHorizontal] = horizontal; + if (horizontal.length !== 2 || vertical.length !== 2 || firstHorizontal === undefined) + return; + const firstHorizX = parseFloat(firstHorizontal.x); + // It's split if no vertical is in the same column as either horizontal. + const isSplitLayout = + vertical.find((line) => Math.abs(parseFloat(line.x) - firstHorizX) < 1) === undefined; + + if (isSpread) + return isSplitLayout ? output.fettersSpreadSplit!() : output.fettersSpreadColumn!(); + return isSplitLayout ? output.fettersStackSplit!() : output.fettersStackColumn!(); + }, + outputStrings: { + bubbleNetMech: { + en: '${fettersBubble} + ${spreadStack}', + }, + bubbleNetMechPartner: { + en: '${fettersBubble} + ${spreadStack} (w/${player})', + }, + bubbleBuff: { + en: 'Bubble', + }, + fettersBuff: { + en: 'Fetters', + }, + spread: Outputs.spread, + stacks: { + en: 'Stacks', + }, + bubbleAnything: { + en: 'Next to Horizontal (1)', + }, + fettersSpreadSplit: { + en: 'Diagonal of Vertical (2)', + }, + fettersSpreadColumn: { + en: 'Next to Vertical (2)', + }, + fettersStackSplit: { + en: 'On Horizontal (1)', + }, + fettersStackColumn: { + en: 'Diagonal of Horizontal (1)', + }, + }, + }, + { + id: 'AAI Ketuduke Hydro Buff Double', + type: 'StartsUsing', + netRegex: { id: ['8AB8', '8AB4'], source: 'Ketuduke' }, + condition: (data) => data.ketuHydroBuffCount === 2 || data.ketuHydroBuffCount === 5, + alertText: (data, matches, output) => { + data.ketuHydroBuffIsSpreadFirst = matches.id === '8AB8'; + return data.ketuHydroBuffIsSpreadFirst ? output.spread!() : output.stacks!(); + }, + outputStrings: { + spread: { + en: 'Spread => Stacks', + }, + stacks: { + en: 'Stacks => Spread', + }, + }, + }, + { + id: 'AAI Ketuduke Hydro Buff Double Followup', + type: 'Ability', + netRegex: { id: ['8ABA', '8AB7'], source: 'Ketuduke' }, + suppressSeconds: 10, + infoText: (data, matches, output) => { + const wasSpread = matches.id === '8ABA'; + if (wasSpread && data.ketuHydroBuffIsSpreadFirst === true) { + if (data.ketuHydroBuffIsRoleStacks) + return output.roleStacks!(); + return output.stacks!(); + } else if (!wasSpread && data.ketuHydroBuffIsSpreadFirst === false) { + return output.spread!(); + } + }, + outputStrings: { + spread: Outputs.spread, + stacks: { + en: 'Stacks', + }, + roleStacks: { + en: 'Role Stacks', + }, + }, + }, + { + id: 'AAI Ketuduke Hydrofall Role Stack Warning', + type: 'GainsEffect', + netRegex: { effectId: 'EA3' }, + delaySeconds: (data, matches) => { + data.ketuStackTargets.push(matches.target); + return data.ketuStackTargets.length === 2 ? 0 : 0.5; + }, + alarmText: (data, _matches, output) => { + const [stack1, stack2] = data.ketuStackTargets; + if (data.ketuStackTargets.length !== 2 || stack1 === undefined || stack2 === undefined) + return; + + // Sorry, non-standard light party comps. + if (!isStandardLightParty(data)) + return; + + const isStack1DPS = data.party.isDPS(stack1); + const isStack2DPS = data.party.isDPS(stack2); + + // If both stacks are on dps or neither stack is on a dps, then you have + // standard "partner" stacks of one support and one dps. If one is on a dps + // and one is on a support (which can happen if somebody dies), then + // you (probably) need to have role stacks. + if (isStack1DPS === isStack2DPS) + return; + + data.ketuHydroBuffIsRoleStacks = true; + + // Handle Blowing Bubbles/Angry Seas spread+stack combo. + if (data.ketuHydroBuffIsSpreadFirst === true) + return output.spreadThenRoleStacks!(); + else if (data.ketuHydroBuffIsSpreadFirst === false) + return output.roleStacksThenSpread!(); + return output.roleStacks!(); + }, + run: (data) => data.ketuStackTargets = [], + outputStrings: { + roleStacks: { + en: 'Role Stacks', + }, + spreadThenRoleStacks: { + en: 'Spread => Role Stacks', + }, + roleStacksThenSpread: { + en: 'Role Stacks => Spread', + }, + }, + }, + { + id: 'AAI Ketuduke Receding Twintides', + type: 'StartsUsing', + netRegex: { id: '8ACC', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsRoleStacks) + return output.outInRoleStacks!(); + return output.outInStacks!(); + }, + run: (data) => data.ketuTwintidesNext = 'in', + outputStrings: { + outInStacks: { + en: 'Out => In + Stacks', + }, + outInRoleStacks: { + en: 'Out => In + Role Stacks', + }, + }, + }, + { + id: 'AAI Ketuduke Encroaching Twintides', + type: 'StartsUsing', + netRegex: { id: '8ACE', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsRoleStacks) + return output.inOutRoleStacks!(); + return output.inOutStacks!(); + }, + run: (data) => data.ketuTwintidesNext = 'out', + outputStrings: { + inOutStacks: { + en: 'In => Out + Stacks', + }, + inOutRoleStacks: { + en: 'In => Out + Role Stacks', + }, + }, + }, + { + id: 'AAI Ketuduke Twintides Followup', + type: 'Ability', + // 8ABC = Sphere Shatter, which happens slightly after the Twintides hit. + // You can technically start moving along the safe Sphere Shatter side 0.5s earlier + // after the initial out/in, but this is hard to explain. + netRegex: { id: '8ABC', source: 'Ketuduke', capture: false }, + suppressSeconds: 5, + infoText: (data, _matches, output) => { + const mech = data.ketuTwintidesNext; + if (mech === undefined) + return; + const mechStr = output[mech]!(); + const stackStr = data.ketuHydroBuffIsRoleStacks ? output.roleStacks!() : output.stack!(); + return output.text!({ inOut: mechStr, stack: stackStr }); + }, + run: (data) => delete data.ketuTwintidesNext, + outputStrings: { + text: { + en: '${inOut} + ${stack}', + }, + in: Outputs.in, + out: Outputs.out, + stack: { + en: 'Stacks', + }, + roleStacks: { + en: 'Role Stacks', + }, + }, + }, + { + id: 'AAI Ketuduke Spring Crystals 2', + type: 'AddedCombatant', + netRegex: { npcNameId: '12607', capture: false }, + condition: (data) => data.ketuSpringCrystalCount === 2 && data.ketuCrystalAdd.length === 4, + // We could call this absurdly early, but knowing this doesn't help with anything + // until you know what your debuff is, so move it later both so it is less absurd + // futuresight and so you don't have to remember it as long. + delaySeconds: 5, + alertText: (data, _matches, output) => { + const horizontal = data.ketuCrystalAdd.filter((x) => isHorizontalCrystal(x)); + const vertical = data.ketuCrystalAdd.filter((x) => !isHorizontalCrystal(x)); + if (horizontal.length !== 2 || vertical.length !== 2) + return; + + // Crystal positions are always -15, -5, 5, 15. + + // Check if any verticals are on the outer vertical edges. + for (const line of vertical) { + const y = parseFloat(line.y); + if (y < -10 || y > 10) + return output.eastWestSafe!(); + } + + // Check if any horizontals are on the outer horizontal edges. + for (const line of horizontal) { + const x = parseFloat(line.x); + if (x < -10 || x > 10) + return output.northSouthSafe!(); + } + + return output.cornersSafe!(); + }, + outputStrings: { + northSouthSafe: { + en: 'North/South', + }, + eastWestSafe: { + en: 'East/West', + }, + cornersSafe: { + en: 'Corners', + }, + }, + }, + { + id: 'AAI Ketuduke Angry Seas', + type: 'StartsUsing', + netRegex: { id: '8AC1', source: 'Ketuduke', capture: false }, + alertText: (data, _matches, output) => { + if (data.ketuHydroBuffIsSpreadFirst) + return output.knockbackSpread!(); + if (data.ketuHydroBuffIsRoleStacks) + return output.knockbackRoleStacks!(); + return output.knockbackStacks!(); + }, + outputStrings: { + knockbackSpread: { + en: 'Knockback => Spread', + }, + knockbackStacks: { + en: 'Knockback => Stacks', + }, + knockbackRoleStacks: { + en: 'Knockback => Role Stacks', + }, + }, + }, + // ---------------- second trash ---------------- + { + id: 'AAI Wood Golem Ancient Aero III', + type: 'StartsUsing', + netRegex: { id: '8C4C', source: 'Aloalo Wood Golem' }, + condition: (data) => data.CanSilence(), + response: Responses.interrupt('alarm'), + }, + { + id: 'AAI Wood Golem Tornado', + type: 'StartsUsing', + netRegex: { id: '8C4D', source: 'Aloalo Wood Golem' }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tornadoOn: { + en: 'Away from ${player}', + }, + tornadoOnYou: { + en: 'Tornado on YOU', + }, + }; + + if (data.me === matches.target) + return { alertText: output.tornadoOnYou!() }; + return { infoText: output.tornadoOn!({ player: data.party.member(matches.target) }) }; + }, + }, + { + id: 'AAI Wood Golem Tornado Bind', + type: 'GainsEffect', + netRegex: { effectId: 'EC0' }, + condition: (data) => data.CanCleanse(), + infoText: (data, matches, output) => { + return output.text!({ player: data.party.member(matches.target) }); + }, + outputStrings: { + text: { + en: 'Cleanse ${player}', + }, + }, + }, + { + id: 'AAI Wood Golem Ovation', + type: 'StartsUsing', + netRegex: { id: '8BC1', source: 'Aloalo Wood Golem', capture: false }, + response: Responses.getBehind('info'), + }, + { + id: 'AAI Islekeeper Gravity Force', + type: 'StartsUsing', + netRegex: { id: '8BC5', source: 'Aloalo Islekeeper' }, + response: Responses.stackMarkerOn(), + }, + { + id: 'AAI Islekeeper Isle Drop', + type: 'StartsUsing', + netRegex: { id: '8C6F', source: 'Aloalo Islekeeper', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Behind + Out', + }, + }, + }, + { + id: 'AAI Islekeeper Ancient Quaga', + type: 'StartsUsing', + netRegex: { id: '8C4E', source: 'Aloalo Islekeeper', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'AAI Islekeeper Ancient Quaga Enrage', + type: 'StartsUsing', + netRegex: { id: '8C2F', source: 'Aloalo Islekeeper', capture: false }, + alarmText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Kill Islekeeper!', + }, + }, + }, + // ---------------- Lala ---------------- + { + id: 'AAI Lala Inferno Theorem', + type: 'StartsUsing', + netRegex: { id: '88AE', source: 'Lala', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAI Lala Rotation Tracker', + type: 'HeadMarker', + netRegex: { id: ['01E4', '01E5'], target: 'Lala' }, + run: (data, matches) => data.lalaBossRotation = matches.id === '01E4' ? 'clock' : 'counter', + }, + { + id: 'AAI Lala Angular Addition Tracker', + type: 'Ability', + netRegex: { id: ['8889', '8D2E'], source: 'Lala' }, + run: (data, matches) => data.lalaBossTimes = matches.id === '8889' ? 3 : 5, + }, + { + id: 'AAI Lala Arcane Blight', + type: 'StartsUsing', + netRegex: { id: ['888B', '888C', '888D', '888E'], source: 'Lala' }, + alertText: (data, matches, output) => { + const initialDir = { + '888B': 2, // initial back safe + '888C': 0, // initial front safe + '888D': 1, // initial right safe + '888E': 3, // initial left safe + }[matches.id]; + if (initialDir === undefined) + return; + if (data.lalaBossTimes === undefined) + return; + if (data.lalaBossRotation === undefined) + return; + const rotationFactor = data.lalaBossRotation === 'clock' ? 1 : -1; + const finalDir = (initialDir + rotationFactor * data.lalaBossTimes + 8) % 4; + + const diff = (finalDir - initialDir + 4) % 4; + if (diff !== 1 && diff !== 3) + return; + return { + 0: output.front!(), + 1: output.right!(), + 2: output.back!(), + 3: output.left!(), + }[finalDir]; + }, + run: (data) => { + delete data.lalaBossTimes; + delete data.lalaBossRotation; + }, + outputStrings: { + front: Outputs.front, + back: Outputs.back, + left: Outputs.left, + right: Outputs.right, + }, + }, + { + id: 'AAI Lala Analysis Collect', + type: 'GainsEffect', + netRegex: { effectId: ['E8E', 'E8F', 'E90', 'E91'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const effectMap: { [effectId: string]: typeof data.lalaUnseen } = { + 'E8E': 'front', + 'E8F': 'back', + 'E90': 'right', + 'E91': 'left', + } as const; + data.lalaUnseen = effectMap[matches.effectId]; + }, + }, + { + id: 'AAI Lala Times Collect', + type: 'GainsEffect', + netRegex: { effectId: ['E89', 'ECE'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const effectMap: { [effectId: string]: typeof data.lalaPlayerTimes } = { + 'E89': 3, + 'ECE': 5, + } as const; + data.lalaPlayerTimes = effectMap[matches.effectId]; + }, + }, + { + id: 'AAI Lala Player Rotation Collect', + type: 'HeadMarker', + netRegex: { id: ['01ED', '01EE'] }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + const idMap: { [id: string]: typeof data.lalaPlayerRotation } = { + '01ED': 'counter', + '01EE': 'clock', + } as const; + data.lalaPlayerRotation = idMap[matches.id]; + }, + }, + { + id: 'AAI Lala Targeted Light', + type: 'StartsUsing', + netRegex: { id: '8CDE', source: 'Lala', capture: false }, + alertText: (data, _matches, output) => { + const initialUnseen = data.lalaUnseen; + if (initialUnseen === undefined) + return; + + const initialDir = { + front: 0, + right: 1, + back: 2, + left: 3, + }[initialUnseen]; + + const rotation = data.lalaPlayerRotation; + if (rotation === undefined) + return; + const times = data.lalaPlayerTimes; + if (times === undefined) + return; + + // The safe spot rotates, so the player counter-rotates. + const rotationFactor = rotation === 'clock' ? -1 : 1; + const finalDir = (initialDir + rotationFactor * times + 8) % 4; + + return { + 0: output.front!(), + 1: output.right!(), + 2: output.back!(), + 3: output.left!(), + }[finalDir]; + }, + run: (data) => { + delete data.lalaUnseen; + delete data.lalaPlayerTimes; + }, + outputStrings: { + front: { + en: 'Face Towards Lala', + }, + back: { + en: 'Look Away from Lala', + }, + left: { + en: 'Left Flank towards Lala', + }, + right: { + en: 'Right Flank towards Lala', + }, + }, + }, + { + id: 'AAI Lala Strategic Strike', + type: 'StartsUsing', + netRegex: { id: '88AD', source: 'Lala' }, + response: Responses.tankBuster(), + }, + { + id: 'AAI Lala Planar Tactics', + type: 'GainsEffect', + // E8B = Surge Vector + // E8C = Subtractive Suppressor Alpha + netRegex: { effectId: ['E8C', 'E8B'] }, + condition: (data, matches) => { + data.lalaSubAlpha.push(matches); + return data.lalaSubAlpha.length === 6; + }, + durationSeconds: 7, + // Only run once, as Surge Vector is used again. + suppressSeconds: 9999999, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + one: { + en: 'One', + }, + bigTwo: { + en: 'Two (stack with three)', + }, + smallTwo: { + en: 'Two (stack with one)', + }, + eitherTwo: { + en: 'Either Two (w/${player})', + }, + three: { + en: 'Three', + }, + // This is just a raidcall so you can direct your friends. + smallTwoOn: { + en: '(Two with one: ${players})', + }, + unknownNum: { + en: '${num}', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + }; + + // For brevity, this code calls "small two" the two that stacks with one + // and the "big two" the two that stacks with three. + const stacks = data.lalaSubAlpha.filter((x) => x.effectId === 'E8B').map((x) => x.target); + const nums = data.lalaSubAlpha.filter((x) => x.effectId === 'E8C'); + const myNumberStr = nums.find((x) => x.target === data.me)?.count; + if (myNumberStr === undefined) + return; + const myNumber = parseInt(myNumberStr); + if (myNumber < 1 || myNumber > 4) + return; + + const defaultOutput = { + alertText: output.unknownNum!({ num: output[`num${myNumber}`]!() }), + } as const; + + if (stacks.length !== 2 || nums.length !== 4) + return defaultOutput; + + const one = nums.find((x) => parseInt(x.count) === 1)?.target; + if (one === undefined) + return defaultOutput; + const isOneStack = stacks.includes(one); + const twos = nums.filter((x) => parseInt(x.count) === 2).map((x) => x.target); + + const smallTwos: string[] = []; + for (const thisTwo of twos) { + // can this two stack with the one? + const isThisTwoStack = stacks.includes(thisTwo); + if (isThisTwoStack && !isOneStack || !isThisTwoStack && isOneStack) + smallTwos.push(thisTwo); + } + + const [smallTwo1, smallTwo2] = smallTwos; + if (smallTwos.length === 0 || smallTwo1 === undefined) + return defaultOutput; + + const isPlayerSmallTwo = smallTwos.includes(data.me); + + // Worst case adjust + if (isPlayerSmallTwo && smallTwo2 !== undefined) { + const otherPlayer = smallTwo1 === data.me ? smallTwo2 : smallTwo1; + return { alarmText: output.eitherTwo!({ player: data.party.member(otherPlayer) }) }; + } + + let playerRole: string; + if (one === data.me) { + playerRole = output.one!(); + } else if (twos.includes(data.me)) { + playerRole = isPlayerSmallTwo ? output.smallTwo!() : output.bigTwo!(); + } else { + playerRole = output.three!(); + } + + if (isPlayerSmallTwo) + return { alertText: playerRole }; + + return { + alertText: playerRole, + infoText: output.smallTwoOn!({ players: smallTwos.map((x) => data.party.member(x)) }), + }; + }, + }, + { + id: 'AAI Lala Forward March', + type: 'GainsEffect', + // E83 = Forward March + netRegex: { effectId: 'E83' }, + condition: Conditions.targetIsYou(), + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 8, + durationSeconds: 4, + alertText: (data, _matches, output) => { + const rotation = data.lalaPlayerRotation; + if (rotation === undefined) + return; + const times = data.lalaPlayerTimes; + if (times === undefined) + return; + + const rotationFactor = rotation === 'clock' ? 1 : -1; + const finalDir = (rotationFactor * times + 8) % 4; + if (finalDir === 1) + return output.left!(); + if (finalDir === 3) + return output.right!(); + }, + run: (data) => { + delete data.lalaPlayerRotation; + delete data.lalaPlayerTimes; + }, + outputStrings: { + left: { + en: 'Leftward March', + }, + right: { + en: 'Rightward March', + }, + }, + }, + { + id: 'AAI Lala Spatial Tactics', + type: 'GainsEffect', + // E8D = Subtractive Suppressor Beta + netRegex: { effectId: 'E8D' }, + condition: Conditions.targetIsYou(), + suppressSeconds: 999999, + alertText: (_data, matches, output) => { + const num = parseInt(matches.count); + if (num < 1 || num > 4) + return; + return output[`num${num}`]!(); + }, + outputStrings: { + num1: { + en: 'One (avoid all)', + }, + num2: { + en: 'Two (stay middle)', + }, + num3: { + en: 'Three (adjacent to middle)', + }, + num4: { + en: 'Four', + }, + }, + }, + // ---------------- Statice ---------------- + { + id: 'AAI Statice Aero IV', + type: 'StartsUsing', + netRegex: { id: '8949', source: 'Statice', capture: false }, + response: Responses.aoe(), + }, + { + id: 'AAI Statice Trick Reload', + type: 'Ability', + // 8925 = Locked and Loaded + // 8926 = Misload + netRegex: { id: ['8925', '8926'], source: 'Statice' }, + preRun: (data, matches) => data.staticeBullet.push(matches), + alertText: (data, _matches, output) => { + // Statice loads 8 bullets, two are duds. + // The first and the last are always opposite, and one of them is a dud. + // The first/ last bullets are for Trapshooting and the middle six are for Trigger Happy. + const [bullet] = data.staticeBullet; + if (data.staticeBullet.length !== 1 || bullet === undefined) + return; + const isStack = bullet.id === '8926'; + data.staticeTrapshooting = isStack ? ['stack', 'spread'] : ['spread', 'stack']; + return isStack ? output.stackThenSpread!() : output.spreadThenStack!(); + }, + infoText: (data, _matches, output) => { + const lastBullet = data.staticeBullet[data.staticeBullet.length - 1]; + if (data.staticeBullet.length < 2 || data.staticeBullet.length > 7) + return; + if (lastBullet?.id !== '8926') + return; + data.staticeTriggerHappy = data.staticeBullet.length - 1; + return output.numSafeLater!({ num: output[`num${data.staticeTriggerHappy}`]!() }); + }, + run: (data) => { + if (data.staticeBullet.length === 8) + data.staticeBullet = []; + }, + outputStrings: { + stackThenSpread: Outputs.stackThenSpread, + spreadThenStack: Outputs.spreadThenStack, + numSafeLater: { + en: '(${num} safe later)', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAI Statice Trapshooting', + type: 'StartsUsing', + netRegex: { id: ['8D1A', '8959'], source: 'Statice', capture: false }, + alertText: (data, _matches, output) => { + const mech = data.staticeTrapshooting.shift(); + if (mech === undefined) + return; + return output[mech]!(); + }, + outputStrings: { + spread: Outputs.spread, + stack: Outputs.stackMarker, + }, + }, + { + id: 'AAI Statice Trigger Happy', + type: 'StartsUsing', + netRegex: { id: '894B', source: 'Statice', capture: false }, + alertText: (data, _matches, output) => { + const num = data.staticeTriggerHappy; + if (num === undefined) + return; + return output[`num${num}`]!(); + }, + run: (data) => delete data.staticeTriggerHappy, + outputStrings: { + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAI Statice Bull\'s-eye', + type: 'GainsEffect', + netRegex: { effectId: 'E9E' }, + delaySeconds: (data, matches) => { + // Note: this collects for the pinwheeling dartboard version too. + data.staticeDart.push(matches); + return data.staticeDart.length === 3 ? 0 : 0.5; + }, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + dartOnYou: { + en: 'Dart on YOU', + }, + noDartOnYou: { + en: 'No Dart', + }, + flexCall: { + en: '(${player} unmarked)', + }, + }; + + if (data.staticeIsPinwheelingDartboard) + return; + + if (data.staticeDart.length === 0) + return; + + const dartTargets = data.staticeDart.map((x) => x.target); + + if (!dartTargets.includes(data.me)) + return { alertText: output.noDartOnYou!() }; + + const partyNames = data.party.partyNames; + + const flexers = partyNames.filter((x) => !dartTargets.includes(x)); + const [flex] = flexers; + const flexPlayer = flexers.length === 1 ? data.party.member(flex) : undefined; + + return { + alertText: output.dartOnYou!(), + infoText: output.flexCall!({ player: flexPlayer }), + }; + }, + run: (data) => data.staticeDart = [], + }, + { + id: 'AAI Statice Surprise Balloon Reminder', + // This is an early reminder for the following Trigger Happy with knockback. + // However, because there's a tight window to immuune both knockbacks, + // call this ~15s early (in case anybody forgot). + type: 'StartsUsing', + netRegex: { id: '894D', source: 'Statice', capture: false }, + infoText: (data, _matches, output) => { + const num = data.staticeTriggerHappy; + if (num === undefined) + return; + // We'll re-call this out with the knockback warning. + // However, also clear `data.staticeTriggerHappy` to avoid double callouts. + data.staticePopTriggerHappyNum = num; + return output.numSafeSoon!({ num: output[`num${num}`]!() }); + }, + run: (data) => delete data.staticeTriggerHappy, + outputStrings: { + numSafeSoon: { + en: '(${num} safe soon)', + }, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAI Statice Pop', + type: 'StartsUsing', + // TODO: this might need a slight delay + netRegex: { id: '894E', source: 'Statice', capture: false }, + suppressSeconds: 20, + alertText: (data, _matches, output) => { + const num = data.staticePopTriggerHappyNum; + if (num === undefined) + return output.knockback!(); + + const numStr = output[`num${num}`]!(); + return output.knockbackToNum!({ num: numStr }); + }, + run: (data) => delete data.staticePopTriggerHappyNum, + outputStrings: { + knockbackToNum: { + en: 'Knockback => ${num}', + }, + knockback: Outputs.knockback, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + }, + }, + { + id: 'AAI Statice Face', + type: 'GainsEffect', + // DD2 = Forward March + // DD3 = About Face + // DD4 = Left Face + // DD5 = Right Face + netRegex: { effectId: ['DD2', 'DD3', 'DD4', 'DD5'] }, + condition: Conditions.targetIsYou(), + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 7, + durationSeconds: 5, + alertText: (data, matches, output) => { + let mech = output.unknown!(); + + const num = data.staticeTriggerHappy; + if (num !== undefined) { + mech = output[`num${num}`]!(); + delete data.staticeTriggerHappy; + } else { + const mechName = data.staticeTrapshooting.shift(); + mech = mechName === undefined ? output.unknown!() : output[mechName]!(); + } + + return { + 'DD2': output.forward!({ mech: mech }), + 'DD3': output.backward!({ mech: mech }), + 'DD4': output.left!({ mech: mech }), + 'DD5': output.right!({ mech: mech }), + }[matches.effectId]; + }, + outputStrings: { + forward: { + en: 'Forward March => ${mech}', + }, + backward: { + en: 'Backward March => ${mech}', + }, + left: { + en: 'Left March => ${mech}', + }, + right: { + en: 'Right March => ${mech}', + }, + spread: Outputs.spread, + stack: Outputs.stackMarker, + num1: Outputs.num1, + num2: Outputs.num2, + num3: Outputs.num3, + num4: Outputs.num4, + num5: Outputs.num5, + num6: Outputs.num6, + unknown: Outputs.unknown, + }, + }, + { + id: 'AAI Statice Present Box Counter', + // This happens ~1s prior to ActorControlExtra on bomb. + type: 'StartsUsing', + netRegex: { id: '8955', source: 'Statice', capture: false }, + run: (data) => data.staticePresentBoxCount++, + }, + { + id: 'AAI Statice Present Box Missile', + type: 'Tether', + netRegex: { source: 'Surprising Missile', id: '0011' }, + delaySeconds: (data, matches) => { + data.staticeMissileTether.push(matches); + return data.staticeMissileTether.length === 2 ? 0 : 0.5; + }, + durationSeconds: 7, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + missileOnYou: { + en: 'Bait Tethers => Missile Spread', + }, + }; + + if (data.staticeMissileTether.length !== 2) + return; + + const missileTether = data.staticeMissileTether.find((x) => x.target === data.me); + if (missileTether === undefined) + return; + + return { alertText: output.missileOnYou!() }; + }, + run: (data) => data.staticeMissileTether = [], + }, + { + id: 'AAI Statice Present Box Claw', + type: 'Tether', + netRegex: { source: 'Surprising Claw', id: '0011' }, + delaySeconds: (data, matches) => { + data.staticeClawTether.push(matches); + return data.staticeClawTether.length === 2 ? 0 : 0.5; + }, + durationSeconds: 7, + alertText: (data, _matches, output) => { + if (data.staticeClawTether.length !== 2) + return; + if (!data.staticeClawTether.map((x) => x.target).includes(data.me)) + return; + return output.stack!(); + }, + run: (data) => data.staticeClawTether = [], + outputStrings: { + stack: { + en: 'Juke Claw => Stack', + }, + }, + }, + { + id: 'AAI Statice Burning Chains', + type: 'GainsEffect', + netRegex: { effectId: '301' }, + condition: Conditions.targetIsYou(), + // TODO: add a strategy for dart colors and say where to go here + // for the Pinwheeling Dartboard if you have a dart. + response: Responses.breakChains(), + }, + { + id: 'AAI Statice Shocking Abandon', + type: 'StartsUsing', + netRegex: { id: '8948', source: 'Statice' }, + response: Responses.tankBuster(), + }, + { + id: 'AAI Statice Pinwheeling Dartboard Tracker', + type: 'StartsUsing', + netRegex: { id: '8CBC', source: 'Statice', capture: false }, + run: (data) => data.staticeIsPinwheelingDartboard = true, + }, + { + id: 'AAI Statice Pinwheeling Dartboard Color', + type: 'AddedCombatant', + netRegex: { npcNameId: '12507' }, + durationSeconds: 6, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + dartOnYou: { + en: 'Dart (w/${player})', + }, + noDartOnYou: { + en: 'No Dart', + }, + blue: { + en: 'Avoid Blue', + }, + red: { + en: 'Avoid Red', + }, + yellow: { + en: 'Avoid Yellow', + }, + }; + + let infoText: string | undefined; + + const centerX = -200; + const centerY = 0; + const x = parseFloat(matches.x) - centerX; + const y = parseFloat(matches.y) - centerY; + + // 12 pie slices, the edge of the first one is directly north. + // It goes in B R Y order repeating 4 times. + // The 0.5 subtraction (12 - 0.5 = 11.5) is because the Homing Pattern + // lands directly in the middle of a slice. + const dir12 = Math.round(6 - 6 * Math.atan2(x, y) / Math.PI + 11.5) % 12; + + const colorOffset = dir12 % 3; + const colorMap: { [offset: number]: typeof data.staticeHomingColor } = { + 0: 'blue', + 1: 'red', + 2: 'yellow', + } as const; + + data.staticeHomingColor = colorMap[colorOffset]; + if (data.staticeHomingColor !== undefined) + infoText = output[data.staticeHomingColor]!(); + + if (data.staticeDart.length !== 2) + return { infoText }; + + const dartTargets = data.staticeDart.map((x) => x.target); + if (!dartTargets.includes(data.me)) + return { alertText: output.noDartOnYou!(), infoText: infoText }; + + const [target1, target2] = dartTargets; + if (target1 === undefined || target2 === undefined) + return { infoText }; + const otherTarget = data.party.member(target1 === data.me ? target2 : target1); + return { alertText: output.dartOnYou!({ player: otherTarget }), infoText: infoText }; + }, + }, + { + id: 'AAI Statice Pinwheeling Dartboard Mech', + type: 'HeadMarker', + netRegex: { id: headmarkerIds.tethers }, + condition: (data) => data.staticeIsPinwheelingDartboard, + delaySeconds: (data, matches) => { + data.staticeDartboardTether.push(matches); + return data.staticeDartboardTether.length === 2 ? 0 : 0.5; + }, + alertText: (data, _matches, output) => { + if (data.staticeDartboardTether.length !== 2) + return; + + const tethers = data.staticeDartboardTether.map((x) => x.target); + + if (tethers.includes(data.me)) { + const [tether1, tether2] = tethers; + const other = data.party.member(tether1 === data.me ? tether2 : tether1); + return output.tether!({ player: other }); + } + + const partyNames = data.party.partyNames; + const nonTethers = partyNames.filter((x) => !tethers.includes(x)); + const [stack1, stack2] = nonTethers; + const other = data.party.member(stack1 === data.me ? stack2 : stack1); + return output.stack!({ player: other }); + }, + run: (data) => data.staticeDartboardTether = [], + outputStrings: { + // TODO: maybe this should remind you of dart color + tether: { + en: 'Tether w/${player}', + }, + stack: { + en: 'Stack w/${player}', + }, + }, + }, + ], + timelineReplace: [ + { + locale: 'en', + replaceText: { + 'Hydrobullet/Hydrofall': 'Hydrobullet/fall', + 'Hydrofall/Hydrobullet': 'Hydrofall/bullet', + 'Receding Twintides/Encroaching Twintides': 'Receding/Encroaching Twintides', + 'Far Tide/Near Tide': 'Far/Near Tide', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt new file mode 100644 index 0000000000..fc7f6354d6 --- /dev/null +++ b/ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt @@ -0,0 +1,424 @@ +### ANOTHER ALOALO ISLAND +# ZoneId: 49B + +hideall "--Reset--" +hideall "--sync--" + +# .*is no longer sealed +0.0 "--Reset--" SystemLogMessage { id: "7DE" } window 100000 jump 0 + +#~~~~~~~~~~# +# KETUDUKE # +#~~~~~~~~~~# + +# -p 8AD4:1015.2 +# -ii 8AA7 8AD5 8AAE 8AB9 8AB6 8AD1 8ABE 8ABF 8AC9 8ACB 8AC0 8AAA 8AA9 8AC6 8AC8 8AC3 8A82 8A83 + +# The Dawn Trial will be sealed off +1000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1146" } window 10000,0 +1010.2 "--sync--" StartsUsing { id: "8AD4", source: "Ketuduke" } window 20,20 +1015.2 "Tidal Roar" Ability { id: "8AD4", source: "Ketuduke" } + +1022.0 "--middle--" Ability { id: "8A77", source: "Ketuduke" } +1027.0 "Spring Crystals 1" Ability { id: "8AA8", source: "Ketuduke" } +1034.0 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1041.0 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8AB8|8AB4)", source: "Ketuduke" } +1050.0 "Fluke Gale (cast)" Ability { id: "8AB1", source: "Ketuduke" } +1060.0 "Fluke Gale 1" Ability { id: "8AB2", source: "Ketuduke" } +1062.0 "Fluke Gale 2" Ability { id: "8AB3", source: "Ketuduke" } +1065.0 "Saturate" Ability { id: "8AAC", source: "Spring Crystal" } +1065.1 "Hydrobullet/Hydrofall" Ability { id: "(8ABA|8AB7)", source: "Ketuduke" } + +1074.0 "Hydrofall/Hydrobullet (buff)" Ability { id: "(8AB4|8AB8)", source: "Ketuduke" } +1077.1 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8C6D|8AB5)", source: "Ketuduke" } +1084.3 "Blowing Bubbles" Ability { id: "8ABD", source: "Ketuduke" } +1091.4 "Hydrobomb x3" Ability { id: "8AD0", source: "Ketuduke" } +1096.3 "Hydrofall/Hydrobullet" Ability { id: "(8AB7|8ABA)", source: "Ketuduke" } +1099.6 "Hydrobomb x3" Ability { id: "8AD0", source: "Ketuduke" } +1102.5 "Hydrobullet/Hydrobullet" Ability { id: "(8ABA|8AB7)", source: "Ketuduke" } + +1113.6 "Hydrofall (buff)" Ability { id: "8AB4", source: "Ketuduke" } +1118.9 "Strewn Bubbles" Ability { id: "8ABB", source: "Ketuduke" } +1130.3 "Receding Twintides/Encroaching Twintides" Ability { id: "(8ACC|8ACE)", source: "Ketuduke" } +1130.9 "Sphere Shatter 1" Ability { id: "8ABC", source: "Ketuduke" } +1133.4 "Far Tide/Near Tide" Ability { id: "(8ACF|8ACD)", source: "Ketuduke" } +1134.0 "Sphere Shatter 2" Ability { id: "8ABC", source: "Ketuduke" } +1134.0 "Hydrofall" Ability { id: "8AB7", source: "Ketuduke" } + +1143.7 "Hydrobullet (buff)" Ability { id: "8AB8", source: "Ketuduke" } +1149.7 "Roar" Ability { id: "8AC4", source: "Ketuduke" } +1154.6 "Spring Crystals 2" Ability { id: "8AA8", source: "Ketuduke" } +1164.6 "Bubble Net" Ability { id: "8AC5", source: "Ketuduke" } +1177.0 "Hydrobullet" Ability { id: "8ABA", source: "Ketuduke" } +1177.6 "Saturate" Ability { id: "8AAC", source: "Spring Crystal" } +1182.7 "Updraft" Ability { id: "8AC7", source: "Ketuduke" } +1184.1 "Hundred Lashings" Ability { id: "8ACA", source: "Aloalo Zaratan" } + +1190.7 "--middle--" Ability { id: "8A77", source: "Ketuduke" } +1199.4 "Hydrofall/Hydrobullet (buff)" Ability { id: "(8AB4|8AB8)", source: "Ketuduke" } +1202.4 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8C6D|8AB5)", source: "Ketuduke" } +1210.4 "Angry Seas" Ability { id: "8AC1", source: "Ketuduke" } +1211.6 "Hydrofall/Hydrobullet" Ability { id: "(8AB7|8ABA)", source: "Ketuduke" } +1212.2 "Hydrobullet/Hydrobullet" Ability { id: "(8ABA|8AB7)" } # this sometimes has the wrong source +1214.8 "Spring Crystals 3" Ability { id: "8AA8", source: "Ketuduke" } +1216.8 "Hydrobullet" Ability { id: "8ABA", source: "Ketuduke" } +1221.8 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1227.8 "Fluke Typhoon (cast)" Ability { id: "8AAF", source: "Ketuduke" } +1233.8 "Fluke Typhoon" Ability { id: "8AB0", source: "Ketuduke" } +1236.4 "Saturate" Ability { id: "8AAB", source: "Spring Crystal" } +1238.8 "Burst x4" Ability { id: "8AC2", source: "Ketuduke" } + +1246.7 "Spring Crystals 4" Ability { id: "8AA8", source: "Ketuduke" } +1253.7 "Bubble Net" Ability { id: "8AAD", source: "Ketuduke" } +1260.7 "Hydrobullet/Hydrofall (buff)" Ability { id: "(8AB8|8AB4)", source: "Ketuduke" } +1269.8 "Fluke Gale (cast)" Ability { id: "8AB1", source: "Ketuduke" } +1279.9 "Fluke Gale 1" Ability { id: "8AB2", source: "Ketuduke" } +1281.9 "Fluke Gale 2" Ability { id: "8AB3", source: "Ketuduke" } +1284.9 "Saturate" Ability { id: "8AAC", source: "Spring Crystal" } +1285.0 "Hydrobullet/Hydrofall" Ability { id: "(8ABA|8AB7)", source: "Ketuduke" } + +1293.9 "Hydrofall (buff)" Ability { id: "8AB4", source: "Ketuduke" } +1299.2 "Strewn Bubbles" Ability { id: "8ABB", source: "Ketuduke" } +1310.6 "Receding Twintides/Encroaching Twintides" Ability { id: "(8ACC|8ACE)", source: "Ketuduke" } +1311.2 "Sphere Shatter 1" Ability { id: "8ABC", source: "Ketuduke" } +1313.7 "Far Tide/Near Tide" Ability { id: "(8ACF|8ACD)", source: "Ketuduke" } +1314.3 "Sphere Shatter 2" Ability { id: "8ABC", source: "Ketuduke" } +1314.3 "Hydrofall" Ability { id: "8AB7", source: "Ketuduke" } +1325.0 "Tidal Roar" Ability { id: "8AD4", source: "Ketuduke" } + +1331.8 "--sync--" StartsUsing { id: "8AD6", source: "Ketuduke" } +1341.8 "Tidal Roar Enrage" Ability { id: "8AD6", source: "Ketuduke" } + +# ALL ENCOUNTER ABILITIES +# 8A77 --sync-- Ketuduke repositioning +# 8A82 Riptide ability on players from Angry Seas Airy Bubble when you step in one +# 8A83 Fetters ability on players from Angry Seas Airy Bubble when you step in one after 8A82 Riptide +# 8AA7 --sync-- auto damage from Ketuduke +# 8AA8 Spring Crystals cast and ability to summon Spring Crystal adds (all flavors) +# 8AA9 衝撃 self-targeted ability from Spring Crystal orbs +# 8AAA 衝撃 self-targeted ability from Spring Crystal rupees +# 8AAB Saturate cast and damage from Spring Crystal orb circle +# 8AAC Saturate cast and damage from Spring Crystal rupee line laser +# 8AAD Bubble Net self-targeted cast before Bubbles along with 8AAE during Spring Crystals 1 +# 8AAE Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 1 +# 8AAF Fluke Typhoon self-targeted cast before 8AB0 knockback during Spring Crystals 3 +# 8AB0 Fluke Typhoon cast and knockback ability on Spring Crystal and players during Spring Crystals 3 +# 8AB1 Fluke Gale self-targeted cast that adds limit cut winds +# 8AB2 Fluke Gale cast and ability for limit cut 1 wind +# 8AB3 Fluke Gale cast and ability for limit cut 2 wind +# 8AB4 Hydrofall self-targeted cast that adds stack markers +# 8AB5 Hydrofall self-targeted "stack second" ability before Blowing Bubbles +# 8AB6 Hydrofall ability on players that adds stack debuffs +# 8AB7 Hydrofall damage from stack debuffs +# 8AB8 Hydrobullet self-targeted cast that adds stack markers +# 8AB9 Hydrobullet ability on players that adds spread debuffs +# 8ABA Hydrobullet damage from spread debuffs +# 8ABB Strewn Bubbles self-targeted cast before 8ABC Sphere Shatter moving arches +# 8ABC Sphere Shatter damage from moving arches +# 8ABD Blowing Bubbles self-targeted cast that adds Airy Bubble Adds +# 8ABE Riptide ability on players from Blowing Bubbles Airy Bubble when you step in one +# 8ABF Fetters ability on players from Blowing Bubbles Airy Bubble when you step in one after 8ABE Riptide +# 8AC0 Angry Seas self-targeted cast for 8AC1 red line knockback +# 8AC1 Angry Seas cast and knockback damage from red line +# 8AC2 Burst tower damage +# 8AC3 Big Burst tower failure damage +# 8AC4 Roar self-targeted cast that summons Zaratan adds +# 8AC5 Bubble Net self-targeted cast before Bubbles along with 8AC6 during Spring Crystals 2 +# 8AC6 Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 2 +# 8AC7 Updraft self-targeted cast to boost adds and players into the air +# 8AC8 Updraft ability on players for 8AC7 Updraft +# 8AC9 Hundred Lashings cast and damage for non-bubbled Zaratan 180 cleave (no damage on bubbled players) +# 8ACA Hundred Lashings self-targeted cast for bubbled Zaratan adds +# 8ACB Hundred Lashings cast and damage for bubbled Zaratan 180 cleave (no damage on non-bubbled players) +# 8ACC Receding Twintides cast and damage for initial out during out->in +# 8ACD Near Tide fast cast and damage for second out during in->out with 8ACE Encroaching Twintides +# 8ACE Encroaching Twintides cast and damage for initial in during in->out +# 8ACF Far Tide fast cast and damage for second in during out->in with 8ACC Receding Twintides +# 8AD0 Hydrobomb self-targeted cast for 8AD1 puddles +# 8AD1 Hydrobomb cast and damage for 3x puddles duruing 8ABD Blowing Bubbles +# 8AD4 Tidal Roar self-targeted cast for raidwide aoe +# 8AD5 Tidal Roar damage from 8AD4 +# 8AD6 Tidal Roar cast and enrage damage +# 8C6D Hydrobullet self-targeted "spread second" ability before Blowing Bubbles + + +#~~~~~~# +# LALA # +#~~~~~~# + +# -p 88AE:2011.1 +# -ii 888F 8892 8CDF 889A 889B 88A8 889C 889D + +# The Dusk Trial will be sealed off +2000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1147" } window 10000,0 +2006.1 "--sync--" StartsUsing { id: "88AE", source: "Lala" } window 20,20 +2011.1 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } + +2013.2 "--middle--" Ability { id: "8874", source: "Lala" } +2018.3 "Angular Addition" Ability { id: "(8889|8D2E)", source: "Lala" } +2026.4 "Arcane Blight" Ability { id: "(888B|888C|888D|888E)", source: "Lala" } + +2029.6 "--middle--" Ability { id: "8874", source: "Lala" } +2034.7 "Analysis" Ability { id: "8895", source: "Lala" } +2039.8 "Arcane Array 1" Ability { id: "8890", source: "Lala" } +2044.9 "Angular Addition" Ability { id: "(8889|8D2E)", source: "Lala" } +2045.6 "Bright Pulse" Ability { id: "8891", source: "Lala" } +2046.9 "Radiance 1" Ability { id: "8894", source: "Arcane Globe" } +2053.0 "Arcane Blight" Ability { id: "(888B|888C|888D|888E)", source: "Lala" } +# This can be +1.2s if it's on the final square instead of penultimate. +2054.1 "Radiance 2" #Ability { id: "8894", source: "Arcane Globe" } +2061.2 "Targeted Light" Ability { id: "8CDE", source: "Lala" } + +2072.5 "Strategic Strike" Ability { id: "88AD", source: "Lala" } + +2085.6 "Planar Tactics" Ability { id: "8898", source: "Lala" } +2100.7 "Arcane Mine" Ability { id: "8899", source: "Lala" } +2108.6 "Symmetric Surge x2" Ability { id: "889E", source: "Lala" } + +2112.8 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } +2122.9 "Strategic Strike" Ability { id: "88AD", source: "Lala" } + +2131.1 "--middle--" Ability { id: "8874", source: "Lala" } +2138.3 "Spatial Tactics" Ability { id: "88A0", source: "Lala" } +2143.4 "Arcane Array 2" Ability { id: "889F", source: "Lala" } +2149.2 "Bright Pulse" Ability { id: "8891", source: "Lala" } +2150.5 "Inferno Divide 1" Ability { id: "8893", source: "Arcane Font" } window 1,1 +2150.5 "Radiance" Ability { id: "8D1F", source: "Arcane Globe" } +2152.9 "Inferno Divide 2" Ability { id: "8893", source: "Arcane Font" } window 1,1 +2156.6 "Inferno Divide 3" Ability { id: "8893", source: "Arcane Font" } window 1,1 +2157.6 "Angular Addition" Ability { id: "(8889|8D2E)", source: "Lala" } +2157.8 "Inferno Divide 4" Ability { id: "8893", source: "Arcane Font" } window 1,1 +2160.2 "Inferno Divide 5" Ability { id: "8893", source: "Arcane Font" } window 1,1 +2165.6 "Arcane Blight" Ability { id: "(888B|888C|888D|888E)", source: "Lala" } + +2173.7 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } +2184.8 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } + +2196.9 "Symmetric Surge" Ability { id: "88A1", source: "Lala" } +2202.0 "Constructive Figure" Ability { id: "88A3", source: "Lala" } +2207.1 "Arcane Plot" Ability { id: "88A2", source: "Lala" } +2212.9 "Bright Pulse" Ability { id: "8891", source: "Lala" } +2221.2 "Arcane Point" Ability { id: "88A5", source: "Lala" } +2221.8 "Aero II" Ability { id: "88A4", source: "Aloalo Golem" } +2222.0 "Powerful Light" Ability { id: "88A6", source: "Lala" } +2230.3 "Explosive Theorem" Ability { id: "88A7", source: "Lala" } +2234.7 "Symmetric Surge x2" Ability { id: "889E", source: "Lala" } +2235.4 "Telluric Theorem" Ability { id: "88A9", source: "Lala" } + +2248.4 "Strategic Strike" Ability { id: "88AD", source: "Lala" } +2256.5 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } + +2263.6 "--middle--" Ability { id: "8874", source: "Lala" } +2269.0 "Analysis" Ability { id: "8895", source: "Lala" } +2274.1 "Arcane Array 3" Ability { id: "8890", source: "Lala" } +2279.2 "Angular Addition" Ability { id: "(8889|8D2E)", source: "Lala" } +2279.9 "Bright Pulse" Ability { id: "8891", source: "Lala" } +2281.2 "Radiance 1" Ability { id: "8894", source: "Arcane Globe" } +2287.3 "Arcane Blight" Ability { id: "(888B|888C|888D|888E)", source: "Lala" } +2290.9 "Radiance 2" Ability { id: "8894", source: "Arcane Globe" } +2295.6 "Targeted Light" Ability { id: "8CDE", source: "Lala" } +2306.7 "Strategic Strike" Ability { id: "88AD", source: "Lala" } + +2316.8 "Inferno Theorem" Ability { id: "88AE", source: "Lala" } + +2319.9 "--sync--" StartsUsing { id: "8C25", source: "Lala" } window 20,20 +2329.9 "Inferno Theorem Enrage" Ability { id: "8C25", source: "Lala" } + + +# ALL ENCOUNTER ABILITIES +# 368 attack auto damage from Lala +# 8874 --sync-- repositioning for Lala +# 8889 Angular Addition self-targeted ability to give boss III +# 888B Arcane Blight self-targeted cast for initial back-safe 270 degree rotating cleave +# 888C Arcane Blight self-targeted cast for initial front-safe 270 degree rotating cleave +# 888D Arcane Blight self-targeted cast for initial east-safe 270 degree rotating cleave +# 888E Arcane Blight self-targeted cast for initial west-safe 270 degree rotating cleave +# 888F Arcane Blight cast and damage from 270 degree rotating cleave +# 8890 Arcane Array self-targeted cast to summon moving blue squares (#1) +# 8891 Bright Pulse cast and damage for initial blue square +# 8892 Bright Pulse damage from moving blue square +# 8893 Inferno Divide orange square cross explosion damage during Spatial Tactics +# 8894 Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #1, #3) +# 8895 Analysis self-targeted cast before giving players +# 8898 Planar Tactics self-targeted cast before Arcane Mines +# 8899 Arcane Mine self-targeted cast to create 8 Arcane Mine squares +# 889A Arcane Mine cast and damage for initial Arcane Mine squares +# 889B Arcane Combustion damage from walking over an Arcane Mine +# 889C Massive Explosion damage from failing to resolve Subractive Suppressor Alpha +# 889D Massive Explosion damage from failing to resolve Subractive Suppressor Beta +# 889E Symmetric Surge damage from two person stack that gives magic vuln up +# 889F Arcane Array self-targeted cast to summon moving blue squares (#2) +# 88A0 Spatial Tactics self-targeted cast prior to Arcane Array 2 +# 88A1 Symmetric Surge self-targeted cast before this mechanic +# 88A2 Arcane Plot self-targeted cast to summon blue squares for Symmetric Surge +# 88A3 Constructive Figure self-targeted cast that summons Aloalo Golem on edge +# 88A4 Aero II cast and line damage from Aloalo Golem during Symmetric Surge +# 88A5 Arcane Point self-targeted cast that gives players 88A6 Powerful Light spreads +# 88A6 Powerful Light spread damage on players that turn the squares they are on blue +# 88A7 Explosive Theorem self-targeted cast for very large spreads +# 88A8 Explosive Theorem cast and damage on players for spreads with Telluric Theorem puddles +# 88A9 Telluric Theorem cast and damage for large puddles from Explosive Theorem +# 88AD Strategic Strike cast and damage for non-cleaving 3x tankbuster +# 88AE Inferno Theorem cast and raidwide damage +# 8C25 Inferno Theorem cast and enrage damage +# 8CDE Targeted Light self-targeted cast for weak spot boss tether +# 8CDF Targeted Light cast and damage on players for 8CDE +# 8D1F Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #2) +# 8D2E Angular Addition self-targeted ability to give boss V + + +#~~~~~~~~~~~~~~~~~~~~# +# STATICE WITH A GUN # +#~~~~~~~~~~~~~~~~~~~~# + +# -p 8949:3013.8 +# -ii 8947 8925 8926 895A 895B 894C 8987 8A6A 895F 8960 8CC2 8982 8CBE 89F9 8C24 8957 8958 +# -it Statice + +# Midnight Trial will be sealed off +3000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1148" } window 10000,0 +3008.8 "--sync--" StartsUsing { id: "8949", source: "Statice" } window 20,20 +3013.8 "Aero IV" Ability { id: "8949", source: "Statice" } + +3018.0 "--middle--" Ability { id: "8927", source: "Statice" } +3023.4 "Trick Reload" Ability { id: "894A", source: "Statice" } +3038.2 "Trapshooting 1" Ability { id: "8D1A", source: "Statice" } +3048.6 "Trigger Happy" Ability { id: "894B", source: "Statice" } +3056.5 "Ring a Ring o' Explosions" Ability { id: "895C", source: "Statice" } +3070.6 "Trapshooting 2" Ability { id: "8959", source: "Statice" } +3074.6 "Burst" Ability { id: "895D", source: "Bomb" } + +3081.8 "--middle--" Ability { id: "8927", source: "Statice" } +3087.4 "Trick Reload" Ability { id: "894A", source: "Statice" } +3101.2 "Ring a Ring o' Explosions" Ability { id: "895C", source: "Statice" } +3106.3 "Dartboard of Dancing Explosives " Ability { id: "8CBD", source: "Statice" } +3120.1 "Trapshooting 1" Ability { id: "8959", source: "Statice" } +3122.0 "Burst" Ability { id: "895D", source: "Bomb" } +3125.3 "Uncommon Ground" Ability { id: "8954", source: "Statice" } + +3132.3 "--middle--" Ability { id: "8927", source: "Statice" } +3137.7 "Surprise Balloon" Ability { id: "894D", source: "Statice" } +3144.8 "Beguiling Glitter" Ability { id: "8963", source: "Statice" } +3149.8 "Surprise Needle 1" #Ability { id: "894F", source: "Needle" } +3150.7 "Pop 1" Ability { id: "894E", source: "Statice" } +3151.5 "Surprise Needle 2" #Ability { id: "894F", source: "Needle" } +3152.4 "Trigger Happy" Ability { id: "894B", source: "Statice" } +3153.2 "Surprise Needle 3" #Ability { id: "894F", source: "Needle" } +3154.9 "Surprise Needle 4" #Ability { id: "894F", source: "Needle" } +3155.6 "Pop 2" Ability { id: "894E", source: "Statice" } +3160.4 "Trapshooting 2" Ability { id: "8959", source: "Statice" } +3172.5 "Aero IV" Ability { id: "8949", source: "Statice" } + +3179.7 "--untargetable--" +3179.7 "--middle--" Ability { id: "8927", source: "Statice" } +3184.1 "Ring a Ring o' Explosions" Ability { id: "895C", source: "Statice" } +3189.2 "Present Box 1" Ability { id: "8955", source: "Statice" } +3194.3 "Fireworks (cast)" Ability { id: "895E", source: "Statice" } +3204.0 "Fireworks" Ability { id: "895F", source: "Statice" } +3204.2 "Burst" Ability { id: "895D", source: "Bomb" } +3204.3 "Faerie Ring" Ability { id: "8956", source: "Surprising Staff" } + +3206.3 "--targetable--" +3212.5 "Shocking Abandon" Ability { id: "8948", source: "Statice" } + +3222.7 "Pinwheeling Dartboard" Ability { id: "8CBC", source: "Statice" } +3231.4 "Fireworks (cast)" Ability { id: "895E", source: "Statice" } +3233.4 "Fire Spread" Ability { id: "8952", source: "Statice" } duration 11 +3241.3 "Fireworks" Ability { id: "895F", source: "Statice" } +3241.6 "Uncommon Ground" Ability { id: "8954", source: "Statice" } +3253.6 "Aero IV" Ability { id: "8949", source: "Statice" } + +3260.8 "--middle--" Ability { id: "8927", source: "Statice" } +3266.5 "Beguiling Glitter" Ability { id: "8963", source: "Statice" } +3273.6 "Trick Reload" Ability { id: "894A", source: "Statice" } +3288.4 "Trapshooting 1" Ability { id: "8D1A", source: "Statice" } +3295.5 "Present Box 2" Ability { id: "8955", source: "Statice" } +3302.2 "Ring a Ring o' Explosions" Ability { id: "895C", source: "Statice" } +3309.6 "Trigger Happy" Ability { id: "894B", source: "Statice" } +3310.2 "Faerie Ring" Ability { id: "8956", source: "Surprising Staff" } +3316.5 "Trapshooting 2" Ability { id: "8959", source: "Statice" } +3320.4 "Burst" Ability { id: "895D", source: "Bomb" } + +3327.6 "Aero IV" Ability { id: "8949", source: "Statice" } +3335.7 "Aero IV" Ability { id: "8949", source: "Statice" } + +3338.8 "--sync--" StartsUsing { id: "8C23", source: "Statice" } +3348.8 "Aero IV Enrage" Ability { id: "8C23", source: "Statice" } + + +# ALL ENCOUNTER ABILITIES +# 8925 Locked and Loaded ability during 894A Trick Reload when a bullet is in the gun +# 8926 Misload ability during 894A Trick Reload when a bullet missed the gun oops +# 8927 --sync-- repositioning from Statice +# 8947 --sync-- auto damage from Statice +# 8948 Shocking Abandon cast and tankbuster damage +# 8949 Aero IV cast and raidwide damage +# 894A Trick Reload self-targeted cast to load gun with 8925/8926 +# 894B Trigger Happy self-targeted cast for limit cut dart board +# 894C Trigger Happy cast and damage for limit cut dart board (filled pie slice) +# 894D Surprise Balloon self-targeted cast +# 894E Pop knockback from Surprise Balloon being popped +# 894F Surprise Needle short cast and ability blue line aoe from needle adds that pop balloons +# 8954 Uncommon Ground light damage on people who are not on a dartboard color with Bull's-eye +# 8955 Present Box self-targeted cast for bombs/donuts/missiles/hands +# 8956 Faerie Ring cast and damage for donut rings during Present Box +# 8957 Burst high damage from running into Surprising Missile tethered add +# 8958 Death by Claw high damage from running into Surprising Claw tethered add +# 8959 Trapshooting self-targeted cast after Trick Reload (some instances are 8D1A) +# 895A Trapshooting stack damage from Trick Reload +# 895B Trapshooting spread damage from Trick Reload +# 895C Ring a Ring o' Explosions self-targeted cast for rotating bombs +# 895D Burst cast and damage from bomb explosion +# 895E Fireworks self-targeted cast +# 895F Fireworks two person stack damage during Present Box / Pinwheeling Dartboard +# 8960 Fireworks spread damage during Present Box / Pinwheeling Dartboard +# 8963 Beguiling Glitter self-targeted cast to give players Face debuffs +# 8982 Fire Spread self-targeted damage for initial rotating fire (from Ball of Fire) +# 8987 Trigger Happy cast and zero damage for limit cut dart board (empty pie slice) +# 89F9 Fire Spread ongoing rotating fire damage (from Statice) +# 8A6A --sync-- ability on Bomb when rotating +# 8C23 Aero IV cast and enrage damage +# 8C24 Aero IV post-enrage follow-up damage just in case +# 8CBC Pinwheeling Dartboard self-targeted cast to summon dartboard with rotating fire +# 8CBD Dartboard of Dancing Explosives self-targeted cast for colored dartboard +# 8CBE Burning Chains damage from not breaking chains +# 8CC2 Uncommon Ground heavy damage on people who are on the same dartboard color with Bull's-eye +# 8D1A Trapshooting self-targeted cast after Trick Reload (some instances are 8959) + + +#~~~~~~~~~# +# TRASH 1 # +#~~~~~~~~~# + +# ALL ENCOUNTER ABILITIES +# 7A56 --sync-- various auto damage (trash 1) +# 8BC0 --sync-- damage from Twister tornados +# 8C6E Lead Hook casted damage from Kiwakin 3x tankbuster +# 8C62 Lead Hook damage from hit 2 +# 8C53 Lead Hook damage from hit 3 +# 8C64 Water III casted damage from Snipper stack marker +# 8C63 Sharp Strike casted damage from Kiwakin tank buster with a concussion dot +# 8BB8 Tail Screw casted damage from Kiwakin baited circle +# 8BB9 Bubble Shower casted damage from Snipper front conal +# 8BBA Crab Dribble fast casted damage from Snipper back conal after Bubble Shower 8BB9 +# 8BBD Hydrocannon casted damage from Ray front line +# 8BBF Expulsion casted damage from Ray "get out" +# 8BBE Electric Whorl casted damage from Ray "get in" +# 8C65 Hydroshot casted damage from Monk knockback line with a dot +# 8BBB Cross Attack casted damage from Monk tankbuster + +#~~~~~~~~~# +# TRASH 2 # +#~~~~~~~~~# + +# TODO: does Wood Golem have an enrage?? + +# ALL ENCOUNTER ABILITIES +# 7A58 --sync-- various auto damage (trash 2) +# 8BC1 Ovation cast and damage from Wood Golem front line aoe +# 8BC5 Gravity Force cast and stack damage from Islekeeper +# 8C2F Ancient Quaga cast and damage for Islekeeper raidwide enrage +# 8C4C Ancient Aero III interruptable cast and damage for Wood Golem raidwide +# 8C4D Tornado cast and damage from Wood Golem that binds the initial target and heavies all targets +# 8C4E Ancient Quaga cast and damage for Islekeeper raidwide +# 8C6F Isle Drop cast and damage for Islekeeper front circle diff --git a/util/sync_files.ts b/util/sync_files.ts index 9e28e36d3b..c2700fb073 100644 --- a/util/sync_files.ts +++ b/util/sync_files.ts @@ -23,7 +23,7 @@ export type ZoneReplace = { const zoneReplace: ZoneReplace[] = [ { - // Criterion + // Sildihn Criterion fileMap: { 'ui/raidboss/data/06-ew/dungeon/another_sildihn_subterrane.ts': 'ui/raidboss/data/06-ew/dungeon/another_sildihn_subterrane-savage.ts', @@ -274,7 +274,7 @@ const zoneReplace: ZoneReplace[] = [ }, }, { - // Criterion + // Mount Rokkon Criterion fileMap: { 'ui/raidboss/data/06-ew/dungeon/another_mount_rokkon.ts': 'ui/raidboss/data/06-ew/dungeon/another_mount_rokkon-savage.ts', @@ -555,6 +555,177 @@ const zoneReplace: ZoneReplace[] = [ '14B5': '89F4', // Comet }, }, + { + // Aloalo Criterion + fileMap: { + 'ui/raidboss/data/06-ew/dungeon/another_aloalo_island.ts': + 'ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.ts', + 'ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island.ts': + 'ui/oopsyraidsy/data/06-ew/dungeon/another_aloalo_island-savage.ts', + 'ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt': + 'ui/raidboss/data/06-ew/dungeon/another_aloalo_island-savage.txt', + }, + prefix: { 'AAI': 'AAIS' }, + other: { + 'AnotherAloaloIsland': 'AnotherAloaloIslandSavage', + 'another_aloalo_island.txt': 'another_aloalo_island-savage.txt', + '# Another Aloalo Island': '# Another Aloalo Island (Savage)', + '\(\'AAI ': '\(\'AAIS ', + }, + // eslint-disable-next-line max-len + // grep "^# [A-F0-9]\{4\} " ui/raidboss/data/06-ew/dungeon/another_aloalo_island.txt | sort | sed "s/^..//" | sed "s/^\(....\) \(.*\)$/ '\1': 'TODO', \/\/ \2/" + // TODO: missing various enrages (both listed and likely unlisted) + id: { + '7A56': '7A56', // --sync-- various auto damage (trash 1) + '7A58': '7A58', // --sync-- various auto damage (trash 2) + '8874': '8874', // --sync-- repositioning for Lala + '8889': '8BE0', // Angular Addition self-targeted ability to give boss III + '888B': '8BE2', // Arcane Blight self-targeted cast for initial back-safe 270 degree rotating cleave + '888C': '8BE3', // Arcane Blight self-targeted cast for initial front-safe 270 degree rotating cleave + '888D': '8BE4', // Arcane Blight self-targeted cast for initial east-safe 270 degree rotating cleave + '888E': '8BE5', // Arcane Blight self-targeted cast for initial west-safe 270 degree rotating cleave + '888F': '8BE6', // Arcane Blight cast and damage from 270 degree rotating cleave + '8890': '8BE7', // Arcane Array self-targeted cast to summon moving blue squares (#1) + '8891': '8BE8', // Bright Pulse cast and damage for initial blue square + '8892': '8BE9', // Bright Pulse damage from moving blue square + '8893': '8BEA', // Inferno Divide orange square cross explosion damage during Spatial Tactics + '8894': '8BEB', // Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #1, #3) + '8895': '8BEC', // Analysis self-targeted cast before giving players + '8898': '8BEF', // Planar Tactics self-targeted cast before Arcane Mines + '8899': '8BF0', // Arcane Mine self-targeted cast to create 8 Arcane Mine squares + '889A': '8BF1', // Arcane Mine cast and damage for initial Arcane Mine squares + '889B': '8BF2', // Arcane Combustion damage from walking over an Arcane Mine + '889C': '8BF3', // Massive Explosion damage from failing to resolve Subractive Suppressor Alpha + '889D': '8BF4', // Massive Explosion damage from failing to resolve Subractive Suppressor Beta + '889E': '8BF5', // Symmetric Surge damage from two person stack that gives magic vuln up + '889F': '8BF6', // Arcane Array self-targeted cast to summon moving blue squares (#2) + '88A0': '8BF7', // Spatial Tactics self-targeted cast prior to Arcane Array 2 + '88A1': '8BF8', // Symmetric Surge self-targeted cast before this mechanic + '88A2': '8BF9', // Arcane Plot self-targeted cast to summon blue squares for Symmetric Surge + '88A3': '8BFA', // Constructive Figure self-targeted cast that summons Aloalo Golem on edge + '88A4': '8BFB', // Aero II cast and line damage from Aloalo Golem during Symmetric Surge + '88A5': '8BFC', // Arcane Point self-targeted cast that gives players 88A6 Powerful Light spreads + '88A6': '8BFD', // Powerful Light spread damage on players that turn the squares they are on blue + '88A7': '8BFE', // Explosive Theorem self-targeted cast for very large spreads + '88A8': '8BFF', // Explosive Theorem cast and damage on players for spreads with Telluric Theorem puddles + '88A9': '8C00', // Telluric Theorem cast and damage for large puddles from Explosive Theorem + '88AD': '8C04', // Strategic Strike cast and damage for non-cleaving 3x tankbuster + '88AE': '8C05', // Inferno Theorem cast and raidwide damage + '8925': '8925', // Locked and Loaded ability during 894A Trick Reload when a bullet is in the gun + '8926': '8926', // Misload ability during 894A Trick Reload when a bullet missed the gun oops + '8927': '8927', // --sync-- repositioning from Statice + '8947': '8964', // --sync-- auto damage from Statice + '8948': '8965', // Shocking Abandon cast and tankbuster damage + '8949': '8966', // Aero IV cast and raidwide damage + '894A': '8967', // Trick Reload self-targeted cast to load gun with 8925/8926 + '894B': '8968', // Trigger Happy self-targeted cast for limit cut dart board + '894C': '8969', // Trigger Happy cast and damage for limit cut dart board (filled pie slice) + '894D': '8927', // Surprise Balloon self-targeted cast + '894E': '896B', // Pop knockback from Surprise Balloon being popped + '894F': '896C', // Surprise Needle short cast and ability blue line aoe from needle adds that pop balloons + '8954': '8971', // Uncommon Ground light damage on people who are not on a dartboard color with Bull's-eye + '8955': '8972', // Present Box self-targeted cast for bombs/donuts/missiles/hands + '8956': '8973', // Faerie Ring cast and damage for donut rings during Present Box + '8957': '8974', // Burst high damage from running into Surprising Missile tethered add + '8958': '8975', // Death by Claw high damage from running into Surprising Claw tethered add + '8959': '8976', // Trapshooting self-targeted cast after Trick Reload (some instances are 8D1A) + '895A': '8977', // Trapshooting stack damage from Trick Reload + '895B': '8978', // Trapshooting spread damage from Trick Reload + '895C': '8979', // Ring a Ring o' Explosions self-targeted cast for rotating bombs + '895D': '897A', // Burst cast and damage from bomb explosion + '895E': '897B', // Fireworks self-targeted cast + '895F': '897C', // Fireworks two person stack damage during Present Box / Pinwheeling Dartboard + '8960': '897D', // Fireworks spread damage during Present Box / Pinwheeling Dartboard + '8963': '8980', // Beguiling Glitter self-targeted cast to give players Face debuffs + '8982': '896F', // Fire Spread self-targeted damage for initial rotating fire (from Ball of Fire) + '8987': '8988', // Trigger Happy cast and zero damage for limit cut dart board (empty pie slice) + '89F9': '89FB', // Fire Spread ongoing rotating fire damage (from Statice) + '8A6A': '8A6A', // --sync-- ability on Bomb when rotating + '8A77': '8A77', // --sync-- Ketuduke repositioning + '8A82': '8A82', // Riptide ability on players from Angry Seas Airy Bubble when you step in one + '8A83': '8A83', // Fetters ability on players from Angry Seas Airy Bubble when you step in one after 8A82 Riptide + '8AA7': '8AA7', // --sync-- auto damage from Ketuduke + '8AA8': '8AA8', // Spring Crystals cast and ability to summon Spring Crystal adds (all flavors) + '8AA9': '8AD9', // 衝撃 self-targeted ability from Spring Crystal orbs + '8AAA': '8ADA', // 衝撃 self-targeted ability from Spring Crystal rupees + '8AAB': '8ADB', // Saturate cast and damage from Spring Crystal orb circle + '8AAC': '8ADC', // Saturate cast and damage from Spring Crystal rupee line laser + '8AAD': '8AAD', // Bubble Net self-targeted cast before Bubbles along with 8AAE during Spring Crystals 1 + '8AAE': '8ADD', // Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 1 + '8AAF': '8AAF', // Fluke Typhoon self-targeted cast before 8AB0 knockback during Spring Crystals 3 + '8AB0': '8AB0', // Fluke Typhoon cast and knockback ability on Spring Crystal and players during Spring Crystals 3 + '8AB1': '8AB1', // Fluke Gale self-targeted cast that adds limit cut winds + '8AB2': '8AB2', // Fluke Gale cast and ability for limit cut 1 wind + '8AB3': '8AB3', // Fluke Gale cast and ability for limit cut 2 wind + '8AB4': '8AB4', // Hydrofall self-targeted cast that adds stack markers + '8AB5': '8AB5', // Hydrofall self-targeted "stack second" ability before Blowing Bubbles + '8AB6': '8AB6', // Hydrofall ability on players that adds stack debuffs + '8AB7': '8ADE', // Hydrofall damage from stack debuffs + '8AB8': '8AB8', // Hydrobullet self-targeted cast that adds stack markers + '8AB9': '8AB9', // Hydrobullet ability on players that adds spread debuffs + '8ABA': '8ADF', // Hydrobullet damage from spread debuffs + '8ABB': '8ABB', // Strewn Bubbles self-targeted cast before 8ABC Sphere Shatter moving arches + '8ABC': '8AE0', // Sphere Shatter damage from moving arches + '8ABD': '8ABD', // Blowing Bubbles self-targeted cast that adds Airy Bubble Adds + '8ABE': '8ABE', // Riptide ability on players from Blowing Bubbles Airy Bubble when you step in one + '8ABF': '8ABF', // Fetters ability on players from Blowing Bubbles Airy Bubble when you step in one after 8ABE Riptide + '8AC0': '8AC0', // Angry Seas self-targeted cast for 8AC1 red line knockback + '8AC1': '8AE1', // Angry Seas cast and knockback damage from red line + '8AC2': '8AE2', // Burst tower damage + '8AC3': '8AE3', // Big Burst tower failure damage + '8AC4': '8AC4', // Roar self-targeted cast that summons Zaratan adds + '8AC5': '8AC5', // Bubble Net self-targeted cast before Bubbles along with 8AC6 during Spring Crystals 2 + '8AC6': '8AE4', // Bubble Net cast and ability on players that adds Bubbles/Fetters debuffs during Spring Crystals 2 + '8AC7': '8AC7', // Updraft self-targeted cast to boost adds and players into the air + '8AC8': '8AC8', // Updraft ability on players for 8AC7 Updraft + '8AC9': '8AE5', // Hundred Lashings cast and damage for non-bubbled Zaratan 180 cleave (no damage on bubbled players) + '8ACA': '8ACA', // Hundred Lashings self-targeted cast for bubbled Zaratan adds + '8ACB': '8AE6', // Hundred Lashings cast and damage for bubbled Zaratan 180 cleave (no damage on non-bubbled players) + '8ACC': '8AE7', // Receding Twintides cast and damage for initial out during out->in + '8ACD': '8AE8', // Near Tide fast cast and damage for second out during in->out with 8ACE Encroaching Twintides + '8ACE': '8AE9', // Encroaching Twintides cast and damage for initial in during in->out + '8ACF': '8AEA', // Far Tide fast cast and damage for second in during out->in with 8ACC Receding Twintides + '8AD0': '8AD0', // Hydrobomb self-targeted cast for 8AD1 puddles + '8AD1': '8AEB', // Hydrobomb cast and damage for 3x puddles duruing 8ABD Blowing Bubbles + '8AD4': '8AD4', // Tidal Roar self-targeted cast for raidwide aoe + '8AD5': '8AED', // Tidal Roar damage from 8AD4 + '8AD6': 'TODO', // Tidal Roar cast and enrage damage + '8BB8': '8BC9', // Tail Screw casted damage from Kiwakin baited circle + '8BB9': '8BCA', // Bubble Shower casted damage from Snipper front conal + '8BBA': '8BCB', // Crab Dribble fast casted damage from Snipper back conal after Bubble Shower 8BB9 + '8BBB': '8C4F', // Cross Attack casted damage from Monk tankbuster + '8BBD': '8C4B', // Hydrocannon casted damage from Ray front line + '8BBE': '8BCD', // Electric Whorl casted damage from Ray "get in" + '8BBF': '8BCE', // Expulsion casted damage from Ray "get out" + '8BC0': '8BCF', // --sync-- damage from Twister tornados + '8BC1': '8BD4', // Ovation cast and damage from Wood Golem front line aoe + '8BC5': '8C3A', // Gravity Force cast and stack damage from Islekeeper + '8C23': 'TODO', // Aero IV cast and enrage damage + '8C24': 'TODO', // Aero IV post-enrage follow-up damage just in case + '8C25': 'TODO', // Inferno Theorem cast and enrage damage + '8C2F': 'TODO', // Ancient Quaga cast and damage for Islekeeper raidwide enrage + '8C4C': '8BD2', // Ancient Aero III interruptable cast and damage for Wood Golem raidwide + '8C4D': '8BD3', // Tornado cast and damage from Wood Golem that binds the initial target and heavies all targets + '8C4E': '8C39', // Ancient Quaga cast and damage for Islekeeper raidwide + '8C53': '8BC4', // Lead Hook damage from hit 3 + '8C62': '8BC6', // Lead Hook damage from hit 2 + '8C63': '8BC8', // Sharp Strike casted damage from Kiwakin tank buster with a concussion dot + '8C64': '8BCC', // Water III casted damage from Snipper stack marker + '8C65': '8BD1', // Hydroshot casted damage from Monk knockback line with a dot + '8C6D': '8C6D', // Hydrobullet self-targeted "spread second" ability before Blowing Bubbles + '8C6E': '8BC7', // Lead Hook casted damage from Kiwakin 3x tankbuster + '8C6F': '8C3C', // Isle Drop cast and damage for Islekeeper front circle + '8CBC': '8CBF', // Pinwheeling Dartboard self-targeted cast to summon dartboard with rotating fire + '8CBD': '8CC0', // Dartboard of Dancing Explosives self-targeted cast for colored dartboard + '8CBE': '8CC1', // Burning Chains damage from not breaking chains + '8CC2': '8CC3', // Uncommon Ground heavy damage on people who are on the same dartboard color with Bull's-eye + '8CDE': '8CE0', // Targeted Light self-targeted cast for weak spot boss tether + '8CDF': '8CE1', // Targeted Light cast and damage on players for 8CDE + '8D1A': '8D1C', // Trapshooting self-targeted cast after Trick Reload (some instances are 8959) + '8D1F': '8D20', // Radiance damage from Arcane Globe being hit by a blue square (Arcane Array #2) + '8D2E': '8D2F', // Angular Addition self-targeted ability to give boss V + }, + }, ]; const processFile = (filename: string, zone: ZoneReplace, inputText: string): string => {