Files
vtt_work/wg-greyknights/scripts/auto-loadout.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

138 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// gk-greyknight — auto-attach loadout (scoped + incognito + ORDO) v0.6.1
Hooks.once("ready", () => {
const PACK_KEY = "gk-greyknight.gk-items";
const PACK_SRC_PREFIX = `Compendium.${PACK_KEY}.`;
const CORE = {
knife: "Compendium.wng-core.items.Item.4QVBQYWXXLyvFWkj",
pistol: "Compendium.wng-core.items.Item.7b2xcPWBB7up65PM",
ordo: "Compendium.wng-core.items.Item.mpcvHYlffwcBy9gP"
};
const ITEMS = {
ability: "Aegis of Titan (Ability)",
nemesis: "Nemesis Force Halberd",
storm: "Grey Knight Armour Integrated Storm Bolter",
aegisPA: "Aegis Power Armour",
aegisTerm: "Terminator Aegis (Grey Knights)",
pilgrim: "Pilgrim Astartes Power Armor",
ordo: "ORDO MALLEUS"
};
const FLAG_SCOPE = "gk-greyknight"; const FLAG_DONE = "autoLoadoutDone";
async function fetchFromCompendium(name) {
const pack = game.packs.get(PACK_KEY);
if (!pack) return null;
const index = await pack.getIndex();
const hit = index.find(e => e.name === name);
if (!hit) return null;
const doc = await pack.getDocument(hit._id);
return doc?.toObject();
}
async function fetchFromUuid(uuid) { try { const doc = await fromUuid(uuid); return doc?.toObject(); } catch(e){ return null; } }
async function ensureOnActor(actor, srcDoc, equip=false) {
const name = srcDoc?.name; if (!name) return null;
let have = actor.items.getName(name);
if (have) { if (typeof have.system?.equipped !== "undefined") await have.update({"system.equipped": !!equip}); return have; }
if (equip && srcDoc.system && typeof srcDoc.system.equipped !== "undefined") srcDoc.system.equipped = true;
const [created] = await actor.createEmbeddedDocuments("Item", [srcDoc]);
return created;
}
async function ensureByName(actor, name, equip=false) {
const have = actor.items.getName(name);
if (have) { if (typeof have.system?.equipped !== "undefined") await have.update({"system.equipped": !!equip}); return have; }
let src = await fetchFromCompendium(name);
if (!src) { const world = game.items.getName(name); if (world) src = world.toObject(); }
if (!src) return null;
return ensureOnActor(actor, src, equip);
}
async function setEquipped(actor, name, on) {
const it = actor.items.getName(name);
if (it && typeof it.system?.equipped !== "undefined") await it.update({"system.equipped": !!on});
}
if (window._gkAutoLoadoutHookId) { Hooks.off("createItem", window._gkAutoLoadoutHookId); window._gkAutoLoadoutHookId = null; }
window._gkAutoLoadoutHookId = Hooks.on("createItem", async (item, opts, userId) => {
try {
if (item?.type !== "archetype") return;
// v12+: pochodzenie z paczki przeniesione do _stats.compendiumSource (flags.core.sourceId deprecated)
const src = item?._stats?.compendiumSource || item?.flags?.core?.sourceId || "";
const isFromOurPack = src.startsWith(PACK_SRC_PREFIX);
const isOurGKByName = item?.name === "Grey Knight (Archetype)";
if (!isFromOurPack && !isOurGKByName) return;
if (game.userId !== userId) return;
const actor = item.parent; if (!actor) return;
if (actor.getFlag(FLAG_SCOPE, FLAG_DONE)) return;
await actor.setFlag(FLAG_SCOPE, FLAG_DONE, true);
await ensureByName(actor, ITEMS.ability, false);
await ensureByName(actor, "Brotherhood of Psykers", false);
let ordoDoc = await fetchFromUuid(CORE.ordo);
if (!ordoDoc) ordoDoc = await fetchFromCompendium(ITEMS.ordo);
if (ordoDoc) await ensureOnActor(actor, ordoDoc, false);
const content = `
<style>
.gk-choices .opt{display:flex;gap:.6rem;align-items:flex-start;padding:.35rem .5rem;border:1px solid #6663;border-radius:6px;margin:.35rem 0}
.gk-choices .opt label{font-weight:600}
.gk-choices .hint{font-size:.9em;opacity:.85;margin-left:1.7rem}
</style>
<div class="gk-choices">
<div class="opt">
<input type="radio" name="mode" value="incog" id="gk-incog">
<label for="gk-incog">Incognito — Pilgrim Astartes Power Armor + Combat Knife + Bolt Pistol</label>
</div>
<div class="hint">Lowprofile loadout (bez Nemesis/SB)</div>
<div class="opt">
<input type="radio" name="mode" value="war" id="gk-war" checked>
<label for="gk-war">WarPlate — Aegis Power Armour</label>
</div>
<div class="hint">Aegis PA + Nemesis + Integrated Storm Bolter</div>
<div class="opt">
<input type="radio" name="mode" value="term" id="gk-term">
<label for="gk-term">Terminator — Terminator Aegis</label>
</div>
<div class="hint">Terminator Aegis + Nemesis + Integrated Storm Bolter</div>
</div>`;
const choice = await new Promise(resolve => {
new Dialog({ title: "Grey Knight — Wybierz Loadout",
content,
buttons: {
ok: {label: "Zastosuj", icon:"<i class='fas fa-check'></i>", callback: html => resolve(html.find('input[name=\"mode\"]:checked').val() || "war")},
cancel: {label:"Anuluj", callback: ()=>resolve(null)}
}, default: "ok"
}).render(true);
});
if (!choice) return;
await setEquipped(actor, ITEMS.aegisPA, false);
await setEquipped(actor, ITEMS.aegisTerm, false);
await setEquipped(actor, ITEMS.nemesis, false);
await setEquipped(actor, ITEMS.storm, false);
await setEquipped(actor, ITEMS.pilgrim, false);
if (choice === "incog") {
await ensureByName(actor, ITEMS.pilgrim, true);
const knife = await fetchFromUuid(CORE.knife); if (knife) await ensureOnActor(actor, knife, true);
const pistol = await fetchFromUuid(CORE.pistol); if (pistol) await ensureOnActor(actor, pistol, true);
} else if (choice === "war") {
await ensureByName(actor, ITEMS.nemesis, true);
await ensureByName(actor, ITEMS.storm, true);
await ensureByName(actor, ITEMS.aegisPA, true);
} else if (choice === "term") {
await ensureByName(actor, ITEMS.nemesis, true);
await ensureByName(actor, ITEMS.storm, true);
await ensureByName(actor, ITEMS.aegisTerm, true);
}
const abil = actor.items.getName(ITEMS.ability);
const anyAegisOn = !!actor.items.find(i => ["armour","armor"].includes(i.type) && /Aegis/.test(i.name) && i.system?.equipped);
if (abil?.effects?.length) for (const ef of abil.effects) if (ef.name?.toLowerCase().includes("synergy")) await ef.update({disabled: !anyAegisOn});
ui.notifications.info(`GK loadout: ${choice.toUpperCase()} ustawiony.`);
} catch (e) { console.error("gk-greyknight auto-loadout error:", e); }
});
console.log("gk-greyknight: auto-loadout hook active (v0.6.1)");
});