diff --git a/build/@.txt b/build/@.txt new file mode 100644 index 00000000..761c6a7e --- /dev/null +++ b/build/@.txt @@ -0,0 +1,9 @@ +🔙🔚🔛🔜🔝 +⊕︎⊗︎⌾︎⏺︎▢︎◉︎◎︎●︎◯︎☀︎☯︎☹︎☺︎☻︎☼︎🞅🞆🞇🞈🞉 +🔴🟠🟡🟢🔵🟣🟤⚪⚫ +🟥🟧🟨🟩🟦🟪🟫◻️◼️▪️▫️⬛⬜ +🠜🠝🠞🠟 +🡄🡅🡆🡇 +🡐🡑🡒🡓 +🡸🡹🡺🡻🡼🡽🡾🡿 +🢀🢁🢂🢃🢄🢅🢆🢇 diff --git a/package.json b/package.json index f27472f7..68530e28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cactbot-build", - "version": "99.166.0", + "version": "99.168.0", "license": "Apache-2.0", "type": "module", "types": "./types", diff --git a/plugin/CactbotEventSource/Properties/AssemblyInfo.cs b/plugin/CactbotEventSource/Properties/AssemblyInfo.cs index b88079e9..449e5782 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.166.0.0")] -[assembly: AssemblyFileVersion("99.166.0.0")] +[assembly: AssemblyVersion("99.168.0.0")] +[assembly: AssemblyFileVersion("99.168.0.0")] diff --git a/plugin/CactbotOverlay/Properties/AssemblyInfo.cs b/plugin/CactbotOverlay/Properties/AssemblyInfo.cs index 5b68b41e..8378e11c 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.166.0.0")] -[assembly: AssemblyFileVersion("99.166.0.0")] +[assembly: AssemblyVersion("99.168.0.0")] +[assembly: AssemblyFileVersion("99.168.0.0")] diff --git a/resources/content_type.ts b/resources/content_type.ts index f089114c..4ef744a5 100644 --- a/resources/content_type.ts +++ b/resources/content_type.ts @@ -5,6 +5,7 @@ const data = { 'ChaoticAllianceRaid': 37, 'Companions': 12, 'CustomDeliveries': 25, + 'DeepDungeonExtras': 39, 'DeepDungeons': 21, 'DisciplesOfTheHand': 17, 'DisciplesOfTheLand': 16, @@ -17,6 +18,7 @@ const data = { 'GoldSaucer': 19, 'GrandCompany': 11, 'Guildhests': 3, + 'HallOfTheNovice': 20, 'IslandSanctuary': 36, 'Levequests': 10, 'OccultCrescent': 38, @@ -28,7 +30,9 @@ const data = { 'Raids': 5, 'RetainerVentures': 18, 'SaveTheQueen': 29, + 'SeasonalEvents': 22, 'SocietyQuests': 13, + 'TheDiadem': 23, 'TheHunt': 33, 'TheMaskedCarnivale': 27, 'TreasureHunt': 9, diff --git a/ui/raidboss/data/03-hw/deepdungeon/the_palace_of_the_dead_general.ts b/ui/raidboss/data/03-hw/deepdungeon/the_palace_of_the_dead_general.ts index ff9a9f10..4011e5ad 100644 --- a/ui/raidboss/data/03-hw/deepdungeon/the_palace_of_the_dead_general.ts +++ b/ui/raidboss/data/03-hw/deepdungeon/the_palace_of_the_dead_general.ts @@ -1,3 +1,4 @@ +import Outputs from '../../../../../resources/outputs'; import { Responses } from '../../../../../resources/responses'; import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; @@ -73,7 +74,7 @@ const triggerSet: TriggerSet = { // ---------------- Pomanders ---------------- { id: 'PotD General Pomander Duplicate', - // duplicate item message: https://xivapi.com/LogMessage/7222?pretty=true + // duplicate pomander message: https://v2.xivapi.com/api/sheet/LogMessage/7222 // en: You return the pomander of ${pomander} to the coffer. You cannot carry any more of that item. type: 'SystemLogMessage', netRegex: { id: '1C36' }, @@ -111,12 +112,8 @@ const triggerSet: TriggerSet = { return output.duplicate!({ pomander: output.raising!() }); case 16: return output.duplicate!({ pomander: output.resolution!() }); - case 17: - return output.duplicate!({ pomander: output.frailty!() }); - case 18: - return output.duplicate!({ pomander: output.concealment!() }); - case 19: - return output.duplicate!({ pomander: output.petrification!() }); + default: + return output.duplicate!({ pomander: output.unknown!() }); } }, outputStrings: { @@ -128,7 +125,7 @@ const triggerSet: TriggerSet = { cn: '${pomander} 重复', ko: '${pomander} 중복', }, - // pomanders: https://xivapi.com/deepdungeonItem?pretty=true + // pomanders: https://v2.xivapi.com/api/sheet/DeepDungeonItem safety: { en: 'Safety', de: 'Siegelbruchs', @@ -257,37 +254,14 @@ const triggerSet: TriggerSet = { cn: '基路伯化', ko: '쿠리부 변신', }, - frailty: { - en: 'Frailty', - de: 'Feindschwächung', - fr: 'Incapacité', - ja: '敵弱体', - cn: '弱化敌人', - ko: '적 약화', - }, - concealment: { - en: 'Concealment', - de: 'Verschwindens', - fr: 'Invisibilité', - ja: 'バニシュ', - cn: '隐形', - ko: '배니시', - }, - petrification: { - en: 'Petrification', - de: 'Feindversteinerung', - fr: 'Pétrification', - ja: '敵石化', - cn: '石化敌人', - ko: '적 석화', - }, + unknown: Outputs.unknown, }, }, // ---------------- Floor Notifications ---------------- { id: 'PotD General Cairn of Passage', // portal to transfer between floors - // Cairn of Passage activation message: https://xivapi.com/LogMessage/7245?pretty=true + // Cairn of Passage activation message: https://v2.xivapi.com/api/sheet/LogMessage/7245 // en: The Cairn of Passage is activated! type: 'SystemLogMessage', netRegex: { id: '1C4D', capture: false }, diff --git a/ui/raidboss/data/04-sb/deepdungeon/heaven-on-high_general.ts b/ui/raidboss/data/04-sb/deepdungeon/heaven-on-high_general.ts index e7236964..3a90d4f4 100644 --- a/ui/raidboss/data/04-sb/deepdungeon/heaven-on-high_general.ts +++ b/ui/raidboss/data/04-sb/deepdungeon/heaven-on-high_general.ts @@ -1,3 +1,4 @@ +import Outputs from '../../../../../resources/outputs'; import { Responses } from '../../../../../resources/responses'; import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; @@ -63,7 +64,7 @@ const triggerSet: TriggerSet = { // ---------------- Pomanders and Magicite ---------------- { id: 'HoH General Pomander Duplicate', - // duplicate item message: https://xivapi.com/LogMessage/7222?pretty=true + // duplicate pomander message: https://v2.xivapi.com/api/sheet/LogMessage/7222 // en: You return the pomander of ${pomander} to the coffer. You cannot carry any more of that item. type: 'SystemLogMessage', netRegex: { id: '1C36' }, @@ -91,22 +92,18 @@ const triggerSet: TriggerSet = { return output.duplicate!({ pomander: output.witching!() }); case 11: return output.duplicate!({ pomander: output.serenity!() }); - case 12: - return output.duplicate!({ pomander: output.rage!() }); - case 13: - return output.duplicate!({ pomander: output.lust!() }); case 14: return output.duplicate!({ pomander: output.intuition!() }); case 15: return output.duplicate!({ pomander: output.raising!() }); - case 16: - return output.duplicate!({ pomander: output.resolution!() }); case 17: return output.duplicate!({ pomander: output.frailty!() }); case 18: return output.duplicate!({ pomander: output.concealment!() }); case 19: return output.duplicate!({ pomander: output.petrification!() }); + default: + return output.duplicate!({ pomander: output.unknown!() }); } }, outputStrings: { @@ -118,7 +115,7 @@ const triggerSet: TriggerSet = { cn: '${pomander} 重复', ko: '${pomander} 중복', }, - // pomanders: https://xivapi.com/deepdungeonItem?pretty=true + // pomanders: https://v2.xivapi.com/api/sheet/DeepDungeonItem safety: { en: 'Safety', de: 'Siegelbruchs', @@ -207,22 +204,6 @@ const triggerSet: TriggerSet = { cn: '魔法效果解除', ko: '마법 효과 해제', }, - rage: { - en: 'Rage', - de: 'Manticoren', - fr: 'Manticore', - ja: 'マンティコア化', - cn: '曼提克化', - ko: '만티코어 변신', - }, - lust: { - en: 'Lust', - de: 'Sukkuben', - fr: 'Succube', - ja: 'サキュバス化', - cn: '梦魔化', - ko: '서큐버스 변신', - }, intuition: { en: 'Intuition', de: 'Finders', @@ -239,14 +220,6 @@ const triggerSet: TriggerSet = { cn: '重生', ko: '리레이즈', }, - resolution: { - en: 'Resolution', - de: 'Kuribu', - fr: 'Kuribu', - ja: 'クリブ化', - cn: '基路伯化', - ko: '쿠리부 변신', - }, frailty: { en: 'Frailty', de: 'Feindschwächung', @@ -271,11 +244,12 @@ const triggerSet: TriggerSet = { cn: '石化敌人', ko: '적 석화', }, + unknown: Outputs.unknown, }, }, { id: 'HoH General Magicite Duplicate', - // duplicate item message: https://xivapi.com/LogMessage/9208?pretty=true + // duplicate magicite message: https://v2.xivapi.com/api/sheet/LogMessage/9208 // en: You return the splinter of ${magicite} magicite to the coffer. You cannot carry any more of that item. type: 'SystemLogMessage', netRegex: { id: '23F8' }, @@ -289,6 +263,8 @@ const triggerSet: TriggerSet = { return output.duplicate!({ magicite: output.vortex!() }); case 4: return output.duplicate!({ magicite: output.elder!() }); + default: + return output.duplicate!({ magicite: output.unknown!() }); } }, outputStrings: { @@ -300,7 +276,7 @@ const triggerSet: TriggerSet = { cn: '${magicite} 重复', ko: '${magicite} 중복', }, - // magicite: https://xivapi.com/DeepDungeonMagicStone?pretty=true + // magicite: https://v2.xivapi.com/api/sheet/DeepDungeonMagicStone inferno: { en: 'Inferno', de: 'Ifrit', @@ -333,13 +309,14 @@ const triggerSet: TriggerSet = { cn: '奥丁', ko: '오딘', }, + unknown: Outputs.unknown, }, }, // ---------------- Floor Notifications ---------------- { id: 'HoH General Beacon of Passage', // portal to transfer between floors - // Beacon of Passage activation message: https://xivapi.com/LogMessage/7245?pretty=true + // Beacon of Passage activation message: https://v2.xivapi.com/api/sheet/LogMessage/7245 // en: The Beacon of Passage is activated! type: 'SystemLogMessage', netRegex: { id: '1C4D', capture: false }, diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_21-30.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_21-30.ts index 3158ae9d..ee6f867b 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_21-30.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_21-30.ts @@ -62,7 +62,7 @@ const triggerSet: TriggerSet = { type: 'HeadMarker', netRegex: { id: '00C5' }, condition: Conditions.targetIsYou(), - alertText: (_data, _matches, outputs) => outputs.text!(), + alertText: (_data, _matches, output) => output.text!(), outputStrings: { text: { en: '5x chasing puddles on you!', diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_41-50.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_41-50.ts index b28ae27b..f710448a 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_41-50.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_41-50.ts @@ -37,7 +37,7 @@ const triggerSet: TriggerSet = { de: 'Raus oder unterbreche ${name}', fr: 'Extérieur ou interrompez ${name}', ja: '沈黙: ${name}', - cn: '出去或打断 ${name}', + cn: '远离或打断 ${name}', ko: '밖으로 또는 인터럽트: ${name}', }, }, diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_81-90.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_81-90.ts index da3d7a4f..a8dbf12a 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_81-90.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_81-90.ts @@ -155,7 +155,7 @@ const triggerSet: TriggerSet = { de: 'Geh in eine Intercardinale Himmelsrichtung vom Boss', fr: 'Allez en intercardinal du boss', ja: 'ボスの斜めへ', - cn: '去Boss的对角线方向', + cn: '去Boss的斜角方向', ko: '보스 비스듬히 피해요', }, }, @@ -348,6 +348,7 @@ const triggerSet: TriggerSet = { between: { en: '${safe}, between eggs', de: '${safe}, zwichen den Eiern', + fr: '${safe}, entre les œufs', ja: '${safe}, たまごの間', cn: '${safe}, 蛋中间', ko: '${safe}, 알 사이', diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_91-100.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_91-100.ts index d170ae2c..91cce448 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_91-100.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_91-100.ts @@ -40,7 +40,7 @@ const triggerSet: TriggerSet = { de: 'Raus oder unterbreche ${name}', fr: 'Extérieur ou interrompez ${name}', ja: '沈黙: ${name}', - cn: '出去或打断 ${name}', + cn: '远离或打断 ${name}', ko: '밖으로 또는 인터럽트: ${name}', }, }, @@ -219,7 +219,7 @@ const triggerSet: TriggerSet = { de: 'Lass dich von Eis treffen', fr: 'Faites-vous toucher par la glace', ja: '氷へ', - cn: '被冰打', + cn: '吃冰', ko: '얼음에 부디쳐요', }, fire: { @@ -227,7 +227,7 @@ const triggerSet: TriggerSet = { de: 'Lass dich von Feuer treffen', fr: 'Faites-vous toucher par le feu', ja: '炎へ', - cn: '被火打', + cn: '吃火', ko: '불에 부디쳐요', }, }, @@ -251,7 +251,7 @@ const triggerSet: TriggerSet = { de: 'Lass dich von Eis treffen', fr: 'Faites-vous toucher par la glace', ja: '氷へ', - cn: '被冰打', + cn: '吃冰', ko: '얼음에 부디쳐요', }, avoid: { @@ -283,7 +283,7 @@ const triggerSet: TriggerSet = { de: 'Lass dich von Feuer treffen', fr: 'Faites-vous toucher par le feu', ja: '炎へ', - cn: '被火打', + cn: '吃火', ko: '불에 부디쳐요', }, avoid: { diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts index acd3c0b7..a883bf1f 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts @@ -1,3 +1,4 @@ +import Outputs from '../../../../../resources/outputs'; import { Responses } from '../../../../../resources/responses'; import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; @@ -63,7 +64,7 @@ const triggerSet: TriggerSet = { // ---------------- Protomanders and Demiclones ---------------- { id: 'EO General Protomander Duplicate', - // duplicate item message: https://xivapi.com/LogMessage/7222?pretty=true + // duplicate protomander message: https://v2.xivapi.com/api/sheet/LogMessage/7222 // en: You return the protomander of ${protomander} to the coffer. You cannot carry any more of that item. type: 'SystemLogMessage', netRegex: { id: '1C36' }, @@ -101,6 +102,8 @@ const triggerSet: TriggerSet = { return output.duplicate!({ protomander: output.intuition!() }); case 35: return output.duplicate!({ protomander: output.raising!() }); + default: + return output.duplicate!({ protomander: output.unknown!() }); } }, outputStrings: { @@ -108,11 +111,11 @@ const triggerSet: TriggerSet = { en: '${protomander} duplicate', de: 'Doppelter ${protomander}', fr: '${protomander} dupliqué(e)', - ja: 'もっています: ${protomander}', + ja: '${protomander} 被り', cn: '${protomander} 重复', ko: '${protomander} 또 나옴', }, - // protomanders: https://xivapi.com/deepdungeonItem?pretty=true + // protomanders: https://v2.xivapi.com/api/sheet/DeepDungeonItem lethargy: { en: 'Lethargy', de: 'Trägheit', @@ -241,11 +244,12 @@ const triggerSet: TriggerSet = { cn: '重生', ko: '리레이즈(Raising)', }, + unknown: Outputs.unknown, }, }, { id: 'EO General Demiclone Duplicate', - // duplicate item message: https://xivapi.com/LogMessage/10287?pretty=true + // duplicate demiclone message: https://v2.xivapi.com/api/sheet/LogMessage/10287 // en: You return the ${demiclone} demiclone to the coffer. You cannot carry any more of that item. type: 'SystemLogMessage', netRegex: { id: '282F' }, @@ -257,6 +261,8 @@ const triggerSet: TriggerSet = { return output.duplicate!({ demiclone: output.doga!() }); case 3: return output.duplicate!({ demiclone: output.onion!() }); + default: + return output.duplicate!({ demiclone: output.unknown!() }); } }, outputStrings: { @@ -268,7 +274,7 @@ const triggerSet: TriggerSet = { cn: '${demiclone} 重复', ko: '${demiclone} 또 나옴', }, - // demiclones: https://xivapi.com/DeepDungeonDemiclone?pretty=true + // demiclones: https://v2.xivapi.com/api/sheet/DeepDungeonDemiclone unei: { en: 'Unei', de: 'Unei', @@ -293,13 +299,14 @@ const triggerSet: TriggerSet = { cn: '洋葱剑士', ko: '양파 기사', }, + unknown: Outputs.unknown, }, }, // ---------------- Floor Notifications ---------------- { id: 'EO General Pylon of Passage', // portal to transfer between floors - // Pylon of Passage activation message: https://xivapi.com/LogMessage/7245?pretty=true + // Pylon of Passage activation message: https://v2.xivapi.com/api/sheet/LogMessage/7245 // en: The Pylon of Passage is activated! type: 'SystemLogMessage', netRegex: { id: '1C4D', capture: false }, diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts new file mode 100644 index 00000000..18179fe4 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts @@ -0,0 +1,309 @@ +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +// Triggers applicable to all Pilgrim's Traverse floors. + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseGeneral', + zoneId: [ + ZoneId.PilgrimsTraverseStones1_10, + ZoneId.PilgrimsTraverseStones11_20, + ZoneId.PilgrimsTraverseStones21_30, + ZoneId.PilgrimsTraverseStones31_40, + ZoneId.PilgrimsTraverseStones41_50, + ZoneId.PilgrimsTraverseStones51_60, + ZoneId.PilgrimsTraverseStones61_70, + ZoneId.PilgrimsTraverseStones71_80, + ZoneId.PilgrimsTraverseStones81_90, + ZoneId.PilgrimsTraverseStones91_100, + ], + zoneLabel: { + en: 'Pilgrim\'s Traverse (All Stones)', + ko: '필그림 트래버스 (모든 층)', + }, + + triggers: [ + // ---------------- Mimics ---------------- + { + id: 'PT General Mimic Spawn', + // 14264 = Mimic (floor 1-30 bronze chests, can stun or interrupt) + // 14265 = Mimic (floor 31-60 silver chests, can stun or interrupt) + // 14266 = Mimic (floor 61+ gold chests, can interrupt, immune to stun) + // TODO: some Mimics may spawn after transference between floors and get called early before being found + type: 'AddedCombatant', + netRegex: { npcNameId: ['14264', '14265', '14266'], capture: false }, + suppressSeconds: 1, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Mimic spawned!', + de: 'Mimik ist erschienen!', + fr: 'Un mimic apparait !', + ja: 'ミミック!', + cn: '已生成 拟态怪!', + ko: '미믹이 나왔어요!', + }, + }, + }, + { + id: 'PT General Mimic Malice', + // inflicts Accursed Pox (43F) if not interrupted + type: 'StartsUsing', + netRegex: { id: 'AF34', source: 'Mimic' }, + response: Responses.interruptIfPossible(), + }, + // ---------------- Pomanders and Juniper Incense ---------------- + { + id: 'PT General Pomander Duplicate', + // duplicate item message: https://v2.xivapi.com/api/sheet/LogMessage/7222 + // en: You return the pomander of ${pomander} to the coffer. You cannot carry any more of that item. + type: 'SystemLogMessage', + netRegex: { id: '1C36' }, + infoText: (_data, matches, output) => { + switch (parseInt(matches.param1, 16)) { + case 1: + return output.duplicate!({ pomander: output.safety!() }); + case 2: + return output.duplicate!({ pomander: output.sight!() }); + case 3: + return output.duplicate!({ pomander: output.strength!() }); + case 4: + return output.duplicate!({ pomander: output.steel!() }); + case 5: + return output.duplicate!({ pomander: output.affluence!() }); + case 6: + return output.duplicate!({ pomander: output.flight!() }); + case 7: + return output.duplicate!({ pomander: output.alteration!() }); + case 8: + return output.duplicate!({ pomander: output.purity!() }); + case 9: + return output.duplicate!({ pomander: output.fortune!() }); + case 10: + return output.duplicate!({ pomander: output.witching!() }); + case 11: + return output.duplicate!({ pomander: output.serenity!() }); + case 14: + return output.duplicate!({ pomander: output.intuition!() }); + case 15: + return output.duplicate!({ pomander: output.raising!() }); + case 36: + return output.duplicate!({ pomander: output.haste!() }); + case 37: + return output.duplicate!({ pomander: output.purification!() }); + case 38: + return output.duplicate!({ pomander: output.devotion!() }); + default: + return output.duplicate!({ pomander: output.unknown!() }); + } + }, + outputStrings: { + duplicate: { + en: '${pomander} duplicate', + de: 'Doppelter ${pomander}', + fr: '${pomander} dupliqué(e)', + ja: '${pomander} 被り', + cn: '${pomander} 重复', + ko: '${pomander} 또 나옴', + }, + // pomanders: https://v2.xivapi.com/api/sheet/DeepDungeonItem + safety: { + en: 'Safety', + de: 'Siegelbruchs', + fr: 'Désamorçage', + ja: '呪印解除', + cn: '咒印解除', + ko: '함정 해제(Safety)', + }, + sight: { + en: 'Sight', + de: 'Sicht', + fr: 'Localisation', + ja: 'サイトロ', + cn: '全景', + ko: '사이트로(Sight)', + }, + strength: { + en: 'Strength', + de: 'Stärkung', + fr: 'Puissance', + ja: '自己強化', + cn: '强化自身', + ko: '자기 강화(Strength)', + }, + steel: { + en: 'Steel', + de: 'Abwehr', + fr: 'Protection', + ja: '防御強化', + cn: '强化防御', + ko: '방어 강화(Steel)', + }, + affluence: { + en: 'Affluence', + de: 'Schätze', + fr: 'Décèlement', + ja: '宝箱増加', + cn: '宝箱增加', + ko: '보물상자 증가(Affluence)', + }, + flight: { + en: 'Flight', + de: 'Feindtods', + fr: 'Sécurisation', + ja: '敵排除', + cn: '减少敌人', + ko: '적 감소(Flight)', + }, + alteration: { + en: 'Alteration', + de: 'Feindwandlung', + fr: 'Affaiblissement', + ja: '敵変化', + cn: '改变敌人', + ko: '적 변화(Alteration)', + }, + purity: { + en: 'Purity', + de: 'Entzauberung', + fr: 'Anti-maléfice', + ja: '解呪', + cn: '解咒', + ko: '저주 해제(Purity)', + }, + fortune: { + en: 'Fortune', + de: 'Glücks', + fr: 'Chance', + ja: '運気上昇', + cn: '运气上升', + ko: '운 상승(Fortune)', + }, + witching: { + en: 'Witching', + de: 'Wandlung', + fr: 'Mutation', + ja: '形態変化', + cn: '形态变化', + ko: '적 변형(Witching)', + }, + serenity: { + en: 'Serenity', + de: 'Enthexung', + fr: 'Dissipation', + ja: '魔法効果解除', + cn: '魔法效果解除', + ko: '마법 효과 해제(Serenity)', + }, + intuition: { + en: 'Intuition', + de: 'Finders', + fr: 'Intuition', + ja: '財宝感知', + cn: '感知宝藏', + ko: '보물 탐지(Intuition)', + }, + raising: { + en: 'Raising', + de: 'Lebens', + fr: 'Résurrection', + ja: 'リレイズ', + cn: '重生', + ko: '리레이즈(Raising)', + }, + haste: { + en: 'Haste', + ko: '헤이스트(Haste)', + }, + purification: { + en: 'Purification', + ko: '이상 현상 바리어(Purification)', + }, + devotion: { + en: 'Devotion', + ko: '헌신(Devotion)', + }, + unknown: Outputs.unknown, + }, + }, + { + id: 'PT General Incense Duplicate', + // two different SystemLogMessage depending on which incense + // duplicate incense message 1: https://v2.xivapi.com/api/sheet/LogMessage/9208 + // duplicate incense message 2: https://v2.xivapi.com/api/sheet/LogMessage/10287 + // en: You return the piece of ${incense} incense to the coffer. You cannot carry any more of that item. + type: 'SystemLogMessage', + netRegex: { id: ['23F8', '282F'] }, + infoText: (_data, matches, output) => { + const id = matches.id; + const param1 = parseInt(matches.param1, 16); + + // incense items are in two different tables: DeepDungeonDemiclone and DeepDungeonMagicStone + // https://v2.xivapi.com/api/sheet/DeepDungeonDemiclone + // https://v2.xivapi.com/api/sheet/DeepDungeonMagicStone + if (id === '23F8' && param1 === 5) { + // incense is from DeepDungeonMagicStone + return output.duplicate!({ incense: output.poisonfruit!() }); + } + + if (id === '282F') { + // incense is from DeepDungeonDemiclone + switch (param1) { + case 4: + return output.duplicate!({ incense: output.mazeroot!() }); + case 5: + return output.duplicate!({ incense: output.barkbalm!() }); + } + } + + return output.duplicate!({ incense: output.unknown!() }); + }, + outputStrings: { + duplicate: { + en: '${incense} duplicate', + ko: '${incense} 또 나옴', + }, + mazeroot: { + en: 'Mazeroot', + ko: '미로뿌리(Mazeroot)', + }, + barkbalm: { + en: 'Barkbalm', + ko: '나무껍질연고(Barkbalm)', + }, + poisonfruit: { + en: 'Poisonfruit', + ko: '독과일(Poisonfruit)', + }, + unknown: Outputs.unknown, + }, + }, + // ---------------- Floor Notifications ---------------- + { + id: 'PT General Pylon of Passage', + // portal to transfer between floors + // Pylon of Passage activation message: https://v2.xivapi.com/api/sheet/LogMessage/7245 + // en: The Pylon of Passage is activated! + type: 'SystemLogMessage', + netRegex: { id: '1C4D', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Pylon of Passage activated', + de: 'Translokator aktiviert', + fr: 'Pylone de téléportation activé', + ja: '転移が出来ます', + cn: '传送装置已启动', + ko: '다음 층으로 갈 수 있어요', + }, + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_41-50.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_41-50.ts new file mode 100644 index 00000000..e04787f3 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_41-50.ts @@ -0,0 +1,50 @@ +import Conditions from '../../../../../resources/conditions'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones41_50', + zoneId: ZoneId.PilgrimsTraverseStones41_50, + triggers: [ + // ---------------- Stone 50 Boss: Ogbunabali ---------------- + { + id: 'PT 41-50 Ogbunabali Liquefaction', + type: 'StartsUsing', + netRegex: { id: 'AA0B', source: 'Ogbunabali', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Stand on a rock', + ko: '바위 위로 올라서요', + }, + }, + }, + { + id: 'PT 41-50 Ogbunabali Sandpit', + type: 'HeadMarker', + netRegex: { id: '0280', capture: true }, + condition: Conditions.targetIsYou(), + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: '4x Chasing AoE on YOU', + ko: '따라오는 장판 x4', + }, + }, + }, + { + id: 'PT 41-50 Ogbunabali Biting Wind', + type: 'StartsUsing', + netRegex: { id: 'AA12', source: 'Ogbunabali', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 5, + countdownSeconds: 5, + response: Responses.knockback(), + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_51-60.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_51-60.ts new file mode 100644 index 00000000..49ebd313 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_51-60.ts @@ -0,0 +1,66 @@ +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones51_60', + zoneId: ZoneId.PilgrimsTraverseStones51_60, + triggers: [ + // ---------------- Stone 60 Boss: Ancestral Maliktender ---------------- + { + id: 'PT 51-60 Ancestral Maliktender Spineshot', + type: 'StartsUsing', + netRegex: { id: 'AF42', source: 'Ancestral Maliktender', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 51-60 Ancestral Maliktender Spinning Needles', + type: 'StartsUsing', + netRegex: { id: ['AF43', 'AF44'], source: 'Ancestral Maliktender', capture: true }, + durationSeconds: 16, + alertText: (_data, matches, output) => { + const rotation = matches.id === 'AF43' ? output.counterclockwise!() : output.clockwise!(); + return output.text!({ rotation: rotation }); + }, + outputStrings: { + text: { + en: 'Sides + Rotate ${rotation}', + ko: '옆쪽에서 + ${rotation}으로 돌아요', + }, + clockwise: Outputs.clockwise, + counterclockwise: Outputs.counterclockwise, + }, + }, + { + id: 'PT 51-60 Ancestral Maliktender One/Two-stone March', + type: 'StartsUsing', + netRegex: { id: ['AF3E', 'AF3F'], source: 'Ancestral Maliktender', capture: true }, + infoText: (_data, matches, output) => { + const id = matches.id; + if (id === 'AF3E') + return output.text!({ count: output.once!() }); + return output.text!({ count: output.twice!() }); + }, + outputStrings: { + text: { + en: 'Cactuar move ${count}', + ko: '선인장이 ${count} 이동', + }, + once: { + en: 'Once', + ko: '한 번', + }, + twice: { + en: 'Twice', + ko: '두 번', + }, + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_61-70.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_61-70.ts new file mode 100644 index 00000000..2c5c9107 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_61-70.ts @@ -0,0 +1,195 @@ +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import { Directions } from '../../../../../resources/util'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +type DirectionOutputCardinalRelative = + | 'front' + | 'right' + | 'back' + | 'left' + | 'unknown'; + +const outputCardinalRelativeDir: DirectionOutputCardinalRelative[] = [ + 'front', + 'right', + 'back', + 'left', +]; + +const outputRelativeFrom4DirNum = (dirNum: number): DirectionOutputCardinalRelative => { + return outputCardinalRelativeDir[dirNum] ?? 'unknown'; +}; + +export interface Data extends RaidbossData { + octupleSwipes?: number[]; + calledOctupleSwipes?: boolean; +} + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones61_70', + zoneId: ZoneId.PilgrimsTraverseStones61_70, + triggers: [ + // ---------------- Stone 70 Boss: Forgiven Zeal ---------------- + // A993 = Zealous Glower dummy self-cast, back-to-front line + // A98E = Zealous Glower dummy self-cast, front-to-back line + // A99A = Ardorous Eye dummy self-cast, clockwise ring + // A99F = Ardorous Eye dummy self-cast, counterclockwise ring + // A9A5 = 2000-mina Swing damage cast + // A9A7 = Disorienting Groan damage cast + // --- Octuple Swipe --- + // A9A8 = dummy self-cast for castbar + // A9A9 = instant, final damage cast + // A9AA = instant, damage cast + // A9AB = instant, damage cast + // A9AC = instant, damage cast + // A9AD = dummy cast showing telegraph + { + id: 'PT 61-70 Forgiven Zeal Zealous Glower', + type: 'StartsUsing', + netRegex: { id: ['A993', 'A98E'], source: 'Forgiven Zeal', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 61-70 Forgiven Zeal Zealous Glower Dodge Direction', + type: 'Ability', + netRegex: { id: ['A993', 'A98E'], source: 'Forgiven Zeal', capture: true }, + durationSeconds: 15, + infoText: (_data, matches, output) => { + const dir = matches.id === 'A993' ? output.backFront!() : output.frontBack!(); + return output.text!({ dir: dir }); + }, + outputStrings: { + text: { + en: 'Dodge ${dir}', + ko: '${dir} 피해요!', + }, + backFront: { + en: 'Back-to-front', + ko: '앞에서 뒤로', + }, + frontBack: { + en: 'Front-to-back', + ko: '뒤에서 앞으로', + }, + }, + }, + { + id: 'PT 61-70 Forgiven Zeal Ardorous Eye', + type: 'StartsUsing', + netRegex: { id: ['A99A', 'A99F'], source: 'Forgiven Zeal', capture: false }, + response: Responses.getIn(), + }, + { + id: 'PT 61-70 Forgiven Zeal Ardorous Eye Dodge Direction', + type: 'Ability', + netRegex: { id: ['A99A', 'A99F'], source: 'Forgiven Zeal', capture: true }, + durationSeconds: 20, + infoText: (_data, matches, output) => { + const dir = matches.id === 'A99A' ? output.clockwise!() : output.counterclockwise!(); + return output.text!({ dir: dir }); + }, + outputStrings: { + text: { + en: 'Dodge ${dir}', + ko: '${dir}으로 피해요!', + }, + clockwise: Outputs.clockwise, + counterclockwise: Outputs.counterclockwise, + }, + }, + { + id: 'PT 61-70 Forgiven Zeal Disorienting Groan', + type: 'StartsUsing', + netRegex: { id: 'A9A7', source: 'Forgiven Zeal', capture: false }, + response: Responses.knockback(), + }, + { + id: 'PT 61-70 Forgiven Zeal 2000-mina Swing', + type: 'StartsUsing', + netRegex: { id: 'A9A5', source: 'Forgiven Zeal', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 61-70 Forgiven Zeal Octuple Swipe Cleanup', + type: 'Ability', + netRegex: { id: 'A9A9', source: 'Forgiven Zeal', capture: false }, + run: (data) => { + delete data.octupleSwipes; + delete data.calledOctupleSwipes; + }, + }, + { + id: 'PT 61-70 Forgiven Zeal Octuple Swipe', + type: 'StartsUsingExtra', + netRegex: { id: 'A9AD', capture: true }, + condition: (data) => !data.calledOctupleSwipes, + durationSeconds: 18, + alertText: (data, matches, output) => { + const heading = Directions.hdgTo4DirNum(parseFloat(matches.heading)); + data.octupleSwipes ??= []; + data.octupleSwipes.push(heading); + + if (data.octupleSwipes.length < 8) + return; + + data.calledOctupleSwipes = true; + const [swipe1, swipe4, swipe5, swipe8] = [ + data.octupleSwipes[0], + data.octupleSwipes[3], + data.octupleSwipes[4], + data.octupleSwipes[7], + ]; + + if ( + swipe1 === undefined || + swipe4 === undefined || + swipe5 === undefined || + swipe8 === undefined + ) + return output.avoid!(); + + const dir1 = outputRelativeFrom4DirNum(swipe1); + const dir4 = outputRelativeFrom4DirNum(swipe4); + const dir5 = outputRelativeFrom4DirNum(swipe5); + const dir8 = outputRelativeFrom4DirNum(swipe8); + + if (swipe1 === swipe8) + // swipe order is 1 > 2 > 3 > 4 > 5 > 6 > 7 > 1 + // dodge order is 4 > 1 > 1 > 1 > 1 > 1 > 1 > 4 + return output.rewind!({ dir4: output[dir4]!(), dir1: output[dir1]!() }); + + // swipe order is 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 + // dodge order is 4 > 1 > 1 > 1 > 8 > 5 > 5 > 5 + return output.repeat!({ + dir1: output[dir1]!(), + dir4: output[dir4]!(), + dir5: output[dir5]!(), + dir8: output[dir8]!(), + }); + }, + outputStrings: { + repeat: { + en: '${dir4} => ${dir1} x3 => ${dir8} => ${dir5} x3', + ko: '${dir4} 🔜 ${dir1}x3 🔜 ${dir8} 🔜 ${dir5}x3', + }, + rewind: { + en: '${dir4} => ${dir1} x6 => ${dir4}', + ko: '${dir4} 🔜 ${dir1}x6 🔜 ${dir4}', + }, + avoid: { + en: 'Avoid swipes x8', + ko: '스와이프x8 피해요!', + }, + left: Outputs.left, + right: Outputs.right, + front: Outputs.front, + back: Outputs.back, + }, + }, + ], +}; + +export default triggerSet; 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 new file mode 100644 index 00000000..4cc16a2d --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_71-80.ts @@ -0,0 +1,115 @@ +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export interface Data extends RaidbossData { + prowlingDeath?: 'shadowOfDeath' | 'nowhereToRun'; +} + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones71_80', + zoneId: ZoneId.PilgrimsTraverseStones71_80, + triggers: [ + // ---------------- Stone 80 Boss: Forgiven Profanity ---------------- + // A9C9 = Roaring Ring dummy self-cast, donut + front cleave + // A9CB = Roaring Ring dummy self-cast, donut + back cleave + // A9CC = Roaring Ring damage cast + // A9CD = Perilous Lair dummy self-cast, pbaoe + front cleave + // A9CF = Perilous Lair dummy self-cast, pbaoe + back cleave + // A9D0 = Perilous Lair damage cast + // A9D1 = Profane Waul whiskers cleave damage cast + // A9D3 = Prowling Death applies debuffs + // A9D4 = Stalking Static dummy self-cast + // AB13 = Stalking Static small line cleave + // AC1C = Static Shock final big AoE + { + id: 'PT 71-80 Forgiven Profanity Roaring Ring', + type: 'StartsUsing', + netRegex: { id: ['A9C9', 'A9CB'], source: 'Forgiven Profanity', capture: true }, + alertText: (data, matches, output) => { + const id = matches.id; + let backFront; + if (data.prowlingDeath === 'shadowOfDeath') + backFront = id === 'A9C9' ? output.front!() : output.back!(); + else + backFront = id === 'A9C9' ? output.back!() : output.front!(); + return output.text!({ in: output.in!(), backFront: backFront }); + }, + run: (data) => { + delete data.prowlingDeath; + }, + outputStrings: { + text: { + en: '${in} + ${backFront}', + ko: '${backFront} + ${in}', + }, + back: Outputs.back, + front: Outputs.front, + in: Outputs.in, + }, + }, + { + id: 'PT 71-80 Forgiven Profanity Perilous Lair', + type: 'StartsUsing', + netRegex: { id: ['A9CD', 'A9CF'], source: 'Forgiven Profanity', capture: true }, + alertText: (data, matches, output) => { + const id = matches.id; + let backFront; + if (data.prowlingDeath === 'shadowOfDeath') + backFront = id === 'A9CD' ? output.front!() : output.back!(); + else + backFront = id === 'A9CD' ? output.back!() : output.front!(); + return output.text!({ out: output.out!(), backFront: backFront }); + }, + run: (data) => { + delete data.prowlingDeath; + }, + outputStrings: { + text: { + en: '${out} + ${backFront}', + ko: '${backFront} + ${out}', + }, + back: Outputs.back, + front: Outputs.front, + out: Outputs.out, + }, + }, + { + id: 'PT 71-80 Forgiven Profanity Stalking Static', + type: 'StartsUsing', + netRegex: { id: 'A9D4', source: 'Forgiven Profanity', capture: false }, + durationSeconds: 12, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid final AoE', + ko: '마지막 장판 피해요!', + }, + }, + }, + { + id: 'PT 71-80 Forgiven Profanity Prowling Death', + type: 'StartsUsing', + netRegex: { id: 'A9D3', source: 'Forgiven Profanity', capture: false }, + response: Responses.aoe(), + }, + { + id: 'PT 71-80 Forgiven Profanity Prowling Death Collect', + // 11A6 = Shadow of Death, lethal on expiration, cleanse by being hit by whiskers cleave + // en: All who deny Light are condemned to death... + // 11A7 = Nowhere to Run, gain stacks on movement, lethal at 8 stacks + // en: Death approaches all who dare tread forth... + type: 'GainsEffect', + netRegex: { effectId: ['11A6', '11A7'], capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => { + data.prowlingDeath = matches.effectId === '11A6' ? 'shadowOfDeath' : 'nowhereToRun'; + }, + }, + ], +}; + +export default triggerSet; 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 new file mode 100644 index 00000000..8edcd8bf --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_81-90.ts @@ -0,0 +1,47 @@ +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones81_90', + zoneId: ZoneId.PilgrimsTraverseStones81_90, + + triggers: [ + // ---------------- Stone 90 Boss: Malacoda ---------------- + { + id: 'PT 81-90 Malacoda Backhand Right', + type: 'StartsUsing', + netRegex: { id: 'ACDA', source: 'Malacoda', capture: false }, + response: Responses.goLeft(), + }, + { + id: 'PT 81-90 Malacoda Backhand Left', + type: 'StartsUsing', + netRegex: { id: 'ACDB', source: 'Malacoda', capture: false }, + response: Responses.goRight(), + }, + { + id: 'PT 81-90 Malacoda Fore-hind Folly', + type: 'StartsUsing', + netRegex: { id: 'ACE2', source: 'Malacoda', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 81-90 Malacoda Twin-winged Treachery', + type: 'StartsUsing', + netRegex: { id: 'ACE3', source: 'Malacoda', capture: false }, + response: Responses.goFrontBack(), + }, + { + id: 'PT 81-90 Malacoda Skinflayer', + type: 'StartsUsing', + netRegex: { id: 'ACEA', source: 'Malacoda', capture: false }, + response: Responses.knockback(), + }, + ], +}; + +export default triggerSet; 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 new file mode 100644 index 00000000..1e2d9181 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_the_final_verse.ts @@ -0,0 +1,237 @@ +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { Responses } from '../../../../../resources/responses'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +// Pilgrim's Traverse Stone 99/The Final Verse +// TODO: Bounds of Sin dodge direction +// TODO: Abysal Blaze left/right safe spots +// TODO: timeline + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'TheFinalVerse', + zoneId: [ + ZoneId.PilgrimsTraverseStones91_100, + ZoneId.TheFinalVerse, + ], + zoneLabel: { + en: 'Pilgrim\'s Traverse Stone 99/The Final Verse', + }, + + triggers: [ + // ---------------- Stone 99/The Final Verse Boss: Eminent Grief/Devoured Eater ---------------- + { + id: 'PT 99 Devoured Eater Blade of First Light', + type: 'StartsUsing', + netRegex: { id: ['AC21', 'AC22', 'AC27', 'AC28'], source: 'Devoured Eater', capture: true }, + alertText: (_data, matches, output) => { + const id = matches.id; + if (id === 'AC21' || id === 'AC27') + return output.sides!(); + return output.middle!(); + }, + outputStrings: { + sides: Outputs.sides, + middle: Outputs.goIntoMiddle, + }, + }, + { + id: 'PT 99 Eminent Grief Ball of Fire', + type: 'Ability', + netRegex: { id: ['AC1D', 'AC24'], source: 'Eminent Grief', capture: false }, + response: Responses.moveAway('alarm'), + }, + { + id: 'PT 99 Eminent Grief Chains of Condemnation', + // raidwide + applies 11D2 Chains of Condemnation for 3s; heavy damage if moving + type: 'StartsUsing', + netRegex: { id: ['AC20', 'AC26'], source: 'Eminent Grief', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 5, + countdownSeconds: 5, + durationSeconds: 8, + alertText: (_data, _matches, outputs) => outputs.text!(), + outputStrings: { + text: { + en: 'AoE + Stop Moving!', + ko: '전체 공격 + 동작 그만!', + }, + }, + }, + { + id: 'PT 99 Devoured Eater Bounds of Sin', + // applies 119E Bind for 3s + // AC33 = sequential damage cast, may have good position data for dodge direction + type: 'Ability', + netRegex: { id: 'AC32', source: 'Devoured Eater', capture: false }, + delaySeconds: 3, + response: Responses.moveAway('alert'), + }, + { + id: 'PT 99 Eminent Grief Spinelash Bait', + // laser will break glass + summon add if it hits a window + type: 'HeadMarker', + netRegex: { id: '00EA', capture: true }, + condition: Conditions.targetIsYou(), + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Laser on YOU', + ko: '내게 레이저!', + }, + }, + }, + { + id: 'PT 99 Eminent Grief Spinelash', + type: 'StartsUsing', + netRegex: { id: 'B03E', source: 'Eminent Grief', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid laser', + ko: '레이저 피해요', + }, + }, + }, + { + id: 'PT 99 Vodoriga Minion Spawn', + // 14039 = Vodoriga Minion + type: 'AddedCombatant', + netRegex: { npcNameId: '14039', capture: false }, + response: Responses.killExtraAdd(), + }, + { + id: 'PT 99 Eminent Grief Drain Aether', + // AC38 = short cast + // AC39 = long cast + // [AC3B, AC3D] = failstate casts? + type: 'StartsUsing', + netRegex: { id: ['AC38', 'AC39'], source: 'Eminent Grief', capture: true }, + delaySeconds: (_data, matches) => + matches.id === 'AC38' ? 0 : parseFloat(matches.castTime) - 5, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Light debuff', + ko: '빛🟡 받아요', + }, + }, + }, + { + id: 'PT 99 Devoured Eater Drain Aether', + // AC3A = short cast + // AC3C = long cast + type: 'StartsUsing', + netRegex: { id: ['AC3A', 'AC3C'], source: 'Devoured Eater', capture: true }, + delaySeconds: (_data, matches) => + matches.id === 'AC3A' ? 0 : parseFloat(matches.castTime) - 4, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Dark debuff', + ko: '어둠⚫️ 받아요', + }, + }, + }, + { + id: 'PT 99 Eminent Grief Abyssal Blaze Safe Spots', + // AC2A = first cast, horizontal exaflares, front safe + // AC2B = first cast, vertical exaflares, left or right safe + // AC2C = second instant cast, horizontal exaflares, back safe + // AC2D = second instant cast, vertical exaflares, left or right safe + // AC2E = used approximately 7s after each horizontal/vertical indicator, may have good data for starting positions + // AC2F = diamonds glow, exaflares start at end of cast + // AC30 = instant, exaflare explosion/damage + type: 'Ability', + netRegex: { id: ['AC2A', 'AC2B', 'AC2C', 'AC2D'], source: 'Eminent Grief', capture: true }, + durationSeconds: 10, + infoText: (_data, matches, outputs) => { + const id = matches.id; + switch (id) { + case 'AC2A': + return outputs.text!({ safe: outputs.frontSafe!() }); + case 'AC2B': + return outputs.text!({ safe: outputs.leftRightSafe!() }); + case 'AC2C': + return outputs.text!({ safe: outputs.backSafe!() }); + case 'AC2D': + return outputs.text!({ safe: outputs.leftRightSafe!() }); + } + }, + outputStrings: { + text: { + en: '${safe}, for later', + ko: '(나중에 ${safe})', + }, + frontSafe: { + en: 'Front safe', + ko: '앞쪽이 안전', + }, + backSafe: { + en: 'Back safe', + ko: '뒤쪽이 안전', + }, + leftRightSafe: { + en: 'Check safe side', + ko: '좌우 안전한 곳 찾아봐요', + }, + }, + }, + { + id: 'PT 99 Eminent Grief Abyssal Blaze', + type: 'StartsUsing', + netRegex: { id: 'AC2F', source: 'Eminent Grief', capture: false }, + suppressSeconds: 1, + alertText: (_data, _matches, outputs) => outputs.text!(), + outputStrings: { + text: { + en: 'Avoid Exaflares', + ko: '엑사플레어 피해요', + }, + }, + }, + { + id: 'PT 99 Eminent Grief 불의 알', + type: 'StartsUsing', + netRegex: { id: 'AC2F', source: 'Eminent Grief', capture: false }, + delaySeconds: 20, + durationSeconds: 4, + suppressSeconds: 1, + alertText: (_data, _matches, outputs) => outputs.text!(), + outputStrings: { + text: { + en: '(Spread, later)', + ko: '(곧 구슬, 흩어져요)', + }, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Devoured Eater': 'erodiert(?:e|er|es|en) Sündenvertilger', + 'Eminent Grief': 'Eminent(?:e|er|es|en) Trauer', + }, + }, + { + 'locale': 'fr', + 'replaceSync': { + 'Devoured Eater': 'purgateur dévoré', + 'Eminent Grief': 'Pontife du Chagrin', + }, + }, + { + 'locale': 'ja', + 'replaceSync': { + 'Devoured Eater': '侵蝕された罪喰い', + 'Eminent Grief': 'エミネントグリーフ', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/trial/arkveld-ex.ts b/ui/raidboss/data/07-dt/trial/arkveld-ex.ts index f894f342..a69d0d4f 100644 --- a/ui/raidboss/data/07-dt/trial/arkveld-ex.ts +++ b/ui/raidboss/data/07-dt/trial/arkveld-ex.ts @@ -23,8 +23,6 @@ const diceMap: { [id: string]: number } = { '019B': 8, } as const; -// const diceDuration: number[] = [8, 11, 14, 17, 20, 23, 26, 29] as const; - // the Windward Wilds (Extreme) const triggerSet: TriggerSet = { id: 'ArkveldEx', @@ -44,12 +42,12 @@ const triggerSet: TriggerSet = { { id: 'ArkveldEx 날개짓', type: 'StartsUsing', - // 43891 왼쪽 - // 43892 오른쪽 - netRegex: { id: ['AB73', 'AB74'] }, + netRegex: { id: ['AB73', 'AB74', 'B019', 'B020'] }, durationSeconds: 3, infoText: (_data, matches, output) => { - const dir = matches.id === 'AB74' ? output.left!() : output.right!(); + const dir = matches.id === 'AB74' || matches.id === 'B020' + ? output.left!() + : output.right!(); return output.text!({ direction: dir }); }, outputStrings: { @@ -61,10 +59,24 @@ 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', - netRegex: { id: 'AB81', capture: false }, + netRegex: { id: ['B030', 'B037'], capture: false }, durationSeconds: 5, suppressSeconds: 1, infoText: (_data, _matches, output) => output.text!(), @@ -78,7 +90,7 @@ const triggerSet: TriggerSet = { { id: 'ArkveldEx 하얀 돌진', type: 'StartsUsing', - netRegex: { id: 'AB82', capture: false }, + netRegex: { id: ['B02B', 'B045'], capture: false }, durationSeconds: 5, suppressSeconds: 1, infoText: (_data, _matches, output) => output.text!(), @@ -89,8 +101,7 @@ const triggerSet: TriggerSet = { }, }, }, - /* - { + /* { id: 'ArkveldEx White Flash', type: 'StartsUsing', netRegex: { id: 'AB82', capture: false }, @@ -99,8 +110,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: Outputs.healerGroups, }, - }, - */ + }, */ { id: 'ArkveldEx Rush', type: 'StartsUsing', @@ -127,9 +137,7 @@ const triggerSet: TriggerSet = { { id: 'ArkveldEx Wyvern\'s Ouroblade', type: 'StartsUsing', - // 43916 왼쪽 - // 43918 오른쪽 - netRegex: { id: ['AB8C', 'AB8E'] }, + netRegex: { id: ['AB8C', 'AB8E', 'B031', 'B032'] }, durationSeconds: 4, response: (data, matches, output) => { // cactbot-builtin-response @@ -145,7 +153,9 @@ const triggerSet: TriggerSet = { left: Outputs.left, right: Outputs.right, }; - const dir = matches.id === 'AB8E' ? output.left!() : output.right!(); + const dir = matches.id === 'AB8E' || matches.id === 'B032' + ? output.left!() + : output.right!(); if (data.wildEnergy) return { alertText: output.spread!({ direction: dir }) }; return { infoText: output.text!({ direction: dir }) }; @@ -155,7 +165,7 @@ const triggerSet: TriggerSet = { { id: 'ArkveldEx Steeltail Thrust', type: 'StartsUsing', - netRegex: { id: 'ABAD', capture: false }, + netRegex: { id: ['ABAD', 'B035'], capture: false }, infoText: (_data, _matches, output) => output.text!(), outputStrings: { text: { @@ -244,19 +254,19 @@ const triggerSet: TriggerSet = { response: (data, matches, output) => { // cactbot-builtin-response output.responseOutputStrings = { - cw1: { en: 'East', ko: '오른쪽으로' }, - cw2: { en: 'South', ko: '남쪽으로' }, - cw3: { en: 'West', ko: '왼쪽으로' }, - cw4: { en: 'North', ko: '북쪽으로' }, - ccw1: { en: 'West', ko: '왼쪽으로' }, - ccw2: { en: 'South', ko: '남쪽으로' }, - ccw3: { en: 'East', ko: '오른쪽으로' }, - ccw4: { en: 'North', ko: '북쪽으로' }, + cw1: { en: 'East', ko: '오른쪽🡆으로' }, + cw2: { en: 'South', ko: '남쪽🡇으로' }, + cw3: { en: 'West', ko: '왼쪽🡄으로' }, + cw4: { en: 'North', ko: '북쪽🡅으로' }, + ccw1: { en: 'West', ko: '왼쪽🡄으로' }, + ccw2: { en: 'South', ko: '남쪽🡇으로' }, + ccw3: { en: 'East', ko: '오른쪽🡆으로' }, + ccw4: { en: 'North', ko: '북쪽🡅으로' }, others: { en: 'Go center', ko: '한가운데서 대기' }, }; - data.chaseDir = matches.id === 'ABB3' ? 'cw' : 'ccw'; if (data.dice === undefined) return; + data.chaseDir = matches.id === 'ABB3' ? 'cw' : 'ccw'; if (data.dice > 4) return { infoText: output.others!() }; const res = `${data.chaseDir}${data.dice}` as const; @@ -267,34 +277,85 @@ const triggerSet: TriggerSet = { { id: 'ArkveldEx Chase Dice', type: 'StartsUsing', - netRegex: { id: 'ABB8', capture: false }, + netRegex: { id: ['ABB5', 'ABB8'], capture: false }, durationSeconds: 4, response: (data, _matches, output) => { // cactbot-builtin-response output.responseOutputStrings = { - cw5: { en: 'East', ko: '오른쪽으로' }, - cw6: { en: 'South', ko: '남쪽으로' }, - cw7: { en: 'West', ko: '왼쪽으로' }, - cw8: { en: 'North', ko: '북쪽으로' }, - ccw5: { en: 'West', ko: '왼쪽으로' }, - ccw6: { en: 'South', ko: '남쪽으로' }, - ccw7: { en: 'East', ko: '오른쪽으로' }, - ccw8: { en: 'North', ko: '북쪽으로' }, + cw5: { en: 'East', ko: '오른쪽🡆으로' }, + cw6: { en: 'South', ko: '남쪽🡇으로' }, + cw7: { en: 'West', ko: '왼쪽🡄으로' }, + cw8: { en: 'North', ko: '북쪽🡅으로' }, + ccw5: { en: 'West', ko: '왼쪽🡄으로' }, + ccw6: { en: 'South', ko: '남쪽🡇으로' }, + ccw7: { en: 'East', ko: '오른쪽🡆으로' }, + ccw8: { en: 'North', ko: '북쪽🡅으로' }, avoid: { en: 'Avoid!', - ko: '피해요!', + ko: '한가운데로 피해욧!', }, }; - data.chases = (data.chases ?? 0) + 1; + if (data.dice === undefined) + return; + data.chases++; if (data.chases === data.dice) return { infoText: output.avoid!() }; - if (data.chases < 5 && (data.chases + 4) === data.dice) { + if ((data.chases + 4) === data.dice) { const res = `${data.chaseDir}${data.dice}` as const; return { alertText: output[res]!() }; } }, }, + { + id: 'ArkveldEx Weal Beam', + type: 'HeadMarker', + netRegex: { id: '01D6' }, + durationSeconds: 5, + response: (data, matches, output) => { + // cactbot-builtin-response + output.responseOutputStrings = { + beamMe: { + en: 'Beam on YOU', + ko: '나에게 빔!', + }, + partyMove: { + en: '(Avoid beam)', + ko: '(빔 피해요)', + }, + }; + if (data.me === matches.target) { + return { alertText: output.beamMe!() }; + } + return { infoText: output.partyMove!() }; + }, + }, + { + id: 'ArkveldEx Wrathful Rattle', + type: 'StartsUsing', + netRegex: { id: 'ABA7', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Waves', + ko: '연속 물결 장판', + }, + }, + }, + { + id: 'ArkveldEx Roar + Exaflare', + type: 'StartsUsing', + netRegex: { id: 'ABAF', capture: false }, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'AoE + Exaflare', + ko: '전체 공격 🔜 엑사플레어', + }, + }, + }, ], }; export default triggerSet; + +// 추출: StartsCasting 14:([^:]*):Guardian Arkveld:([^:]*) diff --git a/util/gen_coverage_report.ts b/util/gen_coverage_report.ts index e9ce4b49..3c4f79c5 100644 --- a/util/gen_coverage_report.ts +++ b/util/gen_coverage_report.ts @@ -358,6 +358,8 @@ const buildTotals = (coverage: Coverage, missingTranslations: MissingTranslation [ContentType.VCDungeonFinder]: ContentType.Dungeons, // lump chaotic alliance raids with raids for now. [ContentType.ChaoticAllianceRaid]: ContentType.Raids, + // lump Deep Dungeon bonus fights (e.g., The Final Verse) with Deep Dungeons. + [ContentType.DeepDungeonExtras]: ContentType.DeepDungeons, }; const contentType = contentTypeRemap[origContentType] ?? origContentType; diff --git a/util/zone_overrides.ts b/util/zone_overrides.ts index 6cd1f6e3..db06dda6 100644 --- a/util/zone_overrides.ts +++ b/util/zone_overrides.ts @@ -97,9 +97,12 @@ const _SYNTHETIC_IDS: NameKeyToTerritoryId = { 'CuttersCry72': 170, }; -// This will override the data-soourced ContentType with our own value. +// This will override the data-sourced ContentType with our own value. const _SYNTHETIC_CONTENT_TYPE: NameKeyToTerritoryId = { - // empty since 7.1 + 'HallOfTheNovice': 20, + 'SeasonalEvents': 22, + 'TheDiadem': 23, + 'DeepDungeonExtras': 39, }; // Certain collisions will always happen and we don't care because we're not