mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 13:34:42 +00:00
180 lines
7.1 KiB
JavaScript
180 lines
7.1 KiB
JavaScript
// W&G Voidships Builder — Voidship Actor Sheet (vehicle+flag emulation)
|
||
// v1.0.0 (safe MVP) — works on Foundry v11–v13
|
||
/* 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: 680,
|
||
tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "wg" }]
|
||
});
|
||
}
|
||
|
||
/** 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();
|
||
|
||
const hull = this.actor.getFlag(VB_MOD, "hull") || {};
|
||
data.hull = {
|
||
class: hull.class || this.actor.name,
|
||
size: hull.size || "Cruiser",
|
||
slotsAllowed: hull.slots || { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 }
|
||
};
|
||
data.crew = this.actor.getFlag(VB_MOD, "crew") || {
|
||
command: 1, pilot: 1, sensors: 1, engineering: 2,
|
||
gunnery: 2, security: 2, flightDeck: 0, passengers: 0
|
||
};
|
||
data.rtRaw = this.actor.getFlag(VB_MOD, "rtRaw") || {};
|
||
|
||
// 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`);
|
||
});
|