From d977cff4e492ef765a77a420d2f067ee1bf2db1c Mon Sep 17 00:00:00 2001 From: Michal Tuszowski Date: Thu, 18 Sep 2025 00:09:29 +0200 Subject: [PATCH] Final final final --- wg-greyknights/module.json | 5 +- wg-greyknights/packs/gk-items.db | 2 +- wg-greyknights/scripts/ui-gkpanel.js | 201 +++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 wg-greyknights/scripts/ui-gkpanel.js diff --git a/wg-greyknights/module.json b/wg-greyknights/module.json index 0d2a015..3169b5f 100644 --- a/wg-greyknights/module.json +++ b/wg-greyknights/module.json @@ -1,7 +1,7 @@ { "id": "gk-greyknight", "title": "Grey Knights — Compendium (Wrath & Glory)", - "version": "0.6.4.6", + "version": "0.6.5.2", "compatibility": { "minimum": "11", "verified": "13" @@ -26,7 +26,8 @@ ], "esmodules": [ "scripts/auto-loadout.js", - "scripts/helpers.js" + "scripts/helpers.js", + "scripts/ui-gkpanel.js" ], "readme": "README.txt" } \ No newline at end of file diff --git a/wg-greyknights/packs/gk-items.db b/wg-greyknights/packs/gk-items.db index 1ee04d5..75e5667 100644 --- a/wg-greyknights/packs/gk-items.db +++ b/wg-greyknights/packs/gk-items.db @@ -10,8 +10,8 @@ {"name":"The Shrouding (Sanctic)","type":"psychicPower","img":"modules/wng-core/assets/icons/powers/power.webp","effects":[],"flags":{"core":{}},"system":{"dn":"7","cost":10,"activation":"action","duration":"Sustained","range":"Self or Ally within 15 m","multiTarget":false,"keywords":"PSYCHIC, SANCTIC","prerequisites":"PSYKER Keyword","description":"

Warp-wrought mist blurs the target. Attacks against the target suffer +1 DN (ranged) while Sustained; moving more than normal speed ends the effect at end of Turn. Potency: also impose +1 DN on melee attacks.

"},"_id":"a0b1c2d3e4f50619"} {"name":"Might of Titan (Sanctic)","type":"psychicPower","img":"modules/wng-core/assets/icons/powers/power.webp","effects":[],"flags":{"core":{}},"system":{"dn":"7","cost":15,"activation":"action","duration":"Sustained","range":"15 m","multiTarget":false,"keywords":"PSYCHIC, SANCTIC","prerequisites":"PSYKER Keyword","description":"

Infuse an ally with the adamant strength of Titan. The target gains +1 to Strength-based Tests and +1ED on melee attacks until the start of your next Turn; while Sustained, the bonuses persist. Potency: also +1 to Toughness Tests.

"},"_id":"a0b1c2d3e4f5061a"} {"name":"Brotherhood of Psykers","type":"ability","img":"modules/wng-core/assets/icons/abilities/adeptus-astartes.webp","effects":[],"flags":{"core":{}},"system":{"description":"

Brotherhood of Psykers. The Grey Knights fight as one mind, their souls warded by hexagrammic rites and the tutelage of Titan. This Ability is the Grey Knights counterpart to Chapter Cult.

","cost":0,"requirements":"GREY KNIGHTS"},"_id":"b1a2c3d4e5f60718"} +{"name":"PsyboltAmmunition","type":"ammo","img":"modules/wng-core/assets/icons/ammo/ammo.webp","system":{"quantity":1,"rarity":"unique","description":"UpgradeforBOLT/StormBolterweapons.Grants+1ED.AgainstDAEMON/PSYKER,attacksgainRending(1).Applymanuallytoattackresolvingrules;conditionaleffectsarenotautomated.","value":5,"keywords":"BOLT,SANCTIFIED","effect":"","traits":{"list":[{"name":"force","type":"add"},{"name":"blessed","type":"add","rating":"2"}]}},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1756061909914,"modifiedTime":1756061909914,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.iMlwOxHqV9PWIEz5","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"iMlwOxHqV9PWIEz5"} {"name":"Psycannon","type":"weapon","img":"modules/wng-core/assets/icons/weapons/autocannon.webp","system":{"quantity":1,"rarity":"unique","description":"Anti-daemonicboltweapon.VsDAEMON/PSYKER:+1ED.OnCritical:gainsRending(1)fortheresult.","value":9,"keywords":"BOLT,IMPERIUM,ADEPTUSASTARTES,GREYKNIGHTS,SANCTIFIED","equipped":false,"attack":{"base":0,"bonus":0,"rank":"none"},"damage":{"base":14,"bonus":0,"rank":0,"ed":{"base":3,"bonus":0,"rank":0,"dice":""},"ap":{"base":-2,"bonus":0,"rank":0,"dice":""},"enabled":false,"otherDamage":{"mortal":"0","wounds":"0","shock":"0"},"dice":""},"ed":{"base":0,"bonus":0,"rank":0},"ap":{"base":0,"bonus":0,"rank":0},"category":"ranged","range":{"short":20,"medium":40,"long":60,"melee":1,"thrown":null},"ammo":{"id":""},"salvo":2,"traits":{"list":[{"name":"heavy","rating":"7"},{"name":"rapidFire","rating":"2"}]},"upgrades":[],"otherDamage":{},"combi":{},"twinned":false},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755858910417,"modifiedTime":1756051080757,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.luQa0ffP6nroOeN8","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"luQa0ffP6nroOeN8"} {"name":"ORDO MALLEUS","type":"keyword","img":"modules/wng-core/assets/factions/the-inquisition.svg","effects":[{"_id":"xs6DzM62O4RjebzK","changes":[{"key":"system.combat.conviction.bonus","mode":2,"value":"1","priority":null},{"key":"system.corruption.current","mode":2,"value":"3","priority":null}],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"transfer":true,"flags":{"wrath-and-glory":{"description":"","migrated":true,"manualEffectKeys":true}},"tint":"#ffffff","origin":null,"name":"ORDO MALLEUS (Choice 1)","description":"","statuses":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null},"img":"modules/wng-core/assets/factions/the-inquisition.svg","type":"base","system":{"transferData":{"type":"other","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"scriptData":[],"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"sort":0},{"_id":"Q0aNWykjDCqeTtQL","changes":[],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"transfer":true,"flags":{"wrath-and-glory":{"description":"Scholar/Investigation related to Daemons/The Warp","changeCondition":{"0":{"description":"Scholar/Investigation related to Daemons/The Warp","script":""}},"manualEffectKeys":true}},"tint":"#ffffff","origin":null,"name":"ORDO MALLEUS (Choice 2)","description":"","statuses":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null},"img":"modules/wng-core/assets/factions/the-inquisition.svg","type":"base","system":{"scriptData":[{"script":"return args.fields.pool += 2;","label":"Bonus dice on any Scholar (Int) and Investigation (Int) Tests related to daemons and the Warp.","trigger":"dialog","options":{"targeter":false,"hideScript":"return ![\"scholar\", \"investigation\"].includes(args.skill);","activateScript":"","submissionScript":"","deleteEffect":false,"defending":false},"async":false}],"transferData":{"type":"other","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"sort":0},{"name":"Choice","img":"modules/wng-core/assets/factions/the-inquisition.svg","_id":"tk4CpA9MHTJ47VAe","type":"base","system":{"transferData":{"type":"document","originalType":"document","documentType":"Actor","avoidTest":{"value":"none","opposed":false,"prevention":true,"reversed":false},"testIndependent":false,"equipTransfer":false,"selfOnly":false,"prompt":false,"area":{"templateData":{"borderColor":null,"fillColor":null,"texture":null},"keep":false,"aura":{"transferred":false,"render":false},"duration":"sustained"},"zone":{"type":"zone","transferred":false,"traits":{},"skipImmediateOnPlacement":false,"keep":false}},"itemTargetData":{"ids":[],"allItems":false},"scriptData":[{"script":"let effects = this.item.effects.contents.filter(i => i.id != this.effect.id);\n\nlet choice = await ItemDialog.create(effects, 1, {title : this.item.name, text: this.effect.name});\nchoice[0].updateSource({name : choice[0].baseName, \"system.transferData.type\" : \"document\"})","label":"Choice","trigger":"immediate","options":{"targeter":false,"deleteEffect":true,"defending":false},"async":false}],"zone":{"type":"zone","traits":{},"skipImmediateOnPlacement":false},"sourceData":{"test":{}}},"changes":[],"disabled":false,"duration":{"startTime":null,"seconds":null,"combat":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"description":"","origin":null,"tint":"#ffffff","transfer":true,"statuses":[],"sort":0,"flags":{"wrath-and-glory":{"manualEffectKeys":false}},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","lastModifiedBy":null}}],"flags":{"wng-core":{"initialization-folder":"Ordos","sort":0},"core":{}},"system":{"description":"

The Daemon Hunters

Smallest amongst the Ordos, duty bound to battle daemons and the corrupting influence of Chaos. Every encounter with their foe, every scrap of arcane lore learnt, degrades body and soul. The Chamber Militant of the Ordo Malleus are the Grey Knights, making rare and terrible crusades together. 

"},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755816946335,"modifiedTime":1755816946335,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.mpcvHYlffwcBy9gP","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":"SXjZUgQ1pROPVE9j","_id":"mpcvHYlffwcBy9gP"} -{"name":"PsyboltAmmunition","type":"ammo","img":"modules/wng-core/assets/icons/ammo/ammo.webp","system":{"quantity":1,"rarity":"unique","description":"UpgradeforBOLT/StormBolterweapons.Grants+1ED.AgainstDAEMON/PSYKER,attacksgainRending(1).Applymanuallytoattackresolvingrules;conditionaleffectsarenotautomated.","value":5,"keywords":"BOLT,SANCTIFIED","effect":"","traits":{"list":[{"name":"force","type":"add"},{"name":"blessed","type":"add","rating":"2"}]}},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1756061909914,"modifiedTime":1756061909914,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.iMlwOxHqV9PWIEz5","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"iMlwOxHqV9PWIEz5"} {"name":"Grey Knight (Archetype)","type":"archetype","img":"modules/wng-core/assets/icons/abilities/the-inquisition.webp","effects":[{"_id":"uMkkBfS2rRXluXA6","name":"Aegis of Titan — Innate Purity","icon":"icons/svg/aura.svg","origin":"","disabled":false,"duration":{"startTime":null},"changes":[{"key":"system.combat.resolve.bonus","mode":2,"value":1,"priority":20}],"transfer":true,"flags":{}}],"flags":{"wng-forsaken":{"initialization-folder":"Archetypes","sort":0},"core":{}},"system":{"tier":4,"journal":"DMmW708Q5AQvQfuy","species":{"name":"Adeptus Astartes","id":"KpwHo0A92bR7iVMB"},"faction":{"name":"Adeptus Astartes","id":"CN5E9Z5GqfiR1MDn"},"influence":2,"cost":359,"keywords":["IMPERIUM","ADEPTUS ASTARTES","GREY KNIGHTS","INQUISITION","DAEMONHUNTERS","ORDO MALLEUS"],"attributes":{"strength":5,"toughness":5,"agility":5,"initiative":5,"willpower":6,"intellect":3,"fellowship":null},"skills":{"athletics":3,"awareness":3,"ballisticSkill":5,"cunning":null,"deception":null,"insight":null,"intimidation":null,"investigation":null,"leadership":null,"medicae":null,"persuasion":null,"pilot":null,"psychicMastery":4,"scholar":2,"stealth":3,"survival":1,"tech":null,"weaponSkill":5},"ability":{"id":"DaEMUDPe8wmrWz70","name":"Aegis of Titan","description":"Innate Purity: +1 vs Corruption. Aegis Synergy: +1 vs Corruption while equipped with Aegis armour. Re-roll 1 die on Deny the Witch each round."},"wargear":{"freeItems":[]},"suggested":{"attributes":{"strength":null,"toughness":null,"agility":null,"initiative":null,"willpower":null,"intellect":null,"fellowship":null},"skills":{"athletics":null,"awareness":null,"ballisticSkill":null,"cunning":null,"deception":null,"insight":null,"intimidation":null,"investigation":null,"leadership":null,"medicae":null,"persuasion":null,"pilot":null,"psychicMastery":null,"scholar":null,"stealth":null,"survival":null,"tech":null,"weaponSkill":null},"talents":{"list":[]}}},"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755771384639,"modifiedTime":1755771384639,"lastModifiedBy":"8fThFXcxV83JCCcX","exportSource":{"worldId":"gilead-wandg","uuid":"Item.TCmE7YmUDUVQ3xoz","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":"DEb55m6iVZ0NUOxA","_id":"qSv5JzFRjTYdw1zI"} {"name":"Psilencer","type":"weapon","img":"modules/wng-core/assets/icons/weapons/assault-cannon.webp","system":{"quantity":1,"rarity":"unique","description":"High-ratepsykicdisruptor.VsPSYKER:+1ED;hitPSYKERcannotAiminthesameround.","value":9,"keywords":"IMPERIUM,ADEPTUSASTARTES,GREYKNIGHTS,SANCTIFIED","equipped":false,"attack":{"base":0,"bonus":0,"rank":"none"},"damage":{"base":11,"bonus":0,"rank":0,"ed":{"base":2,"bonus":0,"rank":0,"dice":""},"ap":{"base":0,"bonus":0,"rank":0,"dice":""},"enabled":false,"otherDamage":{"mortal":"0","wounds":"0","shock":"0"},"dice":""},"ed":{"base":0,"bonus":0,"rank":0},"ap":{"base":0,"bonus":0,"rank":0},"category":"ranged","range":{"short":12,"medium":24,"long":36,"melee":1,"thrown":null},"ammo":{"id":""},"salvo":3,"traits":{"list":[{"name":"rapidFire","rating":"3"},{"name":"reliable"}]},"upgrades":[],"otherDamage":{},"combi":{},"twinned":false},"effects":[],"_stats":{"coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1","createdTime":1755859392521,"modifiedTime":1756051080757,"lastModifiedBy":"ETeghv7x8fjtIEKK","exportSource":{"worldId":"gilead2","uuid":"Item.qvk9TxIezbO2C5OK","coreVersion":"13.347","systemId":"wrath-and-glory","systemVersion":"7.1.1"}},"folder":null,"flags":{},"_id":"qvk9TxIezbO2C5OK"} diff --git a/wg-greyknights/scripts/ui-gkpanel.js b/wg-greyknights/scripts/ui-gkpanel.js new file mode 100644 index 0000000..0328758 --- /dev/null +++ b/wg-greyknights/scripts/ui-gkpanel.js @@ -0,0 +1,201 @@ +// gk-greyknight — ui-gkpanel.js (0.6.5.2) + +const GK_NS = "gk-greyknight"; +const GK_DEBUG = false; + +/* ---------------- helpers ---------------- */ +function gkActorHasBrotherhood(actor) { + return !!actor?.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers"); +} +function gkAlliesAndBonus(actor) { + const tokens = canvas?.tokens?.placeables || []; + const ids = new Set(); + for (const t of tokens) { + const a = t.actor; if (!a) continue; + if (gkActorHasBrotherhood(a)) ids.add(a.id); + } + const total = ids.size; + const bonus = ids.has(actor?.id) ? Math.max(0, total - 1) : total; + return { total, bonus }; +} + +/* ---------------- settings (per-user) ---------------- */ +Hooks.once("init", () => { + game.settings.register(GK_NS, "panelVisible", { + name: "Brotherhood panel visible", + hint: "Show the Brotherhood Focus panel by default.", + scope: "client", config: true, type: Boolean, default: true, + onChange: (v) => { v ? GKFocusPanel.show(true) : GKFocusPanel.hide(); } + }); + game.settings.register(GK_NS, "panelAutoOpen", { + name: "Auto-open when eligible", + hint: "Auto-open the panel when your selected actor (or controlled token) has Brotherhood of Psykers.", + scope: "client", config: true, type: Boolean, default: true, + onChange: () => GKFocusPanel.render() + }); +}); + +// v13: dodatkowy hook na zmiany ustawień po stronie klienta +Hooks.on?.("clientSettingChanged", (key, value) => { + if (key === `${GK_NS}.panelVisible`) return value ? GKFocusPanel.show(true) : GKFocusPanel.hide(); + if (key === `${GK_NS}.panelAutoOpen`) return GKFocusPanel.render(); +}); + +/* ---------------- panel ---------------- */ +class GKFocusPanel { + static async getState() { + return (await game.user.getFlag(GK_NS, "focusPanel")) || {}; + } + static async saveState(patch) { + const cur = await this.getState(); + return game.user.setFlag(GK_NS, "focusPanel", { ...cur, ...patch }); + } + static async isVisible() { + const v = game.settings.get(GK_NS, "panelVisible"); + const st = await this.getState(); + return v && !st.closed; + } + static async hide() { + await this.saveState({ closed: true, forced: false }); + $("#gk-focus-panel").remove(); + } + static async show(force = false) { + await this.saveState({ closed: false, forced: !!force }); + this.render(); + } + static async toggle() { + const vis = await this.isVisible(); + if (vis) return this.hide(); + return this.show(true); // force-open nawet bez aktora GK + } + + static currentActor() { + return game.user?.character || canvas.tokens?.controlled?.[0]?.actor || null; + } + + static async render() { + const st = await this.getState(); + if (st.closed || !game.settings.get(GK_NS, "panelVisible")) { $("#gk-focus-panel").remove(); return; } + + const actor = this.currentActor(); + if (!st.forced) { + if (!actor) { $("#gk-focus-panel").remove(); return; } + if (!gkActorHasBrotherhood(actor) && !game.settings.get(GK_NS, "panelAutoOpen")) { + $("#gk-focus-panel").remove(); return; + } + } + return this._renderInternal(actor); + } + + static async _renderInternal(actor) { + const st = await this.getState(); + + const { total, bonus } = gkAlliesAndBonus(actor); + const x = Number.isFinite(st.x) ? st.x : (window.innerWidth - 260); + const y = Number.isFinite(st.y) ? st.y : (window.innerHeight - 120); + const w = Number.isFinite(st.w) ? st.w : 260; + const h = Number.isFinite(st.h) ? st.h : 110; + + const root = $("#gk-focus-panel"); + const html = $(` +
+
+

