Final final final

This commit is contained in:
2025-09-18 00:09:29 +02:00
parent 56ada5e1d9
commit d977cff4e4
3 changed files with 205 additions and 3 deletions
+3 -2
View File
@@ -1,7 +1,7 @@
{ {
"id": "gk-greyknight", "id": "gk-greyknight",
"title": "Grey Knights — Compendium (Wrath & Glory)", "title": "Grey Knights — Compendium (Wrath & Glory)",
"version": "0.6.4.6", "version": "0.6.5.2",
"compatibility": { "compatibility": {
"minimum": "11", "minimum": "11",
"verified": "13" "verified": "13"
@@ -26,7 +26,8 @@
], ],
"esmodules": [ "esmodules": [
"scripts/auto-loadout.js", "scripts/auto-loadout.js",
"scripts/helpers.js" "scripts/helpers.js",
"scripts/ui-gkpanel.js"
], ],
"readme": "README.txt" "readme": "README.txt"
} }
File diff suppressed because one or more lines are too long
+201
View File
@@ -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 = $(`
<section id="gk-focus-panel" class="window-app"
style="position:fixed; left:${x}px; top:${y}px;
width:${w}px; height:${h}px; min-width:200px; min-height:80px;
resize: both; overflow:auto; background:#111A; color:#fff;
border:1px solid #444; border-radius:6px; z-index:70;">
<header class="window-header" style="cursor:move; display:flex; align-items:center; gap:.5rem; padding:.25rem .5rem; border-bottom:1px solid #444;">
<h4 class="window-title" style="margin:0; font-size:.95rem;">Brotherhood Focus</h4>
<a class="gk-close header-button" style="margin-left:auto;" title="Close"><i class="fas fa-times"></i></a>
</header>
<section class="window-content" style="padding:.4rem .6rem;">
<div><span style="opacity:.9">Allies</span>: <b>${total}</b> &nbsp;·&nbsp; <span style="opacity:.9">Bonus</span>: <b>+${bonus}</b></div>
<div style="opacity:.85; font-size:.9em; margin-top:.25rem;">Only one linked GK should use a psychic power as their <em>Action</em> per round. Subsequent powers add +1 <em>Wrath</em> die.</div>
</section>
</section>`);
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<string, SceneControl>; tools = Record<string, SceneControlTool>
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());