// 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());