mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 13:34:42 +00:00
aaa
This commit is contained in:
@@ -5,7 +5,14 @@
|
||||
"version": "1.4.8",
|
||||
"compatibility": { "minimum": "12", "verified": "13" },
|
||||
"authors": [{ "name": "Gizmo & GPT-5 Thinking" }],
|
||||
"esmodules": ["scripts/voidships.js"],
|
||||
"scripts": [
|
||||
"scripts/voidships.js",
|
||||
"scripts/voidship-actor.js"
|
||||
],
|
||||
"templates": [
|
||||
"templates/voidship-sheet.hbs"
|
||||
],
|
||||
|
||||
"packs": [
|
||||
{ "label": "W&G Voidship Hulls (Empty)", "name": "wg-voidship-hulls", "path": "packs/wg-voidship-hulls.db", "type": "Actor", "system": "wrath-and-glory" },
|
||||
{ "label": "W&G Voidship Items", "name": "wg-voidship-items", "path": "packs/wg-voidship-items.db", "type": "Item", "system": "wrath-and-glory" },
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// 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: 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`);
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
<form class="voidship-sheet">
|
||||
<header class="sheet-header flexrow" style="align-items:center; gap:.5rem;">
|
||||
<img class="profile" src="{{actor.img}}" width="48" height="48"/>
|
||||
<h1 class="charname"><input name="name" type="text" value="{{actor.name}}"/></h1>
|
||||
<div style="margin-left:auto; display:flex; gap:.5rem;">
|
||||
{{#if isVoidship}}
|
||||
<button type="button" data-action="configure-base"><i class="fas fa-cog"></i> Base</button>
|
||||
<button type="button" data-action="unmark-voidship"><i class="fas fa-times-circle"></i> Unmark</button>
|
||||
{{else}}
|
||||
<button type="button" data-action="mark-voidship"><i class="fas fa-star"></i> Mark as Voidship</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{#unless isVoidship}}
|
||||
<section class="warning" style="margin:.5rem 0; padding:.5rem; background:#531a1a; color:#fff; border:1px solid #aa4444;">
|
||||
This vehicle is not marked as a <b>Voidship</b>. Click <b>Mark as Voidship</b> to enable derived voidship stats.
|
||||
</section>
|
||||
{{/unless}}
|
||||
|
||||
<nav class="tabs" data-group="primary">
|
||||
<a class="item" data-tab="stats">Stats</a>
|
||||
<a class="item" data-tab="components">Components</a>
|
||||
</nav>
|
||||
|
||||
<section class="sheet-body">
|
||||
<div class="tab" data-group="primary" data-tab="stats">
|
||||
<div class="grid grid-2col" style="gap:.5rem;">
|
||||
<div class="card">
|
||||
<h3>Totals</h3>
|
||||
<ol class="properties">
|
||||
<li>{{cfg.labels.armour}}: <b>{{voidship.total.armour}}</b></li>
|
||||
<li>{{cfg.labels.integrity}}: <b>{{voidship.total.integrity}}</b></li>
|
||||
<li>{{cfg.labels.manoeuvre}}: <b>{{voidship.total.manoeuvre}}</b></li>
|
||||
<li>{{cfg.labels.detection}}: <b>{{voidship.total.detection}}</b></li>
|
||||
<li>{{cfg.labels.speed}}: <b>{{voidship.total.speed}}</b></li>
|
||||
<li>{{cfg.labels.spaceFree}}: <b>{{voidship.total.spaceFree}}</b></li>
|
||||
<li>{{cfg.labels.powerFree}}: <b>{{voidship.total.powerFree}}</b></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Slots</h3>
|
||||
<ol class="properties">
|
||||
<li>Prow: <b>{{voidship.slots.prow}}</b></li>
|
||||
<li>Dorsal: <b>{{voidship.slots.dorsal}}</b></li>
|
||||
<li>Port: <b>{{voidship.slots.port}}</b></li>
|
||||
<li>Starboard: <b>{{voidship.slots.starboard}}</b></li>
|
||||
<li>Keel: <b>{{voidship.slots.keel}}</b></li>
|
||||
<li>Other: <b>{{voidship.slots.other}}</b></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="grid grid-2col" style="gap:.5rem;">
|
||||
<div class="card">
|
||||
<h3>Base (flags)</h3>
|
||||
<ol class="properties">
|
||||
<li>{{cfg.labels.armour}}: <b>{{voidship.base.armour}}</b></li>
|
||||
<li>{{cfg.labels.integrity}}: <b>{{voidship.base.integrity}}</b></li>
|
||||
<li>{{cfg.labels.manoeuvre}}: <b>{{voidship.base.manoeuvre}}</b></li>
|
||||
<li>{{cfg.labels.detection}}: <b>{{voidship.base.detection}}</b></li>
|
||||
<li>{{cfg.labels.speed}}: <b>{{voidship.base.speed}}</b></li>
|
||||
<li>{{cfg.labels.space}}: <b>{{voidship.base.space}}</b></li>
|
||||
<li>{{cfg.labels.power}}: <b>{{voidship.base.power}}</b></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Mods (items)</h3>
|
||||
<ol class="properties">
|
||||
<li>{{cfg.labels.armour}}: <b>{{voidship.mods.armour}}</b></li>
|
||||
<li>{{cfg.labels.integrity}}: <b>{{voidship.mods.integrity}}</b></li>
|
||||
<li>{{cfg.labels.manoeuvre}}: <b>{{voidship.mods.manoeuvre}}</b></li>
|
||||
<li>{{cfg.labels.detection}}: <b>{{voidship.mods.detection}}</b></li>
|
||||
<li>{{cfg.labels.speed}}: <b>{{voidship.mods.speed}}</b></li>
|
||||
<li>{{cfg.labels.space}}: <b>{{voidship.mods.space}}</b></li>
|
||||
<li>{{cfg.labels.power}}: <b>{{voidship.mods.power}}</b></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab" data-group="primary" data-tab="components">
|
||||
<section>
|
||||
<p>Mounted items are read from the Actor's inventory. For best results, put per-component data in <code>item.system.voidship</code>:</p>
|
||||
<pre>{
|
||||
"slot": "Prow|Dorsal|Port|Starboard|Keel",
|
||||
"mods": { "armour":0, "integrity":0, "manoeuvre":0, "detection":0, "speed":0, "space":0, "power":0 },
|
||||
"use": { "space":0, "power":0 }
|
||||
}</pre>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
Reference in New Issue
Block a user