|
| 1 | +import Conditions from '../../../../../resources/conditions'; |
| 2 | +import Outputs from '../../../../../resources/outputs'; |
| 3 | +import { callOverlayHandler } from '../../../../../resources/overlay_plugin_api'; |
| 4 | +import { Responses } from '../../../../../resources/responses'; |
| 5 | +import { Directions } from '../../../../../resources/util'; |
1 | 6 | import ZoneId from '../../../../../resources/zone_id'; |
2 | 7 | import { RaidbossData } from '../../../../../types/data'; |
3 | 8 | import { TriggerSet } from '../../../../../types/trigger'; |
4 | 9 |
|
5 | | -export type Data = RaidbossData; |
| 10 | +export interface Data extends RaidbossData { |
| 11 | + reignDir?: number; |
| 12 | + moonbeamBites: number[]; |
| 13 | + moonbeamBitesTracker: number; |
| 14 | + moonlightQuadrant2?: string; |
| 15 | +} |
| 16 | + |
| 17 | +const centerX = 100; |
| 18 | +const centerY = 100; |
| 19 | + |
| 20 | +const headMarkerData = { |
| 21 | + // Shared tankbuster marker used for Great Divide |
| 22 | + 'tankbuster': '0256', |
| 23 | + // Stack marker used for first two Heavensearths |
| 24 | + 'stack': '003E', |
| 25 | + // Stack marker used for Heavensearth in Tactical Pack |
| 26 | + 'stackAdds': '005D', |
| 27 | + // Spread marker used for Gust in Tactical Pack |
| 28 | + 'spread': '0178', |
| 29 | + // Big, pulsing, 4-arrow stack marker for Tracking Tremors |
| 30 | + 'eightHitStack': '013C', |
| 31 | +} as const; |
| 32 | + |
| 33 | +const moonlightOutputStrings = { |
| 34 | + ...Directions.outputStrings8Dir, |
| 35 | + safeQuad: { |
| 36 | + en: '${quad}', |
| 37 | + de: '${quad}', |
| 38 | + ja: '${quad}', |
| 39 | + cn: '${quad}', |
| 40 | + ko: '${quad}', |
| 41 | + }, |
| 42 | + safeQuadrants: { |
| 43 | + en: '${quad1} => ${quad2}', |
| 44 | + de: '${quad1} => ${quad2}', |
| 45 | + ja: '${quad1} => ${quad2}', |
| 46 | + cn: '${quad1} => ${quad2}', |
| 47 | + ko: '${quad1} => ${quad2}', |
| 48 | + }, |
| 49 | +}; |
6 | 50 |
|
7 | 51 | const triggerSet: TriggerSet<Data> = { |
8 | 52 | id: 'AacCruiserweightM4', |
9 | 53 | zoneId: ZoneId.AacCruiserweightM4, |
10 | 54 | timelineFile: 'r8n.txt', |
11 | | - triggers: [], |
| 55 | + initData: () => ({ |
| 56 | + moonbeamBites: [], |
| 57 | + moonbeamBitesTracker: 0, |
| 58 | + }), |
| 59 | + triggers: [ |
| 60 | + { |
| 61 | + id: 'R8N Extraplanar Pursuit', |
| 62 | + type: 'StartsUsing', |
| 63 | + netRegex: { id: 'A38E', source: 'Howling Blade', capture: false }, |
| 64 | + response: Responses.aoe(), |
| 65 | + }, |
| 66 | + { |
| 67 | + id: 'R8N Great Divide', |
| 68 | + type: 'HeadMarker', |
| 69 | + netRegex: { id: [headMarkerData.tankbuster], capture: true }, |
| 70 | + response: Responses.sharedTankBuster(), |
| 71 | + }, |
| 72 | + { |
| 73 | + id: 'R8N Wolves\' Reign', |
| 74 | + type: 'StartsUsing', |
| 75 | + // A96D and A96E are used in first part of fight |
| 76 | + // A90F or A910 is used after adds |
| 77 | + // Normal mode is always in |
| 78 | + netRegex: { id: ['A96D', 'A96E', 'A90F', 'A910'], source: 'Howling Blade', capture: false }, |
| 79 | + infoText: (_data, _matches, output) => { |
| 80 | + return output.inLater!(); |
| 81 | + }, |
| 82 | + outputStrings: { |
| 83 | + inLater: { |
| 84 | + en: '(In Later)', |
| 85 | + de: '(später Rein)', |
| 86 | + ja: '(あとで内側)', |
| 87 | + cn: '(稍后内侧)', |
| 88 | + ko: '(나중에 안)', |
| 89 | + }, |
| 90 | + }, |
| 91 | + }, |
| 92 | + { |
| 93 | + id: 'R8N Wolves\' Reign Direction', |
| 94 | + type: 'StartsUsing', |
| 95 | + netRegex: { id: ['A96D', 'A96E', 'A90F', 'A910'], source: 'Howling Blade', capture: true }, |
| 96 | + delaySeconds: (_data, matches) => parseFloat(matches.castTime) + 1.2, |
| 97 | + promise: async (data, matches) => { |
| 98 | + const actors = (await callOverlayHandler({ |
| 99 | + call: 'getCombatants', |
| 100 | + ids: [parseInt(matches.sourceId, 16)], |
| 101 | + })).combatants; |
| 102 | + const actor = actors[0]; |
| 103 | + if (actors.length !== 1 || actor === undefined) { |
| 104 | + console.error( |
| 105 | + `R8N Wolves' Reign Direction: Wrong actor count ${actors.length}`, |
| 106 | + ); |
| 107 | + return; |
| 108 | + } |
| 109 | + |
| 110 | + data.reignDir = (Directions.hdgTo16DirNum(actor.Heading) + 8) % 16; |
| 111 | + }, |
| 112 | + infoText: (data, _matches, output) => { |
| 113 | + const dir = output[Directions.output16Dir[data.reignDir ?? -1] ?? 'unknown']!(); |
| 114 | + return output.inDir!({ dir: dir }); |
| 115 | + }, |
| 116 | + run: (data) => { |
| 117 | + data.reignDir = undefined; |
| 118 | + }, |
| 119 | + outputStrings: { |
| 120 | + ...Directions.outputStrings16Dir, |
| 121 | + inDir: { |
| 122 | + en: 'In ${dir}', |
| 123 | + de: 'Rein ${dir}', |
| 124 | + ja: '内側 ${dir}', |
| 125 | + cn: '内侧 ${dir}', |
| 126 | + ko: '${dir} 안', |
| 127 | + }, |
| 128 | + }, |
| 129 | + }, |
| 130 | + { |
| 131 | + id: 'R8N Heavensearth', |
| 132 | + type: 'HeadMarker', |
| 133 | + netRegex: { id: [headMarkerData.stack, headMarkerData.stackAdds], capture: true }, |
| 134 | + response: Responses.stackMarkerOn(), |
| 135 | + }, |
| 136 | + { |
| 137 | + id: 'R8N Beckon Moonlight Quadrants', |
| 138 | + type: 'Ability', |
| 139 | + // First set is constant, later sets use different spell ids |
| 140 | + // A97E/A3DE => Right cleave self-cast |
| 141 | + // A97F/A3DF => Left cleave self-cast |
| 142 | + netRegex: { id: ['A97E', 'A3DE', 'A97F', 'A3DF'], source: 'Moonlit Shadow', capture: true }, |
| 143 | + delaySeconds: 0.1, |
| 144 | + durationSeconds: 10, |
| 145 | + promise: async (data, matches) => { |
| 146 | + const actors = (await callOverlayHandler({ |
| 147 | + call: 'getCombatants', |
| 148 | + ids: [parseInt(matches.sourceId, 16)], |
| 149 | + })).combatants; |
| 150 | + const actor = actors[0]; |
| 151 | + if (actors.length !== 1 || actor === undefined) { |
| 152 | + console.error( |
| 153 | + `R8N Beckon Moonlight Quadrants: Wrong actor count ${actors.length}`, |
| 154 | + ); |
| 155 | + return; |
| 156 | + } |
| 157 | + |
| 158 | + const dirNum = Directions.xyTo8DirNum(actor.PosX, actor.PosY, centerX, centerY); |
| 159 | + // Moonbeam's Bite (A97C/A376 Left / A97D/A377 Right) half-room cleaves |
| 160 | + // Defining the cleaved side |
| 161 | + if (matches.id === 'A97E' || matches.id === 'A3DE') { |
| 162 | + const counterclock = dirNum === 0 ? 6 : dirNum - 2; |
| 163 | + data.moonbeamBites.push(counterclock); |
| 164 | + } |
| 165 | + if (matches.id === 'A97F' || matches.id === 'A3DF') { |
| 166 | + const clockwise = (dirNum + 2) % 8; |
| 167 | + data.moonbeamBites.push(clockwise); |
| 168 | + } |
| 169 | + }, |
| 170 | + infoText: (data, _matches, output) => { |
| 171 | + if (data.moonbeamBites.length === 1 || data.moonbeamBites.length === 3) |
| 172 | + return; |
| 173 | + |
| 174 | + const quadrants = [1, 3, 5, 7]; |
| 175 | + const moonbeam1 = data.moonbeamBites[0] ?? -1; |
| 176 | + const moonbeam2 = data.moonbeamBites[1] ?? -1; |
| 177 | + let safeQuads1 = quadrants.filter((quadrant) => { |
| 178 | + return quadrant !== moonbeam1 + 1; |
| 179 | + }); |
| 180 | + safeQuads1 = safeQuads1.filter((quadrant) => { |
| 181 | + return quadrant !== (moonbeam1 === 0 ? 7 : moonbeam1 - 1); |
| 182 | + }); |
| 183 | + safeQuads1 = safeQuads1.filter((quadrant) => { |
| 184 | + return quadrant !== moonbeam2 + 1; |
| 185 | + }); |
| 186 | + safeQuads1 = safeQuads1.filter((quadrant) => { |
| 187 | + return quadrant !== (moonbeam2 === 0 ? 7 : moonbeam2 - 1); |
| 188 | + }); |
| 189 | + |
| 190 | + // Early output for first two |
| 191 | + if (data.moonbeamBites.length === 2) { |
| 192 | + if (safeQuads1.length !== 1 || safeQuads1[0] === undefined) { |
| 193 | + console.error( |
| 194 | + `R8N Beckon Moonlight Quadrants: Invalid safeQuads1, length of ${safeQuads1.length}.`, |
| 195 | + ); |
| 196 | + return; |
| 197 | + } |
| 198 | + const quad = output[Directions.outputFrom8DirNum(safeQuads1[0] ?? -1)]!(); |
| 199 | + return output.safeQuad!({ quad: quad }); |
| 200 | + } |
| 201 | + |
| 202 | + const moonbeam3 = data.moonbeamBites[2] ?? -1; |
| 203 | + const moonbeam4 = data.moonbeamBites[3] ?? -1; |
| 204 | + let safeQuads2 = quadrants.filter((quadrant) => { |
| 205 | + return quadrant !== moonbeam3 + 1; |
| 206 | + }); |
| 207 | + safeQuads2 = safeQuads2.filter((quadrant) => { |
| 208 | + return quadrant !== (moonbeam3 === 0 ? 7 : moonbeam3 - 1); |
| 209 | + }); |
| 210 | + safeQuads2 = safeQuads2.filter((quadrant) => { |
| 211 | + return quadrant !== moonbeam4 + 1; |
| 212 | + }); |
| 213 | + safeQuads2 = safeQuads2.filter((quadrant) => { |
| 214 | + return quadrant !== (moonbeam4 === 0 ? 7 : moonbeam4 - 1); |
| 215 | + }); |
| 216 | + |
| 217 | + if (safeQuads1[0] === undefined || safeQuads2[0] === undefined) { |
| 218 | + console.error( |
| 219 | + `R8N Beckon Moonlight Quadrants: First safeQuads missing`, |
| 220 | + ); |
| 221 | + return; |
| 222 | + } |
| 223 | + if (safeQuads1.length !== 1) { |
| 224 | + console.error( |
| 225 | + `R8N Beckon Moonlight Quadrants: Invalid safeQuads1, length of ${safeQuads1.length}`, |
| 226 | + ); |
| 227 | + return; |
| 228 | + } |
| 229 | + if (safeQuads2.length !== 1) { |
| 230 | + console.error( |
| 231 | + `R8N Beckon Moonlight Quadrants: Invalid safeQuads2, length of ${safeQuads2.length}`, |
| 232 | + ); |
| 233 | + return; |
| 234 | + } |
| 235 | + |
| 236 | + // Store quadrant for move call |
| 237 | + data.moonlightQuadrant2 = output[Directions.outputFrom8DirNum(safeQuads2[0] ?? -1)]!(); |
| 238 | + |
| 239 | + const quad1 = output[Directions.outputFrom8DirNum(safeQuads1[0] ?? -1)]!(); |
| 240 | + return output.safeQuadrants!({ quad1: quad1, quad2: data.moonlightQuadrant2 }); |
| 241 | + }, |
| 242 | + outputStrings: moonlightOutputStrings, |
| 243 | + }, |
| 244 | + { |
| 245 | + id: 'R8N Beckon Moonlight Quadrant Two', |
| 246 | + type: 'StartsUsing', |
| 247 | + // A97C/A376 => Moonbeam's Bite dash with Left cleave |
| 248 | + // A97D/A377 => Moonbeam's Bite dash with Right cleave |
| 249 | + netRegex: { id: ['A97C', 'A376', 'A97D', 'A377'], source: 'Moonlit Shadow', capture: true }, |
| 250 | + condition: (data) => { |
| 251 | + data.moonbeamBitesTracker = data.moonbeamBitesTracker + 1; |
| 252 | + if (data.moonbeamBitesTracker === 2) |
| 253 | + return true; |
| 254 | + return false; |
| 255 | + }, |
| 256 | + delaySeconds: (_data, matches) => parseFloat(matches.castTime), |
| 257 | + infoText: (data, _matches, output) => { |
| 258 | + return output.safeQuad!({ quad: data.moonlightQuadrant2 }); |
| 259 | + }, |
| 260 | + run: (data) => { |
| 261 | + // Reset for additional Beckon Moonlights |
| 262 | + if (data.moonbeamBitesTracker === 4) { |
| 263 | + data.moonbeamBites = []; |
| 264 | + delete data.moonlightQuadrant2; |
| 265 | + data.moonbeamBitesTracker = 0; |
| 266 | + } |
| 267 | + }, |
| 268 | + outputStrings: moonlightOutputStrings, |
| 269 | + }, |
| 270 | + { |
| 271 | + id: 'R8N Shadowchase', |
| 272 | + // A981 cast before adds |
| 273 | + // A374 cast after adds |
| 274 | + type: 'StartsUsing', |
| 275 | + netRegex: { id: ['A981', 'A374'], source: 'Howling Blade', capture: false }, |
| 276 | + suppressSeconds: 1, |
| 277 | + alertText: (_data, _matches, output) => { |
| 278 | + return output.behindClones!(); |
| 279 | + }, |
| 280 | + outputStrings: { |
| 281 | + behindClones: { |
| 282 | + en: 'Behind Clones', |
| 283 | + }, |
| 284 | + }, |
| 285 | + }, |
| 286 | + { |
| 287 | + id: 'R8N Gust', |
| 288 | + type: 'HeadMarker', |
| 289 | + netRegex: { id: [headMarkerData.spread], capture: true }, |
| 290 | + condition: Conditions.targetIsYou(), |
| 291 | + response: Responses.spread(), |
| 292 | + }, |
| 293 | + { |
| 294 | + id: 'R8N Ravenous Saber', |
| 295 | + type: 'StartsUsing', |
| 296 | + netRegex: { id: 'A743', source: 'Howling Blade', capture: false }, |
| 297 | + durationSeconds: 7, |
| 298 | + response: Responses.bigAoe(), |
| 299 | + }, |
| 300 | + { |
| 301 | + id: 'R8N Weal of Stone', |
| 302 | + // TODO: Add direction such as Avoid lines from ${dir} |
| 303 | + // There are two casts: A989 and A988 |
| 304 | + type: 'StartsUsing', |
| 305 | + netRegex: { id: 'A989', source: 'Wolf of Stone', capture: false }, |
| 306 | + suppressSeconds: 1, |
| 307 | + alertText: (_data, _matches, output) => { |
| 308 | + return output.lines!(); |
| 309 | + }, |
| 310 | + outputStrings: { |
| 311 | + lines: { |
| 312 | + en: 'Avoid Lines', |
| 313 | + de: 'Vermeide Linien', |
| 314 | + ja: '直線攻撃を避ける', |
| 315 | + cn: '躲避直线 AoE', |
| 316 | + ko: '직선장판 피하기', |
| 317 | + }, |
| 318 | + }, |
| 319 | + }, |
| 320 | + { |
| 321 | + id: 'R8N Shadowchase Rotate', |
| 322 | + // Call to move behind Dragon Head after clones dash |
| 323 | + // These only spawn after adds |
| 324 | + // Dragon Heads cast Roaring Wind (A984 and A985) |
| 325 | + type: 'StartsUsing', |
| 326 | + netRegex: { id: 'A374', source: 'Howling Blade', capture: true }, |
| 327 | + delaySeconds: (_data, matches) => parseFloat(matches.castTime), |
| 328 | + suppressSeconds: 1, |
| 329 | + infoText: (_data, _matches, output) => { |
| 330 | + return output.rotate!(); |
| 331 | + }, |
| 332 | + outputStrings: { |
| 333 | + rotate: { |
| 334 | + en: 'Rotate', |
| 335 | + de: 'Rotieren', |
| 336 | + ja: '回転', |
| 337 | + cn: '旋转', |
| 338 | + ko: '회전', |
| 339 | + }, |
| 340 | + }, |
| 341 | + }, |
| 342 | + { |
| 343 | + id: 'R8N Tracking Tremors', |
| 344 | + type: 'StartsUsing', |
| 345 | + netRegex: { id: 'A4E2', source: 'Howling Blade', capture: false }, |
| 346 | + durationSeconds: 10, |
| 347 | + infoText: (_data, _matches, output) => output.text!(), |
| 348 | + outputStrings: { |
| 349 | + text: { |
| 350 | + en: 'Stack x5', |
| 351 | + de: 'Sammeln x5', |
| 352 | + fr: 'Package x5', |
| 353 | + ja: '頭割り x5', |
| 354 | + cn: '5次分摊', |
| 355 | + ko: '쉐어 5번', |
| 356 | + }, |
| 357 | + }, |
| 358 | + }, |
| 359 | + { |
| 360 | + id: 'R8N Weal of Stone Cardinals', |
| 361 | + // There are two casts and cardinals is always safe: |
| 362 | + // A98C Weal of Stone |
| 363 | + // A98D Weal of Stone |
| 364 | + type: 'StartsUsing', |
| 365 | + netRegex: { id: 'A98D', source: 'Wolf of Stone', capture: false }, |
| 366 | + suppressSeconds: 1, |
| 367 | + alertText: (_data, _matches, output) => { |
| 368 | + return output.cardinals!(); |
| 369 | + }, |
| 370 | + outputStrings: { |
| 371 | + cardinals: Outputs.cardinals, |
| 372 | + }, |
| 373 | + }, |
| 374 | + ], |
12 | 375 | }; |
13 | 376 |
|
14 | 377 | export default triggerSet; |
0 commit comments