From 1c56b9b79e7c91fbe458691d2ad34d81388670fa Mon Sep 17 00:00:00 2001 From: Wexx <86693821+wexxlee@users.noreply.github.com> Date: Mon, 2 Sep 2024 20:29:32 -0700 Subject: [PATCH] raidboss: add The Forecaster (S-rank: Living Memory) (#401) --- ui/raidboss/data/07-dt/hunts/living_memory.ts | 105 +++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/ui/raidboss/data/07-dt/hunts/living_memory.ts b/ui/raidboss/data/07-dt/hunts/living_memory.ts index 01db41a0e5..f065c37da4 100644 --- a/ui/raidboss/data/07-dt/hunts/living_memory.ts +++ b/ui/raidboss/data/07-dt/hunts/living_memory.ts @@ -5,8 +5,7 @@ import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; -// TODO: Add triggers for The Forecaster (S-Rank) -// TODO: Code Execution/Reverse Code: possibly add individual step callouts? +// TODO: Sally - Code Execution/Reverse Code: possibly add individual step callouts? type ExecutionSafe = 'out' | 'in' | 'cardinals'; const executionIdToSafeMap: { [id: string]: ExecutionSafe } = { @@ -22,8 +21,28 @@ const executionOutputStrings = { next: Outputs.next, } as const; +type ForecastSafe = 'under' | 'out' | 'behind' | 'intercards'; +const forecastNpcYellMap: { [id: string]: ForecastSafe[] } = { + '425E': ['under', 'out', 'intercards'], // "Reacquiring data... Searing sunshine will give way to electric storms, followed by the coldest of cold snaps!" + '425F': ['out', 'behind', 'under'], // "Reacquiring data... Thunderclouds will give way to ferocious gales then sizzling heat!" + '4260': ['intercards', 'out', 'behind'], // "Reacquiring data... Blinding blizzards will turn to lightning storms, followed by tremendous gales!" + '4261': ['behind', 'under', 'out'], // "Reacquiring data... A howling tempest will stoke raging wildfires, followed by an almighty thunderstorm!" +}; +const forecastNpcYellIds = Object.keys(forecastNpcYellMap); + +const climateChangeNpcYellMap: { [id: string]: Partial> } = { + // id: { replacedEffect: newEffect } + '428A': { 'intercards': 'under' }, // " ErRoR! Revising forecast... Expect wildfire conditions, not wintry woe!" + '428B': { 'behind': 'intercards' }, // " eRrOr! Revising forecast... Expect blizzard conditions, not violent zephyrs!" + '428C': { 'under': 'out' }, // " erRoR! Revising forecast... Expect hyperelectricity, not searing sunshine!" + '428D': { 'out': 'behind' }, // " ErROr! Revising forecast... Expect gale-force winds, not shocking storms!" +}; +const climateChangeNpcYellIds = Object.keys(climateChangeNpcYellMap); +const weatherChannelCastIds = ['967C', '967E', '9680', '9682']; + export interface Data extends RaidbossData { executionSafe: ExecutionSafe[]; + forecastSafe: ForecastSafe[]; } const triggerSet: TriggerSet = { @@ -31,6 +50,7 @@ const triggerSet: TriggerSet = { zoneId: ZoneId.LivingMemory, initData: () => ({ executionSafe: [], + forecastSafe: [], }), triggers: [ // ****** A-RANK: Cat's Eye ****** // @@ -85,6 +105,7 @@ const triggerSet: TriggerSet = { }, }, }, + // ****** A-RANK: Sally the Sweeper ****** // { id: 'Hunt Sally Execution Model Collect', @@ -127,7 +148,87 @@ const triggerSet: TriggerSet = { run: (data) => data.executionSafe = [], outputStrings: executionOutputStrings, }, + // ****** S-RANK: The Forecaster ****** // + { + id: 'Hunt The Forecaster Wildfire Conditions', + type: 'StartsUsing', + netRegex: { id: '9684', source: 'The Forecaster', capture: false }, + response: Responses.getUnder('alert'), + }, + { + id: 'Hunt The Forecaster Hyperelectricity', + type: 'StartsUsing', + netRegex: { id: '9685', source: 'The Forecaster', capture: false }, + response: Responses.outOfMelee('alert'), + }, + { + id: 'Hunt The Forecaster Gale-force Winds', + type: 'StartsUsing', + netRegex: { id: '9686', source: 'The Forecaster', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'Hunt The Forecaster Blizzard Conditions', + type: 'StartsUsing', + netRegex: { id: '9687', source: 'The Forecaster', capture: false }, + alertText: (_data, _matches, output) => output.intercards!(), + outputStrings: { + intercards: Outputs.intercards, + }, + }, + // Forecaster announces three weather effects via `NpcYell`, and then applies 3 buffs + // with 'Forecast'. (There are only 4 possible sequences based on `NpcYell` entries.) + // Forecaster may also follow with an `NpcYell` message indicating one of the 3 buffs + // will be swapped to a different effect, and will use 'Climate Change' to apply a new + // buff indicating the new effect (although the old buff remains active). + // Again, based on NpcYell entries, however, there are only 4 possible substitutions. + // So we can get all we need from the initial (and, if present, subsequent) NpcYell message. + { + id: 'Hunt The Forecaster Forecast Collect', + type: 'NpcYell', + netRegex: { npcNameId: '347D', npcYellId: forecastNpcYellIds }, + run: (data, matches) => { + const safe = forecastNpcYellMap[matches.npcYellId]; + if (safe === undefined) + throw new UnreachableCode(); + data.forecastSafe = safe; + }, + }, + { + id: 'Hunt The Forecaster Climate Change Collect', + type: 'NpcYell', + netRegex: { npcNameId: '347D', npcYellId: climateChangeNpcYellIds }, + run: (data, matches) => { + const swapMap = climateChangeNpcYellMap[matches.npcYellId]; + if (swapMap === undefined) + throw new UnreachableCode(); + data.forecastSafe = data.forecastSafe.map((effect) => swapMap[effect] || effect); + }, + }, + { + id: 'Hunt The Forecaster Weather Channel', + type: 'StartsUsing', + // There are 4 possible cast ids for Weather Channel, and each corresponds to + // the first attack in the sequence. But due to Climate Change, we can't reliably + // use the cast id to predict the final sequence, so just trigger on all of them. + netRegex: { id: weatherChannelCastIds, source: 'The Forecaster', capture: false }, + durationSeconds: 11, + alertText: (data, _matches, output) => { + const safe = data.forecastSafe; + if (safe.length !== 3) + return; + return safe.map((spot) => output[spot]!()).join(output.next!()); + }, + run: (data) => data.forecastSafe = [], + outputStrings: { + under: Outputs.getUnder, + out: Outputs.out, + behind: Outputs.getBehind, + intercards: Outputs.intercards, + next: Outputs.next, + }, + }, ], timelineReplace: [ {