// 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: `

Choose a psychic discipline for this character:

(You can change this later in Actor → Flags → gk-greyknight → discipline.)

`, buttons: { ok: { label: "Confirm", icon: "", 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: `

Select a discipline for this character:

`, buttons: { ok: { label: "Confirm", icon: "", 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 = `

Brotherhood Focus — Summary

Allies with Brotherhood in scene: ${total} (excluding self: ${bonus} bonus die to Psychic Mastery).

Reminder: each subsequent power cast this round using the shared focus adds one extra Wrath die. Only one linked character should use a psychic power as their Action per round.

`; ChatMessage.create({content, whisper: owners, speaker: {actor: actor.id}}); } catch (e) { console.error("gk-greyknight QoL summary:", e); } }); // gk-greyknight 0.6.4.5 — Reliable Brotherhood notification on psychic power use Hooks.on("createChatMessage", async (doc, options, userId) => { try { // Heuristic detection of psychic power usage const flavor = (doc.flavor || "").toLowerCase(); const contentText = (ui?.chat && doc.content) ? TextEditor?.stripHTML(doc.content)?.toLowerCase?.() : (doc.content || "").toLowerCase(); const keyWords = ["psychic", "psychic mastery", "power", "smite", "hammerhand", "sanctuary", "shrouding", "might of titan"]; const looksPsychic = keyWords.some(k => flavor.includes(k) || contentText.includes(k)); if (!looksPsychic) return; // Resolve actor let actor = null; const sp = doc.speaker || {}; if (sp.actor) actor = game.actors?.get(sp.actor); if (!actor && canvas?.tokens) { const t = canvas.tokens.get(sp.token) || canvas.tokens.controlled?.[0]; actor = t?.actor || actor; } if (!actor) return; // Check Brotherhood ability on actor const hasBrotherhood = actor.items?.some(i => i.type === "ability" && i.name === "Brotherhood of Psykers"); if (!hasBrotherhood) return; // Count allies with Brotherhood in the scene 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; // Compose message const msg = `
Brotherhood Focus: Allies with Brotherhood here: ${total} → bonus to Psychic Mastery: +${bonus} dice.
House rule: only one linked Grey Knight should use a psychic power as their Action per round; subsequent powers add +1 Wrath die.
`; // Whisper to owners + GM const ownerIds = Object.entries(actor.ownership || {}) .filter(([uid, lvl]) => lvl >= (CONST.DOCUMENT_OWNERSHIP_LEVELS?.OWNER ?? 3)) .map(([uid]) => uid); const gmIds = game.users?.filter(u => u.isGM).map(u => u.id) ?? []; const whisper = Array.from(new Set([...ownerIds, ...gmIds])); await ChatMessage.create({content: msg, whisper, speaker: {actor: actor.id}}); // If in combat, enforce one-per-round reminder const combat = game.combat; if (combat) { 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) { await combat.setFlag("gk-greyknight", key, actor.id); } } } catch (e) { console.error("gk-greyknight 0.6.4.5 notification error:", e); } });