Import gk-greyknight-0.1.0.zip do wg-greyknights (2025-08-21T19:51:05Z)

This commit is contained in:
2025-08-21 19:51:05 +00:00
parent dc82906983
commit 24ad39dc6f
16 changed files with 4801 additions and 13 deletions
+1
View File
@@ -0,0 +1 @@
Homebrew content for personal table use.
+14
View File
@@ -0,0 +1,14 @@
# GK Grey Knight (Wrath & Glory)
This module installs homebrew Grey Knight content for the Wrath & Glory system.
## Contents
- Items: Aegis Power Armour, Terminator Aegis (Grey Knights), Grey Knight Armour Integrated Storm Bolter, Nemesis Force Halberd, Aegis of Titan (Ability), Grey Knight (Archetype).
- Macros (scripts): Import from module, Loadout Manager, Aegis Glow Toggle.
- Preset Actors: GK Incognito, GK Power Armour, GK Terminator (`foundry/actors/*.json`).
## Install
1. Copy the `gk-greyknight` folder into `{UserData}/Data/modules/` and restart Foundry **or** use a manifest URL that points to `module.json`.
2. Enable **Grey Knight Add-on (Wrath & Glory)** in your World.
3. Go to *Configuration → Module Settings → Grey Knight Tools* to open the helper dialog.
4. Click **Import Items from Module** to populate your Items Directory.
5. Use **Loadout Manager** on a GK actor to switch between Incognito / Power Armour / Terminator.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -2,10 +2,10 @@
"name": "Aegis of Titan (Ability)", "name": "Aegis of Titan (Ability)",
"type": "ability", "type": "ability",
"img": "icons/svg/aura.svg", "img": "icons/svg/aura.svg",
"_id": "DaEMUDPe8wmrWz70", "_id": "3SQI68UqiAewIU1y",
"effects": [ "effects": [
{ {
"_id": "WD4ApIMZ3pnaPDtl", "_id": "5suMwN6MqzHdEg4V",
"name": "Aegis of Titan — Innate Purity", "name": "Aegis of Titan — Innate Purity",
"icon": "icons/svg/aura.svg", "icon": "icons/svg/aura.svg",
"origin": "", "origin": "",
@@ -25,7 +25,7 @@
"flags": {} "flags": {}
}, },
{ {
"_id": "OybEJ8Tl1hRkI5zJ", "_id": "Bp0mJSit3PToM3N3",
"name": "Aegis of Titan — Aegis Synergy", "name": "Aegis of Titan — Aegis Synergy",
"icon": "icons/svg/upgrade.svg", "icon": "icons/svg/upgrade.svg",
"origin": "", "origin": "",
@@ -4,7 +4,7 @@
"img": "icons/svg/shield.svg", "img": "icons/svg/shield.svg",
"effects": [ "effects": [
{ {
"_id": "25aqxUBwyQtSAdxb", "_id": "WzCZif9l1GFukRaE",
"name": "Aegis Wards (Constant)", "name": "Aegis Wards (Constant)",
"icon": "icons/svg/aura.svg", "icon": "icons/svg/aura.svg",
"origin": "", "origin": "",
@@ -31,7 +31,7 @@
"flags": {} "flags": {}
}, },
{ {
"_id": "LfRNsXknme6JLgSC", "_id": "D27fn83cJnvlfpC4",
"name": "Aegis Warding Runes (Toggle)", "name": "Aegis Warding Runes (Toggle)",
"icon": "icons/svg/explosion.svg", "icon": "icons/svg/explosion.svg",
"origin": "", "origin": "",
@@ -100,5 +100,5 @@
} }
}, },
"folder": "K59Lb8PvaCmIrviu", "folder": "K59Lb8PvaCmIrviu",
"_id": "11NZTwYXPlHBlnXC" "_id": "pTSF0G0Cvx4PzPXI"
} }
@@ -4,7 +4,7 @@
"img": "icons/svg/book.svg", "img": "icons/svg/book.svg",
"effects": [ "effects": [
{ {
"_id": "uMkkBfS2rRXluXA6", "_id": "T0IpNmU0xjFwmcwr",
"name": "Aegis of Titan — Innate Purity", "name": "Aegis of Titan — Innate Purity",
"icon": "icons/svg/aura.svg", "icon": "icons/svg/aura.svg",
"origin": "", "origin": "",
@@ -286,5 +286,5 @@
} }
}, },
"folder": "DEb55m6iVZ0NUOxA", "folder": "DEb55m6iVZ0NUOxA",
"_id": "qSv5JzFRjTYdw1zI" "_id": "AhWvgUliU6XV5GFT"
} }
@@ -100,5 +100,5 @@
} }
}, },
"folder": "85dkcvhBYli5TJrZ", "folder": "85dkcvhBYli5TJrZ",
"_id": "SDFwequVtp6WsCFj" "_id": "1R3iq7EsPdCYkMiU"
} }
@@ -99,5 +99,5 @@
} }
}, },
"folder": "85dkcvhBYli5TJrZ", "folder": "85dkcvhBYli5TJrZ",
"_id": "NX6bWADcIa03iGCK" "_id": "O68u3VzArlrZwwFb"
} }
@@ -4,7 +4,7 @@
"img": "icons/svg/tower.svg", "img": "icons/svg/tower.svg",
"effects": [ "effects": [
{ {
"_id": "OvE7cPAtNLDdEj6j", "_id": "0f6j24Bb5F9IltEj",
"name": "Aegis Wards (Constant)", "name": "Aegis Wards (Constant)",
"icon": "icons/svg/aura.svg", "icon": "icons/svg/aura.svg",
"origin": "", "origin": "",
@@ -31,7 +31,7 @@
"flags": {} "flags": {}
}, },
{ {
"_id": "RXHERg7YjunnEQ8h", "_id": "fABPJslze9yrrlcc",
"name": "Aegis Warding Runes (Toggle)", "name": "Aegis Warding Runes (Toggle)",
"icon": "icons/svg/explosion.svg", "icon": "icons/svg/explosion.svg",
"origin": "", "origin": "",
@@ -103,5 +103,5 @@
} }
}, },
"folder": "K59Lb8PvaCmIrviu", "folder": "K59Lb8PvaCmIrviu",
"_id": "9XTvCiJElW4LJij9" "_id": "vq0e5MD7FXcEqQFi"
} }
+26
View File
@@ -0,0 +1,26 @@
{
"id": "gk-greyknight",
"title": "Grey Knight Add-on (Wrath & Glory)",
"description": "Homebrew Grey Knights kit for Wrath & Glory: archetype, Aegis armours, integrated storm bolter, Nemesis halberd, ability, macros, and preset actors.",
"version": "0.1.0",
"compatibility": {
"minimum": "13",
"verified": "13"
},
"authors": [
{
"name": "User & Assistant"
}
],
"systems": [
"wrath-and-glory"
],
"esmodules": [
"scripts/auto-loadout.js"
],
"readme": "README.txt",
"license": "LICENSE",
"url": "https://example.com/gk-greyknight",
"manifest": "https://example.com/gk-greyknight/module.json",
"download": "https://example.com/gk-greyknight/gk-greyknight-0.1.0.zip"
}
+48
View File
@@ -0,0 +1,48 @@
// gk-greyknight — auto hooks + small tools menu
Hooks.once("ready", () => {
// Hook: when our Archetype is added, open Loadout Manager
if (!window._gkAutoLoadoutHook) {
window._gkAutoLoadoutHook = Hooks.on("createItem", async (item, opts, userId) => {
try {
if (item?.type !== "archetype") return;
if (item?.name !== "Grey Knight (Archetype)") return;
const actor = item.parent;
if (!actor || game.userId !== userId) return;
const code = await (await fetch("/modules/gk-greyknight/scripts/macros/gk-loadout-manager.js")).text();
(0, eval)(code);
} catch (e) { console.error("gk-greyknight auto-loadout error", e); }
});
}
game.settings.registerMenu("gk-greyknight", "tools", {
name: "Grey Knight Tools",
label: "Open Tools",
icon: "fas fa-shield-alt",
type: class extends FormApplication {
async _updateObject(event, formData) {}
static get defaultOptions(){ return foundry.utils.mergeObject(super.defaultOptions, { id:"gk-tools", title:"Grey Knight Tools", template: null}); }
async render(...args){
new Dialog({
title: "Grey Knight Tools",
content: `<p>Quick actions for GK content.</p>`,
buttons: {
import: {label:"Import Items from Module", icon:"<i class='fas fa-file-import'></i>", callback: async ()=>{
const code = await (await fetch("/modules/gk-greyknight/scripts/macros/gk-import-from-module.js")).text();
(0, eval)(code);
}},
loadout: {label:"Open Loadout Manager", icon:"<i class='fas fa-robot'></i>", callback: async ()=>{
const code = await (await fetch("/modules/gk-greyknight/scripts/macros/gk-loadout-manager.js")).text();
(0, eval)(code);
}},
glow: {label:"Toggle Aegis Glow", icon:"<i class='fas fa-lightbulb'></i>", callback: async ()=>{
const code = await (await fetch("/modules/gk-greyknight/scripts/macros/gk-glow-toggle.js")).text();
(0, eval)(code);
}}
},
default: "import"
}).render(true);
}
},
restricted: true
});
});
@@ -0,0 +1,34 @@
// GK Aegis Glow Toggle (module)
(async () => {
const tok = canvas.tokens.controlled[0] ?? null;
const actor = tok?.actor ?? game.user.character;
const token = tok ?? canvas.tokens.placeables.find(t => t.actor?.id === actor?.id);
if (!actor || !token) return ui.notifications.warn("Wybierz token lub przypisz postać do użytkownika.");
const aegisArm = actor.items.find(i => ["armour","armor"].includes(i.type) && /Aegis/.test(i.name));
if (!aegisArm) return ui.notifications.warn("Nie znaleziono Aegis armour na postaci.");
const eff = aegisArm.effects.find(e => e.name?.toLowerCase().startsWith("aegis warding runes"));
if (!eff) return ui.notifications.warn("Nie znaleziono efektu toggle na Aegis armour.");
const wasDisabled = eff.disabled ?? true;
await eff.update({ disabled: !wasDisabled });
const FLAG_SCOPE = "wng-gk"; const FLAG_KEY = "prevLight";
const setGlowOn = async () => {
const prev = token.document.getFlag(FLAG_SCOPE, FLAG_KEY);
if (!prev) await token.document.setFlag(FLAG_SCOPE, FLAG_KEY, foundry.utils.deepClone(token.document.light));
await token.document.update({
light: { color:"#66ccff", coloration:2, alpha:0.7, dim:16, bright:0, angle:360,
luminosity:0.5, attenuation:0.35, saturation:0, contrast:0, shadows:0,
animation:{ type:"pulse", speed:3, intensity:4, reverse:false } }
});
await canvas.perception.update({ lighting: { refresh: true } });
};
const setGlowOff = async () => {
const prev = token.document.getFlag(FLAG_SCOPE, FLAG_KEY);
if (prev) { await token.document.update({ light: prev }); await token.document.unsetFlag(FLAG_SCOPE, FLAG_KEY); }
else { await token.document.update({ light: { dim:0, bright:0, color:null } }); }
await canvas.perception.update({ lighting: { refresh: true } });
};
if (wasDisabled) { await setGlowOn(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes <span style="color:#66ccff">ENGAGED</span>. (Akcja)` }); }
else { await setGlowOff(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes DISENGAGED.` }); }
})();
@@ -0,0 +1,40 @@
// GK Import Items From Module into Items Directory
(async () => {
const FILES = [
"fvtt-Item-archetype-grey-knight_v5.json",
"fvtt-Item-ability-aegis-of-titan_v5.json",
"fvtt-Item-nemesis-force-halberd_v4.json",
"fvtt-Item-gk-armour-integrated-storm-bolter_v2.json",
"fvtt-Item-aegis-power-armour_v2.json",
"fvtt-Item-terminator-aegis-gk_v4.json"
];
const MODULE_ID = "gk-greyknight";
const base = `modules/${MODULE_ID}/foundry`;
const ensureFolder = async (name, color="#4b6eaf") => {
let f = game.folders.find(x => x.type === "Item" && x.name === name);
if (!f) f = await Folder.create({name, type:"Item", color});
return f;
};
const worldFolder = await ensureFolder("Grey Knight Pack", "#4b6eaf");
const fetchJSON = async (path) => {
const res = await fetch(`/${path}`);
if (!res.ok) throw new Error(`Nie mogę pobrać: ${path} (HTTP ${res.status})`);
return await res.json();
};
let created = 0, updated = 0;
for (const fn of FILES) {
const full = `${base}/${fn}`;
try {
const data = await fetchJSON(full);
if (data._id) delete data._id;
data.folder = worldFolder.id;
const existing = game.items.getName(data.name);
if (existing) { await existing.update(data); updated++; }
else { await Item.create(data); created++; }
} catch (e) { console.error("Import err", full, e); ui.notifications.warn(`Błąd importu: ${fn}`); }
}
ui.notifications.info(`GK Module Import: utworzono ${created}, zaktualizowano ${updated}.`);
})();
@@ -0,0 +1,64 @@
// GK Loadout Manager (module)
(async () => {
const NAMES = {
archetype: "Grey Knight (Archetype)",
ability: "Aegis of Titan (Ability)",
nemesis: "Nemesis Force Halberd",
storm: "Grey Knight Armour Integrated Storm Bolter",
aegis: "Aegis Power Armour",
term: "Terminator Aegis (Grey Knights)"
};
const FOLDER = "Grey Knight Pack";
const actor = canvas.tokens.controlled[0]?.actor ?? game.user.character;
if (!actor) return ui.notifications.warn("Zaznacz token albo przypisz postać.");
const mode = await Dialog.prompt({
title: "Grey Knight Loadout Manager",
content: `<p>Wybierz lub zmień loadout:</p>
<label><input type="radio" name="m" value="incognito"> Incognito (bez Aegis)</label><br/>
<label><input type="radio" name="m" value="war" checked> War-Plate (Aegis Power Armour)</label><br/>
<label><input type="radio" name="m" value="term"> Terminator (Terminator Aegis)</label>`,
label: "OK",
callback: html => html.find('input[name="m"]:checked').val()
});
if (!mode) return;
const findWorldItem = (name) => {
const inFolder = game.items.filter(i => i.name === name && i.folder?.name === FOLDER);
if (inFolder.length) return inFolder[0];
return game.items.getName(name) ?? null;
};
const ensureOnActor = async (name) => {
const have = actor.items.getName(name);
if (have) return have;
const worldIt = findWorldItem(name);
if (!worldIt) { ui.notifications.warn(`Brak w Items Directory: ${name} — użyj importera modułu.`); return null; }
const created = await actor.createEmbeddedDocuments("Item", [worldIt.toObject()]);
return created[0];
};
for (const key of Object.keys(NAMES)) await ensureOnActor(NAMES[key]);
const setEquipped = async (name, on) => { const it = actor.items.getName(name); if (it) await it.update({"system.equipped": !!on}); };
await setEquipped(NAMES.aegis, false);
await setEquipped(NAMES.term, false);
await setEquipped(NAMES.storm, false);
await setEquipped(NAMES.nemesis, false);
if (mode === "war") {
await setEquipped(NAMES.aegis, true);
await setEquipped(NAMES.storm, true);
await setEquipped(NAMES.nemesis, true);
} else if (mode === "term") {
await setEquipped(NAMES.term, true);
await setEquipped(NAMES.storm, true);
await setEquipped(NAMES.nemesis, true);
}
const anyAegisEquipped = !!actor.items.find(i => ["armour","armor"].includes(i.type) && /Aegis/.test(i.name) && i.system?.equipped);
const abilItem = actor.items.getName(NAMES.ability);
if (abilItem) for (const ef of abilItem.effects ?? []) if (ef.name?.includes("Aegis Synergy")) await ef.update({ disabled: !anyAegisEquipped });
ui.notifications.info(`Loadout ustawiony: ${mode.toUpperCase()}.`);
})();