Files
vtt_work/wg-greyknights/scripts/helpers.js
T
gitea a67c18d691 fix(foundry): poprawki zgodności v13/v14 + skrypt NeDB→LevelDB
Poprawki punktowe (niskiego ryzyka, z fallbackiem na v11/v12):
- greyknights/auto-loadout: pochodzenie z paczki przez _stats.compendiumSource
  (flags.core.sourceId deprecated od v12)
- greyknights/gk-glow-toggle: canvas.perception.update -> płaska flaga
  refreshLighting (stary format {lighting:{refresh}} nie działa od v11)
- greyknights/helpers: TextEditor -> foundry.applications.ux.TextEditor;
  game.ui -> ui (martwy no-op)
- greyknights/ui-gkpanel: scene control tool v13 onClick -> onChange
- voidships-builder/voidships: loadTemplates -> foundry.applications.handlebars
- voidships-builder/voidship-actor: Actors -> foundry.documents.collections.Actors
- usunięto śmieciowe packs/*.db.bak

Skrypt help_scripts/pack_nedb_to_leveldb.mjs: przepakowuje kompendia NeDB(.db)
-> LevelDB (wymagane w v11+/v13/v14), czyta paczki z module.json, domyślnie
dry-run. Wykrywa też rozbieżność: gk-greyknights i wg-voidships-journal mają
w manifeście ścieżkę bez .db, a na dysku plik NeDB.

Nie ruszano vendored investigation-board (do aktualizacji z upstreamu).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 19:31:37 +02:00

117 lines
5.4 KiB
JavaScript

// gk-greyknight helpers.js — consolidated 0.6.4.6
// Zawiera: Brotherhood prompt + starter, powiadomienie (także poza walką), sheet summary,
// mini-widżet, oraz patcher traitów (Psycannon/Psilencer -> force).
/* ---------------- Utilities ---------------- */
const GK_DEBUG = false;
const FLAG_SCOPE = "gk-greyknight";
async function findItemInAllPacksByName(name, type=null) {
for (const pack of game.packs) {
if (pack.documentName !== "Item") continue;
const idx = await pack.getIndex();
const hit = idx.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);
});
}
async function gkResolveActorFromMessage(doc) {
const sp = doc.speaker || {};
let actor = sp.actor ? game.actors?.get(sp.actor) : null;
if (!actor && canvas?.tokens) {
const t = (sp.token && canvas.tokens.get(sp.token)) || canvas.tokens.controlled?.[0] || null;
actor = t?.actor || actor;
}
return actor;
}
async function gkIsPsychicMessage(doc) {
try {
// v13: TextEditor przeniesiony do foundry.applications.ux (global deprecated) — z fallbackiem na v11/v12
const _TE = foundry.applications?.ux?.TextEditor?.implementation ?? globalThis.TextEditor;
const raw = `${doc.flavor || ""} ${_TE?.stripHTML?.(doc.content || "") || ""}`.toLowerCase();
const keys = ["psychic","psychic mastery","psychic power","moc psioniczna","smite","hammerhand","sanctuary","shrouding","might of titan"];
if (keys.some(k=>raw.includes(k))) return true;
const flagsStr = JSON.stringify(doc.flags || {}).toLowerCase();
if (flagsStr.includes("psychic")) return true;
if (flagsStr.includes("psychicpower")) return true;
if (flagsStr.includes("\"power\"")) return true;
const tryUuids = [
doc.getFlag?.("core","sourceId"),
doc.getFlag?.("wng-core","itemUuid"),
doc.flags?.document?.uuid,
doc.flags?.uuid
].filter(Boolean);
for (const u of tryUuids) {
try { const it = await fromUuid(u); if (it?.type === "psychicPower") return true; } catch(e){}
}
} catch(e) { if (GK_DEBUG) console.warn("gk IsPsychicMessage error:", e); }
return false;
}
function gkBrotherhoodAlliesAndBonus(actor) {
const tokens = canvas?.tokens?.placeables || [];
const ids = 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) ids.add(a.id);
}
const total = ids.size;
const bonus = ids.has(actor?.id) ? Math.max(0, total - 1) : total;
return { total, bonus };
}
/* ---------------- 1) Brotherhood: prompt + starter ---------------- */
Hooks.once("ready", () => {
// Avoid duplicate hook if hot-reloaded
if (window._gkBroCreateHookId) { Hooks.off("createItem", window._gkBroCreateHookId); window._gkBroCreateHookId = null; }
window._gkBroCreateHookId = 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);
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.");
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.");
// globalny obiekt to `ui`, nie `game.ui`; activateTab działa w v12/v13
ui.sidebar?.activateTab?.("compendium");
}
}
} catch (e) { console.error("gk-greyknight — Brotherhood prompt:", e); }
});
});