Brotherhood Focus

+ +
+
+
Allies: ${total}  ·  Bonus: +${bonus}
+
Only one linked GK should use a psychic power as their Action per round. Subsequent powers add +1 Wrath die.
+
+
`); + + if (root.length) root.replaceWith(html); else $("body").append(html); + + html.find(".gk-close").off("click").on("click", async () => { + const left = parseInt(html.css("left")) || x; + const top = parseInt(html.css("top")) || y; + const ww = parseInt(html.outerWidth()) || w; + const hh = parseInt(html.outerHeight()) || h; + await this.saveState({ closed: true, forced: false, x:left, y:top, w:ww, h:hh }); + html.remove(); + }); + + const handle = html.find(".window-header"); + let dragging = false, ox = 0, oy = 0; + function onMove(ev) { + if (!dragging) return; + const nx = Math.max(0, Math.min(window.innerWidth - html.outerWidth(), ev.clientX - ox)); + const ny = Math.max(0, Math.min(window.innerHeight - html.outerHeight(), ev.clientY - oy)); + html.css({ left: nx, top: ny }); + } + async function onUp() { + if (!dragging) return; + dragging = false; + $(window).off("pointermove.gkfp").off("pointerup.gkfp"); + const left = parseInt(html.css("left")) || 0; + const top = parseInt(html.css("top")) || 0; + const ww = parseInt(html.outerWidth()) || w; + const hh = parseInt(html.outerHeight()) || h; + await GKFocusPanel.saveState({ x:left, y:top, w:ww, h:hh }); + } + handle.off("pointerdown.gkfp").on("pointerdown.gkfp", (ev) => { + dragging = true; + const r = html[0].getBoundingClientRect(); + ox = ev.clientX - r.left; oy = ev.clientY - r.top; + $(window).on("pointermove.gkfp", onMove).on("pointerup.gkfp", onUp); + }); + + $(window).off("pointerup.gkfp-size").on("pointerup.gkfp-size", async () => { + if (!document.body.contains(html[0])) return; + const ww = parseInt(html.outerWidth()) || w; + const hh = parseInt(html.outerHeight()) || h; + await GKFocusPanel.saveState({ w: ww, h: hh }); + }); + } +} + +/* ---------------- left toolbar button (v12 & v13) ---------------- */ +Hooks.on("getSceneControlButtons", (controls) => { + // v12: controls = SceneControl[] + if (Array.isArray(controls)) { + const token = controls.find(c => c?.name === "token"); + if (!token) return; + if (token.tools?.some?.(t => t?.name === "gk-toggle-focus")) return; + token.tools.push({ + name: "gk-toggle-focus", + title: "Toggle Brotherhood Focus Panel", + icon: "fas fa-bolt", + button: true, + visible: true, + order: 999, + onClick: () => GKFocusPanel.toggle() + }); + return; + } + // v13: controls = Record; tools = Record + const token = controls?.token; + if (!token) return; + token.tools ??= {}; + if (!token.tools["gk-toggle-focus"]) { + token.tools["gk-toggle-focus"] = { + name: "gk-toggle-focus", + title: "Toggle Brotherhood Focus Panel", + icon: "fas fa-bolt", + button: true, + visible: true, + order: 999, + onClick: () => GKFocusPanel.toggle() + }; + } +}); + +/* ---------------- reactive hooks ---------------- */ +Hooks.on("canvasReady", () => GKFocusPanel.render()); +Hooks.on("controlToken", () => GKFocusPanel.render()); +Hooks.on("updateCombat", () => GKFocusPanel.render()); +Hooks.on("createToken", () => GKFocusPanel.render()); +Hooks.on("deleteToken", () => GKFocusPanel.render());