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],
|
'Tactician' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001951, 'extraAbilityGameID' => 16889],
|
||||||
'Shield Samba' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001826, 'extraAbilityGameID' => 16012],
|
'Shield Samba' => ['dr' => 15, 'buffType' => 'buff', 'statusId' => 1001826, 'extraAbilityGameID' => 16012],
|
||||||
'Magick Barrier' => ['dr' => 10, 'buffType' => 'buff', 'statusId' => 1002707, 'extraAbilityGameID' => 25857],
|
'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 ─────────────────────────────────────────────────────────────
|
// ── Shields ─────────────────────────────────────────────────────────────
|
||||||
// PLD
|
// PLD
|
||||||
'Divine Veil' => ['dr' => 0, 'buffType' => 'shield', 'statusId' => 1001362, 'extraAbilityGameID' => 3540],
|
'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": {
|
"185": {
|
||||||
"cast": 20,
|
"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": {
|
"188": {
|
||||||
"cast": 0,
|
"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": {
|
"3540": {
|
||||||
"cast": 0,
|
"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": {
|
"3613": {
|
||||||
"cast": 0,
|
"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": {
|
"7385": {
|
||||||
"cast": 0,
|
"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": {
|
"7388": {
|
||||||
"cast": 0,
|
"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": {
|
"7405": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 1200
|
"recast": 1200,
|
||||||
|
"names": {
|
||||||
|
"en": "Troubadour",
|
||||||
|
"de": "Troubadour",
|
||||||
|
"fr": "Troubadour",
|
||||||
|
"jp": "トルバドゥール"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/002000/002612_hr1.png"
|
||||||
},
|
},
|
||||||
"7432": {
|
"7432": {
|
||||||
"cast": 0,
|
"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": {
|
"7535": {
|
||||||
"cast": 0,
|
"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": {
|
"7549": {
|
||||||
"cast": 0,
|
"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": {
|
"7560": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 900
|
"recast": 900,
|
||||||
|
"names": {
|
||||||
|
"en": "Addle",
|
||||||
|
"de": "Stumpfsinn",
|
||||||
|
"fr": "Embrouillement",
|
||||||
|
"jp": "アドル"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/000000/000861_hr1.png"
|
||||||
},
|
},
|
||||||
"16012": {
|
"16012": {
|
||||||
"cast": 0,
|
"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": {
|
"16160": {
|
||||||
"cast": 0,
|
"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": {
|
"16471": {
|
||||||
"cast": 0,
|
"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": {
|
"16536": {
|
||||||
"cast": 0,
|
"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": {
|
"16538": {
|
||||||
"cast": 0,
|
"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": {
|
"16548": {
|
||||||
"cast": 0,
|
"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": {
|
"16556": {
|
||||||
"cast": 0,
|
"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": {
|
"16559": {
|
||||||
"cast": 0,
|
"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": {
|
"16889": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 1200
|
"recast": 1200,
|
||||||
|
"names": {
|
||||||
|
"en": "Tactician",
|
||||||
|
"de": "Taktiker",
|
||||||
|
"fr": "Tacticien",
|
||||||
|
"jp": "タクティシャン"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/003000/003040_hr1.png"
|
||||||
},
|
},
|
||||||
"24291": {
|
"24291": {
|
||||||
"cast": 0,
|
"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": {
|
"24292": {
|
||||||
"cast": 0,
|
"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": {
|
"24298": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 300
|
"recast": 300,
|
||||||
|
"names": {
|
||||||
|
"en": "Kerachole",
|
||||||
|
"de": "Kerachole",
|
||||||
|
"fr": "Kerachole",
|
||||||
|
"jp": "ケーラコレ"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/003000/003666_hr1.png"
|
||||||
},
|
},
|
||||||
"24305": {
|
"24305": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 1200
|
"recast": 1200,
|
||||||
|
"names": {
|
||||||
|
"en": "Haima",
|
||||||
|
"de": "Haima",
|
||||||
|
"fr": "Haima",
|
||||||
|
"jp": "ハイマ"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/003000/003673_hr1.png"
|
||||||
},
|
},
|
||||||
"24310": {
|
"24310": {
|
||||||
"cast": 0,
|
"cast": 0,
|
||||||
"recast": 1200
|
"recast": 1200,
|
||||||
|
"names": {
|
||||||
|
"en": "Holos",
|
||||||
|
"de": "Holos",
|
||||||
|
"fr": "Holos",
|
||||||
|
"jp": "ホーリズム"
|
||||||
|
},
|
||||||
|
"icon": "https://xivapi.com/i/003000/003678_hr1.png"
|
||||||
},
|
},
|
||||||
"24311": {
|
"24311": {
|
||||||
"cast": 0,
|
"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": {
|
"25751": {
|
||||||
"cast": 0,
|
"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": {
|
"25789": {
|
||||||
"cast": 0,
|
"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": {
|
"25799": {
|
||||||
"cast": 0,
|
"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": {
|
"25857": {
|
||||||
"cast": 0,
|
"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": {
|
"25868": {
|
||||||
"cast": 0,
|
"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": {
|
"34685": {
|
||||||
"cast": 0,
|
"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": {
|
"34686": {
|
||||||
"cast": 0,
|
"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": {
|
"36920": {
|
||||||
"cast": 0,
|
"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": {
|
"37011": {
|
||||||
"cast": 0,
|
"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": {
|
"37025": {
|
||||||
"cast": 0,
|
"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": {
|
"37034": {
|
||||||
"cast": 0,
|
"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 extReportCode = '';
|
||||||
let mitigationNames = {};
|
let mitigationNames = {};
|
||||||
let planRefId = '';
|
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 ──────────────────────────────────────────────────────────
|
// ── Player grid ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -388,6 +409,7 @@
|
|||||||
refEvents = json.aoe_events ?? [];
|
refEvents = json.aoe_events ?? [];
|
||||||
refFightStart = json.fight_start ?? fight.startTime;
|
refFightStart = json.fight_start ?? fight.startTime;
|
||||||
refPlayers = json.players ?? [];
|
refPlayers = json.players ?? [];
|
||||||
|
await ensureActionIconCache();
|
||||||
window.App.setUrlState?.({
|
window.App.setUrlState?.({
|
||||||
compareReportCode: extReportCode,
|
compareReportCode: extReportCode,
|
||||||
compareFightId: refId,
|
compareFightId: refId,
|
||||||
@ -581,7 +603,7 @@
|
|||||||
...eventDebuffs.map(m => ({ ...m, missing: false })),
|
...eventDebuffs.map(m => ({ ...m, missing: false })),
|
||||||
...eventMissingDebuffs.map(m => ({ ...m, missing: true })),
|
...eventMissingDebuffs.map(m => ({ ...m, missing: true })),
|
||||||
].map(m => {
|
].map(m => {
|
||||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
const iconSrc = mitigationIcon(m);
|
||||||
if (!iconSrc) return '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
return m.missing
|
return m.missing
|
||||||
@ -608,7 +630,7 @@
|
|||||||
|
|
||||||
// DR buff icons (shown below player box)
|
// DR buff icons (shown below player box)
|
||||||
const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType === 'buff').map(m => {
|
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 '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${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 ?? []))
|
const refDebuffIconsHtml = refVisible.flatMap(t => (t.mitigations ?? []))
|
||||||
.filter(m => m.buffType === 'debuff' && !seenRefDebuffKeys.has(m.key ?? m.name) && seenRefDebuffKeys.add(m.key ?? m.name))
|
.filter(m => m.buffType === 'debuff' && !seenRefDebuffKeys.has(m.key ?? m.name) && seenRefDebuffKeys.add(m.key ?? m.name))
|
||||||
.map(m => {
|
.map(m => {
|
||||||
const iconSrc = MITIG_ICONS[m.key] ?? MITIG_ICONS[m.name];
|
const iconSrc = mitigationIcon(m);
|
||||||
if (!iconSrc) return '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${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 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 '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
const k = m.key ?? m.name;
|
const k = m.key ?? m.name;
|
||||||
@ -786,6 +808,7 @@
|
|||||||
setupPhases(window.App?.phases ?? []);
|
setupPhases(window.App?.phases ?? []);
|
||||||
renderPlayers(json.players ?? []);
|
renderPlayers(json.players ?? []);
|
||||||
mitigationNames = json.mitigation_names ?? {};
|
mitigationNames = json.mitigation_names ?? {};
|
||||||
|
await ensureActionIconCache();
|
||||||
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
renderTimeline(json.aoe_events ?? [], json.fight_start ?? fightStart);
|
||||||
|
|
||||||
document.getElementById('analysis-loading').style.display = 'none';
|
document.getElementById('analysis-loading').style.display = 'none';
|
||||||
|
|||||||
@ -29,25 +29,51 @@
|
|||||||
'PLD': [
|
'PLD': [
|
||||||
{ name: 'Passage of Arms', buffType: 'buff' },
|
{ name: 'Passage of Arms', buffType: 'buff' },
|
||||||
{ name: 'Divine Veil', buffType: 'shield' },
|
{ 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' },
|
{ name: 'Reprisal', buffType: 'debuff' },
|
||||||
],
|
],
|
||||||
'WAR': [
|
'WAR': [
|
||||||
{ name: 'Shake It Off', buffType: 'shield' },
|
{ 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' },
|
{ name: 'Reprisal', buffType: 'debuff' },
|
||||||
],
|
],
|
||||||
'DRK': [
|
'DRK': [
|
||||||
{ name: 'Dark Missionary', buffType: 'buff' },
|
{ 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' },
|
{ name: 'Reprisal', buffType: 'debuff' },
|
||||||
],
|
],
|
||||||
'GNB': [
|
'GNB': [
|
||||||
{ name: 'Heart of Light', buffType: 'buff' },
|
{ 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' },
|
{ name: 'Reprisal', buffType: 'debuff' },
|
||||||
],
|
],
|
||||||
'WHM': [
|
'WHM': [
|
||||||
{ name: 'Temperance', buffType: 'buff' },
|
{ name: 'Temperance', buffType: 'buff' },
|
||||||
{ name: 'Divine Benison', buffType: 'shield' },
|
{ name: 'Divine Benison', buffType: 'shield', extraAbilityGameID: 7432, duration: 15 },
|
||||||
{ name: 'Divine Caress', buffType: 'shield' },
|
{ name: 'Divine Caress', buffType: 'shield' },
|
||||||
],
|
],
|
||||||
'SCH': [
|
'SCH': [
|
||||||
@ -55,14 +81,14 @@
|
|||||||
{ name: 'Expedient', buffType: 'buff' },
|
{ name: 'Expedient', buffType: 'buff' },
|
||||||
{ name: 'Fey Illumination', buffType: 'buff' },
|
{ name: 'Fey Illumination', buffType: 'buff' },
|
||||||
{ name: 'Galvanize', buffType: 'shield' },
|
{ name: 'Galvanize', buffType: 'shield' },
|
||||||
{ name: 'Seraphic Veil', buffType: 'shield' },
|
{ name: 'Seraphic Veil', buffType: 'shield', extraAbilityGameID: 16548, duration: 30 },
|
||||||
{ name: 'Catalyze', buffType: 'shield' },
|
{ name: 'Catalyze', buffType: 'shield' },
|
||||||
],
|
],
|
||||||
'AST': [
|
'AST': [
|
||||||
{ name: 'Collective Unconscious', buffType: 'buff' },
|
{ name: 'Collective Unconscious', buffType: 'buff' },
|
||||||
{ name: 'Neutral Sect', buffType: 'shield' },
|
{ name: 'Neutral Sect', buffType: 'shield' },
|
||||||
{ name: 'Intersection', buffType: 'shield' },
|
{ name: 'Intersection', buffType: 'shield', extraAbilityGameID: 16556, duration: 30 },
|
||||||
{ name: 'the Spire', buffType: 'shield' },
|
{ name: 'the Spire', buffType: 'shield', extraAbilityGameID: 37025, duration: 30 },
|
||||||
],
|
],
|
||||||
'SGE': [
|
'SGE': [
|
||||||
{ name: 'Kerachole', buffType: 'buff' },
|
{ name: 'Kerachole', buffType: 'buff' },
|
||||||
@ -71,9 +97,9 @@
|
|||||||
{ name: 'Panhaima', buffType: 'shield' },
|
{ name: 'Panhaima', buffType: 'shield' },
|
||||||
{ name: 'Eukrasian Prognosis', buffType: 'shield' },
|
{ name: 'Eukrasian Prognosis', buffType: 'shield' },
|
||||||
{ name: 'Eukrasian Prognosis II', buffType: 'shield' },
|
{ name: 'Eukrasian Prognosis II', buffType: 'shield' },
|
||||||
{ name: 'Eukrasian Diagnosis', buffType: 'shield' },
|
{ name: 'Eukrasian Diagnosis', buffType: 'shield', extraAbilityGameID: 24291, duration: 30 },
|
||||||
{ name: 'Differential Diagnosis', buffType: 'shield' },
|
{ name: 'Differential Diagnosis', buffType: 'shield', extraAbilityGameID: 24291, duration: 30 },
|
||||||
{ name: 'Haima', buffType: 'shield' },
|
{ name: 'Haima', buffType: 'shield', extraAbilityGameID: 24305, duration: 15 },
|
||||||
],
|
],
|
||||||
'BRD': [{ name: 'Troubadour', buffType: 'buff' }],
|
'BRD': [{ name: 'Troubadour', buffType: 'buff' }],
|
||||||
'MCH': [{ name: 'Tactician', buffType: 'buff' }],
|
'MCH': [{ name: 'Tactician', buffType: 'buff' }],
|
||||||
@ -81,16 +107,31 @@
|
|||||||
{ name: 'Shield Samba', buffType: 'buff' },
|
{ name: 'Shield Samba', buffType: 'buff' },
|
||||||
{ name: 'Improvised Finish', buffType: 'shield' },
|
{ 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' }],
|
'DRG': [{ name: 'Feint', buffType: 'debuff' }],
|
||||||
'NIN': [{ name: 'Feint', buffType: 'debuff' }],
|
'NIN': [
|
||||||
'SAM': [{ name: 'Feint', buffType: 'debuff' }],
|
{ name: 'Shade Shift', buffType: 'shield', extraAbilityGameID: 2241, duration: 20 },
|
||||||
'RPR': [{ name: 'Feint', buffType: 'debuff' }],
|
{ 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' }],
|
'VPR': [{ name: 'Feint', buffType: 'debuff' }],
|
||||||
'BLM': [{ name: 'Addle', buffType: 'debuff' }],
|
'BLM': [
|
||||||
|
{ name: 'Manaward', buffType: 'shield', extraAbilityGameID: 157, duration: 20 },
|
||||||
|
{ name: 'Addle', buffType: 'debuff' },
|
||||||
|
],
|
||||||
'SMN': [
|
'SMN': [
|
||||||
{ name: 'Addle', buffType: 'debuff' },
|
{ name: 'Addle', buffType: 'debuff' },
|
||||||
{ name: 'Radiant Aegis', buffType: 'shield' },
|
{ name: 'Radiant Aegis', buffType: 'shield', extraAbilityGameID: 25799, duration: 30 },
|
||||||
],
|
],
|
||||||
'RDM': [
|
'RDM': [
|
||||||
{ name: 'Addle', buffType: 'debuff' },
|
{ name: 'Addle', buffType: 'debuff' },
|
||||||
@ -98,16 +139,24 @@
|
|||||||
],
|
],
|
||||||
'PCT': [
|
'PCT': [
|
||||||
{ name: 'Addle', buffType: 'debuff' },
|
{ name: 'Addle', buffType: 'debuff' },
|
||||||
{ name: 'Tempera Coat', buffType: 'shield' },
|
{ name: 'Tempera Coat', buffType: 'shield', extraAbilityGameID: 34685, duration: 10 },
|
||||||
{ name: 'Tempera Grassa', buffType: 'shield' },
|
{ name: 'Tempera Grassa', buffType: 'shield' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ABILITY_JOB_MAP = {
|
const ABILITY_JOB_MAP = {
|
||||||
'Passage of Arms': 'PLD', 'Divine Veil': 'PLD', 'Guardian': 'PLD',
|
'Passage of Arms': 'PLD', 'Divine Veil': 'PLD',
|
||||||
'Shake It Off': 'WAR', 'Bloodwhetting': 'WAR',
|
'Hallowed Ground': 'PLD', 'Sentinel': 'PLD', 'Guardian': 'PLD',
|
||||||
'Dark Missionary': 'DRK',
|
'Bulwark': 'PLD', 'Holy Sheltron': 'PLD', 'Intervention': 'PLD',
|
||||||
'Heart of Light': 'GNB',
|
'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',
|
'Temperance': 'WHM', 'Divine Benison': 'WHM', 'Divine Caress': 'WHM',
|
||||||
'Sacred Soil': 'SCH', 'Expedient': 'SCH', 'Fey Illumination': 'SCH',
|
'Sacred Soil': 'SCH', 'Expedient': 'SCH', 'Fey Illumination': 'SCH',
|
||||||
'Galvanize': 'SCH', 'Seraphic Veil': 'SCH', 'Catalyze': 'SCH',
|
'Galvanize': 'SCH', 'Seraphic Veil': 'SCH', 'Catalyze': 'SCH',
|
||||||
@ -120,6 +169,11 @@
|
|||||||
'Troubadour': 'BRD',
|
'Troubadour': 'BRD',
|
||||||
'Tactician': 'MCH',
|
'Tactician': 'MCH',
|
||||||
'Shield Samba': 'DNC', 'Improvised Finish': 'DNC',
|
'Shield Samba': 'DNC', 'Improvised Finish': 'DNC',
|
||||||
|
'Riddle of Earth': 'MNK',
|
||||||
|
'Shade Shift': 'NIN',
|
||||||
|
'Third Eye': 'SAM',
|
||||||
|
'Arcane Crest': 'RPR',
|
||||||
|
'Manaward': 'BLM',
|
||||||
'Radiant Aegis': 'SMN',
|
'Radiant Aegis': 'SMN',
|
||||||
'Magick Barrier': 'RDM',
|
'Magick Barrier': 'RDM',
|
||||||
'Tempera Coat': 'PCT', 'Tempera Grassa': 'PCT',
|
'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) {
|
function assignmentAbilityName(assignment, plan = null) {
|
||||||
const key = assignment?.ability ?? '';
|
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() {
|
function plannerLanguage() {
|
||||||
return window.App?.language || localStorage.getItem('ff14-mitigator-language') || 'en';
|
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() {
|
async function ensureActionMetaLoaded() {
|
||||||
if (actionMetaPromise) return actionMetaPromise;
|
if (actionMetaPromise) return actionMetaPromise;
|
||||||
actionMetaPromise = (async () => {
|
actionMetaPromise = (async () => {
|
||||||
@ -156,6 +172,13 @@ async function ensureActionMetaLoaded() {
|
|||||||
const meta = {
|
const meta = {
|
||||||
id,
|
id,
|
||||||
name: action.Name_en ?? '',
|
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,
|
cast: (parseInt(compact?.[id]?.cast ?? action.Cast100ms ?? 0, 10) || 0) / 10,
|
||||||
recast: (parseInt(compact?.[id]?.recast ?? action.Recast100ms ?? 0, 10) || 0) / 10,
|
recast: (parseInt(compact?.[id]?.recast ?? action.Recast100ms ?? 0, 10) || 0) / 10,
|
||||||
duration: durations.find(Number.isFinite) ?? 15,
|
duration: durations.find(Number.isFinite) ?? 15,
|
||||||
@ -164,11 +187,16 @@ async function ensureActionMetaLoaded() {
|
|||||||
if (meta.name) byName[meta.name] = meta;
|
if (meta.name) byName[meta.name] = meta;
|
||||||
}
|
}
|
||||||
for (const [id, action] of Object.entries(compact ?? {})) {
|
for (const [id, action] of Object.entries(compact ?? {})) {
|
||||||
|
const names = action.names ?? {};
|
||||||
byId[id] = {
|
byId[id] = {
|
||||||
...(byId[id] ?? { 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,
|
cast: (parseInt(action.cast ?? 0, 10) || 0) / 10,
|
||||||
recast: (parseInt(action.recast ?? 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;
|
actionMetaById = byId;
|
||||||
actionMetaByName = byName;
|
actionMetaByName = byName;
|
||||||
@ -434,7 +462,7 @@ function renderPlanDetail(plan) {
|
|||||||
initTimeline(plan.id);
|
initTimeline(plan.id);
|
||||||
initMechanicClicks(plan.id);
|
initMechanicClicks(plan.id);
|
||||||
renderInfoPanel(plan);
|
renderInfoPanel(plan);
|
||||||
ensureActionMetaLoaded().then(() => refreshTimeline(plan.id));
|
ensureActionMetaLoaded().then(() => refreshMechanicList(plan.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function avgNonTankMaxHp(plan) {
|
function avgNonTankMaxHp(plan) {
|
||||||
@ -502,7 +530,7 @@ function renderMechanicListHtml(plan) {
|
|||||||
: a.buffType === 'shield' ? 'badge-assign-shield'
|
: a.buffType === 'shield' ? 'badge-assign-shield'
|
||||||
: 'badge-assign-buff';
|
: 'badge-assign-buff';
|
||||||
const isMissing = !!a.job && !activeJobSet.has(a.job);
|
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 ability = assignmentAbilityName(a, plan);
|
||||||
const label = a.job ? `${escHtml(a.job)} · ${escHtml(ability)}` : escHtml(ability);
|
const label = a.job ? `${escHtml(a.job)} · ${escHtml(ability)}` : escHtml(ability);
|
||||||
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
const title = isMissing ? `${escHtml(a.job)} nicht in Jobaufstellung` : '';
|
||||||
@ -641,8 +669,31 @@ const JOB_GANTT_ORDER = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TIMELINE_PERSONAL_ABILITIES = new Set([
|
const TIMELINE_PERSONAL_ABILITIES = new Set([
|
||||||
|
'Rampart',
|
||||||
|
'Hallowed Ground',
|
||||||
|
'Sentinel',
|
||||||
'Guardian',
|
'Guardian',
|
||||||
|
'Bulwark',
|
||||||
|
'Holy Sheltron',
|
||||||
|
'Intervention',
|
||||||
|
'Holmgang',
|
||||||
|
'Vengeance',
|
||||||
|
'Damnation',
|
||||||
|
'Thrill of Battle',
|
||||||
|
'Raw Intuition',
|
||||||
'Bloodwhetting',
|
'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',
|
'Divine Benison',
|
||||||
'Intersection',
|
'Intersection',
|
||||||
'the Spire',
|
'the Spire',
|
||||||
@ -652,6 +703,11 @@ const TIMELINE_PERSONAL_ABILITIES = new Set([
|
|||||||
'Seraphic Veil',
|
'Seraphic Veil',
|
||||||
'Radiant Aegis',
|
'Radiant Aegis',
|
||||||
'Tempera Coat',
|
'Tempera Coat',
|
||||||
|
'Riddle of Earth',
|
||||||
|
'Shade Shift',
|
||||||
|
'Third Eye',
|
||||||
|
'Arcane Crest',
|
||||||
|
'Manaward',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function timelineOptions(plan) {
|
function timelineOptions(plan) {
|
||||||
@ -664,7 +720,7 @@ function timelineOptions(plan) {
|
|||||||
function timelineAbilityVisible(ability, options) {
|
function timelineAbilityVisible(ability, options) {
|
||||||
const isPersonal = TIMELINE_PERSONAL_ABILITIES.has(ability.name);
|
const isPersonal = TIMELINE_PERSONAL_ABILITIES.has(ability.name);
|
||||||
const isShield = ability.buffType === 'shield';
|
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;
|
if (isShield && !options.includeShields && ability.name !== 'Panhaima') return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -700,6 +756,18 @@ function jobCanUseAbility(job, ability) {
|
|||||||
return (JOB_ABILITIES[job] ?? []).some(a => a.name === 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) {
|
function timelineRowsForAssignment(rows, assignment) {
|
||||||
const assignedJob = assignment.job ?? '';
|
const assignedJob = assignment.job ?? '';
|
||||||
return rows.filter(row => {
|
return rows.filter(row => {
|
||||||
@ -949,8 +1017,17 @@ function assignmentOverlapsJob(plan, job, ability, timestamp, ignore = null, can
|
|||||||
}
|
}
|
||||||
|
|
||||||
function actionMetaForAssignment(assignment) {
|
function actionMetaForAssignment(assignment) {
|
||||||
const id = String(assignment?.actionId ?? assignment?.extraAbilityGameID ?? '');
|
const def = abilityDefinition(assignment?.ability, assignment?.job ?? '');
|
||||||
return (id && actionMetaById[id]) || actionMetaByName[assignment?.ability] || null;
|
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) {
|
function assignmentCooldownSeconds(assignment) {
|
||||||
@ -1020,7 +1097,7 @@ function renderTimelineHtml(plan) {
|
|||||||
const cls = a.buffType === 'debuff' ? 'timeline-mitigation--debuff'
|
const cls = a.buffType === 'debuff' ? 'timeline-mitigation--debuff'
|
||||||
: a.buffType === 'shield' ? 'timeline-mitigation--shield'
|
: a.buffType === 'shield' ? 'timeline-mitigation--shield'
|
||||||
: 'timeline-mitigation--buff';
|
: 'timeline-mitigation--buff';
|
||||||
const icon = MITIG_ICONS[a.ability] ?? '';
|
const icon = abilityIcon(a.ability);
|
||||||
const abilityLabel = assignmentAbilityName(a, plan);
|
const abilityLabel = assignmentAbilityName(a, plan);
|
||||||
blocks.push(`
|
blocks.push(`
|
||||||
<button class="timeline-mitigation ${cls}${selected}${unresolved}"
|
<button class="timeline-mitigation ${cls}${selected}${unresolved}"
|
||||||
@ -1036,8 +1113,8 @@ function renderTimelineHtml(plan) {
|
|||||||
</button>
|
</button>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
const icon = MITIG_ICONS[row.ability] ?? '';
|
const icon = abilityIcon(row.ability);
|
||||||
const abilityDisplayName = plan.mitigationNames?.[row.ability] ?? row.ability;
|
const abilityDisplayName = localizedAbilityName(row.ability, plan);
|
||||||
const jobStartCls = row.firstForJob ? ' timeline-player-row--job-start' : '';
|
const jobStartCls = row.firstForJob ? ' timeline-player-row--job-start' : '';
|
||||||
return `
|
return `
|
||||||
<div class="timeline-row timeline-player-row${jobStartCls}" data-row-idx="${row.idx}" data-job="${escHtml(row.job)}" data-ability="${escHtml(row.ability)}">
|
<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;
|
if (!plan || !jobCanUseAbility(job, ability)) return;
|
||||||
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
const mechanic = plan.mechanics.find(m => m.id === mechanicId);
|
||||||
if (!mechanic) return;
|
if (!mechanic) return;
|
||||||
|
const def = abilityDefinition(ability, job);
|
||||||
mechanic.assignments = mechanic.assignments ?? [];
|
mechanic.assignments = mechanic.assignments ?? [];
|
||||||
const assignment = {
|
const assignment = {
|
||||||
ability,
|
ability,
|
||||||
abilityName: plan.mitigationNames?.[ability],
|
abilityName: localizedAbilityName(ability, plan),
|
||||||
actionId: actionMetaByName[ability]?.id ?? null,
|
actionId: def?.extraAbilityGameID ?? actionMetaByName[ability]?.id ?? null,
|
||||||
job,
|
job,
|
||||||
buffType,
|
buffType,
|
||||||
timestamp: Math.max(0, Math.round(timestamp)),
|
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)
|
.filter((row, idx, arr) => arr.findIndex(r => r.job === row.job) === idx)
|
||||||
.map(row => ({
|
.map(row => ({
|
||||||
label: `${row.job}${row.name ? ` · ${row.name}` : ''}`,
|
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, {
|
disabled: assignmentOverlapsJob(plan, row.job, block.dataset.ability, timestamp, {
|
||||||
mechanicId: block.dataset.mechanicId,
|
mechanicId: block.dataset.mechanicId,
|
||||||
ability: block.dataset.ability,
|
ability: block.dataset.ability,
|
||||||
@ -1641,7 +1719,7 @@ function initTimeline(planId) {
|
|||||||
const ab = (JOB_ABILITIES[rowJob] ?? []).find(a => a.name === rowAbility);
|
const ab = (JOB_ABILITIES[rowJob] ?? []).find(a => a.name === rowAbility);
|
||||||
const candidate = ab ? {
|
const candidate = ab ? {
|
||||||
ability: rowAbility,
|
ability: rowAbility,
|
||||||
actionId: actionMetaByName[rowAbility]?.id ?? null,
|
actionId: ab.extraAbilityGameID ?? actionMetaByName[rowAbility]?.id ?? null,
|
||||||
job: rowJob,
|
job: rowJob,
|
||||||
buffType: ab.buffType,
|
buffType: ab.buffType,
|
||||||
} : null;
|
} : null;
|
||||||
@ -2381,9 +2459,9 @@ function renderAbilityModalContent() {
|
|||||||
const activeClass = isActive ? ' ability-chip--active' : '';
|
const activeClass = isActive ? ' ability-chip--active' : '';
|
||||||
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
const otherClass = byOtherJob ? ' ability-chip--other-job' : '';
|
||||||
const title = byOtherJob ? `Bereits von ${escHtml(assigned.job)} zugewiesen` : '';
|
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 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}"
|
return `<button class="ability-chip ${cls}${activeClass}${otherClass}"
|
||||||
data-ability="${escHtml(ab.name)}"
|
data-ability="${escHtml(ab.name)}"
|
||||||
data-job="${escHtml(job)}"
|
data-job="${escHtml(job)}"
|
||||||
@ -2431,10 +2509,11 @@ function toggleAbilityAssignment(abilityName, job, buffType) {
|
|||||||
assignment.job = job;
|
assignment.job = job;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const def = abilityDefinition(abilityName, job);
|
||||||
const assignment = {
|
const assignment = {
|
||||||
ability: abilityName,
|
ability: abilityName,
|
||||||
abilityName: plan.mitigationNames?.[abilityName],
|
abilityName: localizedAbilityName(abilityName, plan),
|
||||||
actionId: actionMetaByName[abilityName]?.id ?? null,
|
actionId: def?.extraAbilityGameID ?? actionMetaByName[abilityName]?.id ?? null,
|
||||||
job,
|
job,
|
||||||
buffType,
|
buffType,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -289,6 +289,41 @@ function action_field(array $action, string $field): ?int
|
|||||||
return null;
|
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);
|
$plannerAbilityNames = read_planner_ability_names($plannerDataSource);
|
||||||
$actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames);
|
$actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames);
|
||||||
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
$wanted = array_fill_keys(array_map('strval', $actionIds), true);
|
||||||
@ -311,6 +346,13 @@ foreach ($wanted as $id => $_) {
|
|||||||
$filtered[$id] = [
|
$filtered[$id] = [
|
||||||
'cast' => action_field($action, 'Cast100ms'),
|
'cast' => action_field($action, 'Cast100ms'),
|
||||||
'recast' => action_field($action, 'Recast100ms'),
|
'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