diff --git a/package.json b/package.json index 68530e28..64c173f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cactbot-build", - "version": "99.168.0", + "version": "99.170.0", "license": "Apache-2.0", "type": "module", "types": "./types", diff --git a/plugin/CactbotEventSource/FFXIVProcessKo.cs b/plugin/CactbotEventSource/FFXIVProcessKo.cs index 8f06cf84..936cc507 100644 --- a/plugin/CactbotEventSource/FFXIVProcessKo.cs +++ b/plugin/CactbotEventSource/FFXIVProcessKo.cs @@ -8,7 +8,7 @@ namespace Cactbot { public class FFXIVProcessKo : FFXIVProcess { - // Last updated for FFXIV 7.2 + // Last updated for FFXIV 7.3 // Per aers/FFXIVClientStructs, what we call EntityMemory is actually: // Client::Game::Character::Character (0x22E0) // Client::Game::Object::GameObject (0x190) @@ -25,28 +25,28 @@ public unsafe struct EntityMemory { [FieldOffset(0x30)] public fixed byte Name[nameBytes]; - [FieldOffset(0x74)] + [FieldOffset(0x78)] public uint id; - [FieldOffset(0x8C)] + [FieldOffset(0x90)] public EntityType type; - [FieldOffset(0x92)] + [FieldOffset(0x96)] public ushort distance; - [FieldOffset(0xA0)] + [FieldOffset(0xB0)] public Single pos_x; - [FieldOffset(0xA4)] + [FieldOffset(0xB4)] public Single pos_z; - [FieldOffset(0xA8)] + [FieldOffset(0xB8)] public Single pos_y; - [FieldOffset(0xB0)] + [FieldOffset(0xC0)] public Single rotation; - [FieldOffset(0x190)] + [FieldOffset(0x1A0)] public CharacterDetails charDetails; } @@ -106,7 +106,8 @@ public FFXIVProcessKo(ILogger logger) : base(logger) { } // In combat boolean. // This address is written to by "mov [rax+rcx],bl" and has three readers. // This reader is "cmp byte ptr [ffxiv_dx11.exe+????????],00 { (0),0 }" - private static String kInCombatSignature = "803D??????????74??488B03488BCBFF50"; + // Updated in 7.3, signature was no longer unique, include the preceeding "jz LAB_?????????" + private static String kInCombatSignature = "74??803D??????????74??488B03488BCBFF50"; private static int kInCombatSignatureOffset = -15; private static bool kInCombatSignatureRIP = true; // Because this line is a cmp byte line, the signature is not at the end of the line. diff --git a/plugin/CactbotEventSource/Properties/AssemblyInfo.cs b/plugin/CactbotEventSource/Properties/AssemblyInfo.cs index 449e5782..ca849fdf 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.168.0.0")] -[assembly: AssemblyFileVersion("99.168.0.0")] +[assembly: AssemblyVersion("99.170.0.0")] +[assembly: AssemblyFileVersion("99.170.0.0")] diff --git a/plugin/CactbotOverlay/Properties/AssemblyInfo.cs b/plugin/CactbotOverlay/Properties/AssemblyInfo.cs index 8378e11c..d544097c 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.168.0.0")] -[assembly: AssemblyFileVersion("99.168.0.0")] +[assembly: AssemblyVersion("99.170.0.0")] +[assembly: AssemblyFileVersion("99.170.0.0")] diff --git a/resources/netlog_defs.ts b/resources/netlog_defs.ts index 1af00fb6..15d38254 100644 --- a/resources/netlog_defs.ts +++ b/resources/netlog_defs.ts @@ -1413,9 +1413,15 @@ const latestLogDefinitions = { blankFields: [6, 7, 8], playerIds: { 2: null, + 10: null, }, canAnonymize: true, firstOptionalField: undefined, + analysisOptions: { + include: 'filter', + filters: { sourceId: '4.{7}' }, // NPC casts only + combatantIdFields: [2, 10], + }, }, ContentFinderSettings: { type: '265', diff --git a/resources/zone_info.ts b/resources/zone_info.ts index 705a38fa..41e2099c 100644 --- a/resources/zone_info.ts +++ b/resources/zone_info.ts @@ -10674,6 +10674,7 @@ const data: ZoneInfoType = { 'en': 'Vault Oneiron', 'fr': 'Le coffre d\'Oneiron', 'ja': '宝物庫 ボルト・オネイロン', + 'ko': '보물고 볼트 오네이론', }, 'offsetX': -100, 'offsetY': -100, @@ -10684,6 +10685,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第1~10朝圣路', 'de': 'Pilgers Pfad (Ebenen 1-10)', 'en': 'Pilgrim\'s Traverse (Stones 1-10)', 'fr': 'Le Sanctuaire des pèlerins (zones 1-10)', @@ -10698,6 +10700,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第11~20朝圣路', 'de': 'Pilgers Pfad (Ebenen 11-20)', 'en': 'Pilgrim\'s Traverse (Stones 11-20)', 'fr': 'Le Sanctuaire des pèlerins (zones 11-20)', @@ -10712,6 +10715,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第21~30朝圣路', 'de': 'Pilgers Pfad (Ebenen 21-30)', 'en': 'Pilgrim\'s Traverse (Stones 21-30)', 'fr': 'Le Sanctuaire des pèlerins (zones 21-30)', @@ -10726,6 +10730,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第31~40朝圣路', 'de': 'Pilgers Pfad (Ebenen 31-40)', 'en': 'Pilgrim\'s Traverse (Stones 31-40)', 'fr': 'Le Sanctuaire des pèlerins (zones 31-40)', @@ -10740,6 +10745,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第41~50朝圣路', 'de': 'Pilgers Pfad (Ebenen 41-50)', 'en': 'Pilgrim\'s Traverse (Stones 41-50)', 'fr': 'Le Sanctuaire des pèlerins (zones 41-50)', @@ -10754,6 +10760,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第51~60朝圣路', 'de': 'Pilgers Pfad (Ebenen 51-60)', 'en': 'Pilgrim\'s Traverse (Stones 51-60)', 'fr': 'Le Sanctuaire des pèlerins (zones 51-60)', @@ -10768,6 +10775,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第61~70朝圣路', 'de': 'Pilgers Pfad (Ebenen 61-70)', 'en': 'Pilgrim\'s Traverse (Stones 61-70)', 'fr': 'Le Sanctuaire des pèlerins (zones 61-70)', @@ -10782,6 +10790,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第71~80朝圣路', 'de': 'Pilgers Pfad (Ebenen 71-80)', 'en': 'Pilgrim\'s Traverse (Stones 71-80)', 'fr': 'Le Sanctuaire des pèlerins (zones 71-80)', @@ -10796,6 +10805,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第81~90朝圣路', 'de': 'Pilgers Pfad (Ebenen 81-90)', 'en': 'Pilgrim\'s Traverse (Stones 81-90)', 'fr': 'Le Sanctuaire des pèlerins (zones 81-90)', @@ -10810,6 +10820,7 @@ const data: ZoneInfoType = { 'contentType': 21, 'exVersion': 5, 'name': { + 'cn': '朝圣交错路 第91~100朝圣路', 'de': 'Pilgers Pfad (Ebenen 91-100)', 'en': 'Pilgrim\'s Traverse (Stones 91-100)', 'fr': 'Le Sanctuaire des pèlerins (zones 91-100)', @@ -10829,6 +10840,7 @@ const data: ZoneInfoType = { 'en': 'The Meso Terminal', 'fr': 'Le Mésoterminal', 'ja': '永久幽界 メインターミナル', + 'ko': '메인 터미널', }, 'offsetX': -132, 'offsetY': -113, @@ -10844,6 +10856,7 @@ const data: ZoneInfoType = { 'en': 'The Bayside Battleground', 'fr': 'Le Rivage redoutable', 'ja': 'ベイサイド・バトルグラウンド', + 'ko': '바닷가 전투장', }, 'offsetX': -100, 'offsetY': -100, @@ -10859,6 +10872,7 @@ const data: ZoneInfoType = { 'en': 'Crystalline Conflict (Custom Match - The Bayside Battleground)', 'fr': 'Crys. Conflict (partie perso. - Le Rivage redoutable)', 'ja': 'クリスタルコンフリクト(ベイサイド・バトルグラウンド:カスタムマッチ)', + 'ko': '크리스탈라인 컨플릭트(바닷가 전투장: 친선 경기)', }, 'offsetX': -100, 'offsetY': -100, @@ -10874,6 +10888,7 @@ const data: ZoneInfoType = { 'en': 'The Ageless Necropolis', 'fr': 'La Nécropole intemporelle', 'ja': '永遠の闇討滅戦', + 'ko': '영원한 어둠 토벌전', }, 'offsetX': -100, 'offsetY': -100, @@ -10889,6 +10904,7 @@ const data: ZoneInfoType = { 'en': 'The Minstrel\'s Ballad: Necron\'s Embrace', 'fr': 'La Nécropole intemporelle (extrême)', 'ja': '極永遠の闇討滅戦', + 'ko': '극 영원한 어둠 토벌전', }, 'offsetX': -100, 'offsetY': -100, @@ -10899,6 +10915,7 @@ const data: ZoneInfoType = { 'contentType': 4, 'exVersion': 5, 'name': { + 'cn': '护锁刃龙狩猎战', 'de': 'Jagd auf Wächter-Arkveld', 'en': 'The Windward Wilds', 'fr': 'Chasse à l\'Arkveld Gardien', @@ -10918,6 +10935,7 @@ const data: ZoneInfoType = { 'en': 'The Wreath of Snakes (Unreal)', 'fr': 'L\'Îlot des Amertumes (irréel)', 'ja': '幻青龍征魂戦', + 'ko': '환 청룡 토벌전', }, 'offsetX': -100, 'offsetY': -100, @@ -10949,6 +10967,7 @@ const data: ZoneInfoType = { 'en': 'San d\'Oria: The Second Walk', 'fr': 'San d\'Oria - La deuxième perambulation', 'ja': 'サンドリア:ザ・セカンドウォーク', + 'ko': '산도리아: 두 번째 반향세계', }, 'offsetX': -826, 'offsetY': -871, @@ -10959,6 +10978,7 @@ const data: ZoneInfoType = { 'contentType': 4, 'exVersion': 5, 'name': { + 'cn': '护锁刃龙上位狩猎战', 'de': 'Jagd auf Wächter-Arkveld (schwer)', 'en': 'The Windward Wilds (Extreme)', 'fr': 'Chasse à l\'Arkveld Gardien (extrême)', @@ -10973,6 +10993,7 @@ const data: ZoneInfoType = { 'contentType': 39, 'exVersion': 5, 'name': { + 'cn': '卓异的悲寂深想战', 'de': 'Eminente Trauer (variabel)', 'en': 'The Final Verse (Quantum)', 'fr': 'Le Verset final (quantique)', @@ -10987,6 +11008,7 @@ const data: ZoneInfoType = { 'contentType': 39, 'exVersion': 5, 'name': { + 'cn': '卓异的悲寂歼灭战', 'de': 'Eminente Trauer', 'en': 'The Final Verse', 'fr': 'Le Verset final', diff --git a/ui/raidboss/data/02-arr/dungeon/cutters_cry.ts b/ui/raidboss/data/02-arr/dungeon/cutters_cry.ts index 709bd3a1..7e4a7c03 100644 --- a/ui/raidboss/data/02-arr/dungeon/cutters_cry.ts +++ b/ui/raidboss/data/02-arr/dungeon/cutters_cry.ts @@ -10,6 +10,7 @@ const triggerSet: TriggerSet = { zoneId: ZoneId.CuttersCry, comments: { en: 'pre-7.3 rework', + de: 'Vor der 7.3 Überarbeitung', fr: 'Avant le remaniement 7.3', cn: '7.3改版前', ko: '7.3 개편 전', 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 4011e5ad..cad55177 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 @@ -52,6 +52,7 @@ const triggerSet: TriggerSet = { // TODO: some Mimics may spawn after transference between floors and get called early before being found type: 'AddedCombatant', netRegex: { npcNameId: '2566', capture: false }, + suppressSeconds: 1, infoText: (_data, _matches, output) => output.text!(), outputStrings: { text: { 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 3a90d4f4..868d64f3 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 @@ -41,6 +41,7 @@ const triggerSet: TriggerSet = { // TODO: some Quivering Coffers may spawn after transference between floors and get called early before being found type: 'AddedCombatant', netRegex: { npcNameId: '739[2-4]', capture: false }, + suppressSeconds: 1, infoText: (_data, _matches, output) => output.text!(), outputStrings: { text: { diff --git a/ui/raidboss/data/04-sb/trial/seiryu-ex.ts b/ui/raidboss/data/04-sb/trial/seiryu-ex.ts index 9e50da58..141609c2 100644 --- a/ui/raidboss/data/04-sb/trial/seiryu-ex.ts +++ b/ui/raidboss/data/04-sb/trial/seiryu-ex.ts @@ -47,6 +47,7 @@ const triggerSet: TriggerSet = { outputStrings: { grabTether: { en: 'Grab Tank Tether, Point Cleave Away', + de: 'Nimm Tank-Verbindung, zeige den Cleave weg', fr: 'Prenez le lien Tank, visez l\'extérieur', cn: '接走坦克连线,引导扇形远离人群', ko: '줄 채고, 바깥으로 유도', @@ -240,24 +241,28 @@ const triggerSet: TriggerSet = { dirW: Outputs.west, goToSnakes: { en: 'Go ${dir} Toward Snakes', + de: 'Geh Richtung ${dir} zu den Schlangen', fr: 'Allez ${dir} vers les serpents', cn: '向 ${dir} 蛇走', ko: '뱀 ${dir}으로 가요', }, goToUnknownSnakes: { en: 'Go Toward Snakes', + de: 'Geh zu den Schlangen', fr: 'Allez vers les serpents', cn: '向蛇走', ko: '뱀 쪽으로 가요', }, outOfMiddleTowardSnakes: { en: 'Out Of Middle, Knockback To ${dir}', + de: 'Raus aus der Mitte, Rückstoß nach ${dir}', fr: 'Loin du centre, Poussée vers ${dir}', cn: '离开中间,击退到 ${dir}', ko: '가운데는 위험! ${dir}으로 넉백', }, outOfMiddleUnknownSnakes: { en: 'Out Of Middle, Knockback Toward Snakes', + de: 'Raus aus der Mitte, Rückstoß zu den Schlangen', fr: 'Loin du centre, Poussée vers les serpents', cn: '离开中间,向蛇击退', ko: '가운데는 위험! 뱀 쪽으로 넉백', @@ -355,11 +360,10 @@ const triggerSet: TriggerSet = { timelineReplace: [ { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Aka-no-shiki': 'Aka no Shiki', 'Ao-no-shiki': 'Ao no Shiki', - 'Blue Orochi': 'blau[a] Orochi', + 'Blue Orochi': 'blau(?:e|er|es|en) Orochi', 'Iwa-no-shiki': 'Iwa no Shiki', 'Numa-no-shiki': 'Numa no Shiki', 'Seiryu': 'Seiryu', @@ -367,6 +371,8 @@ const triggerSet: TriggerSet = { 'Yama-no-shiki': 'Yama no Shiki', }, 'replaceText': { + '--large add spawns--': '--großes Add erscheint--', + '--small adds spawn--': '--kleine Adds erscheinen--', '100-tonze Swing': '100-Tonzen-Schwung', 'Blazing Aramitama': 'Flammende Aramitama', 'Blue Bolt': 'Blauer Blitz', @@ -387,7 +393,7 @@ const triggerSet: TriggerSet = { 'Serpent Descending': 'Niedergang der Schlange', 'Serpent\'s Fang': 'Schlangengiftzahn', 'Strength of Spirit': 'Stärke des Geistes', - 'Summon Shiki': 'Shiki-Beschwörung ', + 'Summon Shiki': 'Shiki-Beschwörung', 'Yama-kagura': 'Yamakagura', }, }, diff --git a/ui/raidboss/data/05-shb/etc/the_diadem.ts b/ui/raidboss/data/05-shb/etc/the_diadem.ts index 9749074c..ac85b6c9 100644 --- a/ui/raidboss/data/05-shb/etc/the_diadem.ts +++ b/ui/raidboss/data/05-shb/etc/the_diadem.ts @@ -9,6 +9,8 @@ const triggerSet: TriggerSet = { zoneId: ZoneId.TheDiadem, comments: { en: 'Timed node spawn alert', + de: 'Zeitliche Sammelstellen Erscheinungsalarm', + fr: 'Alerte de génération de nœud programmée', cn: '限时采集点生成警报', ko: '시간제 노드 출현 알림', }, diff --git a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_51-60.ts b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_51-60.ts index 5a725186..596e483c 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_51-60.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_floors_51-60.ts @@ -1,5 +1,7 @@ import Conditions from '../../../../../resources/conditions'; +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'; @@ -118,7 +120,7 @@ const triggerSet: TriggerSet = { }, { id: 'EO 51-60 Servomechanical Minotaur 16 Octuple Swipe Cleanup', - type: 'StartsUsing', + type: 'Ability', netRegex: { id: '7C80', source: 'Servomechanical Minotaur 16', capture: false }, run: (data) => { delete data.octupleSwipes; @@ -127,63 +129,55 @@ const triggerSet: TriggerSet = { }, { id: 'EO 51-60 Servomechanical Minotaur 16 Octuple Swipe', - type: 'Ability', - netRegex: { id: '7C7B', source: 'Servomechanical Minotaur 16' }, + type: 'StartsUsingExtra', + netRegex: { id: '7C7B', capture: true }, condition: (data) => !data.calledOctupleSwipes, - durationSeconds: 10, + durationSeconds: 18, alertText: (data, matches, output) => { - // convert the heading into 0=N, 1=E, 2=S, 3=W - const heading = Math.round(2 - 2 * parseFloat(matches.heading) / Math.PI) % 4; - + const heading = Directions.hdgTo4DirNum(parseFloat(matches.heading)); data.octupleSwipes ??= []; data.octupleSwipes.push(heading); - if (data.octupleSwipes.length <= 4) + if (data.octupleSwipes.length < 5) return; data.calledOctupleSwipes = true; if (data.octupleSwipes[0] === data.octupleSwipes[4]) // swipe order is Front > Back > Right > Left > Front > Back > Right > Left - return output.text!({ - dir1: output.left!(), - dir2: output.front!(), - dir3: output.left!(), - dir4: output.front!(), - }); + // dodge order is Left > Front > Front > Front > Left > Front > Front > Front + return output.repeat!({ left: output.left!(), front: output.front!() }); if (data.octupleSwipes[3] === data.octupleSwipes[4]) // swipe order is Front > Back > Right > Left > Left > Right > Back > Front - return output.text!({ - dir1: output.left!(), - dir2: output.front!(), - dir3: output.front!(), - dir4: output.left!(), - }); + // dodge order is Left > Front > Front > Front > Front > Front > Front > Left + return output.rewind!({ left: output.left!(), front: output.front!() }); // something went wrong data.calledOctupleSwipes = false; - return; + return output.avoid!(); }, outputStrings: { - front: { - en: 'Front', - ja: '前', - ko: '🡹', + repeat: { + en: '${left} => ${front} x3 => ${left} => ${front} x3', + de: '${left} => ${front} x3 => ${left} => ${front} x3', + cn: '${left} => ${front} x3 => ${left} => ${front} x3', + ko: '${left} 🔜 ${front}x3 🔜 ${left} 🔜 ${front}x3', }, - left: { - en: 'Left', - ja: '左へ', - ko: '🡸', + rewind: { + en: '${left} => ${front} x6 => ${left}', + de: '${left} => ${front} x6 => ${left}', + cn: '${left} => ${front} x6 => ${left}', + ko: '${left} 🔜 ${front}x6 🔜 ${left}', }, - text: { - en: '${dir1} > ${dir2} > ${dir3} > ${dir4}', - de: '${dir1} > ${dir2} > ${dir3} > ${dir4}', - fr: '${dir1} > ${dir2} > ${dir3} > ${dir4}', - ja: '${dir1} > ${dir2} > ${dir3} > ${dir4}', - cn: '${dir1} > ${dir2} > ${dir3} > ${dir4}', - ko: '${dir1} ${dir2} ${dir3} ${dir4}', + avoid: { + en: 'Avoid swipes x8', + de: 'Vermeide Schwung x8', + cn: '避开顺劈 x8', + ko: '스와이프x8 피해요!', }, + left: Outputs.left, + front: Outputs.front, }, }, { 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 a883bf1f..4d2a3513 100644 --- a/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts +++ b/ui/raidboss/data/06-ew/deepdungeon/eureka_orthos_general.ts @@ -42,6 +42,7 @@ const triggerSet: TriggerSet = { // TODO: some Mimics may spawn after transference between floors and get called early before being found type: 'AddedCombatant', netRegex: { npcNameId: '2566', capture: false }, + suppressSeconds: 1, infoText: (_data, _matches, output) => output.text!(), outputStrings: { text: { diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts index 18179fe4..d3589354 100644 --- a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_general.ts @@ -24,6 +24,8 @@ const triggerSet: TriggerSet = { ], zoneLabel: { en: 'Pilgrim\'s Traverse (All Stones)', + de: 'Pilgers Pfad (Alle Steine)', + cn: '朝圣交错路 (全朝圣路)', ko: '필그림 트래버스 (모든 층)', }, @@ -218,14 +220,26 @@ const triggerSet: TriggerSet = { }, haste: { en: 'Haste', + de: 'Hast', + fr: 'Hâte', + ja: 'ヘイスト', + cn: '加速', ko: '헤이스트(Haste)', }, purification: { en: 'Purification', + de: 'Reinigung', + fr: 'Purification', + ja: '浄化の守り', + cn: '净化护符', ko: '이상 현상 바리어(Purification)', }, devotion: { en: 'Devotion', + de: 'Weisung', + fr: 'Dévotion', + ja: '巡礼の導き', + cn: '朝圣的指引', ko: '헌신(Devotion)', }, unknown: Outputs.unknown, @@ -266,18 +280,34 @@ const triggerSet: TriggerSet = { outputStrings: { duplicate: { en: '${incense} duplicate', + de: 'Doppelter ${incense}', + fr: '${incense} dupliqué(e)', + ja: '${incense} 被り', + cn: '${incense} 重复', ko: '${incense} 또 나옴', }, mazeroot: { en: 'Mazeroot', + de: 'Wandelwurz', + fr: 'Sagacité', + ja: '明敏', + cn: '敏慧', ko: '미로뿌리(Mazeroot)', }, barkbalm: { en: 'Barkbalm', + de: 'Sakralharz', + fr: 'Quiétude', + ja: '安寧', + cn: '安宁', ko: '나무껍질연고(Barkbalm)', }, poisonfruit: { en: 'Poisonfruit', + de: 'Todesbeeren', + fr: 'Fatalité', + ja: '宿命', + cn: '宿命', ko: '독과일(Poisonfruit)', }, unknown: Outputs.unknown, @@ -304,6 +334,38 @@ const triggerSet: TriggerSet = { }, }, ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Mimic': 'Mimik', + }, + }, + { + 'locale': 'fr', + 'replaceSync': { + 'Mimic': 'Mimic', + }, + }, + { + 'locale': 'ja', + 'replaceSync': { + 'Mimic': 'ミミック', + }, + }, + { + 'locale': 'cn', + 'replaceSync': { + 'Mimic': '拟态怪', + }, + }, + { + 'locale': 'ko', + 'replaceSync': { + 'Mimic': '미믹', + }, + }, + ], }; export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_01-10.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_01-10.ts new file mode 100644 index 00000000..0a81fa5c --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_01-10.ts @@ -0,0 +1,135 @@ +import { Directions } from '../../../../../resources/util'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +// Pilgrim's Traverse Stones 01-10 +// TODO: Ornamental Leafman Leafmash jump order/safe spots + +// center of room for Ornamental Leafman encounter +const leafmanCenter = { + 'x': -300, + 'y': -300, +} as const; + +export interface Data extends RaidbossData { + shrubDir?: number[]; +} + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones1_10', + zoneId: ZoneId.PilgrimsTraverseStones1_10, + + triggers: [ + // ---------------- Stone 01-09 Mobs ---------------- + // intentionally blank + // ---------------- Stone 10 Boss: Ornamental Leafman ---------------- + { + id: 'PT 01-10 Ornamental Leafman Hedge Mazing', + // Branch Out/Hedge Mazing: boss faces south during the entire mechanic + // two adds explode 180° from each other, third add explodes 45° from one of the other two + // adds can be on cardinals or intercardinals + // creates two safe spots; one larger (called out) and one smaller + type: 'StartsUsingExtra', + netRegex: { id: 'AF37', capture: true }, + durationSeconds: 13.2, + infoText: (data, matches, output) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + const dirNum = Directions.xyTo8DirNum(x, y, leafmanCenter.x, leafmanCenter.y); + + (data.shrubDir ??= []).push(dirNum); + + if (data.shrubDir.length < 3) + return; + + let safeDirNum = -1; + const dirs = data.shrubDir.sort((a, b) => a - b); + const [dir1, dir2, dir3] = [dirs[0], dirs[1], dirs[2]]; + if (dir1 === undefined || dir2 === undefined || dir3 === undefined) + return output.unknownSafe!(); + if (dir2 - dir1 === 4) { + // ex: dir1 = N, dir2 = S, dir3 = NW, safe = E + safeDirNum = (dir1 + 2) % 8; + } else if (dir3 - dir2 === 4) { + // ex: dir1 = N, dir2 = NE, dir3 = SW, safe = SE + safeDirNum = (dir2 + 2) % 8; + } else if (dir3 - dir1 === 4) { + // ex: dir1 = N, dir2 = NE, dir3 = S, safe = W + safeDirNum = (dir3 + 2) % 8; + } + + if (safeDirNum === -1) + return output.unknownSafe!(); + + const safeDir = Directions.output8Dir[safeDirNum]; + if (safeDir === undefined) + return output.unknownSafe!(); + + return output.safe!({ safe: output[safeDir]!() }); + }, + outputStrings: { + safe: { + en: 'Go ${safe} + Out', + de: 'Geh nach ${safe} + Raus', + cn: '去 ${safe} + 外', + ko: '${safe} + 밖으로', + }, + unknownSafe: { + en: 'Out + Avoid shrublet explosions', + de: 'Raus + Vermeide Blumling Explosionen', + cn: '外 + 避开花小人爆炸', + ko: '밖으로 + 덤불 폭발 피해요', + }, + ...Directions.outputStrings8Dir, + }, + }, + { + id: 'PT 01-10 Ornamental Leafman Hedge Mazing Cleanup', + type: 'Ability', + netRegex: { id: 'AF36', source: 'Ornamental Leafman', capture: false }, + run: (data) => { + delete data.shrubDir; + }, + }, + { + id: 'PT 01-10 Ornamental Leafman Leafmash', + type: 'StartsUsing', + netRegex: { id: 'AC17', source: 'Ornamental Leafman', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 5, + infoText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Away from jump x4', + de: 'Weg vom Sprung x4', + cn: '远离跳跃 x4', + ko: '점프 피해요x4', + }, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Ornamental Leafman': 'Blumerich', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Ornamental Leafman': 'enfleuri', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Ornamental Leafman': '花人', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_11-20.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_11-20.ts new file mode 100644 index 00000000..6e4bfe4c --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_11-20.ts @@ -0,0 +1,134 @@ +import Conditions from '../../../../../resources/conditions'; +import { UnreachableCode } from '../../../../../resources/not_reached'; +import Outputs from '../../../../../resources/outputs'; +import { Directions } from '../../../../../resources/util'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { TriggerSet } from '../../../../../types/trigger'; + +// Pilgrim's Traverse Stones 11-20 + +type FootDirId = keyof typeof footDirs; + +const footDirs = { + '395': 'dirNE', + '396': 'dirNW', + '397': 'dirSE', + '398': 'dirSW', +} as const; + +const isFootDirId = (id: string): id is FootDirId => { + return id in footDirs; +}; + +export interface Data extends RaidbossData { + footOrder?: string[]; +} + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones11_20', + zoneId: ZoneId.PilgrimsTraverseStones11_20, + + triggers: [ + // ---------------- Stone 11-19 Mobs ---------------- + // intentionally blank + // ---------------- Stone 20 Boss: Forgiven Emulation ---------------- + { + id: 'PT 11-20 Forgiven Emulation Touchdown', + // boss always faces north during this mechanic + // 395 = front left foot + // 396 = front right foot + // 397 = back left foot + // 398 = back right foot + type: 'GainsEffect', + netRegex: { effectId: '808', target: 'Forgiven Emulation', capture: true }, + // rarely, there is a weird doubling of the 808 status loglines + // (possibly due to high latency or server load); suppress to avoid this + suppressSeconds: 2.5, + infoText: (data, matches, output) => { + const count = matches.count; + (data.footOrder ??= []).push(count); + + if (data.footOrder.length < 4) + return; + + const foot1 = data.footOrder[0]; + const foot4 = data.footOrder[3]; + + if (foot1 === undefined || foot4 === undefined) + return output.text!({ + knockback: output.knockback!(), + dir1: output.unknown!(), + dir4: output.unknown!(), + }); + + if (!isFootDirId(foot1) || !isFootDirId(foot4)) + throw new UnreachableCode(); + + return output.text!({ + knockback: output.knockback!(), + dir1: output[footDirs[foot1]]!(), + dir4: output[footDirs[foot4]]!(), + }); + }, + outputStrings: { + text: { + en: '${knockback} ${dir4} => ${dir1}', + de: '${knockback} ${dir4} => ${dir1}', + cn: '${knockback} ${dir4} => ${dir1}', + ko: '${knockback} ${dir4} 🔜 ${dir1}', + }, + knockback: Outputs.knockback, + unknown: Outputs.unknown, + ...Directions.outputStrings8Dir, + }, + }, + { + id: 'PT 11-20 Forgiven Emulation Touchdown Cleanup', + type: 'Ability', + netRegex: { id: 'A9BF', source: 'Forgiven Emulation', capture: false }, + run: (data) => { + delete data.footOrder; + }, + }, + { + id: 'PT 11-20 Forgiven Emulation Bare Root Planting', + type: 'HeadMarker', + netRegex: { id: '0017', capture: true }, + condition: Conditions.targetIsYou(), + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Seed on YOU', + de: 'Samen auf DIR', + cn: '种子点名', + ko: '내게 씨앗!', + }, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Forgiven Emulation': 'geläutert(?:e|er|es|en) Wetteifer', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Emulation': 'imitation pardonnée', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Emulation': 'フォーギヴン・エミュレーション', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_21-30.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_21-30.ts new file mode 100644 index 00000000..f6b66496 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_21-30.ts @@ -0,0 +1,218 @@ +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 Stones 21-30 + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones21_30', + zoneId: ZoneId.PilgrimsTraverseStones21_30, + + triggers: [ + // ---------------- Stone 21-29 Mobs ---------------- + { + id: 'PT 21-30 Traverse Clionid Parasitism', + // enrage on targeted player + type: 'StartsUsing', + netRegex: { id: 'AE89', source: 'Traverse Clionid', capture: true }, + alertText: (data, matches, output) => { + const target = matches.target; + if (target === undefined) + return output.enrage!(); + if (target === data.me) + return output.enrageOnYou!(); + return output.enrageOnPlayer!({ player: data.party.member(target) }); + }, + outputStrings: { + enrage: { + en: 'Parasitism', + de: 'Instinkt des Räubers', + cn: '捕食行动', + ko: '기생충', + }, + enrageOnYou: { + en: 'Parasitism on YOU', + de: 'Instinkt des Räubers auf DIR', + cn: '捕食行动点名', + ko: '내게 기생충!', + }, + enrageOnPlayer: { + en: 'Parasitism on ${player}', + de: 'Instinkt des Räubers auf ${player}', + cn: '捕食行动点 ${player}', + ko: '기생충: ${player}', + }, + }, + }, + { + id: 'PT 21-30 Forgiven Intolerance Swinge', + type: 'StartsUsing', + netRegex: { id: 'AE8C', source: 'Forgiven Intolerance', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 21-30 Forgiven Ambition Ripper Claw', + type: 'StartsUsing', + netRegex: { id: 'AE7D', source: 'Forgiven Ambition', capture: false }, + response: Responses.awayFromFront(), + }, + // ---------------- Stone 30 Boss: Forgiven Treachery ---------------- + // 9A3F = Liturgy of Light dummy self-cast + // 9ADA = Brutal Halo inner ring + // 9ADB = Brutal Halo inner-middle ring + // 9AFA = Brutal Halo outer-middle ring + // 9B3F = Brutal Halo outer ring + // 9BC4 = Bounds of Indulgence spinning discs? + // 9DD1 = Grip of Salvation dummy self-cast + // 9E67 = Grip of Salvation damage cast + // 9E65 = Grip of Salvation dummy self-cast + // AF7F = Grip of Salvation damage cast + // 9E66 = Salvation's Reach instant dummy self-cast + // 9DDB = Salvation's Reach instant dummy self-cast + // 9E68 = Salvation's Reach damage cast (very short) + // 9E69 = Salvation's Reach damage cast (very short) + // AF75 = Divine Favor dummy self-cast + // AF76 = Divine Favor environment-targeted telegraph cast + // AF77 = Divine Favor environment-targeted damage cast + { + id: 'PT 21-30 Forgiven Treachery Brutal Halo', + type: 'StartsUsing', + netRegex: { + id: ['9ADA', '9ADB', '9AFA', '9B3F'], + source: 'Forgiven Treachery', + capture: true, + }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 4, + durationSeconds: 4, + countdownSeconds: 4, + infoText: (_data, matches, output) => { + const id = matches.id; + switch (id) { + case '9ADA': + return output.middleOrOuter!(); + case '9ADB': + return output.innerOrOuter!(); + case '9AFA': + return output.innerOrMiddle!(); + case '9B3F': + return output.innerOrMiddle!(); + } + }, + outputStrings: { + middleOrOuter: { + en: 'Get Middle or Outer ring', + de: 'Geh zum mittleren oder äußeren Ring', + cn: '去中圈或外圈', + ko: '중간 / 바깥 링으로', + }, + innerOrOuter: { + en: 'Get Inner or Outer ring', + de: 'Geh zum inneren oder äußeren Ring', + cn: '去内圈或外圈', + ko: '안쪽 / 바깥 링으로', + }, + innerOrMiddle: { + en: 'Get Inner or Middle ring', + de: 'Geh zum inneren oder mittleren Ring', + cn: '去内圈或中圈', + ko: '안쪽 / 중간 링으로', + }, + }, + }, + { + id: 'PT 21-30 Forgiven Treachery Grip of Salvation', + type: 'StartsUsing', + netRegex: { id: ['9E67', 'AF7F'], source: 'Forgiven Treachery', capture: true }, + durationSeconds: (_data, matches) => parseFloat(matches.castTime), + alertText: (_data, matches, output) => { + const id = matches.id; + if (id === '9E67') + return output.left!(); + return output.right!(); + }, + outputStrings: { + left: Outputs.left, + right: Outputs.right, + }, + }, + { + id: 'PT 21-30 Forgiven Treachery Salvation\'s Reach', + // damage cast is too short to give a useful notification; use cleave cast as a proxy + type: 'Ability', + netRegex: { id: ['9E67', 'AF7F'], source: 'Forgiven Treachery', capture: true }, + durationSeconds: 8, + alertText: (_data, matches, output) => { + const id = matches.id; + if (id === '9E67') + return output.text!({ dir: output.right!() }); + return output.text!({ dir: output.left!() }); + }, + outputStrings: { + left: Outputs.left, + right: Outputs.right, + text: { + en: '${dir}, Behind Hand', + de: '${dir}, hinter der Hand', + cn: '${dir}, 去手后', + ko: '${dir}, 손 뒤로', + }, + }, + }, + { + id: 'PT 21-30 Forgiven Treachery Divine Favor', + type: 'HeadMarker', + netRegex: { id: '00C5', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: 12, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Chasing AoE on YOU', + de: 'Verfolgende AoE auf DIR', + fr: 'Ruée sur VOUS', + ja: '追跡AOE', + cn: '追踪AOE点名', + ko: '내게 따라오는 장판', + }, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Forgiven Ambition': 'geläutert(?:e|er|es|en) Begierde', + 'Forgiven Intolerance': 'geläutert(?:e|er|es|en) Intoleranz', + 'Forgiven Treachery': 'geläutert(?:e|er|es|en) Verrat', + 'Traverse Clionid': 'Wallfahrt-Clionid', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Ambition': 'ambition pardonnée', + 'Forgiven Intolerance': 'intolérance pardonnée', + 'Forgiven Treachery': 'traîtrise pardonnée', + 'Traverse Clionid': 'clionide du pèlerinage', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Ambition': 'フォーギヴン・アンビション', + 'Forgiven Intolerance': 'フォーギヴン・イントーラランス', + 'Forgiven Treachery': 'フォーギヴン・トレチャリー', + 'Traverse Clionid': 'トラバース・クリオニッド', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_31-40.ts b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_31-40.ts new file mode 100644 index 00000000..c0477265 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_31-40.ts @@ -0,0 +1,164 @@ +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 Stones 31-40 +// TODO: Forgiven Naivety Blown Blessing add order + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones31_40', + zoneId: ZoneId.PilgrimsTraverseStones31_40, + + triggers: [ + // ---------------- Stone 31-39 Mobs ---------------- + { + id: 'PT 31-40 Traverse Pegasus Gallop', + type: 'StartsUsing', + netRegex: { id: 'AE9E', source: 'Traverse Pegasus', capture: true }, + response: Responses.knockbackOn(), + }, + { + id: 'PT 31-40 Traverse Pegasus Nicker', + type: 'StartsUsing', + netRegex: { id: 'AE9F', source: 'Traverse Pegasus', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 31-40 Traverse Inquisitor Death\'s Door', + type: 'StartsUsing', + netRegex: { id: 'AE96', source: 'Traverse Inquisitor', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 31-40 Forgiven Petulance Left Tentacle', + type: 'StartsUsing', + netRegex: { id: 'AE92', source: 'Forgiven Petulance', capture: false }, + response: Responses.goRight(), + }, + { + id: 'PT 31-40 Forgiven Petulance Right Tentacle', + type: 'StartsUsing', + netRegex: { id: 'AE93', source: 'Forgiven Petulance', capture: false }, + response: Responses.goLeft(), + }, + { + id: 'PT 31-40 Traverse Soldierstone Hand of Judgment', + type: 'StartsUsing', + netRegex: { id: '9E6D', source: 'Traverse Soldierstone', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 3, + response: Responses.outOfMelee(), + }, + { + id: 'PT 31-40 Traverse Soldierstone Moatmaker', + // follows-up with 9E6F Buster Knuckles instant medium AoE + type: 'StartsUsing', + netRegex: { id: '9E6E', source: 'Traverse Soldierstone', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid jump => Out', + de: 'Sprung vermeiden => Raus', + cn: '避开跳跃 => 外', + ko: '점프 피하고 🔜 밖으로', + }, + }, + }, + { + id: 'PT 31-40 Traverse Soldierstone Buster Knuckles', + type: 'StartsUsing', + netRegex: { id: '9E6F', source: 'Traverse Soldierstone', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 31-40 Forgiven Plague Poison Pollen Pair', + type: 'StartsUsing', + netRegex: { id: 'A180', source: 'Forgiven Plague', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 31-40 Forgiven Prejudice Petrifying Light', + type: 'StartsUsing', + netRegex: { id: 'AE8F', source: 'Forgiven Prejudice', capture: true }, + response: Responses.lookAwayFromSource(), + }, + // ---------------- Stone 40 Boss: Forgiven Naivety ---------------- + { + id: 'PT 31-40 Forgiven Naivety Blown Blessing', + type: 'StartsUsing', + netRegex: { id: ['A48B', 'A48C'], source: 'Forgiven Naivety', capture: true }, + durationSeconds: 18, + infoText: (_data, matches, output) => { + const mech = matches.id === 'A48B' ? output.knockback!() : output.aoe!(); + return output.text!({ mech: mech }); + }, + outputStrings: { + text: { + en: '${mech} x3', + de: '${mech} x3', + cn: '${mech} x3', + ko: '${mech}x3', + }, + knockback: Outputs.knockback, + aoe: Outputs.aoe, + }, + }, + { + id: 'PT 31-40 Forgiven Naivety Near Tide', + type: 'StartsUsing', + netRegex: { id: 'B041', source: 'Forgiven Naivety', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 31-40 Forgiven Naivety Far Tide', + type: 'StartsUsing', + netRegex: { id: 'B042', source: 'Forgiven Naivety', capture: false }, + response: Responses.getIn(), + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Forgiven Naivety': 'geläutert(?:e|er|es|en) Naivität', + 'Forgiven Petulance': 'geläutert(?:e|er|es|en) Launenhaftigkeit', + 'Forgiven Plague': 'geläutert(?:e|er|es|en) Plage', + 'Forgiven Prejudice': 'geläutert(?:e|er|es|en) Voreingenommenheit', + 'Traverse Inquisitor': 'Wallfahrt-Inquisitor', + 'Traverse Pegasus': 'Wallfahrt-Pegasus', + 'Traverse Soldierstone': 'Wallfahrt-Steinsoldat', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Naivety': 'naïveté pardonnée', + 'Forgiven Petulance': 'irascibilité pardonnée', + 'Forgiven Plague': 'peste pardonnée', + 'Forgiven Prejudice': 'préjugé pardonné', + 'Traverse Inquisitor': 'inquisiteur du pèlerinage', + 'Traverse Pegasus': 'pégase du pèlerinage', + 'Traverse Soldierstone': 'soldat de pierre du pèlerinage', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Naivety': 'フォーギヴン・ナイヴテイ', + 'Forgiven Petulance': 'フォーギヴン・ペチュランス', + 'Forgiven Plague': 'フォーギヴン・プレイグ', + 'Forgiven Prejudice': 'フォーギヴン・プレジュディス', + 'Traverse Inquisitor': 'トラバース・インクイジター', + 'Traverse Pegasus': 'トラバース・ペガサス', + 'Traverse Soldierstone': 'トラバース・ストーンソルジャー', + }, + }, + ], +}; + +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 index e04787f3..cbb36f68 100644 --- 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 @@ -4,12 +4,66 @@ import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; +// Pilgrim's Traverse Stones 41-50 + export type Data = RaidbossData; const triggerSet: TriggerSet = { id: 'PilgrimsTraverseStones41_50', zoneId: ZoneId.PilgrimsTraverseStones41_50, + triggers: [ + // ---------------- Stone 41-49 Mobs ---------------- + { + id: 'PT 41-50 Traverse Weapon Smite of Rage', + type: 'StartsUsing', + netRegex: { id: 'AEAA', source: 'Traverse Weapon', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 41-50 Traverse Weapon Whirl of Rage', + type: 'StartsUsing', + netRegex: { id: 'AEAB', source: 'Traverse Weapon', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 41-50 Traverse Troubadour Tortoise Stomp', + type: 'StartsUsing', + netRegex: { id: 'A2FC', source: 'Traverse Troubadour', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 3, + response: Responses.outOfMelee(), + }, + { + id: 'PT 41-50 Traverse Triffid Creeping Combination', + type: 'StartsUsing', + netRegex: { id: 'A37E', source: 'Traverse Triffid', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 41-50 Traverse Petreffigy Magnetic Shock', + type: 'StartsUsing', + netRegex: { id: 'A1D3', source: 'Traverse Petreffigy', capture: false }, + response: Responses.drawIn(), + }, + { + id: 'PT 41-50 Traverse Petreffigy Plaincracker', + type: 'StartsUsing', + netRegex: { id: 'A228', source: 'Traverse Petreffigy', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 3, + response: Responses.getOut(), + }, + { + id: 'PT 41-50 Traverse Anaconda Dripping Fang', + type: 'StartsUsing', + netRegex: { id: 'A37A', source: 'Traverse Anaconda', capture: true }, + response: Responses.stunOrInterruptIfPossible(), + }, + { + id: 'PT 41-50 Traverse Pincerbeak Tail Smash', + type: 'StartsUsing', + netRegex: { id: 'AEA7', source: 'Traverse Pincerbeak', capture: false }, + response: Responses.getOut(), + }, // ---------------- Stone 50 Boss: Ogbunabali ---------------- { id: 'PT 41-50 Ogbunabali Liquefaction', @@ -19,6 +73,8 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Stand on a rock', + de: 'Steh auf einem Stein', + cn: '站在一块岩石上', ko: '바위 위로 올라서요', }, }, @@ -32,6 +88,9 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '4x Chasing AoE on YOU', + de: '4x verfolgende AoE auf DIR', + fr: '4x AoE sur VOUS', + cn: '四连追踪 AOE 点名', ko: '따라오는 장판 x4', }, }, @@ -40,9 +99,57 @@ const triggerSet: TriggerSet = { 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(), + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 3, + countdownSeconds: 3, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Stand in quicksand', + de: 'Im Treibsand stehen', + cn: '站在流沙里', + ko: '모래 웅덩이에 올라서요', + }, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Ogbunabali': 'Ogbunabali', + 'Traverse Anaconda': 'Wallfahrt-Anakonda', + 'Traverse Petreffigy': 'Wallfahrt-Bildnis', + 'Traverse Pincerbeak': 'Wallfahrt-Zwicker', + 'Traverse Triffid': 'Wallfahrt-Triffid', + 'Traverse Troubadour': 'Wallfahrt-Tolba', + 'Traverse Weapon': 'Wallfahrt-Waffe', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Ogbunabali': 'Ogbunabali', + 'Traverse Anaconda': 'anaconda du pèlerinage', + 'Traverse Petreffigy': 'effigie du pèlerinage', + 'Traverse Pincerbeak': 'bec-pinceur du pèlerinage', + 'Traverse Triffid': 'triffide du pèlerinage', + 'Traverse Troubadour': 'tolba du pèlerinage', + 'Traverse Weapon': 'arme du pèlerinage', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Ogbunabali': 'オグブナバリ', + 'Traverse Anaconda': 'トラバース・アナコンダ', + 'Traverse Petreffigy': 'トラバース・エフィジィ', + 'Traverse Pincerbeak': 'トラバース・ピンサービーク', + 'Traverse Triffid': 'トラバース・トリフィド', + 'Traverse Troubadour': 'トラバース・トルバ', + 'Traverse Weapon': 'トラバース・ウェポン', + }, }, ], }; 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 index 49ebd313..92aeef69 100644 --- 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 @@ -4,12 +4,98 @@ import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; +// Pilgrim's Traverse Stones 51-60 +// TODO: Ancestral Maliktender Branch Out safespots +// TODO: Ancestral Maliktender One/Two-stone March rotation direction + safespots + export type Data = RaidbossData; const triggerSet: TriggerSet = { id: 'PilgrimsTraverseStones51_60', zoneId: ZoneId.PilgrimsTraverseStones51_60, + triggers: [ + // ---------------- Stone 51-59 Mobs ---------------- + { + id: 'PT 51-60 Traverse Phorusrhacos Painful Gust', + type: 'StartsUsing', + netRegex: { id: 'AEB7', source: 'Traverse Phorusrhacos', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 51-60 Traverse Phorusrhacos Pain Storm', + type: 'StartsUsing', + netRegex: { id: 'AEB8', source: 'Traverse Phorusrhacos', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 51-60 Traverse Phorusrhacos Whorl of Hurt', + type: 'StartsUsing', + netRegex: { id: 'AEB9', source: 'Traverse Phorusrhacos', capture: false }, + response: Responses.getIn(), + }, + { + id: 'PT 51-60 Traverse Monitor Smoldering Scales', + // Gains 11E3 Blaze Spikes for 6s, high counterattack damage when hit + type: 'StartsUsing', + netRegex: { id: 'A4E4', source: 'Traverse Monitor', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 1, + durationSeconds: 7, + countdownSeconds: 7, + alertText: (_data, matches, output) => output.text!({ target: matches.target }), + outputStrings: { + text: { + en: 'Stop attacking ${target}', + de: 'Stoppe Angriffe auf ${target}', + fr: 'Arrêtez d\'attaquer ${target}', + ja: '攻撃禁止: ${target}', + cn: '停止攻击 ${target}', + ko: '${target} 공격 중지', + }, + }, + }, + { + id: 'PT 51-60 Traverse Sand Serpent Earthen Auger', + type: 'StartsUsing', + netRegex: { id: 'A46B', source: 'Traverse Sand Serpent', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 51-60 Traverse Gigant Heavy Scrapline', + type: 'StartsUsing', + netRegex: { id: 'AEC1', source: 'Traverse Gigant', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 51-60 Traverse Saichania Bafflement Bulb', + type: 'StartsUsing', + netRegex: { id: 'A4A0', source: 'Traverse Saichania', capture: true }, + response: Responses.stunOrInterruptIfPossible(), + }, + { + id: 'PT 51-60 Traverse Saichania Trounce', + type: 'StartsUsing', + netRegex: { id: 'A4A3', source: 'Traverse Saichania', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 51-60 Traverse Saichania Mighty Spin', + type: 'StartsUsing', + netRegex: { id: 'A4A4', source: 'Traverse Saichania', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 51-60 Traverse Howler Scythe Tail', + type: 'StartsUsing', + netRegex: { id: 'AEBB', source: 'Traverse Howler', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 51-60 Traverse Howler Master of Levin', + type: 'StartsUsing', + netRegex: { id: 'AEBC', source: 'Traverse Howler', capture: false }, + response: Responses.getIn(), + }, // ---------------- Stone 60 Boss: Ancestral Maliktender ---------------- { id: 'PT 51-60 Ancestral Maliktender Spineshot', @@ -29,6 +115,8 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Sides + Rotate ${rotation}', + de: 'Seiten + Im ${rotation} rotieren', + cn: '两侧 + ${rotation}旋转', ko: '옆쪽에서 + ${rotation}으로 돌아요', }, clockwise: Outputs.clockwise, @@ -48,19 +136,65 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Cactuar move ${count}', + de: 'Kaktor Bewegung - ${count}', + cn: '仙人掌移动 ${count}', ko: '선인장이 ${count} 이동', }, once: { en: 'Once', + de: 'Einmal', + cn: '一次', ko: '한 번', }, twice: { en: 'Twice', + de: 'Zweimal', + cn: '两次', ko: '두 번', }, }, }, ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Ancestral Maliktender': 'Malikkaktor-Ahn', + 'Traverse Gigant': 'Wallfahrt-Gigant', + 'Traverse Howler': 'Wallfahrt-Heuler', + 'Traverse Monitor': 'Wallfahrt-Waran', + 'Traverse Phorusrhacos': 'Wallfahrt-Phorusrhacos', + 'Traverse Saichania': 'Wallfahrt-Saichania', + 'Traverse Sand Serpent': 'Wallfahrt-Sandschlange', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Ancestral Maliktender': 'Malikpampa l\'ancien', + 'Traverse Gigant': 'géant du pèlerinage', + 'Traverse Howler': 'hurleur du pèlerinage', + 'Traverse Monitor': 'varan du pèlerinage', + 'Traverse Phorusrhacos': 'phorusrhacos du pèlerinage', + 'Traverse Saichania': 'saichania du pèlerinage', + 'Traverse Sand Serpent': 'serpent des sables du pèlerinage', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Ancestral Maliktender': '元祖マリクテンダー', + 'Traverse Gigant': 'トラバース・ギガント', + 'Traverse Howler': 'トラバース・ハウラー', + 'Traverse Monitor': 'トラバース・モニター', + 'Traverse Phorusrhacos': 'トラバース・フォルスラコス', + 'Traverse Saichania': 'トラバース・サイカニア', + 'Traverse Sand Serpent': 'トラバース・サンドサーペント', + }, + }, + ], }; 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 index 2c5c9107..00841cc6 100644 --- 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 @@ -5,6 +5,8 @@ import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; +// Pilgrim's Traverse Stones 61-70 + type DirectionOutputCardinalRelative = | 'front' | 'right' @@ -31,7 +33,148 @@ export interface Data extends RaidbossData { const triggerSet: TriggerSet = { id: 'PilgrimsTraverseStones61_70', zoneId: ZoneId.PilgrimsTraverseStones61_70, + triggers: [ + // ---------------- Stone 61-69 Mobs ---------------- + { + id: 'PT 61-70 Forgiven Doubt Body Press', + type: 'StartsUsing', + netRegex: { id: 'AECC', source: 'Forgiven Doubt', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 61-70 Traverse Cliffmole Head Butt', + type: 'StartsUsing', + netRegex: { id: 'AEC4', source: 'Traverse Cliffmole', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 61-70 Forgiven Riot Right-sided Shockwave', + type: 'StartsUsing', + netRegex: { id: 'A4E6', source: 'Forgiven Riot', capture: false }, + response: Responses.goLeftThenRight(), + }, + { + id: 'PT 61-70 Forgiven Riot Left-sided Shockwave', + type: 'StartsUsing', + netRegex: { id: 'A4E8', source: 'Forgiven Riot', capture: false }, + response: Responses.goRightThenLeft(), + }, + { + id: 'PT 61-70 Traverse Gnome Plain Pound', + type: 'StartsUsing', + netRegex: { id: 'AED1', source: 'Traverse Gnome', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 61-70 Forgiven Grudge Crystalline Stingers', + type: 'StartsUsing', + netRegex: { id: 'A610', source: 'Forgiven Grudge', capture: true }, + response: Responses.stunIfPossible(), + }, + { + id: 'PT 61-70 Forgiven Grudge Hailfire', + type: 'StartsUsing', + netRegex: { id: 'A613', source: 'Forgiven Grudge', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 61-70 Traverse Talos Accelerate', + // follows-up with A615 Subduction PBAoE, then A616 Settling Stone donut (both instant cast) + type: 'StartsUsing', + netRegex: { id: 'A614', source: 'Traverse Talos', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Away from jump => Get Under or Out', + de: 'Weg vom Sprung => Geh Unter oder Raus', + cn: '远离跳跃 => 去脚下或外面', + ko: '점프 먼곳으로 🔜 밑이나 밖으로', + }, + }, + }, + { + id: 'PT 61-70 Forgiven Attachment Sewer Water Front', + type: 'StartsUsing', + netRegex: { id: 'AECE', source: 'Forgiven Attachment', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 61-70 Forgiven Attachment Sewer Water Back', + type: 'StartsUsing', + netRegex: { id: 'AECF', source: 'Forgiven Attachment', capture: false }, + response: Responses.goFront('alert'), + }, + { + id: 'PT 61-70 Forgiven Contention Several Thousand Needles', + type: 'StartsUsing', + netRegex: { id: 'A4EC', source: 'Forgiven Contention', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 61-70 Forgiven Imparity Rockslide', + type: 'StartsUsing', + netRegex: { id: 'AEC7', source: 'Forgiven Imparity', capture: false }, + response: Responses.getIntercards(), + }, + { + id: 'PT 61-70 Traverse Queen Unfinal Sting', + type: 'StartsUsing', + netRegex: { id: 'A60E', source: 'Traverse Queen', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 61-70 Traverse Queen Final Sting', + // enrage on targeted player + type: 'StartsUsing', + netRegex: { id: 'A60F', source: 'Traverse Queen', capture: true }, + alertText: (data, matches, output) => { + const target = matches.target; + if (target === undefined) + return output.sting!(); + if (target === data.me) + return output.stingOnYou!(); + return output.stingOnPlayer!({ player: data.party.member(target) }); + }, + outputStrings: { + sting: { + en: 'Final Sting', + de: 'Schlussstich', + cn: '终极针', + ko: '파이널 스팅', + }, + stingOnYou: { + en: 'Final Sting on YOU', + de: 'Schlussstich auf DIR', + cn: '终极针点名', + ko: '내게 파이널 스팅!', + }, + stingOnPlayer: { + en: 'Final Sting on ${player}', + de: 'Schlussstich auf ${player}', + cn: '终极针点${player}', + ko: '파이널 스팅: ${player}', + }, + }, + }, + { + id: 'PT 61-70 Traverse Ngozi Landslip', + type: 'StartsUsing', + netRegex: { id: 'AED3', source: 'Traverse Ngozi', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 61-70 Forgiven Voracity Stone Gaze', + type: 'StartsUsing', + netRegex: { id: 'AECA', source: 'Forgiven Voracity', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 61-70 Forgiven Voracity Body Slam', + type: 'StartsUsing', + netRegex: { id: 'AECB', source: 'Forgiven Voracity', capture: false }, + response: Responses.outOfMelee(), + }, // ---------------- 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 @@ -64,14 +207,20 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Dodge ${dir}', + de: 'Ausweichen ${dir}', + cn: '${dir} 躲避', ko: '${dir} 피해요!', }, backFront: { en: 'Back-to-front', + de: 'Hinten-nach-Vorne', + cn: '后到前', ko: '앞에서 뒤로', }, frontBack: { en: 'Front-to-back', + de: 'Vorne-nach-Hinten', + cn: '前到后', ko: '뒤에서 앞으로', }, }, @@ -94,6 +243,8 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Dodge ${dir}', + de: 'Ausweichen ${dir}', + cn: '${dir} 躲避', ko: '${dir}으로 피해요!', }, clockwise: Outputs.clockwise, @@ -173,14 +324,20 @@ const triggerSet: TriggerSet = { outputStrings: { repeat: { en: '${dir4} => ${dir1} x3 => ${dir8} => ${dir5} x3', + de: '${dir4} => ${dir1} x3 => ${dir8} => ${dir5} x3', + cn: '${dir4} => ${dir1} x3 => ${dir8} => ${dir5} x3', ko: '${dir4} 🔜 ${dir1}x3 🔜 ${dir8} 🔜 ${dir5}x3', }, rewind: { en: '${dir4} => ${dir1} x6 => ${dir4}', + de: '${dir4} => ${dir1} x6 => ${dir4}', + cn: '${dir4} => ${dir1} x6 => ${dir4}', ko: '${dir4} 🔜 ${dir1}x6 🔜 ${dir4}', }, avoid: { en: 'Avoid swipes x8', + de: 'Vermeide Schwung x8', + cn: '避开顺劈 x8', ko: '스와이프x8 피해요!', }, left: Outputs.left, @@ -190,6 +347,64 @@ const triggerSet: TriggerSet = { }, }, ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Forgiven Attachment': 'geläutert(?:e|er|es|en) Anhänglichkeit', + 'Forgiven Contention': 'geläutert(?:e|er|es|en) Streitlust', + 'Forgiven Doubt': 'geläutert(?:e|er|es|en) Zweifel', + 'Forgiven Grudge': 'geläutert(?:e|er|es|en) Missgunst', + 'Forgiven Imparity': 'geläutert(?:e|er|es|en) Ungleichheit', + 'Forgiven Riot': 'geläutert(?:e|er|es|en) Randale', + 'Forgiven Voracity': 'geläutert(?:e|er|es|en) Unersättlichkeit', + 'Forgiven Zeal': 'geläutert(?:e|er|es|en) Übereifer', + 'Traverse Cliffmole': 'Wallfahrt-Klippenmull', + 'Traverse Gnome': 'Wallfahrt-Gnom', + 'Traverse Ngozi': 'Wallfahrt-Ngozi', + 'Traverse Queen': 'Wallfahrt-Bienenkönigin', + 'Traverse Talos': 'Wallfahrt-Talos', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Attachment': 'attachement pardonné', + 'Forgiven Contention': 'contention pardonnée', + 'Forgiven Doubt': 'doute pardonné', + 'Forgiven Grudge': 'rancune pardonnée', + 'Forgiven Imparity': 'inégalité pardonnée', + 'Forgiven Riot': 'révolte pardonnée', + 'Forgiven Voracity': 'voracité pardonnée', + 'Forgiven Zeal': 'zèle pardonné', + 'Traverse Cliffmole': 'taupe des falaises du pèlerinage', + 'Traverse Gnome': 'gnome du pèlerinage', + 'Traverse Ngozi': 'ngozi du pèlerinage', + 'Traverse Queen': 'reine des abeilles du pèlerinage', + 'Traverse Talos': 'talos du pèlerinage', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Forgiven Attachment': 'フォーギヴン・アタッチメント', + 'Forgiven Contention': 'フォーギヴン・コンテンション', + 'Forgiven Doubt': 'フォーギヴン・ダウト', + 'Forgiven Grudge': 'フォーギヴン・グラッジ', + 'Forgiven Imparity': 'フォーギヴン・イムパリティー', + 'Forgiven Riot': 'フォーギヴン・ライオティング', + 'Forgiven Voracity': 'フォーギヴン・ヴォラシティ', + 'Forgiven Zeal': 'フォーギヴン・ズィール', + 'Traverse Cliffmole': 'トラバース・クリフモール', + 'Traverse Gnome': 'トラバース・ノーム', + 'Traverse Ngozi': 'トラバース・ンゴツィ', + 'Traverse Queen': 'トラバース・クイーンビー', + 'Traverse Talos': 'トラバース・タロース', + }, + }, + ], }; 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 index 4cc16a2d..aa64533b 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 @@ -5,6 +5,9 @@ import ZoneId from '../../../../../resources/zone_id'; import { RaidbossData } from '../../../../../types/data'; import { TriggerSet } from '../../../../../types/trigger'; +// Pilgrim's Traverse Stones 71-80 +// TODO: Forgiven Profanity Static Shock safe spot + export interface Data extends RaidbossData { prowlingDeath?: 'shadowOfDeath' | 'nowhereToRun'; } @@ -12,7 +15,136 @@ export interface Data extends RaidbossData { const triggerSet: TriggerSet = { id: 'PilgrimsTraverseStones71_80', zoneId: ZoneId.PilgrimsTraverseStones71_80, + triggers: [ + // ---------------- Stone 71-79 Mobs ---------------- + { + id: 'PT 71-80 Traverse Scissorjaws Sandblast', + type: 'StartsUsing', + netRegex: { id: 'AEE1', source: 'Traverse Scissorjaws', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 71-80 Forgiven Unbelief Gravel Shower', + type: 'StartsUsing', + netRegex: { id: 'AEDB', source: 'Forgiven Unbelief', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 71-80 Traverse Huldu Fracture', + type: 'StartsUsing', + netRegex: { id: 'A712', source: 'Traverse Huldu', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 71-80 Traverse Huldu Self-destruct', + // explodes in a letal PBAoE after death + type: 'StartsUsing', + netRegex: { id: 'A713', source: 'Traverse Huldu', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 71-80 Forgiven Spite Growing Circles of Ablution', + type: 'StartsUsing', + netRegex: { id: 'A652', source: 'Forgiven Spite', capture: false }, + response: Responses.getOutThenIn(), + }, + { + id: 'PT 71-80 Forgiven Spite Shrinking Circles of Ablution', + type: 'StartsUsing', + netRegex: { id: 'A6FC', source: 'Forgiven Spite', capture: false }, + response: Responses.getInThenOut(), + }, + { + id: 'PT 71-80 Forgiven Arrogance Hail of Heels', + type: 'StartsUsing', + netRegex: { id: 'AED7', source: 'Forgiven Arrogance', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Behind x4', + ko: '엉댕이로x4', + }, + }, + }, + { + id: 'PT 71-80 Traverse Worm Earthquake', + type: 'StartsUsing', + netRegex: { id: 'AEDF', source: 'Traverse Worm', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 71-80 Forgiven Dissention Mighty Spin', + type: 'StartsUsing', + netRegex: { id: 'A618', source: 'Forgiven Dissention', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 71-80 Forgiven Dissention Trounce', + type: 'StartsUsing', + netRegex: { id: 'A61A', source: 'Forgiven Dissention', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 71-80 Forgiven Corruption Forward Barrage', + type: 'StartsUsing', + netRegex: { id: 'A651', source: 'Forgiven Corruption', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 71-80 Forgiven Corruption Rolling Barrage', + type: 'StartsUsing', + netRegex: { id: 'A61B', source: 'Forgiven Corruption', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 4, + alertText: (_data, matches, output) => output.breakLOS!({ name: matches.source }), + outputStrings: { + breakLOS: { + en: 'Break line-of-sight to ${name}', + de: 'Unterbreche Sichtlinie zu ${name}', + fr: 'Masquez le champ de vision vers ${name}', + ja: '${name}の視線から隠れる', + cn: '利用掩体卡 ${name} 的视线', + ko: '시선 잘라요: ${name}', + }, + }, + }, + { + id: 'PT 71-80 Traverse Amemet Topple', + type: 'StartsUsing', + netRegex: { id: 'AEDD', source: 'Traverse Amemet', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 71-80 Forgiven Slander Metamorphic Blast', + type: 'StartsUsing', + netRegex: { id: 'AED9', source: 'Forgiven Slander', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 71-80 Forgiven Slander Orogenic Storm', + // medium-sized AoE, locks-on to a player ground position at start of cast + type: 'StartsUsing', + netRegex: { id: 'AEDA', source: 'Forgiven Slander', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid AoE', + ko: '장판 피해요!', + }, + }, + }, + { + id: 'PT 71-80 Forgiven Vanity Cross Lasers', + type: 'StartsUsing', + netRegex: { id: 'AED5', source: 'Forgiven Vanity', capture: false }, + response: Responses.getIntercards(), + }, + { + id: 'PT 71-80 Forgiven Vanity Peripheral Lasers', + type: 'StartsUsing', + netRegex: { id: 'AED6', source: 'Forgiven Vanity', capture: false }, + response: Responses.getIn(), + }, // ---------------- Stone 80 Boss: Forgiven Profanity ---------------- // A9C9 = Roaring Ring dummy self-cast, donut + front cleave // A9CB = Roaring Ring dummy self-cast, donut + back cleave @@ -44,7 +176,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${in} + ${backFront}', - ko: '${backFront} + ${in}', + ko: '${backFront}+${in}', }, back: Outputs.back, front: Outputs.front, @@ -70,7 +202,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: '${out} + ${backFront}', - ko: '${backFront} + ${out}', + ko: '${backFront}+${out}', }, back: Outputs.back, front: Outputs.front, 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 8edcd8bf..e75d19fa 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 @@ -1,8 +1,12 @@ +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'; +// Pilgrim's Traverse Stones 81-90 +// TODO: Malacoda Arcane Beacon safespots + export type Data = RaidbossData; const triggerSet: TriggerSet = { @@ -10,18 +14,232 @@ const triggerSet: TriggerSet = { zoneId: ZoneId.PilgrimsTraverseStones81_90, triggers: [ + // ---------------- Stone 81-89 Mobs ---------------- + { + id: 'PT 81-90 Traverse Cubus Dark II', + type: 'StartsUsing', + netRegex: { id: 'AEE7', source: 'Traverse Cubus', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 81-90 Traverse Gnoll Nox Blast', + type: 'StartsUsing', + netRegex: { id: 'AEE6', source: 'Traverse Gnoll', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 81-90 Traverse Gnoll Maul', + // enrage on targeted player, goes through walls + type: 'StartsUsing', + netRegex: { id: 'AEE5', source: 'Traverse Gnoll', capture: true }, + alertText: (data, matches, output) => { + const target = matches.target; + if (target === undefined) + return output.maul!(); + if (target === data.me) + return output.maulOnYou!(); + return output.maulOnPlayer!({ player: data.party.member(target) }); + }, + outputStrings: { + maul: { + en: 'Maul', + ko: '마울', + }, + maulOnYou: { + en: 'Maul on YOU', + ko: '내게 마울', + }, + maulOnPlayer: { + en: 'Maul on ${player}', + ko: '마울: ${player}', + }, + }, + }, + { + id: 'PT 81-90 Invoked Gremlin Claw', + type: 'StartsUsing', + netRegex: { id: 'AEE2', source: 'Invoked Gremlin', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 81-90 Traverse Rider Valfodr', + type: 'StartsUsing', + netRegex: { id: 'A937', source: 'Traverse Rider', capture: true }, + response: Responses.knockbackOn(), + }, + { + id: 'PT 81-90 Traverse Rider Storm Slash', + type: 'StartsUsing', + netRegex: { id: 'A939', source: 'Traverse Rider', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 81-90 Invoked Arch Demon Abyssal Swing', + type: 'StartsUsing', + netRegex: { id: 'AEEC', source: 'Invoked Arch Demon', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 81-90 Invoked Satana Blizzard Trap', + type: 'StartsUsing', + netRegex: { id: 'AEEB', source: 'Invoked Satana', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 81-90 Invoked Succubus Passions\' Heat', + // applies 3C0 Pyretic in an AoE for 3s, lethal damage if doing anything + type: 'StartsUsing', + netRegex: { id: 'A93A', source: 'Invoked Succubus', capture: true }, + alarmText: (data, matches, output) => { + const target = matches.target; + if (target === undefined) + return output.heat!(); + if (target === data.me) + return output.heatOnYou!(); + return output.heatOnPlayer!({ player: data.party.member(target) }); + }, + outputStrings: { + heat: { + en: 'Pyretic, Avoid AoE', + ko: '파이레틱, 장판 피해욧', + }, + heatOnYou: { + en: 'Pyretic on YOU, Away from Group => Stop Everything!', + ko: '내게 파이레틱, 혼자 멀리 🔜 그대로 멈춰욧!', + }, + heatOnPlayer: { + en: 'Pyretic on ${player}, Avoid AoE', + ko: '파이레틱: ${player}, 장판 피해욧', + }, + }, + }, + { + id: 'PT 81-90 Invoked Succubus Passions\' Heat Pyretic', + // 3C0 = Pyretic, lethal damage if doing anything + type: 'GainsEffect', + netRegex: { effectId: '3C0', capture: true }, + condition: Conditions.targetIsYou(), + durationSeconds: (_data, matches) => parseFloat(matches.duration), + countdownSeconds: (_data, matches) => parseFloat(matches.duration), + response: Responses.stopEverything(), + }, + { + id: 'PT 81-90 Invoked Troubadour Dark II', + type: 'StartsUsing', + netRegex: { id: 'AEF3', source: 'Invoked Troubadour', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 81-90 Invoked Troubadour Inner Demons', + type: 'StartsUsing', + netRegex: { id: 'AEF4', source: 'Invoked Troubadour', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 81-90 Invoked Cerberus Lightning Bolt', + // medium-sized AoE, locks-on to a player ground position at start of cast + type: 'StartsUsing', + netRegex: { id: 'AEF0', source: 'Invoked Cerberus', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid AoE', + ko: '장판 피해욧', + }, + }, + }, + { + id: 'PT 81-90 Invoked Cerberus Hellclaw', + type: 'StartsUsing', + netRegex: { id: 'AEF1', source: 'Invoked Cerberus', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 81-90 Invoked Cerberus Tail Blow', + type: 'StartsUsing', + netRegex: { id: 'AEF2', source: 'Invoked Cerberus', capture: false }, + response: Responses.goFront(), + }, + { + id: 'PT 81-90 Invoked Humbaba Triple/Quadruple Blow', + type: 'StartsUsing', + netRegex: { id: ['A93B', 'A93C'], source: 'Invoked Humbaba', capture: true }, + infoText: (_data, matches, output) => { + const blows = matches.id === 'A93B' ? 3 : 4; + return output.text!({ count: blows }); + }, + outputStrings: { + text: { + en: '${count}x attacks => Get Behind', + ko: '공격x${count} 🔜 엉댕이로', + }, + }, + }, + { + id: 'PT 81-90 Invoked Humbaba Bellows', + type: 'StartsUsing', + netRegex: { id: 'AD05', source: 'Invoked Humbaba', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 81-90 Invoked Caym Double Hex Eye', + type: 'StartsUsing', + netRegex: { id: 'AEEE', source: 'Invoked Caym', capture: true }, + response: Responses.lookAwayFromSource(), + }, + { + id: 'PT 81-90 Invoked Baal Incinerating Lahar', + type: 'StartsUsing', + netRegex: { id: 'A87D', source: 'Invoked Baal', capture: true }, + delaySeconds: (_data, matches) => parseFloat(matches.castTime) - 4, + alertText: (_data, matches, output) => output.breakLOS!({ name: matches.source }), + outputStrings: { + breakLOS: { + en: 'Break line-of-sight to ${name}', + de: 'Unterbreche Sichtlinie zu ${name}', + fr: 'Masquez le champ de vision vers ${name}', + ja: '${name}の視線を遮れ', + ko: '시선 잘라요: ${name}', + }, + }, + }, + { + id: 'PT 81-90 Invoked Baal Abyssal Ray', + // goes through walls + type: 'StartsUsing', + netRegex: { id: 'A87E', source: 'Invoked Baal', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 81-90 Traverse Cama Claw and Tail', + type: 'StartsUsing', + netRegex: { id: 'A87C', source: 'Traverse Cama', capture: false }, + response: Responses.goSides(), + }, // ---------------- Stone 90 Boss: Malacoda ---------------- { id: 'PT 81-90 Malacoda Backhand Right', type: 'StartsUsing', netRegex: { id: 'ACDA', source: 'Malacoda', capture: false }, - response: Responses.goLeft(), + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Behind + Left', + ko: '뒤+왼쪽으로', + }, + }, }, { id: 'PT 81-90 Malacoda Backhand Left', type: 'StartsUsing', netRegex: { id: 'ACDB', source: 'Malacoda', capture: false }, - response: Responses.goRight(), + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Get Behind + Right', + ko: '뒤+오른쪽으로', + }, + }, }, { id: 'PT 81-90 Malacoda Fore-hind Folly', 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 new file mode 100644 index 00000000..9053c567 --- /dev/null +++ b/ui/raidboss/data/07-dt/deepdungeon/pilgrims_traverse_stones_91-100.ts @@ -0,0 +1,197 @@ +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'; + +// Pilgrim's Traverse Stones 91-100 + +export type Data = RaidbossData; + +const triggerSet: TriggerSet = { + id: 'PilgrimsTraverseStones91_100', + zoneId: ZoneId.PilgrimsTraverseStones91_100, + + triggers: [ + // ---------------- Stone 91-98 Mobs ---------------- + { + id: 'PT 91-100 Invoked Dreamer Dark Vision', + // goes through walls + type: 'StartsUsing', + netRegex: { id: 'AD3D', source: 'Invoked Dreamer', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Invoked Dreamer Endless Nightmare', + type: 'StartsUsing', + netRegex: { id: 'AD3E', source: 'Invoked Dreamer', capture: false }, + response: Responses.getOut(), + }, + { + id: 'PT 91-100 Traverse Ya-te-veo Rotten Stench', + // goes through walls + type: 'StartsUsing', + netRegex: { id: 'ADD4', source: 'Traverse Ya-te-veo', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Traverse Triffid Creeping Ivy', + type: 'StartsUsing', + netRegex: { id: 'ADD3', source: 'Traverse Triffid', capture: false }, + response: Responses.awayFromFront(), + }, + { + id: 'PT 91-100 Invoked Poppet Whinge', + type: 'StartsUsing', + netRegex: { id: 'ADE0', source: 'Invoked Poppet', capture: false }, + response: Responses.outOfMelee(), + }, + { + id: 'PT 91-100 Invoked Sawtooth Honeyed Front', + type: 'StartsUsing', + netRegex: { id: 'AD44', source: 'Invoked Sawtooth', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Invoked Sawtooth Honeyed Left', + type: 'StartsUsing', + netRegex: { id: 'AD45', source: 'Invoked Sawtooth', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Go Front + Right', + ko: '앞+오른쪽으로', + }, + }, + }, + { + id: 'PT 91-100 Invoked Sawtooth Honeyed Right', + type: 'StartsUsing', + netRegex: { id: 'AD46', source: 'Invoked Sawtooth', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Go Front + Left', + ko: '앞+왼쪽으로', + }, + }, + }, + { + id: 'PT 91-100 Invoked Bachelor Arachne Web', + type: 'StartsUsing', + netRegex: { id: 'AD41', source: 'Invoked Bachelor', capture: true }, + condition: Conditions.targetIsYou(), + alertText: (_data, matches, output) => output.breakLOS!({ name: matches.source }), + outputStrings: { + breakLOS: { + en: 'Break line-of-sight to ${name}', + de: 'Unterbreche Sichtlinie zu ${name}', + fr: 'Masquez le champ de vision vers ${name}', + ja: '${name}の視線から隠れる', + cn: '利用掩体卡 ${name} 的视线', + ko: '시선 잘라요: ${name}', + }, + }, + }, + { + id: 'PT 91-100 Traverse Warg Triple/Quadruple Skull Dasher', + type: 'StartsUsing', + netRegex: { id: ['AD33', 'AD34'], source: 'Traverse Warg', capture: true }, + infoText: (_data, matches, output) => { + const blows = matches.id === 'AD33' ? 3 : 4; + return output.text!({ count: blows }); + }, + outputStrings: { + text: { + en: '${count}x attacks => Out of Melee', + ko: '공격x${count} 🔜 근접 피해요', + }, + }, + }, + { + id: 'PT 91-100 Traverse Warg Heavy Smash', + type: 'StartsUsing', + netRegex: { id: 'AD36', source: 'Traverse Warg', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Invoked Dahak Tail Drive', + type: 'StartsUsing', + netRegex: { id: 'ADDB', source: 'Invoked Dahak', capture: false }, + response: Responses.goFront(), + }, + { + id: 'PT 91-100 Invoked Dahak Lithic Breath', + // might bait Tail Drive if you dodge behind + type: 'StartsUsing', + netRegex: { id: 'ADDC', source: 'Invoked Dahak', capture: false }, + response: Responses.goSides(), + }, + { + id: 'PT 91-100 Invoked Archaeodemon Unholy Darkness', + // ground-targeted medium AoE with late telegraph + type: 'StartsUsing', + netRegex: { id: 'ADE2', source: 'Invoked Archaeodemon', capture: false }, + alertText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Avoid AoE', + ko: '장판 피해욧', + }, + }, + }, + { + id: 'PT 91-100 Invoked Archaeodemon Karma', + type: 'StartsUsing', + netRegex: { id: 'ADE3', source: 'Invoked Archaeodemon', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Traverse Malevolence Smashing Blow', + type: 'StartsUsing', + netRegex: { id: 'ADE1', source: 'Traverse Malevolence', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Invoked Gusion Right Smite', + type: 'StartsUsing', + netRegex: { id: 'AD39', source: 'Invoked Gusion', capture: false }, + response: Responses.goLeftThenRight(), + }, + { + id: 'PT 91-100 Invoked Gusion Left Smite', + type: 'StartsUsing', + netRegex: { id: 'AD3B', source: 'Invoked Gusion', capture: false }, + response: Responses.goRightThenLeft(), + }, + { + id: 'PT 91-100 Invoked Acheron Authority\'s Edge Right', + type: 'StartsUsing', + netRegex: { id: 'ADDD', source: 'Invoked Acheron', capture: false }, + response: Responses.goLeft(), + }, + { + id: 'PT 91-100 Invoked Acheron Authority\'s Edge Left', + type: 'StartsUsing', + netRegex: { id: 'ADDE', source: 'Invoked Acheron', capture: false }, + response: Responses.goRight(), + }, + { + id: 'PT 91-100 Invoked Destrudo Stare', + // goes through walls + type: 'StartsUsing', + netRegex: { id: 'AD42', source: 'Invoked Destrudo', capture: false }, + response: Responses.getBehind(), + }, + { + id: 'PT 91-100 Invoked Destrudo Mortal Gaze', + type: 'StartsUsing', + netRegex: { id: 'AD43', source: 'Invoked Destrudo', capture: true }, + response: Responses.lookAwayFromSource(), + }, + // ---------------- Stone 99 Boss: Eminent Grief/Devoured Eater ---------------- + // triggers in The Final Verse: pilgrims_traverse_the_final_verse.ts + ], +}; + +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 index 1e2d9181..a705f1e6 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 @@ -1,16 +1,135 @@ import Conditions from '../../../../../resources/conditions'; +import { UnreachableCode } from '../../../../../resources/not_reached'; 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'; +import { OutputStrings, 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; +// === Map Effect info: === +// --- Bounds of Sin walls --- +// locations: +// 00 +// 0B 01 +// 0A 02 +// 09 03 +// 08 04 +// 07 05 +// 06 +// +// flags: +// 00020001 - walls appearing +// 00080004 - walls disappearing +// +// --- Spinelash glass walls --- +// locations: +// 18 | 19 | 1A +// +// flags: +// 00020001 - glass breaking first time +// 00200010 - glass breaking second time + +// === Abyssal Blaze (exaflares) === +// possible exaflare starting locations [x, y]: +// [-582.019, -288.003] +// [-582.019, -311.991] +// [-588.000, -294.015] +// [-588.000, -306.009] +// [-594.012, -299.997] +// [-599.994, -294.015] +// [-599.994, -306.009] +// [-606.006, -299.997] +// [-612.018, -294.015] +// [-612.018, -306.009] +// [-618.000, -288.003] +// [-618.000, -311.991] + +const center = { + 'x': -600, + 'y': -300, +} as const; + +type DirectionOutput12 = + | 'dirN' + | 'dirNNE' + | 'dirENE' + | 'dirE' + | 'dirESE' + | 'dirSSE' + | 'dirS' + | 'dirSSW' + | 'dirWSW' + | 'dirW' + | 'dirWNW' + | 'dirNNW' + | 'unknown'; + +const output12Dir: DirectionOutput12[] = [ + 'dirN', + 'dirNNE', + 'dirENE', + 'dirE', + 'dirESE', + 'dirSSE', + 'dirS', + 'dirSSW', + 'dirWSW', + 'dirW', + 'dirWNW', + 'dirNNW', +]; + +const outputStrings12Dir: OutputStrings = { + dirN: Outputs.dirN, + dirNNE: Outputs.dirNNE, + dirENE: Outputs.dirENE, + dirE: Outputs.dirE, + dirESE: Outputs.dirESE, + dirSSE: Outputs.dirSSE, + dirS: Outputs.dirS, + dirSSW: Outputs.dirSSW, + dirWSW: Outputs.dirWSW, + dirW: Outputs.dirW, + dirWNW: Outputs.dirWNW, + dirNNW: Outputs.dirNNW, + unknown: Outputs.unknown, +}; + +const xyTo12DirNum = (x: number, y: number, centerX: number, centerY: number): number => { + // N = 0, NNE = 1, ..., NNW = 12 + x = x - centerX; + y = y - centerY; + return Math.round(6 - 6 * Math.atan2(x, y) / Math.PI) % 12; +}; + +const outputFrom12DirNum = (dirNum: number): DirectionOutput12 => { + return output12Dir[dirNum] ?? 'unknown'; +}; + +const chainsOfCondemnationOutputStrings = { + chains: { + en: 'AoE + Stop Moving!', + de: 'AoE + Nicht Bewegen!', + ja: '全体攻撃 + 止まれ!', + cn: 'AOE + 停止移动!', + ko: '전체 공격 + 움직이지마!', + }, +} as const; + +export interface Data extends RaidbossData { + myVengeanceExpiration?: number; + sidesMiddle?: 'sides' | 'middle'; + ballChains?: 'ball' | 'chains'; + walls?: number[]; + abyssalSides: boolean; + abyssalFrontBack?: 'front' | 'back'; + abyssalLeftRight?: 'left' | 'right'; + exaflares?: number[]; +} const triggerSet: TriggerSet = { id: 'TheFinalVerse', @@ -20,51 +139,197 @@ const triggerSet: TriggerSet = { ], zoneLabel: { en: 'Pilgrim\'s Traverse Stone 99/The Final Verse', + de: 'Pilgers Pfad Stein 99/Eminente Trauer', + cn: '朝圣交错路 第99朝圣路/卓异的悲寂歼灭战', + ko: '필그림 트래버스 99층/최종장', }, + initData: () => ({ + abyssalSides: false, + }), + triggers: [ // ---------------- Stone 99/The Final Verse Boss: Eminent Grief/Devoured Eater ---------------- + { + id: 'PT 99 HP Difference Warning', + // 9F6 = Damage Up + // 105F = Rehabilitation + type: 'GainsEffect', + netRegex: { + effectId: ['9F6', '105F'], + target: ['Eminent Grief', 'Devoured Eater'], + capture: false, + }, + suppressSeconds: 1, + alarmText: (_data, _matches, output) => output.text!(), + outputStrings: { + text: { + en: 'Check Boss HP Difference', + ko: '보스 체력차 확인해요', + }, + }, + }, + { + id: 'PT 99 Petrification/Hysteria', + // 01 = Petrification (failure from Light Vengeance expiring) + // 128 = Hysteria (failure from Dark Vengeance expiring) + type: 'GainsEffect', + netRegex: { effectId: ['01', '128'], capture: true }, + infoText: (_data, matches, output) => { + const effect = matches.effect; + const target = matches.target; + return output.text!({ effect: effect, target: target }); + }, + outputStrings: { + text: { + en: '${effect} on ${target}', + ko: '${target}에게 ${effect}', + }, + }, + }, + { + id: 'PT 99 Light/Dark Vengeance Refresh Warning', + // 11CF = Dark Vengeance + // 11D0 = Light Vengeance + type: 'GainsEffect', + netRegex: { effectId: ['11CF', '11D0'], capture: true }, + condition: Conditions.targetIsYou(), + preRun: (data, matches) => { + const timestamp = Date.parse(matches.timestamp); + const duration = parseFloat(matches.duration); + data.myVengeanceExpiration = timestamp + duration * 1000; + }, + delaySeconds: (_data, matches) => parseFloat(matches.duration) - 10, + infoText: (data, matches, output) => { + const timestamp = Date.parse(matches.timestamp); + const duration = parseFloat(matches.duration); + const thisExpiration = timestamp + duration * 1000; + const myExpiration = data.myVengeanceExpiration; + if (myExpiration === undefined || myExpiration > thisExpiration) + return; + return output.text!(); + }, + outputStrings: { + text: { + en: 'Refresh Vengeance', + ko: '벤젠스 갱신해둬요', + }, + }, + }, { 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) => { + preRun: (data, matches) => { + const id = matches.id; + if (id === 'AC21' || id === 'AC27') { + data.sidesMiddle = 'sides'; + } else { + data.sidesMiddle = 'middle'; + } + }, + durationSeconds: 9, + alertText: (data, matches, output) => { const id = matches.id; - if (id === 'AC21' || id === 'AC27') - return output.sides!(); - return output.middle!(); + const ballChains = data.ballChains; + const sidesMiddle = data.sidesMiddle; + if (ballChains === undefined || sidesMiddle === undefined) + return; + + if (id === 'AC21' || id === 'AC22') + return output.text!({ mech1: output[sidesMiddle]!(), mech2: output[ballChains]!() }); + return output.text!({ mech1: output[ballChains]!(), mech2: output[sidesMiddle]!() }); }, outputStrings: { + text: { + en: '${mech1} => ${mech2}', + ko: '${mech1} 🔜 ${mech2}', + }, sides: Outputs.sides, middle: Outputs.goIntoMiddle, + ball: Outputs.baitPuddles, + ...chainsOfCondemnationOutputStrings, }, }, { id: 'PT 99 Eminent Grief Ball of Fire', + type: 'StartsUsing', + netRegex: { id: ['AC1D', 'AC24'], source: 'Eminent Grief', capture: true }, + preRun: (data, _matches) => data.ballChains = 'ball', + durationSeconds: 9, + alertText: (data, matches, output) => { + const id = matches.id; + const ballChains = data.ballChains; + const sidesMiddle = data.sidesMiddle; + if (ballChains === undefined || sidesMiddle === undefined) + return; + + if (id === 'AC1D') + return output.text!({ mech1: output[ballChains]!(), mech2: output[sidesMiddle]!() }); + return output.text!({ mech1: output[sidesMiddle]!(), mech2: output[ballChains]!() }); + }, + outputStrings: { + text: { + en: '${mech1} => ${mech2}', + ko: '${mech1} 🔜 ${mech2}', + }, + sides: Outputs.sides, + middle: Outputs.goIntoMiddle, + ball: Outputs.baitPuddles, + ...chainsOfCondemnationOutputStrings, + }, + }, + { + id: 'PT 99 Eminent Grief Ball of Fire Move', type: 'Ability', netRegex: { id: ['AC1D', 'AC24'], source: 'Eminent Grief', capture: false }, - response: Responses.moveAway('alarm'), + response: Responses.moveAway('alert'), }, { 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!(), + preRun: (data, _matches) => data.ballChains = 'chains', + durationSeconds: 9, + alertText: (data, matches, output) => { + const id = matches.id; + const ballChains = data.ballChains; + const sidesMiddle = data.sidesMiddle; + if (ballChains === undefined || sidesMiddle === undefined) + return; + + if (id === 'AC20') + return output.text!({ mech1: output[ballChains]!(), mech2: output[sidesMiddle]!() }); + return output.text!({ mech1: output[sidesMiddle]!(), mech2: output[ballChains]!() }); + }, outputStrings: { text: { - en: 'AoE + Stop Moving!', - ko: '전체 공격 + 동작 그만!', + en: '${mech1} => ${mech2}', }, + sides: Outputs.sides, + middle: Outputs.goIntoMiddle, + ball: Outputs.baitPuddles, + ...chainsOfCondemnationOutputStrings, + }, + }, + { + id: 'PT 99 Blade/Ball/Chains Cleanup', + type: 'Ability', + netRegex: { + id: ['AC29', 'AC24', 'AC26'], + source: ['Eminent Grief', 'Devoured Eater'], + capture: false, + }, + suppressSeconds: 1, + run: (data, _matches) => { + delete data.ballChains; + delete data.sidesMiddle; }, }, { 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, @@ -80,6 +345,9 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Laser on YOU', + de: 'Laser auf DIR', + ja: '自分にレーザー', + cn: '激光点名', ko: '내게 레이저!', }, }, @@ -92,6 +360,9 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Avoid laser', + de: 'Laser vermeiden', + ja: 'レーザーを避ける', + cn: '避开激光', ko: '레이저 피해요', }, }, @@ -116,6 +387,9 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get Light debuff', + de: 'Licht Debuff nehmen', + ja: '光デバフを受ける', + cn: '获取光debuff', ko: '빛🟡 받아요', }, }, @@ -132,12 +406,15 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get Dark debuff', + de: 'Dunkel Debuff nehmen', + ja: '闇デバフを受ける', + cn: '获取暗debuff', ko: '어둠⚫️ 받아요', }, }, }, { - id: 'PT 99 Eminent Grief Abyssal Blaze Safe Spots', + id: 'PT 99 Eminent Grief Abyssal Blaze Front/Back Safe Spot', // AC2A = first cast, horizontal exaflares, front safe // AC2B = first cast, vertical exaflares, left or right safe // AC2C = second instant cast, horizontal exaflares, back safe @@ -146,68 +423,163 @@ const triggerSet: TriggerSet = { // 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) => { + netRegex: { id: ['AC2A', 'AC2C'], source: 'Eminent Grief', capture: true }, + preRun: (data, matches) => { 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!() }); - } + id === 'AC2A' ? data.abyssalFrontBack = 'front' : data.abyssalFrontBack = 'back'; + data.abyssalSides = false; + }, + infoText: (data, _matches, output) => { + const frontBack = data.abyssalFrontBack; + const leftRight = data.abyssalLeftRight; + if (frontBack === undefined || leftRight === undefined) + return; + + return output.text!({ frontBack: output[frontBack]!(), leftRight: output[leftRight]!() }); }, outputStrings: { text: { - en: '${safe}, for later', - ko: '(나중에 ${safe})', - }, - frontSafe: { - en: 'Front safe', - ko: '앞쪽이 안전', - }, - backSafe: { - en: 'Back safe', - ko: '뒤쪽이 안전', + en: '${frontBack}-${leftRight}, for later', + ko: '(나중에 ${frontBack}+${leftRight})', }, - leftRightSafe: { - en: 'Check safe side', - ko: '좌우 안전한 곳 찾아봐요', + front: Outputs.front, + back: Outputs.back, + left: Outputs.left, + right: Outputs.right, + }, + }, + { + id: 'PT 99 Eminent Grief Abyssal Blaze Left/Right Collector', + type: 'Ability', + netRegex: { id: ['AC2B', 'AC2D'], source: 'Eminent Grief', capture: false }, + run: (data, _matches) => { + data.abyssalSides = true; + }, + }, + { + id: 'PT 99 Eminent Grief Abyssal Blaze Left/Right Safe Spot', + type: 'AbilityExtra', + netRegex: { id: 'AC2E', capture: true }, + condition: (data) => data.abyssalSides, + preRun: (data, matches) => { + const x = parseFloat(matches.x); + (data.exaflares ??= []).push(x); + + if (data.exaflares === undefined || data.exaflares.length < 4) + return; + + const exas = data.exaflares.sort((a, b) => a - b); + const [x1, x4] = [exas[0], exas[3]]; + if (x1 === undefined || x4 === undefined) + throw new UnreachableCode(); + + if (x1 < -615) { + data.abyssalLeftRight = 'right'; + } else if (x4 > -585) { + data.abyssalLeftRight = 'left'; + } + }, + infoText: (data, _matches, output) => { + const frontBack = data.abyssalFrontBack; + const leftRight = data.abyssalLeftRight; + if (frontBack === undefined || leftRight === undefined) + return; + + return output.text!({ frontBack: output[frontBack]!(), leftRight: output[leftRight]!() }); + }, + outputStrings: { + text: { + en: '${frontBack}-${leftRight}, for later', + ko: '(나중에 ${frontBack}+${leftRight})', }, + front: Outputs.front, + back: Outputs.back, + left: Outputs.left, + right: Outputs.right, }, }, { id: 'PT 99 Eminent Grief Abyssal Blaze', type: 'StartsUsing', netRegex: { id: 'AC2F', source: 'Eminent Grief', capture: false }, + durationSeconds: 16, suppressSeconds: 1, - alertText: (_data, _matches, outputs) => outputs.text!(), + alertText: (data, _matches, output) => { + const frontBack = data.abyssalFrontBack === undefined ? 'unknown' : data.abyssalFrontBack; + const leftRight = data.abyssalLeftRight === undefined ? 'unknown' : data.abyssalLeftRight; + + return output.text!({ frontBack: output[frontBack]!(), leftRight: output[leftRight]!() }); + }, + run: (data) => { + data.abyssalSides = false; + delete data.abyssalFrontBack; + delete data.abyssalLeftRight; + delete data.exaflares; + }, outputStrings: { text: { - en: 'Avoid Exaflares', - ko: '엑사플레어 피해요', + en: '${frontBack}-${leftRight}, Avoid Exaflares', + ko: '${frontBack}+${leftRight}, 엑사플레어 피해요', }, + front: Outputs.front, + back: Outputs.back, + left: Outputs.left, + right: Outputs.right, + unknown: Outputs.unknown, }, }, { - 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!(), + id: 'PT 99 Bounds of Sin Dodge Direction', + type: 'StartsUsingExtra', + netRegex: { id: 'AC33', capture: true }, + suppressSeconds: (data) => { + const walls = data.walls; + if (walls === undefined || walls.length < 1) + return 0; + return 6.5; + }, + infoText: (data, matches, output) => { + const [x, y] = [parseFloat(matches.x), parseFloat(matches.y)]; + const dir = xyTo12DirNum(x, y, center.x, center.y); + (data.walls ??= []).push(dir); + + const walls = data.walls; + if (walls === undefined || walls.length < 2) + return; + + const [wall1, wall2] = [walls[0], walls[1]]; + if (wall1 === undefined || wall2 === undefined) + throw new UnreachableCode(); + + const isCW = wall2 - wall1 === 1 || wall1 - wall2 === 11; + const isCCW = wall1 - wall2 === 1 || wall2 - wall1 === 11; + const rotationDir = isCW ? 'cw' : isCCW ? 'ccw' : undefined; + + if (rotationDir === undefined) + return output.text!({ dir: output.unknown!() }); + + if (rotationDir === 'cw') { + const dodgeDir = outputFrom12DirNum((wall2 + 10) % 12); + return output.text!({ dir: output[dodgeDir]!() }); + } + const dodgeDir = outputFrom12DirNum((wall1 + 1) % 12); + return output.text!({ dir: output[dodgeDir]!() }); + }, outputStrings: { text: { - en: '(Spread, later)', - ko: '(곧 구슬, 흩어져요)', + en: 'Go ${dir}', + ko: '가세요: ${dir}', }, + unknown: Outputs.unknown, + ...outputStrings12Dir, }, }, + { + id: 'PT 99 Bounds of Sin Dodge Direction Cleanup', + type: 'Ability', + netRegex: { id: 'AC34', source: 'Devoured Eater', capture: false }, + run: (data, _matches, _output) => delete data.walls, + }, ], timelineReplace: [ { @@ -231,6 +603,13 @@ const triggerSet: TriggerSet = { 'Eminent Grief': 'エミネントグリーフ', }, }, + { + 'locale': 'cn', + 'replaceSync': { + 'Devoured Eater': '被侵蚀的食罪灵', + 'Eminent Grief': '卓异的悲寂', + }, + }, ], }; diff --git a/ui/raidboss/data/07-dt/dungeon/meso-terminal.ts b/ui/raidboss/data/07-dt/dungeon/meso-terminal.ts new file mode 100644 index 00000000..a3402814 --- /dev/null +++ b/ui/raidboss/data/07-dt/dungeon/meso-terminal.ts @@ -0,0 +1,713 @@ +import Conditions from '../../../../../resources/conditions'; +import Outputs from '../../../../../resources/outputs'; +import { callOverlayHandler } from '../../../../../resources/overlay_plugin_api'; +import { Responses } from '../../../../../resources/responses'; +import { DirectionOutputIntercard, Directions } from '../../../../../resources/util'; +import ZoneId from '../../../../../resources/zone_id'; +import { RaidbossData } from '../../../../../types/data'; +import { PluginCombatantState } from '../../../../../types/event'; +import { TriggerSet } from '../../../../../types/trigger'; + +export interface Data extends RaidbossData { + chirurgeonSpots: chirurgeonCoords[]; + playerExecutionerId?: string; + seenFirstStorm: boolean; + seenFirstBombardment: boolean; + safeTerror?: PluginCombatantState; + orthoKerauNorth?: boolean; + orthoKerauSouth?: boolean; + kerauDiagSafe: DirectionOutputIntercard[]; +} + +type chirurgeonCoords = { + x: number; + y: number; +}; + +// List out the safe spots for diagonals. +const diagPositiveSafe: DirectionOutputIntercard[] = ['dirNW', 'dirSE']; +const diagNegativeSafe: DirectionOutputIntercard[] = ['dirSW', 'dirNE']; + +const findNorthTerror = (terrors: PluginCombatantState[]): PluginCombatantState | undefined => { + return terrors.filter((terror) => { + // For all Bombardments, one of the north corners will have a single add present, + // at one of (-10, -13) (10, -13) (-17, -12) (17, -12). + // The corresponding -X point will have five adds surrounding it. + // We can consistently identify a north safespot by finding this single add. + + // Only X gets run through abs(), since we are only calling north safe. + const terX = Math.round(Math.abs(terror.PosX)); + const terY = Math.round(terror.PosY); + const x1 = 10; + const y1 = -13; + const x2 = 17; + const y2 = -12; + return (terX === x1 && terY === y1) || (terX === x2 && terY === y2); + })[0]; +}; + +const triggerSet: TriggerSet = { + id: 'themesoterminal', + zoneId: ZoneId.TheMesoTerminal, + timelineFile: 'meso-terminal.txt', + initData: () => { + return { + chirurgeonSpots: [], + playerExecutionerId: undefined, + seenFirstStorm: false, + seenFirstBombardment: false, + impressionActive: false, + safeTerror: undefined, + orthoKerauNorth: undefined, + orthoKerauSouth: undefined, + kerauDiagSafe: [], + }; + }, + triggers: [ + { + id: 'Meso Terminal Chirurgeon Medicine Field', + type: 'StartsUsing', + netRegex: { id: 'AB16', source: 'Chirurgeon General', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Meso Terminal Chirurgeon Biochemical Front', + type: 'StartsUsing', + netRegex: { id: 'AB1A', source: 'Chirurgeon General', capture: false }, + response: Responses.getBehind(), + }, + { + // The origin for the Chirurgeon is (270,12). + // Aerosol can be cast at four cardinal points, + // each ten units along an axis from the origin. + // (270,22), (280,12), (270,2),(260,12) + id: 'Meso Terminal Chirurgeon Pungent Aerosol', + type: 'StartsUsingExtra', + netRegex: { id: 'AB1F', capture: true }, + infoText: (_data, matches, output) => { + const x = parseFloat(matches.x); + const y = parseFloat(matches.y); + const kbNum = Directions.xyTo4DirNum(x, y, 270, 12); + const safeDir = Directions.outputFromCardinalNum(kbNum); + return output.knockbackFrom!({ dir: output[safeDir]!() }); + }, + outputStrings: { + knockbackFrom: { + en: 'Knockback from ${dir}', + de: 'Rückstoß von ${dir}', + cn: '从 ${dir} 击退', + ko: '${dir}에서 넉백', + }, + dirN: Outputs.north, + dirE: Outputs.east, + dirS: Outputs.south, + dirW: Outputs.west, + unknown: Outputs.unknown, + }, + }, + { + // AB1E is the big Sterile Sphere + // AB1D is small + id: 'Meso Terminal Chirurgeon Sterile Sphere Collect', + type: 'StartsUsingExtra', + netRegex: { id: 'AB1D', capture: true }, + run: (data, matches) => { + const location = { + x: Math.round(parseFloat(matches.x)), + y: Math.round(parseFloat(matches.y)), + }; + data.chirurgeonSpots.push(location); + }, + }, + { + id: 'Meso Terminal Chirurgeon Sterile Sphere Call', + type: 'StartsUsing', + netRegex: { id: 'AB1D', source: 'Chirurgeon General', capture: false }, + delaySeconds: 1, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (!data.chirurgeonSpots[0] || !data.chirurgeonSpots[1]) + return; + const spot1 = data.chirurgeonSpots[0]; + const spot2 = data.chirurgeonSpots[1]; + // Both spheres east or both west. + // X value under 270 means east safe. + if (spot1.x === spot2.x) { + if (spot1.x < 270) + return output.goEast!(); + return output.goWest!(); + } + // Both spheres north or south. + // Y values increase from north to south. + // Y value over 12 means north safe. + if (spot1.y === spot2.y) { + if (spot1.y > 12) + return output.goNorth!(); + return output.goSouth!(); + } + return output.unknown!(); + }, + run: (data) => data.chirurgeonSpots = [], + outputStrings: { + goNorth: Outputs.north, + goEast: Outputs.east, + goSouth: Outputs.south, + goWest: Outputs.west, + unknown: Outputs.unknown, + }, + }, + { + id: 'Meso Terminal Chirurgeon Concentrated Dose', + type: 'StartsUsing', + netRegex: { id: 'AB17', source: 'Chirurgeon General', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Meso Terminal Chirurgeon Poison', + type: 'GainsEffect', + netRegex: { effectId: '838', source: 'Chirurgeon General', capture: true }, + condition: (data) => data.CanCleanse(), + infoText: (_data, matches, output) => output.cleanse!({ target: matches.target }), + outputStrings: { + cleanse: { + en: 'Cleanse ${target}', + de: 'Reinige ${target}', + cn: '康复 ${target}', + ko: '에스나: ${target}', + }, + }, + }, + { + id: 'Meso Terminal Executioners Shackles Of Fate Tracking', + type: 'StartsUsing', + netRegex: { id: 'AA3D', capture: true }, + condition: Conditions.targetIsYou(), + run: (data, matches) => data.playerExecutionerId = matches.sourceId, + }, + { + id: 'Meso Terminal Executioners Head-Splitting Roar', + type: 'StartsUsing', + netRegex: { id: 'AA3B', source: 'Hooded Headsman', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Meso Terminal Executioners Chopping Block', + type: 'StartsUsing', + netRegex: { id: 'AA4B', capture: true }, + condition: (data, matches) => data.playerExecutionerId === matches.sourceId, + response: Responses.getOut(), + }, + { + id: 'Meso Terminal Executioners Execution Wheel', + type: 'StartsUsing', + netRegex: { id: 'AA4C', capture: true }, + condition: (data, matches) => data.playerExecutionerId === matches.sourceId, + response: Responses.getIn(), + }, + { + id: 'Meso Terminal Executioners Flaying Flail', + type: 'Ability', + netRegex: { id: 'AA47', capture: true }, + condition: (data, matches) => data.playerExecutionerId === matches.sourceId, + delaySeconds: 1, // Wait for visuals + infoText: (_data, _matches, output) => output.avoidFlails!(), + outputStrings: { + avoidFlails: { + en: 'Away from flails', + de: 'Weg vom Flegel', + cn: '远离铁球', + ko: '플레일 피해요', + }, + }, + }, + { + id: 'Meso Terminal Executioners Hellmaker Adds', + type: 'AddedCombatant', + netRegex: { npcBaseId: '48D2', capture: false }, + condition: (data) => data.role === 'dps', + suppressSeconds: 1, + response: Responses.killAdds(), + }, + { + id: 'Meso Terminal Executioners Will Breaker', + type: 'StartsUsing', + netRegex: { id: 'AF38', capture: true }, + condition: (data, matches) => data.playerExecutionerId === matches.sourceId, + response: Responses.interruptIfPossible(), + }, + { + id: 'Meso Terminal Executioners Relentless Torment', + type: 'StartsUsing', + netRegex: { id: 'AA45', capture: true }, + condition: (data, matches) => data.playerExecutionerId === matches.sourceId, + response: Responses.tankBuster(), + }, + { + id: 'Meso Terminal Executioners Death Penalty', + type: 'GainsEffect', + netRegex: { effectId: '11F2', capture: true }, + condition: (data) => data.CanCleanse(), + alarmText: (_data, matches, output) => output.cleanseDoom!({ target: matches.target }), + outputStrings: { + cleanseDoom: { + en: 'Cleanse ${target}', + de: 'Reinige ${target}', + cn: '康复 ${target}', + ko: '에스나: ${target}', + }, + }, + }, + { + id: 'Meso Terminal Immortal Remains Recollection', + type: 'StartsUsing', + netRegex: { id: 'AB31', source: 'Immortal Remains', capture: false }, + response: Responses.aoe(), + }, + { + id: 'Meso Terminal Immortal Remains Memory Of The Storm', + type: 'StartsUsing', + netRegex: { id: 'AB2D', source: 'Immortal Remains', capture: false }, + response: Responses.stackMarker(), + run: (data) => data.seenFirstStorm = true, + }, + { + id: 'Meso Terminal Immortal Remains Memory Of The Pyre', + type: 'StartsUsing', + netRegex: { id: 'AB30', source: 'Immortal Remains', capture: true }, + response: Responses.tankBuster(), + }, + { + id: 'Meso Terminal Immortal Remains Turmoil Left', + type: 'Ability', + netRegex: { id: 'AB26', source: 'Immortal Remains', capture: false }, + response: Responses.goEast(), + }, + { + id: 'Meso Terminal Immortal Remains Turmoil Right', + type: 'Ability', + netRegex: { id: 'AB27', source: 'Immortal Remains', capture: false }, + response: Responses.goWest(), + }, + + // There aren't a lot of good indications where the Bombardments will go. + // The mechanic places the Preserved Terror adds where the tethers are, + // then does a very short Bombardment cast on the add locations. + // If there is one add, it's a small circle, AB23. + // If there are five adds, it's a large circle, AB24, + // centered equidistant from its five adds. + // On the second and later Bombardments, + // we can trigger off the AB29 Impression cast, but we have nothing like that for the first. + // The best case for finding the locations is to find all the Preserved Terrors, + // then filter down which one is in a safe position in front. + { + id: 'Meso Terminal Immortal Remains First Bombardment Collect', + type: 'GainsEffect', + netRegex: { effectId: '9F8', capture: false }, + // The Preserved Terror adds are used for more mechanics than just Bombardment. + // Unfortunately, we don't have any good way to separate out + // which ones are used for which mechanics. + // The tethers for Bombardment are also used on Electray + // and Keraunography, so we have to be very careful how we find the adds. + condition: (data) => { + // The first Memory of the Storm cast immediately precedes Bombardment. + return data.seenFirstStorm && !data.seenFirstBombardment; + }, + suppressSeconds: 1, + promise: async (data) => { + const callData = await callOverlayHandler({ + call: 'getCombatants', + }); + if (!callData.combatants.length) + return; + const terrors = callData.combatants.filter((actor) => actor.BNpcID === 18624); + data.safeTerror = findNorthTerror(terrors); + }, + }, + { + id: 'Meso Terminal Immortal Remains Bombardment No Knockback', + type: 'GainsEffect', + netRegex: { effectId: '9F8', capture: false }, + condition: (data) => data.seenFirstStorm && !data.seenFirstBombardment, + delaySeconds: 1, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (data.safeTerror === undefined) + return output.unknown!(); + const safeDir = data.safeTerror.PosX < 0 ? 'west' : 'east'; + return output.staticBombardment!({ safe: output[safeDir]!() }); + }, + run: (data) => { + data.seenFirstBombardment = true; + data.safeTerror = undefined; + }, + outputStrings: { + staticBombardment: { + en: 'Go ${safe}; Avoid small AoE', + de: 'Geh nach ${safe}; Vermeide kleine AoE', + cn: '前往 ${safe}; 避开小范围 AoE', + ko: '${safe}으로 가욧! 작은 장판은 피해요', + }, + west: Outputs.west, + east: Outputs.east, + unknown: Outputs.unknown, + }, + }, + { + id: 'Meso Terminal Immortal Remains Bombardment Knockback', + type: 'Ability', + netRegex: { id: 'AB29', source: 'Immortal Remains', capture: false }, // Impression + condition: (data) => data.seenFirstBombardment, + delaySeconds: 1, + suppressSeconds: 1, + promise: async (data) => { + const callData = await callOverlayHandler({ + call: 'getCombatants', + }); + if (!callData.combatants.length) + return; + const terrors = callData.combatants.filter((actor) => actor.BNpcID === 18624); + data.safeTerror = findNorthTerror(terrors); + }, + alertText: (data, _matches, output) => { + if (data.safeTerror === undefined) + return output.unknown!(); + const safeDir = data.safeTerror.PosX < 0 ? 'northwest' : 'northeast'; + return output.knockbackBombardment!({ safe: output[safeDir]!() }); + }, + run: (data) => { + data.safeTerror = undefined; + }, + outputStrings: { + knockbackBombardment: { + en: 'Knockback to ${safe}; Avoid AoE', + de: 'Rückstoß nach ${safe}; Vermeide AoE', + cn: '击退到 ${safe}; 避开 AoE', + ko: '${safe}으로 넉백! 장판은 피해요', + }, + northwest: Outputs.northwest, + northeast: Outputs.northeast, + unknown: Outputs.unknown, + }, + }, + { + // One Keraunography laser is always diagonal, + // one is always orthogonal. + // In all cases but front horizontal, + // orthogonal lasers leave at least a small uptime safespot. + id: 'Meso Terminal Immortal Remains Keraunography Collect', + type: 'StartsUsingExtra', + netRegex: { id: 'AB25', capture: true }, + run: (data, matches) => { + const headingNum = Directions.hdgTo8DirNum(parseFloat(matches.heading)); + // Odd heading numbers are diagonal. + if (headingNum % 2 === 1) { + // Check which diagonal we're working with. + // 1 and 5 are equivalent, as are 3 and 7. + // In practice, only 5 and 3 will ever be seen, + // since the diagonals always originate from the north end of the arena. + const diagIsPositive = (headingNum + 4) % 4 === 1; + const safeDiagonalSpots = diagIsPositive ? diagPositiveSafe : diagNegativeSafe; + data.kerauDiagSafe = safeDiagonalSpots; + } else if (headingNum % 4 === 0) { // Vertical + // As with the diagonals, in practice we will only ever see a 0 heading number. + data.orthoKerauNorth = false; + data.orthoKerauSouth = false; + } else { // Horizontal + // It is possible to see both 2 and 6 in practice, + // but it doesn't matter much. + const isNorth = parseFloat(matches.y) < 0; + data.orthoKerauNorth = isNorth; + data.orthoKerauSouth = !isNorth; + } + }, + }, + { + id: 'Meso Terminal Immortal Remains Keraunography Call', + type: 'StartsUsing', + netRegex: { id: 'AB25', source: 'Immortal Remains', capture: false }, + delaySeconds: 0.1, + suppressSeconds: 1, + alertText: (data, _matches, output) => { + if (data.orthoKerauNorth === undefined || data.orthoKerauSouth === undefined) + return; + if (data.orthoKerauNorth) { + // If the orthogonal laser is horizontal north, + // the only safespots are outside the hitbox, + // but still close enough to be handled with a GCD roll. + const safeDir = data.kerauDiagSafe.filter((dir) => dir === 'dirSW' || dir === 'dirSE')[0]; + if (safeDir !== undefined) + return output[safeDir]!(); + return output.unknown!(); + } else if (data.orthoKerauSouth) { + // If the orthogonal laser is horizontal south, + // the safespot is completely free in front. + const safeDir = data.kerauDiagSafe.filter((dir) => dir === 'dirNW' || dir === 'dirNE')[0]; + if (safeDir !== undefined) + return output[safeDir]!(); + return output.unknown!(); + } + + // If the orthogonal laser is vertical, + // there is a tiny safespot in front, + // as well as a large one on the same side south of the diagonal, + // just outside melee range. + const safeNW = data.kerauDiagSafe.includes('dirSW'); + if (safeNW) + return output.leanLeft!(); + return output.leanRight!(); + }, + run: (data) => { + data.kerauDiagSafe = []; + data.orthoKerauNorth = undefined; + data.orthoKerauSouth = undefined; + }, + outputStrings: { + leanLeft: { + en: 'Front + Lean Left; or Southwest', + de: 'Vorne + Links halten; oder Süd-Westen', + cn: '前方 + 偏左; 或左下', + ko: '앞 + 왼쪽으로 살짝 또는 남서쪽', + }, + leanRight: { + en: 'Front + Lean Right; or Southeast', + de: 'Vorne + Rechts halten; oder Süd-Osten', + cn: '前方 + 偏右; 或右下', + ko: '앞 + 오른쪽으로 살짝 또는 남동쪽', + }, + dirNW: Outputs.northwest, + dirNE: Outputs.northeast, + dirSW: Outputs.southwest, + dirSE: Outputs.southeast, + unknown: Outputs.unknown, + }, + }, + ], + timelineReplace: [ + { + 'locale': 'de', + 'replaceSync': { + 'Bloody Headsman': 'blutig(?:e|er|es|en) Henkersmann', + 'Bygone Aerostat': 'vergangen(?:e|er|es|en) Aerostat', + 'Chirurgeon General': 'Erinnerung eines Feldchirurgen', + 'Hooded Headsman': 'falsch(?:e|er|es|en) Henkersmann', + 'Immortal Remains': 'Erinnerungsschmelz', + 'Pale Headsman': 'blass(?:e|er|es|en) Henkersmann', + 'Pestilent Headsman': 'verderblich(?:e|er|es|en) Henkersmann', + }, + 'replaceText': { + '\\(active\\)': '(aktiv)', + '\\(cast\\)': '(wirken)', + '\\(castbar\\)': '(wirken)', + '--hellmaker adds--': '--Höllenerschaffer Adds--', + 'Biochemical Front': 'Biochemische Front', + 'Bombardment': 'Bombardement', + 'Chopping Block': 'Hackklotzhinrichtung', + 'Concentrated Dose': 'Konzentrierte Dosis', + 'Death Penalty': 'Höchststrafe', + 'Dismemberment': 'Zerstückelung', + 'Electray': 'Elektroblitz', + 'Execution Wheel': 'Folterrad', + 'Flaying Flail': 'Flegel der Sündenläuterung', + 'Head-splitting Roar': 'Schädelspaltergebrüll', + 'Impression': 'Impression', + 'Keraunography': 'Keraunographie', + 'Lawless Pursuit': 'Gesetzlosenverfolgung', + 'Medicine Field': 'Medizinstreuung', + 'Memento': 'Memento', + 'Memory of the Pyre': 'Erinnerung eines Scheiterhaufens', + 'Memory of the Storm': 'Erinnerung eines Sturms', + 'No Man\'s Land': 'Niemandsland', + 'Peal of Judgment': 'Klingen der Sündenläuterung', + 'Pungent Aerosol': 'Stechendes Aerosol', + 'Recollection': 'Rückbesinnung', + 'Relentless Torment': 'Unerbittliche Pein', + 'Sensory Deprivation': 'Sinnesberaubung', + 'Serial Torture': 'Qualen ohne Ende', + 'Shackles of Fate': 'Ketten des Schicksals', + 'Sterile Sphere': 'Sterile Spähre', + 'Turmoil': 'Aufregung', + 'Will Breaker': 'Willensbrecher', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Bloody Headsman': 'mémoire du bourreau rouge', + 'Bygone Aerostat': 'aérostat séculaire', + 'Chirurgeon General': 'mémoire d\'un chef de clinique', + 'Hooded Headsman': 'mémoire d\'un bourreau', + 'Immortal Remains': 'Memoria populi', + 'Pale Headsman': 'mémoire du bourreau jaune', + 'Pestilent Headsman': 'mémoire du bourreau vert', + }, + 'replaceText': { + 'Biochemical Front': 'Pulvérisation avant', + 'Bombardment': 'Bombardement', + 'Chopping Block': 'Lune tranchante', + 'Concentrated Dose': 'Injection concentrée', + 'Death Penalty': 'Condamnation à mort', + 'Dismemberment': 'Lames coupe-jambes', + 'Electray': 'Rayon électrique', + 'Execution Wheel': 'Éclipse tranchante', + 'Flaying Flail': 'Boulets de châtiment', + 'Head-splitting Roar': 'Cri de bourreau', + 'Impression': 'Impression terrestre', + 'Keraunography': 'Fulguration funeste', + 'Lawless Pursuit': 'Astreinte du condamné', + 'Medicine Field': 'Bain thérapeutique', + 'Memento': 'Reconstitution mémorielle', + 'Memory of the Pyre': 'Bombe mémorielle', + 'Memory of the Storm': 'Orage mémoriel', + 'No Man\'s Land': 'Activation des diffuseurs', + 'Peal of Judgment': 'Lames électriques de châtiment', + 'Pungent Aerosol': 'Solution explosive', + 'Recollection': 'Remémoration', + 'Relentless Torment': 'Volée de coups puissante', + 'Sensory Deprivation': 'Anesthésie sensorielle', + 'Serial Torture': 'Châtiments multiples', + 'Shackles of Fate': 'Enchaînement du condamné', + 'Sterile Sphere': 'Diffusion circulaire', + 'Turmoil': 'Commotion terrestre', + 'Will Breaker': 'Bombe pulvérisante', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Bloody Headsman': 'ある処刑人の記憶【緋】', + 'Bygone Aerostat': 'バイゴーン・エアロスタット', + 'Chirurgeon General': 'ある医療長の記憶', + 'Hooded Headsman': 'ある処刑人の記憶', + 'Immortal Remains': 'ある人々の記憶', + 'Pale Headsman': 'ある処刑人の記憶【黄】', + 'Pestilent Headsman': 'ある処刑人の記憶【翠】', + }, + 'replaceText': { + 'Biochemical Front': '前面噴霧', + 'Bombardment': '爆撃', + 'Chopping Block': '円月断', + 'Concentrated Dose': '高圧縮薬弾', + 'Death Penalty': '死刑宣告', + 'Dismemberment': '足切りの刃', + 'Electray': 'エレクトロレイ', + 'Execution Wheel': '月輪断', + 'Flaying Flail': '断罪の鉄球', + 'Head-splitting Roar': '処刑人の咆哮', + 'Impression': 'インプレッション', + 'Keraunography': '雷光', + 'Lawless Pursuit': '罪人捕捉', + 'Medicine Field': '広域散薬', + 'Memento': 'メモリーズ', + 'Memory of the Pyre': 'メモリーボム', + 'Memory of the Storm': 'メモリーストーム', + 'No Man\'s Land': '装置起動', + 'Peal of Judgment': '断罪の雷剣', + 'Pungent Aerosol': '爆発性薬', + 'Recollection': 'リコレクション', + 'Relentless Torment': '強連撃', + 'Sensory Deprivation': '知覚攪乱薬', + 'Serial Torture': '連刑執行', + 'Shackles of Fate': '束縛の鉄鎖', + 'Sterile Sphere': '円状散布', + 'Turmoil': 'ターモイル', + 'Will Breaker': '破砕弾', + }, + }, + { + 'locale': 'cn', + 'replaceSync': { + 'Bloody Headsman': '某个绯红处刑人的记忆', + 'Bygone Aerostat': '旧日航空机', + 'Chirurgeon General': '某个医师长的记忆', + 'Hooded Headsman': '某个处刑人的记忆', + 'Immortal Remains': '某些人的记忆', + 'Pale Headsman': '某个黄褐处刑人的记忆', + 'Pestilent Headsman': '某个翠绿处刑人的记忆', + }, + 'replaceText': { + '--hellmaker adds--': '--焦热刑具出现--', + '\\(active\\)': '(生效)', + '\\(cast\\)': '(咏唱)', + '\\(castbar\\)': '(读条)', + 'Biochemical Front': '正面喷雾', + 'Bombardment': '轰炸', + 'Chopping Block': '圆月断', + 'Concentrated Dose': '高压缩药弹', + 'Death Penalty': '死刑宣告', + 'Dismemberment': '斩足刃', + 'Electray': '雷质射线', + 'Execution Wheel': '月环断', + 'Flaying Flail': '断罪的铁球', + 'Head-splitting Roar': '处刑人的咆哮', + 'Impression': '印象', + 'Keraunography': '雷光', + 'Lawless Pursuit': '捉拿罪犯', + 'Medicine Field': '广域撒药', + 'Memento': '记忆重现', + 'Memory of the Pyre': '记忆爆炸', + 'Memory of the Storm': '记忆风暴', + 'No Man\'s Land': '启动装置', + 'Peal of Judgment': '断罪的雷剑', + 'Pungent Aerosol': '爆炸药液', + 'Recollection': '回忆', + 'Relentless Torment': '强连击', + 'Sensory Deprivation': '知觉扰乱药', + 'Serial Torture': '连续处刑', + 'Shackles of Fate': '铁链束缚', + 'Sterile Sphere': '圆状散布', + 'Turmoil': '慌乱', + 'Will Breaker': '破碎弹', + }, + }, + { + 'locale': 'ko', + 'replaceSync': { + 'Bloody Headsman': '어느 집행인의 기억: 빨강', + 'Bygone Aerostat': '구시대 비행체', + 'Chirurgeon General': '어느 의료장의 기억', + 'Hooded Headsman': '어느 집행인의 기억', + 'Immortal Remains': '어느 사람들의 기억', + 'Pale Headsman': '어느 집행인의 기억: 노랑', + 'Pestilent Headsman': '어느 집행인의 기억: 초록', + }, + 'replaceText': { + '--hellmaker adds--': '--초열 형구 등장--', + '\\(active\\)': '(활성)', + '\\(cast\\)': '(시전)', + '\\(castbar\\)': '(시전바)', + 'Biochemical Front': '전방 분무', + 'Bombardment': '폭격', + 'Chopping Block': '만월참', + 'Concentrated Dose': '고압축 물약', + 'Death Penalty': '사형 언도', + 'Dismemberment': '발목 절단날', + 'Electray': '전기 광선', + 'Execution Wheel': '월륜참', + 'Flaying Flail': '단죄의 철퇴', + 'Head-splitting Roar': '집행인의 포효', + 'Impression': '기억 각인', + 'Keraunography': '뇌광', + 'Lawless Pursuit': '죄인 포착', + 'Medicine Field': '광역 살포제', + 'Memento': '기억 구현', + 'Memory of the Pyre': '기억 폭탄', + 'Memory of the Storm': '기억 폭풍', + 'No Man\'s Land': '장치 기동', + 'Peal of Judgment': '단죄의 뇌검', + 'Pungent Aerosol': '폭발성 약', + 'Recollection': '기억 불러오기', + 'Relentless Torment': '연속 강공격', + 'Sensory Deprivation': '감각 교란제', + 'Serial Torture': '연속 형벌 집행', + 'Shackles of Fate': '속박의 쇠사슬', + 'Sterile Sphere': '원형 살포', + 'Turmoil': '동란', + 'Will Breaker': '파쇄탄', + }, + }, + ], +}; + +export default triggerSet; diff --git a/ui/raidboss/data/07-dt/dungeon/meso-terminal.txt b/ui/raidboss/data/07-dt/dungeon/meso-terminal.txt new file mode 100644 index 00000000..3c7e4aaf --- /dev/null +++ b/ui/raidboss/data/07-dt/dungeon/meso-terminal.txt @@ -0,0 +1,181 @@ +### THE MESO TERMINAL +# ZoneId: 1292 + +hideall "--Reset--" +hideall "--sync--" + +# .*is no longer sealed +0.0 "--Reset--" SystemLogMessage { id: "7DE" } window 0,100000 jump 0 + +#~~~~~~~~~~~~~~~~~~~~# +# Chirurgeon General # +#~~~~~~~~~~~~~~~~~~~~# +# -ii ACD6 + +# The Triage Module will be sealed off +1000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1496" } window 1000,1 +1009.1 "Medicine Field" Ability { id: "AB16", source: "Chirurgeon General" } +1016.5 "No Man's Land" Ability { id: "AB1C", source: "Chirurgeon General" } +1027.1 "Pungent Aerosol" Ability { id: "AB1F", source: "Chirurgeon General" } +1035.1 "Sterile Sphere" Ability { id: "AB1D", source: "Chirurgeon General" } +1041.7 "Biochemical Front" Ability { id: "AB1A", source: "Chirurgeon General" } +1043.8 "No Man's Land" Ability { id: "AB1C", source: "Chirurgeon General" } +1051.8 "Sensory Deprivation" Ability { id: "AB15", source: "Chirurgeon General" } +1063.2 "Pungent Aerosol" Ability { id: "AB1F", source: "Chirurgeon General" } +1069.9 "Concentrated Dose" Ability { id: "AB17", source: "Chirurgeon General" } + +1072.1 label "chirurgeonLoop" +1072.1 "No Man's Land" Ability { id: "AB1C", source: "Chirurgeon General" } +1077.1 "Sensory Deprivation" Ability { id: "AB15", source: "Chirurgeon General" } +1086.7 "Sterile Sphere/Pungent Aerosol" Ability { id: ["AB1D", "AB1E", "AB1F"], source: "Chirurgeon General" } +1094.7 "Pungent Aerosol/Sterile Sphere" Ability { id: ["AB1D", "AB1E", "AB1F"], source: "Chirurgeon General" } +1101.3 "Biochemical Front" Ability { id: "AB1A", source: "Chirurgeon General" } +1108.3 "Medicine Field" Ability { id: "AB16", source: "Chirurgeon General" } +1118.3 "Concentrated Dose" Ability { id: "AB17", source: "Chirurgeon General" } + +1123.6 "No Man's Land" Ability { id: "AB1C", source: "Chirurgeon General" } forcejump "chirurgeonLoop" + +# ALL ENCOUNTER ABILITIES +# AB15 Sensory Deprivation: Cloaks mechanic indicators +# AB16 Medicine Field: raidwide +# AB17 Concentrated Dose: tank buster +# AB1A Biochemical Front: 180-degree frontal cleave +# AB1C No Man's Land: sets up sphere and aerosol mechanics +# AB1D Sterile Sphere: small sphere explosion +# AB1E Sterile Sphere: large sphere explosion +# AB1F Pungent Aerosol: knockback +# ACD6 --sync--: auto-attack + +#~~~~~~~~~~~~~~# +# Executioners # +#~~~~~~~~~~~~~~# + +# -ii AA3A AA3C AA3D AA3F AA4A AA4E AA4F AA50 + +# The Executioner timeline is "best-effort" and will quickly +# desync depending on player or enemy deaths. + +# The Public Forum will be sealed off +2000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1497" } window 2000,1 +2003.2 "Lawless Pursuit" Ability { id: "AA39" } +2011.8 "Head-splitting Roar" Ability { id: "AA3B", source: "Hooded Headsman" } +2022.5 "Shackles of Fate" #Ability { id: "AA3E" } +2027.4 "Dismemberment (cast)" Ability { id: "AA42" } +2034.3 "Dismemberment 1" Ability { id: "AA43" } +2036.7 "Peal of Judgment (castbar)" Ability { id: "AA49" } +2037.4 "Dismemberment 2" Ability { id: "AA43" } +2042.8 "Peal of Judgment (active)" duration 7 #Ability { id: "AA4A" } +2045.8 "Chopping Block/Execution Wheel" Ability { id: ["AA4B", "AA4C"] } +2050.7 "Flaying Flail (castbar)" Ability { id: "AA47" } +2056.6 "Flaying Flail" Ability { id: "AA48" } +2058.5 "--sync--" StartsUsing { id: "AF38", source: "Pale Headsman" } # Will Breaker +2061.0 "Peal of Judgment" Ability { id: "AA49" } +2063.0 "Death Penalty" Ability { id: "AA44" } +2063.0 "--hellmaker adds--" +2063.9 "Peal of Judgment (active)" duration 7 #Ability { id: "AA4A" } +2065.0 "Will Breaker?" #Ability { id: "AF38", source: "Pale Headsman" } +2067.2 "Flaying Flail (castbar)" Ability { id: "AA47" } window 10,5 +2069.0 "Relentless Torment" Ability { id: "AA45" } +2071.8 "Flaying Flail" Ability { id: "AA48" } + +2081.8 label "executionerLoop" +2081.8 "Serial Torture" Ability { id: "AA4D" } +2088.6 "Dismemberment" Ability { id: "AA43" } +2090.6 "Flaying Flail" Ability { id: "AA48" } +2092.6 "Dismemberment" Ability { id: "AA43" } +2094.3 "Chopping Block/Execution Wheel" Ability { id: ["AA4B", "AA4C"] } +2104.8 "Head-splitting Roar" Ability { id: "AA3B", source: "Hooded Headsman" } +2114.5 "Peal of Judgment" #Ability { id: "AA49", source: "Pestilent Headsman" } +2120.6 "Chopping Block/Execution Wheel" Ability { id: ["AA4B", "AA4C"] } window 10,5 +2121.3 "--hellmaker adds--" +2122.2 "Peal of Judgment (active)" #Ability { id: "AA4A", source: "Bloody Headsman" } +2124.2 "Will Breaker?" #Ability { id: "AF38", source: "Pale Headsman" } +2131.2 "Death Penalty" Ability { id: "AA44", source: "Bloody Headsman" } window 30,10 + +2143.0 "Serial Torture" Ability { id: "AA4D" } forcejump "executionerLoop" + + + +# ALL ENCOUNTER ABILITIES +# AA39 Lawless Pursuit: Tether targeted player +# AA3A Head-splitting Roar: raidwide castbar +# AA3B Head-splitting Roar: raidwide (invisible add) +# AA3C --sync--: reposition in own circle +# AA3D Shackles of Fate: chain castbar +# AA3E Shackles of Fate: chain and draw player in +# AA3F Shackles of Fate: chain and draw player in when leaving the circle +# AA42 Dismemberment: blade line AoEs castbar +# AA43 Dismemberment blade line AoEs +# AA44 Death Penalty: dooms target +# AA45 Relentless Torment: tank buster headmarker +# AA46 Relentless Torment: tank buster +# AA47 Flaying Flail: triangle of circle AoEs castbar +# AA48 Flaying Flail: triangle of circle AoEs +# AA49 Peal of Judgment: lightning lines castbar +# AA4A Peal of Judgment: lightning lines +# AA4B Chopping Block: chariot AoE +# AA4C Execution Wheel: dynamo AoE +# AA4D Serial Torture +# AA4E --sync--: auto-attack +# AA4F --sync--: auto-attack +# AA50 --sync--: auto-attack + +#~~~~~~~~~~~~~~~~~~# +# Immortal Remains # +#~~~~~~~~~~~~~~~~~~# + +# -ii AB32 AB2E AB24 AB25 AB2B AB30 + +# Non-Volatile Memory will be sealed off +3000.0 "--sync--" SystemLogMessage { id: "7DC", param1: "1498" } window 3000,1 +3010.1 "Recollection" Ability { id: "AB31", source: "Immortal Remains" } +3019.9 "Memento" Ability { id: "AB21", source: "Immortal Remains" } +3039.3 "Electray 1" #Ability { id: "AB22", source: "Bygone Aerostat" } +3042.3 "Electray 2" #Ability { id: "AB22", source: "Bygone Aerostat" } +3047.1 "Memory of the Storm" Ability { id: "AB2D", source: "Immortal Remains" } +3057.2 "Memento" Ability { id: "AB21", source: "Immortal Remains" } +3073.3 "Bombardment" Ability { id: "AB23", source: "Immortal Remains" } +3075.3 "--sync--" Ability { id: ["AB26", "AB27"], source: "Immortal Remains" } # Turmoil +3079.7 "Turmoil" Ability { id: "AB28", source: "Immortal Remains" } +3088.5 "--sync--" Ability { id: "AB29", source: "Immortal Remains" } # Impression tethers +3093.5 "Impression" Ability { id: "AB2A", source: "Immortal Remains" } +3096.5 "Bombardment" Ability { id: "AB23", source: "Immortal Remains" } +3105.6 "Memory of the Pyre" Ability { id: "AB2F", source: "Immortal Remains" } +3114.7 "Memento" Ability { id: "AB21", source: "Immortal Remains" } +3126.8 "Keraunography" Ability { id: "B078", source: "Immortal Remains" } + +3141.8 label "remainsLoop" +3141.8 "Keraunography" Ability { id: "B078", source: "Immortal Remains" } +3148.0 "Electray 1" Ability { id: "AB22", source: "Bygone Aerostat" } +3151.0 "Electray 2" Ability { id: "AB22", source: "Bygone Aerostat" } +3153.8 "--sync--" Ability { id: ["AB26", "AB27"], source: "Immortal Remains" } # Turmoil +3158.2 "Turmoil" Ability { id: "AB28", source: "Immortal Remains" } +3171.9 "Keraunography" Ability { id: "B078", source: "Immortal Remains" } +3179.9 "--sync--" Ability { id: "AB29", source: "Immortal Remains" } # Impression tethers +3184.9 "Impression" Ability { id: "AB2A", source: "Immortal Remains" } +3187.9 "Bombardment" Ability { id: "AB23", source: "Immortal Remains" } +3197.0 "Memory of the Storm" Ability { id: "AB2D", source: "Immortal Remains" } +3206.1 "Memory of the Pyre" Ability { id: "AB2F", source: "Immortal Remains" } +3216.2 "Recollection" Ability { id: "AB31", source: "Immortal Remains" } + +3234.0 "Keraunography" Ability { id: "B078", source: "Immortal Remains" } forcejump "remainsLoop" + +# ALL ENCOUNTER ABILITIES +# AB21 Memento: Setup cast for Electray/Bombardment/Keraunography +# AB22 Electray: Cross laser AoEs +# AB23 Bombardment: Small red circle AoE +# AB24 Bombardment: Large red circle AoE +# AB25 Keraunography: Castbar for purple lasers +# AB26 Turmoil: Half-arena cleave, left side castbar +# AB27 Turmoil: Half-arena cleave, right side castbar +# AB28 Turmoil: Half-arena cleave, both sides +# AB29 Impression: Blue knockback circle display +# AB2A Impression: Blue knockback circle failure (standing inside) +# AB2B Impression: Blue knockback circle resolve +# AB2D Memory of the Storm: Stack laser self-cast +# AB2E Memory of the Storm: Stack laser (instant) +# AB2F Memory of the Pyre: Tank buster self-cast +# AB30 Memory of the Pyre: Tank buster +# AB31 Recollection: Raidwide +# AB32 --sync--: Auto-attack +# B078 Keraunography: Large purple lasers diff --git a/ui/raidboss/data/07-dt/dungeon/the_underkeep.ts b/ui/raidboss/data/07-dt/dungeon/the_underkeep.ts index 8fc551f3..11227085 100644 --- a/ui/raidboss/data/07-dt/dungeon/the_underkeep.ts +++ b/ui/raidboss/data/07-dt/dungeon/the_underkeep.ts @@ -98,6 +98,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Watch boss for dash', + de: 'Boss anschauen für Ansturm', fr: 'Regardez la position du boss pour la ruée', cn: '观察 BOSS 冲锋', ko: '보스 돌진 조심!', @@ -159,6 +160,7 @@ const triggerSet: TriggerSet = { outputStrings: { spread: { en: 'Spread in ${quad} quadrant', + de: 'Im ${quad} Quadranten verteilen', fr: 'Écartez-vous dans le quandrant ${quad}', cn: '在 ${quad} 象限分散', ko: '${quad}쪽에서 흩어져요', @@ -226,6 +228,7 @@ const triggerSet: TriggerSet = { ...Directions.outputStrings8Dir, cleave: { en: '${dir} half safe', + de: '${dir} Hälfte sicher', fr: '${dir} moitié sûre', cn: '${dir} 半安全', ko: '${dir}쪽 절반 안전', @@ -252,6 +255,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Dodge wall turrets', + de: 'Wand-Geschützturm ausweichen', fr: 'Esquivez les tourelles murales', cn: '躲避墙壁炮台', ko: '벽 레이저 피해요', @@ -266,6 +270,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Spread (all cones expand!)', + de: 'Verteilen (Alle Kegel langziehen!)', fr: 'Écartez-vous (les cônes s\'agrandissent)', cn: '分散 (所有扇形扩大!)', ko: '흩어져요! (부채꼴 장판 커져요)', @@ -280,6 +285,7 @@ const triggerSet: TriggerSet = { outputStrings: { text: { en: 'Get behind + Spread', + de: 'geh Hinter + Verteilen', fr: 'Allez derrière + Dispersion', cn: '后 + 分散', ko: '보스 뒤로 + 흩어져요', @@ -382,18 +388,21 @@ const triggerSet: TriggerSet = { unknown: Outputs.unknown, dodge: { en: 'Inner ${first} => Inner ${second} ${move}', + de: 'Innen ${first} => Innen ${second} ${move}', fr: 'Intérieur ${first} => Intérieur ${second} ${move}', cn: '内 ${first} => 内 ${second} ${move}', ko: '안쪽 ${first} => 안쪽 ${second} ${move}', }, moveAfterLaser: { en: '(after wall laser)', + de: '(nach dem Wand-Laser)', fr: '(après les lasers)', cn: '(然后墙壁激光)', ko: '(벽 레이저 쏜 다음)', }, moveAfterOrb: { en: '(after orb explosion)', + de: '(nach Orb-Explosion)', fr: '(après les explosions des orbes)', cn: '(然后球爆炸)', ko: '(구슬 터지고 난 다음)', 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 3fa9c0ea..cdfea719 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 @@ -39,6 +39,7 @@ const triggerSet: TriggerSet = { zoneId: ZoneId.TheOccultCrescentSouthHorn, comments: { en: 'Occult Crescent South Horn critical encounter triggers/timeline.', + de: 'Kreszentia Südexpedition kritische Begegnungen Triggers/Timeline.', cn: '蜃景幻界新月岛 南征之章 紧急遭遇战 触发器/时间轴。', }, timelineFile: 'occult_crescent_south_horn.txt', @@ -192,6 +193,182 @@ const triggerSet: TriggerSet = { 'Vertical Crosshatch/Horizontal Crosshatch': 'Vertical/Horizontal Crosshatch', }, }, + { + 'locale': 'de', + 'replaceSync': { + 'Ball of Fire': 'Feuerball', + 'Black Star': 'Schwarz(?:e|er|es|en) Stern', + 'Clawmarks': 'Nagelmal', + 'Cloister Demon': 'Klosterdämon', + 'Crescent Berserker': 'kreszent(?:e|er|es|en) Berserker', + 'Crystal Dragon': 'Kristalldrache', + 'Death Claw': 'Todesklaue', + 'Draconic Double': 'Kristalldrachenphantom', + 'Hinkypunk': 'Irrwisch', + 'Lion Rampant': 'ungezügelt(?:e|er|es|en) Löwe', + 'Neo Garula': 'Neo Garula', + 'Nymian Petalodus': 'nymeisch(?:e|er|es|en) Petalodus', + 'Phantom Claw': 'Illusions-Todesklaue', + 'Repaired Lion': 'restauriert(?:e|er|es|en) Löwe', + }, + 'replaceText': { + '\\(1\\)': '(1)', + '\\(2\\)': '(2)', + '\\(3\\)': '(3)', + '\\(Lightning\\)': '(Blitz)', + '\\(Wind\\)': '(Wind)', + '\\(jump\\)': '(Sprung)', + 'Bedrock Uplift': 'Bodenhebung', + 'Blazing Flare': 'Loderndes Flare', + 'Boil Over': 'Rasender Schlag', + 'Channeled Rage': 'Tobsucht', + 'Clawing Shadow': 'Schattenklauen', + 'Clawmarks': 'Nagelmal', + 'Crystal Call': 'Kristallisierung', + 'Crystal Mirror': 'Kristallene Transposition', + 'Crystallized Energy': 'Kristallene Woge', + 'Dirty Nails': 'Dreckige Klauen', + 'Explosion': 'Explosion', + 'Fearsome Facet': 'Phantomkristalle', + 'Gigaflare': 'Gigaflare', + 'Great Ball of Fire': 'Feuerball', + 'Heated Outburst': 'Jähzorn', + 'Heightened Rage': 'Wilder Furor', + 'Hopping Mad': 'Tobender Stoß', + 'Horizontal Crosshatch': 'Horizontale Doppelnägel', + 'Karmic Drain': 'Karmischer Entzug', + 'Lethal Nails': 'Todesnägel', + 'Made Magic': 'Magiefeuer', + 'Manifold Marks': 'Multimal', + 'Primal Roar': 'Lautes Gebrüll', + 'Prismatic Wing': 'Kristallene Schwingen', + 'Raking Scratch': 'Harkenkratzer', + 'Scathing Sweep': 'Seitenhieb', + 'Seal Asunder': 'Siegelbruch', + 'Skulking Orders': 'Strafbefehl', + 'Sunderseal Roar': 'Berstendes Gebrüll', + 'The Grip of Poison': 'Tückische Resonanz', + 'Threefold Marks': 'Tripelmal', + 'Tidal Breath': 'Hauch der Gezeiten', + 'Vertical Crosshatch': 'Vertikale Doppelnägel', + 'Void Thunder III': 'Nichts-Blitzga', + 'White-hot Rage': 'Jähzorniger Schub', + }, + }, + { + 'locale': 'fr', + 'missingTranslations': true, + 'replaceSync': { + 'Ball of Fire': 'orbe de feu', + 'Black Star': 'Étoile noire', + 'Clawmarks': 'griffure', + 'Cloister Demon': 'démon du Cloître', + 'Crescent Berserker': 'berserker de Lunule', + 'Crystal Dragon': 'dragon cristallin', + 'Death Claw': 'Griffe de mort', + 'Draconic Double': 'double de dragon cristallin', + 'Hinkypunk': 'follet folâtre', + 'Lion Rampant': 'lion rampant', + 'Neo Garula': 'néo-garula', + 'Nymian Petalodus': 'petalodus de Nym', + 'Phantom Claw': 'mirage de Griffe de mort', + 'Repaired Lion': 'lion réparé', + }, + 'replaceText': { + 'Bedrock Uplift': 'Surrection', + 'Blazing Flare': 'Brasier ardent', + 'Boil Over': 'Poussée de colère', + 'Channeled Rage': 'Accès de fureur', + 'Clawing Shadow': 'Serres brumeuses', + 'Clawmarks': 'griffure', + 'Crystal Call': 'Cristallisation', + 'Crystal Mirror': 'Transfert cristallin', + 'Crystallized Energy': 'Onde cristalline', + 'Dirty Nails': 'Serres putrides', + 'Explosion': 'Explosion', + 'Fearsome Facet': 'Cristaux spectraux', + 'Gigaflare': 'GigaBrasier', + 'Great Ball of Fire': 'Orbes de feu', + 'Heated Outburst': 'Courroux ardent', + 'Heightened Rage': 'Déchaînement de rage', + 'Hopping Mad': 'Impulsion frénétique', + 'Horizontal Crosshatch': 'Intersection horizontale', + 'Karmic Drain': 'Érosion d\'existence', + 'Lethal Nails': 'Griffes mortelles', + 'Made Magic': 'Déferlante magique', + 'Manifold Marks': 'Marques multiples', + 'Primal Roar': 'Rugissement furieux', + 'Prismatic Wing': 'Aile cristalline', + 'Raking Scratch': 'Griffes ratisseuses', + 'Scathing Sweep': 'Fauche latérale', + 'Seal Asunder': 'Descellement destructeur', + 'Skulking Orders': 'Ordre d\'élimination', + 'Sunderseal Roar': 'Rugissement libérateur', + 'The Grip of Poison': 'Résonnance de la malfaisance', + 'Threefold Marks': 'Triple marque', + 'Tidal Breath': 'Souffle supratidal', + 'Vertical Crosshatch': 'Intersection verticale', + 'Void Thunder III': 'Méga Foudre du néant', + 'White-hot Rage': 'Rage incandescente', + }, + }, + { + 'locale': 'ja', + 'missingTranslations': true, + 'replaceSync': { + 'Ball of Fire': '火球', + 'Black Star': 'ブラックスター', + 'Clawmarks': 'ネイルマーク', + 'Cloister Demon': 'クロイスターデーモン', + 'Crescent Berserker': 'クレセント・バーサーカー', + 'Crystal Dragon': '水晶竜', + 'Death Claw': 'デスクロー', + 'Draconic Double': '水晶竜の幻影', + 'Hinkypunk': 'ヒンキーパンク', + 'Lion Rampant': 'ランパントライオン', + 'Neo Garula': 'ネオガルラ', + 'Nymian Petalodus': 'ニーム・ペタロドゥス', + 'Phantom Claw': 'ミラージュ・デスクロー', + 'Repaired Lion': 'リペアドライオン', + }, + 'replaceText': { + 'Bedrock Uplift': '地盤隆起', + 'Blazing Flare': 'ブレイジングフレア', + 'Boil Over': '怒発', + 'Channeled Rage': '激怒', + 'Clawing Shadow': 'ヘイズクロー', + 'Clawmarks': 'ネイルマーク', + 'Crystal Call': '晶石生成', + 'Crystal Mirror': '晶石転移', + 'Crystallized Energy': '水晶波動', + 'Dirty Nails': 'ダーティクロー', + 'Explosion': '爆発', + 'Fearsome Facet': '幻影晶石', + 'Gigaflare': 'ギガフレア', + 'Great Ball of Fire': '火球', + 'Heated Outburst': '気炎', + 'Heightened Rage': '大激怒', + 'Hopping Mad': '震撃怒涛', + 'Horizontal Crosshatch': 'ホリゾンタル・ダブルネイル', + 'Karmic Drain': 'ライフエロージョン', + 'Lethal Nails': 'デスネイル', + 'Made Magic': '魔力放出', + 'Manifold Marks': 'マルチプルマーク', + 'Primal Roar': '大咆哮', + 'Prismatic Wing': '水晶の翼', + 'Raking Scratch': 'ネイルストリーク', + 'Scathing Sweep': '横薙ぎ', + 'Seal Asunder': '封印破壊', + 'Skulking Orders': 'パニッシングオーダー', + 'Sunderseal Roar': '壊封の咆哮', + 'The Grip of Poison': '邪気の共振', + 'Threefold Marks': 'トリプルマーク', + 'Tidal Breath': 'タイダルブレス', + 'Vertical Crosshatch': 'バーチカル・ダブルネイル', + 'Void Thunder III': 'ヴォイド・サンダガ', + 'White-hot Rage': '気炎怒涛', + }, + }, { 'locale': 'cn', 'replaceSync': { diff --git a/ui/raidboss/data/07-dt/raid/r5s.ts b/ui/raidboss/data/07-dt/raid/r5s.ts index dd26057a..0b416375 100644 --- a/ui/raidboss/data/07-dt/raid/r5s.ts +++ b/ui/raidboss/data/07-dt/raid/r5s.ts @@ -440,12 +440,12 @@ const triggerSet: TriggerSet = { }, { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Dancing Green': 'Springhis Khan', 'Frogtourage': 'Schenkelschwinger', }, 'replaceText': { + 'Debuffs': 'Debuffs', '\\(Cleave\\)': '(Cleave)', '\\(Echo\\)': '(Echo)', '\\(In\\+Protean\\+Echo\\)': '(Rein+Himmelsrichtungen+Echo)', diff --git a/ui/raidboss/data/07-dt/raid/r8n.ts b/ui/raidboss/data/07-dt/raid/r8n.ts index 7579e248..313f1299 100644 --- a/ui/raidboss/data/07-dt/raid/r8n.ts +++ b/ui/raidboss/data/07-dt/raid/r8n.ts @@ -284,6 +284,7 @@ const triggerSet: TriggerSet = { outputStrings: { behindClones: { en: 'Behind Clones', + de: 'Hinter den Klonen', fr: 'Derrière les clones', cn: '去分身后', ko: '분신 뒤로', diff --git a/ui/raidboss/data/07-dt/raid/r8s.ts b/ui/raidboss/data/07-dt/raid/r8s.ts index 0289ef54..e322e613 100644 --- a/ui/raidboss/data/07-dt/raid/r8s.ts +++ b/ui/raidboss/data/07-dt/raid/r8s.ts @@ -863,7 +863,6 @@ const triggerSet: TriggerSet = { }, { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Gleaming Fang': 'Lichtreißer', 'Howling Blade': 'Heulende Klinge', @@ -876,6 +875,11 @@ const triggerSet: TriggerSet = { '--adds-targetable--': '--Adds-anvisierbar--', '--shadow ': '--Schatten ', '--tank/line aoes--': '--Tank/Linien AoEs--', + '\\(circles\\)': '(Kreise)', + '\\(cones\\)': '(Kegel)', + '\\(enrage\\)': '(Finalangriff)', + '\\(Platform\\)': '(Plattform)', + '\\(Towers\\)': '(Türme)', 'Aero III': 'Windga', 'Aerotemporal Blast': 'Temporärer Wind', 'Alpha Stone': 'Erde des Lichtwolfs', @@ -936,7 +940,6 @@ const triggerSet: TriggerSet = { }, { 'locale': 'fr', - 'missingTranslations': true, 'replaceSync': { 'Gleaming Fang': 'croc de lumière', 'Howling Blade': 'Howling Blade', @@ -946,6 +949,14 @@ const triggerSet: TriggerSet = { 'Wolf of Wind': 'loup du vent', }, 'replaceText': { + '--adds-targetable--': '--Adds ciblables--', + '--shadow ': '--Ombre ', + '--tank/line aoes--': '--AOE Tank/En ligne--', + '\\(circles\\)': '(Cercles)', + '\\(cones\\)': '(Cônes)', + '\\(enrage\\)': '(Enrage)', + '\\(Platform\\)': '(Plateforme)', + '\\(Towers\\)': '(Tours)', 'Aero III': 'Méga Vent', 'Aerotemporal Blast': 'Assaut tempétueux à retardement', 'Alpha Stone': 'Terre du loup radieux', diff --git a/ui/raidboss/data/07-dt/trial/necron-ex.ts b/ui/raidboss/data/07-dt/trial/necron-ex.ts index 61a04756..aa6d99f2 100644 --- a/ui/raidboss/data/07-dt/trial/necron-ex.ts +++ b/ui/raidboss/data/07-dt/trial/necron-ex.ts @@ -593,7 +593,6 @@ const triggerSet: TriggerSet = { }, { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Azure Aether': 'azur(?:e|er|es|en) Äther', 'Beckoning Hands': 'lockend(?:e|er|es|en) Hand', @@ -759,6 +758,50 @@ const triggerSet: TriggerSet = { 'Twofold Blight': '青之二重波', }, }, + { + 'locale': 'ko', + 'replaceSync': { + 'Azure Aether': '푸른 영혼 덩어리', + 'Beckoning Hands': '죽음으로 이끄는 손', + 'Icy Hands': '죽음을 부르는 손', + 'Necron': '영원한 어둠', + }, + 'replaceText': { + '\\(cast\\)': '(시전)', + '\\(damage\\)': '(피해)', + '\\(intercards\\)': '(대각)', + '\\(lines?\\)': '(직선)', + '\\(puddles?\\)': '(장판)', + '\\(spread\\)': '(산개)', + '\\(tower\\)': '(탑)', + 'Aetherblight': '푸른 파동', + 'Blue Shockwave': '푸른 충격', + 'Choking Grasp': '짓누르기', + 'Circle of Lives': '푸른 고리 파동', + 'Cold Grip': '검은 죽음의 팔', + 'Crop Rotation': '영혼 순환', + 'Darkness of Eternity': '영원한 어둠', + 'Existential Dread': '어두운 기류', + 'Fear of Death': '죽음에 대한 공포', + 'Fourfold Blight': '푸른 사중 파동', + 'Grand Cross': '그랜드크로스', + 'Inevitability': '죽음의 범람', + 'Mass Macabre': '집단 공황', + 'Memento Mori': '메멘토 모리', + 'Muted Struggle': '찍어누르기', + 'Neutron Ring': '중성자 고리', + 'Relentless Reaping': '연속 푸른 영혼', + 'Shock(?!wave)': '방전', + '(? = { repeatedAOE: { en: 'Continuous light AoE', de: 'Durchgehende Licht AoE', + fr: 'AoE légère continue', ja: '連続光属性AoE', cn: '连续小AoE', ko: '연속 격자 장판', @@ -139,6 +140,7 @@ const triggerSet: TriggerSet = { regaliaYou: { en: 'Laser tether on YOU', de: 'Laserverbindung auf DIR', + fr: 'Lien laser sur VOUS', ja: 'レーザー線処理', cn: '激光连线点名', ko: '내게 유도 레이저 줄', @@ -170,6 +172,7 @@ const triggerSet: TriggerSet = { floating: { en: 'Gravitation -- Levitating', de: 'Gravitation -- Schweben', + fr: 'Gravité -- Lévitation', ja: '重力装置 -- 浮上', cn: '重力 -- 上浮', ko: '곧 뜰꺼예요!', @@ -190,6 +193,7 @@ const triggerSet: TriggerSet = { falling: { en: 'Gravitation -- Falling', de: 'Gravitation -- Fallen', + fr: 'Gravité -- Chute', ja: '重力装置 -- 降下', cn: '重力 -- 下降', ko: '곧 내려가요!', @@ -210,6 +214,7 @@ const triggerSet: TriggerSet = { floating: { en: 'Gravitation -- Levitating', de: 'Gravitation -- Schweben', + fr: 'Gravité -- Lévitation', ja: '重力装置 -- 浮上', cn: '重力 -- 上浮', ko: '곧 뜰꺼예요!', @@ -234,6 +239,7 @@ const triggerSet: TriggerSet = { fallCastle: { en: 'In front of ground windows', de: 'Sei vor dem Fenster am Boden', + fr: 'Devant les fenêtres à l\'avant', ja: '下層の窓前へ', cn: '正对下层空洞', ko: '아랫쪽 구멍으로', @@ -241,6 +247,7 @@ const triggerSet: TriggerSet = { floatCastle: { en: 'In front of middle windows', de: 'Sei vor dem Fenster in der Mitte', + fr: 'Devant les fenêtres du milieu', ja: '中層の窓前へ', cn: '正对上层空洞', ko: '윗쪽 구멍으로', @@ -256,6 +263,7 @@ const triggerSet: TriggerSet = { getInDonut: { en: 'Get in robot circle', de: 'Geh in den Roboterkreis', + fr: 'Aller dans le cercle du robot', ja: 'ドローンサークルの中に', cn: '小月环', ko: '로봇 동글이 안으로', @@ -287,6 +295,7 @@ const triggerSet: TriggerSet = { gazeLater: { en: 'Gaze on you soon', de: 'Bald: Blick auf dir', + fr: 'Regard sur vous bientôt', ja: 'まもなく視線攻撃', cn: '石化眼点名', ko: '내게 곧 눈깔', @@ -314,6 +323,7 @@ const triggerSet: TriggerSet = { shriekYou: { en: 'Gaze -- look away from ${otherTarget}', de: 'Blick -- schau weg von ${otherTarget}', + fr: 'Regard -- Ne regardez pas ${otherTarget}', ja: '視線攻撃 -- ${otherTarget} を見ない', cn: '石化眼 -- 背对 ${otherTarget}', ko: '내게 눈깔! 자리 비켜줘욧! (${otherTarget})', @@ -321,6 +331,7 @@ const triggerSet: TriggerSet = { shriekOthers: { en: 'Look away from ${comboTargets}', de: 'Schau weg von ${comboTargets}', + fr: 'Ne regardez pas ${comboTargets}', ja: '${comboTargets} を見ない', cn: '背对 ${comboTargets}', ko: '보면 안되요: ${comboTargets}', @@ -337,6 +348,7 @@ const triggerSet: TriggerSet = { bombLater: { en: 'Acceleration Bomb soon', de: 'Bald: Beschleunigungsbombe', + fr: 'Bombe d\'accélération bientôt', ja: 'まもなく加速度爆弾', cn: '稍后 加速度炸弹', ko: '곧 가속 폭탄', @@ -367,6 +379,7 @@ const triggerSet: TriggerSet = { flareMarker: { en: 'Flare on YOU', de: 'Flare auf DIR', + fr: 'Brasier sur VOUS', ja: 'フレア処理', cn: '核爆点名', ko: '내게 플레어', @@ -396,6 +409,7 @@ const triggerSet: TriggerSet = { fiveAOE: { en: '5x AoEs', de: '5x AoEs', + fr: '5x AoEs', ja: '5連続AoE', cn: 'AoE (5次)', ko: '5x 전체공격', @@ -406,7 +420,6 @@ const triggerSet: TriggerSet = { timelineReplace: [ { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Queen Eternal': 'Ewig(?:e|er|es|en) Königin', }, diff --git a/ui/raidboss/data/07-dt/trial/seiryu-un.ts b/ui/raidboss/data/07-dt/trial/seiryu-un.ts index 275ee635..c5f9ffbd 100644 --- a/ui/raidboss/data/07-dt/trial/seiryu-un.ts +++ b/ui/raidboss/data/07-dt/trial/seiryu-un.ts @@ -52,6 +52,7 @@ const triggerSet: TriggerSet = { outputStrings: { grabTether: { en: 'Grab Tank Tether, Point Cleave Away', + de: 'Nimm Tank-Verbindung, zeige den Cleave weg', fr: 'Prenez le lien Tank, visez l\'extérieur', cn: '接走坦克连线,引导扇形远离人群', ko: '줄 채고, 바깥으로 유도', @@ -245,24 +246,28 @@ const triggerSet: TriggerSet = { dirW: Outputs.west, goToSnakes: { en: 'Go ${dir} Toward Snakes', + de: 'Geh Richtung ${dir} zu den Schlangen', fr: 'Allez ${dir} vers les serpents', cn: '向 ${dir} 蛇走', ko: '뱀 ${dir}으로 가요', }, goToUnknownSnakes: { en: 'Go Toward Snakes', + de: 'Geh zu den Schlangen', fr: 'Allez vers les serpents', cn: '向蛇走', ko: '뱀 쪽으로 가요', }, outOfMiddleTowardSnakes: { en: 'Out Of Middle, Knockback To ${dir}', + de: 'Raus aus der Mitte, Rückstoß nach ${dir}', fr: 'Loin du centre, Poussée vers ${dir}', cn: '离开中间,击退到 ${dir}', ko: '가운데는 위험! ${dir}으로 넉백', }, outOfMiddleUnknownSnakes: { en: 'Out Of Middle, Knockback Toward Snakes', + de: 'Raus aus der Mitte, Rückstoß zu den Schlangen', fr: 'Loin du centre, Poussée vers les serpents', cn: '离开中间,向蛇击退', ko: '가운데는 위험! 뱀 쪽으로 넉백', @@ -360,11 +365,10 @@ const triggerSet: TriggerSet = { timelineReplace: [ { 'locale': 'de', - 'missingTranslations': true, 'replaceSync': { 'Aka-no-shiki': 'Aka no Shiki', 'Ao-no-shiki': 'Ao no Shiki', - 'Blue Orochi': 'blau[a] Orochi', + 'Blue Orochi': 'blau(?:e|er|es|en) Orochi', 'Iwa-no-shiki': 'Iwa no Shiki', 'Numa-no-shiki': 'Numa no Shiki', 'Seiryu': 'Seiryu', @@ -372,6 +376,8 @@ const triggerSet: TriggerSet = { 'Yama-no-shiki': 'Yama no Shiki', }, 'replaceText': { + '--large add spawns--': '--großes Add erscheint--', + '--small adds spawn--': '--kleine Adds erscheinen--', '100-tonze Swing': '100-Tonzen-Schwung', 'Blazing Aramitama': 'Flammende Aramitama', 'Blue Bolt': 'Blauer Blitz', @@ -392,7 +398,7 @@ const triggerSet: TriggerSet = { 'Serpent Descending': 'Niedergang der Schlange', 'Serpent\'s Fang': 'Schlangengiftzahn', 'Strength of Spirit': 'Stärke des Geistes', - 'Summon Shiki': 'Shiki-Beschwörung ', + 'Summon Shiki': 'Shiki-Beschwörung', 'Yama-kagura': 'Yamakagura', }, }, diff --git a/util/coverage/coverage.ts b/util/coverage/coverage.ts index 7c3fbc20..d77b777d 100644 --- a/util/coverage/coverage.ts +++ b/util/coverage/coverage.ts @@ -329,6 +329,18 @@ const contentTypeToLabel: { ko: '딥 던전', }, }, + [ContentType.DeepDungeonExtras]: { + full: { + en: 'Deep Dungeon Extra Content', + cn: '深层迷宫额外内容', + ko: '딥 던전 확장 콘텐츠', + }, + short: { + en: 'DD+', + cn: '深宫+', + ko: '딥 던전+', + }, + }, [ContentType.Pvp]: { full: { en: 'PvP', @@ -1096,6 +1108,7 @@ const buildZoneTable = (container: HTMLElement, lang: Lang, coverage: Coverage) ContentType.DisciplesOfTheLand, ContentType.TreasureHunt, ContentType.DeepDungeons, + ContentType.DeepDungeonExtras, ContentType.Pvp, undefined, // catchall for the rest diff --git a/util/logtools/encounter_tools.ts b/util/logtools/encounter_tools.ts index 4551cef1..5beaabec 100644 --- a/util/logtools/encounter_tools.ts +++ b/util/logtools/encounter_tools.ts @@ -167,6 +167,7 @@ export class EncounterFinder { // Therefore, we can't require them. const keepTypes: Array = [ ContentType.ChaoticAllianceRaid, + ContentType.DeepDungeonExtras, ContentType.DeepDungeons, ContentType.Dungeons, ContentType.Eureka,