Merge remote-tracking branch 'Akurosia/akus_schabernack4'
This commit is contained in:
commit
858a5e8f49
@ -98,6 +98,40 @@ const MITIGATION_ABILITIES = [
|
||||
'Tactician' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001951, 'extraAbilityGameID' => 16889],
|
||||
'Shield Samba' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001826, 'extraAbilityGameID' => 16012],
|
||||
'Magick Barrier' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1002707, 'extraAbilityGameID' => 25857],
|
||||
// ── Personal / targeted mitigation ─────────────────────────────────────
|
||||
'Rampart' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 7531],
|
||||
// PLD
|
||||
'Hallowed Ground' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 30],
|
||||
'Sentinel' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 17],
|
||||
'Bulwark' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 22],
|
||||
'Holy Sheltron' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 25746],
|
||||
'Intervention' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 7382],
|
||||
// WAR
|
||||
'Holmgang' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 43],
|
||||
'Vengeance' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 44],
|
||||
'Damnation' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 36923],
|
||||
'Thrill of Battle' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 40],
|
||||
'Raw Intuition' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 3551],
|
||||
// DRK
|
||||
'Living Dead' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 3638],
|
||||
'Shadow Wall' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 3636],
|
||||
'Shadowed Vigil' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 36927],
|
||||
'Dark Mind' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 3634],
|
||||
'The Blackest Night' => ['dr' => 0, 'buffType' => 'shield', 'extraAbilityGameID' => 7393],
|
||||
'Oblation' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 25754],
|
||||
// GNB
|
||||
'Superbolide' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 16152],
|
||||
'Nebula' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 16148],
|
||||
'Great Nebula' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 36935],
|
||||
'Camouflage' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 16140],
|
||||
'Heart of Stone' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 16161],
|
||||
'Heart of Corundum' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 25758],
|
||||
// DPS
|
||||
'Riddle of Earth' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 7394],
|
||||
'Shade Shift' => ['dr' => 0, 'buffType' => 'shield', 'extraAbilityGameID' => 2241],
|
||||
'Third Eye' => ['dr' => 0, 'buffType' => 'buff', 'extraAbilityGameID' => 7498],
|
||||
'Arcane Crest' => ['dr' => 0, 'buffType' => 'shield', 'extraAbilityGameID' => 24404],
|
||||
'Manaward' => ['dr' => 0, 'buffType' => 'shield', 'extraAbilityGameID' => 157],
|
||||
// ── Shields ─────────────────────────────────────────────────────────────
|
||||
// PLD
|
||||
'Divine Veil' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001362, 'extraAbilityGameID' => 3540],
|
||||
|
||||
@ -1,150 +1,717 @@
|
||||
{
|
||||
"17": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Sentinel",
|
||||
"de": "Sentinel",
|
||||
"fr": "Sentinelle",
|
||||
"jp": "センチネル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000151_hr1.png"
|
||||
},
|
||||
"22": {
|
||||
"cast": 0,
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Bulwark",
|
||||
"de": "Bollwerk",
|
||||
"fr": "Forteresse",
|
||||
"jp": "ブルワーク"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000167_hr1.png"
|
||||
},
|
||||
"30": {
|
||||
"cast": 0,
|
||||
"recast": 4200,
|
||||
"names": {
|
||||
"en": "Hallowed Ground",
|
||||
"de": "Heiliger Boden",
|
||||
"fr": "Invincible",
|
||||
"jp": "インビンシブル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002502_hr1.png"
|
||||
},
|
||||
"40": {
|
||||
"cast": 0,
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Thrill of Battle",
|
||||
"de": "Kampfrausch",
|
||||
"fr": "Frisson de la bataille",
|
||||
"jp": "スリル・オブ・バトル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000263_hr1.png"
|
||||
},
|
||||
"43": {
|
||||
"cast": 0,
|
||||
"recast": 2400,
|
||||
"names": {
|
||||
"en": "Holmgang",
|
||||
"de": "Holmgang",
|
||||
"fr": "Holmgang",
|
||||
"jp": "ホルムギャング"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000266_hr1.png"
|
||||
},
|
||||
"44": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Vengeance",
|
||||
"de": "Rachsucht",
|
||||
"fr": "Représailles",
|
||||
"jp": "ヴェンジェンス"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000267_hr1.png"
|
||||
},
|
||||
"157": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Manaward",
|
||||
"de": "Mana-Schild",
|
||||
"fr": "Barrière de mana",
|
||||
"jp": "マバリア"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000463_hr1.png"
|
||||
},
|
||||
"185": {
|
||||
"cast": 20,
|
||||
"recast": 25
|
||||
"recast": 25,
|
||||
"names": {
|
||||
"en": "Adloquium",
|
||||
"de": "Adloquium",
|
||||
"fr": "Traité du réconfort",
|
||||
"jp": "鼓舞激励の策"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002801_hr1.png"
|
||||
},
|
||||
"188": {
|
||||
"cast": 0,
|
||||
"recast": 300
|
||||
"recast": 300,
|
||||
"names": {
|
||||
"en": "Sacred Soil",
|
||||
"de": "Geweihte Erde",
|
||||
"fr": "Dogme de survie",
|
||||
"jp": "野戦治療の陣"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002804_hr1.png"
|
||||
},
|
||||
"2241": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Shade Shift",
|
||||
"de": "Superkniff",
|
||||
"fr": "Décalage d'ombre",
|
||||
"jp": "残影"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000607_hr1.png"
|
||||
},
|
||||
"3540": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Divine Veil",
|
||||
"de": "Heiliger Quell",
|
||||
"fr": "Voile divin",
|
||||
"jp": "ディヴァインヴェール"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002508_hr1.png"
|
||||
},
|
||||
"3551": {
|
||||
"cast": 0,
|
||||
"recast": 250,
|
||||
"names": {
|
||||
"en": "Raw Intuition",
|
||||
"de": "Urinstinkt",
|
||||
"fr": "Intuition pure",
|
||||
"jp": "原初の直感"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002559_hr1.png"
|
||||
},
|
||||
"3613": {
|
||||
"cast": 0,
|
||||
"recast": 600
|
||||
"recast": 600,
|
||||
"names": {
|
||||
"en": "Collective Unconscious",
|
||||
"de": "Numinosum",
|
||||
"fr": "Inconscient collectif",
|
||||
"jp": "運命の輪"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003140_hr1.png"
|
||||
},
|
||||
"3634": {
|
||||
"cast": 0,
|
||||
"recast": 600,
|
||||
"names": {
|
||||
"en": "Dark Mind",
|
||||
"de": "Dunkler Geist",
|
||||
"fr": "Esprit ténébreux",
|
||||
"jp": "ダークマインド"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003076_hr1.png"
|
||||
},
|
||||
"3636": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Shadow Wall",
|
||||
"de": "Schattenwand",
|
||||
"fr": "Mur d'ombre",
|
||||
"jp": "シャドウウォール"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003075_hr1.png"
|
||||
},
|
||||
"3638": {
|
||||
"cast": 0,
|
||||
"recast": 3000,
|
||||
"names": {
|
||||
"en": "Living Dead",
|
||||
"de": "Totenerweckung",
|
||||
"fr": "Mort-vivant",
|
||||
"jp": "リビングデッド"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003077_hr1.png"
|
||||
},
|
||||
"7382": {
|
||||
"cast": 0,
|
||||
"recast": 100,
|
||||
"names": {
|
||||
"en": "Intervention",
|
||||
"de": "Intervention",
|
||||
"fr": "Intervention",
|
||||
"jp": "インターベンション"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002512_hr1.png"
|
||||
},
|
||||
"7385": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Passage of Arms",
|
||||
"de": "Waffengang",
|
||||
"fr": "Passe d'armes",
|
||||
"jp": "パッセージ・オブ・アームズ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002515_hr1.png"
|
||||
},
|
||||
"7388": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Shake It Off",
|
||||
"de": "Abschütteln",
|
||||
"fr": "Débarrassage",
|
||||
"jp": "シェイクオフ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002563_hr1.png"
|
||||
},
|
||||
"7393": {
|
||||
"cast": 0,
|
||||
"recast": 150,
|
||||
"names": {
|
||||
"en": "The Blackest Night",
|
||||
"de": "Schwärzeste Nacht",
|
||||
"fr": "Nuit noirissime",
|
||||
"jp": "ブラックナイト"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003081_hr1.png"
|
||||
},
|
||||
"7394": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Riddle of Earth",
|
||||
"de": "Steinernes Enigma",
|
||||
"fr": "Énigme de la terre",
|
||||
"jp": "金剛の極意"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002537_hr1.png"
|
||||
},
|
||||
"7405": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Troubadour",
|
||||
"de": "Troubadour",
|
||||
"fr": "Troubadour",
|
||||
"jp": "トルバドゥール"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002612_hr1.png"
|
||||
},
|
||||
"7432": {
|
||||
"cast": 0,
|
||||
"recast": 300
|
||||
"recast": 300,
|
||||
"names": {
|
||||
"en": "Divine Benison",
|
||||
"de": "Göttlicher Segen",
|
||||
"fr": "Faveur divine",
|
||||
"jp": "ディヴァインベニゾン"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002638_hr1.png"
|
||||
},
|
||||
"7498": {
|
||||
"cast": 0,
|
||||
"recast": 150,
|
||||
"names": {
|
||||
"en": "Third Eye",
|
||||
"de": "Drittes Auge",
|
||||
"fr": "Troisième œil",
|
||||
"jp": "心眼"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003153_hr1.png"
|
||||
},
|
||||
"7531": {
|
||||
"cast": 0,
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Rampart",
|
||||
"de": "Schutzwall",
|
||||
"fr": "Rempart",
|
||||
"jp": "ランパート"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000801_hr1.png"
|
||||
},
|
||||
"7535": {
|
||||
"cast": 0,
|
||||
"recast": 600
|
||||
"recast": 600,
|
||||
"names": {
|
||||
"en": "Reprisal",
|
||||
"de": "Reflexion",
|
||||
"fr": "Rétorsion",
|
||||
"jp": "リプライザル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000806_hr1.png"
|
||||
},
|
||||
"7549": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Feint",
|
||||
"de": "Zermürben",
|
||||
"fr": "Restreinte",
|
||||
"jp": "牽制"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000828_hr1.png"
|
||||
},
|
||||
"7560": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Addle",
|
||||
"de": "Stumpfsinn",
|
||||
"fr": "Embrouillement",
|
||||
"jp": "アドル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/000000/000861_hr1.png"
|
||||
},
|
||||
"16012": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Shield Samba",
|
||||
"de": "Schildsamba",
|
||||
"fr": "Samba protectrice",
|
||||
"jp": "守りのサンバ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003469_hr1.png"
|
||||
},
|
||||
"16140": {
|
||||
"cast": 0,
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Camouflage",
|
||||
"de": "Camouflage",
|
||||
"fr": "Camouflage",
|
||||
"jp": "カモフラージュ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003404_hr1.png"
|
||||
},
|
||||
"16148": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Nebula",
|
||||
"de": "Nebula",
|
||||
"fr": "Nébuleuse",
|
||||
"jp": "ネビュラ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003412_hr1.png"
|
||||
},
|
||||
"16152": {
|
||||
"cast": 0,
|
||||
"recast": 3600,
|
||||
"names": {
|
||||
"en": "Superbolide",
|
||||
"de": "Meteoritenfall",
|
||||
"fr": "Bolide",
|
||||
"jp": "ボーライド"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003416_hr1.png"
|
||||
},
|
||||
"16160": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Heart of Light",
|
||||
"de": "Wackeres Herz",
|
||||
"fr": "Cœur de Lumière",
|
||||
"jp": "ハート・オブ・ライト"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003424_hr1.png"
|
||||
},
|
||||
"16161": {
|
||||
"cast": 0,
|
||||
"recast": 250,
|
||||
"names": {
|
||||
"en": "Heart of Stone",
|
||||
"de": "Steinernes Herz",
|
||||
"fr": "Cœur de pierre",
|
||||
"jp": "ハート・オブ・ストーン"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003425_hr1.png"
|
||||
},
|
||||
"16471": {
|
||||
"cast": 0,
|
||||
"recast": 900
|
||||
"recast": 900,
|
||||
"names": {
|
||||
"en": "Dark Missionary",
|
||||
"de": "Dunkler Bote",
|
||||
"fr": "Missionnaire des Ténèbres",
|
||||
"jp": "ダークミッショナリー"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003087_hr1.png"
|
||||
},
|
||||
"16536": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Temperance",
|
||||
"de": "Linderung",
|
||||
"fr": "Tempérance",
|
||||
"jp": "テンパランス"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002645_hr1.png"
|
||||
},
|
||||
"16538": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Fey Illumination",
|
||||
"de": "Illumination",
|
||||
"fr": "Illumination féérique",
|
||||
"jp": "フェイイルミネーション"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002853_hr1.png"
|
||||
},
|
||||
"16548": {
|
||||
"cast": 0,
|
||||
"recast": 30
|
||||
"recast": 30,
|
||||
"names": {
|
||||
"en": "Seraphic Veil",
|
||||
"de": "Schleier der Seraphim",
|
||||
"fr": "Voile séraphique",
|
||||
"jp": "セラフィックヴェール"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002847_hr1.png"
|
||||
},
|
||||
"16556": {
|
||||
"cast": 0,
|
||||
"recast": 300
|
||||
"recast": 300,
|
||||
"names": {
|
||||
"en": "Celestial Intersection",
|
||||
"de": "Kongruenz",
|
||||
"fr": "Rencontre céleste",
|
||||
"jp": "星天交差"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003556_hr1.png"
|
||||
},
|
||||
"16559": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Neutral Sect",
|
||||
"de": "Neutral",
|
||||
"fr": "Adepte de la neutralité",
|
||||
"jp": "ニュートラルセクト"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003552_hr1.png"
|
||||
},
|
||||
"16889": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Tactician",
|
||||
"de": "Taktiker",
|
||||
"fr": "Tacticien",
|
||||
"jp": "タクティシャン"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003040_hr1.png"
|
||||
},
|
||||
"24291": {
|
||||
"cast": 0,
|
||||
"recast": 15
|
||||
"recast": 15,
|
||||
"names": {
|
||||
"en": "Eukrasian Diagnosis",
|
||||
"de": "Eukratische Diagnose",
|
||||
"fr": "Diagnosis eucrasique",
|
||||
"jp": "エウクラシア・ディアグノシス"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003659_hr1.png"
|
||||
},
|
||||
"24292": {
|
||||
"cast": 0,
|
||||
"recast": 15
|
||||
"recast": 15,
|
||||
"names": {
|
||||
"en": "Eukrasian Prognosis",
|
||||
"de": "Eukratische Prognose",
|
||||
"fr": "Prognosis eucrasique",
|
||||
"jp": "エウクラシア・プログノシス"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003660_hr1.png"
|
||||
},
|
||||
"24298": {
|
||||
"cast": 0,
|
||||
"recast": 300
|
||||
"recast": 300,
|
||||
"names": {
|
||||
"en": "Kerachole",
|
||||
"de": "Kerachole",
|
||||
"fr": "Kerachole",
|
||||
"jp": "ケーラコレ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003666_hr1.png"
|
||||
},
|
||||
"24305": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Haima",
|
||||
"de": "Haima",
|
||||
"fr": "Haima",
|
||||
"jp": "ハイマ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003673_hr1.png"
|
||||
},
|
||||
"24310": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Holos",
|
||||
"de": "Holos",
|
||||
"fr": "Holos",
|
||||
"jp": "ホーリズム"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003678_hr1.png"
|
||||
},
|
||||
"24311": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Panhaima",
|
||||
"de": "Panhaima",
|
||||
"fr": "Panhaima",
|
||||
"jp": "パンハイマ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003679_hr1.png"
|
||||
},
|
||||
"24404": {
|
||||
"cast": 0,
|
||||
"recast": 300,
|
||||
"names": {
|
||||
"en": "Arcane Crest",
|
||||
"de": "Arkanes Wappen",
|
||||
"fr": "Blason arcanique",
|
||||
"jp": "アルケインクレスト"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003632_hr1.png"
|
||||
},
|
||||
"25746": {
|
||||
"cast": 0,
|
||||
"recast": 50,
|
||||
"names": {
|
||||
"en": "Holy Sheltron",
|
||||
"de": "Heiliges Schiltron",
|
||||
"fr": "Schiltron sacré",
|
||||
"jp": "ホーリーシェルトロン"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002950_hr1.png"
|
||||
},
|
||||
"25751": {
|
||||
"cast": 0,
|
||||
"recast": 250
|
||||
"recast": 250,
|
||||
"names": {
|
||||
"en": "Bloodwhetting",
|
||||
"de": "Urimpuls",
|
||||
"fr": "Intuition fougueuse",
|
||||
"jp": "原初の血気"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002569_hr1.png"
|
||||
},
|
||||
"25754": {
|
||||
"cast": 0,
|
||||
"recast": 600,
|
||||
"names": {
|
||||
"en": "Oblation",
|
||||
"de": "Opfergabe",
|
||||
"fr": "Oblation",
|
||||
"jp": "オブレーション"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003089_hr1.png"
|
||||
},
|
||||
"25758": {
|
||||
"cast": 0,
|
||||
"recast": 250,
|
||||
"names": {
|
||||
"en": "Heart of Corundum",
|
||||
"de": "Herz des Korunds",
|
||||
"fr": "Cœur de corindon",
|
||||
"jp": "ハート・オブ・コランダム"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003430_hr1.png"
|
||||
},
|
||||
"25789": {
|
||||
"cast": 0,
|
||||
"recast": 15
|
||||
"recast": 15,
|
||||
"names": {
|
||||
"en": "Improvised Finish",
|
||||
"de": "Improvisiertes Finale",
|
||||
"fr": "Final improvisé",
|
||||
"jp": "インプロビゼーション・フィニッシュ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003479_hr1.png"
|
||||
},
|
||||
"25799": {
|
||||
"cast": 0,
|
||||
"recast": 600
|
||||
"recast": 600,
|
||||
"names": {
|
||||
"en": "Radiant Aegis",
|
||||
"de": "Schimmerschild",
|
||||
"fr": "Égide rayonnante",
|
||||
"jp": "守りの光"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002750_hr1.png"
|
||||
},
|
||||
"25857": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Magick Barrier",
|
||||
"de": "Magiebarriere",
|
||||
"fr": "Barrière anti-magie",
|
||||
"jp": "バマジク"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003237_hr1.png"
|
||||
},
|
||||
"25868": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Expedient",
|
||||
"de": "Sturm und Drang",
|
||||
"fr": "Thèse fluidique",
|
||||
"jp": "疾風怒濤の計"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002878_hr1.png"
|
||||
},
|
||||
"34685": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Tempera Coat",
|
||||
"de": "Tempera-Schicht",
|
||||
"fr": "Enduit a tempera",
|
||||
"jp": "テンペラコート"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003835_hr1.png"
|
||||
},
|
||||
"34686": {
|
||||
"cast": 0,
|
||||
"recast": 10
|
||||
"recast": 10,
|
||||
"names": {
|
||||
"en": "Tempera Grassa",
|
||||
"de": "Fette Tempera",
|
||||
"fr": "Tempera grassa",
|
||||
"jp": "テンペラグラッサ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003836_hr1.png"
|
||||
},
|
||||
"36920": {
|
||||
"cast": 0,
|
||||
"recast": 1200
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Guardian",
|
||||
"de": "Heilige Wacht",
|
||||
"fr": "Garde extrême",
|
||||
"jp": "エクストリームガード"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002524_hr1.png"
|
||||
},
|
||||
"36923": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Damnation",
|
||||
"de": "Verdammnis",
|
||||
"fr": "Damnation",
|
||||
"jp": "ダムネーション"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002573_hr1.png"
|
||||
},
|
||||
"36927": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Shadowed Vigil",
|
||||
"de": "Schattenwacht",
|
||||
"fr": "Vigile ténébreux",
|
||||
"jp": "シャドウヴィジル"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003094_hr1.png"
|
||||
},
|
||||
"36935": {
|
||||
"cast": 0,
|
||||
"recast": 1200,
|
||||
"names": {
|
||||
"en": "Great Nebula",
|
||||
"de": "Große Nebula",
|
||||
"fr": "Grande nébuleuse",
|
||||
"jp": "グレートネビュラ"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003435_hr1.png"
|
||||
},
|
||||
"37011": {
|
||||
"cast": 0,
|
||||
"recast": 10
|
||||
"recast": 10,
|
||||
"names": {
|
||||
"en": "Divine Caress",
|
||||
"de": "Göttliche Umarmung",
|
||||
"fr": "Caresse divine",
|
||||
"jp": "ディヴァインカレス"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/002000/002128_hr1.png"
|
||||
},
|
||||
"37025": {
|
||||
"cast": 0,
|
||||
"recast": 10
|
||||
"recast": 10,
|
||||
"names": {
|
||||
"en": "the Spire",
|
||||
"de": "Turm",
|
||||
"fr": "La Tour",
|
||||
"jp": "ビエルゴの塔"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003115_hr1.png"
|
||||
},
|
||||
"37034": {
|
||||
"cast": 0,
|
||||
"recast": 15
|
||||
"recast": 15,
|
||||
"names": {
|
||||
"en": "Eukrasian Prognosis II",
|
||||
"de": "Eukratische Prognose II",
|
||||
"fr": "Prognosis eucrasique II",
|
||||
"jp": "エウクラシア・プログノシスII"
|
||||
},
|
||||
"icon": "https://xivapi.com/i/003000/003689_hr1.png"
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,27 @@
|
||||
let extReportCode = '';
|
||||
let mitigationNames = {};
|
||||
let planRefId = '';
|
||||
let actionIconPromise = null;
|
||||
const actionIconsByName = {};
|
||||
|
||||
function mitigationIcon(m) {
|
||||
return MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name] ?? actionIconsByName[m.key] ?? actionIconsByName[m.name] ?? '';
|
||||
}
|
||||
|
||||
async function ensureActionIconCache() {
|
||||
if (actionIconPromise) return actionIconPromise;
|
||||
actionIconPromise = (async () => {
|
||||
try {
|
||||
const res = await fetch('assets/jsons/Action.json', { cache: 'no-cache' });
|
||||
if (!res.ok) return;
|
||||
const actions = await res.json();
|
||||
for (const action of Object.values(actions ?? {})) {
|
||||
if (action?.names?.en && action?.icon) actionIconsByName[action.names.en] = action.icon;
|
||||
}
|
||||
} catch { }
|
||||
})();
|
||||
return actionIconPromise;
|
||||
}
|
||||
|
||||
// ── Player grid ──────────────────────────────────────────────────────────
|
||||
|
||||
@ -388,6 +409,7 @@
|
||||
refEvents = json.aoe_events ?? [];
|
||||
refFightStart = json.fight_start ?? fight.startTime;
|
||||
refPlayers = json.players ?? [];
|
||||
await ensureActionIconCache();
|
||||
window.App.setUrlState?.({
|
||||
compareReportCode: extReportCode,
|
||||
compareFightId: refId,
|
||||
@ -581,7 +603,7 @@
|
||||
...eventDebuffs.map(m => ({ ...m, missing: false })),
|
||||
...eventMissingDebuffs.map(m => ({ ...m, missing: true })),
|
||||
].map(m => {
|
||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
||||
const iconSrc = mitigationIcon(m);
|
||||
if (!iconSrc) return '';
|
||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||
return m.missing
|
||||
@ -608,7 +630,7 @@
|
||||
|
||||
// DR buff icons (shown below player box)
|
||||
const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => {
|
||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
||||
const iconSrc = mitigationIcon(m);
|
||||
if (!iconSrc) return '';
|
||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
||||
@ -655,7 +677,7 @@
|
||||
const refDebuffIconsHtml = refVisible.flatMap(t => (t.mitigations ?? []))
|
||||
.filter(m => m.buffType === 'debuff' && !seenRefDebuffKeys.has(m.key ?? m.name) && seenRefDebuffKeys.add(m.key ?? m.name))
|
||||
.map(m => {
|
||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
||||
const iconSrc = mitigationIcon(m);
|
||||
if (!iconSrc) return '';
|
||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
||||
@ -673,7 +695,7 @@
|
||||
: '';
|
||||
|
||||
const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => {
|
||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
||||
const iconSrc = mitigationIcon(m);
|
||||
if (!iconSrc) return '';
|
||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||
const k = m.key ?? m.name;
|
||||
@ -786,6 +808,7 @@
|
||||
setupPhases(window.App?.phases ?? []);
|
||||
renderPlayers(json.players ?? []);
|
||||
mitigationNames = json.mitigation_names ?? {};
|
||||
await ensureActionIconCache();
|
||||
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
||||
|
||||
document.getElementById('analysis-loading').style.display = 'none';
|
||||
|
||||
@ -29,25 +29,51 @@
|
||||
'PLD': [
|
||||
{ name: 'Passage of Arms', buffType: 'buff' },
|
||||
{ name: 'Divine Veil', buffType: 'shield' },
|
||||
{ name: 'Guardian', buffType: 'shield' },
|
||||
{ name: 'Rampart', buffType: 'buff', extraAbilityGameID: 7531, duration: 20 },
|
||||
{ name: 'Hallowed Ground', buffType: 'buff', extraAbilityGameID: 30, duration: 10 },
|
||||
{ name: 'Sentinel', buffType: 'buff', extraAbilityGameID: 17, duration: 15 },
|
||||
{ name: 'Guardian', buffType: 'shield', extraAbilityGameID: 36920, duration: 15 },
|
||||
{ name: 'Bulwark', buffType: 'buff', extraAbilityGameID: 22, duration: 10 },
|
||||
{ name: 'Holy Sheltron', buffType: 'buff', extraAbilityGameID: 25746, duration: 8 },
|
||||
{ name: 'Intervention', buffType: 'buff', extraAbilityGameID: 7382, duration: 8 },
|
||||
{ name: 'Reprisal', buffType: 'debuff' },
|
||||
],
|
||||
'WAR': [
|
||||
{ name: 'Shake It Off', buffType: 'shield' },
|
||||
{ name: 'Bloodwhetting', buffType: 'shield' },
|
||||
{ name: 'Rampart', buffType: 'buff', extraAbilityGameID: 7531, duration: 20 },
|
||||
{ name: 'Holmgang', buffType: 'buff', extraAbilityGameID: 43, duration: 10 },
|
||||
{ name: 'Vengeance', buffType: 'buff', extraAbilityGameID: 44, duration: 15 },
|
||||
{ name: 'Damnation', buffType: 'buff', extraAbilityGameID: 36923, duration: 15 },
|
||||
{ name: 'Thrill of Battle', buffType: 'buff', extraAbilityGameID: 40, duration: 10 },
|
||||
{ name: 'Raw Intuition', buffType: 'buff', extraAbilityGameID: 3551, duration: 6 },
|
||||
{ name: 'Bloodwhetting', buffType: 'shield', extraAbilityGameID: 25751, duration: 8 },
|
||||
{ name: 'Reprisal', buffType: 'debuff' },
|
||||
],
|
||||
'DRK': [
|
||||
{ name: 'Dark Missionary', buffType: 'buff' },
|
||||
{ name: 'Rampart', buffType: 'buff', extraAbilityGameID: 7531, duration: 20 },
|
||||
{ name: 'Living Dead', buffType: 'buff', extraAbilityGameID: 3638, duration: 10 },
|
||||
{ name: 'Shadow Wall', buffType: 'buff', extraAbilityGameID: 3636, duration: 15 },
|
||||
{ name: 'Shadowed Vigil', buffType: 'buff', extraAbilityGameID: 36927, duration: 15 },
|
||||
{ name: 'Dark Mind', buffType: 'buff', extraAbilityGameID: 3634, duration: 10 },
|
||||
{ name: 'The Blackest Night', buffType: 'shield', extraAbilityGameID: 7393, duration: 7 },
|
||||
{ name: 'Oblation', buffType: 'buff', extraAbilityGameID: 25754, duration: 10 },
|
||||
{ name: 'Reprisal', buffType: 'debuff' },
|
||||
],
|
||||
'GNB': [
|
||||
{ name: 'Heart of Light', buffType: 'buff' },
|
||||
{ name: 'Rampart', buffType: 'buff', extraAbilityGameID: 7531, duration: 20 },
|
||||
{ name: 'Superbolide', buffType: 'buff', extraAbilityGameID: 16152, duration: 10 },
|
||||
{ name: 'Nebula', buffType: 'buff', extraAbilityGameID: 16148, duration: 15 },
|
||||
{ name: 'Great Nebula', buffType: 'buff', extraAbilityGameID: 36935, duration: 15 },
|
||||
{ name: 'Camouflage', buffType: 'buff', extraAbilityGameID: 16140, duration: 20 },
|
||||
{ name: 'Heart of Stone', buffType: 'buff', extraAbilityGameID: 16161, duration: 7 },
|
||||
{ name: 'Heart of Corundum', buffType: 'buff', extraAbilityGameID: 25758, duration: 8 },
|
||||
{ name: 'Reprisal', buffType: 'debuff' },
|
||||
],
|
||||
'WHM': [
|
||||
{ name: 'Temperance', buffType: 'buff' },
|
||||
{ name: 'Divine Benison', buffType: 'shield' },
|
||||
{ name: 'Divine Benison', buffType: 'shield', extraAbilityGameID: 7432, duration: 15 },
|
||||
{ name: 'Divine Caress', buffType: 'shield' },
|
||||
],
|
||||
'SCH': [
|
||||
@ -55,14 +81,14 @@
|
||||
{ name: 'Expedient', buffType: 'buff' },
|
||||
{ name: 'Fey Illumination', buffType: 'buff' },
|
||||
{ name: 'Galvanize', buffType: 'shield' },
|
||||
{ name: 'Seraphic Veil', buffType: 'shield' },
|
||||
{ name: 'Seraphic Veil', buffType: 'shield', extraAbilityGameID: 16548, duration: 30 },
|
||||
{ name: 'Catalyze', buffType: 'shield' },
|
||||
],
|
||||
'AST': [
|
||||
{ name: 'Collective Unconscious', buffType: 'buff' },
|
||||
{ name: 'Neutral Sect', buffType: 'shield' },
|
||||
{ name: 'Intersection', buffType: 'shield' },
|
||||
{ name: 'the Spire', buffType: 'shield' },
|
||||
{ name: 'Intersection', buffType: 'shield', extraAbilityGameID: 16556, duration: 30 },
|
||||
{ name: 'the Spire', buffType: 'shield', extraAbilityGameID: 37025, duration: 30 },
|
||||
],
|
||||
'SGE': [
|
||||
{ name: 'Kerachole', buffType: 'buff' },
|
||||
@ -71,9 +97,9 @@
|
||||
{ name: 'Panhaima', buffType: 'shield' },
|
||||
{ name: 'Eukrasian Prognosis', buffType: 'shield' },
|
||||
{ name: 'Eukrasian Prognosis II', buffType: 'shield' },
|
||||
{ name: 'Eukrasian Diagnosis', buffType: 'shield' },
|
||||
{ name: 'Differential Diagnosis', buffType: 'shield' },
|
||||
{ name: 'Haima', buffType: 'shield' },
|
||||
{ name: 'Eukrasian Diagnosis', buffType: 'shield', extraAbilityGameID: 24291, duration: 30 },
|
||||
{ name: 'Differential Diagnosis', buffType: 'shield', extraAbilityGameID: 24291, duration: 30 },
|
||||
{ name: 'Haima', buffType: 'shield', extraAbilityGameID: 24305, duration: 15 },
|
||||
],
|
||||
'BRD': [{ name: 'Troubadour', buffType: 'buff' }],
|
||||
'MCH': [{ name: 'Tactician', buffType: 'buff' }],
|
||||
@ -81,16 +107,31 @@
|
||||
{ name: 'Shield Samba', buffType: 'buff' },
|
||||
{ name: 'Improvised Finish', buffType: 'shield' },
|
||||
],
|
||||
'MNK': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'MNK': [
|
||||
{ name: 'Riddle of Earth', buffType: 'buff', extraAbilityGameID: 7394, duration: 10 },
|
||||
{ name: 'Feint', buffType: 'debuff' },
|
||||
],
|
||||
'DRG': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'NIN': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'SAM': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'RPR': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'NIN': [
|
||||
{ name: 'Shade Shift', buffType: 'shield', extraAbilityGameID: 2241, duration: 20 },
|
||||
{ name: 'Feint', buffType: 'debuff' },
|
||||
],
|
||||
'SAM': [
|
||||
{ name: 'Third Eye', buffType: 'buff', extraAbilityGameID: 7498, duration: 4 },
|
||||
{ name: 'Feint', buffType: 'debuff' },
|
||||
],
|
||||
'RPR': [
|
||||
{ name: 'Arcane Crest', buffType: 'shield', extraAbilityGameID: 24404, duration: 5 },
|
||||
{ name: 'Feint', buffType: 'debuff' },
|
||||
],
|
||||
'VPR': [{ name: 'Feint', buffType: 'debuff' }],
|
||||
'BLM': [{ name: 'Addle', buffType: 'debuff' }],
|
||||
'BLM': [
|
||||
{ name: 'Manaward', buffType: 'shield', extraAbilityGameID: 157, duration: 20 },
|
||||
{ name: 'Addle', buffType: 'debuff' },
|
||||
],
|
||||
'SMN': [
|
||||
{ name: 'Addle', buffType: 'debuff' },
|
||||
{ name: 'Radiant Aegis', buffType: 'shield' },
|
||||
{ name: 'Radiant Aegis', buffType: 'shield', extraAbilityGameID: 25799, duration: 30 },
|
||||
],
|
||||
'RDM': [
|
||||
{ name: 'Addle', buffType: 'debuff' },
|
||||
@ -98,16 +139,24 @@
|
||||
],
|
||||
'PCT': [
|
||||
{ name: 'Addle', buffType: 'debuff' },
|
||||
{ name: 'Tempera Coat', buffType: 'shield' },
|
||||
{ name: 'Tempera Coat', buffType: 'shield', extraAbilityGameID: 34685, duration: 10 },
|
||||
{ name: 'Tempera Grassa', buffType: 'shield' },
|
||||
],
|
||||
};
|
||||
|
||||
const ABILITY_JOB_MAP = {
|
||||
'Passage of Arms': 'PLD', 'Divine Veil': 'PLD', 'Guardian': 'PLD',
|
||||
'Shake It Off': 'WAR', 'Bloodwhetting': 'WAR',
|
||||
'Dark Missionary': 'DRK',
|
||||
'Heart of Light': 'GNB',
|
||||
'Passage of Arms': 'PLD', 'Divine Veil': 'PLD',
|
||||
'Hallowed Ground': 'PLD', 'Sentinel': 'PLD', 'Guardian': 'PLD',
|
||||
'Bulwark': 'PLD', 'Holy Sheltron': 'PLD', 'Intervention': 'PLD',
|
||||
'Shake It Off': 'WAR', 'Holmgang': 'WAR', 'Vengeance': 'WAR',
|
||||
'Damnation': 'WAR', 'Thrill of Battle': 'WAR', 'Raw Intuition': 'WAR',
|
||||
'Bloodwhetting': 'WAR',
|
||||
'Dark Missionary': 'DRK', 'Living Dead': 'DRK', 'Shadow Wall': 'DRK',
|
||||
'Shadowed Vigil': 'DRK', 'Dark Mind': 'DRK', 'The Blackest Night': 'DRK',
|
||||
'Oblation': 'DRK',
|
||||
'Heart of Light': 'GNB', 'Superbolide': 'GNB', 'Nebula': 'GNB',
|
||||
'Great Nebula': 'GNB', 'Camouflage': 'GNB', 'Heart of Stone': 'GNB',
|
||||
'Heart of Corundum': 'GNB',
|
||||
'Temperance': 'WHM', 'Divine Benison': 'WHM', 'Divine Caress': 'WHM',
|
||||
'Sacred Soil': 'SCH', 'Expedient': 'SCH', 'Fey Illumination': 'SCH',
|
||||
'Galvanize': 'SCH', 'Seraphic Veil': 'SCH', 'Catalyze': 'SCH',
|
||||
@ -120,6 +169,11 @@
|
||||
'Troubadour': 'BRD',
|
||||
'Tactician': 'MCH',
|
||||
'Shield Samba': 'DNC', 'Improvised Finish': 'DNC',
|
||||
'Riddle of Earth': 'MNK',
|
||||
'Shade Shift': 'NIN',
|
||||
'Third Eye': 'SAM',
|
||||
'Arcane Crest': 'RPR',
|
||||
'Manaward': 'BLM',
|
||||
'Radiant Aegis': 'SMN',
|
||||
'Magick Barrier': 'RDM',
|
||||
'Tempera Coat': 'PCT', 'Tempera Grassa': 'PCT',
|
||||
|
||||
113
js/planner.js
113
js/planner.js
@ -127,13 +127,29 @@ function fmtNumber(n) {
|
||||
|
||||
function assignmentAbilityName(assignment, plan = null) {
|
||||
const key = assignment?.ability ?? '';
|
||||
return assignment?.abilityName ?? plan?.mitigationNames?.[key] ?? key;
|
||||
const localized = localizedAbilityName(key, plan);
|
||||
if (localized !== key) return localized;
|
||||
return assignment?.abilityName ?? key;
|
||||
}
|
||||
|
||||
function plannerLanguage() {
|
||||
return window.App?.language || localStorage.getItem('ff14-mitigator-language') || 'en';
|
||||
}
|
||||
|
||||
function actionLocalizedName(ability) {
|
||||
const language = plannerLanguage();
|
||||
const meta = actionMetaByName[ability] ?? null;
|
||||
return meta?.names?.[language] || meta?.names?.en || null;
|
||||
}
|
||||
|
||||
function localizedAbilityName(ability, plan = null) {
|
||||
return plan?.mitigationNames?.[ability] ?? actionLocalizedName(ability) ?? ability;
|
||||
}
|
||||
|
||||
function abilityIcon(ability) {
|
||||
return MITIG_ICONS[ability] ?? actionMetaByName[ability]?.icon ?? '';
|
||||
}
|
||||
|
||||
async function ensureActionMetaLoaded() {
|
||||
if (actionMetaPromise) return actionMetaPromise;
|
||||
actionMetaPromise = (async () => {
|
||||
@ -156,6 +172,13 @@ async function ensureActionMetaLoaded() {
|
||||
const meta = {
|
||||
id,
|
||||
name: action.Name_en ?? '',
|
||||
names: {
|
||||
en: action.Name_en ?? '',
|
||||
de: action.Name_de ?? '',
|
||||
fr: action.Name_fr ?? '',
|
||||
jp: action.Name_ja ?? '',
|
||||
},
|
||||
icon: compact?.[id]?.icon ?? null,
|
||||
cast: (parseInt(compact?.[id]?.cast ?? action.Cast100ms ?? 0, 10) || 0) / 10,
|
||||
recast: (parseInt(compact?.[id]?.recast ?? action.Recast100ms ?? 0, 10) || 0) / 10,
|
||||
duration: durations.find(Number.isFinite) ?? 15,
|
||||
@ -164,11 +187,16 @@ async function ensureActionMetaLoaded() {
|
||||
if (meta.name) byName[meta.name] = meta;
|
||||
}
|
||||
for (const [id, action] of Object.entries(compact ?? {})) {
|
||||
const names = action.names ?? {};
|
||||
byId[id] = {
|
||||
...(byId[id] ?? { id }),
|
||||
name: byId[id]?.name ?? names.en ?? '',
|
||||
names: { ...(byId[id]?.names ?? {}), ...names },
|
||||
icon: byId[id]?.icon ?? action.icon ?? '',
|
||||
cast: (parseInt(action.cast ?? 0, 10) || 0) / 10,
|
||||
recast: (parseInt(action.recast ?? 0, 10) || 0) / 10,
|
||||
};
|
||||
if (names.en && !byName[names.en]) byName[names.en] = byId[id];
|
||||
}
|
||||
actionMetaById = byId;
|
||||
actionMetaByName = byName;
|
||||
@ -434,7 +462,7 @@ function renderPlanDetail(plan) {
|
||||
initTimeline(plan.id);
|
||||
initMechanicClicks(plan.id);
|
||||
renderInfoPanel(plan);
|
||||
ensureActionMetaLoaded().then(() => refreshTimeline(plan.id));
|
||||
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
||||
}
|
||||
|
||||
function avgNonTankMaxHp(plan) {
|
||||
@ -502,7 +530,7 @@ function renderMechanicListHtml(plan) {
|
||||
: a.buffType === 'shield' ? 'badge-assign-shield'
|
||||
: 'badge-assign-buff';
|
||||
const isMissing = !!a.job && !activeJobSet.has(a.job);
|
||||
const icon = MITIG_ICONS[a.ability] ?? '';
|
||||
const icon = abilityIcon(a.ability);
|
||||
const ability = assignmentAbilityName(a, plan);
|
||||
const label = a.job ? `${escHtml(a.job)} · ${escHtml(ability)}` : escHtml(ability);
|
||||
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
||||
@ -641,8 +669,31 @@ const JOB_GANTT_ORDER = {
|
||||
};
|
||||
|
||||
const TIMELINE_PERSONAL_ABILITIES = new Set([
|
||||
'Rampart',
|
||||
'Hallowed Ground',
|
||||
'Sentinel',
|
||||
'Guardian',
|
||||
'Bulwark',
|
||||
'Holy Sheltron',
|
||||
'Intervention',
|
||||
'Holmgang',
|
||||
'Vengeance',
|
||||
'Damnation',
|
||||
'Thrill of Battle',
|
||||
'Raw Intuition',
|
||||
'Bloodwhetting',
|
||||
'Living Dead',
|
||||
'Shadow Wall',
|
||||
'Shadowed Vigil',
|
||||
'Dark Mind',
|
||||
'The Blackest Night',
|
||||
'Oblation',
|
||||
'Superbolide',
|
||||
'Nebula',
|
||||
'Great Nebula',
|
||||
'Camouflage',
|
||||
'Heart of Stone',
|
||||
'Heart of Corundum',
|
||||
'Divine Benison',
|
||||
'Intersection',
|
||||
'the Spire',
|
||||
@ -652,6 +703,11 @@ const TIMELINE_PERSONAL_ABILITIES = new Set([
|
||||
'Seraphic Veil',
|
||||
'Radiant Aegis',
|
||||
'Tempera Coat',
|
||||
'Riddle of Earth',
|
||||
'Shade Shift',
|
||||
'Third Eye',
|
||||
'Arcane Crest',
|
||||
'Manaward',
|
||||
]);
|
||||
|
||||
function timelineOptions(plan) {
|
||||
@ -664,7 +720,7 @@ function timelineOptions(plan) {
|
||||
function timelineAbilityVisible(ability, options) {
|
||||
const isPersonal = TIMELINE_PERSONAL_ABILITIES.has(ability.name);
|
||||
const isShield = ability.buffType === 'shield';
|
||||
if (isPersonal && !options.includePersonal) return false;
|
||||
if (isPersonal) return !!options.includePersonal;
|
||||
if (isShield && !options.includeShields && ability.name !== 'Panhaima') return false;
|
||||
return true;
|
||||
}
|
||||
@ -700,6 +756,18 @@ function jobCanUseAbility(job, ability) {
|
||||
return (JOB_ABILITIES[job] ?? []).some(a => a.name === ability);
|
||||
}
|
||||
|
||||
function abilityDefinition(ability, job = '') {
|
||||
if (job) {
|
||||
const own = (JOB_ABILITIES[job] ?? []).find(a => a.name === ability);
|
||||
if (own) return own;
|
||||
}
|
||||
for (const abilities of Object.values(JOB_ABILITIES)) {
|
||||
const found = abilities.find(a => a.name === ability);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function timelineRowsForAssignment(rows, assignment) {
|
||||
const assignedJob = assignment.job ?? '';
|
||||
return rows.filter(row => {
|
||||
@ -949,8 +1017,17 @@ function assignmentOverlapsJob(plan, job, ability, timestamp, ignore = null, can
|
||||
}
|
||||
|
||||
function actionMetaForAssignment(assignment) {
|
||||
const id = String(assignment?.actionId ?? assignment?.extraAbilityGameID ?? '');
|
||||
return (id && actionMetaById[id]) || actionMetaByName[assignment?.ability] || null;
|
||||
const def = abilityDefinition(assignment?.ability, assignment?.job ?? '');
|
||||
const id = String(assignment?.actionId ?? assignment?.extraAbilityGameID ?? def?.extraAbilityGameID ?? '');
|
||||
const meta = (id && actionMetaById[id]) || actionMetaByName[assignment?.ability] || null;
|
||||
if (!def) return meta;
|
||||
return {
|
||||
...(meta ?? {}),
|
||||
id: id || meta?.id,
|
||||
name: meta?.name ?? assignment?.ability,
|
||||
recast: meta?.recast ?? def.recast,
|
||||
duration: def.duration ?? meta?.duration,
|
||||
};
|
||||
}
|
||||
|
||||
function assignmentCooldownSeconds(assignment) {
|
||||
@ -1020,7 +1097,7 @@ function renderTimelineHtml(plan) {
|
||||
const cls = a.buffType === 'debuff' ? 'timeline-mitigation--debuff'
|
||||
: a.buffType === 'shield' ? 'timeline-mitigation--shield'
|
||||
: 'timeline-mitigation--buff';
|
||||
const icon = MITIG_ICONS[a.ability] ?? '';
|
||||
const icon = abilityIcon(a.ability);
|
||||
const abilityLabel = assignmentAbilityName(a, plan);
|
||||
blocks.push(`
|
||||
<button class="timeline-mitigation ${cls}${selected}${unresolved}"
|
||||
@ -1036,8 +1113,8 @@ function renderTimelineHtml(plan) {
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
const icon = MITIG_ICONS[row.ability] ?? '';
|
||||
const abilityDisplayName = plan.mitigationNames?.[row.ability] ?? row.ability;
|
||||
const icon = abilityIcon(row.ability);
|
||||
const abilityDisplayName = localizedAbilityName(row.ability, plan);
|
||||
const jobStartCls = row.firstForJob ? ' timeline-player-row--job-start' : '';
|
||||
return `
|
||||
<div class="timeline-row timeline-player-row${jobStartCls}" data-row-idx="${row.idx}" data-job="${escHtml(row.job)}" data-ability="${escHtml(row.ability)}">
|
||||
@ -1166,11 +1243,12 @@ function addTimelineAssignment(planId, mechanicId, ability, job, buffType, times
|
||||
if (!plan || !jobCanUseAbility(job, ability)) return;
|
||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||
if (!mechanic) return;
|
||||
const def = abilityDefinition(ability, job);
|
||||
mechanic.assignments = mechanic.assignments ?? [];
|
||||
const assignment = {
|
||||
ability,
|
||||
abilityName: plan.mitigationNames?.[ability],
|
||||
actionId: actionMetaByName[ability]?.id ?? null,
|
||||
abilityName: localizedAbilityName(ability, plan),
|
||||
actionId: def?.extraAbilityGameID ?? actionMetaByName[ability]?.id ?? null,
|
||||
job,
|
||||
buffType,
|
||||
timestamp: Math.max(0, Math.round(timestamp)),
|
||||
@ -1613,7 +1691,7 @@ function initTimeline(planId) {
|
||||
.filter((row, idx, arr) => arr.findIndex(r => r.job === row.job) === idx)
|
||||
.map(row => ({
|
||||
label: `${row.job}${row.name ? ` · ${row.name}` : ''}`,
|
||||
icon: MITIG_ICONS[block.dataset.ability] ?? '',
|
||||
icon: abilityIcon(block.dataset.ability),
|
||||
disabled: assignmentOverlapsJob(plan, row.job, block.dataset.ability, timestamp, {
|
||||
mechanicId: block.dataset.mechanicId,
|
||||
ability: block.dataset.ability,
|
||||
@ -1641,7 +1719,7 @@ function initTimeline(planId) {
|
||||
const ab = (JOB_ABILITIES[rowJob] ?? []).find(a => a.name === rowAbility);
|
||||
const candidate = ab ? {
|
||||
ability: rowAbility,
|
||||
actionId: actionMetaByName[rowAbility]?.id ?? null,
|
||||
actionId: ab.extraAbilityGameID ?? actionMetaByName[rowAbility]?.id ?? null,
|
||||
job: rowJob,
|
||||
buffType: ab.buffType,
|
||||
} : null;
|
||||
@ -2381,9 +2459,9 @@ function renderAbilityModalContent() {
|
||||
const activeClass = isActive ? ' ability-chip--active' : '';
|
||||
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
||||
const title = byOtherJob ? `Bereits von ${escHtml(assigned.job)} zugewiesen` : '';
|
||||
const icon = MITIG_ICONS[ab.name] ?? '';
|
||||
const icon = abilityIcon(ab.name);
|
||||
const assignedName = assigned ? assignmentAbilityName(assigned, plan) : '';
|
||||
const label = assignedName || plan.mitigationNames?.[ab.name] || ab.name;
|
||||
const label = assignedName || localizedAbilityName(ab.name, plan);
|
||||
return `<button class="ability-chip ${cls}${activeClass}${otherClass}"
|
||||
data-ability="${escHtml(ab.name)}"
|
||||
data-job="${escHtml(job)}"
|
||||
@ -2431,10 +2509,11 @@ function toggleAbilityAssignment(abilityName, job, buffType) {
|
||||
assignment.job = job;
|
||||
}
|
||||
} else {
|
||||
const def = abilityDefinition(abilityName, job);
|
||||
const assignment = {
|
||||
ability: abilityName,
|
||||
abilityName: plan.mitigationNames?.[abilityName],
|
||||
actionId: actionMetaByName[abilityName]?.id ?? null,
|
||||
abilityName: localizedAbilityName(abilityName, plan),
|
||||
actionId: def?.extraAbilityGameID ?? actionMetaByName[abilityName]?.id ?? null,
|
||||
job,
|
||||
buffType,
|
||||
};
|
||||
|
||||
@ -289,6 +289,41 @@ function action_field(array $action, string $field): ?int
|
||||
return null;
|
||||
}
|
||||
|
||||
function action_text_field(array $action, string $field): ?string
|
||||
{
|
||||
if (array_key_exists($field, $action) && is_scalar($action[$field])) {
|
||||
$value = trim((string)$action[$field]);
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
$fields = $action['fields'] ?? null;
|
||||
if (is_array($fields) && array_key_exists($field, $fields) && is_scalar($fields[$field])) {
|
||||
$value = trim((string)$fields[$field]);
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function action_icon_url(array $action): ?string
|
||||
{
|
||||
$icon = $action['Icon'] ?? $action['fields']['Icon'] ?? null;
|
||||
if (!is_array($icon)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = $icon['path_hr1'] ?? $icon['path'] ?? null;
|
||||
if (!is_string($path) || $path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('#ui/icon/([^/]+)/([^/]+)\.tex$#', $path, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'https://xivapi.com/i/' . $matches[1] . '/' . $matches[2] . '.png';
|
||||
}
|
||||
|
||||
$plannerAbilityNames = read_planner_ability_names($plannerDataSource);
|
||||
$actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames);
|
||||
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
||||
@ -311,6 +346,13 @@ foreach ($wanted as $id => $_) {
|
||||
$filtered[$id] = [
|
||||
'cast' => action_field($action, 'Cast100ms'),
|
||||
'recast' => action_field($action, 'Recast100ms'),
|
||||
'names' => [
|
||||
'en' => action_text_field($action, 'Name_en'),
|
||||
'de' => action_text_field($action, 'Name_de'),
|
||||
'fr' => action_text_field($action, 'Name_fr'),
|
||||
'jp' => action_text_field($action, 'Name_ja'),
|
||||
],
|
||||
'icon' => action_icon_url($action),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user