diff --git a/assets/jsons/Action.json b/assets/jsons/Action.json index c60ca89..3da9e33 100644 --- a/assets/jsons/Action.json +++ b/assets/jsons/Action.json @@ -8,7 +8,8 @@ "fr": "Sentinelle", "jp": "センチネル" }, - "icon": "https://xivapi.com/i/000000/000151_hr1.png" + "icon": "https://xivapi.com/i/000000/000151_hr1.png", + "shield": null }, "22": { "cast": 0, @@ -19,7 +20,8 @@ "fr": "Forteresse", "jp": "ブルワーク" }, - "icon": "https://xivapi.com/i/000000/000167_hr1.png" + "icon": "https://xivapi.com/i/000000/000167_hr1.png", + "shield": null }, "30": { "cast": 0, @@ -30,7 +32,8 @@ "fr": "Invincible", "jp": "インビンシブル" }, - "icon": "https://xivapi.com/i/002000/002502_hr1.png" + "icon": "https://xivapi.com/i/002000/002502_hr1.png", + "shield": null }, "40": { "cast": 0, @@ -41,7 +44,8 @@ "fr": "Frisson de la bataille", "jp": "スリル・オブ・バトル" }, - "icon": "https://xivapi.com/i/000000/000263_hr1.png" + "icon": "https://xivapi.com/i/000000/000263_hr1.png", + "shield": null }, "43": { "cast": 0, @@ -52,7 +56,8 @@ "fr": "Holmgang", "jp": "ホルムギャング" }, - "icon": "https://xivapi.com/i/000000/000266_hr1.png" + "icon": "https://xivapi.com/i/000000/000266_hr1.png", + "shield": null }, "44": { "cast": 0, @@ -63,7 +68,8 @@ "fr": "Représailles", "jp": "ヴェンジェンス" }, - "icon": "https://xivapi.com/i/000000/000267_hr1.png" + "icon": "https://xivapi.com/i/000000/000267_hr1.png", + "shield": null }, "157": { "cast": 0, @@ -74,7 +80,8 @@ "fr": "Barrière de mana", "jp": "マバリア" }, - "icon": "https://xivapi.com/i/000000/000463_hr1.png" + "icon": "https://xivapi.com/i/000000/000463_hr1.png", + "shield": "30% max HP" }, "185": { "cast": 20, @@ -85,7 +92,8 @@ "fr": "Traité du réconfort", "jp": "鼓舞激励の策" }, - "icon": "https://xivapi.com/i/002000/002801_hr1.png" + "icon": "https://xivapi.com/i/002000/002801_hr1.png", + "shield": null }, "188": { "cast": 0, @@ -96,7 +104,8 @@ "fr": "Dogme de survie", "jp": "野戦治療の陣" }, - "icon": "https://xivapi.com/i/002000/002804_hr1.png" + "icon": "https://xivapi.com/i/002000/002804_hr1.png", + "shield": null }, "2241": { "cast": 0, @@ -107,7 +116,8 @@ "fr": "Décalage d'ombre", "jp": "残影" }, - "icon": "https://xivapi.com/i/000000/000607_hr1.png" + "icon": "https://xivapi.com/i/000000/000607_hr1.png", + "shield": "20% max HP" }, "3540": { "cast": 0, @@ -118,7 +128,8 @@ "fr": "Voile divin", "jp": "ディヴァインヴェール" }, - "icon": "https://xivapi.com/i/002000/002508_hr1.png" + "icon": "https://xivapi.com/i/002000/002508_hr1.png", + "shield": "10% max HP" }, "3551": { "cast": 0, @@ -129,7 +140,8 @@ "fr": "Intuition pure", "jp": "原初の直感" }, - "icon": "https://xivapi.com/i/002000/002559_hr1.png" + "icon": "https://xivapi.com/i/002000/002559_hr1.png", + "shield": null }, "3613": { "cast": 0, @@ -140,7 +152,8 @@ "fr": "Inconscient collectif", "jp": "運命の輪" }, - "icon": "https://xivapi.com/i/003000/003140_hr1.png" + "icon": "https://xivapi.com/i/003000/003140_hr1.png", + "shield": null }, "3634": { "cast": 0, @@ -151,7 +164,8 @@ "fr": "Esprit ténébreux", "jp": "ダークマインド" }, - "icon": "https://xivapi.com/i/003000/003076_hr1.png" + "icon": "https://xivapi.com/i/003000/003076_hr1.png", + "shield": null }, "3636": { "cast": 0, @@ -162,7 +176,8 @@ "fr": "Mur d'ombre", "jp": "シャドウウォール" }, - "icon": "https://xivapi.com/i/003000/003075_hr1.png" + "icon": "https://xivapi.com/i/003000/003075_hr1.png", + "shield": null }, "3638": { "cast": 0, @@ -173,7 +188,8 @@ "fr": "Mort-vivant", "jp": "リビングデッド" }, - "icon": "https://xivapi.com/i/003000/003077_hr1.png" + "icon": "https://xivapi.com/i/003000/003077_hr1.png", + "shield": null }, "7382": { "cast": 0, @@ -184,7 +200,8 @@ "fr": "Intervention", "jp": "インターベンション" }, - "icon": "https://xivapi.com/i/002000/002512_hr1.png" + "icon": "https://xivapi.com/i/002000/002512_hr1.png", + "shield": null }, "7385": { "cast": 0, @@ -195,7 +212,8 @@ "fr": "Passe d'armes", "jp": "パッセージ・オブ・アームズ" }, - "icon": "https://xivapi.com/i/002000/002515_hr1.png" + "icon": "https://xivapi.com/i/002000/002515_hr1.png", + "shield": null }, "7388": { "cast": 0, @@ -206,7 +224,8 @@ "fr": "Débarrassage", "jp": "シェイクオフ" }, - "icon": "https://xivapi.com/i/002000/002563_hr1.png" + "icon": "https://xivapi.com/i/002000/002563_hr1.png", + "shield": "15% max HP" }, "7393": { "cast": 0, @@ -217,7 +236,8 @@ "fr": "Nuit noirissime", "jp": "ブラックナイト" }, - "icon": "https://xivapi.com/i/003000/003081_hr1.png" + "icon": "https://xivapi.com/i/003000/003081_hr1.png", + "shield": "25% max HP" }, "7394": { "cast": 0, @@ -228,7 +248,8 @@ "fr": "Énigme de la terre", "jp": "金剛の極意" }, - "icon": "https://xivapi.com/i/002000/002537_hr1.png" + "icon": "https://xivapi.com/i/002000/002537_hr1.png", + "shield": null }, "7405": { "cast": 0, @@ -239,7 +260,8 @@ "fr": "Troubadour", "jp": "トルバドゥール" }, - "icon": "https://xivapi.com/i/002000/002612_hr1.png" + "icon": "https://xivapi.com/i/002000/002612_hr1.png", + "shield": null }, "7432": { "cast": 0, @@ -250,7 +272,8 @@ "fr": "Faveur divine", "jp": "ディヴァインベニゾン" }, - "icon": "https://xivapi.com/i/002000/002638_hr1.png" + "icon": "https://xivapi.com/i/002000/002638_hr1.png", + "shield": "500 potency" }, "7498": { "cast": 0, @@ -261,7 +284,8 @@ "fr": "Troisième œil", "jp": "心眼" }, - "icon": "https://xivapi.com/i/003000/003153_hr1.png" + "icon": "https://xivapi.com/i/003000/003153_hr1.png", + "shield": null }, "7531": { "cast": 0, @@ -272,7 +296,8 @@ "fr": "Rempart", "jp": "ランパート" }, - "icon": "https://xivapi.com/i/000000/000801_hr1.png" + "icon": "https://xivapi.com/i/000000/000801_hr1.png", + "shield": null }, "7535": { "cast": 0, @@ -283,7 +308,8 @@ "fr": "Rétorsion", "jp": "リプライザル" }, - "icon": "https://xivapi.com/i/000000/000806_hr1.png" + "icon": "https://xivapi.com/i/000000/000806_hr1.png", + "shield": null }, "7549": { "cast": 0, @@ -294,7 +320,8 @@ "fr": "Restreinte", "jp": "牽制" }, - "icon": "https://xivapi.com/i/000000/000828_hr1.png" + "icon": "https://xivapi.com/i/000000/000828_hr1.png", + "shield": null }, "7560": { "cast": 0, @@ -305,7 +332,8 @@ "fr": "Embrouillement", "jp": "アドル" }, - "icon": "https://xivapi.com/i/000000/000861_hr1.png" + "icon": "https://xivapi.com/i/000000/000861_hr1.png", + "shield": null }, "16012": { "cast": 0, @@ -316,7 +344,8 @@ "fr": "Samba protectrice", "jp": "守りのサンバ" }, - "icon": "https://xivapi.com/i/003000/003469_hr1.png" + "icon": "https://xivapi.com/i/003000/003469_hr1.png", + "shield": null }, "16140": { "cast": 0, @@ -327,7 +356,8 @@ "fr": "Camouflage", "jp": "カモフラージュ" }, - "icon": "https://xivapi.com/i/003000/003404_hr1.png" + "icon": "https://xivapi.com/i/003000/003404_hr1.png", + "shield": null }, "16148": { "cast": 0, @@ -338,7 +368,8 @@ "fr": "Nébuleuse", "jp": "ネビュラ" }, - "icon": "https://xivapi.com/i/003000/003412_hr1.png" + "icon": "https://xivapi.com/i/003000/003412_hr1.png", + "shield": null }, "16152": { "cast": 0, @@ -349,7 +380,8 @@ "fr": "Bolide", "jp": "ボーライド" }, - "icon": "https://xivapi.com/i/003000/003416_hr1.png" + "icon": "https://xivapi.com/i/003000/003416_hr1.png", + "shield": null }, "16160": { "cast": 0, @@ -360,7 +392,8 @@ "fr": "Cœur de Lumière", "jp": "ハート・オブ・ライト" }, - "icon": "https://xivapi.com/i/003000/003424_hr1.png" + "icon": "https://xivapi.com/i/003000/003424_hr1.png", + "shield": null }, "16161": { "cast": 0, @@ -371,7 +404,8 @@ "fr": "Cœur de pierre", "jp": "ハート・オブ・ストーン" }, - "icon": "https://xivapi.com/i/003000/003425_hr1.png" + "icon": "https://xivapi.com/i/003000/003425_hr1.png", + "shield": null }, "16471": { "cast": 0, @@ -382,7 +416,8 @@ "fr": "Missionnaire des Ténèbres", "jp": "ダークミッショナリー" }, - "icon": "https://xivapi.com/i/003000/003087_hr1.png" + "icon": "https://xivapi.com/i/003000/003087_hr1.png", + "shield": null }, "16536": { "cast": 0, @@ -393,7 +428,8 @@ "fr": "Tempérance", "jp": "テンパランス" }, - "icon": "https://xivapi.com/i/002000/002645_hr1.png" + "icon": "https://xivapi.com/i/002000/002645_hr1.png", + "shield": null }, "16538": { "cast": 0, @@ -404,7 +440,8 @@ "fr": "Illumination féérique", "jp": "フェイイルミネーション" }, - "icon": "https://xivapi.com/i/002000/002853_hr1.png" + "icon": "https://xivapi.com/i/002000/002853_hr1.png", + "shield": null }, "16548": { "cast": 0, @@ -415,7 +452,8 @@ "fr": "Voile séraphique", "jp": "セラフィックヴェール" }, - "icon": "https://xivapi.com/i/002000/002847_hr1.png" + "icon": "https://xivapi.com/i/002000/002847_hr1.png", + "shield": null }, "16556": { "cast": 0, @@ -426,7 +464,8 @@ "fr": "Rencontre céleste", "jp": "星天交差" }, - "icon": "https://xivapi.com/i/003000/003556_hr1.png" + "icon": "https://xivapi.com/i/003000/003556_hr1.png", + "shield": "200% of HP restored" }, "16559": { "cast": 0, @@ -437,7 +476,8 @@ "fr": "Adepte de la neutralité", "jp": "ニュートラルセクト" }, - "icon": "https://xivapi.com/i/003000/003552_hr1.png" + "icon": "https://xivapi.com/i/003000/003552_hr1.png", + "shield": "250% of HP restored" }, "16889": { "cast": 0, @@ -448,7 +488,8 @@ "fr": "Tacticien", "jp": "タクティシャン" }, - "icon": "https://xivapi.com/i/003000/003040_hr1.png" + "icon": "https://xivapi.com/i/003000/003040_hr1.png", + "shield": null }, "24291": { "cast": 0, @@ -459,7 +500,8 @@ "fr": "Diagnosis eucrasique", "jp": "エウクラシア・ディアグノシス" }, - "icon": "https://xivapi.com/i/003000/003659_hr1.png" + "icon": "https://xivapi.com/i/003000/003659_hr1.png", + "shield": null }, "24292": { "cast": 0, @@ -470,7 +512,8 @@ "fr": "Prognosis eucrasique", "jp": "エウクラシア・プログノシス" }, - "icon": "https://xivapi.com/i/003000/003660_hr1.png" + "icon": "https://xivapi.com/i/003000/003660_hr1.png", + "shield": "320% of HP restored" }, "24298": { "cast": 0, @@ -481,7 +524,8 @@ "fr": "Kerachole", "jp": "ケーラコレ" }, - "icon": "https://xivapi.com/i/003000/003666_hr1.png" + "icon": "https://xivapi.com/i/003000/003666_hr1.png", + "shield": null }, "24305": { "cast": 0, @@ -492,7 +536,8 @@ "fr": "Haima", "jp": "ハイマ" }, - "icon": "https://xivapi.com/i/003000/003673_hr1.png" + "icon": "https://xivapi.com/i/003000/003673_hr1.png", + "shield": "300 potency" }, "24310": { "cast": 0, @@ -503,7 +548,8 @@ "fr": "Holos", "jp": "ホーリズム" }, - "icon": "https://xivapi.com/i/003000/003678_hr1.png" + "icon": "https://xivapi.com/i/003000/003678_hr1.png", + "shield": null }, "24311": { "cast": 0, @@ -514,7 +560,8 @@ "fr": "Panhaima", "jp": "パンハイマ" }, - "icon": "https://xivapi.com/i/003000/003679_hr1.png" + "icon": "https://xivapi.com/i/003000/003679_hr1.png", + "shield": "200 potency" }, "24404": { "cast": 0, @@ -525,7 +572,8 @@ "fr": "Blason arcanique", "jp": "アルケインクレスト" }, - "icon": "https://xivapi.com/i/003000/003632_hr1.png" + "icon": "https://xivapi.com/i/003000/003632_hr1.png", + "shield": "10% max HP" }, "25746": { "cast": 0, @@ -536,7 +584,8 @@ "fr": "Schiltron sacré", "jp": "ホーリーシェルトロン" }, - "icon": "https://xivapi.com/i/002000/002950_hr1.png" + "icon": "https://xivapi.com/i/002000/002950_hr1.png", + "shield": null }, "25751": { "cast": 0, @@ -547,7 +596,8 @@ "fr": "Intuition fougueuse", "jp": "原初の血気" }, - "icon": "https://xivapi.com/i/002000/002569_hr1.png" + "icon": "https://xivapi.com/i/002000/002569_hr1.png", + "shield": "400 potency" }, "25754": { "cast": 0, @@ -558,7 +608,8 @@ "fr": "Oblation", "jp": "オブレーション" }, - "icon": "https://xivapi.com/i/003000/003089_hr1.png" + "icon": "https://xivapi.com/i/003000/003089_hr1.png", + "shield": null }, "25758": { "cast": 0, @@ -569,7 +620,8 @@ "fr": "Cœur de corindon", "jp": "ハート・オブ・コランダム" }, - "icon": "https://xivapi.com/i/003000/003430_hr1.png" + "icon": "https://xivapi.com/i/003000/003430_hr1.png", + "shield": null }, "25789": { "cast": 0, @@ -580,7 +632,8 @@ "fr": "Final improvisé", "jp": "インプロビゼーション・フィニッシュ" }, - "icon": "https://xivapi.com/i/003000/003479_hr1.png" + "icon": "https://xivapi.com/i/003000/003479_hr1.png", + "shield": null }, "25799": { "cast": 0, @@ -591,7 +644,8 @@ "fr": "Égide rayonnante", "jp": "守りの光" }, - "icon": "https://xivapi.com/i/002000/002750_hr1.png" + "icon": "https://xivapi.com/i/002000/002750_hr1.png", + "shield": "20% max HP" }, "25857": { "cast": 0, @@ -602,7 +656,8 @@ "fr": "Barrière anti-magie", "jp": "バマジク" }, - "icon": "https://xivapi.com/i/003000/003237_hr1.png" + "icon": "https://xivapi.com/i/003000/003237_hr1.png", + "shield": null }, "25868": { "cast": 0, @@ -613,7 +668,8 @@ "fr": "Thèse fluidique", "jp": "疾風怒濤の計" }, - "icon": "https://xivapi.com/i/002000/002878_hr1.png" + "icon": "https://xivapi.com/i/002000/002878_hr1.png", + "shield": null }, "34685": { "cast": 0, @@ -624,7 +680,8 @@ "fr": "Enduit a tempera", "jp": "テンペラコート" }, - "icon": "https://xivapi.com/i/003000/003835_hr1.png" + "icon": "https://xivapi.com/i/003000/003835_hr1.png", + "shield": "20% max HP" }, "34686": { "cast": 0, @@ -635,7 +692,8 @@ "fr": "Tempera grassa", "jp": "テンペラグラッサ" }, - "icon": "https://xivapi.com/i/003000/003836_hr1.png" + "icon": "https://xivapi.com/i/003000/003836_hr1.png", + "shield": "10% max HP" }, "36920": { "cast": 0, @@ -646,7 +704,8 @@ "fr": "Garde extrême", "jp": "エクストリームガード" }, - "icon": "https://xivapi.com/i/002000/002524_hr1.png" + "icon": "https://xivapi.com/i/002000/002524_hr1.png", + "shield": null }, "36923": { "cast": 0, @@ -657,7 +716,8 @@ "fr": "Damnation", "jp": "ダムネーション" }, - "icon": "https://xivapi.com/i/002000/002573_hr1.png" + "icon": "https://xivapi.com/i/002000/002573_hr1.png", + "shield": null }, "36927": { "cast": 0, @@ -668,7 +728,8 @@ "fr": "Vigile ténébreux", "jp": "シャドウヴィジル" }, - "icon": "https://xivapi.com/i/003000/003094_hr1.png" + "icon": "https://xivapi.com/i/003000/003094_hr1.png", + "shield": null }, "36935": { "cast": 0, @@ -679,7 +740,8 @@ "fr": "Grande nébuleuse", "jp": "グレートネビュラ" }, - "icon": "https://xivapi.com/i/003000/003435_hr1.png" + "icon": "https://xivapi.com/i/003000/003435_hr1.png", + "shield": null }, "37011": { "cast": 0, @@ -690,7 +752,8 @@ "fr": "Caresse divine", "jp": "ディヴァインカレス" }, - "icon": "https://xivapi.com/i/002000/002128_hr1.png" + "icon": "https://xivapi.com/i/002000/002128_hr1.png", + "shield": "400 potency" }, "37025": { "cast": 0, @@ -701,7 +764,8 @@ "fr": "La Tour", "jp": "ビエルゴの塔" }, - "icon": "https://xivapi.com/i/003000/003115_hr1.png" + "icon": "https://xivapi.com/i/003000/003115_hr1.png", + "shield": "400 potency" }, "37034": { "cast": 0, @@ -712,6 +776,7 @@ "fr": "Prognosis eucrasique II", "jp": "エウクラシア・プログノシスII" }, - "icon": "https://xivapi.com/i/003000/003689_hr1.png" + "icon": "https://xivapi.com/i/003000/003689_hr1.png", + "shield": "360% of HP restored" } } diff --git a/js/planner.js b/js/planner.js index 03338db..320366a 100644 --- a/js/planner.js +++ b/js/planner.js @@ -150,6 +150,21 @@ function abilityIcon(ability) { return MITIG_ICONS[ability] ?? actionMetaByName[ability]?.icon ?? ''; } +function abilityShieldText(ability) { + return actionMetaByName[ability]?.shield ?? ''; +} + +function abilityEffectTooltip(ability, buffType, plan = null) { + const lines = [localizedAbilityName(ability, plan)]; + const dr = ABILITY_DR[ability] ?? 0; + const shield = abilityShieldText(ability); + if (dr > 0) lines.push(`Mitigation: ${Math.round(dr * 100)}%`); + if (shield) lines.push(`Shield: ${shield}`); + else if (buffType === 'shield') lines.push('Shield'); + if (dr <= 0 && !shield && buffType !== 'shield') lines.push('No direct damage reduction'); + return lines.join('\n'); +} + async function ensureActionMetaLoaded() { if (actionMetaPromise) return actionMetaPromise; actionMetaPromise = (async () => { @@ -179,6 +194,7 @@ async function ensureActionMetaLoaded() { jp: action.Name_ja ?? '', }, icon: compact?.[id]?.icon ?? null, + shield: compact?.[id]?.shield ?? '', 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, @@ -193,10 +209,11 @@ async function ensureActionMetaLoaded() { name: byId[id]?.name ?? names.en ?? '', names: { ...(byId[id]?.names ?? {}), ...names }, icon: byId[id]?.icon ?? action.icon ?? '', + shield: byId[id]?.shield ?? action.shield ?? '', 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]; + if (names.en) byName[names.en] = byId[id]; } actionMetaById = byId; actionMetaByName = byName; @@ -1115,12 +1132,13 @@ function renderTimelineHtml(plan) { } const icon = abilityIcon(row.ability); const abilityDisplayName = localizedAbilityName(row.ability, plan); + const abilityTooltip = abilityEffectTooltip(row.ability, row.buffType, plan); const jobStartCls = row.firstForJob ? ' timeline-player-row--job-start' : ''; return `
${escHtml(row.job)} - + ${icon ? `` : ''} ${escHtml(abilityDisplayName)} @@ -2462,11 +2480,12 @@ function renderAbilityModalContent() { const icon = abilityIcon(ab.name); const assignedName = assigned ? assignmentAbilityName(assigned, plan) : ''; const label = assignedName || localizedAbilityName(ab.name, plan); + const effectTitle = [title, abilityEffectTooltip(ab.name, ab.buffType, plan)].filter(Boolean).join('\n\n'); return ``; }).join(''); diff --git a/scripts/update_action_json.php b/scripts/update_action_json.php index fb47560..8c9a95a 100644 --- a/scripts/update_action_json.php +++ b/scripts/update_action_json.php @@ -324,6 +324,42 @@ function action_icon_url(array $action): ?string return 'https://xivapi.com/i/' . $matches[1] . '/' . $matches[2] . '.png'; } +function plain_action_text(?string $text): string +{ + if ($text === null || $text === '') { + return ''; + } + + $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + return trim((string)preg_replace('/\s+/u', ' ', $text)); +} + +function action_shield_text(array $action): ?string +{ + $description = plain_action_text(action_text_field($action, 'Description_en')); + if ($description === '' || !preg_match('/barrier|absorbs|absorbed|nullif(?:y|ies)/i', $description)) { + return null; + } + + if (preg_match('/(?:absorbs|absorb|nullifies|nullify)[^.]*?(?:totaling|up to)?\s*(\d+)%\s*(?:of\s*)?(?:your\s+|target\'s\s+)?maximum HP/i', $description, $m)) { + return $m[1] . '% max HP'; + } + + if (preg_match('/barrier[^.]*?(?:absorbs|absorb|nullifies|nullify)[^.]*?(\d+)%\s*(?:of\s*)?(?:your\s+|target\'s\s+)?maximum HP/i', $description, $m)) { + return $m[1] . '% max HP'; + } + + if (preg_match('/barrier[^.]*?(\d+)%\s+of\s+the\s+amount\s+of\s+HP\s+restored/i', $description, $m)) { + return $m[1] . '% of HP restored'; + } + + if (preg_match('/barrier[^.]*?(?:heal of|Cure Potency:)\s*(\d+)\s*potency/i', $description, $m)) { + return $m[1] . ' potency'; + } + + return null; +} + $plannerAbilityNames = read_planner_ability_names($plannerDataSource); $actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames); $wanted = array_fill_keys(array_map('strval', $actionIds), true); @@ -353,6 +389,7 @@ foreach ($wanted as $id => $_) { 'jp' => action_text_field($action, 'Name_ja'), ], 'icon' => action_icon_url($action), + 'shield' => action_shield_text($action), ]; }