diff --git a/ui/raidboss/data/07-dt/raid/r12s.ts b/ui/raidboss/data/07-dt/raid/r12s.ts new file mode 100644 index 00000000000..84b26c61271 --- /dev/null +++ b/ui/raidboss/data/07-dt/raid/r12s.ts @@ -0,0 +1,3944 @@ +import Conditions from '../../../../../resources/conditions'; +import { UnreachableCode } from '../../../../../resources/not_reached'; +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import { + DirectionOutput8, + DirectionOutputCardinal, + DirectionOutputIntercard, + Directions, +} from '../../../../../resources/util'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export type Phase = + | 'doorboss' + | 'curtainCall' + | 'slaughtershed' + | 'replication1' + | 'replication2' + | 'reenactment1' + | 'idyllic' + | 'reenactment2'; + +type DirectionCardinal = Exclude; +type DirectionIntercard = Exclude; + +export interface Data extends RaidbossData { + readonly triggerSetConfig: { + uptimeKnockbackStrat: true | false; + portentStrategy: 'dn' | 'zenith' | 'none'; + }; + phase: Phase; + // Phase 1 + grotesquerieCleave?: + | 'rightCleave' + | 'leftCleave' + | 'frontCleave' + | 'rearCleave'; + myFleshBonds?: 'alpha' | 'beta'; + inLine: { [name: string]: number }; + blobTowerDirs: string[]; + fleshBondsCount: number; + skinsplitterCount: number; + cellChainCount: number; + myMitoticPhase?: string; + hasRot: boolean; + // Phase 2 + actorPositions: { [id: string]: { x: number; y: number; heading: number } }; + replicationCounter: number; + replication1Debuff?: 'fire' | 'dark'; + replication1FireActor?: string; + replication1FireActor2?: string; + replication1FollowUp: boolean; + replication2CloneDirNumPlayers: { [dirNum: number]: string }; + replication2DirNumAbility: { [dirNum: string]: string }; + replication2PlayerAbilities: { [player: string]: string }; + replication2BossId?: string; + replication2PlayerOrder: string[]; + replication2AbilityOrder: string[]; + netherwrathFollowup: boolean; + myMutation?: 'alpha' | 'beta'; + manaSpheres: { + [id: string]: 'lightning' | 'fire' | 'water' | 'wind' | 'blackHole'; + }; + westManaSpheres: { [id: string]: { x: number; y: number } }; + eastManaSpheres: { [id: string]: { x: number; y: number } }; + closeManaSphereIds: string[]; + firstBlackHole?: 'east' | 'west'; + manaSpherePopSide?: 'east' | 'west'; + twistedVisionCounter: number; + replication3CloneOrder: number[]; + replication3CloneDirNumPlayers: { [dirNum: number]: string }; + idyllicVision2NorthSouthCleaveSpot?: 'north' | 'south'; + idyllicDreamActorEW?: string; + idyllicDreamActorNS?: string; + idyllicDreamActorSnaking?: string; + replication4DirNumAbility: { [dirNum: number]: string }; + replication4PlayerAbilities: { [player: string]: string }; + replication4BossCloneDirNumPlayers: { [dirNum: number]: string }; + replication4PlayerOrder: string[]; + replication4AbilityOrder: string[]; + hasLightResistanceDown: boolean; + twistedVision4MechCounter: number; + doomPlayers: string[]; + hasDoom: boolean; + hasPyretic: boolean; + idyllicVision8SafeSides?: 'frontBack' | 'sides'; + idyllicVision7SafeSides?: 'frontBack' | 'sides'; + idyllicVision7SafePlatform?: 'east' | 'west'; +} + +const headMarkerData = { + // Phase 1 + // VFX: com_share3t + 'stack': '00A1', + // VFX: tank_lockonae_6m_5s_01t + 'tankbuster': '0158', + // VFX: VFX: x6rc_cellchain_01x + 'cellChain': '0291', + // VFX: com_share3_7s0p + 'slaughterStack': '013D', + // VFX: target_ae_s7k1 + 'slaughterSpread': '0177', + 'cellChainTether': '016E', + // Phase 2 + // VFX: sharelaser2tank5sec_c0k1, used by Double Sobat (B520) + 'sharedTankbuster': '0256', + // Replication 2 Tethers + 'lockedTether': '0175', // Clone tethers + 'projectionTether': '016F', // Comes from Lindschrat, B4EA Grotesquerie + B4EB Hemorrhagic Projection cleave based on player facing + 'manaBurstTether': '0170', // Comes from Lindschrat, B4E7 Mana Burst defamation + 'heavySlamTether': '0171', // Comes from Lindschrat, B4E8 Heavy Slam stack with projection followup + 'fireballSplashTether': '0176', // Comes from the boss, B4E4 Fireball Splash baited jump +} as const; + +const center = { + x: 100, + y: 100, +} as const; + +const phaseMap: { [id: string]: Phase } = { + 'BEC0': 'curtainCall', + 'B4C6': 'slaughtershed', + 'B509': 'idyllic', +}; + +const isCardinalDir = (dir: DirectionOutput8): dir is DirectionCardinal => { + return (Directions.outputCardinalDir as string[]).includes(dir); +}; + +const isIntercardDir = (dir: DirectionOutput8): dir is DirectionIntercard => { + return (Directions.outputIntercardDir as string[]).includes(dir); +}; + +const triggerSet: TriggerSet = { + id: 'AacHeavyweightM4Savage', + zoneId: ZoneId.AacHeavyweightM4Savage, + config: [ + { + id: 'uptimeKnockbackStrat', + name: { + en: 'Enable uptime knockback strat', + de: 'Aktiviere Uptime Rückstoß Strategie', + fr: 'Activer la strat Poussée-Uptime', + ja: 'エデン零式共鳴編4層:cactbot「ヘヴンリーストライク (ノックバック)」ギミック', // FIXME + cn: '启用击退镜 uptime 策略', + ko: '정확한 타이밍 넉백방지 공략 사용', + tc: '啟用擊退鏡 uptime 策略', + }, + comment: { + en: `If you want cactbot to callout Raptor Knuckles double knockback, enable this option. + Callout happens during/after first animation and requires <1.8s reaction time + to avoid both Northwest and Northeast knockbacks. + NOTE: This will call for each set.`, + }, + type: 'checkbox', + default: false, + }, + { + id: 'portentStrategy', + name: { + en: 'Phase 2 Tower Portent Resolution Strategy', + }, + type: 'select', + options: { + en: { + 'DN Strategy: Dark N Hitbox, Wind Middle Hitbox, Earth/Fire N/S Max Melee': 'dn', + 'Zenith Strategy: Wind N Max Melee, Earth/Dark Middle (Lean North), Fire S Max Melee': + 'zenith', + 'No strategy: call element and debuff': 'none', + }, + }, + default: 'none', + }, + ], + timelineFile: 'r12s.txt', + initData: () => ({ + phase: 'doorboss', + // Phase 1 + inLine: {}, + blobTowerDirs: [], + skinsplitterCount: 0, + fleshBondsCount: 0, + cellChainCount: 0, + hasRot: false, + // Phase 2 + actorPositions: {}, + replicationCounter: 0, + replication1FollowUp: false, + replication2CloneDirNumPlayers: {}, + replication2DirNumAbility: {}, + replication2PlayerAbilities: {}, + replication2PlayerOrder: [], + replication2AbilityOrder: [], + netherwrathFollowup: false, + manaSpheres: {}, + westManaSpheres: {}, + eastManaSpheres: {}, + closeManaSphereIds: [], + twistedVisionCounter: 0, + replication3CloneOrder: [], + replication3CloneDirNumPlayers: {}, + replication4DirNumAbility: {}, + replication4PlayerAbilities: {}, + replication4BossCloneDirNumPlayers: {}, + replication4PlayerOrder: [], + replication4AbilityOrder: [], + hasLightResistanceDown: false, + twistedVision4MechCounter: 0, + doomPlayers: [], + hasDoom: false, + hasPyretic: false, + }), + triggers: [ + { + id: 'R12S Phase Tracker', + type: 'StartsUsing', + netRegex: { id: Object.keys(phaseMap), source: 'Lindwurm' }, + suppressSeconds: 1, + run: (data, matches) => { + const phase = phaseMap[matches.id]; + if (phase === undefined) + throw new UnreachableCode(); + + data.phase = phase; + }, + }, + { + id: 'R12S Phase Two Staging Tracker', + // Due to the way the combatants are added in prior to the cast of Staging, this is used to set the phase + type: 'AddedCombatant', + netRegex: { name: 'Understudy', capture: false }, + condition: (data) => data.phase === 'replication1', + run: (data) => data.phase = 'replication2', + }, + { + id: 'R12S Phase Two Replication Tracker', + type: 'StartsUsing', + netRegex: { id: 'B4D8', source: 'Lindwurm', capture: false }, + run: (data) => { + if (data.replicationCounter === 0) + data.phase = 'replication1'; + data.replicationCounter = data.replicationCounter + 1; + }, + }, + { + id: 'R12S Phase Two Boss ID Collect', + // Store the boss' id later for checking against tether + // Using first B4E1 Staging + type: 'StartsUsing', + netRegex: { id: 'B4E1', source: 'Lindwurm', capture: true }, + condition: (data) => data.phase === 'replication2', + suppressSeconds: 9999, + run: (data, matches) => data.replication2BossId = matches.sourceId, + }, + { + id: 'R12S Phase Two Reenactment Tracker', + type: 'StartsUsing', + netRegex: { id: 'B4EC', source: 'Lindwurm', capture: false }, + run: (data) => { + if (data.phase === 'replication2') { + data.phase = 'reenactment1'; + return; + } + data.phase = 'reenactment2'; + }, + }, + { + id: 'R12S Phase Two Twisted Vision Tracker', + // Used for keeping track of phases in idyllic + type: 'StartsUsing', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: false }, + run: (data) => { + data.twistedVisionCounter = data.twistedVisionCounter + 1; + }, + }, + { + id: 'R12S Phase Two ActorSetPos Tracker', + type: 'ActorSetPos', + netRegex: { id: '4[0-9A-Fa-f]{7}', capture: true }, + condition: (data) => { + if ( + data.phase === 'replication1' || + data.phase === 'replication2' || + data.phase === 'idyllic' + ) + return true; + return false; + }, + run: (data, matches) => + data.actorPositions[matches.id] = { + x: parseFloat(matches.x), + y: parseFloat(matches.y), + heading: parseFloat(matches.heading), + }, + }, + { + id: 'R12S Phase Two ActorMove Tracker', + type: 'ActorMove', + netRegex: { id: '4[0-9A-Fa-f]{7}', capture: true }, + condition: (data) => { + if ( + data.phase === 'replication1' || + data.phase === 'replication2' || + data.phase === 'idyllic' + ) + return true; + return false; + }, + run: (data, matches) => + data.actorPositions[matches.id] = { + x: parseFloat(matches.x), + y: parseFloat(matches.y), + heading: parseFloat(matches.heading), + }, + }, + { + id: 'R12S Phase Two AddedCombatant Tracker', + type: 'AddedCombatant', + netRegex: { id: '4[0-9A-Fa-f]{7}', capture: true }, + condition: (data) => { + if ( + data.phase === 'replication1' || + data.phase === 'replication2' || + data.phase === 'idyllic' + ) + return true; + return false; + }, + run: (data, matches) => + data.actorPositions[matches.id] = { + x: parseFloat(matches.x), + y: parseFloat(matches.y), + heading: parseFloat(matches.heading), + }, + }, + { + id: 'R12S The Fixer', + type: 'StartsUsing', + netRegex: { id: 'B4D7', source: 'Lindwurm', capture: false }, + durationSeconds: 4.7, + response: Responses.bigAoe('alert'), + }, + { + id: 'R12S Directed Grotesquerie Direction Collect', + // Unknown_DE6 spell contains data in its count: + // 40C, Front Cone + // 40D, Right Cone + // 40E, Rear Cone + // 40F, Left Cone + type: 'GainsEffect', + netRegex: { effectId: 'DE6', capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + switch (matches.count) { + case '40C': + data.grotesquerieCleave = 'frontCleave'; + return; + case '40D': + data.grotesquerieCleave = 'rightCleave'; + return; + case '40E': + data.grotesquerieCleave = 'rearCleave'; + return; + case '40F': + data.grotesquerieCleave = 'leftCleave'; + return; + } + }, + }, + { + id: 'R12S Shared Grotesquerie', + type: 'GainsEffect', + netRegex: { effectId: '129A', capture: true }, + delaySeconds: 0.2, + durationSeconds: 17, + infoText: (data, matches, output) => { + const cleave = data.grotesquerieCleave; + const target = matches.target; + if (target === data.me) { + if (cleave === undefined) + return output.baitThenStack!({ stack: output.stackOnYou!() }); + return output.baitThenStackCleave!({ + stack: output.stackOnYou!(), + cleave: output[cleave]!(), + }); + } + + const player = data.party.member(target); + const isDPS = data.party.isDPS(target); + if (isDPS && data.role === 'dps') { + if (cleave === undefined) + return output.baitThenStack!({ + stack: output.stackOnPlayer!({ player: player }), + }); + return output.baitThenStackCleave!({ + stack: output.stackOnPlayer!({ player: player }), + cleave: output[cleave]!(), + }); + } + if (!isDPS && data.role !== 'dps') { + if (cleave === undefined) + return output.baitThenStack!({ + stack: output.stackOnPlayer!({ player: player }), + }); + return output.baitThenStackCleave!({ + stack: output.stackOnPlayer!({ player: player }), + cleave: output[cleave]!(), + }); + } + }, + outputStrings: { + stackOnYou: Outputs.stackOnYou, + stackOnPlayer: Outputs.stackOnPlayer, + frontCleave: { + en: 'Front Cleave', + de: 'Kegel Aoe nach Vorne', + fr: 'Cleave Avant', + ja: '口からおくび', + cn: '前方扇形', + ko: '전방 부채꼴 장판', + tc: '前方扇形', + }, + rearCleave: { + en: 'Rear Cleave', + de: 'Kegel Aoe nach Hinten', + fr: 'Cleave Arrière', + ja: '尻からおなら', + cn: '背后扇形', + ko: '후방 부채꼴 장판', + tc: '背後扇形', + }, + leftCleave: { + en: 'Left Cleave', + de: 'Linker Cleave', + fr: 'Cleave gauche', + ja: '左半面へ攻撃', + cn: '左刀', + ko: '왼쪽 공격', + tc: '左刀', + }, + rightCleave: { + en: 'Right Cleave', + de: 'Rechter Cleave', + fr: 'Cleave droit', + ja: '右半面へ攻撃', + cn: '右刀', + ko: '오른쪽 공격', + tc: '右刀', + }, + baitThenStack: { + en: 'Bait 4x Puddles => ${stack}', + }, + baitThenStackCleave: { + en: 'Bait 4x Puddles => ${stack} + ${cleave}', + }, + }, + }, + { + id: 'R12S Bursting Grotesquerie', + type: 'GainsEffect', + netRegex: { effectId: '1299', capture: true }, + condition: Conditions.targetIsYou(), + delaySeconds: 0.2, + durationSeconds: 17, + infoText: (data, _matches, output) => { + const cleave = data.grotesquerieCleave; + if (cleave === undefined) + return data.phase === 'doorboss' + ? output.baitThenSpread!() + : output.spreadCurtain!(); + return data.phase === 'doorboss' + ? output.baitThenSpreadCleave!({ cleave: output[cleave]!() }) + : output.spreadCurtain!(); + }, + outputStrings: { + frontCleave: { + en: 'Front Cleave', + de: 'Kegel Aoe nach Vorne', + fr: 'Cleave Avant', + ja: '口からおくび', + cn: '前方扇形', + ko: '전방 부채꼴 장판', + tc: '前方扇形', + }, + rearCleave: { + en: 'Rear Cleave', + de: 'Kegel Aoe nach Hinten', + fr: 'Cleave Arrière', + ja: '尻からおなら', + cn: '背后扇形', + ko: '후방 부채꼴 장판', + tc: '背後扇形', + }, + leftCleave: { + en: 'Left Cleave', + de: 'Linker Cleave', + fr: 'Cleave gauche', + ja: '左半面へ攻撃', + cn: '左刀', + ko: '왼쪽 공격', + tc: '左刀', + }, + rightCleave: { + en: 'Right Cleave', + de: 'Rechter Cleave', + fr: 'Cleave droit', + ja: '右半面へ攻撃', + cn: '右刀', + ko: '오른쪽 공격', + tc: '右刀', + }, + baitThenSpread: { + en: 'Bait 4x Puddles => Spread', + }, + baitThenSpreadCleave: { + en: 'Bait 4x Puddles => Spread + ${cleave}', + }, + spreadCurtain: { + en: 'Spread Debuff on YOU', + }, + }, + }, + { + id: 'R12S Ravenous Reach 1 Safe Side', + // These two syncs indicate the animation of where the head will go to cleave + // B49A => West Safe + // B49B => East Safe + type: 'Ability', + netRegex: { id: ['B49A', 'B49B'], source: 'Lindwurm', capture: true }, + condition: (data) => data.phase === 'doorboss', + infoText: (_data, matches, output) => { + if (matches.id === 'B49A') + return output.goWest!(); + return output.goEast!(); + }, + outputStrings: { + goEast: Outputs.east, + goWest: Outputs.west, + }, + }, + { + id: 'R12S Fourth-wall Fusion Stack', + type: 'HeadMarker', + netRegex: { id: headMarkerData['stack'], capture: true }, + condition: (data) => { + if (data.role === 'tank') + return false; + return true; + }, + durationSeconds: 5.1, + response: Responses.stackMarkerOn(), + }, + { + id: 'R12S Tankbuster', + type: 'HeadMarker', + netRegex: { id: headMarkerData['tankbuster'], capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 5.1, + response: Responses.tankBuster(), + }, + { + id: 'R12S In Line Debuff Collector', + type: 'GainsEffect', + netRegex: { effectId: ['BBC', 'BBD', 'BBE', 'D7B'] }, + run: (data, matches) => { + const effectToNum: { [effectId: string]: number } = { + BBC: 1, + BBD: 2, + BBE: 3, + D7B: 4, + } as const; + const num = effectToNum[matches.effectId]; + if (num === undefined) + return; + data.inLine[matches.target] = num; + }, + }, + { + id: 'R12S Bonds of Flesh Flesh α/β Collect', + // Bonds of Flesh has the following timings: + // 1st - 26s + // 2nd - 31s + // 3rd - 36s + // 4rth - 41s + type: 'GainsEffect', + netRegex: { effectId: ['1290', '1292'], capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + data.myFleshBonds = matches.effectId === '1290' ? 'alpha' : 'beta'; + }, + }, + { + id: 'R12S In Line Debuff', + type: 'GainsEffect', + netRegex: { effectId: ['BBC', 'BBD', 'BBE', 'D7B'], capture: false }, + delaySeconds: 0.5, + durationSeconds: 10, + suppressSeconds: 1, + infoText: (data, _matches, output) => { + const myNum = data.inLine[data.me]; + if (myNum === undefined) + return; + const flesh = data.myFleshBonds; + if (flesh === undefined) + return output.order!({ num: myNum }); + if (flesh === 'alpha') { + switch (myNum) { + case 1: + return output.alpha1!(); + case 2: + return output.alpha2!(); + case 3: + return output.alpha3!(); + case 4: + return output.alpha4!(); + } + } + switch (myNum) { + case 1: + return output.beta1!(); + case 2: + return output.beta2!(); + case 3: + return output.beta3!(); + case 4: + return output.beta4!(); + } + }, + outputStrings: { + alpha1: { + en: '1α: Wait for Tether 1', + }, + alpha2: { + en: '2α: Wait for Tether 2', + }, + alpha3: { + en: '3α: Blob Tower 1', + }, + alpha4: { + en: '4α: Blob Tower 2', + }, + beta1: { + en: '1β: Wait for Tether 1', + }, + beta2: { + en: '2β: Wait for Tether 2', + }, + beta3: { + en: '3β: Chain Tower 1', + }, + beta4: { + en: '4β: Chain Tower 2', + }, + order: { + en: '${num}', + de: '${num}', + fr: '${num}', + ja: '${num}', + cn: '${num}', + ko: '${num}', + tc: '${num}', + }, + unknown: Outputs.unknown, + }, + }, + { + id: 'R12S Phagocyte Spotlight Blob Tower Location Collect', + // StartsUsing and StartsUsingExtra can have bad data, there is enough time that Ability is sufficient + // Pattern 1 + // Blob 1: (104, 104) SE Inner + // Blob 2: (96, 96) NW Inner + // Blob 3: (85, 110) SW Outer + // Blob 4: (115, 90) NE Outer + // Pattern 2 + // Blob 1: (104, 96) NE Inner + // Blob 2: (96, 104) SW Inner + // Blob 3: (85, 90) NW Outer + // Blob 4: (115, 110) SE Outer + // Pattern 3 + // Blob 1: (96, 96) NW Inner + // Blob 2: (104, 104) SE Inner + // Blob 3: (115, 90) NE Outer + // Blob 4: (85, 110) SW Outer + // Pattern 4 + // Blob 1: (96, 104) SW Inner + // Blob 2: (104, 96) NE Inner + // Blob 3: (115, 110) SE Outer + // Blob 4: (86, 90) NW Outer + type: 'Ability', + netRegex: { id: 'B4B6', capture: true }, + suppressSeconds: 10, + run: (data, matches) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + const dir = Directions.xyToIntercardDirOutput(x, y, center.x, center.y); + data.blobTowerDirs.push(dir); + + if (dir === 'dirSE') { + data.blobTowerDirs.push('dirNW'); + data.blobTowerDirs.push('dirSW'); + data.blobTowerDirs.push('dirNE'); + } else if (dir === 'dirNE') { + data.blobTowerDirs.push('dirSW'); + data.blobTowerDirs.push('dirNW'); + data.blobTowerDirs.push('dirSE'); + } else if (dir === 'dirNW') { + data.blobTowerDirs.push('dirSE'); + data.blobTowerDirs.push('dirNE'); + data.blobTowerDirs.push('dirSW'); + } else if (dir === 'dirSW') { + data.blobTowerDirs.push('dirNE'); + data.blobTowerDirs.push('dirSE'); + data.blobTowerDirs.push('dirNW'); + } + }, + }, + { + id: 'R12S Phagocyte Spotlight Blob Tower Location (Early)', + // 23.8s until B4B7 Rolling Mass Blob Tower Hit + // Only need to know first blob location + type: 'Ability', + netRegex: { id: 'B4B6', capture: false }, + condition: (data) => data.myFleshBonds === 'alpha', + delaySeconds: 0.1, + durationSeconds: (data) => { + const myNum = data.inLine[data.me]; + // Timings based on next trigger + switch (myNum) { + case 1: + return 17; + case 2: + return 22; + case 3: + return 18; + case 4: + return 18; + } + }, + suppressSeconds: 10, + infoText: (data, _matches, output) => { + const myNum = data.inLine[data.me]; + if (myNum === undefined) + return; + + type index = { + [key: number]: number; + }; + const myNumToDirIndex: index = { + 1: 2, + 2: 3, + 3: 0, + 4: 1, + }; + const dirIndex = myNumToDirIndex[myNum]; + if (dirIndex === undefined) + return; + const towerNum = dirIndex + 1; + + const dir = data.blobTowerDirs[dirIndex]; + if (dir === undefined) + return; + + if (myNum > 2) + return output.innerBlobTower!({ + num: towerNum, + dir: output[dir]!(), + }); + return output.outerBlobTower!({ num: towerNum, dir: output[dir]!() }); + }, + outputStrings: { + ...Directions.outputStringsIntercardDir, + innerBlobTower: { + en: 'Blob Tower ${num} Inner ${dir} (later)', + }, + outerBlobTower: { + en: 'Blob Tower ${num} Outer ${dir} (later)', + }, + }, + }, + { + id: 'R12S Cursed Coil Bind Draw-in', + // Using Phagocyte Spotlight, 1st one happens 7s before bind + // Delayed additionally to reduce overlap with alpha tower location calls + type: 'Ability', + netRegex: { id: 'B4B6', capture: false }, + delaySeconds: 3, // 5s warning + suppressSeconds: 10, + response: Responses.drawIn(), + }, + { + id: 'R12S Skinsplitter Counter', + // These occur every 5s + // Useful for blob tower tracking that happen 2s after + // 2: Tether 1 + // 3: Tether 2 + Blob Tower 1 + // 4: Tether 3 + Blob Tower 2 + // 5: Tether 4 + Blob Tower 3 + // 6: Blob Tower 4 + // 7: Last time to exit + type: 'Ability', + netRegex: { id: 'B4BC', capture: false }, + suppressSeconds: 1, + run: (data) => data.skinsplitterCount = data.skinsplitterCount + 1, + }, + { + id: 'R12S Cell Chain Counter', + type: 'Tether', + netRegex: { id: headMarkerData['cellChainTether'], capture: false }, + condition: (data) => data.phase === 'doorboss', + run: (data) => data.cellChainCount = data.cellChainCount + 1, + }, + { + id: 'R12S Cell Chain Tether Number', + // Helpful for players to keep track of which chain tower is next + // Does not output when it is their turn to break the tether + type: 'Tether', + netRegex: { id: headMarkerData['cellChainTether'], capture: false }, + condition: (data) => { + if (data.phase === 'doorboss' && data.myFleshBonds === 'beta') + return true; + return false; + }, + infoText: (data, _matches, output) => { + const myNum = data.inLine[data.me]; + const num = data.cellChainCount; + if (myNum !== num) { + if (myNum === 1 && num === 3) + return output.beta1Tower!({ + tether: output.tether!({ num: num }), + }); + if (myNum === 2 && num === 4) + return output.beta2Tower!({ + tether: output.tether!({ num: num }), + }); + if (myNum === 3 && num === 1) + return output.beta3Tower!({ + tether: output.tether!({ num: num }), + }); + if (myNum === 4 && num === 2) + return output.beta4Tower!({ + tether: output.tether!({ num: num }), + }); + + return output.tether!({ num: num }); + } + + if (myNum === undefined) + return output.tether!({ num: num }); + }, + outputStrings: { + tether: { + en: 'Tether ${num}', + de: 'Verbindung ${num}', + fr: 'Lien ${num}', + ja: '線 ${num}', + cn: '线 ${num}', + ko: '선 ${num}', + tc: '線 ${num}', + }, + beta1Tower: { + en: '${tether} => Chain Tower 3', + }, + beta2Tower: { + en: '${tether} => Chain Tower 4', + }, + beta3Tower: { + en: '${tether} => Chain Tower 1', + }, + beta4Tower: { + en: '${tether} => Chain Tower 2', + }, + }, + }, + { + id: 'R12S Chain Tower Number', + // Using B4B4 Dramatic Lysis to detect chain broken + type: 'Ability', + netRegex: { id: 'B4B4', capture: false }, + condition: (data) => { + if (data.phase === 'doorboss' && data.myFleshBonds === 'beta') + return true; + return false; + }, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + const mechanicNum = data.cellChainCount; + const myNum = data.inLine[data.me]; + if (myNum === undefined) + return; + + type index = { + [key: number]: number; + }; + const myNumToOrder: index = { + 1: 3, + 2: 4, + 3: 1, + 4: 2, + }; + + const myOrder = myNumToOrder[myNum]; + if (myOrder === undefined) + return; + + if (myOrder === mechanicNum) + return output.tower!({ num: mechanicNum }); + }, + outputStrings: { + tower: { + en: 'Get Chain Tower ${num}', + }, + }, + }, + { + id: 'R12S Bonds of Flesh Flesh α First Two Towers', + // These are not dependent on player timings and so can be hard coded by duration + type: 'GainsEffect', + netRegex: { effectId: '1290', capture: true }, + condition: (data, matches) => { + if (matches.target === data.me) { + const duration = parseFloat(matches.duration); + if (duration < 35) + return false; + return true; + } + return false; + }, + delaySeconds: (_data, matches) => { + const duration = parseFloat(matches.duration); + // The following gives 5s warning to take tower + if (duration > 37) + return 31; // Alpha4 Time + return 26; // Alpha3 Time + }, + alertText: (data, matches, output) => { + const duration = parseFloat(matches.duration); + const dir = data.blobTowerDirs[duration > 40 ? 1 : 0]; + if (duration > 40) { + if (dir !== undefined) + return output.alpha4Dir!({ dir: output[dir]!() }); + return output.alpha4!(); + } + if (dir !== undefined) + return output.alpha3Dir!({ dir: output[dir]!() }); + return output.alpha3!(); + }, + outputStrings: { + ...Directions.outputStringsIntercardDir, + alpha3: { + en: 'Get Blob Tower 1', + }, + alpha4: { + en: 'Get Blob Tower 2', + }, + alpha3Dir: { + en: 'Get Blob Tower 1 (Inner ${dir})', + }, + alpha4Dir: { + en: 'Get Blob Tower 2 (Inner ${dir})', + }, + }, + }, + { + id: 'R12S Unbreakable Flesh α/β Chains and Last Two Towers', + type: 'GainsEffect', + netRegex: { effectId: ['1291', '1293'], capture: true }, + condition: (data, matches) => { + if (matches.target === data.me && data.phase === 'doorboss') + return true; + return false; + }, + alertText: (data, matches, output) => { + const myNum = data.inLine[data.me]; + const flesh = matches.effectId === '1291' ? 'alpha' : 'beta'; + if (flesh === 'alpha') { + if (myNum === 1) { + const dir = data.blobTowerDirs[2]; + if (dir !== undefined) + return output.alpha1Dir!({ + chains: output.breakChains!(), + dir: output[dir]!(), + }); + } + if (myNum === 2) { + const dir = data.blobTowerDirs[3]; + if (dir !== undefined) + return output.alpha2Dir!({ + chains: output.breakChains!(), + dir: output[dir]!(), + }); + } + + // dir undefined or 3rd/4rth in line + switch (myNum) { + case 1: + return output.alpha1!({ chains: output.breakChains!() }); + case 2: + return output.alpha2!({ chains: output.breakChains!() }); + case 3: + return output.alpha3!({ chains: output.breakChains!() }); + case 4: + return output.alpha4!({ chains: output.breakChains!() }); + } + } + switch (myNum) { + case 1: + return output.beta1!({ chains: output.breakChains!() }); + case 2: + return output.beta2!({ chains: output.breakChains!() }); + case 3: + return output.beta3!({ chains: output.breakChains!() }); + case 4: + return output.beta4!({ chains: output.breakChains!() }); + } + return output.getTowers!(); + }, + outputStrings: { + ...Directions.outputStringsIntercardDir, + breakChains: Outputs.breakChains, + getTowers: Outputs.getTowers, + alpha1: { + en: '${chains} 1 + Blob Tower 3 (Outer)', + }, + alpha1Dir: { + en: '${chains} 1 + Blob Tower 3 (Outer ${dir})', + }, + alpha2: { + en: '${chains} 2 + Blob Tower 4 (Outer)', + }, + alpha2Dir: { + en: '${chains} 2 + Blob Tower 4 (Outer ${dir})', + }, + alpha3: { + en: '${chains} 3 + Get Out', + }, + alpha4: { + en: '${chains} 4 + Get Out', + }, + beta1: { + en: '${chains} 1 => Get Middle', + }, + beta2: { + en: '${chains} 2 => Get Middle', + }, + beta3: { + en: '${chains} 3 => Wait for last pair', + }, + beta4: { + en: '${chains} 4 => Get Out', + }, + }, + }, + { + id: 'R12S Chain Tower Followup', + // Using B4B3 Roiling Mass to detect chain tower soak + // Beta player leaving early may get hit by alpha's chain break aoe + type: 'Ability', + netRegex: { id: 'B4B3', capture: true }, + condition: (data, matches) => { + if (data.myFleshBonds === 'beta' && data.me === matches.target) + return true; + return false; + }, + infoText: (data, _matches, output) => { + // Possibly the count could be off if break late (giving damage and damage down) + // Ideal towers are soaked: + // Beta 1 at 5th Skinsplitter + // Beta 2 at 6th Skinsplitter + // Beta 3 at 3rd Skinsplitter + // Beta 4 at 4rth Skinsplitter + const mechanicNum = data.skinsplitterCount; + const myNum = data.inLine[data.me]; + if (myNum === undefined) { + // This can be corrected by the player later + if (mechanicNum < 5) + return output.goIntoMiddle!(); + return output.getOut!(); + } + + if (mechanicNum < 5) { + if (myNum === 1) + return output.beta1Middle!(); + if (myNum === 2) + return output.beta2Middle!(); + if (myNum === 3) + return output.beta3Middle!(); + if (myNum === 4) + return output.beta4Middle!(); + } + if (myNum === 1) + return output.beta1Out!(); + if (myNum === 2) + return output.beta2Out!(); + if (myNum === 3) + return output.beta3Out!(); + if (myNum === 4) + return output.beta4Out!(); + }, + outputStrings: { + getOut: { + en: 'Get Out', + de: 'Raus da', + fr: 'Sortez', + ja: '外へ', + cn: '远离', + ko: '밖으로', + tc: '遠離', + }, + goIntoMiddle: Outputs.goIntoMiddle, + beta1Middle: Outputs.goIntoMiddle, + beta2Middle: Outputs.goIntoMiddle, // Should not happen under ideal situation + beta3Middle: Outputs.goIntoMiddle, + beta4Middle: Outputs.goIntoMiddle, + beta1Out: { // Should not happen under ideal situation + en: 'Get Out', + de: 'Raus da', + fr: 'Sortez', + ja: '外へ', + cn: '远离', + ko: '밖으로', + tc: '遠離', + }, + beta2Out: { + en: 'Get Out', + de: 'Raus da', + fr: 'Sortez', + ja: '外へ', + cn: '远离', + ko: '밖으로', + tc: '遠離', + }, + beta3Out: { // Should not happen under ideal situation + en: 'Get Out', + de: 'Raus da', + fr: 'Sortez', + ja: '外へ', + cn: '远离', + ko: '밖으로', + tc: '遠離', + }, + beta4Out: { // Should not happen under ideal situation + en: 'Get Out', + de: 'Raus da', + fr: 'Sortez', + ja: '外へ', + cn: '远离', + ko: '밖으로', + tc: '遠離', + }, + }, + }, + { + id: 'R12S Blob Tower Followup', + // Using B4B7 Roiling Mass to detect chain tower soak + // Alpha 3 and Alpha 4 get the inner towers before their chains + type: 'Ability', + netRegex: { id: 'B4B7', capture: true }, + condition: (data, matches) => { + if (data.myFleshBonds === 'alpha' && data.me === matches.target) + return true; + return false; + }, + infoText: (data, _matches, output) => { + const mechanicNum = data.skinsplitterCount; + const myNum = data.inLine[data.me]; + if (myNum === undefined) + return; + + if (myNum === mechanicNum) + return output.goIntoMiddle!(); + }, + outputStrings: { + goIntoMiddle: Outputs.goIntoMiddle, + }, + }, + { + id: 'R12S Splattershed', + type: 'StartsUsing', + netRegex: { id: ['B9C3', 'B9C4'], source: 'Lindwurm', capture: false }, + response: Responses.aoe(), + }, + { + id: 'R12S Mitotic Phase Direction Collect', + // Unknown_DE6 spell contains data in its count + type: 'GainsEffect', + netRegex: { effectId: 'DE6', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 10, + infoText: (data, matches, output) => { + data.myMitoticPhase = matches.count; + switch (matches.count) { + case '436': + return output.frontTower!(); + case '437': + return output.rightTower!(); + case '438': + return output.rearTower!(); + case '439': + return output.leftTower!(); + } + }, + outputStrings: { + frontTower: { + en: 'Tower (S/SW)', + }, + rearTower: { + en: 'Tower (N/NE)', + }, + leftTower: { + en: 'Tower (E/SE)', + }, + rightTower: { + en: 'Tower (W/NW)', + }, + }, + }, + { + id: 'R12S Grand Entrance Intercards/Cardinals', + // B4A1 is only cast when cardinals are safe + // B4A2 is only cast when intercardinals are safe + // These casts more than once, so just capture first event + type: 'StartsUsing', + netRegex: { id: ['B4A1', 'B4A2'], capture: true }, + suppressSeconds: 5, + infoText: (data, matches, output) => { + const count = data.myMitoticPhase; + if (count === undefined) + return; + if (matches.id === 'B4A1') { + switch (count) { + case '436': + return output.frontCardinals!(); + case '437': + return output.rightCardinals!(); + case '438': + return output.rearCardinals!(); + case '439': + return output.leftCardinals!(); + } + } + switch (count) { + case '436': + return output.frontIntercards!(); + case '437': + return output.rightIntercards!(); + case '438': + return output.rearIntercards!(); + case '439': + return output.leftIntercards!(); + } + }, + outputStrings: { + frontIntercards: Outputs.southwest, + rearIntercards: Outputs.northeast, + leftIntercards: Outputs.southeast, + rightIntercards: Outputs.northwest, + frontCardinals: Outputs.south, + rearCardinals: Outputs.north, + leftCardinals: Outputs.east, + rightCardinals: Outputs.west, + }, + }, + { + id: 'R12S Rotting Flesh', + type: 'GainsEffect', + netRegex: { effectId: '129B', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 10, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Rotting Flesh on YOU', + }, + }, + }, + { + id: 'R12S Rotting Flesh Collect', + type: 'GainsEffect', + netRegex: { effectId: '129B', capture: true }, + condition: Conditions.targetIsYou(), + run: (data) => data.hasRot = true, + }, + { + id: 'R12S Ravenous Reach 2', + // These two syncs indicate the animation of where the head will go to cleave + // B49A => West Safe + // B49B => East Safe + type: 'Ability', + netRegex: { id: ['B49A', 'B49B'], source: 'Lindwurm', capture: true }, + condition: (data) => data.phase === 'curtainCall', + alertText: (data, matches, output) => { + if (matches.id === 'B49A') { + return data.hasRot ? output.getHitEast!() : output.safeWest!(); + } + return data.hasRot ? output.getHitWest!() : output.safeEast!(); + }, + outputStrings: { + getHitWest: { + en: 'Spread in West Cleave', + }, + getHitEast: { + en: 'Spread in East Cleave', + }, + safeEast: { + en: 'Spread East + Avoid Cleave', + }, + safeWest: { + en: 'Spread West + Avoid Cleave', + }, + }, + }, + { + id: 'R12S Split Scourge and Venomous Scourge', + // B4AB Split Scourge and B4A8 Venomous Scourge are instant casts + // This actor control happens along with boss becoming targetable + // Seems there are two different data0 values possible: + // 1E01: Coming back from Cardinal platforms + // 1E001: Coming back from Intercardinal platforms + type: 'ActorControl', + netRegex: { command: '8000000D', data0: ['1E01', '1E001'], capture: false }, + durationSeconds: 9, + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + if (data.role === 'tank') + return output.tank!(); + return output.party!(); + }, + outputStrings: { + tank: { + en: 'Bait Line AoE from heads', + }, + party: { + en: 'Spread, Away from heads', + }, + }, + }, + { + id: 'R12S Grotesquerie: Curtain Call Spreads', + type: 'StartsUsing', + netRegex: { id: 'BEC0', source: 'Lindwurm', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: 'Bait 5x Puddles', + }, + }, + { + id: 'R12S Curtain Call: Unbreakable Flesh α Chains', + // All players, including dead, receive α debuffs + // TODO: Find safe spots + type: 'GainsEffect', + netRegex: { effectId: '1291', capture: true }, + condition: (data, matches) => { + if (matches.target === data.me && data.phase === 'curtainCall') + return true; + return false; + }, + infoText: (_data, _matches, output) => { + return output.alphaChains!({ + chains: output.breakChains!(), + safe: output.safeSpots!(), + }); + }, + outputStrings: { + breakChains: Outputs.breakChains, + safeSpots: { + en: 'Avoid Blobs', + }, + alphaChains: { + en: '${chains} => ${safe}', + }, + }, + }, + { + id: 'R12S Slaughtershed', + type: 'StartsUsing', + netRegex: { id: ['B4C6', 'B4C3'], source: 'Lindwurm', capture: false }, + response: Responses.bigAoe('alert'), + }, + { + id: 'R12S Slaughtershed Stack', + // TODO: Get Safe spot + type: 'HeadMarker', + netRegex: { id: headMarkerData['slaughterStack'], capture: true }, + condition: (data, matches) => { + const isDPS = data.party.isDPS(matches.target); + if (isDPS && data.role === 'dps') + return true; + if (!isDPS && data.role !== 'dps') + return true; + return false; + }, + durationSeconds: 5.1, + response: Responses.stackMarkerOn(), + }, + { + id: 'R12S Slaughtershed Spread', + // TODO: Get Safe spot + type: 'HeadMarker', + netRegex: { id: headMarkerData['slaughterSpread'], capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 5.1, + suppressSeconds: 1, + response: Responses.spread(), + }, + { + id: 'R12S Serpintine Scourge Right Hand First', + // Left Hand first, then Right Hand + type: 'Ability', + netRegex: { id: 'B4CB', source: 'Lindwurm', capture: false }, + condition: (data) => data.phase === 'slaughtershed', + durationSeconds: 12, + infoText: (_data, _matches, output) => output.rightThenLeft!(), + outputStrings: { + rightThenLeft: Outputs.rightThenLeft, + }, + }, + { + id: 'R12S Serpintine Scourge Left Hand First', + // Right Hand first, then Left Hand + type: 'Ability', + netRegex: { id: 'B4CD', source: 'Lindwurm', capture: false }, + condition: (data) => data.phase === 'slaughtershed', + durationSeconds: 12, + infoText: (_data, _matches, output) => output.leftThenRight!(), + outputStrings: { + leftThenRight: Outputs.leftThenRight, + }, + }, + { + id: 'R12S Raptor Knuckles Right Hand First', + // Right Hand first, then Left Hand + type: 'Ability', + netRegex: { id: 'B4CC', source: 'Lindwurm', capture: false }, + condition: (data) => data.phase === 'slaughtershed', + durationSeconds: 15, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Knockback from Northwest => Knockback from Northeast', + }, + }, + }, + { + id: 'R12S Raptor Knuckles Left Hand First', + // Left Hand first, then Right Hand + type: 'Ability', + netRegex: { id: 'B4CE', source: 'Lindwurm', capture: false }, + condition: (data) => data.phase === 'slaughtershed', + durationSeconds: 15, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Knockback from Northeast => Knockback from Northwest', + }, + }, + }, + { + id: 'R12S Raptor Knuckles Uptime Knockback', + // First knockback is at ~13.374s + // Second knockback is at ~17.964s + // Use knockback at ~11.5s to hit both with ~1.8s leniency + // ~11.457s before is too late as it comes off the same time as hit + // ~11.554s before works (surecast ends ~0.134 after hit) + type: 'Ability', + netRegex: { id: ['B4CC', 'B4CE'], source: 'Lindwurm', capture: false }, + condition: (data) => { + if (data.phase === 'slaughtershed' && data.triggerSetConfig.uptimeKnockbackStrat) + return true; + return false; + }, + delaySeconds: 11.5, + durationSeconds: 1.8, + response: Responses.knockback(), + }, + { + id: 'R12S Refreshing Overkill', + // 10s castTime that could end with enrage or raidwide + type: 'StartsUsing', + netRegex: { id: 'B538', source: 'Lindwurm', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 4, + durationSeconds: 4.7, + response: Responses.bigAoe('alert'), + }, + // Phase 2 + { + id: 'R12S Arcadia Aflame', + type: 'StartsUsing', + netRegex: { id: 'B528', source: 'Lindwurm', capture: false }, + response: Responses.bigAoe('alert'), + }, + { + id: 'R12S Winged Scourge', + // B4DA E/W clones Facing S, Cleaving Front/Back (North/South) + // B4DB N/S clones Facing W, Cleaving Front/Back (East/West) + type: 'StartsUsing', + netRegex: { id: ['B4DA', 'B4DB'], source: 'Lindschrat', capture: true }, + suppressSeconds: 1, + infoText: (data, matches, output) => { + if (matches.id === 'B4DA') { + if (data.replication1FollowUp) + return output.northSouthCleaves2!(); + return output.northSouthCleaves!(); + } + if (data.replication1FollowUp) + return output.eastWestCleaves2!(); + return output.eastWestCleaves!(); + }, + outputStrings: { + northSouthCleaves: { + en: 'North/South Cleaves', + }, + eastWestCleaves: { + en: 'East/West Cleaves', + }, + northSouthCleaves2: { + en: 'North/South Cleaves', + }, + eastWestCleaves2: { + en: 'East/West Cleaves', + }, + }, + }, + { + id: 'R12S Fire and Dark Resistance Down II Collector', + // CFB Dark Resistance Down II + // B79 Fire Resistance Down II + type: 'GainsEffect', + netRegex: { effectId: ['CFB', 'B79'], capture: true }, + condition: Conditions.targetIsYou(), + suppressSeconds: 9999, + run: (data, matches) => { + data.replication1Debuff = matches.effectId === 'CFB' ? 'dark' : 'fire'; + }, + }, + { + id: 'R12S Fire and Dark Resistance Down II', + // CFB Dark Resistance Down II + // B79 Fire Resistance Down II + type: 'GainsEffect', + netRegex: { effectId: ['CFB', 'B79'], capture: true }, + condition: (data, matches) => { + if (data.me === matches.target) + return !data.replication1FollowUp; + return false; + }, + suppressSeconds: 9999, + infoText: (_data, matches, output) => { + return matches.effectId === 'CFB' ? output.dark!() : output.fire!(); + }, + outputStrings: { + fire: { + en: 'Fire Debuff: Spread near Dark (later)', + }, + dark: { + en: 'Dark Debuff: Stack near Fire (later)', + }, + }, + }, + { + id: 'R12S Fake Fire Resistance Down II', + // Two players will not receive a debuff, they will need to act as if they had + // Mechanics happen across 1.1s + type: 'GainsEffect', + netRegex: { effectId: ['CFB', 'B79'], capture: false }, + condition: (data) => !data.replication1FollowUp, + delaySeconds: 1.2, // +0.1s Delay for debuff/damage propagation + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + if (data.replication1Debuff === undefined) + return output.noDebuff!(); + }, + outputStrings: { + noDebuff: { + en: 'No Debuff: Spread near Dark (later)', + }, + }, + }, + { + id: 'R12S Snaking Kick', + // Targets random player + // Second cast of this happens before Grotesquerie, delay until Grotesquerie to reduce chance of none projection players running into it + type: 'StartsUsing', + netRegex: { id: 'B527', source: 'Lindwurm', capture: true }, + delaySeconds: 0.1, // Need to delay for actor position update + suppressSeconds: 9999, + alertText: (data, matches, output) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return output.getBehind!(); + + const dirNum = (Directions.hdgTo16DirNum(actor.heading) + 8) % 16; + const dir = Directions.output16Dir[dirNum] ?? 'unknown'; + return output.getBehindDir!({ + dir: output[dir]!(), + mech: output.getBehind!(), + }); + }, + outputStrings: { + ...Directions.outputStrings16Dir, + getBehind: Outputs.getBehind, + getBehindDir: { + en: '${dir}: ${mech}', + }, + }, + }, + { + id: 'R12S Replication 1 Follow-up Tracker', + // Tracking from B527 Snaking Kick + type: 'Ability', + netRegex: { id: 'B527', source: 'Lindwurm', capture: false }, + suppressSeconds: 9999, + run: (data) => data.replication1FollowUp = true, + }, + { + id: 'R12S Top-Tier Slam Actor Collect', + // Fire NPCs always move in the first Set + // Locations are static + // Fire => Dark => Fire => Dark + // Dark => Fire => Dark => Fire + // The other 4 cleave in a line + // (90, 90) (110, 90) + // (95, 95) (105, 95) + // Boss + // (95, 100) (105, 105) + // (90, 110) (110, 110) + // ActorMove ~0.3s later will have the data + // ActorSet from the clones splitting we can infer the fire entities since their positions and headings are not perfect + // For first set there are two patterns that use these coordinates: + // (100, 86) + // (86, 100) (114, 100) + // (100, 114) + // Either N/S are clones casting Winged Scourge, or the E/W clones cast Winged Scourge + // Each pattern has its own pattern for IDs of the clones, in order + // N/S will have Fire -5 and -6 of its original + // E/W will have Fire -6 and -7 of its original + // Could use -6 to cover both cases, but that doesn't determine which add jumps first + type: 'Ability', + netRegex: { id: 'B4D9', source: 'Lindschrat', capture: true }, + condition: (data, matches) => { + if (data.replication1FollowUp) { + const pos = data.actorPositions[matches.sourceId]; + if (pos === undefined) + return false; + // These values should be 0 when x or y coord has non-zero decimal values + // Heading is also checked as the non fire clones all face a perfect heading + const xFilter = pos.x % 1; + const yFilter = pos.y % 1; + if (xFilter === 0 && yFilter === 0 && pos.heading === -0.0001) + return false; + return true; + } + return false; + }, + suppressSeconds: 9999, // Only need one of the two + run: (data, matches) => data.replication1FireActor = matches.sourceId, + }, + { + id: 'R12S Top-Tier Slam/Mighty Magic Locations', + type: 'Ability', + netRegex: { id: 'B4D9', source: 'Lindschrat', capture: false }, + condition: (data) => { + if (data.replication1FollowUp && data.replication1FireActor !== undefined) + return true; + return false; + }, + delaySeconds: 1, // Data is sometimes not available right away + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + const fireId = data.replication1FireActor; + if (fireId === undefined) + return; + + const actor = data.actorPositions[fireId]; + if (actor === undefined) + return; + + const x = actor.x; + const dirNum = Directions.xyTo8DirNum(x, actor.y, center.x, center.y); + const dir1 = Directions.output8Dir[dirNum] ?? 'unknown'; + const dirNum2 = (dirNum + 4) % 8; + const dir2 = Directions.output8Dir[dirNum2] ?? 'unknown'; + + // Check if combatant moved to inner or outer + const isIn = (x > 94 && x < 106); + const fireIn = isIn ? dir1 : dir2; + const fireOut = isIn ? dir2 : dir1; + + if (data.replication1Debuff === 'dark') + return output.fire!({ + dir1: output[fireIn]!(), + dir2: output[fireOut]!(), + }); + + // Dark will be opposite pattern of Fire + const darkIn = isIn ? dir2 : dir1; + const darkOut = isIn ? dir1 : dir2; + + // Fire debuff players and unmarked bait Dark + return output.dark!({ + dir1: output[darkIn]!(), + dir2: output[darkOut]!(), + }); + }, + outputStrings: { + ...Directions.outputStringsIntercardDir, // Cardinals should result in '???' + fire: { + en: 'Bait Fire In ${dir1}/Out ${dir2} (Partners)', + }, + dark: { + en: 'Bait Dark In ${dir1}/Out ${dir2} (Solo)', + }, + }, + }, + { + id: 'R12S Double Sobat', + // Shared half-room cleave on tank => random turn half-room cleave => + // Esoteric Finisher big circle aoes that hits two highest emnity targets + type: 'HeadMarker', + netRegex: { id: headMarkerData['sharedTankbuster'], capture: true }, + response: Responses.sharedTankBuster(), + }, + { + id: 'R12S Double Sobat 2', + // Followup half-room cleave: + // B521 Double Sobat: 0 degree left turn then B525 + // B522 Double Sobat: 90 degree left turn then B525 + // B523 Double Sobat: 180 degree left turn then B525 + // B524 Double Sobat: 270 degree left turn (this ends up 90 degrees to the right) + type: 'Ability', + netRegex: { id: ['B521', 'B522', 'B523', 'B524'], source: 'Lindwurm', capture: true }, + suppressSeconds: 1, + alertText: (_data, matches, output) => { + const hdg = parseFloat(matches.heading); + const dirNum = Directions.hdgTo16DirNum(hdg); + const getNewDirNum = ( + dirNum: number, + id: string, + ): number => { + switch (id) { + case 'B521': + return dirNum; + case 'B522': + return dirNum - 4; + case 'B523': + return dirNum - 8; + case 'B524': + return dirNum - 12; + } + throw new UnreachableCode(); + }; + + // Adding 16 incase of negative values + const newDirNum = (getNewDirNum(dirNum, matches.id) + 16 + 8) % 16; + + const dir = Directions.output16Dir[newDirNum] ?? 'unknown'; + return output.getBehindDir!({ + dir: output[dir]!(), + mech: output.getBehind!(), + }); + }, + outputStrings: { + ...Directions.outputStrings16Dir, + getBehind: Outputs.getBehind, + getBehindDir: { + en: '${dir}: ${mech}', + }, + }, + }, + { + id: 'R12S Esoteric Finisher', + // After Double Sobat 2, boss hits targets highest emnity target, second targets second highest + type: 'StartsUsing', + netRegex: { id: 'B525', source: 'Lindwurm', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankBusterCleaves: Outputs.tankBusterCleaves, + avoidTankCleaves: Outputs.avoidTankCleaves, + }; + + if (data.role === 'tank' || data.role === 'healer') { + if (data.role === 'healer') + return { infoText: output.tankBusterCleaves!() }; + return { alertText: output.tankBusterCleaves!() }; + } + return { infoText: output.avoidTankCleaves!() }; + }, + }, + { + id: 'R12S Staging 1 Tethered Clone Collect', + // Map the locations to a player name + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data) => data.replicationCounter === 1, + run: (data, matches) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return; + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + data.replication2CloneDirNumPlayers[dirNum] = matches.target; + }, + }, + { + id: 'R12S Staging 1 Tethered Clone', + // Combatants are added ~4s before Staging starts casting + // Same tether ID is used for "locked" ability tethers + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: Conditions.targetIsYou(), + suppressSeconds: 9999, + infoText: (data, matches, output) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return output.cloneTether!(); + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + return output.cloneTetherDir!({ dir: output[dir]!() }); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + cloneTether: { + en: 'Tethered to Clone', + }, + cloneTetherDir: { + en: 'Tethered to ${dir} Clone', + }, + }, + }, + { + id: 'R12S Replication 2 and Replication 4 Ability Tethers Collect', + // Record and store a map of where the tethers come from and what they do for later + // Boss tether handled separately since boss can move around + type: 'Tether', + netRegex: { + id: [ + headMarkerData['projectionTether'], + headMarkerData['manaBurstTether'], + headMarkerData['heavySlamTether'], + ], + capture: true, + }, + condition: (data) => { + if (data.phase === 'replication2' || data.phase === 'idyllic') + return true; + return false; + }, + run: (data, matches) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return; + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + if (data.phase === 'replication2') + data.replication2DirNumAbility[dirNum] = matches.id; + if (data.phase === 'idyllic') + data.replication4DirNumAbility[dirNum] = matches.id; + }, + }, + { + id: 'R12S Replication 2 Ability Tethers Initial Call', + // Occur ~8s after end of Replication 2 cast + type: 'Tether', + netRegex: { + id: [ + headMarkerData['projectionTether'], + headMarkerData['manaBurstTether'], + headMarkerData['heavySlamTether'], + headMarkerData['fireballSplashTether'], + ], + capture: true, + }, + condition: Conditions.targetIsYou(), + suppressSeconds: 9999, // Can get spammy if players have more than 1 tether or swap a lot + infoText: (data, matches, output) => { + if (matches.id === headMarkerData['fireballSplashTether']) + return output.fireballSplashTether!(); + + // Get direction of the tether + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) { + switch (matches.id) { + case headMarkerData['projectionTether']: + return output.projectionTether!(); + case headMarkerData['manaBurstTether']: + return output.manaBurstTether!(); + case headMarkerData['heavySlamTether']: + return output.heavySlamTether!(); + } + return; + } + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + + switch (matches.id) { + case headMarkerData['projectionTether']: + return output.projectionTetherDir!({ dir: output[dir]!() }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTetherDir!({ dir: output[dir]!() }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTetherDir!({ dir: output[dir]!() }); + } + }, + outputStrings: { + ...Directions.outputStrings8Dir, + projectionTether: { + en: 'Cone Tether on YOU', + }, + projectionTetherDir: { + en: '${dir} Cone Tether on YOU', + }, + manaBurstTether: { + en: 'Defamation Tether on YOU', + }, + manaBurstTetherDir: { + en: '${dir} Defamation Tether on YOU', + }, + heavySlamTether: { + en: 'Stack Tether on YOU', + }, + heavySlamTetherDir: { + en: '${dir} Stack Tether on YOU', + }, + fireballSplashTether: { + en: 'Boss Tether on YOU', + }, + }, + }, + { + id: 'R12S Replication 2 Locked Tether Collect', + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data) => { + if ( + data.phase === 'replication2' && + data.replicationCounter === 2 + ) + return true; + return false; + }, + run: (data, matches) => { + const target = matches.target; + const sourceId = matches.sourceId; + // Check if boss tether + if (data.replication2BossId === sourceId) + data.replication2PlayerAbilities[target] = headMarkerData['fireballSplashTether']; + else if (data.replication2BossId !== sourceId) { + const actor = data.actorPositions[sourceId]; + if (actor === undefined) { + // Setting to use that we know we have a tether but couldn't determine what ability it is + data.replication2PlayerAbilities[target] = 'unknown'; + return; + } + + const dirNum = Directions.xyTo8DirNum( + actor.x, + actor.y, + center.x, + center.y, + ); + + // Lookup what the tether was at the same location + const ability = data.replication2DirNumAbility[dirNum]; + if (ability === undefined) { + // Setting to use that we know we have a tether but couldn't determine what ability it is + data.replication2PlayerAbilities[target] = 'unknown'; + return; + } + data.replication2PlayerAbilities[target] = ability; + } + + // Create ability order once we have all 8 players + // If players had more than one tether previously, the extra tethers are randomly assigned + if (Object.keys(data.replication2PlayerAbilities).length === 7) { + // Fill in for player that had no tether, they are going to be boss' defamation + if (data.replication2PlayerAbilities[data.me] === undefined) + data.replication2PlayerAbilities[data.me] = 'none'; + + // Used for Twisted Vision 7 and 8 mechanics + const abilities = data.replication2PlayerAbilities; + const order = [0, 4, 1, 5, 2, 6, 3, 7]; // Order in which clones spawned, this is static + const players = data.replication2CloneDirNumPlayers; // Direction of player's clone + + // Mechanics are resolved clockwise + for (const dirNum of order) { + const player = players[dirNum] ?? 'unknown'; + const ability = abilities[player] ?? 'unknown'; + data.replication2PlayerOrder.push(player); + data.replication2AbilityOrder.push(ability); + } + } + }, + }, + { + id: 'R12S Replication 2 Locked Tether', + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data, matches) => { + if ( + data.phase === 'replication2' && + data.replicationCounter === 2 && + data.me === matches.target + ) + return true; + return false; + }, + delaySeconds: 0.1, + infoText: (data, matches, output) => { + const sourceId = matches.sourceId; + // Check if it's the boss + if (data.replication2BossId === sourceId) + return output.fireballSplashTether!({ + mech1: output.baitJump!(), + }); + + // Get direction of the tether + const actor = data.actorPositions[sourceId]; + const ability = data.replication2PlayerAbilities[data.me]; + if (actor === undefined) { + switch (ability) { + case headMarkerData['projectionTether']: + return output.projectionTether!({ + mech1: output.baitProtean!(), + }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTether!({ + mech1: output.defamationOnYou!(), + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTether!({ + mech1: output.baitProtean!(), + }); + } + return; + } + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + + switch (ability) { + case headMarkerData['projectionTether']: + return output.projectionTetherDir!({ + dir: output[dir]!(), + mech1: output.baitProtean!(), + }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTetherDir!({ + dir: output[dir]!(), + mech1: output.defamationOnYou!(), + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTetherDir!({ + dir: output[dir]!(), + mech1: output.baitProtean!(), + }); + } + }, + outputStrings: { + ...Directions.outputStrings8Dir, + defamationOnYou: Outputs.defamationOnYou, + baitProtean: { + en: 'Bait Protean from Boss', + }, + baitJump: { + en: 'Bait Jump', + }, + projectionTetherDir: { + en: '${dir} Cone Tether: ${mech1}', + }, + projectionTether: { + en: 'Cone Tether: ${mech1}', + }, + manaBurstTetherDir: { + en: '${dir} Defamation Tether: ${mech1}', + }, + manaBurstTether: { + en: 'Defamation Tether: ${mech1}', + }, + heavySlamTetherDir: { + en: '${dir} Stack Tether: ${mech1}', + }, + heavySlamTether: { + en: 'Stack Tether: ${mech1}', + }, + fireballSplashTether: { + en: 'Boss Tether: ${mech1}', + }, + }, + }, + { + id: 'R12S Replication 2 Mana Burst Target', + // A player without a tether will be target for defamation + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: false }, + condition: (data) => { + if (data.phase === 'replication2' && data.replicationCounter === 2) + return true; + return false; + }, + delaySeconds: 0.2, + suppressSeconds: 1, + infoText: (data, _matches, output) => { + if ( + data.replication2PlayerAbilities[data.me] !== 'none' || + data.replication2PlayerAbilities[data.me] === undefined + ) + return; + return output.noTether!({ + mech1: output.defamationOnYou!(), + mech2: output.stackGroups!(), + }); + }, + outputStrings: { + defamationOnYou: Outputs.defamationOnYou, + stackGroups: { + en: 'Stack Groups', + de: 'Gruppen-Sammeln', + fr: 'Package en groupes', + ja: '組み分け頭割り', + cn: '分组分摊', + ko: '그룹별 쉐어', + tc: '分組分攤', + }, + noTether: { + en: 'No Tether: ${mech1} => ${mech2}', + }, + }, + }, + { + id: 'R12S Heavy Slam', + // After B4E7 Mana Burst, Groups must stack up on the heavy slam targetted players + type: 'Ability', + netRegex: { id: 'B4E7', source: 'Lindwurm', capture: false }, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + const ability = data.replication2PlayerAbilities[data.me]; + switch (ability) { + case headMarkerData['projectionTether']: + return output.projectionTether!({ + mech1: output.stackGroups!(), + mech2: output.lookAway!(), + mech3: output.getBehind!(), + }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTether!({ + mech1: output.stackGroups!(), + mech2: output.getBehind!(), + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTether!({ + mech1: output.stackGroups!(), + mech2: output.getBehind!(), + }); + case headMarkerData['fireballSplashTether']: + return output.fireballSplashTether!({ + mech1: output.stackGroups!(), + mech2: output.getBehind!(), + }); + } + return output.noTether!({ + mech1: output.stackGroups!(), + mech2: output.getBehind!(), + }); + }, + outputStrings: { + getBehind: Outputs.getBehind, + lookAway: Outputs.lookAway, + stackGroups: { + en: 'Stack Groups', + de: 'Gruppen-Sammeln', + fr: 'Package en groupes', + ja: '組み分け頭割り', + cn: '分组分摊', + ko: '그룹별 쉐어', + tc: '分組分攤', + }, + stackOnYou: Outputs.stackOnYou, + projectionTether: { + en: '${mech1} + ${mech2} => ${mech3}', + }, + manaBurstTether: { + en: '${mech1} => ${mech2}', + }, + heavySlamTether: { + en: '${mech1} => ${mech2}', + }, + fireballSplashTether: { + en: '${mech1} => ${mech2}', + }, + noTether: { + en: '${mech1} => ${mech2}', + }, + }, + }, + { + id: 'R12S Grotesquerie', + // This seems to be the point at which the look for the Snaking Kick is snapshot + // The VFX B4E9 happens ~0.6s before Snaking Kick + // B4EA has the targetted player in it + // B4EB Hemorrhagic Projection conal aoe goes off ~0.5s after in the direction the player was facing + type: 'Ability', + netRegex: { id: 'B4EA', source: 'Lindwurm', capture: false }, + suppressSeconds: 9999, + alertText: (data, _matches, output) => { + // Get Boss facing + const bossId = data.replication2BossId; + if (bossId === undefined) + return output.getBehind!(); + + const actor = data.actorPositions[bossId]; + if (actor === undefined) + return output.getBehind!(); + + const dirNum = (Directions.hdgTo16DirNum(actor.heading) + 8) % 16; + const dir = Directions.output16Dir[dirNum] ?? 'unknown'; + return output.getBehindDir!({ + dir: output[dir]!(), + mech: output.getBehind!(), + }); + }, + outputStrings: { + ...Directions.outputStrings16Dir, + getBehind: Outputs.getBehind, + getBehindDir: { + en: '${dir}: ${mech}', + }, + }, + }, + { + id: 'R12S Netherwrath Near/Far', + // Boss jumps onto clone of player that took Firefall Splash, there is an aoe around the clone + proteans + type: 'StartsUsing', + netRegex: { id: ['B52E', 'B52F'], source: 'Lindwurm', capture: true }, + infoText: (data, matches, output) => { + const ability = data.replication2PlayerAbilities[data.me]; + const isNear = matches.id === 'B52E'; + + if (isNear) { + switch (ability) { + case headMarkerData['projectionTether']: + return output.projectionTetherNear!({ + proteanBaits: output.beFar!(), + mech1: output.scaldingWave!(), + mech2: output.stacks!(), + spiteBaits: output.near!(), + }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTetherNear!({ + spiteBaits: output.beNear!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.far!(), + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTetherNear!({ + proteanBaits: output.beFar!(), + mech1: output.scaldingWave!(), + mech2: output.stacks!(), + spiteBaits: output.near!(), + }); + case headMarkerData['fireballSplashTether']: + return output.fireballSplashTetherNear!({ + spiteBaits: output.beNear!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.far!(), + }); + } + return output.noTetherNear!({ + spiteBaits: output.beNear!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.far!(), + }); + } + + // Netherwrath Far + switch (ability) { + case headMarkerData['projectionTether']: + return output.projectionTetherFar!({ + proteanBaits: output.beNear!(), + mech1: output.scaldingWave!(), + mech2: output.stacks!(), + spiteBaits: output.far!(), + }); + case headMarkerData['manaBurstTether']: + return output.manaBurstTetherFar!({ + spiteBaits: output.beFar!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.near!(), + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTetherFar!({ + proteanBaits: output.beNear!(), + mech1: output.scaldingWave!(), + mech2: output.stacks!(), + spiteBaits: output.far!(), + }); + case headMarkerData['fireballSplashTether']: + return output.fireballSplashTetherFar!({ + spiteBaits: output.beFar!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.near!(), + }); + } + return output.noTetherFar!({ + spiteBaits: output.beFar!(), + mech1: output.timelessSpite!(), + mech2: output.proteans!(), + proteanBaits: output.near!(), + }); + }, + outputStrings: { + scaldingWave: Outputs.protean, + timelessSpite: Outputs.stackPartner, + stacks: Outputs.stacks, + proteans: { + en: 'Proteans', + }, + beNear: { + en: 'Be Near', + }, + beFar: { + en: 'Be Far', + }, + near: { + en: 'Near', + de: 'Nah', + fr: 'Proche', + cn: '近', + ko: '가까이', + }, + far: { + en: 'Far', + de: 'Fern', + fr: 'Loin', + cn: '远', + ko: '멀리', + }, + projectionTetherFar: { + en: '${proteanBaits} + ${mech1} (${mech2} ${spiteBaits})', + }, + manaBurstTetherFar: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + heavySlamTetherFar: { + en: '${proteanBaits} + ${mech1} (${mech2} ${spiteBaits})', + }, + fireballSplashTetherFar: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + noTetherFar: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + projectionTetherNear: { + en: '${proteanBaits} + ${mech1} (${mech2} ${spiteBaits})', + }, + manaBurstTetherNear: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + heavySlamTetherNear: { + en: '${proteanBaits} + ${mech1} (${mech2} ${spiteBaits})', + }, + fireballSplashTetherNear: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + noTetherNear: { + en: '${spiteBaits} + ${mech1} (${mech2} ${proteanBaits})', + }, + }, + }, + { + id: 'R12S Reenactment 1 Scalding Waves Collect', + // Players need to wait for BBE3 Mana Burst Defamations on the clones to complete before next mechanic + // NOTE: This is used with DN Strategy + type: 'Ability', + netRegex: { id: 'B8E1', source: 'Lindwurm', capture: false }, + condition: (data) => data.phase === 'reenactment1', + suppressSeconds: 9999, + run: (data) => data.netherwrathFollowup = true, + }, + { + id: 'R12S Reenactment 1 Clone Stacks', + // Players need to wait for BBE3 Mana Burst defamations on clones to complete + // This happens three times during reenactment and the third one (which is after the proteans) is the trigger + // NOTE: This is used with DN Strategy + type: 'Ability', + netRegex: { id: 'BBE3', source: 'Lindwurm', capture: false }, + condition: (data) => { + if (data.netherwrathFollowup) { + const order = data.replication2AbilityOrder; + const stack = headMarkerData['heavySlamTether']; + const defamation = headMarkerData['manaBurstTether']; + const projection = headMarkerData['projectionTether']; + // Defined as east/west clones with stacks and SW/NE with defamation + projection + if ( + order[4] === stack && order[5] === stack && + ( + (order[2] === defamation && order[3] === projection) || + (order[2] === projection && order[3] === defamation) + ) + ) + return true; + } + return false; + }, + suppressSeconds: 9999, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'East/West Clone Stacks', + }, + }, + }, + { + id: 'R12S Reenactment 1 Final Defamation Dodge Reminder', + // Players need to run back to north after clone stacks (BE5D Heavy Slam) + // The clone stacks become a defamation and the other a cleave going East or West through the room + // NOTE: This is used with DN Strategy + type: 'Ability', + netRegex: { id: 'BE5D', source: 'Lindwurm', capture: false }, + condition: (data) => { + if (data.netherwrathFollowup) { + const order = data.replication2AbilityOrder; + const stack = headMarkerData['heavySlamTether']; + const defamation = headMarkerData['manaBurstTether']; + const projection = headMarkerData['projectionTether']; + // Defined as east/west clones with stacks and NW/SE with defamation + projection + if ( + order[4] === stack && order[5] === stack && + ( + (order[6] === defamation && order[7] === projection) || + (order[7] === projection && order[6] === defamation) + ) + ) + return true; + } + return false; + }, + suppressSeconds: 9999, + alertText: (_data, _matches, output) => output.north!(), + outputStrings: { + north: Outputs.north, + }, + }, + { + id: 'R12S Mana Sphere Collect and Label', + // Combatants Spawn ~3s before B505 Mutating Cells startsUsing + // Their positions are available at B4FD in the 264 AbilityExtra lines and updated periodically after with 270 lines + // 19208 => Lightning Bowtie (N/S Cleave) + // 19209 => Fire Bowtie (E/W Cleave) + // 19205 => Black Hole + // 19206 => Water Sphere/Chariot + // 19207 => Wind Donut + // Position at add is center, so not useful here yet + type: 'AddedCombatant', + netRegex: { name: 'Mana Sphere', capture: true }, + run: (data, matches) => { + const id = matches.id; + const npcBaseId = parseInt(matches.npcBaseId); + switch (npcBaseId) { + case 19205: + data.manaSpheres[id] = 'blackHole'; + return; + case 19206: + data.manaSpheres[id] = 'water'; + return; + case 19207: + data.manaSpheres[id] = 'wind'; + return; + case 19208: + data.manaSpheres[id] = 'lightning'; + return; + case 19209: + data.manaSpheres[id] = 'fire'; + return; + } + }, + }, + { + id: 'R12S Mutation α/β Collect', + // Used in Blood Mana / Blood Awakening Mechanics + // 12A1 Mutation α: Don't get hit + // 12A3 Mutation β: Get Hit + // Players will get opposite debuff after Blood Mana + type: 'GainsEffect', + netRegex: { effectId: ['12A1', '12A3'], capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + data.myMutation = matches.effectId === '12A1' ? 'alpha' : 'beta'; + }, + }, + { + id: 'R12S Mutation α/β', + type: 'GainsEffect', + netRegex: { effectId: ['12A1', '12A3'], capture: true }, + condition: Conditions.targetIsYou(), + infoText: (_data, matches, output) => { + if (matches.effectId === '12A1') + return output.alpha!(); + return output.beta!(); + }, + outputStrings: { + alpha: { + en: 'Mutation α on YOU', + }, + beta: { + en: 'Mutation β on YOU', + }, + }, + }, + { + id: 'R12S Mana Sphere Position Collect', + // BCB0 Black Holes: + // These are (90, 100) and (110, 100) + // B4FD Shapes + // Side that needs to be exploded will have pairs with 2 of the same x or y coords + // Side to get the shapes to explode will be closest distance to black hole + type: 'AbilityExtra', + netRegex: { id: 'B4FD', capture: true }, + run: (data, matches) => { + // Calculate Distance to Black Hole + const getDistance = ( + x: number, + y: number, + ): number => { + const blackHoleX = x < 100 ? 90 : 110; + const dx = x - blackHoleX; + const dy = y - 100; + return Math.round(Math.sqrt(dx * dx + dy * dy)); + }; + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + const d = getDistance(x, y); + const id = matches.sourceId; + + // Put into different objects for easier lookup + if (x < 100) { + data.westManaSpheres[id] = { x: x, y: y }; + } + data.eastManaSpheres[id] = { x: x, y: y }; + + // Shapes with 6 distance are close, Shapes with 12 are far + if (d < 7) { + data.closeManaSphereIds.push(id); + + // Have enough data to solve at this point + if (data.closeManaSphereIds.length === 2) { + const popSide = x < 100 ? 'east' : 'west'; + data.manaSpherePopSide = popSide; + + const sphereId1 = data.closeManaSphereIds[0]; + const sphereId2 = id; + if (sphereId1 === undefined) + return; + + const sphereType1 = data.manaSpheres[sphereId1]; + const sphereType2 = data.manaSpheres[sphereId2]; + if (sphereType1 === undefined || sphereType2 === undefined) + return; + + // If you see Water, pop side first + // If you see Wind, non-pop side + // Can't be Lightning + Wind because Fire hits the donut + // Fire + Lightning would hit whole room + // Water + Wind would hit whole room + const nonPopSide = popSide === 'east' ? 'west' : 'east'; + const first = [sphereType1, sphereType2]; + const dir2 = first.includes('water') ? popSide : nonPopSide; + data.firstBlackHole = dir2; + } + } + }, + }, + { + id: 'R12S Black Hole and Shapes', + // Black Holes and shapes + type: 'Ability', + netRegex: { id: 'B4FD', source: 'Mana Sphere', capture: false }, + delaySeconds: 0.1, + durationSeconds: 8.3, + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + const popSide = data.manaSpherePopSide; + const blackHole = data.firstBlackHole; + const sphereId1 = data.closeManaSphereIds[0]; + const sphereId2 = data.closeManaSphereIds[1]; + if ( + popSide === undefined || + blackHole === undefined || + sphereId1 === undefined || + sphereId2 === undefined + ) + return data.myMutation === 'alpha' ? output.alpha!() : output.beta!(); + + const sphereType1 = data.manaSpheres[sphereId1]; + const sphereType2 = data.manaSpheres[sphereId2]; + if (sphereType1 === undefined || sphereType2 === undefined) + return data.myMutation === 'alpha' ? output.alpha!() : output.beta!(); + + if (data.myMutation === 'alpha') + return output.alphaDir!({ + dir1: output[popSide]!(), + northSouth: output.northSouth!(), + dir2: output[blackHole]!(), + }); + return output.betaDir!({ + dir1: output[popSide]!(), + shape1: output[sphereType1]!(), + shape2: output[sphereType2]!(), + northSouth: output.northSouth!(), + dir2: output[blackHole]!(), + }); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + northSouth: { + en: 'N/S', + de: 'N/S', + fr: 'N/S', + ja: '南/北', + cn: '上/下', + ko: '남/북', + tc: '上/下', + }, + water: { + en: 'Orb', + }, + lightning: { + en: 'Lightning', + }, + fire: { + en: 'Fire', + }, + wind: { + en: 'Donut', + }, + alpha: { + en: 'Avoid Shape AoEs, Wait by Black Hole', + }, + beta: { + en: 'Shared Shape Soak => Get by Black Hole', + }, + alphaDir: { + en: 'Avoid ${dir1} Shape AoEs => ${dir2} Black Hole + ${northSouth}', + }, + betaDir: { + en: 'Share ${dir1} ${shape1}/${shape2} => ${dir2} Black Hole + ${northSouth}', + }, + }, + }, + { + id: 'R12S Dramatic Lysis Black Hole 1 Reminder', + // This may not happen if all shapes are failed + type: 'Ability', + netRegex: { id: ['B507'], source: 'Lindwurm', capture: false }, + suppressSeconds: 9999, + alertText: (data, _matches, output) => { + const blackHole = data.firstBlackHole; + if (blackHole === undefined) + return data.myMutation === 'alpha' ? output.alpha!() : output.beta!(); + return data.myMutation === 'alpha' + ? output.alphaDir!({ + northSouth: output.northSouth!(), + dir2: output[blackHole]!(), + }) + : output.betaDir!({ + northSouth: output.northSouth!(), + dir2: output[blackHole]!(), + }); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + northSouth: { + en: 'N/S', + de: 'N/S', + fr: 'N/S', + ja: '南/北', + cn: '上/下', + ko: '남/북', + tc: '上/下', + }, + alpha: { + en: 'Get by Black Hole', + }, + beta: { + en: 'Get by Black Hole', + }, + alphaDir: { + en: '${dir2} Black Hole + ${northSouth}', + }, + betaDir: { + en: '${dir2} Black Hole + ${northSouth}', + }, + }, + }, + { + id: 'R12S Blood Wakening Followup', + // Run to the other Black Hole after abilities go off + // B501 Lindwurm's Water III + // B502 Lindwurm's Aero III + // B503 Straightforward Thunder II + // B504 Sideways Fire II + type: 'Ability', + netRegex: { id: ['B501', 'B502', 'B503', 'B504'], source: 'Lindwurm', capture: false }, + suppressSeconds: 9999, + alertText: (data, _matches, output) => { + const blackHole = data.firstBlackHole; + if (blackHole === undefined) + return output.move!(); + const next = blackHole === 'east' ? 'west' : 'east'; + return output.moveDir!({ + northSouth: output.northSouth!(), + dir: output[next]!(), + }); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + northSouth: { + en: 'N/S', + de: 'N/S', + fr: 'N/S', + ja: '南/北', + cn: '上/下', + ko: '남/북', + tc: '上/下', + }, + move: { + en: 'Move to other Black Hole', + }, + moveDir: { + en: '${dir} Black Hole + ${northSouth}', + }, + }, + }, + { + id: 'R12S Netherworld Near/Far', + type: 'StartsUsing', + netRegex: { id: ['B52B', 'B52C'], source: 'Lindwurm', capture: true }, + alertText: (data, matches, output) => { + if (matches.id === 'B52B') + return data.myMutation === 'beta' + ? output.betaNear!({ mech: output.getUnder!() }) + : output.alphaNear!({ mech: output.maxMelee!() }); + return data.myMutation === 'beta' + ? output.betaFar!({ mech: output.maxMelee!() }) + : output.alphaFar!({ mech: output.getUnder!() }); + }, + outputStrings: { + getUnder: Outputs.getUnder, + maxMelee: { + en: 'Max Melee', + }, + alphaNear: { + en: '${mech} (Avoid Near Stack)', + }, + alphaFar: { + en: '${mech} (Avoid Far Stack)', + }, + betaNear: { + en: 'Near β Stack: ${mech}', + }, + betaFar: { + en: 'Far β Stack: ${mech}', + }, + }, + }, + { + id: 'R12S Idyllic Dream', + type: 'StartsUsing', + netRegex: { id: 'B509', source: 'Lindwurm', capture: false }, + durationSeconds: 4.7, + response: Responses.bigAoe('alert'), + }, + { + id: 'R12S Idyllic Dream Staging 2 Clone Order Collect', + type: 'ActorControlExtra', + netRegex: { category: '0197', param1: '11D2', capture: true }, + condition: (data) => { + if (data.phase === 'idyllic' && data.replicationCounter === 2) + return true; + return false; + }, + run: (data, matches) => { + const actor = data.actorPositions[matches.id]; + if (actor === undefined) + return; + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + data.replication3CloneOrder.push(dirNum); + }, + }, + { + id: 'R12S Idyllic Dream Staging 2 First Clone Cardinal/Intercardinal', + type: 'ActorControlExtra', + netRegex: { category: '0197', param1: '11D2', capture: true }, + condition: (data) => { + if (data.phase === 'idyllic' && data.replicationCounter === 2) + return true; + return false; + }, + suppressSeconds: 9999, + infoText: (data, matches, output) => { + const actor = data.actorPositions[matches.id]; + if (actor === undefined) + return; + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + + if (isCardinalDir(dir)) + return output.firstClone!({ cards: output.cardinals!() }); + if (isIntercardDir(dir)) + return output.firstClone!({ cards: output.intercards!() }); + return output.firstClone!({ cards: output.unknown!() }); + }, + outputStrings: { + unknown: Outputs.unknown, + cardinals: Outputs.cardinals, + intercards: Outputs.intercards, + firstClone: { + en: 'First Clone: ${cards}', + }, + }, + }, + { + id: 'R12S Idyllic Dream Staging 2 Tethered Clone Collect', + // Map the locations to a player name + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data) => { + if ( + data.phase === 'idyllic' && + data.replicationCounter === 2 + ) + return true; + return false; + }, + run: (data, matches) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return; + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + data.replication3CloneDirNumPlayers[dirNum] = matches.target; + }, + }, + { + id: 'R12S Idyllic Dream Staging 2 Tethered Clone', + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data, matches) => { + if ( + data.phase === 'idyllic' && + data.replicationCounter === 2 && + data.me === matches.target + ) + return true; + return false; + }, + suppressSeconds: 9999, + infoText: (data, matches, output) => { + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined) + return output.cloneTether!(); + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + return output.cloneTetherDir!({ dir: output[dir]!() }); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + cloneTether: { + en: 'Tethered to Clone', + }, + cloneTetherDir: { + en: 'Tethered to ${dir} Clone', + }, + }, + }, + { + id: 'R12S Idyllic Dream Power Gusher and Snaking Kick Collect', + // Need to know these for later + // B511 Snaking Kick + // B512 from boss is the VFX and has headings that show directions for B50F and B510 + // B50F Power Gusher is the East/West caster + // B510 Power Gusher is the North/South caster + // Right now just the B510 caster is needed to resolve + type: 'StartsUsing', + netRegex: { id: ['B50F', 'B510', 'B511'], source: 'Lindschrat', capture: true }, + run: (data, matches) => { + // Temporal Curtain can have early calls based on matching the id for which add went where + switch (matches.id) { + case 'B510': { + const y = parseFloat(matches.y); + data.idyllicVision2NorthSouthCleaveSpot = y < center.y ? 'north' : 'south'; + data.idyllicDreamActorEW = matches.sourceId; + return; + } + case 'B511': + data.idyllicDreamActorSnaking = matches.sourceId; + return; + case 'B50F': + data.idyllicDreamActorNS = matches.sourceId; + return; + } + }, + }, + { + id: 'R12S Idyllic Dream Power Gusher Vision', + // Call where the E/W safe spots will be later + type: 'StartsUsing', + netRegex: { id: 'B510', source: 'Lindschrat', capture: true }, + infoText: (_data, matches, output) => { + const y = parseFloat(matches.y); + const dir = y < center.y ? 'north' : 'south'; + return output.text!({ dir: output[dir]!(), sides: output.sides!() }); + }, + outputStrings: { + north: Outputs.north, + south: Outputs.south, + sides: Outputs.sides, + text: { + en: '${dir} + ${sides} (later)', + }, + }, + }, + { + id: 'R12S Replication 4 Ability Tethers Initial Call', + type: 'Tether', + netRegex: { + id: [ + headMarkerData['manaBurstTether'], + headMarkerData['heavySlamTether'], + ], + capture: true, + }, + condition: (data, matches) => { + if (data.me === matches.target && data.phase === 'idyllic') + return true; + return false; + }, + delaySeconds: 0.1, + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + const first = data.replication4DirNumAbility[0]; + if (first === undefined) { + return output.getTether!(); + } + + if (first === headMarkerData['heavySlamTether']) + return output.stacksFirst!(); + if (first === headMarkerData['manaBurstTether']) + return output.defamationsFirst!(); + return output.getTether!(); + }, + outputStrings: { + getTether: { + en: 'Get Tether', + }, + defamationsFirst: { + en: 'Defamations First (later); Get Tether', + }, + stacksFirst: { + en: 'Stacks First (later); Get Tether', + }, + }, + }, + { + id: 'R12S Replication 4 Locked Tether Collect', + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data) => { + if ( + data.phase === 'idyllic' && + data.replicationCounter === 4 + ) + return true; + return false; + }, + run: (data, matches) => { + const actor = data.actorPositions[matches.sourceId]; + const target = matches.target; + if (actor === undefined) { + // Setting to use that we know we have a tether but couldn't determine what ability it is + if (data.me === target) + data.replication4PlayerAbilities[target] = 'unknown'; + return; + } + + const dirNum = Directions.xyTo8DirNum( + actor.x, + actor.y, + center.x, + center.y, + ); + + // Store the player at each dirNum + data.replication4BossCloneDirNumPlayers[dirNum] = target; + + // Lookup what the tether was at the same location + const ability = data.replication4DirNumAbility[dirNum]; + if (ability === undefined) { + // Setting to use that we know we have a tether but couldn't determine what ability it is + data.replication4PlayerAbilities[target] = 'unknown'; + return; + } + data.replication4PlayerAbilities[target] = ability; + + // Create ability order once we have all 8 players + // If players had more than one tether previously, the extra tethers are randomly assigned + if (Object.keys(data.replication4PlayerAbilities).length === 8) { + // Used for Twisted Vision 7 and 8 mechanics + const abilities = data.replication4PlayerAbilities; + const order = data.replication3CloneOrder; // Order in which clones spawned + const players = data.replication3CloneDirNumPlayers; // Direction of player's clone + + // Mechanics are resolved clockwise, create order based on cards/inters + const first = order[0]; + if (first === undefined) + return; + const dirNumOrder = first % 2 === 0 ? [0, 2, 4, 6, 1, 3, 5, 7] : [1, 3, 5, 7, 0, 2, 4, 6]; + for (const dirNum of dirNumOrder) { + const player = players[dirNum] ?? 'unknown'; + const ability = abilities[player] ?? 'unknown'; + data.replication4PlayerOrder.push(player); + data.replication4AbilityOrder.push(ability); + } + } + }, + }, + { + id: 'R12S Replication 4 Locked Tether', + // At this point the player needs to dodge the north/south cleaves + chariot + // Simultaneously there will be a B4F2 Lindwurm's Meteor bigAoe that ends with room split + type: 'Tether', + netRegex: { id: headMarkerData['lockedTether'], capture: true }, + condition: (data, matches) => { + if ( + data.phase === 'idyllic' && + data.twistedVisionCounter === 3 && + data.me === matches.target + ) + return true; + return false; + }, + delaySeconds: 0.1, + durationSeconds: 8, + alertText: (data, matches, output) => { + const meteorAoe = output.meteorAoe!({ + bigAoe: output.bigAoe!(), + groups: output.healerGroups!(), + }); + const cleaveOrigin = data.idyllicVision2NorthSouthCleaveSpot; + const myAbility = data.replication4PlayerAbilities[data.me]; + // Get direction of the tether + const actor = data.actorPositions[matches.sourceId]; + if (actor === undefined || cleaveOrigin === undefined) { + switch (myAbility) { + case headMarkerData['manaBurstTether']: + return output.manaBurstTether!({ meteorAoe: meteorAoe }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTether!({ meteorAoe: meteorAoe }); + } + return; + } + + const dirNum = Directions.xyTo8DirNum(actor.x, actor.y, center.x, center.y); + const dir = Directions.output8Dir[dirNum] ?? 'unknown'; + + const dodge = output.dodgeCleaves!({ + dir: output[cleaveOrigin]!(), + sides: output.sides!(), + }); + + switch (myAbility) { + case headMarkerData['manaBurstTether']: + return output.manaBurstTetherDir!({ + dir: output[dir]!(), + dodgeCleaves: dodge, + meteorAoe: meteorAoe, + }); + case headMarkerData['heavySlamTether']: + return output.heavySlamTetherDir!({ + dir: output[dir]!(), + dodgeCleaves: dodge, + meteorAoe: meteorAoe, + }); + } + }, + outputStrings: { + ...Directions.outputStrings8Dir, + north: Outputs.north, + south: Outputs.south, + sides: Outputs.sides, + bigAoe: Outputs.bigAoe, + healerGroups: Outputs.healerGroups, + meteorAoe: { + en: '${bigAoe} + ${groups}', + }, + dodgeCleaves: { + en: '${dir} + ${sides}', + }, + manaBurstTetherDir: { + en: '${dodgeCleaves} (${dir} Defamation Tether) => ${meteorAoe}', + }, + manaBurstTether: { + en: ' N/S Clone (Defamation Tether) => ${meteorAoe}', + }, + heavySlamTetherDir: { + en: '${dodgeCleaves} (${dir} Stack Tether) => ${meteorAoe}', + }, + heavySlamTether: { + en: ' N/S Clone (Stack Tether) => ${meteorAoe}', + }, + }, + }, + { + id: 'R12S Arcadian Arcanum', + // Players hit will receive 1044 Light Resistance Down II debuff + type: 'StartsUsing', + netRegex: { id: 'B529', source: 'Lindwurm', capture: false }, + response: Responses.spread(), + }, + { + id: 'R12S Light Resistance Down II Collect', + // Players cannot soak a tower that has holy (triple element towers) + type: 'GainsEffect', + netRegex: { effectId: '1044', capture: true }, + condition: Conditions.targetIsYou(), + run: (data) => data.hasLightResistanceDown = true, + }, + { + id: 'R12S Light Resistance Down II', + type: 'GainsEffect', + netRegex: { effectId: '1044', capture: true }, + condition: (data, matches) => { + if (data.twistedVisionCounter === 3 && data.me === matches.target) + return true; + return false; + }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Soak Fire/Earth Meteor (later)', + }, + }, + }, + { + id: 'R12S No Light Resistance Down II', + type: 'GainsEffect', + netRegex: { effectId: '1044', capture: false }, + condition: (data) => data.twistedVisionCounter === 3, + delaySeconds: 0.1, + suppressSeconds: 9999, + infoText: (data, _matches, output) => { + if (!data.hasLightResistanceDown) + return output.text!(); + }, + outputStrings: { + text: { + en: 'Soak a White/Star Meteor (later)', + }, + }, + }, + { + id: 'R12S Twisted Vision 4 Stack/Defamation 1', + type: 'StartsUsing', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: false }, + condition: (data) => data.twistedVisionCounter === 4, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + stacks: Outputs.stacks, + stackOnYou: Outputs.stackOnYou, + defamations: { + en: 'Avoid Defamations', + }, + defamationOnYou: Outputs.defamationOnYou, + stacksThenDefamations: { + en: '${mech1} => ${mech2}', + }, + defamationsThenStacks: { + en: '${mech1} => ${mech2}', + }, + stacksThenDefamationOnYou: { + en: '${mech1} => ${mech2}', + }, + defamationsThenStackOnYou: { + en: '${mech1} => ${mech2}', + }, + stackOnYouThenDefamations: { + en: '${mech1} => ${mech2}', + }, + defamationOnYouThenStack: { + en: '${mech1} => ${mech2}', + }, + }; + const player1 = data.replication4BossCloneDirNumPlayers[0]; + const player2 = data.replication4BossCloneDirNumPlayers[4]; + const player3 = data.replication4BossCloneDirNumPlayers[1]; + const player4 = data.replication4BossCloneDirNumPlayers[5]; + const abilityId = data.replication4DirNumAbility[0]; // Only need to know one + + if ( + abilityId === undefined || player1 === undefined || + player2 === undefined || player3 === undefined || + player4 === undefined + ) + return; + + const ability1 = abilityId === headMarkerData['manaBurstTether'] + ? 'defamations' + : abilityId === headMarkerData['heavySlamTether'] + ? 'stacks' + : 'unknown'; + + if (ability1 === 'stacks') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.stackOnYouThenDefamations!({ + mech1: output.stackOnYou!(), + mech2: output.defamations!(), + }), + }; + + if (data.me === player3 || data.me === player4) + return { + infoText: output.stacksThenDefamationOnYou!({ + mech1: output.stacks!(), + mech2: output.defamationOnYou!(), + }), + }; + + return { + infoText: output.stacksThenDefamations!({ + mech1: output.stacks!(), + mech2: output.defamations!(), + }), + }; + } + + if (ability1 === 'defamations') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.defamationOnYouThenStack!({ + mech1: output.defamationOnYou!(), + mech2: output.stacks!(), + }), + }; + + if (data.me === player3 || data.me === player4) + return { + infoText: output.defamationsThenStackOnYou!({ + mech1: output.defamations!(), + mech2: output.stackOnYou!(), + }), + }; + + return { + infoText: output.defamationsThenStacks!({ + mech1: output.defamations!(), + mech2: output.stacks!(), + }), + }; + } + }, + }, + { + id: 'R12S Twisted Vision 4 Stack/Defamation 2-4', + // Used for keeping of which Twisted Vision 4 mechanic we are on + // Note: B519 Heavy Slam and B517 Mana Burst cast regardless of players alive + // A B4F0 Unmitigated Impact will occur should the stack be missed + // Note2: B518 Mana Burst seems to not cast if the target is dead, and there doesn't seem to be repercussions + type: 'Ability', + netRegex: { id: ['B519', 'B517'], source: 'Lindschrat', capture: false }, + condition: (data) => data.twistedVisionCounter === 4 && data.twistedVision4MechCounter < 6, + suppressSeconds: 1, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + stacks: Outputs.stacks, + stackOnYou: Outputs.stackOnYou, + defamations: { + en: 'Avoid Defamations', + }, + defamationOnYou: Outputs.defamationOnYou, + stacksThenDefamations: { + en: '${mech1} => ${mech2}', + }, + defamationsThenStacks: { + en: '${mech1} => ${mech2}', + }, + stacksThenDefamationOnYou: { + en: '${mech1} => ${mech2}', + }, + defamationsThenStackOnYou: { + en: '${mech1} => ${mech2}', + }, + stackOnYouThenDefamations: { + en: '${mech1} => ${mech2}', + }, + defamationOnYouThenStack: { + en: '${mech1} => ${mech2}', + }, + towers: { + en: 'Tower Positions', + de: 'Turm Positionen', + fr: 'Position tour', + ja: '塔の位置へ', + cn: '八人塔站位', + ko: '기둥 자리잡기', + tc: '八人塔站位', + }, + }; + data.twistedVision4MechCounter = data.twistedVision4MechCounter + 2; // Mechanic is done in pairs + // Don't output for first one as it was called 1s prior to this trigger + if (data.twistedVision4MechCounter < 2) + return; + const count = data.twistedVision4MechCounter; + const players = data.replication4BossCloneDirNumPlayers; + const abilityIds = data.replication4DirNumAbility; + const player1 = count === 2 + ? players[1] + : count === 4 + ? players[2] + : players[3]; + const player2 = count === 2 + ? players[5] + : count === 4 + ? players[6] + : players[7]; + const abilityId = count === 2 + ? abilityIds[1] + : count === 4 + ? abilityIds[2] + : abilityIds[3]; + + if ( + abilityId === undefined || player1 === undefined || + player2 === undefined + ) + return; + + const ability1 = abilityId === headMarkerData['manaBurstTether'] + ? 'defamations' + : abilityId === headMarkerData['heavySlamTether'] + ? 'stacks' + : 'unknown'; + + if (count < 6) { + const player3 = count === 2 ? players[2] : players[3]; + const player4 = count === 2 ? players[6] : players[7]; + if (player3 === undefined || player4 === undefined) + return; + + if (ability1 === 'stacks') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.stackOnYouThenDefamations!({ + mech1: output.stackOnYou!(), + mech2: output.defamations!(), + }), + }; + + if (data.me === player3 || data.me === player4) + return { + infoText: output.stacksThenDefamationOnYou!({ + mech1: output.stacks!(), + mech2: output.defamationOnYou!(), + }), + }; + + return { + infoText: output.stacksThenDefamations!({ + mech1: output.stacks!(), + mech2: output.defamations!(), + }), + }; + } + + if (ability1 === 'defamations') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.defamationOnYouThenStack!({ + mech1: output.defamationOnYou!(), + mech2: output.stacks!(), + }), + }; + if (data.me === player3 || data.me === player4) + return { + infoText: output.defamationsThenStackOnYou!({ + mech1: output.defamations!(), + mech2: output.stackOnYou!(), + }), + }; + + return { + infoText: output.defamationsThenStacks!({ + mech1: output.defamations!(), + mech2: output.stacks!(), + }), + }; + } + } + + // Last set followed up with tower positions + if (ability1 === 'stacks') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.stackOnYouThenDefamations!({ + mech1: output.stackOnYou!(), + mech2: output.towers!(), + }), + }; + + return { + infoText: output.stacksThenDefamations!({ + mech1: output.stacks!(), + mech2: output.towers!(), + }), + }; + } + + if (ability1 === 'defamations') { + if (data.me === player1 || data.me === player2) + return { + alertText: output.defamationOnYouThenStack!({ + mech1: output.defamationOnYou!(), + mech2: output.towers!(), + }), + }; + + return { + infoText: output.defamationsThenStacks!({ + mech1: output.defamations!(), + mech2: output.towers!(), + }), + }; + } + }, + }, + { + id: 'R12S Twisted Vision 5 Towers', + // TODO: Get Position of the towers and player side and state the front/left back/right + // Towers aren't visible until after cast, but you would have 4.4s to adjust if the trigger was delayed + // 4s castTime + type: 'StartsUsing', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: true }, + condition: (data) => data.twistedVisionCounter === 5, + durationSeconds: (_data, matches) => parseFloat(matches.castTime) + 4.1, + alertText: (data, _matches, output) => { + if (data.hasLightResistanceDown) + return output.fireEarthTower!(); + return output.holyTower!(); + }, + outputStrings: { + fireEarthTower: { + en: 'Soak Fire/Earth Meteor', + }, + holyTower: { + en: 'Soak a White/Star Meteor', + }, + }, + }, + { + id: 'R12S Hot-blooded Collect', + // Player can still cast, but shouldn't move for 5s duration + type: 'GainsEffect', + netRegex: { effectId: '12A0', capture: true }, + condition: Conditions.targetIsYou(), + run: (data, _matches) => data.hasPyretic = true, + }, + { + id: 'R12S Hot-blooded', + // Player can still cast, but shouldn't move for 5s duration + type: 'GainsEffect', + netRegex: { effectId: '12A0', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: (_data, matches) => parseFloat(matches.duration), + response: Responses.stopMoving(), + }, + { + id: 'R12S Idyllic Dream Lindwurm\'s Stone III', + // TODO: Get their target locations and output avoid + // 5s castTime + type: 'StartsUsing', + netRegex: { id: 'B4F7', source: 'Lindwurm', capture: true }, + durationSeconds: (_data, matches) => parseFloat(matches.castTime), + suppressSeconds: 1, + infoText: (_data, _matches, output) => output.avoidEarthTower!(), + outputStrings: { + avoidEarthTower: { + en: 'Avoid Earth Tower', + }, + }, + }, + { + id: 'R12S Doom Collect', + // Happens about 1.3s after Dark Tower when it casts B4F6 Lindwurm's Dark II + type: 'GainsEffect', + netRegex: { effectId: 'D24', capture: true }, + run: (data, matches) => { + data.doomPlayers.push(matches.target); + if (data.me === matches.target) + data.hasDoom = true; + }, + }, + { + id: 'R12S Doom Cleanse', + type: 'GainsEffect', + netRegex: { effectId: 'D24', capture: false }, + condition: (data) => data.CanCleanse(), + delaySeconds: 0.1, + suppressSeconds: 1, + infoText: (data, _matches, output) => { + const players = data.doomPlayers; + if (players.length === 2) { + const target1 = data.party.member(data.doomPlayers[0]); + const target2 = data.party.member(data.doomPlayers[1]); + return output.cleanseDoom2!({ target1: target1, target2: target2 }); + } + if (players.length === 1) { + const target1 = data.party.member(data.doomPlayers[0]); + return output.cleanseDoom!({ target: target1 }); + } + }, + outputStrings: { + cleanseDoom: { + en: 'Cleanse ${target}', + de: 'Reinige ${target}', + fr: 'Guérison sur ${target}', + cn: '康复 ${target}', + ko: '${target} 에스나', + tc: '康復 ${target}', + }, + cleanseDoom2: { + en: 'Cleanse ${target1}/${target2}', + }, + }, + }, + { + id: 'R12S Nearby and Faraway Portent', + // 129D Lindwurm's Portent prevents stacking the portents + // 129E Farwaway Portent + // 129F Nearby Portent + // 10s duration, need to delay to avoid earth + doom trigger overlap + // This would go out to players that soaked white/holy meteors + type: 'GainsEffect', + netRegex: { effectId: ['129E', '129F'], capture: true }, + condition: Conditions.targetIsYou(), + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 5.3, + infoText: (data, matches, output) => { + if (matches.id === '129E') { + if (data.hasDoom) { + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.farOnYouDarkDN!(); + case 'zenith': + return output.farOnYouDarkZenith!(); + } + return output.farOnYouDark!(); + } + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.farOnYouWindDN!(); + case 'zenith': + return output.farOnYouWindZenith!(); + } + return output.farOnYouWind!(); + } + if (data.hasDoom) { + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.nearOnYouDarkDN!(); + case 'zenith': + return output.nearOnYouDarkZenith!(); + } + return output.nearOnYouDark!(); + } + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.nearOnYouWindDN!(); + case 'zenith': + return output.nearOnYouWindZenith!(); + } + return output.nearOnYouWind!(); + }, + outputStrings: { + nearOnYouWindDN: { + en: 'Near on YOU: Be on Middle Hitbox', + }, + nearOnYouDarkDN: { + en: 'Near on YOU: Be on Hitbox N', + }, + farOnYouWindDN: { + en: 'Far on YOU: Be on Middle Hitbox', + }, + farOnYouDarkDN: { + en: 'Far on YOU: Be on Hitbox N', + }, + nearOnYouWindZenith: { + en: 'Near on YOU: Max Melee N', + }, + nearOnYouDarkZenith: { + en: 'Near on YOU: Be on Middle Hitbox (Lean North)', + }, + farOnYouWindZenith: { + en: 'Far on YOU: Max Melee N', + }, + farOnYouDarkZenith: { + en: 'Far on YOU: Be on Middle Hitbox (Lean North)', + }, + nearOnYouWind: { + en: 'Wind: Near on YOU', + }, + nearOnYouDark: { + en: 'Dark: Near on YOU', + }, + farOnYouWind: { + en: 'Wind: Far on YOU', + }, + farOnYouDark: { + en: 'Dark: Far on YOU', + }, + }, + }, + { + id: 'R12S Nearby and Faraway Portent Baits', + // This would go out on players that soaked fire/earth meteors + type: 'GainsEffect', + netRegex: { effectId: ['129E', '129F'], capture: true }, + condition: (data) => data.hasLightResistanceDown, + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 5.3, + suppressSeconds: 1, + infoText: (data, _matches, output) => { + if (data.hasPyretic) { + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.baitFireDN!(); + case 'zenith': + return output.baitFireZenith!(); + } + return output.baitFire!(); + } + switch (data.triggerSetConfig.portentStrategy) { + case 'dn': + return output.baitEarthDN!(); + case 'zenith': + return output.baitEarthZenith!(); + } + return output.baitEarth!(); + }, + outputStrings: { + baitFireDN: { + en: 'Bait Cone N/S Max Melee', + }, + baitEarthDN: { + en: 'Bait Cone N/S Max Melee', + }, + baitFireZenith: { + en: 'Bait Cone S, Max Melee', + }, + baitEarthZenith: { + en: 'Bait Cone Middle, Max Melee (Lean North)', + }, + baitFire: { + en: 'Fire: Bait Cone', + }, + baitEarth: { + en: 'Earth: Bait Cone', + }, + }, + }, + { + id: 'R12S Temporal Curtain Part 1 Collect', + // Describe actor going into portal + type: 'Ability', + netRegex: { id: 'B51D', source: 'Lindschrat', capture: true }, + run: (data, matches) => { + switch (matches.sourceId) { + case data.idyllicDreamActorEW: + data.idyllicVision8SafeSides = 'frontBack'; + return; + case data.idyllicDreamActorNS: + data.idyllicVision8SafeSides = 'sides'; + } + }, + }, + { + id: 'R12S Temporal Curtain Part 1', + // Describe actor going into portal + type: 'Ability', + netRegex: { id: 'B51D', source: 'Lindschrat', capture: true }, + infoText: (data, matches, output) => { + switch (matches.sourceId) { + case data.idyllicDreamActorEW: + return output.frontBackLater!(); + case data.idyllicDreamActorNS: + return output.sidesLater!(); + } + }, + outputStrings: { + frontBackLater: { + en: 'Portal + Front/Back Clone (later)', + }, + sidesLater: { + en: 'Portal + Sides Clone (later)', + }, + }, + }, + { + id: 'R12S Temporal Curtain Part 2 Collect', + // Describe actor going into portal + type: 'AbilityExtra', + netRegex: { id: 'B4D9', capture: true }, + run: (data, matches) => { + switch (matches.sourceId) { + case data.idyllicDreamActorEW: + data.idyllicVision7SafeSides = 'frontBack'; + return; + case data.idyllicDreamActorNS: + data.idyllicVision7SafeSides = 'sides'; + return; + case data.idyllicDreamActorSnaking: { + const x = parseFloat(matches.x); + data.idyllicVision7SafePlatform = x < 100 ? 'east' : 'west'; + } + } + }, + }, + { + id: 'R12S Temporal Curtain Part 2', + // Describe actor going into portal + type: 'AbilityExtra', + netRegex: { id: 'B4D9', capture: false }, + infoText: (data, _matches, output) => { + if (data.idyllicVision7SafeSides === 'frontBack') { + if (data.idyllicVision7SafePlatform === 'east') + return output.frontBackEastLater!(); + if (data.idyllicVision7SafePlatform === 'west') + return output.frontBackWestLater!(); + } + if (data.idyllicVision7SafeSides === 'sides') { + if (data.idyllicVision7SafePlatform === 'east') + return output.sidesEastLater!(); + if (data.idyllicVision7SafePlatform === 'west') + return output.sidesWestLater!(); + } + }, + outputStrings: { + frontBackWestLater: { + en: 'West Platform => Front/Back Clone (later)', + }, + sidesWestLater: { + en: 'West Platform => Sides Clone (later)', + }, + frontBackEastLater: { + en: 'East Platform => Front/Back Clone (later)', + }, + sidesEastLater: { + en: 'East Platform => Sides Clone (later)', + }, + }, + }, + { + id: 'R12S Twisted Vision 6 Light Party Stacks', + // At end of cast it's cardinal or intercard + type: 'Ability', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: false }, + condition: (data) => data.twistedVisionCounter === 6, + infoText: (data, _matches, output) => { + const first = data.replication3CloneOrder[0]; + if (first === undefined) + return; + const dirNumOrder = first % 2 === 0 ? [0, 2, 4, 6] : [1, 3, 5, 7]; + + // Need to lookup what ability is at each dir, only need cards or intercard dirs + const abilities = data.replication4AbilityOrder.splice(0, 4); + const stackDirs = []; + let i = 0; + + // Find first all stacks in cards or intercards + // Incorrect amount means players made an unsolvable? run + for (const dirNum of dirNumOrder) { + if (abilities[i++] === headMarkerData['heavySlamTether']) + stackDirs.push(dirNum); + } + // Only grabbing first two + const dirNum1 = stackDirs[0]; + const dirNum2 = stackDirs[1]; + + // If we failed to get two stacks, just output generic cards/intercards reminder + if (dirNum1 === undefined || dirNum2 === undefined) { + return first % 2 === 0 ? output.cardinals!() : output.intercards!(); + } + const dir1 = Directions.output8Dir[dirNum1] ?? 'unknown'; + const dir2 = Directions.output8Dir[dirNum2] ?? 'unknown'; + return output.stack!({ dir1: output[dir1]!(), dir2: output[dir2]!() }); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + cardinals: Outputs.cardinals, + intercards: Outputs.intercards, + stack: { + en: 'Stack ${dir1}/${dir2} + Lean Middle Out', + }, + }, + }, + { + id: 'R12S Twisted Vision 7 Safe Platform', + type: 'StartsUsing', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: true }, + condition: (data) => data.twistedVisionCounter === 7, + durationSeconds: (_data, matches) => parseFloat(matches.castTime), + infoText: (data, _matches, output) => { + if (data.idyllicVision7SafeSides === 'frontBack') { + if (data.idyllicVision7SafePlatform === 'east') + return output.frontBackEastPlatform!(); + if (data.idyllicVision7SafePlatform === 'west') + return output.frontBackWestPlatform!(); + } + if (data.idyllicVision7SafeSides === 'sides') { + if (data.idyllicVision7SafePlatform === 'east') + return output.sidesEastPlatform!(); + if (data.idyllicVision7SafePlatform === 'west') + return output.sidesWestPlatform!(); + } + return output.safePlatform!(); + }, + outputStrings: { + safePlatform: { + en: 'Move to Safe Platform Side => Dodge Cleaves', + }, + sidesWestPlatform: { + en: 'West Platform => Sides of Clone', + }, + sidesEastPlatform: { + en: 'East Platform => Sides of Clone', + }, + frontBackEastPlatform: { + en: 'East Platform => Front/Back of Clone', + }, + frontBackWestPlatform: { + en: 'West Platform => Front/Back of Clone', + }, + }, + }, + { + id: 'R12S Twisted Vision 8 Light Party Stacks', + // At end of cast it's cardinal or intercard + type: 'StartsUsing', + netRegex: { id: 'BBE2', source: 'Lindwurm', capture: false }, + condition: (data) => data.twistedVisionCounter === 8, + alertText: (data, _matches, output) => { + const first = data.replication3CloneOrder[0]; + if (first === undefined) + return; + const dirNumOrder = first % 2 !== 0 ? [0, 2, 4, 6] : [1, 3, 5, 7]; + + // Need to lookup what ability is at each dir, only need cards or intercard dirs + const abilities = data.replication4AbilityOrder.slice(4, 8); + const stackDirs = []; + let i = 0; + + // Find first all stacks in cards or intercards + // Incorrect amount means players made an unsolvable? run + for (const dirNum of dirNumOrder) { + if (abilities[i++] === headMarkerData['heavySlamTether']) + stackDirs.push(dirNum); + } + // Only grabbing first two + const dirNum1 = stackDirs[0]; + const dirNum2 = stackDirs[1]; + + // If we failed to get two stacks, just output generic cards/intercards reminder + if (dirNum1 === undefined || dirNum2 === undefined) { + return first % 2 !== 0 ? output.cardinals!() : output.intercards!(); + } + const dir1 = Directions.output8Dir[dirNum1] ?? 'unknown'; + const dir2 = Directions.output8Dir[dirNum2] ?? 'unknown'; + return output.stack!({ dir1: output[dir1]!(), dir2: output[dir2]!() }); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + cardinals: Outputs.cardinals, + intercards: Outputs.intercards, + stack: { + en: 'Stack ${dir1}/${dir2} + Lean Middle Out', + }, + }, + }, + { + id: 'R12S Twisted Vision 8 Dodge Cleaves', + // Trigger on Clone's BE5D Heavy Slam + type: 'Ability', + netRegex: { id: 'BE5D', source: 'Lindwurm', capture: false }, + alertText: (data, _matches, output) => { + if (data.idyllicVision8SafeSides === 'sides') + return output.sides!(); + if (data.idyllicVision8SafeSides === 'frontBack') + return output.frontBack!(); + }, + run: (data) => { + // Prevent re-execution of output + delete data.idyllicVision8SafeSides; + }, + outputStrings: { + sides: { + en: 'Sides of Clone', + }, + frontBack: { + en: 'Front/Back of Clone', + }, + }, + }, + { + id: 'R12S Arcadian Hell', + type: 'StartsUsing', + netRegex: { id: 'B533', source: 'Lindwurm', capture: false }, + durationSeconds: 4.7, + response: Responses.bigAoe('alert'), + }, + ], + timelineReplace: [ + { + 'locale': 'en', + 'replaceText': { + 'Netherwrath Near/Netherwrath Far': 'Netherwrath Near/Far', + 'Netherworld Near/Netherwworld Far': 'Netherworld Near/Far', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/raid/r12s.txt b/ui/raidboss/data/07-dt/raid/r12s.txt new file mode 100644 index 00000000000..abc1d59339a --- /dev/null +++ b/ui/raidboss/data/07-dt/raid/r12s.txt @@ -0,0 +1,762 @@ +### AAC HEAVYWEIGHT M4 (SAVAGE) +# ZoneId: 1327 + +hideall "--Reset--" +hideall "--sync--" + +0.0 "--Reset--" ActorControl { command: "4000000F" } window 0,100000 jump 0 + +### Phase 1: Lindwurm +# -ii B4D3 B495 B4B2 B4B4 B4B3 B4BD B4BE B53E B4B5 B4B1 BE0A B570 B56F B4AD B76A B469 B769 +# -it "Lindwurm" + +0.0 "--sync--" InCombat { inGameCombat: "1" } window 0,1 +1.0 "--sync--" AddedCombatant { npcNameId: "14380", name: "Lindschrat", job: "00", level: "64", ownerId: "0{4}", worldId: "00" } window 10,3 jump "r12s-p2-start" # Sync to P2 immediately through AddCombatant. +15.6 "The Fixer" Ability { id: "B4D7", source: "Lindwurm" } window 20,10 +25.8 "--sync--" Ability { id: "B7C4", source: "Lindwurm" } +40.9 "Mortal Slayer 1" Ability { id: ["B496", "B498"], source: "Lindwurm" } +43.9 "Mortal Slayer 2" Ability { id: ["B496", "B498"], source: "Lindwurm" } +46.9 "Mortal Slayer 3" Ability { id: ["B496", "B498"], source: "Lindwurm" } +49.9 "Mortal Slayer 4" Ability { id: ["B496", "B498"], source: "Lindwurm" } +52.9 "--sync--" Ability { id: "B7C5", source: "Lindwurm" } +61.0 "--sync--" Ability { id: "B9DB", source: "Lindwurm" } + +70.0 "Grotesquerie: Act 1" Ability { id: "BEBD", source: "Lindwurm" } window 70,5 +77.2 "--sync--" Ability { id: ["B49A", "B49B"], source: "Lindwurm" } +79.2 "Phagocyte Spotlight 1" #Ability { id: "B49E", source: "Lindwurm" } +81.2 "Phagocyte Spotlight 2" #Ability { id: "B49E", source: "Lindwurm" } +83.2 "Phagocyte Spotlight 3" #Ability { id: "B49E", source: "Lindwurm" } +85.2 "Phagocyte Spotlight 4" #Ability { id: "B49E", source: "Lindwurm" } +87.3 "--sync--" #Ability { id: "B46E", source: "Lindwurm" } +87.7 "Ravenous Reach" Ability { id: "B49D", source: "Lindwurm" } +88.0 "Hemorrhagic Projection x8" #Ability { id: "B4AF", source: "Lindwurm" } +88.0 "Dramatic Lysis x4" #Ability { id: "B4AA", source: "Lindwurm" } +88.0 "Fourth-wall Fusion" Ability { id: "B4AE", source: "Lindwurm" } +96.7 "Burst" Ability { id: "B49F", source: "Lindwurm" } +97.3 "Visceral Burst x2" #Ability { id: "B4D6", source: "Lindwurm" } +97.3 "Fourth-wall Fusion" Ability { id: "B9B9", source: "Lindwurm" } +107.3 "The Fixer" Ability { id: "B4D7", source: "Lindwurm" } + +119.5 "Grotesquerie: Act 2" Ability { id: "BEBE", source: "Lindwurm" } window 120,5 +128.7 "Phagocyte Spotlight 1" #Ability { id: "B4B6", source: "Lindwurm" } +130.7 "Phagocyte Spotlight 2" #Ability { id: "B4B6", source: "Lindwurm" } +132.7 "Phagocyte Spotlight 3" #Ability { id: "B4B6", source: "Lindwurm" } +134.7 "Cruel Coil" Ability { id: ["B4B9", "B4B8"], source: "Lindwurm" } +134.7 "Phagocyte Spotlight 4" #Ability { id: "B4B6", source: "Lindwurm" } +135.7 "--bind--" Ability { id: "B472", source: "Lindwurm" } duration 5 +140.7 "Skinsplitter 1" Ability { id: "B4BC", source: "Lindwurm" } +143.7 "--sync--" Ability { id: "B4C1", source: "Lindwurm" } +145.7 "Skinsplitter 2" Ability { id: "B4BC", source: "Lindwurm" } +145.7 "--tether 1--" +150.7 "Skinsplitter 3" Ability { id: "B4BC", source: "Lindwurm" } +150.7 "--tether 2--" +152.7 "Roiling Mass 1" Ability { id: "B4B7", source: "Blood Vessel" } +155.7 "Skinsplitter 4" Ability { id: "B4BC", source: "Lindwurm" } +155.7 "--tether 3--" +157.7 "Roiling Mass 2" Ability { id: "B4B7", source: "Blood Vessel" } +160.7 "Skinsplitter 5" Ability { id: "B4BC", source: "Lindwurm" } +160.7 "--tether 4--" +162.7 "Roiling Mass 3" Ability { id: "B4B7", source: "Blood Vessel" } +165.7 "Skinsplitter 6" Ability { id: "B4BC", source: "Lindwurm" } +167.7 "Roiling Mass 4" Ability { id: "B4B7", source: "Blood Vessel" } +170.7 "Skinsplitter 7" Ability { id: "B4BC", source: "Lindwurm" } +176.7 "Constrictor" Ability { id: "B4C2", source: "Lindwurm" } +185.8 "Splattershed (castbar)" Ability { id: "B9C4", source: "Lindwurm" } +188.2 "Splattershed" Ability { id: "B9C6", source: "Lindwurm" } +189.9 "--sync--" Ability { id: "B4CB", source: "Lindwurm" } + +205.9 "Grotesquerie: Act 3" Ability { id: "BEBF", source: "Lindwurm" } window 205,5 +209.0 "--untargetable--" +211.0 "Feral Fission" Ability { id: "BE09", source: "Lindwurm" } +212.0 "Grand Entrance" #Ability { id: ["B4A1", "B4A0"], source: "Lindwurm" } +212.3 "Grand Entrance" #Ability { id: ["B4A1", "B4A2"], source: "Lindwurm" } +212.4 "Grand Entrance" #Ability { id: "B4A3", source: "Lindwurm" } +212.5 "Grand Entrance" Ability { id: "B4A0", source: "Lindwurm" } +212.6 "Grand Entrance" #Ability { id: "B4A3", source: "Lindwurm" } +212.7 "Grand Entrance" #Ability { id: ["B4A1", "B4A2"], source: "Lindwurm" } +212.8 "Grand Entrance" #Ability { id: "B4A3", source: "Lindwurm" } +212.8 "Grand Entrance" #Ability { id: ["B4A1", "B4A2"], source: "Lindwurm" } +213.0 "Grand Entrance" #Ability { id: "B4A3", source: "Lindwurm" } +213.2 "Grand Entrance" #Ability { id: "B4A3", source: "Lindwurm" } +215.6 "Bring Down the House" Ability { id: ["B4A5", "B4A4"], source: "Lindwurm" } +218.0 "Dramatic Lysis x8" #Ability { id: "B4B0", source: "Lindwurm" } +218.2 "Metamitosis x8" Ability { id: "B923", source: "Lindwurm" } +220.6 "--targetable--" +226.9 "Split Scourge (cast)" Ability { id: "B4A7", source: "Lindwurm" } +228.0 "Split Scourge" Ability { id: "B4AB", source: "Lindwurm" } +230.4 "Venomous Scourge" Ability { id: "B4A8", source: "Lindwurm" } +231.5 "--sync--" Ability { id: "B479", source: "Lindwurm" } +239.6 "The Fixer" Ability { id: "B4D7", source: "Lindwurm" } + +251.7 "Grotesquerie: Curtain Call" Ability { id: "BEC0", source: "Lindwurm" } window 251,5 +255.7 "Phagocyte Spotlight 1" #Ability { id: "B49E", source: "Lindwurm" } +255.8 "--sync--" Ability { id: ["B49A", "B49B"], source: "Lindwurm" } +257.6 "Phagocyte Spotlight 2" #Ability { id: "B49E", source: "Lindwurm" } +259.6 "Phagocyte Spotlight 3" #Ability { id: "B49E", source: "Lindwurm" } +261.6 "Phagocyte Spotlight 4" #Ability { id: "B49E", source: "Lindwurm" } +263.6 "Phagocyte Spotlight 5" #Ability { id: "B49E", source: "Lindwurm" } +265.9 "--sync--" Ability { id: "B46E", source: "Lindwurm" } +266.5 "Ravenous Reach" Ability { id: "B49D", source: "Lindwurm" } +266.6 "Cell Shedding x4" #Ability { id: "B4AC", source: "Lindwurm" } +266.8 "Dramatic Lysis x4" #Ability { id: "B4AA", source: "Lindwurm" } +279.4 "Burst" Ability { id: "B49F", source: "Lindwurm" } +286.1 "Splattershed (castbar)" Ability { id: "B9C3", source: "Lindwurm" } +288.5 "Splattershed" Ability { id: "B9C6", source: "Lindwurm" } +290.2 "--sync--" Ability { id: "B4CC", source: "Lindwurm" } +299.3 "--sync--" Ability { id: "B7C4", source: "Lindwurm" } +314.4 "Mortal Slayer 1" Ability { id: ["B496", "B498"], source: "Lindwurm" } +317.4 "Mortal Slayer 2" Ability { id: ["B496", "B498"], source: "Lindwurm" } +320.4 "Mortal Slayer 3" Ability { id: ["B496", "B498"], source: "Lindwurm" } +323.4 "Mortal Slayer 4" Ability { id: ["B496", "B498"], source: "Lindwurm" } +326.4 "--sync--" Ability { id: "B7C5", source: "Lindwurm" } + +# Slaughershed 1 +338.5 "Slaughtershed 1 (castbar)" Ability { id: ["B4C3", "B4C6"], source: "Lindwurm" } window 338,5 +340.9 "Slaughtershed 1" Ability { id: "ADC9", source: "Lindwurm" } +342.6 "--sync--" Ability { id: "B4CB", source: "Lindwurm" } jump "r12s-p1-scourge-left-1" +342.6 "--sync--" Ability { id: "B4CD", source: "Lindwurm" } jump "r12s-p1-scourge-right-1" +342.6 "--sync--" Ability { id: "B4CE", source: "Lindwurm" } jump "r12s-p1-raptor-left-1" +342.6 "--sync--" Ability { id: "B4CC", source: "Lindwurm" } jump "r12s-p1-raptor-right-1" +349.6 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +349.6 "Fourth-wall Fusion" #Ability { id: "B4D5", source: "Lindwurm" } +350.0 "Burst" #Ability { id: "B49F", source: "Lindwurm" } +355.1 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D1", "B4D2", "B4CD", "B4CE"], source: "Lindwurm" } +356.1 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +359.8 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D2", "B4D1", "B4CE", "B4CD"], source: "Lindwurm" } +360.8 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +367.4 "Slaughtershed 2 (castbar)" #Ability { id: ["B4C3", "B4C6"], source: "Lindwurm" } +369.8 "Slaughtershed 2" #Ability { id: "ADC9", source: "Lindwurm" } + +# Serpentine Scourge Left First +442.6 label "r12s-p1-scourge-left-1" +449.6 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +449.6 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +450.0 "Burst" Ability { id: "B49F", source: "Lindwurm" } +455.1 "Serpentine Scourge Left" Ability { id: "B4D1", source: "Lindwurm" } +456.1 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +459.8 "Serpentine Scourge Right" Ability { id: "B4D2", source: "Lindwurm" } +460.8 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +464.4 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed2" + +# Serpentine Scourge Right First +540.6 label "r12s-p1-scourge-right-1" +547.6 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +547.6 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +548.0 "Burst" Ability { id: "B49F", source: "Lindwurm" } +553.1 "Serpentine Scourge Right" Ability { id: "B4D2", source: "Lindwurm" } +554.1 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +557.7 "Serpentine Scourge Left" Ability { id: "B4D1", source: "Lindwurm" } +558.7 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +562.4 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed2" + +# Raptor Knuckles Left First +641.1 label "r12s-p1-raptor-left-1" +648.1 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +648.1 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +648.5 "Burst" Ability { id: "B49F", source: "Lindwurm" } +653.6 "Raptor Knuckles Northeast" Ability { id: "B4D0", source: "Lindwurm" } +654.4 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +658.3 "Raptor Knuckles Northwest" Ability { id: "B4CF", source: "Lindwurm" } +659.1 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +662.9 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed2" + +# Raptor Knuckles Right First +742.7 label "r12s-p1-raptor-right-1" +749.7 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +749.7 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +750.1 "Burst" Ability { id: "B49F", source: "Lindwurm" } +755.2 "Raptor Knuckles Northwest" Ability { id: "B4CF", source: "Lindwurm" } +756.1 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +759.9 "Raptor Knuckles Northeast" Ability { id: "B4D0", source: "Lindwurm" } +760.7 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +764.5 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed2" + +# Slaughtershed 2 +864.4 label "r12s-p1-slaughtershed2" +867.4 "Slaughtershed 2 (castbar)" Ability { id: ["B4C3", "B4C6"], source: "Lindwurm" } +869.8 "Slaughtershed 2" Ability { id: "ADC9", source: "Lindwurm" } + +871.5 "--sync--" Ability { id: "B4CB", source: "Lindwurm" } jump "r12s-p1-scourge-left-2" +871.5 "--sync--" Ability { id: "B4CD", source: "Lindwurm" } jump "r12s-p1-scourge-right-2" +871.5 "--sync--" Ability { id: "B4CE", source: "Lindwurm" } jump "r12s-p1-raptor-left-2" +871.5 "--sync--" Ability { id: "B4CC", source: "Lindwurm" } jump "r12s-p1-raptor-right-2" +878.5 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +878.5 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +878.9 "Burst" Ability { id: "B49F", source: "Lindwurm" } +884.0 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D1", "B4D2", "B4CD", "B4CE"], source: "Lindwurm" } +884.8 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +888.6 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D2", "B4D1", "B4CE", "B4CD"], source: "Lindwurm" } +889.4 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +896.3 "Slaughtershed 3 (castbar)" #Ability { id: ["B4C3", "B4C6"], source: "Lindwurm" } +898.7 "Slaughtershed 3" #Ability { id: "ADC9", source: "Lindwurm" } + +# Serpentine Scourge Left Second +971.6 label "r12s-p1-scourge-left-2" +978.6 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +978.6 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +979.0 "Burst" Ability { id: "B49F", source: "Lindwurm" } +984.1 "Serpentine Scourge Left" Ability { id: "B4D1", source: "Lindwurm" } +985.1 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +988.7 "Serpentine Scourge Right" Ability { id: "B4D2", source: "Lindwurm" } +989.7 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +993.3 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed3" + +# Serpentine Scourge Right Second +1069.9 label "r12s-p1-scourge-right-2" +1076.9 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1076.9 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1077.3 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1082.4 "Serpentine Scourge Right" Ability { id: "B4D2", source: "Lindwurm" } +1083.4 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1087.0 "Serpentine Scourge Left" Ability { id: "B4D1", source: "Lindwurm" } +1088.0 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1091.7 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed3" + +# Raptor Knuckles Left Second +1171.5 label "r12s-p1-raptor-left-2" +1178.5 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1178.5 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1178.9 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1184.0 "Raptor Knuckles Northeast" Ability { id: "B4D0", source: "Lindwurm" } +1184.8 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1188.6 "Raptor Knuckles Northwest" Ability { id: "B4CF", source: "Lindwurm" } +1189.4 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1193.3 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed3" + +# Raptor Knuckles Right Second +1271.5 label "r12s-p1-raptor-right-2" +1278.5 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1278.5 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1278.9 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1284.0 "Raptor Knuckles Right" Ability { id: "B4CF", source: "Lindwurm" } +1284.8 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1288.6 "Raptor Knuckles Left" Ability { id: "B4D0", source: "Lindwurm" } +1289.4 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1293.3 "--sync--" StartsUsing { id: ["B4C3", "B4C6"], source: "Lindwurm" } forcejump "r12s-p1-slaughtershed3" + +# Slaughtershed 3 +1393.3 label "r12s-p1-slaughtershed3" +1396.3 "Slaughtershed 3 (castbar)" Ability { id: ["B4C3", "B4C6"], source: "Lindwurm" } +1398.7 "Slaughtershed 3" Ability { id: "ADC9", source: "Lindwurm" } + +1400.4 "--sync--" Ability { id: "B4CB", source: "Lindwurm" } jump "r12s-p1-scourge-left-3" +1400.4 "--sync--" Ability { id: "B4CD", source: "Lindwurm" } jump "r12s-p1-scourge-right-3" +1400.4 "--sync--" Ability { id: "B4CE", source: "Lindwurm" } jump "r12s-p1-raptor-left-3" +1400.4 "--sync--" Ability { id: "B4CC", source: "Lindwurm" } jump "r12s-p1-raptor-right-3" +1407.4 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1407.4 "Fourth-wall Fusion" #Ability { id: "B4D5", source: "Lindwurm" } +1407.8 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1412.9 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D1", "B4D2", "B4CD", "B4CE"], source: "Lindwurm" } +1413.9 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +1417.6 "Serpentine Scourge/Raptor Knuckles?" #Ability { id: ["B4D2", "B4D1", "B4CE", "B4CD"], source: "Lindwurm" } +1418.6 "Serpentine Scourge/--knockback--?" #Ability { id: ["B9BC", "B9C7"], source: "Lindwurm" } +1431.5 "The Fixer (Enrage)?" #Ability { id: "B2C7", source: "Lindwurm" } +1436.7 "Refreshing Overkill (Enrage)?" #Ability { id: "B538", source: "Lindwurm" } + +# Serpentine Scourge Left Third +1498.8 label "r12s-p1-scourge-left-3" +1505.8 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1505.8 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1506.2 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1511.3 "Serpentine Scourge Left" Ability { id: "B4D1", source: "Lindwurm" } +1512.3 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1515.9 "Serpentine Scourge Right" Ability { id: "B4D2", source: "Lindwurm" } +1516.9 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1526.5 "--sync--" StartsUsing { id: "B2C7", source: "Lindwurm" } jump "r12s-p1-enrage-alt" +1526.5 "--untargetable?--" +1531.5 "The Fixer (Enrage)?" #Ability { id: "B2C7", source: "Lindwurm" } +1536.7 "Refreshing Overkill (Enrage)?" #Ability { id: "B538", source: "Lindwurm" } +1536.8 "--untargetable?--" +1536.8 "Refreshing Overkill (Enrage)?" #Ability { id: "B53A", source: "Lindwurm" } +1546.7 "--sync--" StartsUsing { id: "B538", source: "Lindwurm" } window 20,3 forcejump "r12s-p1-enrage" + +# Serpentine Scourge Right Third +1600.4 label "r12s-p1-scourge-right-3" +1607.4 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1607.4 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1607.8 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1612.9 "Serpentine Scourge Left/Right" Ability { id: "B4D2", source: "Lindwurm" } +1613.9 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1617.6 "Serpentine Scourge Right/Left" Ability { id: "B4D1", source: "Lindwurm" } +1618.6 "Serpentine Scourge" Ability { id: "B9BC", source: "Lindwurm" } +1626.5 "--sync--" StartsUsing { id: "B2C7", source: "Lindwurm" } jump "r12s-p1-enrage-alt" +1626.5 "--untargetable?--" +1631.5 "The Fixer (Enrage)?" #Ability { id: "B2C7", source: "Lindwurm" } +1636.7 "Refreshing Overkill (Enrage)?" #Ability { id: "B538", source: "Lindwurm" } +1636.8 "--untargetable?--" +1636.8 "Refreshing Overkill (Enrage)?" #Ability { id: "B53A", source: "Lindwurm" } + +1646.7 "--sync--" StartsUsing { id: "B538", source: "Lindwurm" } window 20,3 forcejump "r12s-p1-enrage" + +# Raptor Knuckles Left Third +1700.4 label "r12s-p1-raptor-left-3" +1707.4 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1707.4 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1707.8 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1712.9 "Raptor Knuckles Northeast" Ability { id: "B4D0", source: "Lindwurm" } +1713.7 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1717.5 "Raptor Knuckles Northwest" Ability { id: "B4CF", source: "Lindwurm" } +1718.3 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1726.5 "--sync--" StartsUsing { id: "B2C7", source: "Lindwurm" } jump "r12s-p1-enrage-alt" +1726.5 "--untargetable?--" +1731.5 "The Fixer (Enrage)?" #Ability { id: "B2C7", source: "Lindwurm" } +1736.7 "Refreshing Overkill (Enrage)?" #Ability { id: "B538", source: "Lindwurm" } +1736.8 "--untargetable?--" +1736.8 "Refreshing Overkill (Enrage)?" #Ability { id: "B53A", source: "Lindwurm" } +1746.7 "--sync--" StartsUsing { id: "B538", source: "Lindwurm" } window 20,3 forcejump "r12s-p1-enrage" + +# Raptor Knuckles Right Third +1800.4 label "r12s-p1-raptor-right-3" +1807.4 "Dramatic Lysis x4" #Ability { id: "B4D4", source: "Lindwurm" } +1807.4 "Fourth-wall Fusion" Ability { id: "B4D5", source: "Lindwurm" } +1807.8 "Burst" Ability { id: "B49F", source: "Lindwurm" } +1812.9 "Raptor Knuckles Northwest" Ability { id: "B4CF", source: "Lindwurm" } +1813.7 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1817.5 "Raptor Knuckles Northeast" Ability { id: "B4D0", source: "Lindwurm" } +1818.3 "--knockback--" Ability { id: "B9C7", source: "Lindwurm" } +1826.5 "--sync--" StartsUsing { id: "B2C7", source: "Lindwurm" } jump "r12s-p1-enrage-alt" +1826.5 "--untargetable?--" +1831.5 "The Fixer (Enrage)?" #Ability { id: "B2C7", source: "Lindwurm" } +1836.7 "Refreshing Overkill (Enrage)?" #Ability { id: "B538", source: "Lindwurm" } +1836.8 "--untargetable?--" +1836.8 "Refreshing Overkill (Enrage)?" #Ability { id: "B53A", source: "Lindwurm" } +1846.7 "--sync--" StartsUsing { id: "B538", source: "Lindwurm" } window 20,3 forcejump "r12s-p1-enrage" + +# Enrage sequence (Above 20%?) +1926.5 label "r12s-p1-enrage-alt" +1931.5 "The Fixer (Enrage)" Ability { id: "B2C7", source: "Lindwurm" } + +# Enrage sequence +2027.7 label "r12s-p1-enrage" +2036.7 "Refreshing Overkill (Enrage)?" Ability { id: "B538", source: "Lindwurm" } window 510,5 +2036.8 "--untargetable--" +2036.8 "Refreshing Overkill (Enrage)?" Ability { id: "B53A", source: "Lindwurm" } + +# Kill/Transition sequence +2036.8 "Refreshing Overkill" Ability { id: "B539", source: "Lindwurm" } +2073.0 "--sync--" Ability { id: "BB9C", source: "Lindwurm" } +2073.5 "Down for the Count" duration 42 +2075.7 "--sync--" Ability { id: "B53B", source: "Lindwurm" } +2117.7 "--targetable--" + + +### Phase 2: Lindwurm II +# -p B528:3015.7 +# -ii B51F B4DA B4DB B4DD B4DF B4E3 B4E6 B4F0 B4E9 B4F1 B4EE B4EF B508 B4F5 B512 B513 B514 B515 B4F9 B51B +# -it "Lindwurm" +3000.5 label "r12s-p2-start" +3010.7 "--sync--" StartsUsing { id: "B528", source: "Lindwurm" } window 3100,10 +3015.7 "Arcadia Aflame" Ability { id: "B528", source: "Lindwurm" } +3022.9 "--middle--" Ability { id: "B4D9", source: "Lindwurm" } +3028.0 "Replication 1" Ability { id: "B4D8", source: "Lindwurm" } +3039.7 "Top-tier Slam x2" Ability { id: "B4DE", source: "Lindschrat" } +3040.5 "Winged Scourge x4" Ability { id: "B4DC", source: "Lindwurm" } +3040.7 "Mighty Magic x4" #Ability { id: "B4E0", source: "Lindwurm" } +3045.3 "Snaking Kick" Ability { id: "B527", source: "Lindwurm" } +3053.7 "--clones move 1--" #Ability { id: "B4D9", source: "Lindschrat" } +3054.7 "--clones move 2--" #Ability { id: "B4D9", source: "Lindschrat" } +3061.0 "Top-tier Slam x2" Ability { id: "B4DE", source: "Lindschrat" } +3061.8 "Winged Scourge x4" Ability { id: "B4DC", source: "Lindwurm" } +3062.1 "Mighty Magic x4" #Ability { id: "B4E0", source: "Lindwurm" } +3069.4 "Double Sobat (castbar)" Ability { id: "B520", source: "Lindwurm" } +3070.0 "Double Sobat 1" Ability { id: ["B521", "B522", "B523", "B524"], source: "Lindwurm" } +3074.6 "Double Sobat 2" Ability { id: "B525", source: "Lindwurm" } +3077.0 "Esoteric Finisher" Ability { id: "B526", source: "Lindwurm" } + +# NOTE: Depending on clone order and tethers player takes, spells at each instance will differ +# TBD: Generalize these? +3091.2 "Staging" Ability { id: "B4E1", source: "Lindwurm" } +3092.5 "--clones x2 1--" #ActorControlExtra { category: "0197", param1: "11D2" } +3094.0 "--clones x2 2--" #ActorControlExtra { category: "0197", param1: "11D2" } +3095.5 "--clones x2 3--" #ActorControlExtra { category: "0197", param1: "11D2" } +3097.0 "--clones x2 4--" #ActorControlExtra { category: "0197", param1: "11D2" } +3099.1 "--locked tethers--" Tether { id: "0175", source: "Understudy" } +3102.2 "--sync--" Ability { id: "B4E2", source: "Lindwurm" } +3105.4 "Replication 2" Ability { id: "B4D8", source: "Lindwurm" } +3108.6 "--boss clones x6--" ActorControlExtra { category: "0197", param1: "11D5" } +3113.8 "--tethers--" #Tether { id: ["016F", "0170", "0171", "0176"] } # These could change hands causing sync issue +3121.8 "--locked tethers--" Tether { id: "0175", source: ["Lindschrat", "Lindwurm"] } +3127.8 "Firefall Splash" Ability { id: "B4E4", source: "Lindwurm" } +3128.5 "Scalding Waves x4" Ability { id: "B4E5", source: "Lindwurm" } +3129.9 "Mana Burst x3" Ability { id: "B4E7", source: "Lindwurm" } +3135.5 "Heavy Slam x2" Ability { id: "B4E8", source: "Lindschrat" } +3136.7 "Grotesquerie x2" Ability { id: "B4EA", source: "Lindwurm" } +3137.3 "Hemorrhagic Projection x2" Ability { id: "B4EB", source: "Lindwurm" } +3141.1 "Snaking Kick" Ability { id: "B527", source: "Lindwurm" } + +3151.3 "Reenactment 1" Ability { id: "B4EC", source: "Lindwurm" } +3159.4 "Firefall Splash" #Ability { id: "B4ED", source: "Lindschrat" } +3159.4 "Netherwrath Near/Netherwrath Far" Ability { id: ["B52E", "B52F"], source: "Lindwurm" } +3160.6 "Mana Burst 1" Ability { id: "BBE3", source: "Lindwurm" } +3160.6 "Timeless Spite x2" Ability { id: "B530", source: "Lindwurm" } +3160.7 "Scalding Waves x4" #Ability { id: "B8E1", source: "Lindwurm" } +3164.5 "Grotesquerie 1" #Ability { id: "B4EA", source: "Lindwurm" } +3164.5 "Mana Burst 2" Ability { id: "BBE3", source: "Lindwurm" } +3165.1 "Hemorrhagic Projection 1" Ability { id: "B922", source: "Lindwurm" } +3168.8 "Heavy Slam x2" Ability { id: "BE5D", source: "Lindwurm" } +3172.6 "Mana Burst 3" Ability { id: "BBE3", source: "Lindwurm" } +3172.6 "Grotesquerie 2" #Ability { id: "B4EA", source: "Lindwurm" } +3173.2 "Hemorrhagic Projection 2" Ability { id: "B922", source: "Lindwurm" } +3178.6 "--middle--" Ability { id: "B4D9", source: "Lindwurm" } + +# Blood Mana / Blood Wakening Phase (Superchain) +3183.8 "Mutating Cells" Ability { id: "B505", source: "Lindwurm" } +3185.0 "--sync--" Ability { id: "B506", source: "Lindwurm" } +3190.0 "Blood Mana" Ability { id: "B4FB", source: "Lindwurm" } +3193.3 "--black holes--" Ability { id: "BCB0", source: "Mana Sphere" } +3193.7 "--shapes--" Ability { id: "B4FD", source: "Mana Sphere" } +3200.7 "Bloody Burst x2" #Ability { id: "B4FE", source: "Lindwurm" } # Goes off when soaked by player +3202.2 "Dramatic Lysis x8" Ability { id: "B507", source: "Lindwurm" } +3203.2 "--close shapes eaten--" Ability { id: "B4FF", source: "Mana Sphere" } +3204.1 "--sync--" Ability { id: "BCB0", source: "Mana Sphere" } # Blackhole 1 +3206.2 "--far shapes eaten--" Ability { id: "B4FF", source: "Mana Sphere" } +3207.0 "--sync--" Ability { id: "BCB0", source: "Mana Sphere" } # Blackhole 2 +3209.2 "--soaked shapes eaten--" Ability { id: "B4FF", source: "Mana Sphere" } +3210.1 "--sync--" Ability { id: "BCB0", source: "Mana Sphere" } # blackhole 1 + +3216.7 "Blood Wakening" Ability { id: "B500", source: "Lindwurm" } +3217.9 "--sync--" Ability { id: "B4FC", source: "Mana Sphere" } +3218.3 "Black Hole 1" Ability { id: ["B501", "B502", "B503", "B504"], source: "Lindwurm" } +3222.9 "--sync--" Ability { id: "B4FC", source: "Mana Sphere" } +3223.3 "Black Hole 2" Ability { id: ["B501", "B502", "B503", "B504"], source: "Lindwurm" } +3227.6 "Netherworld Near/Netherworld Far" Ability { id: ["B52B", "B52C"], source: "Lindwurm" } +3228.8 "Wailing Wave x3" Ability { id: "B52D", source: "Lindwurm" } +3231.8 "Dramatic Lysis x8" Ability { id: "B507", source: "Lindwurm" } +3235.8 "Arcadia Aflame" Ability { id: "B528", source: "Lindwurm" } +3245.0 "Double Sobat (castbar)" Ability { id: "B520", source: "Lindwurm" } +3245.6 "Double Sobat 1" Ability { id: ["B521", "B522", "B523", "B524"], source: "Lindwurm" } +3250.2 "Double Sobat 2" Ability { id: "B525", source: "Lindwurm" } +3252.6 "Esoteric Finisher" Ability { id: "B526", source: "Lindwurm" } +3260.7 "--middle--" Ability { id: "B4D9", source: "Lindwurm" } + +# Idyllic Dream +3268.8 "Idyllic Dream" Ability { id: "B509", source: "Lindwurm" } +3275.0 "Staging" Ability { id: "B4E1", source: "Lindwurm" } +3276.3 "--clones x4 1--" #ActorControlExtra { category: "0197", param1: "11D2" } +3278.3 "--clones x4 2--" #ActorControlExtra { category: "0197", param1: "11D2" } +3280.4 "--locked tethers--" Tether { id: "0175", source: "Understudy" } +3283.4 "--sync--" Ability { id: "B4E2", source: "Lindwurm" } +3290.1 "Twisted Vision 1" Ability { id: "BBE2", source: "Lindwurm" } +3296.2 "Replication 3" Ability { id: "B4D8", source: "Lindwurm" } +3299.4 "--boss clones x3--" ActorControlExtra { category: "0197", param1: "11D5" } +3308.6 "Twisted Vision 2" Ability { id: "BBE2", source: "Lindwurm" } +3309.6 "Power Gusher x2" #Ability { id: ["B50F", "B510"], source: "Lindschrat" } +3309.6 "Snaking Kick" #Ability { id: "B511", source: "Lindschrat" } +3314.7 "Replication 4" Ability { id: "B4D8", source: "Lindwurm" } +3317.9 "--boss clones x2 1--" #ActorControlExtra { category: "0197", param1: "11D5" } +3318.9 "--boss clones x2 2--" #ActorControlExtra { category: "0197", param1: "11D5" } +3319.9 "--boss clones x2 3--" #ActorControlExtra { category: "0197", param1: "11D5" } +3320.9 "--boss clones x2 4--" #ActorControlExtra { category: "0197", param1: "11D5" } +3326.1 "--tethers--" #Tether { id: ["0170", "0171"], source: "Lindschrat" } # These could change hands causing sync issue +3334.1 "--locked tethers--" Tether { id: "0175", source: "Lindschrat" } + +# Twisted Vision 3: Towers Preview +3334.3 "Twisted Vision 3" Ability { id: "BBE2", source: "Lindwurm" } +3338.7 "Snaking Kick" Ability { id: "BE95", source: "Lindwurm" } +3338.9 "Power Gusher x4" #Ability { id: "B516", source: "Lindwurm" } # Front/Back are apparently counting as their own casts +3342.8 "Lindwurm's Meteor" Ability { id: "B4F2", source: "Lindwurm" } +3348.9 "Downfall" Ability { id: "B4F3", source: "Lindwurm" } +3355.0 "Arcadian Arcanum (castbar)" Ability { id: "B529", source: "Lindwurm" } +3356.2 "Arcadian Arcanum" Ability { id: "B9D9", source: "Lindwurm" } + +# Twisted Vision 4: First Stacks/Defamations +# Orders could be different, but we can detect B517 abiltiy to know if Mana Burst is coming 1.2s before B518 +3363.0 "Twisted Vision 4" Ability { id: "BBE2", source: "Lindwurm" } +3369.6 "Clone 1 Heavy Slam?" Ability { id: "B519", source: "Lindschrat" } # Or B517 Mana Burst (Lindschrat) happens here +3370.8 "Clone 1 Mana Burst?" Ability { id: "B518", source: "Lindwurm" } +3374.6 "Clone 2 Heavy Slam?" Ability { id: "B519", source: "Lindschrat" } # Or B517 Mana Burst (Lindschrat) happens here +3375.8 "Clone 2 Mana Burst?" Ability { id: "B518", source: "Lindwurm" } +3379.6 "Clone 3 Heavy Slam?" Ability { id: "B519", source: "Lindschrat" } # Or B517 Mana Burst (Lindschrat) happens here +3380.8 "Clone 3 Mana Burst?" Ability { id: "B518", source: "Lindwurm" } +3384.6 "Clone 4 Heavy Slam?" Ability { id: "B519", source: "Lindschrat" } # Or B517 Mana Burst (Lindschrat) happens here +3385.8 "Clone 4 Mana Burst?" Ability { id: "B518", source: "Lindwurm" } + +# Twisted Vision 5: Towers +3393.6 "Twisted Vision 5" Ability { id: "BBE2", source: "Lindwurm" } +3398.0 "Cosmic Kiss x8" Ability { id: "B4F4", source: "Lindwurm" } +3398.6 "--Hot-blooded x2--" duration 5 +3398.7 "Lindwurm's Dark II x2" Ability { id: "B4F6", source: "Lindwurm" } +3398.7 "--Doom x2--" duration 8 +3403.7 "Lindwurm's Stone III x2" #Ability { id: "B4F7", source: "Lindwurm" } +3408.7 "Lindwurm's Thunder II x4" #Ability { id: "B4FA", source: "Lindwurm" } +3408.7 "Lindwurm's Glare x4" #Ability { id: "B4F8", source: "Lindwurm" } +3417.1 "Temporal Curtain" Ability { id: "B51C", source: "Lindwurm" } +3420.2 "--clone takes portal--" Ability { id: "B51D", source: "Lindschrat" } +3423.3 "--clones on platform--" Ability { id: "B4D9", source: "Lindschrat" } + +# Twisted Vision 6: Reenactment 2 Part 1 +# NOTE: Practical solution seems to be that you have x2 mana burts + x2 heavy slams +# This is because mana burts hits will knockback and heavy slams give Magic Vulns +# In theory you could do somehow get here and survive with: +# 3x Mana Burst + 1 Heavy Slam => 3x Heavy Slam + 1 Mana Burst or +# 3x Heavy Slam + 1 Mana Burst => 3x Mana Burst + 1 Heavy Slam +# But those would probably require lots of mit and tank immune probably on 2/3 of the heavy slams in the 3 set +3429.6 "Twisted Vision 6" Ability { id: "BBE2", source: "Lindwurm" } +3430.6 "Power Gusher" Ability { id: "B50F", source: "Lindschrat" } +3430.6 "Snaking Kick" Ability { id: "BCAF", source: "Lindschrat" } +3435.7 "Reenactment 2" Ability { id: "B4EC", source: "Lindwurm" } +3439.0 "Clone Mana Burst x2" Ability { id: "BBE3", source: "Lindwurm" } +3439.2 "Clone Heavy Slam x2" Ability { id: "BE5D", source: "Lindwurm" } + +# Twisted Vision 7: Safe Platform + Front/Back or Sides Platform +3444.8 "Twisted Vision 7" Ability { id: "BBE2", source: "Lindwurm" } +3449.2 "Snaking Kick" Ability { id: "BE95", source: "Lindwurm" } +3449.4 "Power Gusher" Ability { id: "B516", source: "Lindwurm" } + +# Twisted Vision 8: Reenactment 2 Part 2 +3452.3 "Twisted Vision 8" Ability { id: "BBE2", source: "Lindwurm" } +3458.6 "--sync--" Ability { id: "B51E", source: "Lindschrat" } +3459.8 "Clone Mana Burst x2" Ability { id: "BBE3", source: "Lindwurm" } +3460.0 "Clone Heavy Slam x2" Ability { id: "BE5D", source: "Lindwurm" } +3464.8 "Power Gusher" Ability { id: "B516", source: "Lindwurm" } +3470.7 "Idyllic Dream" Ability { id: "B509", source: "Lindwurm" } +3478.8 "Double Sobat (castbar)" Ability { id: "B520", source: "Lindwurm" } +3479.6 "Double Sobat 1" Ability { id: ["B521", "B522", "B523", "B524"], source: "Lindwurm" } +3484.2 "Double Sobat 2" Ability { id: "B525", source: "Lindwurm" } +3486.6 "Esoteric Finisher" Ability { id: "B526", source: "Lindwurm" } + +# Enrage Sequence +3499.8 "Replication 5" Ability { id: "B46C", source: "Lindwurm" } +3513.2 "Arcadian Hell 1 (boss) " Ability { id: "B533", source: "Lindwurm" } +3513.2 "Arcadian Hell 1 x4 (clones)" #Ability { id: "B534", source: "Lindschrat" } +3529.4 "Arcadian Hell 2 (boss)" Ability { id: "B533", source: "Lindwurm" } +3529.4 "Arcadian Hell 2 x8 (clones)" #Ability { id: "B535", source: "Lindschrat" } +3542.3 "--sync--" StartsUsing { id: "B537", source: "Lindwurm" } +3552.3 "Arcadian Hell (Boss Enrage)" Ability { id: "B537", source: "Lindwurm" } +3552.3 "Arcadian Hell x16 (Clones Enrage)" #Ability { id: "BEC1", source: "Lindschrat" } + +# IGNORED ABILITIES +# Phase 1 +# B4D3 --sync--: Attack autos +# B495 Mortal Slayer: VFX +# B4B2 Unmitigated Explosion: Missed blob tower +# B4B4 Dramatic Lysis: Damage from breaking a tether; These can be broken at various times +# B4B5 Dramatic Lysis: 6s to break tether, else this applies every 1s, doing damage and giving a damage down +# B4B3 Roiling Mass: Damage from soaking a tether-created tower; The towers can be created at various times +# B4BE Constrictor: VFX Ending of Cruel Coil B4B8 +# B4BD Constrictor: VFX Ending of Cruel Coil B4B9 +# B4B1 Metamitosis: VFX +# B469 Ravenous Reach: VFX +# B769 Ravenous Reach: VFX +# B76A Ravenous Reach: VFX +# B56F --sync--: These happen same time as Bring Down the House BE0A +# B570 --sync--: These happen same time as Bring Down the House BE0A +# BE0A Bring Down the House: VFX +# B4CF Raptor Knuckles: VFX for knockback from NW +# B4D0 Raptor Knuckles: VFX for knockback from NE +# B4AD Cell Death: Failing Cell Shedding by not getting hit by Ravenous Reach + +# Phase 2 +# B51F --sync--: Attack autos +# B4DA Winged Scourge: VFX E/W clones Facing S, Cleaving Front/Back (North/South) +# B4DB Winged Scourge: VFX N/S clones Facing W, Cleaving Front/Back (East/West) +# B4DD Top-tier Slam: VFX (cast that gives Fire Debuff) +# B4DF Mighty Magic: VFX (cast that gives Dark Debuff) +# B4E3 Firefall Splash: VFX +# B4E6 Mana Burst: VFX +# B4F0 Unmitigated Impact: No one stacked in a Heavy Slam, causes 1035 Sustained Damage DoT +# B4E9 Grotesquerie: VFX +# B4F1 Grotesquerie: VFX used in Reenactment +# B4EE Mana Burst: VFX used in Reenactment +# B4EF Heavy Slam: VFX used in Reenactment +# B508 Unmitigated Explosion: Getting hit when you have Mitigation α or failing to get hit with Mitigation β, applies 30s damage down +# B4FF --sync--: VFX Mana Spheres eaten by Black Hole +# BCB0 --sync--: VFX Black Hole light eruption +# B4F5 Unmitigated Explosion: Missing Cosmic Kiss Tower (Twisted Vision 5) +# B512 Power Gusher: VFX during Twisted Vision 2 with B50F and B510 +# B513 Power Gusher: VFX during Twisted Vision 3, related to the B50F +# B514 Power Gusher: VFX during Twisted Vision 3, related to the B510 +# B515 Snaking Kick: VFX during Twisted Vision 3 +# B4F9 Pyretic Wurm: Damage suffered when player moves while under affect of 12A0 Hot-blooded +# B51B Power Gusher: VFX during Twisted Vision 8, related to B516 + +# ALL ENCOUNTER ABILITIES +# Phase 1 +# ADC9 Slaughtershed +# B2C7 The Fixer: Alternative enrage +# B469 Ravenous Reach: VFX +# B46E --sync-- +# B472 --sync-- Animation coinciding with boss jumping to middle and binding the players +# B479 --sync-- +# B495 Mortal Slayer +# B496 Mortal Slayer: Green (DPS/Healer) Orbs +# B498 Mortal Slayer: Purple (Tank) Orbs +# B49A --sync--: Animation for Wyrm moving West (Cleaving East) +# B49B --sync--: Animation for Wyrm moving East (Cleaving West) +# B49D Ravenous Reach +# B49E Phagocyte Spotlight +# B49F Burst +# B4A0 Grand Entrance: Small circle aoe of wyrm going into ground? Does a knockback and gives damage down +# B4A1 Grand Entrance: Only cast when caradinals are safe; Small circle aoe of wyrm coming out of ground? Does a knockback and gives damage down +# B4A2 Grand Entrance: Only cast when intercardinal are safe +# B4A3 Grand Entrance +# B4A4 Bring Down the House +# B4A5 Bring Down the House +# B4A6 Bring Down the House +# B4A7 Split Scourge +# B4A8 Venomous Scourge +# B4AA Dramatic Lysis: Spread AoE damage +# B4AB Split Scourge +# B4AC Cell Shedding +# B4AD Cell Death +# B4AE Fourth-wall Fusion +# B4AF Hemorrhagic Projection +# B4B0 Dramatic Lysis +# B4B1 Metamitosis +# B4B2 Unmitigated Explosion +# B4B3 Roiling Mass: Chain Tower soaks +# B4B4 Dramatic Lysis +# B4B5 Dramatic Lysis +# B4B6 Phagocyte Spotlight +# B4B7 Roiling Mass: Blob Tower Soaks +# B4B8 Cruel Coil: Boss starting one way? +# B4B9 Cruel Coil: Boss starting the other way? +# B4BA Cruel Coil +# B4BC Skinsplitter +# B4BE Constrictor: VFX Ending of Cruel Coil B4B8 +# B4BD Constrictor: VFX Ending of Cruel Coil B4B9 +# B4BF Constrictor +# B4C1 --sync-- +# B4C2 Constrictor: "soft enrage" damage for Cruel Coil +# B4C3 Slaughtershed +# B4C6 Slaughtershed +# B4CB --sync--: Animation that indicates B4D1 Serpintine Scourge (Left Hand/E) is first +# B4CC --sync--: Animation that indicates B4CF Raptor Knuckles (Right Hand/NW) is first +# B4CD --sync--: Animation that indicates B4D2 Serpintine Scourge (Right hand/W) is first +# B4CE --sync--: Animation that indicates B4D0 Raptor Knuckles (Left Hand/NE) is first +# B4CF Raptor Knuckles +# B4D0 Raptor Knuckles +# B4D1 Serpentine Scourge +# B4D2 Serpentine Scourge +# B4D3 --sync-- +# B4D4 Dramatic Lysis +# B4D5 Fourth-wall Fusion +# B4D6 Visceral Burst +# B4D7 The Fixer +# B4F5 Unmitigated Explosion +# B4F9 Pyretic Wurm +# B538 Refreshing Overkill: Enrage castbar +# B539 Refreshing Overkill: Non-enrage +# B53A Refreshing Overkill: Enrage +# B53E Skinsplitter: Damage from running into snake during Cruel Coil +# B56F --sync-- +# B570 --sync-- +# B571 --sync-- +# B769 Ravenous Reach: VFX +# B76A Ravenous Reach: VFX +# B7C4 --sync-- +# B7C5 --sync-- +# B923 Metamitosis +# B9B9 Fourth-wall Fusion +# B9BC Serpentine Scourge +# B9C3 Splattershed +# B9C4 Splattershed +# B9C6 Splattershed +# B9C7 Raptor Knuckles: Damage +# B9DB --sync-- +# BE09 Feral Fission +# BE0A Bring Down the House +# BEBD Grotesquerie: Act 1 +# BEBE Grotesquerie: Act 2 +# BEBF Grotesquerie: Act 3 +# BEC0 Grotesquerie: Curtain Call + +# Phase 2 +# B46C Replication +# B4D8 Replication +# B4D9 --sync-- +# B4DA Winged Scourge +# B4DB Winged Scourge +# B4DC Winged Scourge +# B4DD Top-tier Slam +# B4DE Top-tier Slam +# B4DF Mighty Magic +# B4E0 Mighty Magic +# B4E1 Staging +# B4E2 --sync-- +# B4E3 Firefall Splash +# B4E4 Firefall Splash +# B4E5 Scalding Waves +# B4E6 Mana Burst +# B4E7 Mana Burst +# B51F --sync-- +# B4E8 Heavy Slam +# B4E9 Grotesquerie +# B4EA Grotesquerie +# B4EB Hemorrhagic Projection +# B4EC Reenactment +# B4ED Firefall Splash +# B4EE Mana Burst +# B4EF Heavy Slam +# B4F0 Unmitigated Impact +# B4F1 Grotesquerie +# B4F2 Lindwurm's Meteor +# B4F3 Downfall +# B4F4 Cosmic Kiss +# B4F5 Unmitigated Explosion +# B4F6 Lindwurm's Dark II +# B4F7 Lindwurm's Stone III +# B4F8 Lindwurm's Glare +# B4FA Lindwurm's Thunder II +# B4FB Blood Mana +# B4FC --sync-- +# B4FD --sync-- +# B4FE Bloody Burst +# B4FF --sync-- +# B500 Blood Wakening +# B501 Lindwurm's Water III +# B502 Lindwurm's Aero III +# B503 Straightforward Thunder II +# B504 Sideways Fire II +# B505 Mutating Cells +# B506 --sync-- +# B507 Dramatic Lysis +# B508 Unmitigated Explosion +# B509 Idyllic Dream +# B50F Power Gusher +# B510 Power Gusher +# B511 Snaking Kick +# B512 Power Gusher +# B513 Power Gusher +# B514 Power Gusher +# B515 Snaking Kick +# B516 Power Gusher: Cast during Twisted Vision 7 and 8 +# B517 Mana Burst: VFX during Twisted Vision 4, happens 1.2s before B518, useful for sync branch +# B518 Mana Burst +# B519 Heavy Slam +# B51A Power Gusher +# B51B Power Gusher +# B51C Temporal Curtain +# B51D --sync-- +# B51E --sync-- +# B51F --sync--: Attack +# B520 Double Sobat: Castbar +# B521 Double Sobat: 0 degree left turn then B525 +# B522 Double Sobat: 90 degree left turn then B525 +# B523 Double Sobat: 180 degree left turn then B525 +# B524 Double Sobat: 270 degree left turn (turns to the right) +# B525 Double Sobat: Followup cleave +# B526 Esoteric Finisher +# B527 Snaking Kick +# B528 Arcadia Aflame +# B529 Arcadian Arcanum +# B52B Netherworld Near +# B52C Netherworld Far +# B52D Wailing Wave +# B52E Netherwrath Near +# B52F Netherwrath Far +# B530 Timeless Spite +# B533 Arcadian Hell +# B534 Arcadian Hell +# B535 Arcadian Hell +# B537 Arcadian Hell +# B8E1 Scalding Waves +# B922 Hemorrhagic Projection +# B9D9 Arcadian Arcanum +# BBE2 Twisted Vision +# BBE3 Mana Burst +# BCAF Snaking Kick +# BCB0 --sync--: Blackhole spawn +# BE5D Heavy Slam +# BE95 Snaking Kick +# BEC1 Arcadian Hell