This commit is contained in:
2025-09-18 09:12:46 +02:00
parent e8ad547844
commit 5892e6148d
3 changed files with 266 additions and 1 deletions
@@ -0,0 +1,165 @@
// W&G Voidships Builder — Voidship Actor Sheet (vehicle+flag emulation)
// v1.0.0 (safe MVP) — works on Foundry v11v13
/* eslint-env es2021 */
const VB_MOD = "wg-voidships-builder";
class VoidshipSheet extends ActorSheet {
static get defaultOptions() {
const opts = super.defaultOptions;
return foundry.utils.mergeObject(opts, {
classes: ["wg-voidships", "sheet", "actor", "voidship"],
template: `modules/${VB_MOD}/templates/voidship-sheet.hbs`,
width: 720,
height: 620,
tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "stats" }]
});
}
/** Prefer to use this sheet only for vehicles marked flagiem voidship */
_canUserView(user) {
return !!user;
}
get isVoidship() {
return this.actor?.getFlag(VB_MOD, "isVoidship") === true;
}
/** Aggregate base + mods from items into derived summary (safe, optional) */
_computeDerived() {
const a = this.actor;
const base = {
armour: Number(a.getFlag(VB_MOD, "base.armour")) || 0,
integrity: Number(a.getFlag(VB_MOD, "base.integrity")) || 0,
manoeuvre: Number(a.getFlag(VB_MOD, "base.manoeuvre")) || 0,
detection: Number(a.getFlag(VB_MOD, "base.detection")) || 0,
speed: Number(a.getFlag(VB_MOD, "base.speed")) || 0,
space: Number(a.getFlag(VB_MOD, "base.space")) || 0,
power: Number(a.getFlag(VB_MOD, "base.power")) || 0
};
const mods = { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 };
const slots = { prow:0, dorsal:0, port:0, starboard:0, keel:0, other:0 };
const items = a?.items ?? [];
for (const it of items) {
const eq = (it.system?.equipped ?? true); // treat as mounted unless explicitly off
const v = it.system?.voidship ?? {};
const m = v.mods ?? {};
if (eq) {
for (const k of Object.keys(mods)) {
const val = Number(m[k]);
if (Number.isFinite(val)) mods[k] += val;
}
}
const slot = String((v.slot || it.getFlag(VB_MOD, "slot") || "")).toLowerCase();
if (slot && slots.hasOwnProperty(slot)) slots[slot]++; else slots.other++;
}
const total = {
armour: base.armour + mods.armour,
integrity: base.integrity + mods.integrity,
manoeuvre: base.manoeuvre + mods.manoeuvre,
detection: base.detection + mods.detection,
speed: base.speed + mods.speed,
space: base.space + mods.space,
power: base.power + mods.power
};
// Headroom (positive = free)
total.spaceFree = total.space;
total.powerFree = total.power;
// If components encode their usage (voidship.use.{space|power})
for (const it of items) {
const use = it.system?.voidship?.use ?? {};
if (Number.isFinite(Number(use.space))) total.spaceFree -= Number(use.space);
if (Number.isFinite(Number(use.power))) total.powerFree -= Number(use.power);
}
return { base, mods, total, slots };
}
async getData(options={}) {
const data = await super.getData(options);
data.isVoidship = this.isVoidship;
data.voidship = this._computeDerived();
// expose a minimal config (labels)
data.cfg = {
labels: {
armour: "Armour",
integrity: "Integrity",
manoeuvre: "Manoeuvre",
detection: "Detection",
speed: "Speed",
space: "Space",
power: "Power",
spaceFree: "Space (free)",
powerFree: "Power (free)"
}
};
return data;
}
activateListeners(html) {
super.activateListeners(html);
// Guard: allow sheet UI even for non-voidship, but prompt to mark
html.find("[data-action='mark-voidship']").on("click", async () => {
await this.actor.setFlag(VB_MOD, "isVoidship", true);
ui.notifications.info("Marked as Voidship.");
this.render(true);
});
html.find("[data-action='unmark-voidship']").on("click", async () => {
await this.actor.unsetFlag(VB_MOD, "isVoidship");
ui.notifications.info("Unmarked Voidship.");
this.render(true);
});
html.find("[data-action='configure-base']").on("click", async () => {
const cur = this._computeDerived().base;
const content = `
<div class="form-group"><label>Armour</label><input type="number" name="armour" value="${cur.armour}"/></div>
<div class="form-group"><label>Integrity</label><input type="number" name="integrity" value="${cur.integrity}"/></div>
<div class="form-group"><label>Manoeuvre</label><input type="number" name="manoeuvre" value="${cur.manoeuvre}"/></div>
<div class="form-group"><label>Detection</label><input type="number" name="detection" value="${cur.detection}"/></div>
<div class="form-group"><label>Speed</label><input type="number" name="speed" value="${cur.speed}"/></div>
<div class="form-group"><label>Space</label><input type="number" name="space" value="${cur.space}"/></div>
<div class="form-group"><label>Power</label><input type="number" name="power" value="${cur.power}"/></div>
`;
new Dialog({
title: "Voidship — Base Stats",
content, buttons: {
ok: {
label: "Save",
callback: async (html) => {
const form = html[0].querySelector("form") || html[0];
const get = (k) => Number(form.querySelector(`[name="${k}"]`)?.value || 0);
const patch = {
"base.armour": get("armour"),
"base.integrity": get("integrity"),
"base.manoeuvre": get("manoeuvre"),
"base.detection": get("detection"),
"base.speed": get("speed"),
"base.space": get("space"),
"base.power": get("power")
};
for (const [k,v] of Object.entries(patch)) await this.actor.setFlag(VB_MOD, k, v);
this.render(true);
}
},
cancel: { label: "Cancel" }
},
default: "ok"
}, { jQuery: true }).render(true);
});
}
}
Hooks.once("init", () => {
// Register the sheet for the 'vehicle' type (W&G Vehicles via Church of Steel)
Actors.registerSheet(VB_MOD, VoidshipSheet, {
types: ["vehicle"],
makeDefault: false,
label: "Voidship (W&G)"
});
// Optional: a convenient factory on game namespace
game.wgVoidships = game.wgVoidships || {};
game.wgVoidships.createVoidship = async function(name="New Voidship") {
const actor = await Actor.create({ name, type: "vehicle", folder: null });
await actor.setFlag(VB_MOD, "isVoidship", true);
ui.notifications.info(`Voidship created: ${name}`);
return actor;
};
console.log(`${VB_MOD}: VoidshipSheet registered`);
});