Files
vtt_work/wg-voidships-foundry/scripts/voidships.js
T
2025-08-29 21:11:32 +02:00

151 lines
7.0 KiB
JavaScript

/* global game, ui, Roll, foundry, mergeObject */
// Minimal helper for Wrath & Glory voidship play
// Reads data/voidships.json and creates Vehicle actors representing starships.
// Also provides shield tracking, critical hit table creation, and simple actions.
Hooks.once("ready", () => {
game.wgVoidships = {
async importShips(folderName="Voidships") {
const module = game.modules.get("wg-voidships-foundry");
const path = `${module?.path}/data/voidships.json`;
const ships = await (await fetch(path)).json();
let folder = game.folders.find(f => f.type==="Actor" && f.name===folderName);
if (!folder) folder = await Folder.create({name: folderName, type: "Actor"});
for (const s of ships) {
const name = s.name;
const data = {
name,
type: "vehicle",
folder: folder.id,
system: {
mnvr: Math.max(0, s.handling),
value: 20,
traits: { list: [{name:"voidship"}] },
combat: {
size: "colossal",
speed: s.speedVU || 10, // in VU; informational
wounds: { value: 0, bonus: 0, max: s.hull || 20 },
resilience: { bonus: 0, total: s.armour || 10 },
defence: { bonus: s.defence || 3 },
fly: 0
},
notes: `Mounts: ${s.mounts || ""}\nTraits: ${s.traits || ""}`
},
flags: {
"wg-voidships": {
shields: { current: s.shields||0, max: s.shields||0 },
speedVU: s.speedVU||10,
handling: s.handling||0,
cr: s.cr||5,
tur: s.tur||0,
mounts: s.mounts||"",
traits: s.traits||"",
components: s.components||[]
}
},
prototypeToken: { name, width: 4, height: 4, displayName: CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER }
};
const existing = game.actors.find(a => a.name===name);
const actor = existing ?? await Actor.create(data);
if (existing) await actor.update(data);
// Add a simple Item that tracks Void Shields
if (!actor.items.find(i => i.name==="Void Shields")) {
await actor.createEmbeddedDocuments("Item", [{
name: "Void Shields",
type: "gear",
system: {
description: `<p>Tracks starship shield layers (SHD). Current: ${s.shields||0}/${s.shields||0}</p>`,
value: 0,
rarity: "unique",
keywords: "VOIDSHIP"
}
}]);
}
}
ui.notifications.info(`Imported ${ships.length} voidships into folder "${folderName}".`);
},
async ensureCriticalHitsTable() {
const name = "Voidship Critical Hits (d66)";
let table = game.tables.getName(name);
if (table) return table;
const module = game.modules.get("wg-voidships-foundry");
const rows = await (await fetch(`${module?.path}/data/critical_hits.json`)).json();
const results = rows.map(r => ({
type: CONST.TABLE_RESULT_TYPES.TEXT,
text: `<b>${r.d66}</b> — <u>${r.name}</u><br/>${r.effect}`,
range: [1,1],
weight: 1
}));
table = await RollTable.create({name, results, displayRoll: true, formula: "1d36"});
ui.notifications.info("Created roll table: " + name);
return table;
},
// Utilities for shields and damage
async applyHit(a, hits=1, dmg=0) {
// Apply hits to shields first (per-hit, not per-damage)
const shields = foundry.utils.getProperty(a, "flags.wg-voidships.shields") ?? {current:0,max:0};
let absorbed = 0, remaining = hits;
while (remaining>0 && shields.current>0) {
shields.current -= 1; absorbed += 1; remaining -= 1;
}
if (absorbed>0) ui.notifications.info(`${a.name}: ${absorbed} hit(s) absorbed by Void Shields.`);
if (remaining>0 && dmg>0) {
// Simple damage: reduce by Resilience then apply Wounds
const res = foundry.utils.getProperty(a, "system.combat.resilience.total") ?? 10;
const net = Math.max(0, dmg - res);
if (net>0) {
const cur = a.system.combat.wounds.value;
await a.update({"system.combat.wounds.value": cur + net});
ChatMessage.create({content:`${a.name} suffers <b>${net}</b> HULL damage (after AR ${res}).`});
} else {
ChatMessage.create({content:`${a.name} shrugs the damage (AR ${res}).`});
}
}
await a.update({"flags.wg-voidships.shields.current": shields.current});
},
async rechargeShields(a, amount=null) {
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
const cur = foundry.utils.getProperty(a, "flags.wg-voidships.shields.current") ?? 0;
const val = Math.min(max, amount==null ? max : cur + amount);
await a.update({"flags.wg-voidships.shields.current": val});
ui.notifications.info(`${a.name} shields set to ${val}/${max}.`);
},
async setShields(a, val) {
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
val = Math.max(0, Math.min(max, val));
await a.update({"flags.wg-voidships.shields.current": val});
ui.notifications.info(`${a.name} shields now ${val}/${max}.`);
}
};
});
// Macros (global helpers)
game.wgVoidshipsMacros = {
async importVoidships() { return game.wgVoidships.importShips(); },
async createCritTable() { return game.wgVoidships.ensureCriticalHitsTable(); },
async applyHit() {
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
const hits = Number(await Dialog.prompt({title:"Hits", content:"How many hits to apply?", label:"Apply", callback: html => html.querySelector("input")?.value, render: html => html.append('<input type="number" value="1"/>') })) || 1;
const dmg = Number(await Dialog.prompt({title:"Damage", content:"Base damage (after weapon calc)?", label:"Apply", callback: html => html.querySelector("input")?.value, render: html => html.append('<input type="number" value="0"/>') })) || 0;
return game.wgVoidships.applyHit(a, hits, dmg);
},
async rechargeShields() {
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
return game.wgVoidships.rechargeShields(a);
},
async setShields() {
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
const cur = foundry.utils.getProperty(a, "flags.wg-voidships.shields.current") ?? 0;
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
const val = Number(await Dialog.prompt({title:"Set Shields", content:`0..${max}`, label:"Set", callback: html => html.querySelector("input")?.value, render: html => html.append(`<input type="number" value="${cur}"/>`) })) || cur;
return game.wgVoidships.setShields(a, val);
}
};