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