diff --git a/wg-voidships-builder/module.json b/wg-voidships-builder/module.json index 08fb7e5..e3e382a 100644 --- a/wg-voidships-builder/module.json +++ b/wg-voidships-builder/module.json @@ -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" }, diff --git a/wg-voidships-builder/scripts/voidship-actor.js b/wg-voidships-builder/scripts/voidship-actor.js new file mode 100644 index 0000000..b908582 --- /dev/null +++ b/wg-voidships-builder/scripts/voidship-actor.js @@ -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 = ` +
+
+
+
+
+
+
+ `; + 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`); +}); diff --git a/wg-voidships-builder/templates/voidship-sheet.hbs b/wg-voidships-builder/templates/voidship-sheet.hbs new file mode 100644 index 0000000..89a6cca --- /dev/null +++ b/wg-voidships-builder/templates/voidship-sheet.hbs @@ -0,0 +1,93 @@ +
+
+ +

+
+ {{#if isVoidship}} + + + {{else}} + + {{/if}} +
+
+ + {{#unless isVoidship}} +
+ This vehicle is not marked as a Voidship. Click Mark as Voidship to enable derived voidship stats. +
+ {{/unless}} + + + +
+
+
+
+

Totals

+
    +
  1. {{cfg.labels.armour}}: {{voidship.total.armour}}
  2. +
  3. {{cfg.labels.integrity}}: {{voidship.total.integrity}}
  4. +
  5. {{cfg.labels.manoeuvre}}: {{voidship.total.manoeuvre}}
  6. +
  7. {{cfg.labels.detection}}: {{voidship.total.detection}}
  8. +
  9. {{cfg.labels.speed}}: {{voidship.total.speed}}
  10. +
  11. {{cfg.labels.spaceFree}}: {{voidship.total.spaceFree}}
  12. +
  13. {{cfg.labels.powerFree}}: {{voidship.total.powerFree}}
  14. +
+
+
+

Slots

+
    +
  1. Prow: {{voidship.slots.prow}}
  2. +
  3. Dorsal: {{voidship.slots.dorsal}}
  4. +
  5. Port: {{voidship.slots.port}}
  6. +
  7. Starboard: {{voidship.slots.starboard}}
  8. +
  9. Keel: {{voidship.slots.keel}}
  10. +
  11. Other: {{voidship.slots.other}}
  12. +
+
+
+
+
+
+

Base (flags)

+
    +
  1. {{cfg.labels.armour}}: {{voidship.base.armour}}
  2. +
  3. {{cfg.labels.integrity}}: {{voidship.base.integrity}}
  4. +
  5. {{cfg.labels.manoeuvre}}: {{voidship.base.manoeuvre}}
  6. +
  7. {{cfg.labels.detection}}: {{voidship.base.detection}}
  8. +
  9. {{cfg.labels.speed}}: {{voidship.base.speed}}
  10. +
  11. {{cfg.labels.space}}: {{voidship.base.space}}
  12. +
  13. {{cfg.labels.power}}: {{voidship.base.power}}
  14. +
+
+
+

Mods (items)

+
    +
  1. {{cfg.labels.armour}}: {{voidship.mods.armour}}
  2. +
  3. {{cfg.labels.integrity}}: {{voidship.mods.integrity}}
  4. +
  5. {{cfg.labels.manoeuvre}}: {{voidship.mods.manoeuvre}}
  6. +
  7. {{cfg.labels.detection}}: {{voidship.mods.detection}}
  8. +
  9. {{cfg.labels.speed}}: {{voidship.mods.speed}}
  10. +
  11. {{cfg.labels.space}}: {{voidship.mods.space}}
  12. +
  13. {{cfg.labels.power}}: {{voidship.mods.power}}
  14. +
+
+
+
+ +
+
+

Mounted items are read from the Actor's inventory. For best results, put per-component data in item.system.voidship:

+
{
+  "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 }
+}
+
+
+
+