From 03272501b8b7f53909041ecc378d39214108180d Mon Sep 17 00:00:00 2001 From: ksh Date: Sat, 29 Nov 2025 11:10:57 +0900 Subject: [PATCH 1/2] feat(arkveld): add initial trigger set and timeline for The Windward Wilds encounter - Created arkveld.ts to define triggers and localization for the Guardian Arkveld fight. - Added arkveld.txt containing detailed timeline and mechanics for the encounter. - Included translations for ability names and mechanics in Chinese. --- .../pilgrims_traverse_stones_71-80.ts | 5 + .../pilgrims_traverse_stones_81-90.ts | 13 +- .../pilgrims_traverse_stones_91-100.ts | 4 + .../pilgrims_traverse_the_final_verse.ts | 11 + .../eureka/occult_crescent_south_horn.ts | 5044 ++++++++++++++++- .../eureka/occult_crescent_south_horn.txt | 2588 ++++++++- ui/raidboss/data/07-dt/trial/arkveld-ex.ts | 50 +- ui/raidboss/data/07-dt/trial/arkveld-ex.txt | 461 +- ui/raidboss/data/07-dt/trial/arkveld.ts | 42 + ui/raidboss/data/07-dt/trial/arkveld.txt | 321 ++ 10 files changed, 8401 insertions(+), 138 deletions(-) create mode 100644 ui/raidboss/data/07-dt/trial/arkveld.ts create mode 100644 ui/raidboss/data/07-dt/trial/arkveld.txt diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_71-80.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_71-80.ts index aa64533b..d496da59 100644 --- a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_71-80.ts +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_71-80.ts @@ -63,6 +63,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get Behind x4', + cn: '去背后4次', ko: '엉댕이로x4', }, }, @@ -129,6 +130,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Avoid AoE', + cn: '避开AoE', ko: '장판 피해요!', }, }, @@ -176,6 +178,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${in} + ${backFront}', + cn: '${in} + ${backFront}', ko: '${backFront}+${in}', }, back: Outputs.back, @@ -202,6 +205,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${out} + ${backFront}', + cn: '${out} + ${backFront}', ko: '${backFront}+${out}', }, back: Outputs.back, @@ -218,6 +222,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Avoid final AoE', + cn: '躲避终点AoE', ko: '마지막 장판 피해요!', }, }, diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_81-90.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_81-90.ts index e75d19fa..749d89f9 100644 --- a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_81-90.ts +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_81-90.ts @@ -43,14 +43,17 @@ const triggerSet: TriggerSet = { outputStrings: { maul: { en: 'Maul', + cn: '咬杀', ko: '마울', }, maulOnYou: { en: 'Maul on YOU', + cn: '咬杀点名', ko: '내게 마울', }, maulOnPlayer: { en: 'Maul on ${player}', + cn: '咬杀点 ${player}', ko: '마울: ${player}', }, }, @@ -101,14 +104,17 @@ const triggerSet: TriggerSet = { outputStrings: { heat: { en: 'Pyretic, Avoid AoE', + cn: '热病, 避开AoE', ko: '파이레틱, 장판 피해욧', }, heatOnYou: { en: 'Pyretic on YOU, Away from Group => Stop Everything!', + cn: '热病点名, 远离小队成员 => 停止一切行动!', ko: '내게 파이레틱, 혼자 멀리 🔜 그대로 멈춰욧!', }, heatOnPlayer: { en: 'Pyretic on ${player}, Avoid AoE', + cn: '热病点 ${player}, 避开AoE', ko: '파이레틱: ${player}, 장판 피해욧', }, }, @@ -144,6 +150,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Avoid AoE', + cn: '避开AoE', ko: '장판 피해욧', }, }, @@ -171,6 +178,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${count}x attacks => Get Behind', + cn: '${count}次攻击 => 靠近', ko: '공격x${count} 🔜 엉댕이로', }, }, @@ -198,7 +206,8 @@ const triggerSet: TriggerSet = { en: 'Break line-of-sight to ${name}', de: 'Unterbreche Sichtlinie zu ${name}', fr: 'Masquez le champ de vision vers ${name}', - ja: '${name}の視線を遮れ', + ja: '${name}の視線から隠れる', + cn: '利用掩体卡 ${name} 的视线', ko: '시선 잘라요: ${name}', }, }, @@ -225,6 +234,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get Behind + Left', + cn: '去背后 + 左侧', ko: '뒤+왼쪽으로', }, }, @@ -237,6 +247,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get Behind + Right', + cn: '去背后 + 右侧', ko: '뒤+오른쪽으로', }, }, diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_91-100.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_91-100.ts index 9053c567..a57f88d2 100644 --- a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_91-100.ts +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_91-100.ts @@ -60,6 +60,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Go Front + Right', + cn: '去前面 + 右侧', ko: '앞+오른쪽으로', }, }, @@ -72,6 +73,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Go Front + Left', + cn: '去前面 + 左侧', ko: '앞+왼쪽으로', }, }, @@ -104,6 +106,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${count}x attacks => Out of Melee', + cn: '${count}次攻击 => 离开近战范围', ko: '공격x${count} 🔜 근접 피해요', }, }, @@ -136,6 +139,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Avoid AoE', + cn: '避开AoE', ko: '장판 피해욧', }, }, diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_the_final_verse.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_the_final_verse.ts index a705f1e6..ff4440ca 100644 --- a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_the_final_verse.ts +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_the_final_verse.ts @@ -165,6 +165,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Check Boss HP Difference', + cn: '检查 BOSS 血量差', ko: '보스 체력차 확인해요', }, }, @@ -183,6 +184,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${effect} on ${target}', + cn: '${effect} 点 ${target}', ko: '${target}에게 ${effect}', }, }, @@ -212,6 +214,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Refresh Vengeance', + cn: '刷新光/暗 Debuff', ko: '벤젠스 갱신해둬요', }, }, @@ -243,6 +246,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${mech1} => ${mech2}', + cn: '${mech1} => ${mech2}', ko: '${mech1} 🔜 ${mech2}', }, sides: Outputs.sides, @@ -271,6 +275,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${mech1} => ${mech2}', + cn: '${mech1} => ${mech2}', ko: '${mech1} 🔜 ${mech2}', }, sides: Outputs.sides, @@ -306,6 +311,8 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${mech1} => ${mech2}', + cn: '${mech1} => ${mech2}', + ko: '${mech1} 🔜 ${mech2}', }, sides: Outputs.sides, middle: Outputs.goIntoMiddle, @@ -440,6 +447,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${frontBack}-${leftRight}, for later', + cn: '稍后去 ${leftRight}-${frontBack}', ko: '(나중에 ${frontBack}+${leftRight})', }, front: Outputs.front, @@ -490,6 +498,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${frontBack}-${leftRight}, for later', + cn: '稍后去 ${leftRight}-${frontBack}', ko: '(나중에 ${frontBack}+${leftRight})', }, front: Outputs.front, @@ -519,6 +528,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${frontBack}-${leftRight}, Avoid Exaflares', + cn: '在 ${leftRight}-${frontBack} 躲避地火', ko: '${frontBack}+${leftRight}, 엑사플레어 피해요', }, front: Outputs.front, @@ -568,6 +578,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Go ${dir}', + cn: '去 ${dir}', ko: '가세요: ${dir}', }, unknown: Outputs.unknown, diff --git a/ui/raidboss/data/07-dt/eureka/occult_crescent_south_horn.ts b/ui/raidboss/data/07-dt/eureka/occult_crescent_south_horn.ts index cdfea719..4072797a 100644 --- a/ui/raidboss/data/07-dt/eureka/occult_crescent_south_horn.ts +++ b/ui/raidboss/data/07-dt/eureka/occult_crescent_south_horn.ts @@ -1,10 +1,61 @@ +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { callOverlayHandler } from '../../../../../resources/overlay_plugin_api'; import { Responses } from '../../../../../resources/responses'; +import { Directions } from '../../../../../resources/util'; import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; +import { PluginCombatantState } from '../../../../../types/event'; +import { NetMatches } from '../../../../../types/net_matches'; import { TriggerSet } from '../../../../../types/trigger'; export interface Data extends RaidbossData { ce?: string; + demonTabletChiselTargets: string[]; + demonTabletRotationCounter: number; + demonTabletIsFrontSide: boolean; + demonTabletCometeor?: 'near' | 'afar'; + demonTabletCometSouthTargets: string[]; + demonTabletCometNorthTargets: string[]; + demonTabletHasMeteor: boolean; + demonTabletMeteor?: 'north' | 'south'; + demonTabletIsFrontRight?: boolean; + demonTabletGravityTowers?: 'north' | 'south'; + deadStarsIsSlice2: boolean; + deadStarsSliceTargets: string[]; + deadStarsFirestrikeTargets: string[]; + deadStarsIsVengeful: boolean; + deadStarsVengeful1: number[]; + deadStarsVengeful2: number[]; + deadStarsOozeCount: number; + deadStarsOoze?: NetMatches['GainsEffect']; + deadStarsWasHitByOoze: boolean; + deadStarsWasVennDiagramed: boolean; + deadStarsLiquifiedNereid: number[]; + deadStarsLiquifiedTriton: number[]; + deadStarsSnowballTetherDirNum?: number; + deadStarsSnowballTetherCount: number; + prongedPassageActLoc: { [id: string]: string }; + prongedPassageIdolCastCount: { [id: string]: number }; + marbleDragonImitationRainCount: number; + marbleDragonImitationBlizzardCount: number; + marbleDragonImitationRainDir?: 'east' | 'west' | 'north'; + marbleDragonTwisterClock?: 'clockwise' | 'counterclockwise'; + marbleDragonImitationRainCrosses: string[]; + marbleDragonTankbusterFilter: boolean; + marbleDragonDelugeTargets: string[]; + marbleDragonDiveDirNum?: number; + marbleDragonIsFrigidDive: boolean; + marbleDragonHasWickedWater: boolean; + magitaurCriticalBlowCount: number; + magitaurRuneAxeDebuff?: 'big1' | 'big2' | 'small1' | 'small2'; + magitaurRuneTargets: string[]; + magitaurRuinousRuneCount: number; + magitaurRune2Targets: string[]; + magitaurBigRune2Target?: string; + magitaurLancelightCount: number; + bossDir?: number; + playerDir?: number; } // List of events: @@ -32,6 +83,286 @@ const ceIds: { [ce: string]: string } = { theUnbridled: '348', trialByClaw: '349', withExtremePredjudice: '339', + demonTablet: '33B', + centralGallery: '33F', + deadStars: '33C', + upperExterior: '340', + marbleDragon: '33D', + bindingLock: '341', + infamyOfBloodMagitaur: '33E', +}; + +const headMarkerData = { + // Demon Tablet Occult Chisel tankbuster aoe marker + 'demonTabletTankbuster': '01F1', + // Demon Tablet Portentous Comet Stack + Launch North marker + 'demonTabletLaunchNorthStack': '023E', + // Demon Tablet Portentous Comet Stack + Launch South marker + 'demonTabletLaunchSouthStack': '023F', + // Dead Stars boss tethers to each other + 'deadStarsTether': '0136', + // Dead Stars boss tethers + 'deadStarsBossTether': '00F9', + // Dead Stars Slice 'n' Dice tankbuster cleave + 'deadStarsTankbuster': '01D7', + // Dead Stars Avalaunch Proximity Stack + 'deadStarsAvalaunchStack': '0064', + // Dead Stars snowball spike tether + 'deadStarsSnowballTether': '00F6', + // Dead Stars snowball tether + 'deadStarsSnowballTether2': '0001', + // Dead Stars Avalaunch Stack + // Tower Progenitor and Tower Progenitrix Punishing Pounce Stack + // Magitaur Holy IV Stack + 'prongedPassageStack': '0064', + // Marble Dragon tankbuster from Dread Deluge + // Neo Garula tankbuster from Squash in Noise Complaint CE + // Hinkypunk tankbuster from Dread Dive in Flame of Dusk CE + // Death Claw tankbuster from Dirty Nails in Crawling Death CE + // Repaired Lion tankbuster from Scratch in Eternal Watch CE + // Mysterious Mindflayer tankbuster from Void Thunder III in Scourge of the Mind CE + // Crescent Inkstain tankbuster from Amorphic Flail + // Crescent Karlabos tankbuster from Wild Claw + // Crescent Fan tankbuster from Tight Tornado + 'marbleDragonTankbuster': '00DA', + // Marble Dragon red pinwheel markers from Wicked Water + 'marbleDragonWickedWater': '0017', + // Magitaur big red pinwheel marker from Ruinous Rune (A251) + 'magitaurBigRuinousRune': '023D', + // Magiatur small red pinwheel markers from Ruinous Rune (A250) + 'magitaurSmallRuinousRune': '0159', +} as const; + +// Occult Crescent Forked Tower: Blood Demon Tablet consts +// const demonTabletCenterX = 700; +const demonTabletCenterY = 379; + +// Function to find safe spot for summoned statues +const demonTabletFindGravityCorner = ( + x: number, + y: number, +): boolean | undefined => { + if (x > 687 && x < 689) { + if ((y > 351 && y < 353) || (y > 394.5 && y < 396.5)) + return true; + if ((y > 361.5 && y < 363.5) || (y > 387 && y < 389)) + return false; + } else if (x > 711 && x < 713) { + if ((y > 361.5 && y < 363.5) || (y > 405 && y < 407)) + return true; + if ((y > 369 && y < 371) || (y > 394.5 && y < 396.5)) + return false; + } + return undefined; +}; + +// Occult Crescent Forked Tower: Blood Dead Stars consts +const deadStarsCenterX = -800; +const deadStarsCenterY = 360; +const deadStarsRedEffectId = '1159'; +const deadStarsBlueEffectId = '115A'; +const deadStarsRedHitId = 'A5E3'; +const deadStarsBlueHitId = 'A5E4'; +const deadStarsOutputStrings = { + lineStacksOnPlayers: { + en: 'Line Stacks on ${player1}, ${player2}, ${player3}', + cn: '直线分摊点 ${player1}, ${player2}, ${player3}', + ko: '한줄 뭉쳐요: ${player1}, ${player2}, ${player3}', + }, + lineStackOnYouTankCleave: { + en: 'Line Stack on YOU, Avoid Tank Cleave', + cn: '直线分摊点名,躲避坦克顺劈', + ko: '내게 한줄 뭉치고 탱크 쪼개기 피해요', + }, + lineStackOnYou: { + en: 'Line Stack on YOU', + de: 'Linien Stack auf DIR', + fr: 'Package en ligne sur VOUS', + ja: '直線頭割り', + cn: '直线分摊点名', + ko: '내게 한줄 뭉치기가!', + }, +}; + +// Function to find a safe spot in Primordial Chaos +// Expected inputs are the dirNums of two oozes +const deadStarsFindSafeSpot = ( + ooze1: number, + ooze2: number, +): number => { + // Filter from map of valid ooze locations where oozes are + const safeDirNums = [1, 3, 5, 7].filter( + (dirNum) => { + return dirNum !== ooze1 && dirNum !== ooze2; + }, + ); + const safe1 = safeDirNums[0]; + const safe2 = safeDirNums[1]; + if ((safe1 === 7 && safe2 === 1) || (safe1 === 1 && safe2 === 7)) + return 0; // North + if ((safe1 === 1 && safe2 === 3) || (safe1 === 3 && safe2 === 1)) + return 2; // East + if ((safe1 === 3 && safe2 === 5) || (safe1 === 5 && safe2 === 3)) + return 4; // South + if ((safe1 === 5 && safe2 === 7) || (safe1 === 7 && safe2 === 5)) + return 6; // West + if ((safe1 === 3 && safe2 === 7) || (safe1 === 7 && safe2 === 3)) + return 3; // Also northwest + if ((safe1 === 1 && safe2 === 5) || (safe1 === 5 && safe2 === 1)) + return 1; // Also southwest + return -1; +}; +// Used with deadStarsFindSafeSpot to map to longform direction +const deadStarsMapOutput = [ + 'north', + 'northeast', + 'east', + 'southeast', + 'south', + 'southwest', + 'west', + 'northwest', + 'unknown', +]; + +// Occult Crescent Forked Tower: Pronged Passage consts +const prongedPassageCenterY = 315; + +// Occult Crescent Forked Tower: Marble Dragon consts +const marbleDragonCenterX = -337; +const marbleDragonCenterY = 157; + +// Function to find and validate a puddle location during Imitation Rain 2 +const getPuddleLocation = ( + x: number, + y: number, +): 'NE' | 'SE' | 'SW' | 'NW' | 'center' | undefined => { + if (x > -338 && x < -336) + return 'center'; + // East side puddles + if (x > -322 && x < -319) { + if (y > 140 && y < 142) + return 'NE'; + if (y > 172 && y < 174) + return 'SE'; + } + // West side puddles + if (x > -354 && x < -352) { + if (y > 140 && y < 142) + return 'NW'; + if (y > 172 && y < 174) + return 'SW'; + } + return undefined; +}; + +// Occult Crescent Forked Tower: Magitaur consts +const magitaurOutputStrings = { + rune1BigAoeOnYou: { + en: 'Big AOE on YOU, Go to Wall by Purple Circle', + cn: '大圈点名, 去紫圈墙边', + ko: '내게 큰 장판, 보라색 원 벽 쪽으로 가기', + }, + rune1SmallAoeOnYou: { + en: 'Small aoe on YOU, Stay Square => Between Squares', + cn: '小圈点名, 留在方块内 => 方块间', + ko: '내게 작은 장판, 네모 안에 있기 => 네모 사이', + }, + rune1BigAoeOnPlayer: { + en: 'Big AOE on ${player}, Be on Square', + cn: '大圈点 ${player}, 去方块内', + ko: '${player}에게 큰 장판, 네모 안에 있기', + }, + rune1SmallAoesOnPlayers: { + en: 'Small aoes on ${player1}, ${player2}, ${player3}', + cn: '小圈点 ${player1}, ${player2}, ${player3}', + ko: '작은 장판 ${player1}, ${player2}, ${player3}', + }, + rune1SmallAoEStayThenIn: { + en: 'Stay for AOE => In, Between Squares', + cn: '留在方块外 => 内, 方块间', + ko: '장판 대기 => 네모 사이', + }, + rune2BigAoeOnYouLater: { + en: 'Big AOE on YOU (Later)', + cn: '大圈点名 (稍后)', + ko: '내게 큰 장판 (나중에)', + }, + rune2SmallAoeOnYouLater: { + en: 'Small aoe on YOU (Later)', + cn: '小圈点名 (稍后)', + ko: '내게 작은 장판 (나중에)', + }, + rune2InBigAoeOnYou: { + en: 'In, Between Squares => To Wall', + cn: '内, 方块间 => 去墙边', + ko: '내, 네모 사이 => 벽 쪽으로', + }, + rune2InSmallAoeOnYou: { + en: 'In, Between Squares => Solo Square', + cn: '内, 方块间 => 单人方块', + ko: '내, 네모 사이 => 혼자 네모', + }, + rune2AoesOnPlayers: { + en: 'AOEs on ${player1}, ${player2}, ${player3}', + cn: '圈点 ${player1}, ${player2}, ${player3}', + ko: '장판 ${player1}, ${player2}, ${player3}', + }, + rune2AvoidPlayers: { + en: 'On Square, Avoid ${player1} & ${player2}', + cn: '方块内, 远离 ${player1} 和 ${player2}', + ko: '네모 안, ${player1} 와 ${player2} 피하기', + }, + rune2SmallAoeOnYouReminder: { + en: 'Small aoe on YOU, Be on Square (Solo)', + cn: '小圈点名, 去方块内 (单人)', + ko: '내게 작은 장판, 네모 안에 있기 (혼자)', + }, + rune2BigAoeOnYouReminder: { + en: 'Big AOE on YOU, Go to Wall by Purple Circle', + cn: '大圈点名, 去紫圈墙边', + ko: '내게 큰 장판, 보라색 원 벽 쪽으로 가기', + }, + inThenOnSquare: { + en: 'In, between Squares => On Square', + cn: '内, 方块间 => 方块内', + ko: '내, 네모 사이 => 네모 안', + }, + northeastOff: { + en: 'Northeast Off', + cn: '右上外', + ko: '오른쪽 위 밖', + }, + northeastOn: { + en: 'Northeast On', + cn: '右上内', + ko: '오른쪽 위 안', + }, + southOff: { + en: 'South Off', + cn: '下方外', + ko: '아래쪽 밖', + }, + southOn: { + en: 'South On', + cn: '下方内', + ko: '아래쪽 안', + }, + northwestOff: { + en: 'Northwest Off', + cn: '左上外', + ko: '왼쪽 위 밖', + }, + out: { + en: 'Out, Square Corner', + cn: '外, 方块角落', + ko: '밖, 네모 모서리', + }, + in: { + en: 'In, between Squares', + cn: '内, 方块间', + ko: '내, 네모 사이', + }, }; const triggerSet: TriggerSet = { @@ -41,149 +372,4378 @@ const triggerSet: TriggerSet = { en: 'Occult Crescent South Horn critical encounter triggers/timeline.', de: 'Kreszentia Südexpedition kritische Begegnungen Triggers/Timeline.', cn: '蜃景幻界新月岛 南征之章 紧急遭遇战 触发器/时间轴。', + ko: '초승달 섬: 남부편 비상 조우 트리거/타임라인', }, + config: [ + { + id: 'demonTabletRotation', + name: { + en: 'Forked Tower: Blood Demon Tablet Rotation Strategy', + cn: '两歧塔力之塔 恶魔板 旋转策略', + ko: '포크 타워: 피의 악마 판 회전 전략', + }, + type: 'select', + options: { + en: { + 'Less movement by calling direction to go around instead of get behind.': 'optimization', + 'Early movement with get behind calls.': 'none', + }, + cn: { + '提示绕行方向(非绕后),减少移动量': 'optimization', + '提前提示绕后,方便提早移动': 'none', + }, + ko: { + '돌아가는 방향을 호출하여 이동량 감소 (뒤로 돌아가지 않음)': 'optimization', + '뒤로 돌아가는 호출을 조기에 하여 미리 이동 가능': 'none', + }, + }, + default: 'none', + }, + { + id: 'marbleDragonImitationRainStrategy', + name: { + en: 'Forked Tower: Blood Marble Dragon Imitation Rain 1 and 5 Strategy', + cn: '两歧塔力之塔 大理石龙 仿效雨 1和5 策略', + ko: '포크 타워: 피의 대리석 용 모방 비 1과 5 전략', + }, + type: 'select', + options: { + en: { + 'Cross-based: Calls based on southern cross puddle.': 'cross', + 'Ice-based: Calls based on Ice Puddle nearest to wall.': 'ice', + }, + cn: { + '十字基准: 根据十字冰圈位置提示': 'cross', + '冰基准: 根据离墙最近的冰圈提示': 'ice', + }, + ko: { + '십자 기준: 남쪽 십자 얼음 장판 위치에 따른 호출': 'cross', + '얼음 기준: 벽과 가장 가까운 얼음 장판 위치에 따른 호출': 'ice', + }, + }, + default: 'cross', + }, + { + id: 'magitaurDaggers', + name: { + en: 'Forked Tower: Blood Magitaur Dagger Strategy', + cn: '两歧塔力之塔 魔陶洛斯 暗杀短剑 策略', + ko: '포크 타워: 피의 마기타우르 단검 전략', + }, + type: 'select', + options: { + en: { + 'BAP Daggers (Number and Letter Floor Markers)': 'bap', + 'No strategy (Y-Pattern and ⅄-Pattern)': 'none', + }, + cn: { + 'BAP短剑标记(数字和字母场景标记)': 'bap', + '无特定策略(Y型与⅄型)': 'none', + }, + ko: { + 'BAP 단검 (숫자 및 문자 바닥 표시)': 'bap', + '전략 없음 (Y-패턴 및 ⅄-패턴)': 'none', + }, + }, + default: 'none', + }, + ], timelineFile: 'occult_crescent_south_horn.txt', + initData: () => ({ + demonTabletChiselTargets: [], + demonTabletRotationCounter: 0, + demonTabletIsFrontSide: true, + demonTabletCometSouthTargets: [], + demonTabletCometNorthTargets: [], + demonTabletHasMeteor: false, + deadStarsIsSlice2: false, + deadStarsSliceTargets: [], + deadStarsFirestrikeTargets: [], + deadStarsIsVengeful: false, + deadStarsVengeful1: [], + deadStarsVengeful2: [], + deadStarsOozeCount: 0, + deadStarsWasHitByOoze: false, + deadStarsWasVennDiagramed: false, + deadStarsLiquifiedNereid: [], + deadStarsLiquifiedTriton: [], + deadStarsSnowballTetherCount: 0, + prongedPassageActLoc: {}, + prongedPassageIdolCastCount: { + 'north': 0, + 'south': 0, + }, + marbleDragonImitationRainCount: 0, + marbleDragonImitationBlizzardCount: 0, + marbleDragonImitationRainCrosses: [], + marbleDragonTankbusterFilter: false, + marbleDragonDelugeTargets: [], + marbleDragonIsFrigidDive: false, + marbleDragonHasWickedWater: false, + magitaurCriticalBlowCount: 0, + magitaurRuneTargets: [], + magitaurRuinousRuneCount: 0, + magitaurRune2Targets: [], + magitaurLancelightCount: 0, + }), resetWhenOutOfCombat: false, + timelineTriggers: [ + { + id: 'Occult Crescent Marble Dragon Draconiform Motion Bait', + // Usually we would use a 7s beforeSeconds value, however 6.3s avoids needing to create a second trigger to delay bait calls for an ice-based strategy + // and maintains consistency between the Draconiform Motion baits throughout the fight and strategy selection + regex: /Draconiform Motion/, + beforeSeconds: 6.3, + alertText: (data, _matches, output) => { + if ( + data.marbleDragonImitationRainDir !== undefined && + data.marbleDragonImitationRainCount < 6 + ) + return output.baitCleaveThenDir!({ + dir: output[data.marbleDragonImitationRainDir]!(), + }); + if (data.marbleDragonImitationRainCount >= 6) { + if (data.marbleDragonTwisterClock === 'clockwise') + return output.baitCleaveThenDir!({ + dir: output.northSouth!(), + }); + if (data.marbleDragonTwisterClock === 'counterclockwise') + return output.baitCleaveThenDir!({ + dir: output.eastWest!(), + }); + } + return output.baitCleave!(); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + eastWest: { + en: 'East/West', + cn: '东/西', + ko: '동/서', + }, + northSouth: { + en: 'North/South', + cn: '南/北', + ko: '남/북', + }, + baitCleave: { + en: 'Bait Cleave', + cn: '诱导顺劈', + ko: '쪼개기 유도', + }, + baitCleaveThenDir: { + en: 'Bait Cleave => ${dir}', + cn: '诱导顺劈 => ${dir}', + ko: '쪼개기 유도 🔜 ${dir}', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Rune Axe Square Position', + // Debuffs are based on proximity to squares + regex: /Rune Axe/, + beforeSeconds: 7, + alertText: (_data, _matches, output) => output.squarePosition!(), + outputStrings: { + squarePosition: { + en: 'Rune Axe Square Position', + cn: '符文之斧方块站位', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Holy Lance Square Position', + // Debuffs are based on proximity to squares + regex: /Holy Lance/, + beforeSeconds: 7, + alertText: (_data, _matches, output) => output.squarePosition!(), + outputStrings: { + squarePosition: { + en: 'Holy Lance Square Position', + cn: '圣枪方块站位', + ko: '성창 네모 모서리 위치', + }, + }, + }, + ], triggers: [ { - id: 'Occult Crescent Critical Encounter', - type: 'ActorControl', - netRegex: { command: '80000014' }, - run: (data, matches) => { - // This fires when you win, lose, or teleport out. - if (matches.data0 === '00') { - if (data.ce !== undefined && data.options.Debug) - console.log(`Stop CE: ${data.ce}`); - // Stop any active timelines. - data.StopCombat(); - // Prevent further triggers for any active CEs from firing. - delete data.ce; + id: 'Occult Crescent Critical Encounter', + type: 'ActorControl', + netRegex: { command: '80000014' }, + run: (data, matches) => { + // This fires when you win, lose, or teleport out. + if (matches.data0 === '00') { + if (data.ce !== undefined && data.options.Debug) + console.log(`Stop CE: ${data.ce}`); + // Stop any active timelines. + data.StopCombat(); + // Prevent further triggers for any active CEs from firing. + delete data.ce; + return; + } + + delete data.ce; + const ceId = matches.data0.toUpperCase(); + for (const key in ceIds) { + if (ceIds[key] === ceId) { + if (data.options.Debug) + console.log(`Start CE: ${key} (${ceId})`); + data.ce = key; + return; + } + } + + if (data.options.Debug) + console.log(`Start CE: ??? (${ceId})`); + }, + }, + { + id: 'Occult Crescent Forked Tower: Blood Clear Data', + type: 'SystemLogMessage', + // "is no longer sealed" + netRegex: { id: '7DE', capture: false }, + run: (data) => { + delete data.demonTabletIsFrontRight; + delete data.demonTabletCometeor; + delete data.demonTabletMeteor; + delete data.demonTabletGravityTowers; + delete data.deadStarsOoze; + delete data.deadStarsSnowballTetherDirNum; + delete data.marbleDragonImitationRainDir; + delete data.marbleDragonTwisterClock; + delete data.marbleDragonDiveDirNum; + delete data.magitaurRuneAxeDebuff; + delete data.magitaurBigRune2Target; + delete data.bossDir; + delete data.playerDir; + data.demonTabletChiselTargets = []; + data.demonTabletRotationCounter = 0; + data.demonTabletIsFrontSide = true; + data.demonTabletCometSouthTargets = []; + data.demonTabletCometNorthTargets = []; + data.demonTabletHasMeteor = false; + data.deadStarsIsSlice2 = false; + data.deadStarsSliceTargets = []; + data.deadStarsFirestrikeTargets = []; + data.deadStarsIsVengeful = false; + data.deadStarsVengeful1 = []; + data.deadStarsVengeful2 = []; + data.deadStarsOozeCount = 0; + data.deadStarsWasHitByOoze = false; + data.deadStarsWasVennDiagramed = false; + data.deadStarsLiquifiedNereid = []; + data.deadStarsLiquifiedTriton = []; + data.deadStarsSnowballTetherCount = 0; + data.prongedPassageActLoc = {}; + data.prongedPassageIdolCastCount = { + 'north': 0, + 'south': 0, + }; + data.marbleDragonImitationRainCount = 0; + data.marbleDragonImitationBlizzardCount = 0; + data.marbleDragonImitationRainCrosses = []; + data.marbleDragonTankbusterFilter = false; + data.marbleDragonDelugeTargets = []; + data.marbleDragonIsFrigidDive = false; + data.marbleDragonHasWickedWater = false; + data.magitaurCriticalBlowCount = 0; + data.magitaurRuneTargets = []; + data.magitaurRuinousRuneCount = 0; + data.magitaurRune2Targets = []; + data.magitaurLancelightCount = 0; + }, + }, + { + id: 'Occult Crescent Cloister Demon Tidal Breath', + type: 'StartsUsing', + netRegex: { source: 'Cloister Demon', id: 'A190', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'Occult Crescent Berserker Scathing Sweep', + type: 'StartsUsing', + netRegex: { source: 'Crescent Berserker', id: 'A6C3', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'Occult Crescent Hinkypunk Dread Dive', + type: 'StartsUsing', + netRegex: { source: 'Hinkypunk', id: 'A1A4', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Occult Crescent Hinkypunk Shades Nest', + type: 'StartsUsing', + // TODO: Some of these are from boss, some are not. + netRegex: { source: 'Hinkypunk', id: ['A19C', 'A19D', 'A430', 'A431'], capture: true }, + suppressSeconds: 1, + response: Responses.getIn(), + run: (_data, matches) => console.log(`Shades Nest: ${matches.id}`), + }, + { + id: 'Occult Crescent Hinkypunk Shades Crossing', + type: 'StartsUsing', + // TODO: Some of these are from boss, some are not. + netRegex: { source: 'Hinkypunk', id: ['A19F', 'A1A0', 'A432', 'A433'], capture: true }, + suppressSeconds: 1, + response: Responses.getIntercards(), + run: (_data, matches) => console.log(`Shades Nest: ${matches.id}`), + }, + { + id: 'Occult Crescent Hinkypunk Lamplight', + type: 'StartsUsing', + netRegex: { source: 'Hinkypunk', id: ['A1A5', 'A310'], capture: false }, + suppressSeconds: 1, + response: Responses.aoe(), + }, + { + id: 'Occult Crescent Black Star Choco Windstorm', + type: 'StartsUsing', + netRegex: { source: 'Black Star', id: 'A0BB', capture: false }, + response: Responses.getOut(), + }, + { + id: 'Occult Crescent Black Star Choco Cyclone', + type: 'StartsUsing', + netRegex: { source: 'Black Star', id: 'A0BC', capture: false }, + response: Responses.getIn(), + }, + { + id: 'Occult Crescent Neo Garula Squash', + type: 'StartsUsing', + netRegex: { source: 'Neo Garula', id: 'A0E5', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Occult Crescent Lion Rampant Fearsome Glint', + type: 'StartsUsing', + netRegex: { source: 'Lion Rampant', id: 'A1C3', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'Occult Crescent Death Claw Dirty Nails', + type: 'StartsUsing', + netRegex: { source: 'Death Claw', id: 'A174', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Occult Crescent Death Claw Grip of Poison', + type: 'StartsUsing', + netRegex: { source: 'Death Claw', id: 'A175', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'Occult Crescent Death Claw Vertical Crosshatch', + type: 'StartsUsing', + netRegex: { source: 'Death Claw', id: ['A16B', 'A172'], capture: false }, + response: Responses.getSidesThenFrontBack('alert'), + }, + { + id: 'Occult Crescent Death Claw Horizontal Crosshatch', + type: 'StartsUsing', + netRegex: { source: 'Death Claw', id: ['A16C', 'A173'], capture: false }, + response: Responses.getFrontBackThenSides('alert'), + }, + { + id: 'Occult Crescent Repaired Lion Holy Blaze', + type: 'StartsUsing', + netRegex: { source: 'Repaired Lion', id: 'A151', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'Occult Crescent Repaired Lion Scratch', + type: 'StartsUsing', + netRegex: { source: 'Repaired Lion', id: 'A155', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Occult Crescent Nymian Petalodus Hydrocleave', + type: 'StartsUsing', + netRegex: { source: 'Nymian Petalodus', id: 'A88D', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'Occult Crescent Demon Tablet Demonic Dark II', + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'A306', capture: false }, + response: Responses.bigAoe(), + }, + { + id: 'Occult Crescent Demon Tablet Ray of Dangers Near/Expulsion Afar', + // A2F3 Ray of Dangers Near + // A2F4 Ray of Expulsion Afar + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A2F3', 'A2F4'], capture: true }, + alertText: (_data, matches, output) => { + if (matches.id === 'A2F3') + return output.out!(); + return output.inKnockback!(); + }, + outputStrings: { + out: Outputs.out, + inKnockback: { + en: 'In => Knockback', + cn: '内 => 击退', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Ray of Expulsion Afar Knockback', + // 10s castTime + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'A2F4', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 6, + response: Responses.knockback(), + }, + { + id: 'Occult Crescent Demon Tablet Occult Chisel', + // Boss' top three targets targeted with A308 Occult Chisel aoe tankbuster + // A307 Occult Chisel castbar associated + type: 'HeadMarker', + netRegex: { id: [headMarkerData.demonTabletTankbuster], capture: true }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankbustersOnPlayers: { + en: 'Tankbusters on ${player1}, ${player2}, ${player3}', + cn: '坦克死刑点 ${player1}, ${player2}, ${player3}', + }, + tankBusterOnYou: Outputs.tankBusterOnYou, + }; + data.demonTabletChiselTargets.push(matches.target); + if (data.demonTabletChiselTargets.length < 3) + return; + + const target1 = data.demonTabletChiselTargets[0]; + const target2 = data.demonTabletChiselTargets[1]; + const target3 = data.demonTabletChiselTargets[2]; + if (data.me === target1 || data.me === target2 || data.me === target3) + return { alertText: output.tankBusterOnYou!() }; + + return { + infoText: output.tankbustersOnPlayers!({ + player1: data.party.member(target1), + player2: data.party.member(target2), + player3: data.party.member(target3), + }), + }; + }, + run: (data) => { + if (data.demonTabletChiselTargets.length === 3) + data.demonTabletChiselTargets = []; + }, + }, + { + id: 'Occult Crescent Demon Tablet Demonograph of Dangears Near/Expulsion Afar', + // A2F6 Demonograph of Dangers Near + // A2F7 Demonograph of Expulsion Afar + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A2F6', 'A2F7'], capture: true }, + alertText: (_data, matches, output) => { + if (matches.id === 'A2F6') + return output.out!(); + return output.inKnockback!(); + }, + outputStrings: { + out: Outputs.out, + inKnockback: { + en: 'In => Knockback', + cn: '内 => 击退', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Demonograph of Expulsion Afar Knockback', + // 10s castTime + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'A2F7', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 6, + response: Responses.knockback(), + }, + { + id: 'Occult Crescent Demon Tablet Rotate Left/Right', + // A302 Rotate Left + // A301 Rotate Right + // Configurable to use an optimization callout, skipping get behind calls + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A302', 'A301'], capture: true }, + promise: async (data, matches) => { + // Only check if in front/behind for first rotation + if (data.demonTabletRotationCounter % 2) + return; + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Demon Tablet Rotate Left/Right: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Demon Tablet Rotate Left/Right: Wrong actor count ${actors.length}`, + ); + return; + } + const bossDirNum = Directions.hdgTo4DirNum(actor.Heading); + const getSide = ( + y: number, + ): number => { + // First Rotation is always N or S + // N Platform + if (y < demonTabletCenterY) + return 0; + // S Platform + if (y > demonTabletCenterY) + return 2; + + return -1; + }; + const playerDirNum = getSide(me.PosY); + data.demonTabletIsFrontSide = (playerDirNum === bossDirNum) + ? true + : false; + }, + alertText: (data, matches, output) => { + // First Rotation + if (!(data.demonTabletRotationCounter % 2)) { + if ( + data.demonTabletIsFrontSide && + data.triggerSetConfig.demonTabletRotation !== 'optimization' + ) { + if (matches.id === 'A301') + return output.leftThenGetBehind!(); + return output.rightThenGetBehind!(); + } + if (matches.id === 'A301') + return output.left!(); + return output.right!(); + } + + // Second Rotation + if ( + data.demonTabletIsFrontSide && + data.triggerSetConfig.demonTabletRotation === 'optimization' + ) { + // Optimization callout since it is faster to go with boss direction + if (matches.id === 'A301') + return output.goRightAround!(); + return output.goLeftAround!(); + } + // Reminders to be behind + if (matches.id === 'A301') + return output.leftBehind!(); + return output.rightBehind!(); + }, + run: (data) => { + data.demonTabletRotationCounter = data.demonTabletRotationCounter + 1; + }, + outputStrings: { + left: Outputs.left, + right: Outputs.right, + leftBehind: { + en: 'Left (Behind Boss)', + cn: '左侧 (Boss后方)', + }, + rightBehind: { + en: 'Right (Behind Boss)', + cn: '右侧 (Boss后方)', + }, + leftThenGetBehind: { + en: 'Left => Get Behind', + cn: '左侧 => 去Boss后方', + }, + rightThenGetBehind: { + en: 'Right => Get Behind', + cn: '右侧 => 去Boss后方', + }, + goRightAround: { + en: 'Go Right and Around', + cn: '右侧绕行', + }, + goLeftAround: { + en: 'Go Left and Around', + cn: '左侧绕行', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Cometeor of Dangers Near/Expulsion Afar', + // A2E4 Cometeor of Dangers Near + // A2E5 Cometeor of Expulsion Afar + // This cast happens about 0.1s before players are marked with comets + // Around the time of the cast, there is a 261 log line for a combatant added in memory + // BNpcID 2014582 combatant is responsible for the meteor ground marker + // Two possible locations: + // (700, 349) => North + // (700, 409) => South + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A2E4', 'A2E5'], capture: true }, + preRun: (data, matches) => { + data.demonTabletCometeor = matches.id === 'A2E4' ? 'near' : 'afar'; + }, + delaySeconds: 0.5, // Delayed to retreive comet data and meteor data + promise: async (data) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + })).combatants; + const meteors = actors.filter((c) => c.BNpcID === 2014582); + const meteor = meteors[0]; + if (meteor === undefined || meteors.length !== 1) { + console.error( + `Occult Crescent Demon Tablet Cometeor of Dangers Near/Expulsion Afar: Wrong meteor count ${meteors.length}`, + ); + return; + } + if (meteor.PosY === 349) { + data.demonTabletMeteor = 'north'; + } else if (meteor.PosY === 409) + data.demonTabletMeteor = 'south'; + }, + alertText: (data, matches, output) => { + // Do not call for those with comets + const north1 = data.demonTabletCometNorthTargets[0]; + const north2 = data.demonTabletCometNorthTargets[1]; + const south1 = data.demonTabletCometSouthTargets[0]; + const south2 = data.demonTabletCometSouthTargets[1]; + if ( + data.me === north1 || data.me === north2 || + data.me === south1 || data.me === south2 + ) + return; + + const mech = matches.id === 'A2E4' ? 'out' : 'inKnockback'; + const getDir = ( + hasMeteor: boolean, + meteorDir?: 'north' | 'south', + ): string => { + if (meteorDir !== undefined) { + if (hasMeteor) + return meteorDir; + if (meteorDir === 'north') + return 'south'; + if (meteorDir === 'south') + return 'north'; + } + return 'unknown'; + }; + + // Flip direction if we don't have meteor + const dir = getDir(data.demonTabletHasMeteor, data.demonTabletMeteor); + + if (dir === 'unknown') { + if (data.demonTabletHasMeteor) + return output.hasMeteorMech!({ mech: output[mech]!() }); + return output[mech]!(); + } + + if (data.demonTabletHasMeteor) + return output.hasMeteorDirMech!({ dir: output[dir]!(), mech: output[mech]!() }); + return output.dirMech!({ dir: output[dir]!(), mech: output[mech]!() }); + }, + run: (data) => { + // Clear comet targets for Cometeor 2 + if ( + data.demonTabletCometNorthTargets.length === 2 && + data.demonTabletCometSouthTargets.length === 2 + ) { + data.demonTabletCometNorthTargets = []; + data.demonTabletCometSouthTargets = []; + } + }, + outputStrings: { + north: Outputs.north, + south: Outputs.south, + out: Outputs.out, + inKnockback: { + en: 'In => Knockback', + cn: '内 => 击退', + }, + dirMech: { + en: '${dir} & ${mech}', + cn: '${dir} 和 ${mech}', + }, + hasMeteorMech: { + en: 'Meteor on YOU, ${mech}', + cn: '陨石点名, ${mech}', + }, + hasMeteorDirMech: { + en: 'Meteor on YOU, Go ${dir} & ${mech}', + cn: '陨石点名, 去${dir} 并 ${mech}', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Cometeor of Dangers Near/Expulsion Afar Knockback', + // 10s castTime + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'A2E5', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 6, + response: Responses.knockback(), + }, + { + id: 'Occult Crescent Demon Tablet Crater Later Gains Effect', + // Players targeted by meteor get an unlogged headmarker and Crater Later (1102) 12s debuff + // These apply about 0.1s after Cometeor cast + type: 'GainsEffect', + netRegex: { effectId: '1102', capture: true }, + condition: Conditions.targetIsYou(), + run: (data) => { + data.demonTabletHasMeteor = true; + }, + }, + { + id: 'Occult Crescent Demon Tablet Crater Later Loses Effect', + // Clear state for second set + type: 'LosesEffect', + netRegex: { effectId: '1102', capture: true }, + condition: Conditions.targetIsYou(), + delaySeconds: 6, // Time until Portentous Comet (stack launcher) completed + run: (data) => { + data.demonTabletHasMeteor = false; + }, + }, + { + id: 'Occult Crescent Demon Tablet Portentous Comet', + // Headmarkers associated with casts A2E8 Portentous Comet + // TODO: Reminder call for stack markers to move away or towards boss? + // Note: Reset of target collectors happens in Cometeor trigger + type: 'HeadMarker', + netRegex: { + id: [ + headMarkerData.demonTabletLaunchSouthStack, + headMarkerData.demonTabletLaunchNorthStack, + ], + capture: true, + }, + condition: (data, matches) => { + // Gather data for four players before continuing + if (matches.id === headMarkerData.demonTabletLaunchSouthStack) + data.demonTabletCometSouthTargets.push(matches.target); + if (matches.id === headMarkerData.demonTabletLaunchNorthStack) + data.demonTabletCometNorthTargets.push(matches.target); + if ( + data.demonTabletCometNorthTargets.length === 2 && + data.demonTabletCometSouthTargets.length === 2 + ) + return true; + return false; + }, + delaySeconds: (data) => { + // Delay for those without stack markers to avoid conflict with meteor/cross calls + const north1 = data.demonTabletCometNorthTargets[0]; + const north2 = data.demonTabletCometNorthTargets[1]; + const south1 = data.demonTabletCometSouthTargets[0]; + const south2 = data.demonTabletCometSouthTargets[1]; + if ( + data.me === north1 || data.me === north2 || + data.me === south1 || data.me === south2 + ) + return 0; + + // castTime of Cometeor of Dangers Near / Expulsion Afar + // Boss lands at this time, locking in the stack players to their perspective sides + return 9.7; + }, + durationSeconds: (data) => { + // Additional duration for those who received call early + const north1 = data.demonTabletCometNorthTargets[0]; + const north2 = data.demonTabletCometNorthTargets[1]; + const south1 = data.demonTabletCometSouthTargets[0]; + const south2 = data.demonTabletCometSouthTargets[1]; + if ( + data.me === north1 || data.me === north2 || + data.me === south1 || data.me === south2 + ) + return 16.7; // castTime of Portentous Comet + return 7; // Time between Cometeor cast end and Portentous Comet end + }, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + stackLaunchTowardsBoss: { + en: 'Stack, Launch towards Boss', + cn: '集合, 向Boss方向击飞', + }, + stackLaunchOverBoss: { + en: 'Stack, Launch over Boss', + cn: '集合, 越过Boss击飞', + }, + goNorthOutStackOnYou: { + en: 'Go North Out => Stack Launch Marker on You', + cn: '去上方外侧 => 集合击飞点名', + }, + goNorthInStackOnYou: { + en: 'Go North In (Knockback) => Stack Launch Marker on You', + cn: '去上方内侧 (击退) => 集合击飞点名', + }, + goSouthOutStackOnYou: { + en: 'Go South Out => Stack Launch Marker on You', + cn: '去下方外侧 => 集合击飞点名', + }, + goSouthInStackOnYou: { + en: 'Go South In (Knockback) => Stack Launch Marker on You', + cn: '去下方内侧 (击退) => 集合击飞点名', + }, + }; + + const north1 = data.demonTabletCometNorthTargets[0]; + const north2 = data.demonTabletCometNorthTargets[1]; + const south1 = data.demonTabletCometSouthTargets[0]; + const south2 = data.demonTabletCometSouthTargets[1]; + if (data.me === north1 || data.me === north2) { + if (data.demonTabletCometeor === 'near') + return { alertText: output.goSouthOutStackOnYou!() }; + return { alertText: output.goSouthInStackOnYou!() }; + } + if (data.me === south1 || data.me === south2) { + if (data.demonTabletCometeor === 'near') + return { alertText: output.goNorthOutStackOnYou!() }; + return { alertText: output.goNorthInStackOnYou!() }; + } + + if (data.demonTabletHasMeteor) + return { alertText: output.stackLaunchOverBoss!() }; + return { infoText: output.stackLaunchTowardsBoss!() }; + }, + }, + { + id: 'Occult Crescent Demon Tablet Summon', + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'A30D', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Add Positions and Out', + cn: '小怪站位并远离', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Gravity Towers Collect', + // Only need to collect Explosion A2F1 or A2EF + type: 'StartsUsingExtra', + netRegex: { id: 'A2F1', capture: true }, + suppressSeconds: 1, + run: (data, matches) => { + const y = parseFloat(matches.y); + if (y < demonTabletCenterY) { + data.demonTabletGravityTowers = 'north'; + return; + } + data.demonTabletGravityTowers = 'south'; + }, + }, + { + id: 'Occult Crescent Demon Tablet Gravity of Dangears Near/Expulsion Afar', + // A2EA Gravity of Dangers Near + // AA01 Gravity of Expulsion Afar + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A2EA', 'AA01'], capture: true }, + alertText: (data, matches, output) => { + const towers = (data.demonTabletGravityTowers === 'north') + ? output.north!() + : (data.demonTabletGravityTowers === 'south') + ? output.south!() + : undefined; + if (matches.id === 'A2EA') { + if (towers !== undefined) + return output.dirOutThenTowers!({ dir: towers }); + return output.goTowerSideOut!(); + } + if (towers !== undefined) + return output.dirInThenTowers!({ dir: towers }); + return output.goTowerSideOut!(); + }, + outputStrings: { + north: Outputs.north, + south: Outputs.south, + dirOutThenTowers: { + en: '${dir} Out => Towers', + cn: '${dir} 外侧 => 塔', + }, + goTowerSideOut: { + en: 'Go Towers Side and Out', + cn: '去塔侧并远离', + }, + dirInThenTowers: { + en: '${dir} In => Knockback => Towers', + cn: '${dir} 内侧 => 击退 => 塔', + }, + goTowerSideIn: { + en: 'Go Towers Side and In => Knockback', + cn: '去塔侧并内侧 => 击退', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Gravity of Dangears Near/Expulsion Afar Knockback', + // 10s castTime + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: 'AA01', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 6, + response: Responses.knockback(), + }, + { + id: 'Occult Crescent Demon Tablet Erase Gravity Safe Corner (Early)', + // The statues are added ~0.1s before Summon (A2E9) cast + // BNpcID 2014581 combatants are responsible for the statues + // The combatants are still invisible for ~5s when the data is available + type: 'StartsUsing', + netRegex: { id: 'A2E9', capture: false }, + delaySeconds: 0.5, // Need some delay for latency + durationSeconds: 21, // Time until tower => safe corner call + promise: async (data) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + })).combatants; + const statues = actors.filter((c) => c.BNpcID === 2014581); + if (statues === undefined || statues.length !== 4) { + console.error( + `Occult Crescent Demon Tablet Summon Statue Locations: Wrong statue count ${statues.length}`, + ); + return; + } + if (statues[0] === undefined) { + console.error( + `Occult Crescent Demon Tablet Summon Statue Locations: Invalid statue data.`, + ); + return; + } + // Only need to examine one statue + const statue = statues[0]; + const x = statue.PosX; + const y = statue.PosY; + + data.demonTabletIsFrontRight = demonTabletFindGravityCorner(x, y); + if (data.demonTabletIsFrontRight === undefined) { + console.error( + `Occult Crescent Demon Tablet Statue Locations: Unrecognized coordinates (${x}, ${y})`, + ); + } + }, + infoText: (data, _matches, output) => { + if (data.demonTabletIsFrontRight === undefined) + return; + return data.demonTabletIsFrontRight + ? output.frontRightLater!() + : output.backLeftLater!(); + }, + outputStrings: { + frontRightLater: { + en: 'Front Right (Later)', + cn: '右前 (稍后)', + }, + backLeftLater: { + en: 'Back Left (Later)', + cn: '左后 (稍后)', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Erase Gravity Collect', + // This re-updates the values and is a backup in case the early call fails + // Statues cast Erase Gravity, which sends them and anyone near up in the air + // Boss casts Restore Gravity which will cause the statues and players to fall back down + // Statues falling down trigger aoes + // Players could be on either side, dependent on where the towers were + // Pattern 1: (Front right safe) + // (688, 352) + // (712, 362.5) + // + // ----- Boss ----- + // + // (688, 395.5) + // (712, 406) + // Pattern 2: (Back left safe) + // + // (688, 362.5) + // (712, 370) + // ----- Boss ----- + // (688, 388) + // (712, 395.5) + // + // Data from StartsUsing is inaccurate, but the Extra lines are close enough + type: 'StartsUsingExtra', + netRegex: { id: 'A2EB', capture: true }, + suppressSeconds: 1, + run: (data, matches) => { + // Only need to examine one statue + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + + data.demonTabletIsFrontRight = demonTabletFindGravityCorner(x, y); + + // Log error for unrecognized coordinates + if (data.demonTabletIsFrontRight === undefined) { + console.error( + `Occult Crescent Demon Tablet Erase Gravity Collect: Unrecognized coordinates (${x}, ${y})`, + ); + } + }, + }, + { + id: 'Occult Crescent Demon Tablet Gravity/Ground Towers', + // Some players need to go to statues for levitate at this point + type: 'StartsUsing', + netRegex: { source: 'Demon Tablet', id: ['A2EA', 'AA01'], capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + infoText: (data, _matches, output) => { + const corner = data.demonTabletIsFrontRight === undefined + ? output.safeCorner!() + : data.demonTabletIsFrontRight + ? output.frontRight!() + : output.backLeft!(); + + return output.towersThenSafeSpot!({ towers: output.getTowers!(), corner: corner }); + }, + outputStrings: { + towersThenSafeSpot: { + en: '${towers} => ${corner}', + cn: '${towers} => ${corner}', + }, + getTowers: Outputs.getTowers, + frontRight: { + en: 'Front Right', + de: 'Vorne Rechts', + fr: 'Avant Droit', + ja: '前右', + cn: '右前', + ko: '앞 오른쪽', + }, + backLeft: { + en: 'Back Left', + de: 'Hinten Links', + fr: 'Arrière Gauche', + ja: '後左', + cn: '左后', + ko: '뒤 왼쪽', + }, + safeCorner: { + en: 'Safe Corner', + cn: '安全角落', + }, + }, + }, + { + id: 'Occult Crescent Demon Tablet Gravity/Ground Tower Explosion', + // This could also capture the Unmitigated Explosion that happens 2.1s later, however + // if there aren't any towers resolved it's probably a wipe + type: 'Ability', + netRegex: { source: 'Demon Tablet', id: ['A2F1', 'A2EF'], capture: false }, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (data.demonTabletIsFrontRight === undefined) + return output.avoidFallingStatues!(); + if (data.demonTabletIsFrontRight) + return output.frontRight!(); + return output.backLeft!(); + }, + outputStrings: { + avoidFallingStatues: { + en: 'Avoid Falling Statues', + cn: '躲避下落雕像', + }, + frontRight: { + en: 'Front Right', + de: 'Vorne Rechts', + fr: 'Avant Droit', + ja: '前右', + cn: '右前', + ko: '앞 오른쪽', + }, + backLeft: { + en: 'Back Left', + de: 'Hinten Links', + fr: 'Arrière Gauche', + ja: '後左', + cn: '左后', + ko: '뒤 왼쪽', + }, + }, + }, + { + id: 'Occult Crescent Tower Manticore Left/Right Hammer', + // Needs to be slowed by slowed by Time Mage or it is 4.2s into a 0.7s followup + // Can be out-ranged as well + // A7BF Left Hammer (7.8s with Slow) + // A7C0 Right Hammer (7.8s with Slow) + // A7E6 Left Hammer (1.5s followup with Slow) + // A7E7 Right Hammer (1.5s followup with Slow) + type: 'StartsUsing', + netRegex: { source: 'Tower Manticore', id: ['A7BF', 'A7C0'], capture: true }, + infoText: (_data, matches, output) => { + if (matches.id === 'A7BF') + return output.rightThenLeft!(); + return output.leftThenRight!(); + }, + outputStrings: { + leftThenRight: Outputs.leftThenRight, + rightThenLeft: Outputs.rightThenLeft, + }, + }, + { + id: 'Occult Crescent Tower Manticore Left/Right Hammer Followup', + // Cast bar can be interrupted leading to extra calls if using castTime + type: 'Ability', + netRegex: { source: 'Tower Manticore', id: ['A7BF', 'A7C0'], capture: true }, + suppressSeconds: 1, + alertText: (_data, matches, output) => { + if (matches.id === 'A7BF') + return output.left!(); + return output.right!(); + }, + outputStrings: { + left: Outputs.left, + right: Outputs.right, + }, + }, + { + id: 'Occult Crescent Dead Stars Decisive Battle', + // Each boss targets ground, avoid getting hit by more than one aoe + // A5FA Decisive Battle from Triton + // A5FB Decisive Battle from Nereid + // A5FC Decisive Battle from Phobos + type: 'StartsUsing', + netRegex: { source: 'Phobos', id: 'A5FC', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Occult Crescent Dead Stars Boss Tether', + // Status effects on players applied without NetworkBuff log lines + // 1156 Tritonic Gravity (Purple Beta) + // 1157 Nereidic Gravity (Red Alpha) + // 1158 Phobosic Gravity (Green Gamma) + type: 'Tether', + netRegex: { id: [headMarkerData.deadStarsBossTether], capture: true }, + condition: (data, matches) => { + // Tethers come from player + if (data.me === matches.source) + return true; + return false; + }, + infoText: (_data, matches, output) => { + return output.boss!({ boss: matches.target }); + }, + outputStrings: { + boss: { + en: 'Tethered to ${boss}', + cn: '连线 ${boss}', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Slice \'n\' Dice', + // Each boss uses tankbuster cleave on main target deadStarsSliceBuster + // A601 Slice 'n' Dice castbar + // A602 Slice 'n' Dice cast that does damage + type: 'HeadMarker', + netRegex: { id: [headMarkerData.deadStarsTankbuster], capture: true }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankCleavesOnPlayers: { + en: 'Tank Cleaves on ${player1}, ${player2}, ${player3}', + cn: '坦克顺劈点 ${player1}, ${player2}, ${player3}', + }, + tankCleaveOnYou: Outputs.tankCleaveOnYou, + tankCleaveOnYouLineStack: { + en: 'Tank Cleave on YOU, Avoid Line Stack', + cn: '坦克顺劈点名, 避开直线分摊', + }, + }; + data.deadStarsSliceTargets.push(matches.target); + if (data.deadStarsSliceTargets.length < 3) + return; + + const target1 = data.deadStarsSliceTargets[0]; + const target2 = data.deadStarsSliceTargets[1]; + const target3 = data.deadStarsSliceTargets[2]; + if (data.me === target1 || data.me === target2 || data.me === target3) { + if (!data.deadStarsIsSlice2) + return { alertText: output.tankCleaveOnYou!() }; + return { alertText: output.tankCleaveOnYouLineStack!() }; + } + + // Do not call out with Firestrike 2 + if (data.deadStarsIsSlice2) + return; + + return { + infoText: output.tankCleavesOnPlayers!({ + player1: data.party.member(target1), + player2: data.party.member(target2), + player3: data.party.member(target3), + }), + }; + }, + run: (data) => { + // Do not clear data for Firestrike 2 to use + if (data.deadStarsSliceTargets.length === 3 && !data.deadStarsIsSlice2) { + data.deadStarsSliceTargets = []; + data.deadStarsIsSlice2 = true; + } + }, + }, + { + id: 'Occult Crescent Dead Stars Three-Body Problem', + // Each boss casts this, logs show A5B5 as 'Three-Body Probl─' + // Only 'Three-Body Problem' text is visible in castbars + // Primordial Chaos: A5B5 by Phobos into A9BD from Nereid + A5B9 Triton + // Icebound Buffoon: A5B5 by Nereid into A5B8 from Phobos + // Blazing Belligerent: A5B5 by Triton into A5B7 from Phobos + type: 'StartsUsing', + netRegex: { source: ['Phobos', 'Nereid', 'Triton'], id: 'A5B5', capture: false }, + infoText: (_data, _matches, output) => output.outOfHitbox!(), + outputStrings: { + outOfHitbox: Outputs.outOfHitbox, + }, + }, + { + id: 'Occult Crescent Dead Stars Primordial Chaos', + // Each boss targets ground, avoid getting hit by more than one aoe + // A5D9 Primordial Chaos castbar + // A5DC Primordial Chaos damage cast for each alliance + type: 'StartsUsing', + netRegex: { source: 'Phobos', id: 'A5D9', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze Gains Effect', + // Track latest effect on player + type: 'GainsEffect', + netRegex: { effectId: [deadStarsRedEffectId, deadStarsBlueEffectId], capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + data.deadStarsOoze = matches; + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze Loses Effect', + // There isn't a debuff at 0 count, track the loses effect log line + type: 'LosesEffect', + netRegex: { effectId: [deadStarsRedEffectId, deadStarsBlueEffectId], capture: true }, + condition: Conditions.targetIsYou(), + run: (data) => { + delete data.deadStarsOoze; + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze Initial', + // Applied with Primordial Chaos + // Comes in stacks of 1, 2, or 3 + // 1159 Nova Ooze (Red) + // 115A Ice Ooze (Blue) + // Players need to get hit by opposite color Ooze to decrease count + // Hits by same color Oooze will increase count + // Four opportunities to increase/decrease stack, meaning those with lower counts can afford mistakes + // Any stacks remaining before Noxious Nova (A5E5) result in lethal damage + type: 'GainsEffect', + netRegex: { effectId: [deadStarsRedEffectId, deadStarsBlueEffectId], capture: true }, + condition: (data, matches) => { + if (data.me === matches.target && data.deadStarsOozeCount === 0) + return true; + return false; + }, + infoText: (_data, matches, output) => { + const num = parseInt(matches.count, 16); + if (matches.effectId === deadStarsBlueEffectId) { + switch (num) { + case 1: + return output.blue!(); + case 2: + return output.blueTwo!(); + case 3: + return output.blueThree!(); + } + } + switch (num) { + case 1: + return output.red!(); + case 2: + return output.redTwo!(); + case 3: + return output.redThree!(); + } + }, + outputStrings: { + blue: { + en: '+1 Blue', + cn: '+1 蓝', + }, + blueTwo: { + en: '+2 Blue', + cn: '+2 蓝', + }, + blueThree: { + en: '+3 Blue', + cn: '+3 蓝', + }, + red: { + en: '+1 Red', + cn: '+1 红', + }, + redTwo: { + en: '+2 Red', + cn: '+2 红', + }, + redThree: { + en: '+3 Red', + cn: '+3 红', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Frozen Fallout Locations', + // This will output both ooze tells if missing debuff data + // This calls one of two safespots if intercard is safe + // Boss casts A45DD (Frozen Fallout) and A5DF + A5E0 tells + // Liquified Triton (Red) tells are the A5DF casts + // Liquified Nereid (Blue) tells are the A5E0 casts + // Invisible entities are centered in the circle aoes that they tell + // StartsUsing can have inaccurate location, Ability seems to be correct + type: 'Ability', + netRegex: { source: ['Phobos', 'Triton'], id: ['A5DF', 'A5E0'], capture: true }, + preRun: (data, matches) => { + const dirNum = Directions.xyTo8DirNum( + parseFloat(matches.x), + parseFloat(matches.y), + deadStarsCenterX, + deadStarsCenterY, + ); + if (matches.id === 'A5DF') + data.deadStarsLiquifiedTriton.push(dirNum); + if (matches.id === 'A5E0') + data.deadStarsLiquifiedNereid.push(dirNum); + }, + infoText: (data, matches, output) => { + if ( + data.deadStarsLiquifiedTriton.length !== 4 && + data.deadStarsLiquifiedNereid.length !== 4 + ) + return; + + const redOoze = data.deadStarsLiquifiedTriton; + const blueOoze = data.deadStarsLiquifiedNereid; + if (redOoze === undefined || blueOoze === undefined) + return; + + if (data.deadStarsOoze === undefined) { + const dirNums = matches.id === 'A5DF' ? redOoze : blueOoze; + + if ( + dirNums[0] === undefined || dirNums[1] === undefined || + dirNums[2] === undefined || dirNums[3] === undefined + ) + return; + const dirs = [ + output[Directions.outputFrom8DirNum(dirNums[0])]!(), + output[Directions.outputFrom8DirNum(dirNums[1])]!(), + output[Directions.outputFrom8DirNum(dirNums[2])]!(), + output[Directions.outputFrom8DirNum(dirNums[3])]!(), + ]; + + // Output both if failed to get deadStarsOooze matches + if (matches.id === 'A5DF') + return output.red!({ dirs: dirs }); + if (matches.id === 'A5E0') + return output.blue!({ dirs: dirs }); + + return; + } + + // Determine which slime locations to use for hits + const dirNums = data.deadStarsOoze.effectId === deadStarsBlueEffectId + ? redOoze + : blueOoze; + + if ( + dirNums[0] === undefined || dirNums[1] === undefined || + dirNums[2] === undefined || dirNums[3] === undefined || + redOoze[1] === undefined || blueOoze[1] === undefined || + redOoze[2] === undefined || blueOoze[2] === undefined || + redOoze[3] === undefined || blueOoze[3] === undefined + ) + return; + + const hitSpots = [ + output[Directions.outputFrom8DirNum(dirNums[0])]!(), + output[Directions.outputFrom8DirNum(dirNums[1])]!(), + output[Directions.outputFrom8DirNum(dirNums[2])]!(), + ]; + // Ignoring initial safe spot + const safeSpots = [ + output[Directions.outputFrom8DirNum(deadStarsFindSafeSpot(blueOoze[1], redOoze[1]))]!(), + output[Directions.outputFrom8DirNum(deadStarsFindSafeSpot(blueOoze[2], redOoze[2]))]!(), + output[Directions.outputFrom8DirNum(deadStarsFindSafeSpot(blueOoze[3], redOoze[3]))]!(), + ]; + + const count = parseInt(data.deadStarsOoze.count, 16); + if (count === 1) { + if (data.deadStarsOoze.effectId === deadStarsBlueEffectId) + return output.red1!({ + hit1: hitSpots[0], + safe1: safeSpots[0], + safe2: safeSpots[1], + safe3: safeSpots[2], + }); + if (data.deadStarsOoze.effectId === deadStarsRedEffectId) + return output.blue1!({ + hit1: hitSpots[0], + safe1: safeSpots[0], + safe2: safeSpots[1], + safe3: safeSpots[2], + }); + } + if (count === 2) { + if (data.deadStarsOoze.effectId === deadStarsBlueEffectId) + return output.red2!({ + hit1: hitSpots[0], + hit2: hitSpots[1], + safe1: safeSpots[1], + safe2: safeSpots[2], + }); + if (data.deadStarsOoze.effectId === deadStarsRedEffectId) + return output.blue2!({ + hit1: hitSpots[0], + hit2: hitSpots[1], + safe1: safeSpots[1], + safe2: safeSpots[2], + }); + } + if (count === 3) { + if (data.deadStarsOoze.effectId === deadStarsBlueEffectId) + return output.blue3!({ + hit1: hitSpots[0], + hit2: hitSpots[1], + hit3: hitSpots[2], + safe1: safeSpots[2], + }); + if (data.deadStarsOoze.effectId === deadStarsRedEffectId) + return output.blue3!({ + hit1: hitSpots[0], + hit2: hitSpots[1], + hit3: hitSpots[2], + safe1: safeSpots[2], + }); + } + }, + tts: null, // Trigger happens 1 sec before individual call and would overlap + outputStrings: { + ...Directions.outputStrings8Dir, + red: { + en: 'Red: ${dirs}', + cn: '红: ${dirs}', + }, + blue: { + en: 'Blue: ${dirs}', + cn: '蓝: ${dirs}', + }, + red1: { + en: '${hit1} => ${safe1} => ${safe2} => ${safe3}', + cn: '${hit1} => ${safe1} => ${safe2} => ${safe3}', + }, + blue1: { + en: '${hit1} => ${safe1} => ${safe2} => ${safe3}', + cn: '${hit1} => ${safe1} => ${safe2} => ${safe3}', + }, + red2: { + en: '${hit1} => ${hit2} => ${safe1} => ${safe2}', + cn: '${hit1} => ${hit2} => ${safe1} => ${safe2}', + }, + blue2: { + en: '${hit1} => ${hit2} => ${safe1} => ${safe2}', + cn: '${hit1} => ${hit2} => ${safe1} => ${safe2}', + }, + red3: { + en: '${hit1} => ${hit2} => ${hit3} => ${safe1}', + cn: '${hit1} => ${hit2} => ${hit3} => ${safe1}', + }, + blue3: { + en: '${hit1} => ${hit2} => ${hit3} => ${safe1}', + cn: '${hit1} => ${hit2} => ${hit3} => ${safe1}', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze 1', + // This could call safe spot for those without buff + type: 'Ability', + netRegex: { source: ['Phobos', 'Triton'], id: ['A5DF', 'A5E0'], capture: false }, + condition: (data) => { + if ( + data.deadStarsLiquifiedTriton.length === 1 && + data.deadStarsLiquifiedNereid.length === 1 + ) + return true; + return false; + }, + infoText: (data, _matches, output) => { + const redOoze = data.deadStarsLiquifiedTriton; + const blueOoze = data.deadStarsLiquifiedNereid; + if ( + redOoze === undefined || blueOoze === undefined || + redOoze[0] === undefined || blueOoze[0] === undefined + ) + return; + + const red = output[deadStarsMapOutput[redOoze[0]] ?? 'unknown']!(); + const blue = output[deadStarsMapOutput[blueOoze[0]] ?? 'unknown']!(); + + if (data.deadStarsOoze === undefined) { + return output.getHitBothOoze!({ red: red, blue: blue }); + } + + if (data.deadStarsOoze.effectId === deadStarsBlueEffectId) + return output.getHitRedOoze!({ hit: red }); + return output.getHitBlueOoze!({ hit: blue }); + }, + outputStrings: { + northeast: Outputs.northeast, + southeast: Outputs.southeast, + southwest: Outputs.southwest, + northwest: Outputs.northwest, + unknown: Outputs.unknown, + getHitRedOoze: { + en: '${hit} for Ooze', + cn: '${hit} 吃软泥', + }, + getHitBlueOoze: { + en: '${hit} for Ooze', + cn: '${hit} 吃软泥', + }, + getHitBothOoze: { + en: 'Red: ${red}, Blue: ${blue}', + cn: '红: ${red}, 蓝: ${blue}', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze Counter', + // Count number of jumps + // Source is unreliable, coming from Triton, Phobos, Liquified Triton, Liquified Nereid + type: 'StartsUsing', + netRegex: { id: [deadStarsRedHitId, deadStarsBlueHitId], capture: false }, + suppressSeconds: 1, + run: (data) => { + data.deadStarsOozeCount = data.deadStarsOozeCount + 1; + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze Hit Tracker', + // Debuffs update about 0.3s after the hit, predict debuff based on ability id and last known debuff + // A5E3 => Liquified Triton, decrease blue count, increase red count + // A5E4 => Liquified Nereid, decrease red count, increase blue count + // These abilities apply a 2s Magic Vulnerability Up (B7D) + // Players can be hit by both, so this is separated from hit trigger call + type: 'Ability', + netRegex: { id: [deadStarsRedHitId, deadStarsBlueHitId], capture: true }, + condition: Conditions.targetIsYou(), + run: (data) => { + if (data.deadStarsWasHitByOoze) + data.deadStarsWasVennDiagramed = true; + data.deadStarsWasHitByOoze = true; + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze 2-4 (Hit by Ooze)', + type: 'Ability', + netRegex: { id: [deadStarsRedHitId, deadStarsBlueHitId], capture: true }, + condition: Conditions.targetIsYou(), + delaySeconds: 0.1, // Only needed to detect player hit by both + suppressSeconds: 1, + alertText: (data, matches, output) => { + // Get list of Ooze jumps based on player's current debuff color + if (data.deadStarsOoze !== undefined) { + const dirNums = data.deadStarsOoze.effectId === deadStarsBlueEffectId + ? data.deadStarsLiquifiedTriton + : data.deadStarsLiquifiedNereid; + if ( + dirNums[0] === undefined || dirNums[1] === undefined || + dirNums[2] === undefined || dirNums[3] === undefined + ) + return; + + const count = parseInt(data.deadStarsOoze.count, 16); + const predict = ( + effectId: string, + id: string, + ): number => { + if ( + (effectId === deadStarsBlueEffectId && id === deadStarsRedHitId) || + (effectId === deadStarsRedEffectId && id === deadStarsBlueHitId) + ) + return -1; + if ( + (effectId === deadStarsBlueEffectId && id === deadStarsBlueHitId) || + (effectId === deadStarsRedEffectId && id === deadStarsRedHitId) + ) + return 1; + return 0; + }; + + // Take last known count if hit by both + const predictedCount = data.deadStarsWasVennDiagramed + ? count + : count + predict(data.deadStarsOoze.effectId, matches.id); + + // Check if player will still need to get hit + if (predictedCount !== 0) { + if (dirNums[data.deadStarsOozeCount] === 1) + return output.getHit!({ dir: output.northeast!() }); + if (dirNums[data.deadStarsOozeCount] === 3) + return output.getHit!({ dir: output.southeast!() }); + if (dirNums[data.deadStarsOozeCount] === 5) + return output.getHit!({ dir: output.southwest!() }); + if (dirNums[data.deadStarsOozeCount] === 7) + return output.getHit!({ dir: output.northwest!() }); + } + } else { + // If player hit by both, the net effect is they will not have a debuff + if (!data.deadStarsWasVennDiagramed) { + // Player either has no debuff, they should be gaining a debuff + const dirNums = matches.id === deadStarsBlueHitId + ? data.deadStarsLiquifiedTriton + : data.deadStarsLiquifiedNereid; + if (dirNums[data.deadStarsOozeCount] === 1) + return output.getHit!({ dir: output.northeast!() }); + if (dirNums[data.deadStarsOozeCount] === 3) + return output.getHit!({ dir: output.southeast!() }); + if (dirNums[data.deadStarsOozeCount] === 5) + return output.getHit!({ dir: output.southwest!() }); + if (dirNums[data.deadStarsOozeCount] === 7) + return output.getHit!({ dir: output.northwest!() }); + } + } + + // Player will have no ooze, calculate where ooze are not jumping to + const blueOoze = data.deadStarsLiquifiedNereid[data.deadStarsOozeCount]; + const redOoze = data.deadStarsLiquifiedTriton[data.deadStarsOozeCount]; + if (blueOoze === undefined || redOoze === undefined) + return; + + // Using longer direction call for single/double direction + const safeSpot = deadStarsFindSafeSpot(blueOoze, redOoze); + + // 1 = Northeast, 3 = Southeast + if (safeSpot !== 1 && safeSpot !== 3) + return output[deadStarsMapOutput[safeSpot] ?? 'unknown']!(); + + // Call both Intercards + const dir1 = output[deadStarsMapOutput[safeSpot] ?? 'unknown']!(); + const dir2 = safeSpot === 1 ? output['southwest']!() : output['northwest']!(); + return output.safeSpots!({ dir1: dir1, dir2: dir2 }); + }, + run: (data) => { + if (data.deadStarsWasVennDiagramed) + data.deadStarsWasVennDiagramed = false; + }, + outputStrings: { + north: Outputs.north, + northeast: Outputs.northeast, + east: Outputs.east, + southeast: Outputs.southeast, + south: Outputs.south, + southwest: Outputs.southwest, + west: Outputs.west, + northwest: Outputs.northwest, + unknown: Outputs.unknown, + getHit: { + en: '${dir} for Ooze', + cn: '去${dir}吃软泥', + }, + safeSpot: { + en: '${dir} Safe Spot', + de: 'Sichere Stelle ${dir}', + fr: '${dir} Zone safe', + ja: '${dir}に安置', + cn: '去${dir}方安全点', + ko: '${dir} 안전 지대', + }, + safeSpots: { + en: '${dir1} / ${dir2} Safe Spots', + cn: '${dir1} / ${dir2} 安全点', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Nova/Ice Ooze 2-4 (Dodged Ooze)', + type: 'Ability', + netRegex: { id: [deadStarsRedHitId, deadStarsBlueHitId], capture: false }, + delaySeconds: 0.1, // Delay to detect if player was hit + suppressSeconds: 1, // Suppress as it hits multiple players + alertText: (data, _matches, output) => { + if (data.deadStarsWasHitByOoze) + return; + // Get list of Ooze jumps based on player's current debuff color + if (data.deadStarsOoze !== undefined) { + const dirNums = data.deadStarsOoze.effectId === deadStarsBlueEffectId + ? data.deadStarsLiquifiedTriton + : data.deadStarsLiquifiedNereid; + + if ( + dirNums[0] === undefined || dirNums[1] === undefined || + dirNums[2] === undefined || dirNums[3] === undefined + ) + return; + + if (dirNums[data.deadStarsOozeCount] === 1) + return output.getHit!({ dir: output.northeast!() }); + if (dirNums[data.deadStarsOozeCount] === 3) + return output.getHit!({ dir: output.southeast!() }); + if (dirNums[data.deadStarsOozeCount] === 5) + return output.getHit!({ dir: output.southwest!() }); + if (dirNums[data.deadStarsOozeCount] === 7) + return output.getHit!({ dir: output.northwest!() }); + } + + // Player has no ooze, calculate where ooze are not jumping to + const blueOoze = data.deadStarsLiquifiedNereid[data.deadStarsOozeCount]; + const redOoze = data.deadStarsLiquifiedTriton[data.deadStarsOozeCount]; + if (blueOoze === undefined || redOoze === undefined) + return; + + // Using longer direction call for single/double direction + const safeSpot = deadStarsFindSafeSpot(blueOoze, redOoze); + + // 1 = Northeast, 3 = Southeast + if (safeSpot !== 1 && safeSpot !== 3) + return output[deadStarsMapOutput[safeSpot] ?? 'unknown']!(); + + // Call both Intercards + const dir1 = output[deadStarsMapOutput[safeSpot] ?? 'unknown']!(); + const dir2 = safeSpot === 1 ? output['southwest']!() : output['northwest']!(); + return output.safeSpots!({ dir1: dir1, dir2: dir2 }); + }, + run: (data) => { + // Reset to false for next jump + data.deadStarsWasHitByOoze = false; + }, + outputStrings: { + north: Outputs.north, + northeast: Outputs.northeast, + east: Outputs.east, + southeast: Outputs.southeast, + south: Outputs.south, + southwest: Outputs.southwest, + west: Outputs.west, + northwest: Outputs.northwest, + unknown: Outputs.unknown, + getHit: { + en: '${dir} for Ooze', + cn: '去${dir}吃软泥', + }, + safeSpot: { + en: '${dir} Safe Spot', + de: 'Sichere Stelle ${dir}', + fr: '${dir} Zone safe', + ja: '${dir}に安置', + cn: '去${dir}方安全点', + ko: '${dir} 안전 지대', + }, + safeSpots: { + en: '${dir1} / ${dir2} Safe Spots', + cn: '${dir1} / ${dir2} 安全点', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Noxious Nova', + // Any stack of Nova Ooze (1159), or Ice Ooze (115A) results in lethal damage + type: 'StartsUsing', + netRegex: { source: 'Phobos', id: 'A5E5', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Occult Crescent Dead Stars Vengeful Tracker', + // Bosses cast A5BC unique to the vengeful casts, but this doesn't have their location at cast + // Bosses jump with A5B4 ~2s after A5BC prior to starting Vengeful casts + // AbilityExtra lines of A5B4 include target location of where they will cast + // Since A5B4 is cast for many other things, this trigger captures when we want the A5B4 data + type: 'Ability', + netRegex: { source: ['Phobos', 'Nereid', 'Triton'], id: 'A5BC', capture: false }, + suppressSeconds: 1, + run: (data) => data.deadStarsIsVengeful = true, + }, + { + id: 'Occult Crescent Dead Stars Vengeful Direction', + // Additional Details on Vengeful Casts: + // Post A5E6 Noxious Nova (A637 Noisome Nuisance) + // A5BD Vengeful Fire III (Triton) + // A5BE Vengeful Blizzard III (Nereid) + // + // Post A5D5 To the Winds (A636 Icebound Buffoon) + // A5BD Vengeful Fire III (Triton) + // A5BF Vengeful Bio III (Phobos) + // + // Post A5C5 To the Winds (A635 Blazing Belligerent) + // A5BE Vengeful Blizzard III (Nereid) + // A5BF Vengeful Bio III (Phobos) + type: 'AbilityExtra', + netRegex: { id: 'A5B4', capture: true }, + condition: (data) => { + return data.deadStarsIsVengeful; + }, + preRun: (data, matches) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + if (data.deadStarsVengeful1.length !== 2) + data.deadStarsVengeful1 = [x, y]; + else if (data.deadStarsVengeful2.length !== 2) + data.deadStarsVengeful2 = [x, y]; + }, + durationSeconds: 7.5, // Vengeful casts end ~0.4s after this + infoText: (data, _matches, output) => { + const boss1 = data.deadStarsVengeful1; + const boss2 = data.deadStarsVengeful2; + + // Calculate mid point (safe spot) and output result + if ( + boss1[0] === undefined || boss1[1] === undefined || + boss2[0] === undefined || boss2[1] === undefined + ) + return; + const x = (boss1[0] + boss2[0]) / 2; + const y = (boss1[1] + boss2[1]) / 2; + const dirNum = Directions.xyTo8DirNum( + x, + y, + deadStarsCenterX, + deadStarsCenterY, + ); + return output[Directions.outputFrom8DirNum(dirNum)]!(); + }, + run: (data) => { + // Reset for next set of casts + if ( + data.deadStarsVengeful1.length === 2 && + data.deadStarsVengeful2.length === 2 + ) { + data.deadStarsVengeful1 = []; + data.deadStarsVengeful2 = []; + data.deadStarsIsVengeful = false; + } + }, + outputStrings: { + ...Directions.outputStrings8Dir, + }, + }, + { + id: 'Occult Crescent Dead Stars Delta Attack', + // There are a multitude of spells in this sequence: + // All three cast A5FD, Triton also casts A5FF and A63E (damage) + // All three cast A5FE, Triton also casts A600 + // Nereid casts A63F (damage) + // All three cast A5FE, Triton also casts A600 + // Phobos casts A63F (damage) + // In total, three hits happen: + // Triton hits at ~5.5s + // Nereid hits at ~6.65s + // Phobos hits at ~7.76s + type: 'StartsUsing', + netRegex: { source: 'Phobos', id: 'A5FD', capture: false }, + durationSeconds: 7, + response: Responses.bigAoe(), + }, + { + id: 'Occult Crescent Dead Stars Firestrike', + // This has a line stack headmarker, but does not appear in the logs + // Each boss starts a 4.7s A603 cast on themselves which comes with A604 on a targeted player + // ~0.13s after A603, each boss casts A606 that does the line aoe damage + type: 'Ability', + netRegex: { source: ['Phobos', 'Nereid', 'Triton'], id: 'A604', capture: true }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = deadStarsOutputStrings; + data.deadStarsFirestrikeTargets.push(matches.target); + if (data.deadStarsFirestrikeTargets.length < 3) + return; + + const target1 = data.deadStarsFirestrikeTargets[0]; + const target2 = data.deadStarsFirestrikeTargets[1]; + const target3 = data.deadStarsFirestrikeTargets[2]; + + if (data.me === target1 || data.me === target2 || data.me === target3) + return { alertText: output.lineStackOnYou!() }; + + return { + infoText: output.lineStacksOnPlayers!({ + player1: data.party.member(target1), + player2: data.party.member(target2), + player3: data.party.member(target3), + }), + }; + }, + run: (data) => { + if (data.deadStarsFirestrikeTargets.length === 3) + data.deadStarsFirestrikeTargets = []; + }, + }, + { + id: 'Occult Crescent Dead Stars Snowball Flight Positions', + // These are each 6.7s casts, covering 9.6s + // Snowball Flight (A5CE) + // Snow Boulder (A5CF) is cast 3 times, 2.5s apart + // Snow Boulder (A5D0) Wild Charge damage is applied when hit + // Knockback timing will vary based on charge order + // Minimum of 4 players needed in each charge, with front person taking major damage + // 3 pairs of soaks, knockback immune recommended to avoid getting hit more than once + type: 'StartsUsing', + netRegex: { source: 'Nereid', id: 'A5CE', capture: false }, + infoText: (_data, _matches, output) => { + return output.chargePositions!(); + }, + outputStrings: { + chargePositions: { + en: 'Wild Charge Positions', + cn: '狂野冲锋站位', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Snowball Flight Knockback', + // CastTime is 6.7s + // Set 1 Knockback at 7s + // Set 2 Knocbkack at 9.6s + // Set 3 Knockback at 12.2s + // This will call out at 6s, covering all three knockbacks + // TODO: Add configurator to select knockback timing + type: 'StartsUsing', + netRegex: { source: 'Nereid', id: 'A5CE', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 0.7, + response: Responses.knockback(), + }, + { + id: 'Occult Crescent Dead Stars Snowball Tether/Knockback', + // Three things happen here + // 1 - Two players get marked with a Proximity Tether + Stack Marker + // 2 - Knockback from center of room + // 3 - Players in stack take proximity damage as if they had their own tether + // Related Spell Ids: + // - Players tethered are targeted by Avalaunch (A5D1) + // - Knockback is caused by Chilling Collision (A5D4) + // - Additional Chilling Collision casts from A5B6 Nereid and A5D3 from Frozen Triton + // - Proximity stack damage is from Avalaunch (A5D2) + // - Snowballs jump using Avalaunch (A89A) + type: 'Tether', + netRegex: { id: [headMarkerData.deadStarsSnowballTether], capture: true }, + preRun: (data) => { + data.deadStarsSnowballTetherCount = data.deadStarsSnowballTetherCount + 1; + }, + promise: async (data, matches) => { + // Only calculate direction for players that are targetted + if (data.me !== matches.target) + return; + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Dead Stars Snowball Tether: Wrong actor count ${actors.length}`, + ); + return; + } + + const dirNum = Directions.xyTo8DirNum( + actor.PosX, + actor.PosY, + deadStarsCenterX, + deadStarsCenterY, + ); + data.deadStarsSnowballTetherDirNum = (dirNum + 4) % 8; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + ...Directions.outputStrings8Dir, + knockbackTetherDir: { + en: 'Tether: Knockback to ${dir} => Stack at Wall', + cn: '连线: 击退到${dir} => 靠墙分摊', + }, + knockbackToSnowball: { + en: 'Knockback to Snowball => Stack at Wall', + cn: '击退到雪球 => 靠墙分摊', + }, + }; + + if ( + data.deadStarsSnowballTetherDirNum !== undefined && + data.me === matches.target + ) { + // This will trigger for each tether a player has + const dir = output[Directions.outputFrom8DirNum(data.deadStarsSnowballTetherDirNum)]!(); + return { alarmText: output.knockbackTetherDir!({ dir: dir }) }; + } + + // A player who has a tether should have a defined direction, but if they don't they'll get two calls + if ( + data.deadStarsSnowballTetherDirNum === undefined && + data.deadStarsSnowballTetherCount === 2 + ) + return { alertText: output.knockbackToSnowball!() }; + }, + }, + { + id: 'Occult Crescent Dead Stars Firestrike 2', + // This has a line stack headmarker, but does not appear in the logs + // Each boss starts a 4.7s A605 (Slice 'n' Dice) cast on themselves which comes with a607 on a targeted player + // ~0.13s after A605, each boss casts A606 that does the line aoe damage + // Meanwhile, boss targets main target with tankbuster cleave A602 Slice 'n' Dice + type: 'Ability', + netRegex: { + source: ['Phobos', 'Nereid', 'Triton', 'Frozen Phobos'], + id: 'A607', + capture: true, + }, + delaySeconds: 0.1, // Delay for Tankbuster target accummulation + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = deadStarsOutputStrings; + data.deadStarsFirestrikeTargets.push(matches.target); + if (data.deadStarsFirestrikeTargets.length < 3) + return; + + const strikeTarget1 = data.deadStarsFirestrikeTargets[0]; + const strikeTarget2 = data.deadStarsFirestrikeTargets[1]; + const strikeTarget3 = data.deadStarsFirestrikeTargets[2]; + if ( + data.me === strikeTarget1 || + data.me === strikeTarget2 || + data.me === strikeTarget3 + ) + return { alertText: output.lineStackOnYouTankCleave!() }; + + // Do not call out to Slice 'n' Dice targets + const sliceTarget1 = data.deadStarsSliceTargets[0]; + const sliceTarget2 = data.deadStarsSliceTargets[1]; + const sliceTarget3 = data.deadStarsSliceTargets[2]; + if ( + data.me === sliceTarget1 || + data.me === sliceTarget2 || + data.me === sliceTarget3 + ) + return; + + return { + infoText: output.lineStacksOnPlayers!({ + player1: data.party.member(strikeTarget1), + player2: data.party.member(strikeTarget2), + player3: data.party.member(strikeTarget3), + }), + }; + }, + run: (data) => { + if (data.deadStarsFirestrikeTargets.length === 3) { + data.deadStarsFirestrikeTargets = []; + data.deadStarsSliceTargets = []; + } + }, + }, + { + id: 'Occult Crescent Dead Stars Six-handed Fistfight', + // Start of enrage sequence + // All three bosses cast a 9.1s Six-handed Fistfight (A5E7) + // They become "Dead Stars", which also casts the spell under A5E9 (10.2s) and A5E8 (9.7s) + // Middle will be taken over/blocked by bosses bodying each other (A5EA Bodied) + type: 'StartsUsing', + netRegex: { source: 'Phobos', id: 'A5E7', capture: false }, + infoText: (_data, _matches, output) => output.outOfMiddleGroups!(), + outputStrings: { + outOfMiddleGroups: { + en: 'Out of Middle, Group Positions', + cn: '远离中间, 分组站位', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Six-handed Fistfight AoE', + // 10.2s cast, delay until 5s before end + type: 'StartsUsing', + netRegex: { source: 'Dead Stars', id: 'A5E9', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 5.2, + suppressSeconds: 1, + response: Responses.bigAoe(), + }, + { + id: 'Occult Crescent Dead Stars Collateral Damage', + type: 'StartsUsing', + netRegex: { source: 'Dead Stars', id: 'A5ED', capture: false }, + infoText: (_data, _matches, output) => output.jetsThenSpread!(), + outputStrings: { + jetsThenSpread: { + en: 'Dodge Two Jets => Spread', + cn: '躲避两次X波 => 分散', + }, + }, + }, + { + id: 'Occult Crescent Dead Stars Collateral Damage Spread', + // 5s to spread after last jet happens, 2s after Collateral Damage cast + type: 'StartsUsing', + netRegex: { source: 'Dead Stars', id: 'A5ED', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) + 2, + alertText: (_data, _matches, output) => output.spread!(), + outputStrings: { + spread: Outputs.spread, + }, + }, + { + id: 'Occult Crescent Pronged Passage Paralyze III', + // Triggers for both bridges on physical ranged dps + type: 'StartsUsing', + netRegex: { source: 'Tower Bhoot', id: 'A903', capture: true }, + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Paralyze III: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Pronged Passage Paralyze III: Wrong actor count ${actors.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + if (actor.PosY < prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'north'; + if (actor.PosY > prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'south'; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + interruptBhoot: { + en: 'Interrupt Bhoot', + cn: '打断浮灵', + }, + northInterrupt: { + en: 'North: Interrupt Bhoot', + cn: '左桥: 打断浮灵', + }, + southInterrupt: { + en: 'South: Interrupt Bhoot', + cn: '右桥: 打断浮灵', + }, + }; + // Tanks have 3y interrupt, only call about actor on their platform + if (data.CanSilence() && data.role === 'tank') { + if (data.prongedPassageActLoc[matches.sourceId] === data.prongedPassageActLoc[data.me]) + return { alarmText: output.interruptBhoot!() }; + } + + // Physical Ranged DPS can reach both platforms + if (data.CanSilence() && data.role !== 'tank') { + if (data.prongedPassageActLoc[matches.sourceId] === 'north') + return { infoText: output.northInterrupt!() }; + if (data.prongedPassageActLoc[matches.sourceId] === 'south') + return { infoText: output.southInterrupt!() }; + } + }, + }, + { + id: 'Occult Crescent Pronged Passage Arcane Spear', + // Floating spears appear and light up 4 rows on each bridge + // Tanks need to be in front + // Phantom Samurai with Shirahadori can also block + // A441 in first two sections, A6F4 in last section + // A441 affects north/south bridge at different times + type: 'StartsUsing', + netRegex: { source: 'Trap', id: 'A441', capture: true }, + suppressSeconds: 1, + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Arcane Spear: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Pronged Passage Arcane Spear: Wrong actor count ${actors.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + if (actor.PosY < prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'north'; + if (actor.PosY > prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'south'; + }, + alertText: (data, matches, output) => { + if (data.prongedPassageActLoc[matches.sourceId] === data.prongedPassageActLoc[data.me]) + return output.wildChargeEast!(); + }, + outputStrings: { + wildChargeEast: { + en: 'Wild Charge (East), Stack in a Row', + cn: '狂野冲锋(右), 在同一行集合', + }, + }, + }, + { + id: 'Occult Crescent Pronged Passage Dense Darkness', + // TODO: Check for Phantom Time Mage Buff? + // NOTE: will trigger for both north/south bridge by default + type: 'StartsUsing', + netRegex: { source: 'Tower Abyss', id: 'A3A8', capture: true }, + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Dense Darkness: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Pronged Passage Dense Darkness: Wrong actor count ${actors.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + if (actor.PosY < prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'north'; + if (actor.PosY > prongedPassageCenterY) + data.prongedPassageActLoc[matches.sourceId] = 'south'; + }, + infoText: (data, matches, output) => { + if (data.prongedPassageActLoc[matches.sourceId] === 'north') + return output.northAoEDispel!(); + if (data.prongedPassageActLoc[matches.sourceId] === 'south') + return output.southAoEDispel!(); + }, + outputStrings: { + northAoEDispel: { + en: 'North: AoE (Dispel if Possible)', + cn: '左桥: AOE (能驱散就驱散)', + }, + southAoEDispel: { + en: 'South: AoE (Dispel if Possible)', + cn: '右桥: AOE (能驱散就驱散)', + }, + }, + }, + { + id: 'Occult Crescent Pronged Passage Ancient Aero III', + // TODO: Check for Phantom Bard Buff? + // 6 Tower Idols cast Ancient Aero III at different times + // Must interrupt with Romeo's Ballad all 6 at same time + // This will count until all 12 have started casting + type: 'StartsUsing', + netRegex: { source: 'Tower Idol', id: 'A61F', capture: true }, + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Ancient Aero III: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Pronged Passage Ancient Aero III: Wrong actor count ${actors.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + const bridge = (actor.PosY < prongedPassageCenterY) ? 'north' : 'south'; + // Ignore actors on other bridge as it's not realistic to stop them + if (data.prongedPassageActLoc[data.me] !== bridge) + return; + data.prongedPassageIdolCastCount[bridge] = (data.prongedPassageIdolCastCount[bridge] ?? 0) + + 1; + }, + infoText: (data, _matches, output) => { + const myBridge = data.prongedPassageActLoc[data.me]; + if (myBridge !== undefined && data.prongedPassageIdolCastCount[myBridge] === 6) { + // Clear data to prevent second firing + data.prongedPassageIdolCastCount = {}; + return output.romeo!(); + } + }, + outputStrings: { + romeo: { + en: 'Romeo\'s Ballad (if possible)', + cn: '爱之歌 (能用就用)', + }, + }, + }, + { + id: 'Occult Crescent Pronged Passage Close Call to Detonate / Far Cry to Detonate', + // Tower Progenitrix casts A620 / A622 + // Tower Progenitor casts A621 / A623 + // Both adds also get a tether and a buff describing the ability + // Only need to capture one as it requires both adds to cast + type: 'StartsUsing', + netRegex: { source: 'Tower Progenitrix', id: ['A620', 'A622'], capture: true }, + promise: async (data) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Close Call to Detonate / Far Cry to Detonat: Wrong combatants count ${combatants.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + topApart: { + en: 'Top row (bosses apart)', + cn: '上排 (BOSS远离)', + }, + bottomApart: { + en: 'Bottom row (bosses apart)', + cn: '下排 (BOSS远离)', + }, + bossesApart: { + en: 'Move bosses apart', + cn: '让BOSS远离', + }, + topTogether: { + en: 'Top row (bosses together)', + cn: '上排 (BOSS靠近)', + }, + bottomTogether: { + en: 'Bottom row (bosses together)', + cn: '下排 (BOSS靠近)', + }, + bossesTogether: { + en: 'Move bosses together', + cn: '让BOSS靠近', + }, + }; + const myBridge = data.prongedPassageActLoc[data.me]; + + // Close to Detonate => Bosses Apart + if (matches.id === 'A620') { + if (myBridge === 'north') { + if (data.role === 'tank') + return { alertText: output.topApart!() }; + return { infoText: output.topApart!() }; + } + if (myBridge === 'south') { + if (data.role === 'tank') + return { alertText: output.bottomApart!() }; + return { infoText: output.bottomApart!() }; + } + return { infoText: output.bossesApart!() }; + } + + // Far to Detonate => Bosses Together + if (myBridge === 'north') { + if (data.role === 'tank') + return { alertText: output.bottomTogether!() }; + return { infoText: output.bottomTogether!() }; + } + if (myBridge === 'south') { + if (data.role === 'tank') + return { alertText: output.topTogether!() }; + return { infoText: output.topTogether!() }; + } + return { infoText: output.bossesTogether!() }; + }, + }, + { + id: 'Occult Crescent Pronged Passage Arcane Spear 2', + // Floating spears appear and light up 4 rows on each bridge + // Tanks need to be in front + // Phantom Samurai with Shirahadori can also block + // A441 in first two sections, A6F4 in last section + // A6F4 affects north/south bridge at same times + type: 'StartsUsing', + netRegex: { source: 'Trap', id: 'A6F4', capture: false }, + suppressSeconds: 1, + alertText: (_data, _matches, output) => output.wildChargeEast!(), + outputStrings: { + wildChargeEast: { + en: 'Wild Charge (East), Stack in a Row', + cn: '狂野冲锋(右), 在同一行集合', + }, + }, + }, + { + id: 'Occult Crescent Pronged Passage Bombshell Drop', + type: 'StartsUsing', + netRegex: { + source: ['Tower Progenitrix', 'Tower Progenitor'], + id: ['A626', 'A627'], + capture: false, + }, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (data.role === 'tank') + return output.pullBossAway!(); + return output.killAdds!(); + }, + outputStrings: { + pullBossAway: { + en: 'Pull boss away from bombs', + cn: '将BOSS拉离炸弹', + }, + killAdds: Outputs.killAdds, + }, + }, + { + id: 'Occult Crescent Pronged Passage Punishing Pounce', + type: 'HeadMarker', + netRegex: { id: [headMarkerData.prongedPassageStack], capture: true }, + condition: (data) => { + // Prevents trigger during Magitaur and Dead Stars + return data.prongedPassageActLoc[data.me] !== undefined; + }, + promise: async (data, matches) => { + const combatants = (await callOverlayHandler({ + call: 'getCombatants', + names: [data.me], + })).combatants; + const me = combatants[0]; + if (combatants.length !== 1 || me === undefined) { + console.error( + `Occult Crescent Pronged Passage Punishing Pounce: Wrong combatants count ${combatants.length}`, + ); + return; + } + const actors = (await callOverlayHandler({ + call: 'getCombatants', + names: [matches.target], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Pronged Passage Punishing Pounce: Wrong actor count ${actors.length}`, + ); + return; + } + data.prongedPassageActLoc[data.me] = me.PosY < prongedPassageCenterY + ? 'north' + : 'south'; + if (actor.PosY < prongedPassageCenterY) + data.prongedPassageActLoc[matches.target] = 'north'; + if (actor.PosY > prongedPassageCenterY) + data.prongedPassageActLoc[matches.target] = 'south'; + }, + infoText: (data, matches, output) => { + if (data.prongedPassageActLoc[matches.target] === data.prongedPassageActLoc[data.me]) + return output.stackOnPlayer!({ player: data.party.member(matches.target) }); + }, + outputStrings: { + stackOnPlayer: Outputs.stackOnPlayer, + }, + }, + { + id: 'Occult Crescent Marble Dragon Tankbuster Filter', + // Used to tracker encounter for filtering + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '77F1', capture: false }, + run: (data) => data.marbleDragonTankbusterFilter = true, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Star', + // 77F1 Imitation Star is a 4.7s cast + // 9ECC Imitation Star damage casts happen 1.8 to 2.9s after + // This cast also applies a 15s bleed called Bleeding (828) + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '77F1', capture: false }, + response: Responses.bleedAoe(), + }, + { + id: 'Occult Crescent Marble Dragon Draconiform Motion', + // Boss turns to face random player and casts 77C1 Draconiform Motion + // This is a 3.7s that coincides with these 4.5s casts: + // 77E6 Draconiform Motion (knockback cleave fromm tail) + // 77E5 Draconiform Motion (knockback cleave from head) + // Getting hit also applies D96 Thrice-come Ruin debuff + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '77C1', capture: false }, + alertText: (data, _matches, output) => { + if ( + data.marbleDragonImitationRainDir !== undefined && + data.marbleDragonImitationRainCount < 6 + ) + return output[data.marbleDragonImitationRainDir]!(); + if (data.marbleDragonImitationRainCount >= 6) { + if (data.marbleDragonTwisterClock === 'clockwise') + return output.northSouth!(); + if (data.marbleDragonTwisterClock === 'counterclockwise') + return output.eastWest!(); + } + return output.sides!(); + }, + run: (data) => { + delete data.marbleDragonImitationRainDir; + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + eastWest: { + en: 'East/West', + cn: '左/右', + }, + northSouth: { + en: 'North/South', + cn: '上/下', + }, + sides: Outputs.sides, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain Counter', + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7687', capture: false }, + run: (data) => { + data.marbleDragonImitationRainCount = data.marbleDragonImitationRainCount + 1; + data.marbleDragonImitationBlizzardCount = 0; + // Clear clock data for Imitation Rain 6 and 7 + if (data.marbleDragonImitationRainCount === 5) + delete data.marbleDragonTwisterClock; + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 1 and 5 Direction (Cross-based)', + // North Puddles + // (-355, 141) (-343, 141) (-331, 141) (-319, 141) + // South Puddles + // (-355, 173) (-343, 173) (-331, 173) (-319, 173) + // BNpcID 2014547 combatant is responsible for the cross puddles, accessible right before Imitation Rain (7797) NetworkAOEAbility + // If (-331, 173) or (-343, 141) is cross, then go East. + // If (-343, 173) or (-331, 141) is cross, then go West. + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7797', capture: false }, + condition: (data) => { + if ( + (data.marbleDragonImitationRainCount === 1 || + data.marbleDragonImitationRainCount === 5) && + data.triggerSetConfig.marbleDragonImitationRainStrategy === 'cross' + ) + return true; + return false; + }, + delaySeconds: 0.5, // Need to delay for latency + suppressSeconds: 1, + promise: async (data) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + })).combatants; + const crosses = actors.filter((c) => c.BNpcID === 2014547); + if (crosses.length !== 2 || crosses[0] === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 1 and 5 Direction (Cross-based): Wrong actor count ${crosses.length}`, + ); + return; + } + // Only need to check one of the two crosses + const x = crosses[0].PosX; + const y = crosses[0].PosY; + + if ( + ((x > -332 && x < -330) && (y > 172 && y < 174)) || + ((x > -344 && x < -342) && (y > 140 && y < 142)) + ) { + data.marbleDragonImitationRainDir = 'east'; + } else if ( + ((x > -344 && x < -342) && (y > 172 && y < 174)) || + ((x > -332 && x < -330) && (y > 140 && y < 142)) + ) { + data.marbleDragonImitationRainDir = 'west'; + } else { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 1 and 5 Direction (Cross-based): Unexpected coordinates (${x}, ${y})`, + ); + } + }, + infoText: (data, _matches, output) => { + if (data.marbleDragonImitationRainDir === undefined) + return; + const dir = data.marbleDragonImitationRainDir; + const dir1 = output[dir]!(); + + // Second direction is either north or south, but not known yet + if (data.marbleDragonHasWickedWater) { + const dir2 = output.wickedWater!({ dir: dir1 }); + return dir === 'east' + ? output.eastThenWickedWater!({ dir1: dir1, dir2: dir2 }) + : output.westThenWickedWater!({ dir1: dir1, dir2: dir2 }); + } + return dir === 'east' ? output.eastLater!({ dir: dir1 }) : output.westLater!({ dir: dir1 }); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + eastLater: { + en: '(${dir} Later)', + cn: '(稍后 ${dir})', + }, + westLater: { + en: '(${dir} Later)', + cn: '(稍后 ${dir})', + }, + eastThenWickedWater: { + en: '(${dir1} Later => ${dir2})', + cn: '(稍后 ${dir1} => ${dir2})', + }, + westThenWickedWater: { + en: '(${dir1} Later => ${dir2})', + cn: '(稍后 ${dir1} => ${dir2})', + }, + wickedWater: { + en: 'Get Hit ${dir}', + cn: '站在${dir}吃圈', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 1 and 5 Collect (Ice-based)', + // Alternate Strategy using the Imitation Icicle closest to Wall for inital East/West call + // Imitation Icicle location data is in the StartsUsingExtra lines of Imitation Icicle (75E4) + // Four possible locations for Imitation Icicles + // North Puddle by West wall + // (-353, 153) + // (-331, 161) + // South Puddle by West wall + // (-331, 153) + // (-355, 161) This seems like a bug? + // North Puddle by East Wall + // (-319, 153) + // (-343, 161) + // South Puddle by East Wall + // (-343, 153) + // (-319, 161) + // This is available ~2.4s before Draconiform Motion (77C1) startsUsing + // 271 log line slightly earlier could be grabbed with OverlayPlugin, but timing could vary + // Output conflicts with Draconiform Motion Bait trigger, so this just collects + type: 'StartsUsingExtra', + netRegex: { id: '75E4', capture: true }, + condition: (data) => { + if ( + (data.marbleDragonImitationRainCount === 1 || + data.marbleDragonImitationRainCount === 5) && + data.triggerSetConfig.marbleDragonImitationRainStrategy === 'ice' + ) + return true; + return false; + }, + suppressSeconds: 1, + run: (data, matches, _output) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + + // Could have either north or south puddle in the pattern + // North Puddle by East Wall + if ( + ((x > -320 && x < -318) && (y < marbleDragonCenterY)) || + ((x > -345 && x < -342) && (y > marbleDragonCenterY)) + ) { + data.marbleDragonImitationRainDir = 'east'; + // Then north + return; + } + // South Puddle by East Wall + if ( + ((x > -345 && x < -342) && (y < marbleDragonCenterY)) || + ((x > -320 && x < -318) && (y > marbleDragonCenterY)) + ) { + data.marbleDragonImitationRainDir = 'east'; + // Then south + } + // North Puddle by West Wall + if ( + ((x > -355 && x < -352) && (y < marbleDragonCenterY)) || + ((x > -332 && x < -330) && (y > marbleDragonCenterY)) + ) { + data.marbleDragonImitationRainDir = 'west'; + // Then north + return; + } + // South Puddle by West Wall + // NOTE: South check expanded to -352 incase it is fixed later + if ( + ((x > -332 && x < -330) && (y < marbleDragonCenterY)) || + ((x > -356 && x < -352) && (y > marbleDragonCenterY)) + ) { + data.marbleDragonImitationRainDir = 'west'; + // Then south + return; + } + console.error( + `Occult Crescent Marble Dragon Imitation Rain 1 and 5 Collect (Ice-based): Unexpected coordinates (${x}, ${y})`, + ); + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Blizzard Counter', + // Imitation Blizzard (Cross) (7614) + // Imitation Blizzard (Circle) (7602) + // Not currently tracking the Imitation Blizzard (Tower) (7615) + // Used to track puddle explosions during Imitation Rains for calls on where to dodge to + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: ['7614', '7602'], capture: false }, + suppressSeconds: 1, + run: (data) => { + data.marbleDragonImitationBlizzardCount = data.marbleDragonImitationBlizzardCount + 1; + }, + }, + { + id: 'Occult Crescent Marble Dragon Dread Deluge', + // Tankbuster targets one tank in each alliance party, 6 tanks total + // Applies a heavy bleed to target + // TODO: Determine if they are in player's party to call just that name + type: 'HeadMarker', + netRegex: { id: [headMarkerData.marbleDragonTankbuster], capture: true }, + condition: (data) => { + // Prevent triggering in CEs such as Noise Complaint and Flame of Dusk + // This also triggers by certain mobs when out of combat + return data.marbleDragonTankbusterFilter; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + tankBusterBleeds: { + en: 'Tankbuster Bleeds', + cn: '坦克流血死刑', + }, + tankBusterBleedOnYou: { + en: 'Tankbuster bleed on YOU', + cn: '坦克流血死刑点名', + }, + }; + data.marbleDragonDelugeTargets.push(matches.target); + if (data.marbleDragonDelugeTargets.length < 6) + return; + + const target1 = data.marbleDragonDelugeTargets[0]; + const target2 = data.marbleDragonDelugeTargets[1]; + const target3 = data.marbleDragonDelugeTargets[2]; + const target4 = data.marbleDragonDelugeTargets[3]; + const target5 = data.marbleDragonDelugeTargets[4]; + const target6 = data.marbleDragonDelugeTargets[6]; + if ( + data.me === target1 || data.me === target2 || data.me === target3 || + data.me === target4 || data.me === target5 || data.me === target6 + ) + return { alertText: output.tankBusterBleedOnYou!() }; + if (data.role === 'tank' || data.role === 'healer') + return { alertText: output.tankBusterBleeds!() }; + return { infoText: output.tankBusterBleeds!() }; + }, + run: (data) => { + if (data.marbleDragonDelugeTargets.length === 6) + data.marbleDragonDelugeTargets = []; + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 2 Direction', + // Call East/West later for movement after Draconiform Motion and use data collected here for later calls + // Twisters will rotate CW or CCW + // The center is always a cross, the other two form a diagonal with the center + // (-337, 133) + // (-353, 141) (-321, 141) + // (-337, 157) + // (-353, 173) (-321, 173) + // (-337, 181) + // BNpcID 2014547 combatant is responsible for the cross puddles, accessible around Imitation Rain (7797) NetworkAOEAbility + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7797', capture: false }, + condition: (data) => { + if (data.marbleDragonImitationRainCount === 2) + return true; + return false; + }, + delaySeconds: 0.5, // NPC Add available before or slightly after the cast + suppressSeconds: 1, + promise: async (data) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + })).combatants; + const crosses = actors.filter((c) => c.BNpcID === 2014547); + if (crosses.length !== 3 || crosses === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 2 Direction: Wrong actor count ${crosses.length}`, + ); + return; + } + + const cross1 = crosses[0]; + const cross2 = crosses[1]; + const cross3 = crosses[2]; + if (cross1 === undefined || cross2 === undefined || cross3 === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 2 Direction: Invalid actors.`, + ); + return; + } + + const getCrossLocation = ( + combatant: PluginCombatantState, + ): 'NE' | 'SE' | 'SW' | 'NW' | 'center' | undefined => { + const x = combatant.PosX; + const y = combatant.PosY; + const result = getPuddleLocation(x, y); + if (result === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 2 Direction: Unexpected puddle location (${x}, ${y})`, + ); + } + return result; + }; + + // Get Locations of cross puddles + const cross1Location = getCrossLocation(cross1); + const cross2Location = getCrossLocation(cross2); + const cross3Location = getCrossLocation(cross3); + + // Ignoring the center puddle, net result should be length 2 + if (cross1Location !== 'center' && cross1Location !== undefined) + data.marbleDragonImitationRainCrosses.push(cross1Location); + if (cross2Location !== 'center' && cross2Location !== undefined) + data.marbleDragonImitationRainCrosses.push(cross2Location); + if (cross3Location !== 'center' && cross3Location !== undefined) + data.marbleDragonImitationRainCrosses.push(cross3Location); + + // East/West call based on south puddle location + if (data.marbleDragonImitationRainCrosses !== undefined) { + const dir = data.marbleDragonImitationRainCrosses[0]; + if (dir === 'NE' || dir === 'SW') + data.marbleDragonImitationRainDir = 'west'; + if (dir === 'NW' || dir === 'SE') + data.marbleDragonImitationRainDir = 'east'; + } + }, + infoText: (data, _matches, output) => { + if (data.marbleDragonImitationRainDir === undefined) + return; + return output[data.marbleDragonImitationRainDir]!(); + }, + outputStrings: { + east: { + en: '(East Later)', + cn: '(稍后左)', + }, + west: { + en: '(West Later)', + cn: '(稍后右)', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 2 Pattern', + // Twisters will rotate CW or CCW and start moving 1s before end of Draconiform Motion (77C1) + // They spawn at (-362, 157) and (-312, 157) as combatant "Icewind" about ~1.6s after Frigid Twister (7638) + // About 3.2s later, they start using Frigid Twister (76CF) abilities + // At Spawn headings are ~2.00 for left side, ~-2.00 for right + // They start turning ~0.5s after AddedCombatant, but these turns seem random + // Heading appears to snap into expected place once they start moving, but timing for each can vary slightly + type: 'AddedCombatant', + netRegex: { name: 'Icewind', capture: true }, + condition: (data) => { + if (data.marbleDragonImitationRainCount === 2) + return true; + return false; + }, + delaySeconds: 5.7, // Before the move, the actor seems to just spin randomly in place + suppressSeconds: 1, // Only need one of the combatants + promise: async (data, matches) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.id, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 2 Pattern: Wrong actor count ${actors.length}`, + ); + return; + } + + const x = actor.PosX; + const facing = Directions.hdgTo16DirNum(actor.Heading); + const getTwisterSide = ( + x: number, + ): 'west' | 'east' | undefined => { + if (x > -363 && x < -361) + return 'west'; + if (x > -313 && x < -311) + return 'east'; + return undefined; + }; + + const side = getTwisterSide(x); + if ( + (side === 'west' && (facing >= 0 && facing <= 3)) || // N to ENE + (side === 'east' && (facing >= 8 && facing <= 11)) // S to WSW + ) + data.marbleDragonTwisterClock = 'clockwise'; + else if ( + (side === 'west' && (facing >= 5 && facing <= 8)) || // ESE to S + (side === 'east' && ((facing >= 13 && facing <= 15) || facing === 0)) // WNW to N + ) + data.marbleDragonTwisterClock = 'counterclockwise'; + }, + infoText: (data, _matches, output) => { + if (data.marbleDragonTwisterClock === undefined) + return; + const clock = data.marbleDragonTwisterClock; + const crosses = data.marbleDragonImitationRainCrosses; + // Only need one cross puddle + if (crosses === undefined || (crosses[0] === undefined && crosses[1] === undefined)) + return output[clock]!(); + if ( + (clock === 'clockwise' && + ((crosses[0] === 'NE' || crosses[0] === 'SW') || + (crosses[1] === 'NE' || crosses[1] === 'SW'))) || + (clock === 'counterclockwise' && + ((crosses[0] === 'NW' || crosses[0] === 'SE') || + (crosses[1] === 'NW' || crosses[1] === 'SE'))) + ) + return output.circlesFirst!({ clock: output[clock]!() }); + if ( + (clock === 'clockwise' && + ((crosses[0] === 'NW' || crosses[0] === 'SE') || + (crosses[1] === 'NW' || crosses[1] === 'SE'))) || + (clock === 'counterclockwise' && + ((crosses[0] === 'NE' || crosses[0] === 'SW') || + (crosses[1] === 'NE' || crosses[1] === 'SW'))) + ) + return output.crossesFirst!({ clock: output[clock]!() }); + return output[clock]!(); + }, + outputStrings: { + crossesFirst: { + en: 'Crosses First + ${clock}', + cn: '先十字 + ${clock}', + }, + circlesFirst: { + en: 'Circles First + ${clock}', + cn: '先圆圈 + ${clock}', + }, + clockwise: Outputs.clockwise, + counterclockwise: Outputs.counterclockwise, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 2 Dodge 1', + // Imitation Blizzard (Cross) (7614) + // Imitation Blizzard (Circle) (7602) + // First cast is always 2 circles or 2 crosses + // Assuming player followed south cross priority call + // Cross has more time to get to the called direction than circle + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: ['7614', '7602'], capture: true }, + condition: (data) => { + if ( + data.marbleDragonImitationRainCount === 2 && + data.marbleDragonImitationBlizzardCount === 1 + ) + return true; + return false; + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + suppressSeconds: 1, + response: (_data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + dirESE: Outputs.dirESE, + dirWSW: Outputs.dirWSW, + cross1Dodge: { + en: '${dir}', + cn: '${dir}', + }, + circles1Dodge: { + en: '${dir}', + cn: '${dir}', + }, + }; + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + const loc = getPuddleLocation(x, y); + if (loc === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 2 Dodge 1: Unexpected puddle location (${x}, ${y})`, + ); + return; + } + + // Crosses + if (matches.id === '7614') { + if (loc === 'NW' || loc === 'SE') + return { infoText: output.cross1Dodge!({ dir: output.dirESE!() }) }; + if (loc === 'NE' || loc === 'SW') + return { infoText: output.cross1Dodge!({ dir: output.dirWSW!() }) }; + } + // Circles may be able to stay where they were or move slightly to avoid center Cross + // South Cross priority = SW, so WSW is the direction to go + if (loc === 'NW' || loc === 'SE') + return { alertText: output.circles1Dodge!({ dir: output.dirWSW!() }) }; + // South Cross priority = SE, so ESE is the direction to go + if (loc === 'NE' || loc === 'SW') + return { alertText: output.circles1Dodge!({ dir: output.dirESE!() }) }; + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 2 Dodge 2', + // Imitation Blizzard (Cross) (7614) + // Cross first = player already directed to a safe spot previously + // Circles pattern has a cross here that is unique to it + // Assuming player followed south cross priority call + // Calling East/West as those are the easy spots to get to, center is safe as well + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '7614', capture: true }, + condition: (data) => { + if ( + data.marbleDragonImitationRainCount === 2 && + data.marbleDragonImitationBlizzardCount === 2 + ) + return true; + return false; + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + suppressSeconds: 1, + alertText: (data, _matches, output) => { + const crosses = data.marbleDragonImitationRainCrosses; + if (crosses === undefined || crosses[0] === undefined) + return output.twoDirs!({ dir1: output.east!(), dir2: output.west!() }); + + // Check where a cross spawned at earlier + if (crosses[0] === 'NE' || crosses[0] === 'SW') + return output.west!(); + if (crosses[0] === 'NW' || crosses[0] === 'SE') + return output.east!(); + + // Invalid data on the cross, output both dirs + return output.twoDirs!({ dir1: output.east!(), dir2: output.west!() }); + }, + outputStrings: { + east: Outputs.east, + west: Outputs.west, + twoDirs: { + en: '${dir1}/${dir2}', + cn: '${dir1}/${dir2}', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 2 Frigid Twister Reminder', + // Frigid Twister continues for ~5s after Imitation Blizzard + // Call to Avoid Twister + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: ['7614', '7602'], capture: true }, + condition: (data) => { + if ( + data.marbleDragonImitationRainCount === 2 && + data.marbleDragonImitationBlizzardCount === 3 + ) + return true; + return false; + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + suppressSeconds: 1, + infoText: (_data, _matches, output) => output.avoidTwister!(), + outputStrings: { + avoidTwister: { + en: 'Avoid Twister', + cn: '远离龙卷风', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Frigid Dive Direction', + // Prior to Frigid Dive (7796), boss casts unknown_7795 which is it moving to the dive position + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7795', capture: true }, + promise: async (data, matches) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.sourceId, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Marble Dragon Frigid Dive Direction: Wrong actor count ${actors.length}`, + ); + return; + } + data.marbleDragonDiveDirNum = Directions.xyTo8DirNum( + actor.PosX, + actor.PosY, + marbleDragonCenterX, + marbleDragonCenterY, + ); + }, + alertText: (data, _matches, output) => { + if (data.marbleDragonDiveDirNum === undefined) { + return output.bossDiveThenTowers!(); + } + const dir1 = output[Directions.outputFrom8DirNum(data.marbleDragonDiveDirNum)]!(); + const dir2 = output[Directions.outputFrom8DirNum((data.marbleDragonDiveDirNum + 4) % 8)]!(); + return output.diveDirsThenTowers!({ dir1: dir1, dir2: dir2 }); + }, + run: (data) => { + data.marbleDragonIsFrigidDive = true; + }, + outputStrings: { + ...Directions.outputStrings8Dir, + diveDirsThenTowers: { + en: '${dir1}/${dir2} Dive => Towers', + cn: '${dir1}/${dir2} 俯冲 => 塔', + }, + bossDiveThenTowers: { + en: 'Boss Dive => Towers', + cn: 'BOSS俯冲 => 塔', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Towers 1 and 3', + // Frigid Dive (7796) triggers the center cross puddle to go off + // Using Frigid Dive (93BB) damage 7.7s cast to trigger call + // Players can modify cardinals/intercards to an assigned tower direction + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '93BB', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + alertText: (data, _matches, output) => { + if (data.marbleDragonDiveDirNum === undefined) { + return output.towersUnknownDir!(); + } + const dir1 = output[Directions.outputFrom8DirNum(data.marbleDragonDiveDirNum)]!(); + const dir2 = output[Directions.outputFrom8DirNum((data.marbleDragonDiveDirNum + 4) % 8)]!(); + // `marbleDragonDiveDirNum % 2 === 0` = this is aimed at a cardinal, so intercard towers are second + if (data.marbleDragonDiveDirNum % 2 === 0) + return output.towerDirsThenIntercardTowers!({ dir1: dir1, dir2: dir2 }); + return output.towerDirsThenCardinalTowers!({ dir1: dir1, dir2: dir2 }); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + towersUnknownDir: { + en: 'Towers => Cardinal/Intercard Towers', + cn: '塔 => 正点/斜点塔', + }, + towerDirsThenCardinalTowers: { + en: '${dir1}/${dir2} Towers => Cardinal Towers', + cn: '${dir1}/${dir2} 塔 => 正点塔', + }, + towerDirsThenIntercardTowers: { + en: '${dir1}/${dir2} Towers => Intercard Towers', + cn: '${dir1}/${dir2} 塔 => 斜点塔', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Towers 2 and 4', + // Once Imitation Blizzard 7614, 0.7s and 7615, 3.7s casts have gone off, towers appear in ~0.4s + // These tower casts occur after Wicked Water as well + // Using the cross (7614) Imitation Blizzard as it only occurs once per dive versus the 7615 (towers) + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '7614', capture: true }, + condition: (data) => { + // Only execute during Frigid Dive Towers + return data.marbleDragonIsFrigidDive; + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (data.marbleDragonDiveDirNum === undefined) { + return output.unknownTowers!(); + } + + // `marbleDragonDiveDirNum % 2 === 0` = this is aimed at a cardinal, so intercard towers are second + if (data.marbleDragonDiveDirNum % 2 === 0) + return output.intercardTowers!(); + return output.cardinalTowers!(); + }, + outputStrings: { + ...Directions.outputStrings8Dir, + unknownTowers: { + en: 'Cardinal/Intercard Towers', + cn: '正点/斜点塔', + }, + cardinalTowers: { + en: 'Cardinal Towers', + cn: '正点塔', + }, + intercardTowers: { + en: 'Intercardinal Towers', + cn: '斜点塔', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Frigid Dive Cleanup', + // Ability conflicts in timing with towers 2, this trigger fires before in emulator + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7615', capture: false }, + condition: (data) => { + // Only execute during Frigid Dive Towers + return data.marbleDragonIsFrigidDive; + }, + delaySeconds: 1, + suppressSeconds: 1, + run: (data) => { + // Clear data for subsequent Frigid Dive/Towers + data.marbleDragonIsFrigidDive = false; + data.marbleDragonDiveDirNum = undefined; + }, + }, + { + id: 'Occult Crescent Marble Dragon Lifeless Legacy', + // castTime is 35s + type: 'StartsUsing', + netRegex: { source: 'Marble Dragon', id: '7798', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 7, + response: Responses.bigAoe(), + }, + { + id: 'Occult Crescent Marble Dragon Wicked Water', + // Boss casts 77E7 Wicked Water, several players get marked + // After cast end, marked players affected the following: + // 3AA Throttle (46s) + // 10EE Wicked Water (46s) + // An Imitation Blizzard hit changes Wicked Water into 10EF Gelid Gaol + // Players must be broken out of the gaol to clear the Throttle debuff + type: 'HeadMarker', + netRegex: { id: [headMarkerData.marbleDragonWickedWater], capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 20, // Time until reminder + infoText: (_data, _matches, output) => output.wickedWaterOnYou!(), + run: (data) => data.marbleDragonHasWickedWater = true, + outputStrings: { + wickedWaterOnYou: { + en: 'Wicked Water on YOU', + cn: '水圈点名', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Wicked Water Reminder', + // Need to avoid getting hit by multiple Imitation Blizzards + // Cross Imitation Blizzards should be avoided + // Cross Imitation Blizzards resolve at ~23s remaining on the debuff + // Needs some delay to not conflict with Draconiform Motion callouts + // 20s is ~2s after Draconiform Motion and gives ~3s to get hit + type: 'HeadMarker', + netRegex: { id: [headMarkerData.marbleDragonWickedWater], capture: true }, + condition: Conditions.targetIsYou(), + delaySeconds: 20, + alertText: (_data, _matches, output) => output.getHitByIceExplosion!(), + outputStrings: { + getHitByIceExplosion: { + en: 'Get hit by ice explosion', + cn: '吃冰圈爆炸', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Gelid Gaol', + // If capture someone in Gaol, trigger break Gaols + type: 'GainsEffect', + netRegex: { effectId: '10EF', capture: false }, + condition: (data) => { + // Only output for those that do not have Wicked Water + if (data.marbleDragonHasWickedWater) + return false; + return true; + }, + suppressSeconds: 47, // Duration of Wicked Water + 1s + alertText: (_data, _matches, output) => output.breakGaols!(), + outputStrings: { + breakGaols: { + en: 'Break Gaols', + cn: '打破冰牢', + }, + }, + }, + { + id: 'Occult Crescent Marble Dragon Imitation Rain 6 and 7 Puddles', + // Call East/West or North/South later for movement after Draconiform Motion and use data collected here for later calls + // Twisters will rotate CW or CCW + // Cross puddles are either E/W or N/S + // (-337, 135) + // (-359, 157) (-315, 157) + // (-337, 179) + // BNpcID 2014547 combatant is responsible for the cross puddles, accessible around Imitation Rain (7797) NetworkAOEAbility + type: 'Ability', + netRegex: { source: 'Marble Dragon', id: '7797', capture: false }, + condition: (data) => { + if ( + data.marbleDragonImitationRainCount === 6 || + data.marbleDragonImitationRainCount === 7 + ) + return true; + return false; + }, + delaySeconds: 0.5, // NPC Add available before or slightly after the cast + suppressSeconds: 1, + promise: async (data) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + })).combatants; + const crosses = actors.filter((c) => c.BNpcID === 2014547); + if (crosses.length !== 2 || crosses === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 6 and 7 Puddles: Wrong actor count ${crosses.length}`, + ); return; } - delete data.ce; - const ceId = matches.data0.toUpperCase(); - for (const key in ceIds) { - if (ceIds[key] === ceId) { - if (data.options.Debug) - console.log(`Start CE: ${key} (${ceId})`); - data.ce = key; - return; + const cross1 = crosses[0]; + const cross2 = crosses[1]; + if (cross1 === undefined || cross2 === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 6 and 7 Puddles: Invalid actors.`, + ); + return; + } + + // Function to find and validate a puddle location during Imitation Rain 6 + const getPuddleLocation = ( + x: number, + y: number, + ): 'N' | 'E' | 'S' | 'W' | undefined => { + // N/S Puddles + if (x > -338 && x < -336) { + if (y > 134 && y < 136) + return 'N'; + if (y > 178 && y < 180) + return 'S'; + } + // E/W Puddles + if (y > 156 && y < 158) { + if (x > -316 && x < -314) + return 'E'; + if (x > -360 && x < -358) + return 'W'; + } + return undefined; + }; + const getCrossLocation = ( + combatant: PluginCombatantState, + ): 'N' | 'E' | 'S' | 'W' | undefined => { + const x = combatant.PosX; + const y = combatant.PosY; + const result = getPuddleLocation(x, y); + if (result === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 6 and 7 Puddles: Unexpected puddle location (${x}, ${y})`, + ); } + return result; + }; + + // Get Locations of cross puddles + const cross1Location = getCrossLocation(cross1); + const cross2Location = getCrossLocation(cross2); + + // Clear data from previous Imitation Rains + data.marbleDragonImitationRainCrosses = []; + + if (cross1Location !== undefined) + data.marbleDragonImitationRainCrosses.push(cross1Location); + if (cross2Location !== undefined) + data.marbleDragonImitationRainCrosses.push(cross2Location); + + // East/West or North/South call based on puddle location + if (data.marbleDragonImitationRainCrosses !== undefined) { + const dir = data.marbleDragonImitationRainCrosses[0]; + if (dir === 'N' || dir === 'S') + data.marbleDragonImitationRainDir = 'east'; + if (dir === 'E' || dir === 'W') + data.marbleDragonImitationRainDir = 'north'; } + }, + infoText: (data, _matches, output) => { + // Unable to predict on Imitation Rain 6 due to not yet knowing CW or CCW at this time + if (data.marbleDragonImitationRainCount !== 7) + return; - if (data.options.Debug) - console.log(`Start CE: ??? (${ceId})`); + if ( + data.marbleDragonImitationRainDir === undefined || + data.marbleDragonTwisterClock === undefined + ) + return; + const clock = data.marbleDragonTwisterClock; + const crosses = data.marbleDragonImitationRainCrosses; + + // Only need one puddle needed + if (crosses === undefined || crosses[0] === undefined) + return; + if ( + (clock === 'clockwise' && + (crosses[0] === 'N' || crosses[0] === 'S')) || + (clock === 'counterclockwise' && + (crosses[0] === 'E' || crosses[0] === 'W')) + ) + return output.circlesFirst!(); + if ( + (clock === 'clockwise' && + (crosses[0] === 'E' || crosses[0] === 'W')) || + (clock === 'counterclockwise' && + (crosses[0] === 'N' || crosses[0] === 'S')) + ) + return output.crossesFirst!(); + }, + outputStrings: { + circlesFirst: { + en: 'Circles First', + cn: '先圆圈', + }, + crossesFirst: { + en: 'Crosses First', + cn: '先十字', + }, }, }, { - id: 'Occult Crescent Cloister Demon Tidal Breath', - type: 'StartsUsing', - netRegex: { source: 'Cloister Demon', id: 'A190', capture: false }, - response: Responses.getBehind(), + id: 'Occult Crescent Marble Dragon Imitation Rain 6 Pattern', + // Twisters will rotate CW or CCW and start moving 1s before end of Draconiform Motion (77C1) + // They spawn at (-354.5, 174.5) and (-319.5, 139.5) as combatant "Icewind" about ~1.7s after Frigid Twister (7638) + // About 3.2s later, they start using Frigid Twister (76CF) abilities + // At Spawn headings are ~1.95 for southwest side, ~-0.57 for northeast + // They start turning ~0.5s after AddedCombatant, but these turns seem random + // Heading appears to snap into expected place once they start moving, but timing for each can vary slightly + type: 'AddedCombatant', + netRegex: { name: 'Icewind', capture: true }, + condition: (data) => { + if (data.marbleDragonImitationRainCount === 6) + return true; + return false; + }, + delaySeconds: 5.7, // Before the move, the actor seems to just spin randomly in place + suppressSeconds: 1, // Only need one of the combatants + promise: async (data, matches) => { + const actors = (await callOverlayHandler({ + call: 'getCombatants', + ids: [parseInt(matches.id, 16)], + })).combatants; + const actor = actors[0]; + if (actors.length !== 1 || actor === undefined) { + console.error( + `Occult Crescent Marble Dragon Imitation Rain 6 Pattern: Wrong actor count ${actors.length}`, + ); + return; + } + + const x = actor.PosX; + const facing = Directions.hdgTo16DirNum(actor.Heading); + const getTwisterSide = ( + x: number, + ): 'southwest' | 'northeast' | undefined => { + if (x < marbleDragonCenterX) + return 'southwest'; + if (x > marbleDragonCenterX) + return 'northeast'; + return undefined; + }; + + const side = getTwisterSide(x); + if ( + (side === 'southwest' && (facing >= 13 && facing <= 15)) || // WNW to NNW + (side === 'northeast' && (facing >= 5 && facing <= 7)) // ESE to SSW + ) + data.marbleDragonTwisterClock = 'clockwise'; + else if ( + (side === 'southwest' && (facing >= 5 && facing <= 7)) || // ESE to SSW + (side === 'northeast' && (facing >= 13 && facing <= 15)) // WNW to NNW + ) + data.marbleDragonTwisterClock = 'counterclockwise'; + }, + infoText: (data, _matches, output) => { + if (data.marbleDragonTwisterClock === undefined) + return; + const clock = data.marbleDragonTwisterClock; + const crosses = data.marbleDragonImitationRainCrosses; + const dir = clock === 'clockwise' + ? output.northSouth!() + : output.eastWest!(); + // Only need one puddle needed + if (crosses === undefined || crosses[0] === undefined) + return output.dirClock!({ dir: dir, clock: output[clock]!() }); + if ( + (clock === 'clockwise' && + (crosses[0] === 'N' || crosses[0] === 'S')) || + (clock === 'counterclockwise' && + (crosses[0] === 'E' || crosses[0] === 'W')) + ) + return output.dirCirclesFirst!({ + dir: dir, + clock: output[clock]!(), + }); + if ( + (clock === 'clockwise' && + (crosses[0] === 'E' || crosses[0] === 'W')) || + (clock === 'counterclockwise' && + (crosses[0] === 'N' || crosses[0] === 'S')) + ) + return output.dirCrossesFirst!({ + dir: dir, + clock: output[clock]!(), + }); + return output.dirClock!({ dir: dir, clock: output[clock]!() }); + }, + outputStrings: { + eastWest: { + en: 'East/West', + cn: '左/右', + }, + northSouth: { + en: 'North/South', + cn: '上/下', + }, + dirCrossesFirst: { + en: '${dir}: Crosses First + ${clock}', + cn: '${dir}: 先十字 + ${clock}', + }, + dirCirclesFirst: { + en: '${dir}: Circles First + ${clock}', + cn: '${dir}: 先圆圈 + ${clock}', + }, + dirClock: { + en: '${dir}: ${clock}', + cn: '${dir}: ${clock}', + }, + clockwise: Outputs.clockwise, + counterclockwise: Outputs.counterclockwise, + }, }, { - id: 'Occult Crescent Berserker Scathing Sweep', - type: 'StartsUsing', - netRegex: { source: 'Crescent Berserker', id: 'A6C3', capture: false }, - response: Responses.getBehind(), + id: 'Occult Crescent Marble Dragon Towers 5 and 6', + // Ball of Ice A716 spawns the towers + // Towers are either vertical (2 columns of 3) or horizontal (2 rows of 3) + // The StartsUsing 20 log lines can be wrong, but the StartsUsingExtra 263 lines seem to be correct + // There are six Marble Dragon actors that cast Imitation Blizzard 7615 which signifies end of towers + // If StartsUsingExtra lines are wrong, may need to change to OverlayPlugin + // Horizontal: + // (-346.019, 151.006) (-337.016, 151.006) (-328.013, 151.006) + // (-346.019, 162.999) (-337.016, 162.999) (-328.013, 162.999) + // Vertical: + // (-331.004, 148.015) (-342.998, 148.015) + // (-331.004, 157.018) (-342.998, 157.018) + // (-331.004, 165.990) (-342.998, 165.990) + // Since the coords are unique between patterns, only need to check one tower's x or y coord + // TODO: Additionall call earlier with infoText? + type: 'StartsUsingExtra', + netRegex: { id: 'A716', capture: true }, + condition: (data) => { + // Only execute outside Frigid Dive Towers + return !data.marbleDragonIsFrigidDive; + }, + suppressSeconds: 1, + alertText: (data, matches, output) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + + // Check for next safe spots, reverse of the first call + const clock = data.marbleDragonTwisterClock; + const dir = clock === 'clockwise' + ? output.eastWest!() + : output.northSouth!(); + + if ((x > -332 && x < -330) || (x > -344 && x < -342)) { + if (clock !== undefined) + return output.getVerticalTowersDir!({ dir: dir }); + return output.getVerticalTowers!(); + } + + if ((y > 150 && y < 152) || (y > 162 && y < 164)) { + if (clock !== undefined) + return output.getHorizontalTowersDir!({ dir: dir }); + return output.getHorizontalTowers!(); + } + + // Unrecognized coordinates + console.error( + `Occult Crescent Marble Dragon Towers 5 and 6: Unrecognized coordinates (${x}, ${y})`, + ); + if (clock !== undefined) + return output.getTowersDir!({ text: output.getTowers!(), dir: dir }); + return output.getTowers!(); + }, + outputStrings: { + eastWest: { + en: 'East/West', + cn: '左/右', + }, + northSouth: { + en: 'North/South', + cn: '上/下', + }, + getTowers: Outputs.getTowers, + getVerticalTowers: { + en: 'Get Vertical Towers', + cn: '去竖排塔', + }, + getHorizontalTowers: { + en: 'Get Horizontal Towers', + cn: '去横排塔', + }, + getTowersDir: { + en: '${text} => ${dir}', + cn: '${text} => ${dir}', + }, + getVerticalTowersDir: { + en: 'Get Vertical Towers => ${dir}', + cn: '去竖排塔 => ${dir}', + }, + getHorizontalTowersDir: { + en: 'Get Horizontal Towers => ${dir}', + cn: '去横排塔 => ${dir}', + }, + }, }, { - id: 'Occult Crescent Hinkypunk Dread Dive', + id: 'Occult Crescent Marble Dragon Imitation Rain 6 and 7 Second Safe Spots', + // Imitation Blizzard 7615 (tower) type: 'StartsUsing', - netRegex: { source: 'Hinkypunk', id: 'A1A4', capture: true }, - response: Responses.tankBuster(), + netRegex: { source: 'Marble Dragon', id: '7615', capture: true }, + condition: (data) => { + // Only execute outside Frigid Dive Towers + return !data.marbleDragonIsFrigidDive; + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), // After tower snapshots + suppressSeconds: 1, + alertText: (data, _matches, output) => { + // Check for next safe spots, reverse of the first call + const clock = data.marbleDragonTwisterClock; + if (clock === 'clockwise') + return output.eastWest!(); + if (clock === 'counterclockwise') + return output.northSouth!(); + }, + outputStrings: { + eastWest: { + en: 'East/West', + cn: '左/右', + }, + northSouth: { + en: 'North/South', + cn: '上/下', + }, + }, }, { - id: 'Occult Crescent Hinkypunk Shades Nest', + id: 'Occult Crescent Guardian Wraith Scream', + // 10.5s castTime type: 'StartsUsing', - // TODO: Some of these are from boss, some are not. - netRegex: { source: 'Hinkypunk', id: ['A19C', 'A19D', 'A430', 'A431'], capture: true }, - suppressSeconds: 1, - response: Responses.getIn(), - run: (_data, matches) => console.log(`Shades Nest: ${matches.id}`), + netRegex: { source: 'Guardian Wraith', id: 'A7CE', capture: false }, + response: Responses.getOut(), }, { - id: 'Occult Crescent Hinkypunk Shades Crossing', - type: 'StartsUsing', - // TODO: Some of these are from boss, some are not. - netRegex: { source: 'Hinkypunk', id: ['A19F', 'A1A0', 'A432', 'A433'], capture: true }, + id: 'Occult Crescent Guardian Golem Toxic Minerals', + // Guardian Golem casts Toxic Minerals (A352), nearby players get affected by 25s Toxic Minerals (115C) + // Phantom Oracle must use Recuperation to cleanse subsequent Doom from players + // A 21s Doom is applied after the 25s Toxic Minerals effect ends + // Recuperation adds a 20s buff to players and on expiration will cleanse the Doom + // The Doom can also be cleansed with Esuna + // TODO: Filter for Phantom Oracle + // TODO: Cleanse call for Doom, but it is not yet logged, it's probably 11CE? + type: 'GainsEffect', + netRegex: { effectId: '115C', capture: true }, + condition: Conditions.targetIsYou(), + // 25s - 20s, plus some delay for buff/debuff propagation + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 20 + 0.5, suppressSeconds: 1, - response: Responses.getIntercards(), - run: (_data, matches) => console.log(`Shades Nest: ${matches.id}`), + infoText: (_data, _matches, output) => output.recuperation!(), + outputStrings: { + recuperation: { + en: 'Recuperation (if possible)', + cn: '痊愈宣告 (能用就用)', + }, + }, }, { - id: 'Occult Crescent Hinkypunk Lamplight', + id: 'Occult Crescent Guardian Bersker Raging Slice', + // Untelegraphed long line cleave that goes through walls type: 'StartsUsing', - netRegex: { source: 'Hinkypunk', id: ['A1A5', 'A310'], capture: false }, - suppressSeconds: 1, - response: Responses.aoe(), + netRegex: { source: 'Guardian Berserker', id: 'A7CF', capture: false }, + response: Responses.awayFromFront(), }, { - id: 'Occult Crescent Black Star Choco Windstorm', + id: 'Occult Crescent Guardian Knight Buster Knuckles', type: 'StartsUsing', - netRegex: { source: 'Black Star', id: 'A0BB', capture: false }, - response: Responses.getOut(), + netRegex: { source: 'Guardian Knight', id: 'A7D4', capture: false }, + response: Responses.getOutThenIn(), }, { - id: 'Occult Crescent Black Star Choco Cyclone', + id: 'Occult Crescent Guardian Knight Earthquake', + // Using Buster Knuckles (A7D5) delayed until 8.7s castTime as trigger for Earthquake (A7ED) type: 'StartsUsing', - netRegex: { source: 'Black Star', id: 'A0BC', capture: false }, + netRegex: { source: 'Guardian Knight', id: 'A7D4', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime), response: Responses.getIn(), }, { - id: 'Occult Crescent Neo Garula Squash', + id: 'Occult Crescent Guardian Knight Line of Fire', type: 'StartsUsing', - netRegex: { source: 'Neo Garula', id: 'A0E5', capture: true }, - response: Responses.tankBuster(), + netRegex: { source: 'Guardian Knight', id: 'A7D5', capture: false }, + response: Responses.awayFromFront(), }, { - id: 'Occult Crescent Lion Rampant Fearsome Glint', + id: 'Occult Crescent Guardian Weapon Whirl of Rage', type: 'StartsUsing', - netRegex: { source: 'Lion Rampant', id: 'A1C3', capture: false }, + netRegex: { source: 'Guardian Weapon', id: 'A708', capture: false }, + infoText: (_data, _matches, output) => output.outOfMelee!(), + outputStrings: { + outOfMelee: Outputs.outOfMelee, + }, + }, + { + id: 'Occult Crescent Guardian Weapon Smite of Rage', + type: 'StartsUsing', + netRegex: { source: 'Guardian Weapon', id: 'A707', capture: false }, response: Responses.awayFromFront(), }, { - id: 'Occult Crescent Death Claw Dirty Nails', + id: 'Occult Crescent Master Lockward', + // Players must not intertupt Cunning Keywork (A7E4) 5.7s cast from Master Lockward + type: 'AddedCombatant', + netRegex: { name: 'Master Lockward', capture: false }, + infoText: (_data, _matches, output) => output.spawned!(), + outputStrings: { + spawned: { + en: 'Master Lockward spawned', + cn: '首领看锁人出现', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Unsealed Aura', + // A264 Unsealed Aura cast + // 9BE7 Unsealed Aura damage type: 'StartsUsing', - netRegex: { source: 'Death Claw', id: 'A174', capture: true }, - response: Responses.tankBuster(), + netRegex: { source: 'Magitaur', id: 'A264', capture: false }, + response: Responses.aoe(), }, { - id: 'Occult Crescent Death Claw Grip of Poison', + id: 'Occult Crescent Magitaur Unseal Tank Autos Near/Far', + // A241 Attacks will go to closest players + // A242 Attacks will go to furthest players + // Boss also gains an effect and weapon the specific weapon glows + // Yellow Axe = 2 closest players + // Blue Lance = 2 furthest players + // Applies Unsealed to the boss (10F3): + // A242 applies it with count of '353' => Tanks Far, Party Close + // A241 applies it with count of '354' => Tanks Close, Party Far + type: 'Ability', + netRegex: { source: 'Magitaur', id: ['A241', 'A242'], capture: true }, + alertText: (_data, matches, output) => { + if (matches.id === 'A241') + return output.tanksNear!(); + return output.tanksFar!(); + }, + outputStrings: { + tanksFar: { + en: 'Tanks Far (Party Close) x3', + cn: '坦克远离 (人群靠近) x3', + }, + tanksNear: { + en: 'Tanks Close (Party Far) x3', + cn: '坦克靠近 (人群远离) x3', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Assassin\'s Dagger Pattern', + // A261 StartsUsingExtra lines contain different y values between patterns + // Pattern 1 (Letters in BAP Daggers) + // (672.384, -689.963) + // (727.622, -689.963) + // (700.003, -642.110) + // Pattern 2 (Numbers in BAP Daggers) + // (672.384, -658.071) + // (727.622, -658.071) + // (700.003, -705.435) + // BAP Daggers: + // See https://www.youtube.com/playlist?list=PL7RVNORIbhth-I3mFGEqRknCpSlP7EWDc youtube playlist for explainer videos + // Supposedly created by a group named "BAP", in theory a group formed during Baldesion Arsenal on Primal DC + // 1. Start on letter or number on their square for 5 hits, then dodge axeblow/lanceblow + // 2. After dodge, party waits for 1 hit and then waits on D marker until lanceblow/axeblow cast + type: 'StartsUsingExtra', + netRegex: { id: 'A261', capture: true }, + suppressSeconds: 1, // There are three daggers, only capture one + infoText: (data, matches, output) => { + // Only need to examine one dagger + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + + // Pattern 1 + if ((y > -691 && y < -688) || (y > -643 && y < -640)) { + if (data.triggerSetConfig.magitaurDaggers === 'bap') + return output.startOnLetters!(); + return output.pattern1!(); + } + + // Pattern 2 + if ((y > -660 && y < -657) || (y > -707 && y < -704)) { + if (data.triggerSetConfig.magitaurDaggers === 'bap') + return output.startOnNumbers!(); + return output.pattern2!(); + } + + // Log error for unrecognized coordinates + console.error( + `Occult Crescent Magitaur Assassin\'s Dagger Pattern: Unrecognized coordinates (${x}, ${y})`, + ); + }, + tts: (data, matches, output) => { + const y = parseFloat(matches.y); + + // Pattern 1 + if ((y > -691 && y < -688) || (y > -643 && y < -640)) { + if (data.triggerSetConfig.magitaurDaggers === 'none') + return output.pattern1TtsText!(); + } + }, + outputStrings: { + startOnLetters: { + en: 'Start on Letters', + cn: '字母点开始', + }, + startOnNumbers: { + en: 'Start on Numbers', + cn: '数字点开始', + }, + pattern1: { + en: '⅄ Daggers', // Displays an upside down Y + cn: '⅄ 形短剑', + }, + pattern1TtsText: { + en: 'Flipped Y Daggers', + cn: '倒 Y 形短剑', + }, + pattern2: { + en: 'Y Daggers', + cn: 'Y 形短剑', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Critical Axeblow/Lanceblow Counter', + // For tracking which part in the encounter the cast is + // 1 = Assassin's Dagger Cast + // 2 = Assassin's Dagger Opposite Cast + // 3 = Sage's Blow Cast + // 4 = Sage's Blow Opposite Cast + // 5 = Rune Axe Lanceblow + // 6 = Rune Axe Axeblow + // 7 = Assassin's Dagger Lanceblow + // 8 = Assassin's Dagger Axeblow + // 9 = Holy Lance Lanceblow + // 10 = Holy Lance Axeblow + // 11 = Assassin's Dagger Lanceblow + // 12 = Assassin's Dagger Axeblow type: 'StartsUsing', - netRegex: { source: 'Death Claw', id: 'A175', capture: false }, - response: Responses.bleedAoe(), + netRegex: { source: 'Magitaur', id: ['A247', 'A24B'], capture: false }, + run: (data) => { + data.magitaurCriticalBlowCount = data.magitaurCriticalBlowCount + 1; + }, }, { - id: 'Occult Crescent Death Claw Vertical Crosshatch', + id: 'Occult Crescent Magitaur Critical Axeblow/Lanceblow', + // Do not trigger for the Lanceblow during Rune Axe or during Holy Lance type: 'StartsUsing', - netRegex: { source: 'Death Claw', id: ['A16B', 'A172'], capture: false }, - response: Responses.getSidesThenFrontBack('alert'), + netRegex: { source: 'Magitaur', id: ['A247', 'A24B'], capture: true }, + condition: (data) => { + return data.magitaurCriticalBlowCount !== 5 && data.magitaurCriticalBlowCount !== 9; + }, + alertText: (_data, matches, output) => { + if (matches.id === 'A247') + return output.out!(); + return output.in!(); + }, + outputStrings: magitaurOutputStrings, }, { - id: 'Occult Crescent Death Claw Horizontal Crosshatch', + id: 'Occult Crescent Magitaur Forked Fury', + // Hits 3 nearest and 3 furthest players with tankbuster + // TODO: Determine close/far autos from boss buff? type: 'StartsUsing', - netRegex: { source: 'Death Claw', id: ['A16C', 'A173'], capture: false }, - response: Responses.getFrontBackThenSides('alert'), + netRegex: { source: 'Magitaur', id: 'A265', capture: false }, + alertText: (data, _matches, output) => { + if (data.role === 'tank') + return output.nearFarTankCleave!(); + return output.avoidCleave!(); + }, + outputStrings: { + avoidCleave: { + en: 'Be on boss hitbox (avoid tank cleaves)', + de: 'Geh auf den Kreis vom Boss (vermeide Tank Cleaves)', + fr: 'Sur la hitbox (évitez les tanks cleaves)', + ja: 'ボス背面のサークル上に', + cn: '站在目标圈上 (远离坦克死刑)', + ko: '보스 히트박스 경계에 있기 (광역 탱버 피하기)', + }, + nearFarTankCleave: { + en: 'Near and far tank cleave => 2 tank autos', + cn: '近远坦克死刑 => 2次坦克普攻', + }, + }, }, { - id: 'Occult Crescent Repaired Lion Holy Blaze', + id: 'Occult Crescent Magitaur Aura Burst / Holy Canisters', + // A25A Aura Burst (Yellow) cast or A25B Holy (Blue) cast + // Tell for which canisters to focus type: 'StartsUsing', - netRegex: { source: 'Repaired Lion', id: 'A151', capture: false }, - response: Responses.awayFromFront(), + netRegex: { source: 'Magitaur', id: ['A25A', 'A25B'], capture: true }, + infoText: (_data, matches, output) => { + if (matches.id === 'A25A') + return output.yellowCanisters!(); + return output.blueCanisters!(); + }, + outputStrings: { + blueCanisters: { + en: 'Attack Blue Canisters (Lance)', + cn: '攻击蓝色罐子 (枪)', + }, + yellowCanisters: { + en: 'Attack Yellow Canisters (Axe)', + cn: '攻击黄色罐子 (斧)', + }, + }, }, { - id: 'Occult Crescent Repaired Lion Scratch', + id: 'Occult Crescent Magitaur Aura Burst / Holy', + // This is a long 18.7s cast + 1s damage + // A25A Aura Burst (Yellow) cast or A25B Holy (Blue) cast + // 9BE5 Aura Burst damage or 9BE6 Holy damage type: 'StartsUsing', - netRegex: { source: 'Repaired Lion', id: 'A155', capture: true }, - response: Responses.tankBuster(), + netRegex: { source: 'Magitaur', id: ['A25A', 'A25B'], capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 5, + response: Responses.aoe(), }, { - id: 'Occult Crescent Nymian Petalodus Hydrocleave', + id: 'Occult Crescent Magitaur Sage\'s Staff', + // Boss spawns three staves that will fire an untelegraphed line at nearest target + // A25F Mana Expulsion is the untelegraphed line stack damage 14.4s after + // There is an In/Out dodge before Mana Expulsion + // These can be focused into a single stack, but some parties split into groups + type: 'Ability', + netRegex: { source: 'Magitaur', id: 'A25E', capture: false }, + delaySeconds: 8.5, + suppressSeconds: 1, + alertText: (_data, _matches, output) => output.lineStackStaff!(), + outputStrings: { + lineStackStaff: { + en: 'Line stack at staff', + cn: '直线分摊法杖伤害', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Rune Axe Debuffs', + // Applied 1s after Rune Axe (A24F) cast and 1s before first headmarkers + // Prey: Greater Axebit (10F1) 9s + // Prey: Lesser Axebit (10F0) 13s + // Prey: Greater Axebit (10F1) 21s + // Prey: Lesser Axebit (10F0) 21s + // TODO: Fires multiple times for players with more than one debuff + type: 'GainsEffect', + netRegex: { effectId: ['10F0', '10F1'], capture: true }, + condition: Conditions.targetIsYou(), + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = magitaurOutputStrings; + + const duration = parseFloat(matches.duration); + if (duration < 15) { + if (matches.effectId === '10F1') { + data.magitaurRuneAxeDebuff = 'big1'; + return { alarmText: output.rune1BigAoeOnYou!() }; + } + data.magitaurRuneAxeDebuff = 'small1'; + return { infoText: output.rune1SmallAoeOnYou!() }; + } + + if (matches.effectId === '10F1') { + data.magitaurRuneAxeDebuff = 'big2'; + return { infoText: output.rune2BigAoeOnYouLater!() }; + } + data.magitaurRuneAxeDebuff = 'small2'; + return { infoText: output.rune2SmallAoeOnYouLater!() }; + }, + }, + { + id: 'Occult Crescent Magitaur Ruinous Rune Counter', + // 1: Big Ruinous Rune + // 2: Small Ruinous Rune x3 + // 3: Big Ruinous Rune, Small Ruinous Rune x2 + // 4: This happens on #2 ability to prevent Lanceblow reminder from retriggering + // 5: Happens in Ruinous Rune 2 Reminder prevent future Critical Lanceblows from retriggering + type: 'HeadMarker', + netRegex: { + id: [headMarkerData.magitaurBigRuinousRune, headMarkerData.magitaurSmallRuinousRune], + capture: false, + }, + suppressSeconds: 1, + run: (data) => { + data.magitaurRuinousRuneCount = data.magitaurRuinousRuneCount + 1; + }, + }, + { + id: 'Occult Crescent Magitaur Big Ruinous Rune 1 Target', + // This can be placed N, SE, or SW at the wall by Universal Cylinders (purple circles) + // Explosion must avoid square tiles + // Earlier, players were given debuff at end of Rune Axe (A24F) cast + // Prey: Greater Axebit (10F1) 9s + type: 'HeadMarker', + netRegex: { id: [headMarkerData.magitaurBigRuinousRune], capture: true }, + condition: (data) => { + // Don't trigger for players with debuff as they received trigger 1s prior + if ( + data.magitaurRuinousRuneCount === 1 && + data.magitaurRuneAxeDebuff === undefined + ) + return true; + return false; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = magitaurOutputStrings; + const target = matches.target; + + return { + infoText: output.rune1BigAoeOnPlayer!({ + player: data.party.member(target), + }), + }; + }, + }, + { + id: 'Occult Crescent Magitaur Small Ruinous Rune 1 Targets', + // These must be placed on separate squares + // Players are also given a debuff: + // Prey: Lesser Axebit (10F0) 13s + type: 'HeadMarker', + netRegex: { id: [headMarkerData.magitaurSmallRuinousRune], capture: true }, + condition: (data) => { + return data.magitaurRuinousRuneCount === 2; + }, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = magitaurOutputStrings; + data.magitaurRuneTargets.push(matches.target); + if (data.magitaurRuneTargets.length < 3) + return; + + // Don't repeat for small aoe players or call for players with debuffs + if (data.magitaurRuneAxeDebuff !== undefined) + return; + + const target1 = data.magitaurRuneTargets[0]; + const target2 = data.magitaurRuneTargets[1]; + const target3 = data.magitaurRuneTargets[2]; + + return { + infoText: output.rune1SmallAoesOnPlayers!({ + player1: data.party.member(target1), + player2: data.party.member(target2), + player3: data.party.member(target3), + }), + }; + }, + }, + { + id: 'Occult Crescent Magitaur Rune Axe Lanceblow', + // Trigger once the big Ruinous Rune (A251) has gone off + // Players with first set of small Ruinous Runes (A250) stay on square + // Rest of players must get off + // The A251 aoe occurs with a Lanceblow almost immediately after, so pre-call that + // NOTE: This is for magitaurCriticalBlowCount === 5 + type: 'Ability', + netRegex: { source: 'Magitaur', id: 'A251', capture: false }, + condition: (data) => { + // Only execute on the first Big Ruinous Rune ability + return data.magitaurRuinousRuneCount === 2; + }, + suppressSeconds: 1, // In case of aoes hitting other players + alertText: (data, _matches, output) => { + const target1 = data.magitaurRuneTargets[0]; + const target2 = data.magitaurRuneTargets[1]; + const target3 = data.magitaurRuneTargets[2]; + + if (data.me === target1 || data.me === target2 || data.me === target3) + return output.rune1SmallAoEStayThenIn!(); + return output.in!(); + }, + outputStrings: magitaurOutputStrings, + }, + { + id: 'Occult Crescent Magitaur Ruinous Rune 2 Targets', + // Second set has a big and two smalls resolve simultaneously + // These markers come out about 0.1~0.3s before set one smalls expire + // There is some trigger overlap to handle for unlucky players who get both sets + // Big resolves like usual + // Players with small will be on their own square with party on 3rd square + // Players are also given a debuff: + // Prey: Greater Axebit (10F1) 21s + // Prey: Lesser Axebit (10F0) 21s + type: 'HeadMarker', + netRegex: { + id: [headMarkerData.magitaurBigRuinousRune, headMarkerData.magitaurSmallRuinousRune], + capture: true, + }, + condition: (data) => { + // Big Ruinous Rune = 1, 3x Small Ruinous Runes = 2 + return data.magitaurRuinousRuneCount === 3; + }, + preRun: (data, matches) => { + if (matches.id === headMarkerData.magitaurBigRuinousRune) + data.magitaurBigRune2Target = matches.target; + else if (matches.id === headMarkerData.magitaurSmallRuinousRune) + data.magitaurRune2Targets.push(matches.target); + }, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = magitaurOutputStrings; + if (data.magitaurBigRune2Target === undefined || data.magitaurRune2Targets.length < 2) + return; + + // Lanceblow call happens here for the player with small aoe from round 1 + // Do not output for them to avoid duplicate + const rune1Small1 = data.magitaurRuneTargets[0]; + const rune1Small2 = data.magitaurRuneTargets[1]; + const rune1Small3 = data.magitaurRuneTargets[2]; + if ( + data.me === rune1Small1 || + data.me === rune1Small2 || + data.me === rune1Small3 + ) + return; + + const big = data.magitaurBigRune2Target; + const small1 = data.magitaurRune2Targets[0]; + const small2 = data.magitaurRune2Targets[1]; + + // These three players receive alert trigger in ~3s with the info + if (data.me === big || data.me === small1 || data.me === small2) + return; + + return { + infoText: output.rune2AoesOnPlayers!({ + player1: data.party.member(big), + player2: data.party.member(small1), + player3: data.party.member(small2), + }), + }; + }, + }, + { + id: 'Occult Crescent Magitaur Small Ruinous Rune Lanceblow Reminder', + // Trigger on Small Ruinous Rune (A250) aoe + // Players have ~2.1s to move based on damage cast timing of Critical Lanceblow + // NOTE: This occurs for magitaurCriticalBlowCount === 5 + type: 'Ability', + netRegex: { source: 'Magitaur', id: 'A250', capture: true }, + condition: (data, matches) => { + // This could be altered to not call for players without markers, but + // calling for player that got hit with the aoe could also save a life + if (matches.target === data.me && data.magitaurRuinousRuneCount === 3) + return true; + + // Players that get hit and are not targeted do not get an output + return false; + }, + alertText: (data, _matches, output) => { + // Check if player has a marker again + const big = data.magitaurBigRune2Target; + const small1 = data.magitaurRune2Targets[0]; + const small2 = data.magitaurRune2Targets[1]; + if (data.me === big) + return output.rune2InBigAoeOnYou!(); + if (data.me === small1 || data.me === small2) + return output.rune2InSmallAoeOnYou!(); + return output.inThenOnSquare!(); + }, + outputStrings: magitaurOutputStrings, + }, + { + id: 'Occult Crescent Magitaur Small Ruinous Rune 1 Ability Tracker', + // Trigger on Small Ruinous Rune (A250) aoe + // Prevents trigger of Lanceblow Reminder on second set + type: 'Ability', + netRegex: { source: 'Magitaur', id: 'A250', capture: false }, + condition: (data) => { + return data.magitaurRuinousRuneCount === 3; + }, + delaySeconds: 1, // Delay time for first set of small Ruinous Runes aoes to propogate + suppressSeconds: 1, + run: (data) => { + data.magitaurRuinousRuneCount = 4; + }, + }, + { + id: 'Occult Crescent Magitaur Ruinous Rune 2 Reminder', + // Capture either alliance's Critical Lanceblow damage cast + // Using castTime of A24B is unreliable since damage cast comes later + type: 'Ability', + netRegex: { source: 'Magitaur', id: ['A24E', 'A24D'], capture: false }, + condition: (data) => { + return data.magitaurRuinousRuneCount === 4; + }, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + const big = data.magitaurBigRune2Target; + const small1 = data.magitaurRune2Targets[0]; + const small2 = data.magitaurRune2Targets[1]; + + if (data.me === big) + return output.rune2BigAoeOnYouReminder!(); + if (data.me === small1 || data.me === small2) + return output.rune2SmallAoeOnYouReminder!(); + + return output.rune2AvoidPlayers!({ + player1: data.party.member(small1), + player2: data.party.member(small2), + }); + }, + run: (data) => { + // Prevent trigger from firing after + data.magitaurRuinousRuneCount = 5; + }, + outputStrings: magitaurOutputStrings, + }, + { + id: 'Occult Crescent Magitaur Lancepoint Debuffs Initial', + // Prey: Lancepoint (10F2) is applied ~1s after Holy Lance (A255) + // Comes up to three players in a set marked with these durations: 33s, 25s, and 17s + // Presumably these would have gone out 1 of each time to each square if players pre-positioned + // Can be buggy and have a refresh log + // This might not be solvable without knowing the player's square as + // to if they should be told to stand in middle of their square/avoid overlap + type: 'GainsEffect', + netRegex: { effectId: '10F2', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: (_data, matches) => parseFloat(matches.duration), + suppressSeconds: 34, // Duration of the debuffs +1s + infoText: (_data, matches, output) => { + const duration = parseFloat(matches.duration); + if (duration < 18) + return output.shortStackOnYou!(); + if (duration < 26) + return output.mediumStackOnYou!(); + return output.longStackOnYou!(); + }, + outputStrings: { + shortStackOnYou: { + en: 'Short Stack on YOU (17)', + cn: '短分摊点名 (17秒)', + }, + mediumStackOnYou: { + en: 'Medium Stack on YOU (25)', + cn: '中分摊点名 (25秒)', + }, + longStackOnYou: { + en: 'Long Stack on YOU (33)', + cn: '长分摊点名 (33秒)', + }, + }, + }, + { + id: 'Occult Crescent Magitaur Lancelight On/Off Square', + // Tracking A256 which seems to be related to the Lance aninmations when + // Lancelight A258 or A259 goes off + // TODO: Get player position for an alertText and filter? + // Players can manually blank the outputString for the other squares in configuration + // Holy IV targets need to avoid overlapping outside square if it isn't their turn to go out + type: 'Ability', + netRegex: { source: 'Luminous Lance', id: 'A256', capture: false }, + suppressSeconds: 1, + response: (data, _matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = magitaurOutputStrings; + data.magitaurLancelightCount = data.magitaurLancelightCount + 1; + switch (data.magitaurLancelightCount) { + case 1: // ~13s after debuffs + return { infoText: output.northeastOff!() }; + case 4: // ~19s after debuffs (stack 1 goes off ~2s prior) + return { infoText: output.northeastOn!() }; + case 5: // ~21s after debuffs + return { infoText: output.southOff!() }; + case 8: // ~27s after debuffs, (stack 2 goes off ~2s prior) + return { infoText: output.southOn!() }; + case 9: // ~29s after debuffs + return { infoText: output.northwestOff!() }; + case 12: // ~35s after debuffs (stack 3 goes off ~2s prior) + return { alertText: output.out!() }; + } + }, + }, + { + id: 'Occult Crescent Magitaur Holy Lance Critical Lanceblow', + // TODO: Merge Lanceblow Stack trigger here? + // Stack headmarkers come out at this time + // Related debuff for headmarkers is Prey: Lancepoint (10F2) type: 'StartsUsing', - netRegex: { source: 'Nymian Petalodus', id: 'A88D', capture: false }, - response: Responses.awayFromFront(), + netRegex: { source: 'Magitaur', id: 'A24B', capture: false }, + condition: (data) => { + return data.magitaurCriticalBlowCount === 9; + }, + alertText: (_data, _matches, output) => output.in!(), + outputStrings: magitaurOutputStrings, }, ], timelineReplace: [ @@ -191,10 +4751,23 @@ const triggerSet: TriggerSet = { 'locale': 'en', 'replaceText': { 'Vertical Crosshatch/Horizontal Crosshatch': 'Vertical/Horizontal Crosshatch', + 'Twopenny Inflation / Onepenny Inflation / Fourpenny Inflation': + 'Penny Inflation (knockback)', + 'Shades\' Nest/Shade\'s Crossing': 'Shades\' Nest/Crossing', + 'Shades\' Crossing/Shades\' Nest': 'Shades\' Crossing/Nest', + 'Ray of Dangers Near / Ray of Expulsion Afar': 'Ray Near/Far', + 'Demonograph of Dangers Near / Demonograph of Expulsion Afar': 'Deomograph Near/Far', + 'Rotate Right / Rotate Left': 'Rotate Left/Right', + 'Cometeor of Dangers Near / Cometeor of Expulsion Afar': 'Cometeor Near/Far', + 'Gravity of Dangers Near / Gravity of Expulsion Afar': 'Gravity Near/Far', + 'Close Call to Detonate / Far Cry to Detonate': 'Close/Far to Detonate', + 'Critical Axeblow / Critical Lanceblow': 'Critical Axe/Lanceblow', + 'Critical Lanceblow / Critical Axeblow': 'CriticalLanceblow/Axeblow', }, }, { 'locale': 'de', + 'missingTranslations': true, 'replaceSync': { 'Ball of Fire': 'Feuerball', 'Black Star': 'Schwarz(?:e|er|es|en) Stern', @@ -372,66 +4945,377 @@ const triggerSet: TriggerSet = { { 'locale': 'cn', 'replaceSync': { + 'Assassin\'s Dagger': '暗杀短剑', 'Ball of Fire': '火球', + 'Black Chocobo': '黑陆行鸟', 'Black Star': '黑色天星', + 'Chatterbird': '叽喳鸟', 'Clawmarks': '抓痕', 'Cloister Demon': '回廊恶魔', + 'Command Urn': '指令罐', 'Crescent Berserker': '新月狂战士', 'Crystal Dragon': '水晶龙', + 'Dead Stars': '星头三兄弟', 'Death Claw': '死亡爪', + 'Demon Tablet': '恶魔板', 'Draconic Double': '水晶龙的幻影', + 'Frozen Phobos': '冰冻的福博斯', + 'Gaseous Nereid': '燃烧的涅瑞伊得', + 'Gaseous Phobos': '燃烧的福博斯', + 'Guardian Berserker': '狂战士守卫', + 'Guardian Knight': '骑士守卫', + 'Guardian Weapon': '兵装守卫', + 'Guardian Wraith': '幽灵守卫', 'Hinkypunk': '鬼火苗', + 'Holy Sphere': '光球', + 'Ice Golem': '寒冰巨像', + 'Icewind': '冰风', + 'Jesting Jackanapes': '小妖魔', 'Lion Rampant': '跃立狮', + 'Liquified Triton': '融化的特里同', + 'Luminous Lance': '光枪', + 'Magitaur': '魔陶洛斯', + 'Marble Dragon': '大理石龙', + 'Master Lockward': '首领看锁人', + 'Megaloknight': '巨型骑士', + 'Mysterious Mindflayer': '夺心魔', + 'Mythic Idol': '神秘土偶', + 'Mythic Mirror': '神秘土偶的幻影', 'Neo Garula': '进化加鲁拉', + 'Nereid': '涅瑞伊得', 'Nymian Petalodus': '尼姆瓣齿鲨', + 'Occult Knight': '新月骑士', + 'Ochre Stone': '巨岩', + 'Petalodus Progeny': '子代瓣齿鲨', 'Phantom Claw': '死亡爪的幻影', + '(? Shades' Nest => Boss Shades' Nest => Shades' Crossing => Blowout => Boss Shades' Crossing +# Blowout => Shades' Nest => Boss Shades' Nest => Shades' Crossing => Blowout => Boss Shades' Nest +# Shades' Crossing => Blowout => Boss Shades' Nest => Blowout => Shades' Nest => Boss Shades' Nest +# Shades' Crossing => Blowout => Boss Shades' Nest => Blowout => Shades' Nest => Boss Shades' Crossing +# Shades' Crossing => Blowout => Boss Shades' Nest => Shades' Nest => Shades' Crossing => Boss Shades' Nest +# Shades' Crossing => Blowout => Boss Shades' Nest => Shades' Nest => Shades' Crossing => Boss Shades' Crossing +# Shades' Nest => Shades' Crossing => Boss Shades' Nest => Shades' Crossing => Blowout => Boss Shades' Crossing + +16514.3 "--sync--" StartsUsing { id: "A1A3", source: "Hinkypunk" } jump "oc-flame-of-dusk-blowout1" +16514.3 "--sync--" StartsUsing { id: "A19E", source: "Hinkypunk" } jump "oc-flame-of-dusk-nest1" +16514.3 "--sync--" StartsUsing { id: "A1A1", source: "Hinkypunk" } jump "oc-flame-of-dusk-crossing1" +16516.8 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } +16519.6 "--Tell 2--" #Ability { id: ["A19E", "A1A1", "A1A3"], source: "Hinkypunk" } +16522.4 "--Tell 3--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } +16525.2 "--Tell 4--" #Ability { id: ["A19E", "A1A1", "A1A3"], source: "Hinkypunk" } +16526.3 "Molt" #Ability { id: "A199", source: "Hinkypunk" } +16527.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +16531.1 "Blowout/Shades' Nest/Shades' Crossing?" #Ability { id: ["A1A2", "A19D", "A1A0"], source: "Hinkypunk" } duration 0.9 +16532.0 "--knockback?--" #Ability { id: "A1B5", source: "Hinkypunk" } +16534.2 "Shades' Nest/Shades' Crossing/Blowout?" #Ability { id: ["A19D", "A1A0", "A1A2"], source: "Hinkypunk" } duration 0.9 +16537.2 "Shades' Nest (middle)" #Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 + +# Blowout First +# TODO: Determine if this only leads to a future Shades' Crossing => Blowout +16614.3 label "oc-flame-of-dusk-blowout1" +16616.8 "--Tell 1 (Blowout)--" Ability { id: "A1A3", source: "Hinkypunk" } +16619.6 "--Tell 2 (Shades' Nest)--" Ability { id: "A19E", source: "Hinkypunk" } +16622.4 "--Tell 3 (Shades' Crossing)--" Ability { id: "A1A1", source: "Hinkypunk" } +16625.2 "--Tell 4 (Blowout)--" Ability { id: "A1A3", source: "Hinkypunk" } +16626.3 "Molt" Ability { id: "A199", source: "Hinkypunk" } +16627.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +16631.1 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +16632.0 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +16634.2 "Shades' Nest" Ability { id: "A19D", source: "Hinkypunk" } duration 0.9 +16637.2 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +16640.2 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +16644.0 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +16645.6 "--sync--" StartsUsing { id: "A19F", source: "Hinkypunk" } jump "oc-flame-of-dusk-blowout-crossing" +16645.6 "--sync--" StartsUsing { id: "A19C", source: "Hinkypunk" } jump "oc-flame-of-dusk-blowout-nest" +16647.1 "Blowout" #Ability { id: "A1A2", source: "Hinkypunk" } +16648.0 "--knockback--" #Ability { id: "A1B5", source: "Hinkypunk" } +16648.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +16650.6 "Shades' Crossing (Intercards)/Shades' Nest (middle)?" #Ability { id: ["A19F", "A19C"], source: "Hinkypunk" } duration 0.9 +16659.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 + +# Shades' Nest First +# TODO: Determine if this only leads to a future Shades' Crossing => Blowout +# NOTE: This is assuming there could be a possible Shades' Nest from Boss, but no log seen of it yet +16814.3 label "oc-flame-of-dusk-nest1" +16816.8 "--Tell 1 (Shades' Nest)--" Ability { id: "A19E", source: "Hinkypunk" } +16819.6 "--Tell 2 (Shades' Crossing)--" Ability { id: "A1A1", source: "Hinkypunk" } +16822.4 "--Tell 3 (Shades' Crossing)--" Ability { id: "A1A1", source: "Hinkypunk" } +16825.2 "--Tell 4 (Blowout)--" Ability { id: "A1A3", source: "Hinkypunk" } +16826.3 "Molt" Ability { id: "A199", source: "Hinkypunk" } +16827.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +16831.1 "Shades' Nest" Ability { id: "A19D", source: "Hinkypunk" } +16834.2 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +16837.2 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +16840.2 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +16844.0 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } +16845.6 "--sync--" StartsUsing { id: "A19F", source: "Hinkypunk" } jump "oc-flame-of-dusk-blowout-crossing" +16845.6 "--sync--" StartsUsing { id: "A19C", source: "Hinkypunk" } jump "oc-flame-of-dusk-blowout-nest" +16847.1 "Blowout" #Ability { id: "A1A2", source: "Hinkypunk" } +16848.0 "--knockback--" #Ability { id: "A1B5", source: "Hinkypunk" } +16848.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +16850.6 "Shades' Crossing (Intercards)/Shades' Nest (middle)?" #Ability { id: ["A19F", "A19C"], source: "Hinkypunk" } duration 0.9 +16859.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 + +# Shades' Crossing First +# TODO: Determine if this only leads to a future Blowout => Shades' Nest or Shades' Nest => Shades' Crossing +17014.3 label "oc-flame-of-dusk-crossing1" +17016.8 "--Tell 1 (Shades' Crossing)--" Ability { id: "A1A1", source: "Hinkypunk" } +17019.6 "--Tell 2 (Blowout)--" Ability { id: "A1A3", source: "Hinkypunk" } + +17019.9 "--sync--" StartsUsing { id: "A1A3", source: "Hinkypunk" } jump "oc-flame-of-dusk-crossing1-blowout" +17019.9 "--sync--" StartsUsing { id: "A19E", source: "Hinkypunk" } jump "oc-flame-of-dusk-crossing1-nest" +17022.4 "--Tell 3--" #Ability { id: ["A1A3", "A19E"], source: "Hinkypunk" } +17025.2 "--Tell 4--" #Ability { id: ["A19E", "A1A1"], source: "Hinkypunk" } +17026.3 "Molt" #Ability { id: "A199", source: "Hinkypunk" } +17027.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17031.1 "Shades' Crossing" #Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17034.2 "Blowout" #Ability { id: "A1A2", source: "Hinkypunk" } +17035.1 "--knockback--" #Ability { id: "A1B5", source: "Hinkypunk" } +17037.2 "Shades' Nest (middle)" #Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17040.2 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17044.0 "Blowout/Shades' Nest?" #Ability { id: ["A1A2", "A19D"], source: "Hinkypunk" } duration 0.9 +17044.9 "--knockback?--" #Ability { id: "A1B5", source: "Hinkypunk" } +17047.1 "Shades' Nest/Shades' Crossing?" #Ability { id: ["A19D", "A1A0"], source: "Hinkypunk" } duration 0.9 +17048.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } + +# Shades' Crossing => Blowout First to Blowout => Shades' Nest +17119.9 label "oc-flame-of-dusk-crossing1-blowout" +17122.4 "--Tell 3 (Blowout)--" Ability { id: "A1A3", source: "Hinkypunk" } +17125.2 "--Tell 4 (Shades' Nest)--" Ability { id: "A19E", source: "Hinkypunk" } +17126.3 "Molt" Ability { id: "A199", source: "Hinkypunk" } +17127.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17131.1 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17134.2 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +17135.1 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +17137.2 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17140.2 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17144.0 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +17144.9 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +17145.6 "--sync--" StartsUsing { id: "A19F", source: "Hinkypunk" } jump "oc-flame-of-dusk-nest-crossing" +17145.6 "--sync--" StartsUsing { id: "A19C", source: "Hinkypunk" } jump "oc-flame-of-dusk-nest-nest" +17147.1 "Shades' Nest" #Ability { id: "A19D", source: "Hinkypunk" } duration 0.9 +17148.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17150.6 "Shades' Crossing (Intercards)/Shades' Nest (middle)?" #Ability { id: ["A19F", "A19C"], source: "Hinkypunk" } duration 0.9 +17159.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 + +# Shades' Crossing => Blowout First to Shades' Nest => Shades' Crossing +17219.9 label "oc-flame-of-dusk-crossing1-nest" +17222.4 "--Tell 3 (Shades' Nest)--" Ability { id: "A19E", source: "Hinkypunk" } +17225.2 "--Tell 4 (Shades' Crossing)--" Ability { id: "A1A1", source: "Hinkypunk" } +17226.3 "Molt" Ability { id: "A199", source: "Hinkypunk" } +17227.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17231.1 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17234.2 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +17235.1 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +17237.2 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17240.2 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17244.0 "Shades' Nest" Ability { id: "A19D", source: "Hinkypunk" } duration 0.9 +17245.6 "--sync--" StartsUsing { id: "A19F", source: "Hinkypunk" } jump "oc-flame-of-dusk-crossing-crossing" +17245.6 "--sync--" StartsUsing { id: "A19C", source: "Hinkypunk" } jump "oc-flame-of-dusk-crossing-nest" +17247.1 "Shades' Crossing" #Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17248.3 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17250.6 "Shades' Crossing (Intercards)/Shades' Nest (middle)?" #Ability { id: ["A19F", "A19C"], source: "Hinkypunk" } duration 0.9 +17259.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 + +# Branch from Blowout to Shades' Nest +17345.6 label "oc-flame-of-dusk-blowout-nest" +17347.1 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +17348.0 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +17348.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17350.6 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17354.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17359.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17367.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17376.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17388.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17389.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17397.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Branch from Shades' Nest to Shades' Nest +17445.6 label "oc-flame-of-dusk-nest-nest" +17447.1 "Shades' Nest" Ability { id: "A19D", source: "Hinkypunk" } duration 0.9 +17448.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17450.6 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17454.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17459.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17467.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17476.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17488.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17489.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17497.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Branch from Shades' Crossing to Shades' Nest +17545.6 label "oc-flame-of-dusk-crossing-nest" +17547.1 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17548.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17550.6 "Shades' Nest (middle)" Ability { id: "A19C", source: "Hinkypunk" } duration 0.9 +17554.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17559.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17567.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17576.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17588.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17589.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17597.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Branch from Blowout to Shades' Crossing +17645.6 label "oc-flame-of-dusk-blowout-crossing" +17647.1 "Blowout" Ability { id: "A1A2", source: "Hinkypunk" } +17648.0 "--knockback--" Ability { id: "A1B5", source: "Hinkypunk" } +17648.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17650.6 "Shades' Crossing (Intercards)" Ability { id: "A19F", source: "Hinkypunk" } duration 0.9 +17654.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17659.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17667.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17676.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17688.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17689.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17697.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Branch from Shades' Nest to Shades' Crossing +17745.6 label "oc-flame-of-dusk-nest-crossing" +17747.1 "Shades' Nest" Ability { id: "A19D", source: "Hinkypunk" } duration 0.9 +17748.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17750.6 "Shades' Crossing (Intercards)" Ability { id: "A19F", source: "Hinkypunk" } duration 0.9 +17754.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17759.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17767.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17776.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17788.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17789.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17797.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Branch from Shades' Crossing to Shades' Crossing +17845.6 label "oc-flame-of-dusk-crossing-crossing" +17847.1 "Shades' Crossing" Ability { id: "A1A0", source: "Hinkypunk" } duration 0.9 +17848.3 "--sync--" Ability { id: "A19B", source: "Hinkypunk" } +17850.6 "Shades' Crossing (Intercards)" Ability { id: "A19F", source: "Hinkypunk" } duration 0.9 +17854.7 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } jump "oc-flame-of-dusk-end" +17859.7 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17867.9 "Lamplight" #Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17876.2 "Dread Dive" #Ability { id: "A1A4", source: "Hinkypunk" } +17888.5 "Flock of Souls" #Ability { id: "A19A", source: "Hinkypunk" } +17889.5 "--sync--" #Ability { id: "A19B", source: "Hinkypunk" } +17897.6 "--Tell 1--" #Ability { id: ["A1A3", "A19E", "A1A1"], source: "Hinkypunk" } + +# Loop +17954.7 label "oc-flame-of-dusk-end" +17959.7 "Lamplight" Ability { id: "A1A5", source: "Hinkypunk" } duration 0.9 +17962.9 "--sync--" StartsUsing { id: "A1A5", source: "Hinkypunk" } window 5,5 forcejump "oc-flame-of-dusk-loop" + +### The Forked Tower: Blood +#19000.0 "--sync--" ActorControl { command: "80000014", data0: "33B" } window 100000,0 + +### Demon Tablet +# -ii A30A A309 A3AF A303 A304 A308 A2FE A2E6 A2E7 A91D A91E AB12 A2FD A2F2 A81A A353 +# -p A306:20009.1 +# -it "Demon Tablet" "Summoned Arch Demon" "Summoned Demon" +# IGNORED ABILITIES +# A2F2 Unmitigated Explosion (2.1s after Explosion if mechanic failed) +# A81A Unmitigated Explosion (2.1s after Explosion if mechanic failed) +# A2E6 Portentous Cometeor +# A2E7 Portentous Comet +# A2FD Lacunate Stream +# A2FE Lacunate Stream +# A303 Rotation +# A304 Rotation +# A308 Occult Chisel +# A30A Auto-attack from boss +# A309 Auto-attack from boss (non-damaging) +# A353 Auto-attack from Summoned Arch Demon +# A3AF Demonic Dark II +# AB12 Landing +# A91D Landing player hit +# A91E Landing +# +# ALL ENCOUNTER ABILITIES +# A2E4 Cometeor of Dangers Near +# A2E8 Portentous Comet +# A2E9 Summon +# A2EA Gravity of Dangers Near +# A2EB Erase Gravity +# A2EC Restore Gravity +# A2ED Landing +# A2EE Demonography +# A2EF Explosion (Ground Tower Soak Damage) +# A2F1 Explosion (Floating Tower Soak Damage) +# A2F3 Ray of Dangers Near +# A2F5 Ray of Ignorance +# A2F6 Demonograph of Dangers Near +# A2F8 Explosion (Tower Soak Damage) +# A300 Lacunate Stream +# A301 Rotate Right +# A302 Rotate Left +# A305 Rotation +# A306 Demonic Dark II +# A307 Occult Chisel +# A30D Summon +# A354 Landing +# A30B End of History (Enrage cast) +# A355 End of History (Enrage damage) +# A30C End of History (Post-Enrage cast) +# The Palaeography Room will be sealed off +20000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1450" } window 100000,0 +20003.0 "--sync--" Ability { id: "A309", source: "Demon Tablet" } +20003.8 "--sync--" Ability { id: "A30A", source: "Demon Tablet" } +20004.1 "--sync--" StartsUsing { id: "A306", source: "Demon Tablet" } window 100,50 +20009.1 "Demonic Dark II" Ability { id: "A306", source: "Demon Tablet" } +20025.2 "Ray of Dangers Near / Ray of Expulsion Afar" Ability { id: ["A2F3", "A2F4"], source: "Demon Tablet" } +20025.6 "Ray of Ignorance" Ability { id: "A2F5", source: "Demon Tablet" } +20025.6 "Landing" #Ability { id: "A354", source: "Demon Tablet" } +20038.3 "Ray of Dangers Near / Ray of Expulsion Afar" Ability { id: ["A2F3", "A2F4"], source: "Demon Tablet" } +20038.7 "Ray of Ignorance" Ability { id: "A2F5", source: "Demon Tablet" } +20038.7 "Landing" #Ability { id: "A354", source: "Demon Tablet" } +20052.8 "Occult Chisel" Ability { id: ["A307", "A308"], source: "Demon Tablet" } +20068.9 "Demonograph of Dangers Near / Demonograph of Expulsion Afar" Ability { id: ["A2F6", "A2F7"], source: "Demon Tablet" } +20069.3 "Landing" Ability { id: "A354", source: "Demon Tablet" } +20073.7 "Explosion" Ability { id: "A2F8", source: "Demon Tablet" } +20086.8 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20087.7 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20099.0 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20099.9 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20103.2 "Lacunate Stream" Ability { id: "A300", source: "Demon Tablet" } +20123.3 "Cometeor of Dangers Near / Cometeor of Expulsion Afar" Ability { id: ["A2E4", "A2E5"], source: "Demon Tablet" } +20123.7 "Landing" Ability { id: "A354", source: "Demon Tablet" } +20130.3 "Portentous Comet" Ability { id: ["A2E8", "A2E7"], source: "Demon Tablet" } +20137.3 "Portentous Cometeor" Ability { id: "A2E6", source: "Demon Tablet" } +20151.0 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20151.9 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20163.2 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20164.1 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20167.4 "Lacunate Stream" Ability { id: "A300", source: "Demon Tablet" } +20187.5 "Summon" Ability { id: "A30D", source: "Demon Tablet" } +20188.9 "--adds-targetable--" +20233.7 "Summon" Ability { id: "A2E9", source: "Demon Tablet" } +20239.7 "Demonography" Ability { id: "A2EE", source: "Demon Tablet" } +20251.7 "Gravity of Dangers Near / Gravity of Expulsion Afar" Ability { id: ["A2EA", "AA01"], source: "Demon Tablet" } +20252.1 "Landing" Ability { id: "A354", source: "Demon Tablet" } +20255.7 "Erase Gravity" Ability { id: "A2EB", source: "Demon Tablet" } +20261.4 "Explosion" Ability { id: ["A2F1", "A2EF"], source: "Demon Tablet" } +20265.7 "Restore Gravity" Ability { id: "A2EC", source: "Demon Tablet" } +20267.7 "Landing" Ability { id: "A2ED", source: "Demon Tablet" } +20281.8 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20282.7 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20294.0 "Rotate Right / Rotate Left" Ability { id: ["A301", "A302"], source: "Demon Tablet" } +20294.8 "Rotation" Ability { id: ["A304", "A303"], source: "Demon Tablet" } +20298.1 "Lacunate Stream" Ability { id: "A300", source: "Demon Tablet" } +20318.2 "Cometeor of Dangers Near / Cometeor of Expulsion Afar" Ability { id: ["A2E4", "A2E5"], source: "Demon Tablet" } +20318.6 "Landing" Ability { id: "A354", source: "Demon Tablet" } +20325.2 "Portentous Comet" Ability { id: ["A2E8", "A2E7"], source: "Demon Tablet" } +20332.2 "Portentous Cometeor" Ability { id: "A2E6", source: "Demon Tablet" } +20347.8 "Ray of Dangers Near / Ray of Expulsion Afar" Ability { id: ["A2F3", "A2F4"], source: "Demon Tablet" } +20348.2 "Ray of Ignorance" Ability { id: "A2F5", source: "Demon Tablet" } +20348.2 "Landing" #Ability { id: "A354", source: "Demon Tablet" } +20362.3 "Occult Chisel" Ability { id: ["A307", "A308"], source: "Demon Tablet" } +20373.4 "Demonic Dark II" Ability { id: "A306", source: "Demon Tablet" } +20395.4 "End of History (Enrage)" Ability { id: "A30B", source: "Demon Tablet" } +20396.3 "End of History (Enrage)" Ability { id: "A355", source: "Demon Tablet" } + +# Enrage Loop +# NOTE: A355 does damage, not sure on A30C +20397.6 "End of History (Enrage)" Ability { id: "A30C", source: "Demon Tablet" } +20397.6 label "oc-demon-tablet-enrage-loop" +20398.4 "End of History (Enrage)" Ability { id: "A355", source: "Demon Tablet" } +20399.6 "End of History (Enrage)" Ability { id: "A30C", source: "Demon Tablet" } jump "oc-demon-tablet-enrage-loop" +20400.4 "End of History (Enrage)" #Ability { id: "A355", source: "Demon Tablet" } + +### Dead Stars +# -ii 366 A5FA A5FB A608 A602 A5FF A600 A63E A63F A5B9 A5DC A5DF A5DB A5E4 A5DE A5E5 A5F9 A606 A5D1 A5D2 A5CC A5CD A5E8 A5E9 A5EA A5EB A5EC A5EE A5EF A5F1 A5F3 A5BB +# -p A5FC:30016.5 +# -it "Phobos" "Nereid" "Triton" "Frozen Triton" "Gaseous Nereid" "Dead Stars" +# IGNORED ABILITIES +# 366 attack +# A5B9 Three-Body Problem from Triton +# A5CC Flamethrower (Targeted Players) +# A5CD Flamethrower (Damage) +# A5BB unknown_a5bb Transformation, only syncable for slimes +# A5DB Liquified Triton / Liquified Nereid Jump +# A5DE Frozen Fallout from Phobos +# A5E4 Frozen Fallout from Liquified Nereid +# A5E5 Noxious Nova self-cast on Phobos +# A5EA Bodied (bosses colliding with each other) +# A5D1 Avalaunch (Tether Player Target) +# A5D2 Avalaunch (Stack Damage) +# A5DC Primordial Chaos Damage +# A5DF Frozen Fallout tell from Phobos +# A5E8 Six-Handed Fistfight (self-cast) from Dead Stars +# A5E9 Six-Handed Fistfight (damage) from Dead Stars +# A5EB Enrage Sequence related animation +# A5EC Enrage Sequence related animation +# A5EE Collateral Heat Jet +# A5EF Collateral Cold Jet +# A5F1 Collateral Fireball +# A5F3 Collateral Bioball +# A5F9 Excruciating Equilibrium from Phobos +# A5FA Decisive Battle +# A5FB Decisive Battle +# A5FF Delta Attack from Triton +# A600 Delta Attack from Triton +# A63E Delta Attack 1 Damage from Triton +# A63F Delta Attack 2/3 Damage from Nereid and Phobos +# A606 Firestrike Damage +# A602 Slice 'n' Dice (tankbuster related) +# A608 Auto-attack from boss +# +# ALL ENCOUNTER ABILITIES +# A5B4 --sync-- +# A5B5 Three-body Probl +# A5B6 Chilling Collision +# A5B7 Three-body Problem +# A5B8 Three-body Problem +# A5BA --sync-- +# A5BC --sync-- +# A5BD Vengeful Fire III +# A5BE Vengeful Blizzard III +# A5BF Vengeful Bio III +# A5C0 Elemental Impact +# A5C1 Elemental Impact +# A5C2 Elemental Impact +# A5C9 Geothermal Rupture +# A5CA Geothermal Rupture +# A5CB Flame Thrower +# A5CE Snowball Flight +# A5CF Snow Boulder +# A5D0 Snow Boulder +# A5D3 Chilling Collision +# A5D4 Chilling Collision +# A5D5 To the Winds +# A5D9 Primordial Chaos +# A5DA Primordial Chaos +# A5DD Frozen Fallout +# A5E0 Frozen Fallout tells from Phobos and Triton +# A5E1 Frozen Fallout +# A5E2 Frozen Fallout +# A5E3 Frozen Fallout from Liquified Triton +# A5E6 Noxious Nova +# A5E7 Six-handed Fistfight +# A5ED Collateral Damage +# A5F0 Collateral Gas Jet +# A5F2 Collateral Iceball +# A5F4 Fusion Burst (Enrage) +# A5F5 Fusion Burst (Enrage) +# A5F6 --sync-- +# A5F7 Return Phobos and Triton cast this at end of fight +# A5F8 Excruciating Equilibrium +# A5FC Decisive Battle +# A5FD Delta Attack +# A5FE Delta Attack +# A601 Slice 'n' Dice +# A603 Firestrike +# A604 Firestrike +# A605 Slice 'n' Strike +# A607 Firestrike +# A635 Blazing Belligerent +# A636 Icebound Buffoon +# A637 Noisome Nuisance +# A641 Fusion Burst (Post-Enrage) +# A89A Avalaunch +# A908 Fire Spread +# A9BD Three-body Problem +# The Astronomy Room will be sealed off +30000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1451" } window 100000,0 +30008.2 "--sync--" Ability { id: "A5B4", source: "Phobos" } +30010.5 "--sync--" StartsUsing { id: "A5FC", source: "Phobos" } window 100,50 +30016.5 "Decisive Battle" Ability { id: "A5FC", source: "Phobos" } +30026.7 "Slice 'n' Dice" Ability { id: "A601", source: "Phobos" } +30031.9 "--sync--" Ability { id: "A5B4", source: "Phobos" } + +# Slimes +30039.7 "Three-Body Probl" Ability { id: "A5B5", source: "Phobos" } +30040.8 "Three-Body Problem" Ability { id: "A9BD", source: "Nereid" } +30041.4 "Noisome Nuisance" Ability { id: "A637", source: "Phobos" } +30041.7 "--Nereid untargetable--" +30041.7 "--Triton untargetable--" +30053.7 "Primordial Chaos" Ability { id: "A5D9", source: "Phobos" } +30058.9 "Frozen Fallout" StartsUsing { id: "A5DD", source: "Phobos" } duration 10 +30061.5 "Frozen Fallout Tell 1" Ability { id: "A5E0", source: "Phobos" } +30064.1 "Frozen Fallout Tell 2" Ability { id: "A5E0", source: "Triton" } +30066.5 "Frozen Fallout Tell 3" Ability { id: "A5E0", source: "Phobos" } +30068.9 "Frozen Fallout Tell 4" Ability { id: "A5E0", source: "Triton" } +30070.3 "Frozen Fallout 1" Ability { id: "A5E3", source: "Liquified Triton" } +30075.7 "Frozen Fallout 2" Ability { id: "A5E3", source: "Liquified Triton" } +30081.1 "Frozen Fallout 3" Ability { id: "A5E3", source: "Liquified Triton" } +30086.5 "Frozen Fallout 4" Ability { id: "A5E3", source: "Liquified Triton" } +30096.5 "Noxious Nova" Ability { id: "A5E6", source: "Phobos" } +30096.6 "--sync--" Ability { id: "A5BB", source: "Nereid" } +30100.7 "--Nereid targetable--" +30100.7 "--Triton targetable--" +30100.7 "--sync--" Ability { id: "A5BC", source: "Nereid" } +30102.7 "--sync--" Ability { id: "A5B4", source: "Nereid" } +30110.6 "Vengeful Blizzard III" Ability { id: "A5BE", source: "Nereid" } +30110.6 "Vengeful Fire III" #Ability { id: "A5BD", source: "Triton" } +30111.0 "--sync--" Ability { id: "A5BA", source: "Phobos" } +30115.2 "Excruciating Equilibrium" Ability { id: "A5F8", source: "Phobos" } +30120.6 "--sync--" Ability { id: "A5B4", source: "Phobos" } +30127.6 "Delta Attack 1" Ability { id: "A5FD", source: "Phobos" } +30128.7 "Delta Attack 2" Ability { id: "A5FE", source: "Phobos" } +30129.9 "Delta Attack 3" Ability { id: "A5FE", source: "Phobos" } +30135.0 "Firestrike (marker)" Ability { id: "A604", source: "Phobos" } +30140.0 "Firestrike" Ability { id: "A603", source: "Phobos" } +30145.2 "--sync--" Ability { id: "A5B4", source: "Phobos" } + +# Snowballs +30152.9 "Three-Body Probl" Ability { id: "A5B5", source: "Nereid" } +30154.1 "Three-Body Problem" Ability { id: "A5B8", source: "Phobos" } +30154.7 "Icebound Buffoon" Ability { id: "A636", source: "Nereid" } +30155.0 "--Phobos untargetable--" +30155.0 "--Triton untargetable--" +30155.0 "--Snowballs targetable--" +30169.0 "Snowball Flight" Ability { id: "A5CE", source: "Nereid" } +30169.0 "Snow Boulder 1" #Ability { id: "A5D0", source: "Frozen Phobos" } +30171.5 "Snow Boulder 2" Ability { id: "A5D0", source: "Frozen Phobos" } +30174.0 "Snow Boulder 3" Ability { id: "A5D0", source: "Frozen Phobos" } +30179.0 "Chilling Collision" Ability { id: "A5B6", source: "Nereid" } +30180.1 "Chilling Collision (knockback)" Ability { id: "A5D4", source: "Nereid" } +30181.5 "Avalaunch" Ability { id: "A89A", source: "Frozen Phobos" } +30199.8 "To the Winds" Ability { id: "A5D5", source: "Nereid" } +30199.8 "--Snowballs untargetable--" +30199.8 "Self-destruct (Enrage)" #Ability { id: "A5D6", source: "Frozen Triton" } +30200.4 "Self-destruct (Enrage)" #Ability { id: "A5D8", source: "Frozen Triton" } + +30204.1 "--Phobos targetable--" +30204.1 "--Triton targetable--" +30204.1 "--sync--" Ability { id: "A5BC", source: "Phobos" } window 100,0 +30206.1 "--sync--" Ability { id: "A5B4", source: "Phobos" } +30214.0 "Vengeful Bio III" Ability { id: "A5BF", source: "Phobos" } +30214.0 "Vengeful Fire III" #Ability { id: "A5BD", source: "Triton" } +30214.4 "--sync--" Ability { id: "A5BA", source: "Nereid" } +30218.6 "Excruciating Equilibrium" Ability { id: "A5F8", source: "Phobos" } +30223.7 "--sync--" Ability { id: "A5B4", source: "Phobos" } +30231.0 "Delta Attack 1" Ability { id: "A5FD", source: "Phobos" } +30232.1 "Delta Attack 2" Ability { id: "A5FE", source: "Phobos" } +30233.2 "Delta Attack 3" Ability { id: "A5FE", source: "Phobos" } +30238.3 "Firestrike (marker)" Ability { id: "A607", source: "Phobos" } +30243.3 "Slice 'n' Strike" Ability { id: "A605", source: "Phobos" } +30244.5 "Firestrike" #Ability { id: "A606", source: "Phobos" } +30250.7 "--sync--" Ability { id: "A5B4", source: "Phobos" } + +# Fireballs +30258.2 "Three-Body Probl" Ability { id: "A5B5", source: "Triton" } +30259.4 "Three-Body Problem" Ability { id: "A5B7", source: "Phobos" } +30260.0 "Blazing Belligerent" Ability { id: "A635", source: "Triton" } +30260.2 "--Phobos untargetable--" +30260.2 "--Nereid untargetable--" +30274.1 "Elemental Impact 1" Ability { id: "A5C0", source: "Gaseous Phobos" } +30274.5 "Fire Spread 1" Ability { id: "A908", source: "Triton" } +30275.1 "--Fireballs targetable--" +30278.1 "Elemental Impact 2" Ability { id: "A5C1", source: "Gaseous Phobos" } +30278.7 "Fire Spread 2" Ability { id: "A908", source: "Triton" } +30282.3 "Elemental Impact 3" Ability { id: "A5C1", source: "Gaseous Phobos" } +30282.9 "Fire Spread 3" #Ability { id: "A908", source: "Triton" } +30287.5 "Geothermal Rupture" Ability { id: "A5C9", source: "Triton" } +30289.6 "Flame Thrower" Ability { id: "A5CC", source: "Triton" } +30290.6 "Geothermal Rupture 1" Ability { id: "A5CA", source: "Triton" } +30292.6 "Geothermal Rupture 2" Ability { id: "A5CA", source: "Triton" } +30294.6 "Geothermal Rupture 3" Ability { id: "A5CA", source: "Triton" } +30294.6 "Flame Thrower" #Ability { id: "A5CB", source: "Triton" } +30299.6 "Elemental Impact 1" Ability { id: "A5C2", source: "Gaseous Phobos" } +30300.0 "Fire Spread 1" Ability { id: "A908", source: "Triton" } +30303.7 "Elemental Impact 2" Ability { id: "A5C1", source: "Gaseous Phobos" } +30304.1 "Fire Spread 2" #Ability { id: "A908", source: "Triton" } +30307.8 "Elemental Impact 3" Ability { id: "A5C1", source: "Gaseous Phobos" } +30308.2 "Fire Spread 3" Ability { id: "A908", source: "Triton" } +30320.0 "To the Winds" Ability { id: "A5C5", source: "Triton" } +30320.0 "Self-Destruct" #Ability { id: "A5C6", source: "Gaseous Phobos" } +30320.0 "Self-Destruct" #Ability { id: "A5C6", source: "Gaseous Nereid" } +30320.0 "Self-Destruct (Enrage)" #Ability { id: "A5C7", source: "Gaseous Nereid" } +30320.7 "Self-Destruct (Enrage)" #Ability { id: "A5C8", source: "Triton" } + +30324.2 "--Nereid targetable--" +30324.2 "--Phobos targetable--" +30324.2 "--sync--" Ability { id: "A5BC", source: "Phobos" } window 100,0 +30326.2 "--sync--" Ability { id: "A5B4", source: "Phobos" } +30334.1 "Vengeful Bio III" Ability { id: "A5BF", source: "Phobos" } +30334.1 "Vengeful Blizzard III" #Ability { id: "A5BE", source: "Nereid" } +30334.5 "--sync--" Ability { id: "A5BA", source: "Triton" } +30338.7 "Excruciating Equilibrium" Ability { id: "A5F8", source: "Phobos" } +30340.8 "--sync--" Ability { id: "A5B4", source: "Phobos" } + +# Enrage Sequence +# A5F0 - Collateral Gas Jet is used to track all three +# A5F2 - Collateral Iceball is used to track all three +# Collaterall Balls targets each player randomly once +# Dead Stars targetable based on log of earliest hit +30357.9 "Six-Handed Fistfight" Ability { id: "A5E7", source: "Phobos" } +30357.9 "--Bosses untargetable--" +30359.4 "--Dead Stars targetable--" +30370.4 "Collateral Damage" Ability { id: "A5ED", source: "Dead Stars" } duration 11.5 +30370.4 "Collateral Jets 1" #Ability { id: "A5F0", source: "Dead Stars" } +30372.3 "Collateral Jets 2" #Ability { id: "A5F0", source: "Dead Stars" } +30377.2 "Collateral Balls 1 x8" #Ability { id: "A5F2", source: "Triton" } +30378.0 "Collateral Balls 2 x8" #Ability { id: "A5F2", source: "Triton" } +30378.8 "Collateral Balls 3 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30379.6 "Collateral Balls 4 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30380.4 "Collateral Balls 5 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30381.2 "Collateral Balls 6 x8" #Ability { id: "A5F2", source: "Dead Stars" } + +30391.2 "Collateral Damage" Ability { id: "A5ED", source: "Dead Stars" } duration 11.5 +30391.2 "Collateral Jets 1" #Ability { id: "A5F0", source: "Dead Stars" } +30392.9 "Collateral Jets 2" #Ability { id: "A5F0", source: "Dead Stars" } +30398.1 "Collateral Balls 1 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30399.0 "Collateral Balls 2 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30399.8 "Collateral Balls 3 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30400.7 "Collateral Balls 4 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30401.7 "Collateral Balls 5 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30402.7 "Collateral Balls 6 x8" #Ability { id: "A5F2", source: "Dead Stars" } +30417.4 "Fusion Burst (Enrage)" Ability { id: "A5F4", source: "Dead Stars" } +30417.4 "Fusion Burst (Enrage)" #Ability { id: "A5F5", source: "Dead Stars" } +30417.4 "--untargetable--" + +# Enrage Loop +30422.3 "Fusion Burst (Enrage)" Ability { id: "A641", source: "Dead Stars" } +30422.3 label "oc-dead-stars-enrage-loop" +30427.0 "Fusion Burst (Enrage)" Ability { id: "A641", source: "Dead Stars" } jump "oc-dead-stars-enrage-loop" + +# Early Clear +30600.0 "--sync--" StartsUsing { id: "A5F7", source: "Phobos" } window 600,2 +30606.0 "Return (Clear)" Ability { id: "A5F7", source: "Phobos" } + +### Pronged Passage (Bridges) +# Note: Raid splits into two and battles across their own bridge in 3 sections +# If one group is faster than the other, the other's section starts to burn +# If either group is too slow in a section, the ground starts to Burn as a soft enrage +# Unsure if these timings can be reliably predicted +# +# -ii 368 A3A8 A3A9 A43B A43D A442 A443 46D1 64F1 A903 A62A A62B A624 A625 A61F +# -p A441:40011.3 A620:40346.0 +# -it "Tower Bhoot" "Tower Bogy" "Tower Abyss" "Tower Idol" "Tower Progenitrix" "Tower Progenitor" "Lava Bomb" "Aetherial Ward" +# +# IGNORED ABILITIES +# 368 attack +# A3A8 Dense Darkness (Tower Abyss) +# A3A9 Diffuse Darkness (Tower Abyss) +# A43B unknown_a43b (Trap) (Trap's proximity checker?) +# A43D unknown_a43d (Trap) (Massive Trap's proximity checker?) +# A442 Burst (Trap) exploding a Trap +# A443 Massive Explosion (Trap) exploding a massive Trap +# A61F Ancient Aero III (Tower Idol) +# A624 Massive Explosion (Tower Progenitrix) starts enrage cast if Tower Progenitor dies +# A625 Massive Explosion (Tower Progenitor) starts enrage cast if Tower Progenitrix dies +# A62A Nexus of Flame (Tower Progenitrix) Pulsing aoe (1.9s) from being too close/far from Tower Progenitor during A620/A622 +# A62B Massive Explosion (Lava Bomb) Lava Bomb add reaching Tower Progenitor/Tower Progenitrix +# 46D1 Attack (Tower Idol) (Tower Abyss) +# A903 Paralyze III +# 64F1 Radiant Aegis (bug in log? had no source) +# +# ALL ENCOUNTER ABILITIES +# A43F --sync-- Burns trap +# A441 Arcane Spear +# A620 Close Call to Detonate (Tower Progenitrix) +# A621 Close Call to Detonate (Tower Progenitor) +# A622 Far Cry to Detonate (Tower Progenitrix) +# A623 Far Cry to Detonate (Tower Progenitor) +# A626 Bombshell Drop (Tower Progenitrix) +# A627 Bombshell Drop (Tower Progenitor) +# A628 Punishing Pounce (Tower Progenitrix) +# A629 Punishing Pounce (Tower Progenitor) +# A6F4 Arcane Spear +# The pronged passage will be sealed off +40000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1454" } window 100000,0 + +# Encounter 1: Two Tower Bogy and 1 Tower Bhoot +40005.3 "--sync--" StartsUsing { id: "A441", source: "Trap" } window 20,5 +40011.3 "Arcane Spear (side 1)" Ability { id: "A441", source: "Trap" } +40016.1 "Arcane Spear (side 2)" Ability { id: "A441", source: "Trap" } + +# Encounter 2: Tower Abyss (Time Mage and Trap section) +# Dense Darkness A3A8 timing starts from when add is pulled +40041.1 "Arcane Spear (side 1)" Ability { id: "A441", source: "Trap" } +40045.8 "Arcane Spear (side 2)" Ability { id: "A441", source: "Trap" } +40045.8 "--Burns (Section 1)--" Ability { id: "A43F", source: "Trap" } +40082.8 "--Burns (Section 2)--" Ability { id: "A43F", source: "Trap" } + +# Encounter 3: 6 Tower Idols (Phantom Bard and Geomancer section) +# Adds from section 4 could be pulled here +40119.8 "--Burns (Section 3)--" Ability { id: "A43F", source: "Trap" } + +# Encounter 4: Tower Bhoot + two Tower Bogy +40120.0 "--Burns (Section 4)--" Ability { id: "A43F", source: "Trap" } +40142.6 "--Burns (Section 5)--" Ability { id: "A43F", source: "Trap" } +40170.9 "--Burns (Final)--" Ability { id: "A43F", source: "Trap" } + +# Encounter 5: Tower Progenitrix and Tower Progenitor +# Jumping to this section for timings +# Players can encounter Burns here if they are too slow +40343.0 "--sync--" StartsUsing { id: ["A621", "A623", "A620", "A622"], source: ["Tower Progenitor", "Tower Progenitrix"] } window 350,20 +40346.0 "Close Call to Detonate / Far Cry to Detonate" #Ability { id: ["A621", "A623", "A620", "A622"], source: ["Tower Progenitor", "Tower Progenitrix"] } +40363.2 "Arcane Spear" Ability { id: "A6F4", source: "Trap" } +40367.2 "Bombshell Drop" Ability { id: ["A626", "A627"], source: ["Tower Progenitrix", "Tower Progenitor"] } +40367.2 "--adds--" + +# Begin Loop +40391.4 "Arcane Spear" Ability { id: "A6F4", source: "Trap" } +40391.4 label "oc-pronged-bridge-loop" +40391.4 "Close Call to Detonate / Far Cry to Detonate" #Ability { id: ["A621", "A623", "A620", "A622"], source: ["Tower Progenitor", "Tower Progenitrix"] } +40405.5 "Punishing Pounce" Ability { id: ["A628", "A629"], source: ["Tower Progenitrix", "Tower Progenitor"] } +40420.5 "Arcane Spear" Ability { id: "A6F4", source: "Trap" } +40421.5 "Bombshell Drop" Ability { id: ["A626", "A627"], source: ["Tower Progenitrix", "Tower Progenitor"] } +40421.5 "--adds--" + +40449.7 "Arcane Spear" Ability { id: "A6F4", source: "Trap" } jump "oc-pronged-bridge-loop" +40449.7 "Close Call to Detonate / Far Cry to Detonate" #Ability { id: ["A621", "A623", "A620", "A622"], source: ["Tower Progenitor", "Tower Progenitrix"] } +40463.9 "Punishing Pounce" #Ability { id: ["A628", "A629"], source: ["Tower Progenitrix", "Tower Progenitor"] } +40478.9 "Arcane Spear" #Ability { id: "A6F4", source: "Trap" } +40479.9 "Bombshell Drop" #Ability { id: ["A626", "A627"], source: ["Tower Progenitrix", "Tower Progenitor"] } +40479.9 "--adds--" + +### Marble Dragon +# -ii 756B 7842 9ECC 77E5 77E6 7797 76CF 9A26 93BB 76D1 7616 93BA +# -p 77F1:50011.0 76D3:50320.3 +# -it "Marble Dragon" "Ice Golem" "Ice Sprite" +# IGNORED ABILITIES +# 756B Attack +# 76CF Frigid Twister (puddle movement?) +# 7616 Immitation Blizzard tower (7615) without at least 4 players in it +# 76D1 Immitation Blizzard aoe damage from 7616 Immitation Blizzard cast +# 9ECC Imitation Star (Damage), 1.9s to 2.9s after cast +# 7842 unknown_7842 from Tower Idol +# 7797 Imitation Raid (Damage), 0.5-0.7s after cast +# 77E5 Draconiform Motion (Damage from Head), 0.7-0.9s after cast +# 77E6 Draconiform Motion (Damage from Tail?), 0.7-0.9s after cast +# 93BA Lifeless Legacy (damage), 1.9s after cast +# 93BB Frigid Dive (Damage) +# 9A26 Attack (Ice Golem) +# +# ALL ENCOUNTER ABILITIES +# 756C --sync-- +# 756D Lifeless Legacy (Enrage) +# 756E Lifeless Legacy (Post-Enrage) +# 756F Imitation Icicle +# 75E4 Imitation Icicle +# 7602 Imitation Blizzard (circle aoe) +# 7614 Imitation Blizzard (cross aoe) +# 7615 Imitation Blizzard (Tower soak damage) +# 7638 Frigid Twister +# 7687 Imitation Rain +# 76D0 --sync-- +# 76D3 Withering Eternity +# 7795 --sync-- +# 7796 Frigid Dive +# 7798 Lifeless Legacy (cast) +# 77C1 Draconiform Motion +# 77E7 Wicked Water +# 77E8 Dread Deluge cast +# 77F0 Dread Deluge (Damage), 2s after castbar +# 77F1 Imitation Star +# 93BF Frozen Heart +# 9ECD Lifeless Legacy (Enrage) +# A715 Ball of Ice +# A716 Ball of Ice +# The Glaciology Forum will be sealed off +50000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1452" } window 100000,0 +50006.0 "--sync--" StartsUsing { id: "77F1", source: "Marble Dragon" } window 100,50 +50011.0 "Imitation Star" Ability { id: "77F1", source: "Marble Dragon" } duration 1.9 +50025.3 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +50030.4 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50032.4 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +50037.7 "Imitation Icicle" Ability { id: "756F", source: "Marble Dragon" } +50046.0 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +50046.4 "--Icicle Puddles--" Ability { id: "75E4", source: "Marble Dragon" } +50047.6 "Ball of Ice 1" #Ability { id: "A715", source: "Marble Dragon" } +50048.6 "Ball of Ice 2" #Ability { id: "A715", source: "Marble Dragon" } +50049.6 "Ball of Ice 3" #Ability { id: "A715", source: "Marble Dragon" } +50050.0 "--sync--" StartsUsing { id: "7614", source: "Marble Dragon" } jump "oc-md-p1+" +50050.6 "Ball of Ice 4" #Ability { id: "A715", source: "Marble Dragon" } +50051.0 "Imitation Blizzard 1 (circles + cross?)" Ability { id: "7602", source: "Marble Dragon" } jump "oc-md-p1o" +50052.0 "Imitation Blizzard 2 (circles + cross?)" #Ability { id: "7602", source: "Marble Dragon" } +50053.0 "Imitation Blizzard 3 (circles + cross?)" #Ability { id: "7614", source: "Marble Dragon" } +50054.0 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } +50065.0 "Dread Deluge (castbar)" #Ability { id: "77E8", source: "Marble Dragon" } +50067.0 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +50072.1 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } + +# Pattern 1 - Cross First +50150.0 label "oc-md-p1+" +50150.6 "Ball of Ice 4" #Ability { id: "A715", source: "Marble Dragon" } +50151.0 "Imitation Blizzard 1 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50152.0 "Imitation Blizzard 2 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50153.0 "Imitation Blizzard 3 (circles)" #Ability { id: "7602", source: "Marble Dragon" } +50154.0 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } +50162.0 "--sync--" StartsUsing { id: "77E8", source: "Marble Dragon" } jump "oc-md-p1-end" +50165.0 "Dread Deluge (castbar)" #Ability { id: "77E8", source: "Marble Dragon" } +50167.0 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +50172.1 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } +50178.1 "Frigid Twister" #Ability { id: "7638", source: "Marble Dragon" } +50180.2 "--middle--" #Ability { id: "756C", source: "Marble Dragon" } +50183.0 "--twisters start--" #Ability { id: "76CF", source: "Marble Dragon" } + +# Pattern 1 - Circles First +50251.0 label "oc-md-p1o" +50252.0 "Imitation Blizzard 2 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50253.0 "Imitation Blizzard 3 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50254.0 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } + +50262.0 label "oc-md-p1-end" +50265.0 "Dread Deluge (castbar)" Ability { id: "77E8", source: "Marble Dragon" } +50267.0 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +50272.1 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50278.1 "Frigid Twister" Ability { id: "7638", source: "Marble Dragon" } +50280.2 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +50283.0 "--twisters start--" #Ability { id: "76CF", source: "Marble Dragon" } +50286.3 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +50287.2 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +50289.5 "--sync--" StartsUsing { id: "7614", source: "Marble Dragon" } jump "oc-md-p2+" +50289.5 "--sync--" StartsUsing { id: "7602", source: "Marble Dragon" } jump "oc-md-p2o" +50290.5 "Imitation Blizzard (circles/cross?)" #Ability { id: ["7602", "7614"], source: "Marble Dragon" } + +# Pattern 2 - Cross First +# NOTE: This pattern happens slightly sooner than circles +50389.4 label "oc-md-p2+" +50390.4 "Imitation Blizzard (cross)" Ability { id: "7614", source: "Marble Dragon" } +50390.8 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +50394.1 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } +50394.4 "Ball of Ice" #Ability { id: "A715", source: "Marble Dragon" } +50397.7 "Imitation Blizzard (cross)" Ability { id: "7614", source: "Marble Dragon" } jump "oc-md-p2-end" +50402.4 "--twisters end--" #Ability { id: "76CF", source: "Marble Dragon" } +50404.2 "Dread Deluge (castbar)" #Ability { id: "77E8", source: "Marble Dragon" } +50406.2 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +50413.4 "--middle--" #Ability { id: "756C", source: "Marble Dragon" } + +# Pattern 2 - Circles First +50589.5 label "oc-md-p2o" +50590.5 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } +50590.9 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +50594.2 "Imitation Blizzard (circles + cross)" Ability { id: "7614", source: "Marble Dragon" } +50594.5 "Ball of Ice" #Ability { id: "A715", source: "Marble Dragon" } +50597.8 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } + +50597.8 label "oc-md-p2-end" +50602.4 "--twisters end--" #Ability { id: "76CF", source: "Marble Dragon" } +50604.2 "Dread Deluge (castbar)" Ability { id: "77E8", source: "Marble Dragon" } +50606.2 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +50613.4 "--middle--" Ability { id: "756C", source: "Marble Dragon" } + +# Add phase (clockwise or counterclockwise pattern, start timing varies by ~2s) +50620.3 "Withering Eternity" Ability { id: "76D3", source: "Marble Dragon" } window 620,5 +50622.3 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50622.3 "--untargetable--" +50622.3 "--ice golems--" +50625.1 "--dive spot--" Ability { id: "7795", source: "Marble Dragon" } +50634.3 "Frigid Dive" Ability { id: "7796", source: "Marble Dragon" } +50635.7 "--towers x2--" Ability { id: "A716", source: "Marble Dragon" } +50635.7 "Ball of Ice (center)" #Ability { id: "A715", source: "Marble Dragon" } +50639.3 "Imitation Blizzard (towers + cross)" Ability { id: "7615", source: "Marble Dragon" } +50639.7 "--towers x4--" Ability { id: "A716", source: "Marble Dragon" } +50643.3 "Imitation Blizzard (towers)" Ability { id: "7615", source: "Marble Dragon" } +50647.3 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50649.3 "--dive spot--" Ability { id: "7795", source: "Marble Dragon" } +50658.5 "Frigid Dive" Ability { id: "7796", source: "Marble Dragon" } +50659.9 "--towers x2--" Ability { id: "A716", source: "Marble Dragon" } +50659.9 "Ball of Ice (center)" #Ability { id: "A715", source: "Marble Dragon" } +50663.5 "Imitation Blizzard (towers + cross)" Ability { id: "7615", source: "Marble Dragon" } +50663.9 "--towers x4--" Ability { id: "A716", source: "Marble Dragon" } +50667.5 "Imitation Blizzard (towers)" #Ability { id: "7615", source: "Marble Dragon" } +50670.3 "Frozen Heart" Ability { id: "93BF", source: "Ice Golem" } +50671.1 "--golems enrage--" +50671.5 "--sync--" Ability { id: "76D0", source: "Marble Dragon" } +50673.5 "--targetable--" +50674.1 "--ice sprites--" +50709.5 "Lifeless Legacy" Ability { id: "7798", source: "Marble Dragon" } duration 1.9 + +# Possible soft enrage here from adds? +# 93BF Frozen Heart cast by Ice Golems adds premanent Damage Up buff to the Ice Golem +# 7799 Recharge cast by Ice Sprites adds permanent Damage Up buffs to boss + +50723.7 "Wicked Water" Ability { id: "77E7", source: "Marble Dragon" } window 750,5 +50725.8 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50727.5 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +50732.9 "Imitation Icicle" Ability { id: "756F", source: "Marble Dragon" } +50741.2 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +50741.6 "--Icicle Puddles--" Ability { id: "75E4", source: "Marble Dragon" } +50742.7 "Ball of Ice 1" #Ability { id: "A715", source: "Marble Dragon" } +50743.7 "Ball of Ice 2" #Ability { id: "A715", source: "Marble Dragon" } +50744.7 "Ball of Ice 3" #Ability { id: "A715", source: "Marble Dragon" } +50745.1 "--sync--" StartsUsing { id: "7614", source: "Marble Dragon" } jump "oc-md-p3+" +50745.7 "Ball of Ice 4" #Ability { id: "A715", source: "Marble Dragon" } +50746.1 "Imitation Blizzard 1 (circles + cross?)" Ability { id: "7602", source: "Marble Dragon" } jump "oc-md-p3o" +50747.1 "Imitation Blizzard 2 (circles + cross?)" #Ability { id: "7602", source: "Marble Dragon" } +50748.1 "Imitation Blizzard 3 (circles + cross?)" #Ability { id: "7614", source: "Marble Dragon" } +50749.1 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } +50765.1 "Imitation Star" #Ability { id: "77F1", source: "Marble Dragon" } duration 2.9 +50778.3 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } + +# Patern 3 - Cross First +50845.1 label "oc-md-p3+" +50845.7 "Ball of Ice 4" #Ability { id: "A715", source: "Marble Dragon" } +50846.1 "Imitation Blizzard 1 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50847.1 "Imitation Blizzard 2 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50848.1 "Imitation Blizzard 3 (circles)" #Ability { id: "7602", source: "Marble Dragon" } +50849.1 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } +50860.1 "--sync--" StartsUsing { id: "77F1", source: "Marble Dragon" } jump "oc-md-p3-end" +50865.1 "Imitation Star" #Ability { id: "77F1", source: "Marble Dragon" } duration 2.9 +50878.3 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } +50884.3 "Frigid Twister" #Ability { id: "7638", source: "Marble Dragon" } +50886.4 "--middle--" #Ability { id: "756C", source: "Marble Dragon" } +50889.2 "--twisters start--" #Ability { id: "76CF", source: "Marble Dragon" } +50892.5 "Draconiform Motion" #Ability { id: "77C1", source: "Marble Dragon" } + +# Pattern 3 - Circles First +50946.1 label "oc-md-p3o" +50947.1 "Imitation Blizzard 2 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50948.1 "Imitation Blizzard 3 (circles + cross)" #Ability { id: "7614", source: "Marble Dragon" } +50949.1 "Imitation Blizzard 4 (circle)" #Ability { id: "7602", source: "Marble Dragon" } + +50960.1 label "oc-md-p3-end" +50965.1 "Imitation Star" Ability { id: "77F1", source: "Marble Dragon" } duration 2.9 +50978.3 "Imitation Rain" Ability { id: "7687", source: "Marble Dragon" } +50984.3 "Frigid Twister" Ability { id: "7638", source: "Marble Dragon" } +50986.4 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +50989.2 "--twisters start--" #Ability { id: "76CF", source: "Marble Dragon" } +50992.5 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +50993.7 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +50996.3 "--sync--" StartsUsing { id: "7602", source: "Marble Dragon" } jump "oc-md-p4o" +50996.3 "--sync--" StartsUsing { id: "7614", source: "Marble Dragon" } jump "oc-md-p4+" +50997.3 "Imitation Blizzard (circles/crosses?)" #Ability { id: "7602", source: "Marble Dragon" } + +# Pattern 4 - Crosses First +51096.3 label "oc-md-p4+" +51096.7 "Imitation Blizzard (crosses)" Ability { id: "7614", source: "Marble Dragon" } +51097.1 "--towers x6--" Ability { id: "A716", source: "Marble Dragon" } +51100.4 "Imitation Blizzard (towers)" Ability { id: "7615", source: "Marble Dragon" } +51102.6 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +51105.9 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } +51107.4 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +51107.4 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } +51112.2 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +51113.3 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +51115.4 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } +51115.8 "--towers x6--" Ability { id: "A716", source: "Marble Dragon" } +51119.1 "Imitation Blizzard (towers)" #Ability { id: "7615", source: "Marble Dragon" } +51121.3 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +51124.6 "Imitation Blizzard (crosses)" Ability { id: "7614", source: "Marble Dragon" } +51129.4 "--twisters end--" #Ability { id: "76CF", source: "Marble Dragon" } +51132.3 "--sync--" StartsUsing { id: "77F1", source: "Marble Dragon" } jump "oc-md-p4-end" +51137.3 "Imitation Star" #Ability { id: "77F1", source: "Marble Dragon" } +51148.4 "Dread Deluge" #Ability { id: "77E8", source: "Marble Dragon" } +51150.4 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +51182.5 "Lifeless Legacy (Enrage)" #Ability { id: "756D", source: "Marble Dragon" } + +# Pattern 4 - Circles First +51295.7 label "oc-md-p4o" +51296.7 "Imitation Blizzard (circles)" Ability { id: "7602", source: "Marble Dragon" } +51297.1 "--towers x6--" Ability { id: "A716", source: "Marble Dragon" } +51300.4 "Imitation Blizzard (towers)" Ability { id: "7615", source: "Marble Dragon" } +51302.6 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +51305.9 "Imitation Blizzard (crosses)" #Ability { id: "7614", source: "Marble Dragon" } +51307.3 "--middle--" Ability { id: "756C", source: "Marble Dragon" } +51307.3 "Imitation Rain" #Ability { id: "7687", source: "Marble Dragon" } +51312.2 "Ball of Ice" Ability { id: "A715", source: "Marble Dragon" } +51313.0 "Draconiform Motion" Ability { id: "77C1", source: "Marble Dragon" } +51315.4 "Imitation Blizzard (cross)" Ability { id: "7614", source: "Marble Dragon" } +51315.8 "--towers x6--" Ability { id: "A716", source: "Marble Dragon" } +51319.1 "Imitation Blizzard (towers)" Ability { id: "7615", source: "Marble Dragon" } +51321.3 "Ball of Ice" #Ability { id: "A715", source: "Marble Dragon" } +51324.6 "Imitation Blizzard (circles)" #Ability { id: "7602", source: "Marble Dragon" } +51329.4 "--twisters end--" #Ability { id: "76CF", source: "Marble Dragon" } + +51331.8 label "oc-md-p4-end" +51336.8 "Imitation Star" Ability { id: "77F1", source: "Marble Dragon" } duration 2.9 +51347.9 "Dread Deluge (castbar)" Ability { id: "77E8", source: "Marble Dragon" } +51349.9 "Dread Deluge x6" #Ability { id: "77F0", source: "Marble Dragon" } +51382.0 "Lifeless Legacy (Enrage)" Ability { id: "756D", source: "Marble Dragon" } +51383.9 "Lifeless Legacy (Enrage)" Ability { id: "9ECD", source: "Marble Dragon" } + +# Enrage loop +# Note: Time between the loop casts is an estimated guess +51386.7 "Lifeless Legacy (Enrage)" Ability { id: "756E", source: "Marble Dragon" } +51386.7 label "oc-marble-dragon-enrage-loop" +51391.4 "Lifeless Legacy (Enrage)" Ability { id: "756E", source: "Marble Dragon" } jump "oc-marble-dragon-enrage-loop" + +### Magitaur +# -ii A325 9BE7 9BE5 A248 A249 A24D A24E A266 ADC2 ADC3 A25C A25D A258 A259 A243 A244 A245 A246 +# -p A264:60014.3 +# -it "Magitaur" "Lance Empowerment Conduit" "Axe Empowerment Conduit" "Universal Empowerment Conduit" +# IGNORED ABILITIES +# A243 Attack +# A244 Attack +# A245 Attack +# A246 Attack +# A248 Critical Axeblow damage +# A249 Critical Axeblow damage +# A24E Critical Lanceblow damage +# A24D Critical Lanceblow damage +# A258 Lancelight damage +# A259 Lancelight damage +# A25C Arcane Recoil tankbuster when Axe Empowerment Conduit or Lance Empowerment Conduit dies +# A25D Arcane Reaction tankbuster when Universal Empowerment Conduit dies, causes Hysteria (1047) 60s, needs to be killed by Phantom Berserker +# A266 Forked Fury damage +# A325 Attack +# ADC2 Aura Burst enrage damage +# ADC3 Holy enrage damage +# 9BE5 Aura Burst damage +# 9BE6 Holy damage +# 9BE7 Unsealed Aura damage +# +# ALL ENCOUNTER ABILITIES +# A1B7 --sync-- +# A241 Unseal +# A242 Unseal +# A247 Critical Axeblow +# A24B Critical Lanceblow +# A24C --sync-- +# A24F Rune Axe +# A250 Ruinous Rune +# A251 Ruinous Rune +# A252 Carving Rune +# A253 Axeglow +# A254 Axeglow +# A255 Holy Lance +# A256 --sync-- +# A257 Holy IV +# A25A Aura Burst +# A25B Holy +# A25E Sage's Staff +# A25F Mana Expulsion +# A260 Assassin's Dagger +# A261 Assassin's Dagger +# A262 Assassin's Dagger +# A263 Assassin's Dagger +# A264 Unsealed Aura +# A265 Forked Fury +# A267 Unsealed Aura (Enrage cast) +# A268 Unsealed Aura (Post-Enrage cast) +# 9BE8 Unsealed Aura (Enrage damage) +# The Haematology Room will be sealed off +60000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1453" } window 100000,0 +60010.1 "--sync--" StartsUsing { id: "A264", source: "Magitaur" } window 100,50 +60014.3 "Unsealed Aura" Ability { id: "A264", source: "Magitaur" } duration 1 +60030.3 "Unseal" Ability { id: ["A241", "A242"], source: "Magitaur" } +60044.9 "--middle--" Ability { id: "A1B7", source: "Magitaur" } + +# Assassin's Dagger 1 +60051.1 "Assassin's Dagger (castbar)" Ability { id: "A260", source: "Magitaur" } duration 22 +60051.4 "Assassin's Dagger 1" Ability { id: "A261", source: "Assassin's Dagger" } +60053.4 "Assassin's Dagger 1 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60055.4 "Assassin's Dagger 2" #Ability { id: "A262", source: "Assassin's Dagger" } +60057.4 "Assassin's Dagger 2 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } + +60059.1 "--sync--" StartsUsing { id: "A247", source: "Magitaur" } jump "oc-magitaur-daggers-axe" +60059.1 "--sync--" StartsUsing { id: "A24B", source: "Magitaur" } jump "oc-magitaur-daggers-lance" +60059.4 "Assassin's Dagger 3" #Ability { id: "A262", source: "Assassin's Dagger" } +60061.4 "Assassin's Dagger 3 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60063.4 "Assassin's Dagger 4" #Ability { id: "A262", source: "Assassin's Dagger" } +60064.1 "Critical Axeblow / Critical Lanceblow?" #Ability { id: ["A247", "A24B"], source: "Magitaur" } duration 1.1 +60065.4 "Assassin's Dagger 4 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60067.4 "Assassin's Dagger 5" #Ability { id: "A262", source: "Assassin's Dagger" } +60069.4 "Assassin's Dagger 5 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60071.4 "Assassin's Dagger 6" #Ability { id: "A262", source: "Assassin's Dagger" } +60073.4 "Assassin's Dagger 6 Returns" #Ability { id: "A263", source: "Assassin's Dagger" } +60074.4 "Critical Lanceblow / Critical Axeblow?" #Ability { id: ["A24B", "A247"], source: "Magitaur" } duration 1.1 + +# Pattern 1 - Axeblow first +60159.1 label "oc-magitaur-daggers-axe" +60159.4 "Assassin's Dagger 3" #Ability { id: "A262", source: "Assassin's Dagger" } +60161.4 "Assassin's Dagger 3 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60163.4 "Assassin's Dagger 4" #Ability { id: "A262", source: "Assassin's Dagger" } +60164.1 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 +60164.1 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60165.4 "Assassin's Dagger 4 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60167.4 "Assassin's Dagger 5" #Ability { id: "A262", source: "Assassin's Dagger" } +60169.4 "Assassin's Dagger 5 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60171.4 "Assassin's Dagger 6" #Ability { id: "A262", source: "Assassin's Dagger" } +60173.4 "Assassin's Dagger 6 Returns" Ability { id: "A263", source: "Assassin's Dagger" } +60174.4 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60174.4 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B + +60185.0 "Forked Fury" #Ability { id: "A265", source: "Magitaur" } +60195.0 "--middle--" #Ability { id: "A1B7", source: "Magitaur" } + +# Pattern 1 - Lanceblow first +60259.1 label "oc-magitaur-daggers-lance" +60259.4 "Assassin's Dagger 3" #Ability { id: "A262", source: "Assassin's Dagger" } +60261.4 "Assassin's Dagger 3 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60263.4 "Assassin's Dagger 4" #Ability { id: "A262", source: "Assassin's Dagger" } +60264.1 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60264.1 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60265.4 "Assassin's Dagger 4 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60267.4 "Assassin's Dagger 5" #Ability { id: "A262", source: "Assassin's Dagger" } +60269.4 "Assassin's Dagger 5 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60271.4 "Assassin's Dagger 6" #Ability { id: "A262", source: "Assassin's Dagger" } +60273.4 "Assassin's Dagger 6 Returns" Ability { id: "A263", source: "Assassin's Dagger" } +60274.4 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 + +60285.0 "Forked Fury" Ability { id: "A265", source: "Magitaur" } duration 0.6 window 300,5 +60295.0 "--middle--" Ability { id: "A1B7", source: "Magitaur" } +60315.4 "Aura Burst / Holy" Ability { id: ["A25A", "A25B"], source: "Magitaur" } duration 1 +60329.5 "--middle--" Ability { id: "A1B7", source: "Magitaur" } + +# Sage Staff +60336.2 "Sage's Staff" Ability { id: "A25E", source: "Magitaur" } + +60339.1 "--sync--" StartsUsing { id: "A247", source: "Magitaur" } jump "oc-magitaur-staff-axe" +60339.1 "--sync--" StartsUsing { id: "A24B", source: "Magitaur" } jump "oc-magitaur-staff-lance" +60344.6 "Critical Axeblow / Critical Lanceblow?" #Ability { id: ["A247", "A24B"], source: "Magitaur" } duration 1.1 +60344.6 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60350.6 "Mana Expulsion" #Ability { id: "A25F", source: "Sage's Staff" } +60353.6 "Sage's Staff" #Ability { id: "A25E", source: "Magitaur" } +60361.8 "Critical Lanceblow / Critical Axeblow?" #Ability { id: ["A24B", "A247"], source: "Magitaur" } duration 1.1 +60361.8 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60368.0 "Mana Expulsion" #Ability { id: "A25F", source: "Sage's Staff" } +60372.0 "Unsealed Aura" #Ability { id: "A264", source: "Magitaur" } duration 1 +60386.0 "Unseal" #Ability { id: "A241", source: "Magitaur" } + +# Pattern 2 - Axeblow first +60439.1 label "oc-magitaur-staff-axe" +60444.6 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 +60450.6 "Mana Expulsion" Ability { id: "A25F", source: "Sage's Staff" } +60453.6 "Sage's Staff" Ability { id: "A25E", source: "Magitaur" } +60461.8 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 jump "oc-magitaur-staff-end" +60461.8 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60468.0 "Mana Expulsion" #Ability { id: "A25F", source: "Sage's Staff" } +60472.0 "Unsealed Aura" #Ability { id: "A264", source: "Magitaur" } duration 1 +60486.0 "Unseal" #Ability { id: "A241", source: "Magitaur" } +60498.6 "--middle--" #Ability { id: "A1B7", source: "Magitaur" } + +# Pattern 2 - Lanceblow first +60539.1 label "oc-magitaur-staff-lance" +60544.6 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60544.6 "--sync--" #Ability { id: "A24C", source: "Magitaur" } # Only after Lanceblow A24B +60550.6 "Mana Expulsion" Ability { id: "A25F", source: "Sage's Staff" } +60553.6 "Sage's Staff" Ability { id: "A25E", source: "Magitaur" } +60561.8 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 + +60561.8 label "oc-magitaur-staff-end" +60568.0 "Mana Expulsion" Ability { id: "A25F", source: "Sage's Staff" } +60572.0 "Unsealed Aura" Ability { id: "A264", source: "Magitaur" } duration 1 +60586.0 "Unseal" Ability { id: "A241", source: "Magitaur" } +60598.6 "--middle--" Ability { id: "A1B7", source: "Magitaur" } + +# Rune Axe +# NOTE: This section only has Critical Lanceblow +60605.0 "Rune Axe" Ability { id: "A24F", source: "Magitaur" } +60607.1 "--Big Rune Marker--" HeadMarker { id: "023D" } +60611.0 "--Small Rune Markers x3--" #HeadMarker { id: "0159" } +60615.1 "Big Ruinous Rune" #Ability { id: "A251", source: "Magitaur" } +60615.1 "Axeglow" Ability { id: "A254", source: "Magitaur" } +60618.9 "--Big Rune Marker--" HeadMarker { id: "023D" } +60618.9 "--Small Rune Markers x2--" #HeadMarker { id: "0159" } +60619.0 "Carving Rune" #Ability { id: "A252", source: "Magitaur" } +60619.0 "Small Ruinous Rune x3" #Ability { id: "A250", source: "Magitaur" } +60619.0 "Axeglow" Ability { id: "A253", source: "Magitaur" } +60620.2 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } +60620.2 "--sync--" #Ability { id: "A24C", source: "Magitaur" } +60620.9 "Carving Rune" #Ability { id: "A252", source: "Magitaur" } +60622.9 "Carving Rune" #Ability { id: "A252", source: "Magitaur" } +60624.9 "Carving Rune" #Ability { id: "A252", source: "Magitaur" } +60626.9 "Big Ruinous Rune" #Ability { id: "A251", source: "Magitaur" } +60626.9 "Small Ruinous Rune x2" #Ability { id: "A250", source: "Magitaur" } +60626.9 "Axeglow" Ability { id: "A253", source: "Magitaur" } +60626.9 "Axeglow" #Ability { id: "A254", source: "Magitaur" } +60638.8 "Forked Fury" Ability { id: "A265", source: "Magitaur" } duration 0.6 + +60648.8 "--middle--" Ability { id: "A1B7", source: "Magitaur" } +60669.2 "Aura Burst / Holy" Ability { id: ["A25A", "A25B"], source: "Magitaur" } duration 1 + +# Assassin's Dagger 2 +# NOTE: Critical Lanceblow seems to always be first and Axeblow second +60679.1 "Assassin's Dagger (castbar)" Ability { id: "A260", source: "Magitaur" } duration 22 window 10,5 +60679.4 "Assassin's Dagger 1" Ability { id: "A261", source: "Assassin's Dagger" } +60681.4 "Assassin's Dagger 1 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60683.4 "Assassin's Dagger 2" #Ability { id: "A262", source: "Assassin's Dagger" } +60685.4 "Assassin's Dagger 2 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60687.4 "Assassin's Dagger 3" #Ability { id: "A262", source: "Assassin's Dagger" } +60689.4 "Assassin's Dagger 3 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60691.4 "Assassin's Dagger 4" #Ability { id: "A262", source: "Assassin's Dagger" } +60692.1 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60692.1 "--sync--" #Ability { id: "A24C", source: "Magitaur" } +60693.3 "Assassin's Dagger 4 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60695.3 "Assassin's Dagger 5" #Ability { id: "A262", source: "Assassin's Dagger" } +60697.3 "Assassin's Dagger 5 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60699.3 "Assassin's Dagger 6" #Ability { id: "A262", source: "Assassin's Dagger" } +60701.3 "Assassin's Dagger 6 Returns" Ability { id: "A263", source: "Assassin's Dagger" } +60702.3 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 +60712.5 "Unseal" Ability { id: ["A241", "A242"], source: "Magitaur" } +60725.1 "--middle--" Ability { id: "A1B7", source: "Magitaur" } + +# Holy Lance +# NOTE: Critical Lanceblow is always first and Axeblow second +# NOTE2: Logs may have wrong source such as Luminous Lance or Magitaur +60731.8 "Holy Lance" Ability { id: "A255", source: "Magitaur" } +60741.2 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60741.2 "--sync--" #Ability { id: "A24C", source: "Magitaur" } +60745.9 "Lance 1" #Ability { id: "A256", source: "Luminous Lance" } +60747.9 "Lance 2" #Ability { id: "A256", source: "Luminous Lance" } +60749.9 "Holy IV" Ability { id: "A257", source: "Magitaur" } +60749.9 "Lance 3" #Ability { id: "A256", source: "Luminous Lance" } +60751.9 "Lance 4" #Ability { id: "A256", source: "Luminous Lance" } +60753.9 "Lance 5" #Ability { id: "A256", source: "Luminous Lance" } +60755.9 "Lance 6" #Ability { id: "A256", source: "Luminous Lance" } +60757.9 "Holy IV" Ability { id: "A257", source: "Magitaur" } +60757.9 "Lance 7" #Ability { id: "A256", source: "Luminous Lance" } +60759.9 "Lance 8" #Ability { id: "A256", source: "Luminous Lance" } +60761.9 "Lance 9" #Ability { id: "A256", source: "Luminous Lance" } +60763.9 "Lance 10" #Ability { id: "A256", source: "Luminous Lance" } +60765.9 "Holy IV" Ability { id: "A257", source: ["Magitaur", "Luminous Lance"] } +60765.9 "Lance 11" #Ability { id: "A256", source: "Luminous Lance" } +60767.9 "Lance 12" #Ability { id: "A256", source: "Luminous Lance" } +60769.7 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 +60779.0 "Unsealed Aura" Ability { id: "A264", source: "Magitaur" } duration 1 +60788.4 "Forked Fury" Ability { id: "A265", source: "Magitaur" } duration 0.6 + +60798.4 "--middle--" Ability { id: "A1B7", source: "Magitaur" } +60819.0 "Aura Burst / Holy" Ability { id: ["A25A", "A25B"], source: "Magitaur" } duration 1 + +# Assassin's Dagge 3 +# NOTE: Critical Lanceblow seems to always be first and Axeblow second +60828.8 "Assassin's Dagger (castbar)" Ability { id: "A260", source: "Magitaur" } duration 22 +60829.1 "Assassin's Dagger 1" Ability { id: "A261", source: "Assassin's Dagger" } +60831.1 "Assassin's Dagger 1 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60833.1 "Assassin's Dagger 2" #Ability { id: "A262", source: "Assassin's Dagger" } +60835.1 "Assassin's Dagger 2 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60837.1 "Assassin's Dagger 3" #Ability { id: "A262", source: "Assassin's Dagger" } +60839.1 "Assassin's Dagger 3 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60841.1 "Assassin's Dagger 4" #Ability { id: "A262", source: "Assassin's Dagger" } +60841.8 "Critical Lanceblow" Ability { id: "A24B", source: "Magitaur" } duration 1.1 +60841.8 "--sync--" #Ability { id: "A24C", source: "Magitaur" } +60843.0 "Assassin's Dagger 4 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60845.0 "Assassin's Dagger 5" #Ability { id: "A262", source: "Assassin's Dagger" } +60847.0 "Assassin's Dagger 5 Returns" #Ability { id: "A262", source: "Assassin's Dagger" } +60849.0 "Assassin's Dagger 6" #Ability { id: "A262", source: "Assassin's Dagger" } +60851.0 "Assassin's Dagger 6 Returns" Ability { id: "A263", source: "Assassin's Dagger" } +60852.0 "Critical Axeblow" Ability { id: "A247", source: "Magitaur" } duration 1.1 +60879.4 "Unsealed Aura (Enrage)" Ability { id: "A267", source: "Magitaur" } +60880.3 "Unsealed Aura (Enrage)" Ability { id: "9BE8", source: "Magitaur" } +# Enrage Loop +# Note: Time between the loop casts is an estimated guess +60883.4 "Unsealed Aura" Ability { id: "A268", source: "Magitaur" } +60883.4 label "oc-magitaur-enrage-loop" +60887.8 "Unsealed Aura" Ability { id: "A268", source: "Magitaur" } jump "oc-magitaur-enrage-loop" diff --git a/ui/raidboss/data/07-dt/trial/arkveld-ex.ts b/ui/raidboss/data/07-dt/trial/arkveld-ex.ts index a69d0d4f..0e3d11f1 100644 --- a/ui/raidboss/data/07-dt/trial/arkveld-ex.ts +++ b/ui/raidboss/data/07-dt/trial/arkveld-ex.ts @@ -25,7 +25,7 @@ const diceMap: { [id: string]: number } = { // the Windward Wilds (Extreme) const triggerSet: TriggerSet = { - id: 'ArkveldEx', + id: 'TheWindwardWildsExtreme', zoneId: ZoneId.TheWindwardWildsExtreme, timelineFile: 'arkveld-ex.txt', initData: () => ({ @@ -59,20 +59,6 @@ const triggerSet: TriggerSet = { right: Outputs.right, }, }, - /* { - id: 'ArkveldEx 날개짓 이동', - type: 'StartsUsing', - netRegex: { id: ['AB73', 'AB74', 'B019', 'B020'] }, - delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 0.2, - durationSeconds: 2, - alertText: (_data, _matches, output) => output.move!(), - outputStrings: { - move: { - en: 'Move', - ko: '움직여요!', - }, - }, - }, */ { id: 'ArkveldEx 빨간 돌진', type: 'StartsUsing', @@ -354,6 +340,40 @@ const triggerSet: TriggerSet = { }, }, ], + timelineReplace: [ + { + 'locale': 'cn', + 'replaceSync': { + 'Guardian Arkveld': '护锁刃龙', + }, + 'replaceText': { + '\\(aoes\\)': '(圆形AOE)', + '\\(dash\\)': '(冲锋)', + '\\(raidwide\\)': '(全屏)', + '\\(wing\\)': '(翅膀)', + 'Aetheric Resonance': '地脉共振', + 'Chainblade Blow': '锁刃敲打', + 'Chainblade Charge': '锁刃下挥', + 'Clamorous Chase': '锁刃跃动', + 'Dragonspark': '龙光', + 'Forged Fury': '护龙乱击', + 'Greater Resonance': '护龙大共振', + 'Guardian Resonance': '护龙共振', + 'Roar': '咆哮', + 'Rush': '突进', + 'Siegeflight': '锁刃飞翔突进', + 'Steeltail Thrust': '龙尾突刺', + 'White Flash': '白光', + 'Wild Energy': '龙光扩散', + 'Wrathful Rattle': '锁哭龙闪·改', + 'Wyvern\'s Ouroblade': '回旋锁刃【龙闪】', + 'Wyvern\'s Radiance': '龙闪', + 'Wyvern\'s Rattle': '锁哭龙闪', + 'Wyvern\'s Vengeance': '波状龙闪', + 'Wyvern\'s Weal': '龙闪炮', + }, + }, + ], }; export default triggerSet; diff --git a/ui/raidboss/data/07-dt/trial/arkveld-ex.txt b/ui/raidboss/data/07-dt/trial/arkveld-ex.txt index 32c0f1bd..ed3489ad 100644 --- a/ui/raidboss/data/07-dt/trial/arkveld-ex.txt +++ b/ui/raidboss/data/07-dt/trial/arkveld-ex.txt @@ -1,6 +1,465 @@ -# the Windward Wilds (Extreme) +### The Windward Wilds (Extreme) +# ZoneId: 1306 hideall "--Reset--" hideall "--sync--" +0.0 "--Reset--" ActorControl { command: "4000000F" } window 0,100000 jump 0 + +# ALL ENCOUNTER ABILITIES +# +# Chainblade Blow +# Early Phase: +# First cleave brings arm down with AB6F or AB70 +# First hit is AB71 or B015 +# Follow-up is AB72 or B016 +# Second cleave brings arm down with AB75 or AB76 +# First hit is AB77 or B017 +# Follow-up is AB78 or B018 +# +# Post powerup: +# First cleave brings arm down with B019 or B01A +# First hit is B01B or B01E +# Follow-up is B01C or B01F +# Second cleave brings arm down with B021 or B022 +# First hit is B023 or B026 +# Follow-up is B024 or B027 +# +# A94E --sync-- +# Likely an auto attack, mostly appears near when jumping to edge, but +# pretty much never is the actual jump. Sometimes doesn't appear. +# +# AB33 --sync-- +# Consistently appears to be the jump to center. +# +# AB53 --sync-- +# Appears to be the start of exaflares after first towers, but also happens +# during the likely enrage. +# +# AB60 Wyvern's Weal +# ABA0 Wyvern's Weal +# ABA1 Wyvern's Weal +# ABA2 Wyvern's Weal +# AB61 --sync-- +# Chasing line aoe from Arkveld. +# +# AB73 Wyvern's Radiance +# AB74 Wyvern's Radiance +# AB79 Wyvern's Radiance +# AB7A Wyvern's Radiance +# AB80 Wyvern's Radiance +# AB81 Wyvern's Radiance +# AB87 Wyvern's Radiance +# AB88 Wyvern's Radiance +# AB89 Wyvern's Radiance +# AB8A Wyvern's Radiance +# AB94 Wyvern's Radiance +# AB95 Wyvern's Radiance +# AB9D Wyvern's Radiance +# ABA4 Wyvern's Radiance +# ABA5 Wyvern's Radiance +# ABA6 Wyvern's Radiance +# ABA8 Wyvern's Radiance +# ABA9 Wyvern's Radiance +# ABAA Wyvern's Radiance +# AF09 Wyvern's Radiance +# AF0A Wyvern's Radiance +# B01D Wyvern's Radiance +# B020 Wyvern's Radiance +# B025 Wyvern's Radiance +# B028 Wyvern's Radiance +# B02E Wyvern's Radiance +# B02F Wyvern's Radiance +# Some sort of ability used constantly throughout fight inbetween +# mechanics. Not sure if its an auto attack or not. +# +# Guardian Siegeflight + Wyvern's Siegeflight +# Wyvern's Siegeflight is the setup for the red line dash +# Guardian's Siegeflight is the setup for the white line dash +# +# Siegeflight: +# Early phase: +# AB7B Guardian Siegeflight - Red line telegraph +# AB7C Guardian Siegeflight - Red line dash +# AB7E Wyvern's Siegeflight - White line telegraph +# AB7F Wyvern's Siegeflight - White line dash +# +# After powerup: +# B029 Guardian Siegeflight - Red line telegraph +# B02A Wyvern's Siegeflight - White line telegraph +# B02B Guardian Siegeflight - Red line dash +# B02D Wyvern's Siegeflight - White line dash +# +# B030 --sync-- +# B046 --sync-- +# B037 --sync-- +# B045 --sync-- +# Appear to possibly be the actual dashes going with Siegeflight, not 100% +# sure. Some of them used during early phase while the rest used during +# post-powerup phase. +# +# AB83 Dragonspark - Red line explosions (center safe) +# AB82 White Flash - White line explosions (edges safe) +# +# AB7D Guardian Resonance +# AB90 Guardian Resonance +# AB91 Guardian Resonance +# AB93 Guardian Resonance +# AB92 Greater Resonance +# B02C Guardian Resonance +# AB8F Aetheric Resonance +# Skills used during tower spawns. AB90 appears to be when towers hit. +# +# AB84 --sync-- +# B036 --sync-- +# Skills used during setup of dashes + expanding rings mechanic. Unsure if there +# is anything we can sync to for the ring explosion timing. +# +# AB85 Rush +# AB86 Rush +# Appear to be the actual dashes, with AB85 being the first dash and AB86 +# being both follow-up dashes. Haven't found a good sync for the expanding +# rings. +# +# Wyvern's Ouroblade +# The giant halfroom cleave along with spread aoes. Only the first hit +# shows in the timeline. +# +# Early phase: +# AB8B and AB8D are the first hit, each being a different half +# AB8C and AB8E are the secondary follow-up hit similar to chainblade blow +# +# After the power-up: +# B032 and B032 are the first hit, each being a different half +# B033 and B034 are the secondary follow-up hit similar to chainblade blow +# +# AB96 Wyvern's Vengeance +# AB97 Wyvern's Vengeance +# Appears to line up with exaflare hits, but there are so many its hard to +# use as a timeline sync. Ideally we want to sync specific hits for the +# central exaflares. For now, these are excluded from the timeline. +# +# ABB0 Wyvern's Vengeance +# ABB1 Wyvern's Vengeance +# These likely line up to the side coming exaflares, but finding good +# timeline entries for these is difficult. +# +# AB9C Wild Energy +# Appears to be the skill which triggers the aoe explosions on players +# Happens around Ourobalde and during exaflares +# +# AB9E Forged Fury +# AB9F Forged Fury +# AEF8 Forged Fury +# AEF9 Forged Fury +# Power up skill, plus raid wide. +# +# ABA3 Wyvern's Rattle +# Single rolling line aoe, appears along side baited aoes +# +# ABA7 Wrathful Rattle +# Double rolling line aoes, from both vertical and horizontal +# +# ABAB Chainblade Charge +# ABAC Chainblade Charge +# AF0C Chainblade Charge +# Party stack mechanic. First two abilities are wind-up, third is the hit. +# +# ABAD Steeltail Thrust +# AF05 Steeltail Thrust +# AF06 Steeltail Thrust +# B035 Steeltail Thrust +# Tail thrust. One might be the animation while one is thie hit, not sure. +# It looks like damage is on the second skill. +# +# ABAE Roar +# ABAF Roar +# B092 Roar +# Raidwide. B092 might be the larger damage one. +# +# ABB3 Clamorous Chase +# ABB4 Clamorous Chase +# ABB5 Clamorous Chase +# ABB6 Clamorous Chase +# ABB7 Clamorous Chase +# ABB8 Clamorous Chase +# Limit cut. ABB3 and ABB6 happen at the start. ABB4 and ABB7 are the +# dashes, while ABB5 and ABB8 are the wing slices, each depending on which +# direction the initial dashes go. +# +# B077 --sync-- +# Consistently appears to be the jumps to the edge of the map. +# +# ABB2 Forged Fury +# AF4E Forged Fury +# Power up used during enrage. +# 0.0 "--sync--" InCombat { inGameCombat: "1" } window 0,1 +# Opening Raidwide +5.0 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# First Chainblade +13.8 "--sync--" Ability { id: "(AB6F|AB70)", source: "Guardian Arkveld" } +15.0 "Chainblade Blow (1)" Ability { id: "(AB71|B015)", source: "Guardian Arkveld" } +15.4 "--sync--" Ability { id: "(AB72|B016)", source: "Guardian Arkveld" } +18.0 "--sync--" Ability { id: "(AB75|AB76)", source: "Guardian Arkveld" } +19.1 "Chainblade Blow (2)" Ability { id: "(AB77|B017)", source: "Guardian Arkveld" } +19.5 "--sync--" Ability { id: "(AB78|B018)", source: "Guardian Arkveld" } +# Dash + line + party stacks +21.9 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } +30.1 "Siegeflight (dash)" Ability { id: "(AB7B|AB7E)", source: "Guardian Arkveld" } +31.7 "--sync--" Ability { id: "(AB7C|AB7F)", source: "Guardian Arkveld" } +32.5 "--sync--" Ability { id: "(B037|B045)", source: "Guardian Arkveld" } +35.3 "Dragonspark/White Flash" Ability { id: "(AB82|AB83)", source: "Guardian Arkveld" } +# Baited AOEs + lines +36.3 "Wyvern's Rattle" Ability { id: "ABA3", source: "Guardian Arkveld" } +# Dash + line + party stacks +48.2 "Siegeflight (dash)" Ability { id: "(AB7B|AB7E)", source: "Guardian Arkveld" } +49.6 "--sync--" Ability { id: "(AB7C|AB7F)", source: "Guardian Arkveld" } +50.4 "--sync--" Ability { id: "(B037|B045)", source: "Guardian Arkveld" } +53.2 "Dragonspark/White Flash" Ability { id: "(AB82|AB83)", source: "Guardian Arkveld" } +# Second Chainblade +60.0 "--sync--" Ability { id: "(AB6F|AB70)", source: "Guardian Arkveld" } +61.2 "Chainblade Blow (1)" Ability { id: "(AB71|B015)", source: "Guardian Arkveld" } +61.6 "--sync--" Ability { id: "(AB72|B016)", source: "Guardian Arkveld" } +64.2 "--sync--" Ability { id: "(AB75|AB76)", source: "Guardian Arkveld" } +65.3 "Chainblade Blow (2)" Ability { id: "(AB77|B017)", source: "Guardian Arkveld" } +65.7 "--sync--" Ability { id: "(AB78|B018)", source: "Guardian Arkveld" } +# Raidewide +73.0 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to edge +82.8 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# Rush (line dashes + expanding rings) +87.6 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +88.6 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +89.6 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +90.6 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +91.0 "Rush (1)" Ability { id: "AB85", source: "Guardian Arkveld" } +91.6 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +92.6 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +93.2 "Rush (2)" #Ability { id: "AB86", source: "Guardian Arkveld" } +95.2 "Rush (3)" #Ability { id: "AB86", source: "Guardian Arkveld" } +# Jump to center? +101.5 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Half room + aoes +108.7 "Wyvern's Ouroblade" Ability { id: "(AB8B|AB8D)", source: "Guardian Arkveld" } +109.7 "--sync--" Ability { id: "(AB8C|AB8E)", source: "Guardian Arkveld" } +109.9 "Wild Energy (aoes)" #Ability { id: "AB9C", source: "Guardian Arkveld" } +# Tail thrust +116.9 "--sync--" Ability { id: "ABAD", source: "Guardian Arkveld" } +117.5 "Steeltail Thrust" Ability { id: "AF05", source: "Guardian Arkveld" } +# Party Raid stack? +125.6 "--sync--" Ability { id: "ABAB", source: "Guardian Arkveld" } +125.8 "--sync--" Ability { id: "ABAC", source: "Guardian Arkveld" } +127.9 "Chainblade Charge" Ability { id: "AF0C", source: "Guardian Arkveld" } +# Third Chainblade +135.6 "--sync--" Ability { id: "(AB6F|AB70)", source: "Guardian Arkveld" } +136.8 "Chainblade Blow (1)" Ability { id: "(AB71|B015)", source: "Guardian Arkveld" } +137.2 "--sync--" Ability { id: "(AB72|B016)", source: "Guardian Arkveld" } +139.8 "--sync--" Ability { id: "(AB75|AB76)", source: "Guardian Arkveld" } +140.9 "Chainblade Blow (2)" Ability { id: "(AB77|B017)", source: "Guardian Arkveld" } +141.3 "--sync--" Ability { id: "(AB78|B018)", source: "Guardian Arkveld" } +# Raidwide +148.6 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to middle +158.4 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Tower spawn -> bait -> towers +169.4 "--sync--" Ability { id: "AB93", source: "Guardian Arkveld" } +173.1 "Aetheric Resonance" Ability { id: "AB8F", source: "Guardian Arkveld" } +174.4 "Guardian Resonance" Ability { id: "AB90", source: "Guardian Arkveld" } +# Exaflares + Aoes? +# Probably can't sync to the Wyvern's Vengeance due to inconsistency +176.4 "--sync--" Ability { id: "AB53", source: "Guardian Arkveld" } +199.1 "Wild Energy (aoes)" Ability { id: "AB9C", source: "Guardian Arkveld" } +207.1 "Wild Energy (aoes)" Ability { id: "AB9C", source: "Guardian Arkveld" } +# Power up + big raid wide +215.1 "Forged Fury" Ability { id: "AB9E", source: "Guardian Arkveld" } duration 5.2 +217.1 "--sync--" Ability { id: "AB9F", source: "Guardian Arkveld" } +218.0 "--sync--" Ability { id: "AEF8", source: "Guardian Arkveld" } +220.4 "--sync--" Ability { id: "AEF9", source: "Guardian Arkveld" } +# Leap to edge +229.2 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# Raidwide +236.5 "Roar (raidwide)" Ability { id: "B092", source: "Guardian Arkveld" } +# Limit Cut +248.2 "--sync--" Ability { id: "(ABB3|ABB6)", source: "Guardian Arkveld" } +248.4 "Clamorous Chase (1)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +250.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +251.4 "Clamorous Chase (2)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +253.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +254.4 "Clamorous Chase (3)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +256.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +257.4 "Clamorous Chase (4)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +259.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +260.4 "Clamorous Chase (5)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +262.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +263.4 "Clamorous Chase (6)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +265.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +266.4 "Clamorous Chase (7)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +268.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +269.4 "Clamorous Chase (8)" Ability { id: "(ABB4|ABB7)", source: "Guardian Arkveld" } +271.4 "Clamorous Chase (wing)" Ability { id: "(ABB5|ABB8)", source: "Guardian Arkveld" } +# Raid wide +277.4 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to center +286.2 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Towers appear -> Baits -> Towers +297.1 "--sync--" Ability { id: "AB93", source: "Guardian Arkveld" } +300.8 "Aetheric Resonance" Ability { id: "AB8F", source: "Guardian Arkveld" } +302.0 "Guardian Resonance" Ability { id: "AB90", source: "Guardian Arkveld" } +# Some logs don't have this? +303.0 "Greater Resonance" Ability { id: "AB92", source: "Guardian Arkveld" } +# Jump to edge? +304.0 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } +# Line Sweap 1 +314.3 "--sync--" Ability { id: "ABA0", source: "Guardian Arkveld" } +315.1 "Wyvern's Weal" Ability { id: "AB60", source: "Guardian Arkveld" } duration 6.7 +# Line Sweap 2 (Stacks) +321.6 "--sync--" Ability { id: "AB61", source: "Guardian Arkveld" } +323.8 "Wyvern's Weal" Ability { id: "AB60", source: "Guardian Arkveld" } duration 7.6 +330.9 "White Flash" Ability { id: "AB82", source: "Guardian Arkveld" } +# Line Sweap 3 (Aoes) +331.0 "--sync--" Ability { id: "AB61", source: "Guardian Arkveld" } +333.5 "Wyvern's Weal" Ability { id: "AB60", source: "Guardian Arkveld" } duration 7.6 +340.8 "--sync--" Ability { id: "AB61", source: "Guardian Arkveld" } +345.7 "Wild Energy (aoes)" Ability { id: "AB9C", source: "Guardian Arkveld" } +# Autos for a bit +# Raid Wide +358.0 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } window 5,5 +# Jump to edge +361.8 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# Exalines x2 +365.0 "Wrathful Rattle" Ability { id: "ABA7", source: "Guardian Arkveld" } +# Chainblade +375.8 "--sync--" Ability { id: "(B019|B01A)", source: "Guardian Arkveld" } +376.9 "Chainblade Blow (1)" Ability { id: "(B01B|B01E)", source: "Guardian Arkveld" } +377.3 "--sync--" Ability { id: "(B01C|B01F)", source: "Guardian Arkveld" } +379.8 "--sync--" Ability { id: "(B021|B022)", source: "Guardian Arkveld" } +381.1 "Chainblade Blow (2)" Ability { id: "(B023|B026)", source: "Guardian Arkveld" } +381.5 "--sync--" Ability { id: "(B024|B027)", source: "Guardian Arkveld" } +# Raid wide +388.8 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to edge +398.5 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# Dashes + expanding rings +403.2 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +404.1 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +405.1 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +406.1 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +406.5 "Rush (1)" Ability { id: "AB85", source: "Guardian Arkveld" } +407.1 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +408.1 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +408.7 "Rush (2)" #Ability { id: "AB86", source: "Guardian Arkveld" } +410.7 "Rush (3)" #Ability { id: "AB86", source: "Guardian Arkveld" } +# Jump to center +417.4 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Half room cleave + Aoes +423.8 "Wyvern's Ouroblade" Ability { id: "(B031|B032)", source: "Guardian Arkveld" } +424.8 "--sync--" Ability { id: "(B033|B034)", source: "Guardian Arkveld" } +425.0 "Wild Energy (aoes)" Ability { id: "AB9C", source: "Guardian Arkveld" } +# Tail thrust +431.0 "--sync--" Ability { id: "B035", source: "Guardian Arkveld" } +431.6 "Steeltail Thrust" Ability { id: "AF06", source: "Guardian Arkveld" } +# Party Stack +439.8 "--sync--" Ability { id: "ABAB", source: "Guardian Arkveld" } +440.0 "--sync--" Ability { id: "ABAC", source: "Guardian Arkveld" } +442.1 "Chainblade Charge" Ability { id: "AF0C", source: "Guardian Arkveld" } +# Chainblade +450.0 "--sync--" Ability { id: "(B019|B01A)", source: "Guardian Arkveld" } +451.2 "Chainblade Blow (1)" Ability { id: "(B01B|B01E)", source: "Guardian Arkveld" } +451.6 "--sync--" Ability { id: "(B01C|B01F)", source: "Guardian Arkveld" } +454.2 "--sync--" Ability { id: "(B021|B022)", source: "Guardian Arkveld" } +455.3 "Chainblade Blow (2)" Ability { id: "(B023|B026)", source: "Guardian Arkveld" } +455.7 "--sync--" Ability { id: "(B024|B027)", source: "Guardian Arkveld" } +# Raidwide +463.1 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to center +472.9 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Towers spawn -> baits -> Towers +483.9 "--sync--" Ability { id: "AB93", source: "Guardian Arkveld" } +487.6 "Aetheric Resonance" Ability { id: "AB8F", source: "Guardian Arkveld" } +488.8 "Guardian Resonance" Ability { id: "AB90", source: "Guardian Arkveld" } +# Raidwide +495.8 "Roar (raidwide)" Ability { id: "ABAF", source: "Guardian Arkveld" } +# Exaflares from side +# Not sure what to sync here, perhaps abilities don't have Arkveld as the +# source? The Wyvern's Vengeance might be an auto attack or else its timing +# is inconsistent. Perhaps it is related to the tower explosions but the +# timing is off depending on what order the exaflares come? +# Party Stack +515.4 "--sync--" Ability { id: "ABAB", source: "Guardian Arkveld" } window 10,10 +515.6 "--sync--" Ability { id: "ABAC", source: "Guardian Arkveld" } +517.7 "Chainblade Charge" Ability { id: "AF0C", source: "Guardian Arkveld" } +# Chainblade +525.6 "--sync--" Ability { id: "(B019|B01A)", source: "Guardian Arkveld" } +526.8 "Chainblade Blow (1)" Ability { id: "(B01B|B01E)", source: "Guardian Arkveld" } +527.2 "--sync--" Ability { id: "(B01C|B01F)", source: "Guardian Arkveld" } +529.8 "--sync--" Ability { id: "(B021|B022)", source: "Guardian Arkveld" } +530.9 "Chainblade Blow (2)" Ability { id: "(B023|B026)", source: "Guardian Arkveld" } +531.2 "--sync--" Ability { id: "(B024|B027)", source: "Guardian Arkveld" } +# Jump to edge +539.8 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# dash + line + party stacks +547.0 "Siegeflight (dash)" Ability { id: "(B029|B02A)", source: "Guardian Arkveld" } +548.6 "--sync--" Ability { id: "(B02B|B02D)", source: "Guardian Arkveld" } +549.4 "--sync--" Ability { id: "(B030|B046)", source: "Guardian Arkveld" } +552.2 "Dragonspark/White Flash" Ability { id: "(AB82|AB83)", source: "Guardian Arkveld" } +# rolling lines + baited aoes +553.2 "Wyvern's Rattle" Ability { id: "ABA3", source: "Guardian Arkveld" } +# Dash + line + party stacks +563.9 "Siegeflight (dash)" Ability { id: "(B029|B02A)", source: "Guardian Arkveld" } +565.5 "--sync--" Ability { id: "(B02B|B02D)", source: "Guardian Arkveld" } +566.2 "--sync--" Ability { id: "(B030|B046)", source: "Guardian Arkveld" } +569.0 "Dragonspark/White Flash" Ability { id: "(AB82|AB83)", source: "Guardian Arkveld" } +# Chainblade +574.7 "--sync--" Ability { id: "(B019|B01A)", source: "Guardian Arkveld" } +575.9 "Chainblade Blow (1)" Ability { id: "(B01B|B01E)", source: "Guardian Arkveld" } +576.3 "--sync--" Ability { id: "(B01C|B01F)", source: "Guardian Arkveld" } +578.9 "--sync--" Ability { id: "(B021|B022)", source: "Guardian Arkveld" } +580.0 "Chainblade Blow (2)" Ability { id: "(B023|B026)", source: "Guardian Arkveld" } +580.4 "--sync--" Ability { id: "(B024|B027)", source: "Guardian Arkveld" } +# Raid wide +587.7 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump to edge +597.4 "--sync--" Ability { id: "B077", source: "Guardian Arkveld" } window 5,5 +# Dashes + expanding rings +602.3 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +603.3 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +604.3 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +605.3 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +605.7 "Rush (1)" Ability { id: "AB85", source: "Guardian Arkveld" } +606.3 "--sync--" #Ability { id: "AB84", source: "Guardian Arkveld" } +607.3 "--sync--" #Ability { id: "B036", source: "Guardian Arkveld" } +607.9 "Rush (2)" #Ability { id: "AB86", source: "Guardian Arkveld" } +609.8 "Rush (3)" #Ability { id: "AB86", source: "Guardian Arkveld" } +# Jump to center +616.5 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Half room + aoes +622.8 "Wyvern's Ouroblade" Ability { id: "(B031|B032)", source: "Guardian Arkveld" } +623.8 "--sync--" Ability { id: "(B033|B034)", source: "Guardian Arkveld" } +624.0 "Wild Energy (aoes)" Ability { id: "AB9C", source: "Guardian Arkveld" } +# Tail thrust +630.0 "--sync--" Ability { id: "B035", source: "Guardian Arkveld" } +630.6 "Steeltail Thrust" Ability { id: "AF06", source: "Guardian Arkveld" } +# Party stack +638.8 "--sync--" Ability { id: "ABAB", source: "Guardian Arkveld" } +639.0 "--sync--" Ability { id: "ABAC", source: "Guardian Arkveld" } +641.1 "Chainblade Charge" Ability { id: "AF0C", source: "Guardian Arkveld" } +# Chainblade +649.0 "--sync--" Ability { id: "(B019|B01A)", source: "Guardian Arkveld" } +650.2 "Chainblade Blow (1)" Ability { id: "(B01B|B01E)", source: "Guardian Arkveld" } +650.6 "--sync--" Ability { id: "(B01C|B01F)", source: "Guardian Arkveld" } +653.2 "--sync--" Ability { id: "(B021|B022)", source: "Guardian Arkveld" } +654.3 "Chainblade Blow (2)" Ability { id: "(B023|B026)", source: "Guardian Arkveld" } +654.7 "--sync--" Ability { id: "(B024|B027)", source: "Guardian Arkveld" } +# Raidwide +662.1 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Jump center +672.7 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } +# Raidwide +679.1 "Roar (raidwide)" Ability { id: "ABAE", source: "Guardian Arkveld" } +# Likely enrage sequence +682.9 "--sync--" Ability { id: "AB53", source: "Guardian Arkveld" } +692.0 "Forged Fury" Ability { id: "ABB2", source: "Guardian Arkveld" } duration 5.2 +694.0 "--sync--" Ability { id: "AB9F", source: "Guardian Arkveld" } +694.9 "--sync--" Ability { id: "AEF8", source: "Guardian Arkveld" } +697.2 "(enrage)" Ability { id: "AF4E", source: "Guardian Arkveld" } diff --git a/ui/raidboss/data/07-dt/trial/arkveld.ts b/ui/raidboss/data/07-dt/trial/arkveld.ts new file mode 100644 index 00000000..2a50e070 --- /dev/null +++ b/ui/raidboss/data/07-dt/trial/arkveld.ts @@ -0,0 +1,42 @@ +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'TheWindwardWilds', + zoneId: ZoneId.TheWindwardWilds, + timelineFile: 'arkveld.txt', + triggers: [], + timelineReplace: [ + { + 'locale': 'cn', + 'replaceSync': { + 'Guardian Arkveld': '护锁刃龙', + }, + 'replaceText': { + '\\(aoes\\)': '(圆形AOE)', + '\\(dash\\)': '(冲锋)', + '\\(edge\\)': '(场边)', + '\\(raidwide\\)': '(全屏)', + 'Aetheric Resonance': '地脉共振', + 'Chainblade Blow': '锁刃敲打', + 'Chainblade Charge': '锁刃下挥', + 'Forged Fury': '护龙乱击', + 'Guardian Resonance': '护龙共振', + 'Roar': '咆哮', + 'Rush': '突进', + 'Siegeflight': '锁刃飞翔突进', + 'Steeltail Thrust': '龙尾突刺', + 'Wild Energy': '龙光扩散', + 'Wrathful Rattle': '锁哭龙闪·改', + 'Wyvern\'s Ouroblade': '回旋锁刃【龙闪】', + 'Wyvern\'s Rattle': '锁哭龙闪', + 'Wyvern\'s Weal': '龙闪炮', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/trial/arkveld.txt b/ui/raidboss/data/07-dt/trial/arkveld.txt new file mode 100644 index 00000000..d4927d8c --- /dev/null +++ b/ui/raidboss/data/07-dt/trial/arkveld.txt @@ -0,0 +1,321 @@ +### The Windward Wilds +# ZoneId: 1300 + +hideall "--Reset--" +hideall "--sync--" + +0.0 "--Reset--" ActorControl { command: "4000000F" } window 0,100000 jump 0 + +# ALL ENCOUNTER ABILITIES +# +# Probable auto-attacks: +# Wyvern's Radiance +# AB38 AB39 AB3F AB40 AB44 AB45 AB46 AB47 AB51 AB52 AB65 AB66 AB68 AB69 AB6A +# AF07 AF08 B000 B003 B010 B011 +# Used throughout fight at odd times, doesn't seem to line up with +# mechanics. +# +# A94D --sync-- +# Appears at odd times near some jumps, but is inconsistent. +# +# Mechanics: +# +# AB33 -> Jump to center +# B077 --sync-- -> Jump to edge +# +# AB34 Chainblade Blow +# AB35 Chainblade Blow +# AB36 Chainblade Blow +# AB37 Chainblade Blow +# AFFA Chainblade Blow +# AFFB Chainblade Blow +# AFFC Chainblade Blow +# AFFD Chainblade Blow +# AFFE Chainblade Blow +# AFFF Chainblade Blow +# B001 Chainblade Blow +# B002 Chainblade Blow +# Together form the chainblade bow. Some abilities used during early phase, +# while others used after power-up. +# +# One ability is triggered when the hand falls, then two abilities for the +# actual damage. Timeline only shows the first dmage triggers. +# +# AB3A Guardian Siegeflight +# AB3B Guardian Siegeflight +# AB3D Wyvern's Siegeflight +# AB3E Wyvern's Siegeflight +# B00B Guardian Siegeflight +# B00C Wyvern's Siegeflight +# B00D Guardian Siegeflight +# B00F Wyvern's Siegeflight +# B012 --sync-- +# B044 --sync-- +# B014 --sync-- +# B043 --sync-- +# Dash + lines mechanic. Guardian is white line expanding with sides safe, +# while Wyvern's is red line leaving center safe. The first ability appears +# to be the dash, followed by the turn around. +# +# AB48 Wyvern's Ouroblade +# AB49 Wyvern's Ouroblade +# AB4A Wyvern's Ouroblade +# AB4B Wyvern's Ouroblade +# B004 Wyvern's Ouroblade +# B005 Wyvern's Ouroblade +# B006 Wyvern's Ouroblade +# B007 Wyvern's Ouroblade +# Half room cleave, two hits, two variants per phase. Only the first hit +# shows in the timeline. +# +# AB3C Guardian Resonance +# B00E Guardian Resonance +# Lines up with the expanded lines (in or out), but only the white lines +# has this ability. It lines up with the red lines damage, but the timeline +# generator doesn't fill in the red lines ability. +# +# AB4D Guardian Resonance +# AB4E Guardian Resonance +# AB50 Guardian Resonance +# AB4C Aetheric Resonance +# Skills used during towers. +# +# AB41 --sync-- +# B013 --sync-- +# AB42 Rush +# AB43 Rush +# Dashes + expanding rings? Not sure if we have a sync entry for the ring +# explosions. +# +# AB53 --sync-- +# Unsure, something after towers? +# +# AB54 Wyvern's Vengeance +# AB55 Wyvern's Vengeance +# Does seem to be the actual exaflare hits, but we need to identify which +# ones make good log syncs, as there are too many in the generated +# timeline. +# +# AB5A Wild Energy +# Player aoe puddles? +# +# AB5D Forged Fury +# AB5E Forged Fury +# AEF6 Forged Fury +# AEF7 Forged Fury +# Powerup skill, along with raidwide. +# +# AB61 --sync-- +# AB63 Wyvern's Weal +# AFF6 Wyvern's Weal +# AFF7 Wyvern's Weal +# AFF8 Wyvern's Weal +# AFF9 Wyvern's Weal +# Line aoe sweep across arena. + +# AB64 Wyvern's Rattle +# Single rolling line? +# +# AB67 Wrathful Rattle +# Exalines maybe? +# +# AB6B Chainblade Charge +# AB6C Chainblade Charge +# AF0B Chainblade Charge +# Party stack. The last ability is the actual stack, others are part of +# windup. +# +# AB6E Roar - Aoe +# B091 Roar - Aoe +# +# AF04 Steeltail Thrust +# B008 Steeltail Thrust +# Tail thrust. It looks like damage is on the second skill. +# +0.0 "--sync--" InCombat { inGameCombat: "1" } window 0,1 +# Raidwide +5.6 "Roar (raidwide)" Ability { id: "AB6E", source: "Guardian Arkveld" } +# Chainblade Blow x2 +14.4 "--sync--" Ability { id: "(AB34|AB35)", source: "Guardian Arkveld" } +15.6 "Chainblade Blow (1)" Ability { id: "(AB36|AFFA)", source: "Guardian Arkveld" } +15.9 "--sync--" Ability { id: "(AB37|AFFB)", source: "Guardian Arkveld" } +23.3 "--sync--" Ability { id: "(AB34|AB35)", source: "Guardian Arkveld" } +24.5 "Chainblade Blow (2)" Ability { id: "(AB36|AFFA)", source: "Guardian Arkveld" } +24.8 "--sync--" Ability { id: "(AB37|AFFB)", source: "Guardian Arkveld" } +# Jump to edge +31.0 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Dash + lines +39.2 "Siegeflight (dash)" Ability { id: "(AB3A|AB3D)", source: "Guardian Arkveld" } +40.8 "--sync--" Ability { id: "(AB3B|AB3E)", source: "Guardian Arkveld" } +41.5 "--sync--" Ability { id: "(B014|B043)", source: "Guardian Arkveld" } +44.3 "Guardian Resonance" Ability { id: "AB3C", source: "Guardian Arkveld" } +# Rolling line + puddles +44.8 "Wyvern's Rattle" Ability { id: "AB64", source: "Guardian Arkveld" } +# Dash + lines +60.2 "Siegeflight (dash)" Ability { id: "(AB3A|AB3D)", source: "Guardian Arkveld" } +61.6 "--sync--" Ability { id: "(AB3B|AB3E)", source: "Guardian Arkveld" } +62.4 "--sync--" Ability { id: "(B014|B043)", source: "Guardian Arkveld" } +65.3 "Guardian Resonance" Ability { id: "AB3C", source: "Guardian Arkveld" } +# Chainblade Blow +71.3 "--sync--" Ability { id: "(AB34|AB35)", source: "Guardian Arkveld" } +72.5 "Chainblade Blow" Ability { id: "(AB36|AFFA)", source: "Guardian Arkveld" } +72.8 "--sync--" Ability { id: "(AB37|AFFB)", source: "Guardian Arkveld" } +# Jump to edge +85.4 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +90.3 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +91.3 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +92.3 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +93.3 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +93.7 "Rush (1)" Ability { id: "AB42", source: "Guardian Arkveld" } +94.3 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +95.3 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +95.9 "Rush (2)" #Ability { id: "AB43", source: "Guardian Arkveld" } +97.9 "Rush (3)" #Ability { id: "AB43", source: "Guardian Arkveld" } +# Party stack +110.8 "--sync--" Ability { id: "AB6B", source: "Guardian Arkveld" } +111.0 "--sync--" Ability { id: "AB6C", source: "Guardian Arkveld" } +113.1 "Chainblade Charge" Ability { id: "AF0B", source: "Guardian Arkveld" } +# Half room +121.8 "Wyvern's Ouroblade" Ability { id: "(AB48|AB4A)", source: "Guardian Arkveld" } +122.8 "--sync--" Ability { id: "(AB49|AB4B)", source: "Guardian Arkveld" } +132.1 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Towers +144.0 "--sync--" Ability { id: "AB50", source: "Guardian Arkveld" } +147.0 "--sync--" Ability { id: "AB50", source: "Guardian Arkveld" } +148.7 "Aetheric Resonance" Ability { id: "AB4C", source: "Guardian Arkveld" } +149.9 "Guardian Resonance" Ability { id: "(AB4E|AB4D)", source: "Guardian Arkveld" } +# exaflares (could use better syncs) +152.0 "--sync--" Ability { id: "AB53", source: "Guardian Arkveld" } +175.6 "Wild Energy (aoes)" Ability { id: "AB5A", source: "Guardian Arkveld" } +183.8 "Wild Energy (aoes)" Ability { id: "AB5A", source: "Guardian Arkveld" } +# Big raid wide + power up) +192.1 "Forged Fury" Ability { id: "AB5D", source: "Guardian Arkveld" } duration 5.2 +194.1 "--sync--" Ability { id: "AB5E", source: "Guardian Arkveld" } +195.0 "--sync--" Ability { id: "AEF6", source: "Guardian Arkveld" } +197.3 "--sync--" Ability { id: "AEF7", source: "Guardian Arkveld" } +206.1 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Persistant line cleave +213.4 "Roar (raidwide)" Ability { id: "B091", source: "Guardian Arkveld" } +223.6 "Wyvern's Weal" Ability { id: "(AFF6|AFF7)", source: "Guardian Arkveld" } duration 10 +232.8 "--sync--" Ability { id: "AB61", source: "Guardian Arkveld" } +# Aoes +233.1 "Wild Energy (aoes)" Ability { id: "AB5A", source: "Guardian Arkveld" } +# Dash + lines +245.3 "Siegeflight (dash)" Ability { id: "(B00B|B00C)", source: "Guardian Arkveld" } +246.9 "--sync--" Ability { id: "(B00D|B00F)", source: "Guardian Arkveld" } +247.7 "--sync--" Ability { id: "(B012|B044)", source: "Guardian Arkveld" } +250.5 "Guardian Resonance" Ability { id: "B00E", source: "Guardian Arkveld" } +# Chainblade Blow +255.5 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +256.7 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +257.1 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Dash + lines +263.5 "Siegeflight (dash)" Ability { id: "(B00B|B00C)", source: "Guardian Arkveld" } +265.1 "--sync--" Ability { id: "(B00D|B00F)", source: "Guardian Arkveld" } +265.9 "--sync--" Ability { id: "(B012|B044)", source: "Guardian Arkveld" } +268.5 "Guardian Resonance" Ability { id: "B00E", source: "Guardian Arkveld" } +# Chainblade Blow +273.8 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +275.0 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +275.4 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +285.1 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Exalines? +288.3 "Wrathful Rattle" Ability { id: "AB67", source: "Guardian Arkveld" } +299.1 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +300.3 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +300.7 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +317.8 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +322.6 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +323.6 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +324.7 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +325.7 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +326.1 "Rush (1)" Ability { id: "AB42", source: "Guardian Arkveld" } +326.6 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +327.6 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +328.2 "Rush (2)" #Ability { id: "AB43", source: "Guardian Arkveld" } +330.2 "Rush (3)" #Ability { id: "AB43", source: "Guardian Arkveld" } +337.9 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +344.1 "Wyvern's Ouroblade" Ability { id: "(B004|B006)", source: "Guardian Arkveld" } +345.1 "--sync--" Ability { id: "(B005|B007)", source: "Guardian Arkveld" } +# Tail thrust +351.3 "--sync--" Ability { id: "B008", source: "Guardian Arkveld" } +351.9 "Steeltail Thrust" Ability { id: "AF04", source: "Guardian Arkveld" } +# Party Stack +360.1 "--sync--" Ability { id: "AB6B", source: "Guardian Arkveld" } +360.3 "--sync--" Ability { id: "AB6C", source: "Guardian Arkveld" } +362.4 "Chainblade Charge" Ability { id: "AF0B", source: "Guardian Arkveld" } +# Chainblade Blow +369.1 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +370.3 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +370.7 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Towers +379.4 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +391.3 "--sync--" Ability { id: "AB50", source: "Guardian Arkveld" } +394.3 "--sync--" Ability { id: "AB50", source: "Guardian Arkveld" } +396.0 "Aetheric Resonance" Ability { id: "AB4C", source: "Guardian Arkveld" } +397.2 "Guardian Resonance" Ability { id: "(AB4D|AB4E)", source: "Guardian Arkveld" } +399.3 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Rolling line? +401.6 "Wyvern's Rattle" Ability { id: "AB64", source: "Guardian Arkveld" } +# Dash + lines +412.2 "Siegeflight (dash)" Ability { id: "(B00B|B00C)", source: "Guardian Arkveld" } +413.6 "--sync--" Ability { id: "(B00D|B00F)", source: "Guardian Arkveld" } +414.1 "--sync--" Ability { id: "(B012|B044)", source: "Guardian Arkveld" } +417.1 "Guardian Resonance" Ability { id: "B00E", source: "Guardian Arkveld" } +# Chainblade Blow +422.0 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +423.2 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +423.6 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Dash + lines +430.0 "Siegeflight (dash)" Ability { id: "(B00B|B00C)", source: "Guardian Arkveld" } +431.6 "--sync--" Ability { id: "(B00D|B00F)", source: "Guardian Arkveld" } +432.4 "--sync--" Ability { id: "(B012|B044)", source: "Guardian Arkveld" } +435.3 "Guardian Resonance" Ability { id: "B00E", source: "Guardian Arkveld" } +# Chainblade Blow +440.3 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +441.5 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +441.9 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Raidwide +450.3 "Roar (raidwide)" Ability { id: "AB6E", source: "Guardian Arkveld" } +455.1 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Sweepling line Aoe +464.0 "Wyvern's Weal" Ability { id: "(AFF6|AFF7)", source: "Guardian Arkveld" } duration 10 +473.2 "--sync--" Ability { id: "AB61", source: "Guardian Arkveld" } +473.5 "Wild Energy (aoes)" Ability { id: "AB5A", source: "Guardian Arkveld" } +481.6 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +# Exalines? +484.9 "Wrathful Rattle" Ability { id: "AB67", source: "Guardian Arkveld" } +# Chainblade Blow +495.7 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +496.9 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +497.3 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Dashes + expanding rings? +514.1 "(edge)" Ability { id: "B077", source: "Guardian Arkveld" } +518.8 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +519.8 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +520.9 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +521.9 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +522.3 "Rush (1)" Ability { id: "AB42", source: "Guardian Arkveld" } +522.8 "--sync--" #Ability { id: "AB41", source: "Guardian Arkveld" } +523.8 "--sync--" #Ability { id: "B013", source: "Guardian Arkveld" } +524.4 "Rush (2)" #Ability { id: "AB43", source: "Guardian Arkveld" } +526.3 "Rush (3)" #Ability { id: "AB43", source: "Guardian Arkveld" } +534.2 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 +# Halfroom +540.4 "Wyvern's Ouroblade" Ability { id: "(B004|B006)", source: "Guardian Arkveld" } +541.4 "--sync--" Ability { id: "(B005|B007)", source: "Guardian Arkveld" } +# Tail thrust +547.6 "--sync--" Ability { id: "B008", source: "Guardian Arkveld" } +548.2 "Steeltail Thrust" Ability { id: "AF04", source: "Guardian Arkveld" } +# Party stack +556.3 "--sync--" Ability { id: "AB6B", source: "Guardian Arkveld" } +556.5 "--sync--" Ability { id: "AB6C", source: "Guardian Arkveld" } +558.6 "Chainblade Charge" Ability { id: "AF0B", source: "Guardian Arkveld" } +# Chainblade Blow +562.7 "--sync--" Ability { id: "(AFFC|AFFD)", source: "Guardian Arkveld" } +563.9 "Chainblade Blow" Ability { id: "(AFFE|B001)", source: "Guardian Arkveld" } +564.3 "--sync--" Ability { id: "(AFFF|B002)", source: "Guardian Arkveld" } +# Raidwide +588.3 "Roar (raidwide)" Ability { id: "AB6E", source: "Guardian Arkveld" } +# Jump to center +592.1 "(center)" Ability { id: "AB33", source: "Guardian Arkveld" } window 5,5 From e6c052c53fcad4356bbfaf4e0364162c914fc841 Mon Sep 17 00:00:00 2001 From: ksh Date: Sat, 29 Nov 2025 11:18:42 +0900 Subject: [PATCH 2/2] chore: update version to 99.171.0 and adjust Visual Studio path for 2026 --- build/build.ps1 | 2 +- package.json | 2 +- plugin/CactbotEventSource/Properties/AssemblyInfo.cs | 4 ++-- plugin/CactbotOverlay/Properties/AssemblyInfo.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/build.ps1 b/build/build.ps1 index 3e978ae7..22d3cca5 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -229,7 +229,7 @@ if ($in -eq '9999') # 플러그인 빌드 if ($in -eq '1') { - $vspath = "C:\Program Files\Microsoft Visual Studio\2022\Community" + $vspath = "C:\Program Files\Microsoft Visual Studio\18\Community" $ENV:PATH = "$vspath\MSBuild\Current\Bin;${ENV:PATH}"; msbuild -p:Configuration=Release -p:Platform=x64 "..\plugin\Cactbot.sln" -t:rebuild if (-not $?) { diff --git a/package.json b/package.json index 64c173f9..069e43f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cactbot-build", - "version": "99.170.0", + "version": "99.171.0", "license": "Apache-2.0", "type": "module", "types": "./types", diff --git a/plugin/CactbotEventSource/Properties/AssemblyInfo.cs b/plugin/CactbotEventSource/Properties/AssemblyInfo.cs index ca849fdf..de45ef41 100644 --- a/plugin/CactbotEventSource/Properties/AssemblyInfo.cs +++ b/plugin/CactbotEventSource/Properties/AssemblyInfo.cs @@ -21,5 +21,5 @@ // - Revision // GitHub has only 3 version components, so Revision should always be 0. // CactbotOverlay and CactbotEventSource version should match. -[assembly: AssemblyVersion("99.170.0.0")] -[assembly: AssemblyFileVersion("99.170.0.0")] +[assembly: AssemblyVersion("99.171.0.0")] +[assembly: AssemblyFileVersion("99.171.0.0")] diff --git a/plugin/CactbotOverlay/Properties/AssemblyInfo.cs b/plugin/CactbotOverlay/Properties/AssemblyInfo.cs index d544097c..8d2f34a9 100644 --- a/plugin/CactbotOverlay/Properties/AssemblyInfo.cs +++ b/plugin/CactbotOverlay/Properties/AssemblyInfo.cs @@ -20,5 +20,5 @@ // - Build Number // - Revision // GitHub has only 3 version components, so Revision should always be 0. -[assembly: AssemblyVersion("99.170.0.0")] -[assembly: AssemblyFileVersion("99.170.0.0")] +[assembly: AssemblyVersion("99.171.0.0")] +[assembly: AssemblyFileVersion("99.171.0.0")]