Files
vtt_work/wg-greyknights/scripts/helpers.js
T

171 lines
8.3 KiB
JavaScript

// gk-greyknight — helpers v0.6.4 (Brotherhood discipline prompt)
Hooks.once("ready", () => {
const PACK_KEY = "gk-greyknight.gk-items";
const PACK_SRC_PREFIX = `Compendium.${PACK_KEY}.`;
const FLAG_SCOPE = "gk-greyknight";
async function promptDiscipline(actor) {
const existing = await actor.getFlag(FLAG_SCOPE, "discipline");
if (existing) return existing;
return await new Promise(resolve => {
new Dialog({
title: "Brotherhood of Psykers — Discipline",
content: `<p>Choose a psychic discipline for this character:</p>
<div style="display:flex; gap:.75rem; margin-top:.5rem;">
<label><input type="radio" name="disc" value="librarius" checked> Librarius</label>
<label><input type="radio" name="disc" value="sanctic"> Sanctic</label>
</div>
<p style="margin:.5rem 0 0; font-size:.9em; opacity:.8;">(You can change this later in Actor → Flags → gk-greyknight → discipline.)</p>`,
buttons: {
ok: { label: "Confirm", icon: "<i class='fas fa-check'></i>", callback: html => resolve(html.find('input[name=\"disc\"]:checked').val() || "librarius") },
cancel: { label: "Cancel", callback: () => resolve(null) }
},
default: "ok"
}).render(true);
});
}
if (window._gkBroHookId) { Hooks.off("createItem", window._gkBroHookId); window._gkBroHookId = null; }
window._gkBroHookId = Hooks.on("createItem", async (item, opts, userId) => {
try {
if (game.userId !== userId) return;
if (item?.type !== "ability") return;
const name = item?.name || "";
if (name !== "Brotherhood of Psykers") return;
const src = item?.flags?.core?.sourceId || "";
const isFromOurPack = src.startsWith(PACK_SRC_PREFIX);
if (!isFromOurPack && name !== "Brotherhood of Psykers") return;
const actor = item.parent; if (!actor) return;
const chosen = await promptDiscipline(actor);
if (!chosen) return;
await actor.setFlag(FLAG_SCOPE, "discipline", chosen);
if (chosen === "sanctic") {
ui.notifications.info("Discipline set to SANCTIC. Open the GK compendium and pick one Sanctic power as your starter.");
} else {
ui.notifications.info("Discipline set to LIBRARIUS. Use core Librarius powers from wng-core.");
}
} catch (e) { console.error("gk-greyknight brotherhood helper:", e); }
});
console.log("gk-greyknight helpers active (v0.6.4): Brotherhood discipline prompt enabled.");
});
/* gk-greyknight 0.6.4.1 — house-rule reminder (non-blocking) */
Hooks.on("preCreateChatMessage", (doc, data, options, userId) => {
try {
const flavor = (data.flavor || "").toLowerCase();
const isPsychic = flavor.includes("psychic") || flavor.includes("power");
if (!isPsychic) return;
const speaker = data.speaker || {}; const actor = game.actors?.get(speaker.actor);
if (!actor) return;
const hasBrotherhood = actor.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers");
if (!hasBrotherhood) return;
const combat = game.combat; if (!combat) return;
const key = `gk-greyknight.usedPsychicAction.r${combat.round}`;
const prev = combat.getFlag("gk-greyknight", key) || null;
if (prev && prev !== actor.id) ui.notifications?.warn("House rule (Brotherhood Focus): only one Grey Knight should use a psychic power as their Action this round.");
else if (!prev) combat.setFlag("gk-greyknight", key, actor.id);
} catch (e) { console.error("gk-greyknight helpers (house-rule reminder):", e); }
});
// === gk-greyknight 0.6.4.3 — Brotherhood discipline prompt + starter grants ===
Hooks.once("ready", () => {
const FLAG_SCOPE = "gk-greyknight";
// Prevent duplicate hook registration on hot-reload
if (window._gkBroHookId) { Hooks.off("createItem", window._gkBroHookId); window._gkBroHookId = null; }
async function findItemInAllPacksByName(name, type=null) {
for (const pack of game.packs) {
if (pack.documentName !== "Item") continue;
const index = await pack.getIndex();
const hit = index.find(e => e.name === name && (!type || e.type === type));
if (hit) return await pack.getDocument(hit._id);
}
return null;
}
async function ensureHasItemByName(actor, name, type=null) {
const existing = actor.items?.find(i => i.name === name && (!type || i.type === type));
if (existing) return existing;
const doc = await findItemInAllPacksByName(name, type);
if (!doc) return null;
const data = doc.toObject(); delete data._id;
const [created] = await actor.createEmbeddedDocuments("Item", [data]);
return created;
}
async function promptDiscipline(actor) {
const existing = await actor.getFlag(FLAG_SCOPE, "discipline");
if (existing) return existing;
return await new Promise(resolve => {
new Dialog({
title: "Brotherhood of Psykers — Choose Discipline",
content: `<p>Select a discipline for this character:</p>
<label style='display:block;margin-top:.5rem'><input type="radio" name="disc" value="sanctic" checked> Sanctic</label>
<label style='display:block'><input type="radio" name="disc" value="librarius"> Librarius</label>`,
buttons: {
ok: { label: "Confirm", icon: "<i class='fas fa-check'></i>", callback: html => resolve(html.find('input[name="disc"]:checked').val() || "sanctic") },
cancel: { label: "Cancel", callback: () => resolve(null) }
},
default: "ok"
}).render(true);
});
}
window._gkBroHookId = Hooks.on("createItem", async (item, opts, userId) => {
try {
if (game.userId !== userId) return;
if (item?.type !== "ability" || item?.name !== "Brotherhood of Psykers") return;
const actor = item.parent; if (!actor) return;
const chosen = await promptDiscipline(actor);
if (!chosen) return;
await actor.setFlag(FLAG_SCOPE, "discipline", chosen);
// Ensure PSYKER keyword so powers are available
const psyker = await ensureHasItemByName(actor, "PSYKER", "keyword");
if (!psyker) ui.notifications?.warn("PSYKER keyword not found in loaded packs. Add it manually from wng-core.");
// Starter power by discipline
if (chosen === "sanctic") {
const ok = await ensureHasItemByName(actor, "Hammerhand (Sanctic)", "psychicPower");
if (!ok) ui.notifications?.warn("Couldn't auto-add Hammerhand (Sanctic). Drag a Sanctic power from the GK pack.");
} else {
const ok = await ensureHasItemByName(actor, "Smite", "psychicPower");
if (!ok) {
ui.notifications?.warn("Smite not found in available packs. Please add a Librarius power manually.");
game.ui?.sidebar?.tabs?.compendium?.activate();
}
}
} catch (e) { console.error("gk-greyknight — Brotherhood prompt:", e); }
});
});
// gk-greyknight 0.6.4.4 — QoL: Brotherhood Focus summary on sheet open
Hooks.on("renderActorSheet", async (sheet, htmlEl, data) => {
try {
const actor = sheet.actor; if (!actor) return;
const hasBro = actor.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers");
if (!hasBro) return;
const tokens = canvas?.tokens?.placeables || [];
const allied = new Set();
for (const t of tokens) {
const a = t.actor; if (!a) continue;
const ok = a.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers");
if (ok) allied.add(a.id);
}
const total = allied.size;
const bonus = allied.has(actor.id) ? Math.max(0, total - 1) : total;
const owners = Object.keys(actor.ownership || {}).filter(uid => actor.ownership[uid] >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
const content = `<div class="gk-brotherhood-summary">
<h3 style="margin:0 0 .25rem 0;">Brotherhood Focus — Summary</h3>
<p style="margin:0;">Allies with Brotherhood in scene: <b>${total}</b> (excluding self: <b>${bonus}</b> bonus die to <em>Psychic Mastery</em>).</p>
<p style="margin:.25rem 0 0;font-size:.9em;opacity:.85;">Reminder: each subsequent power cast this round using the shared focus adds one extra <em>Wrath die</em>. Only one linked character should use a psychic power as their <em>Action</em> per round.</p>
</div>`;
ChatMessage.create({content, whisper: owners, speaker: {actor: actor.id}});
} catch (e) { console.error("gk-greyknight QoL summary:", e); }
});