From a4c51710ec257ec1fcde8867dcbb0bd3a4b15a24 Mon Sep 17 00:00:00 2001 From: Michal Tuszowski Date: Thu, 18 Sep 2025 21:14:20 +0200 Subject: [PATCH] aaa --- .../packs/wg-voidship-hulls.db | 25 +- .../packs/wg-voidship-items.db | 25 +- .../scripts/voidship-actor.js | 619 +++++++----------- .../templates/voidship-sheet.hbs | 107 ++- wg-voidships-builder/wg-voidships-builder.zip | Bin 0 -> 22419 bytes 5 files changed, 340 insertions(+), 436 deletions(-) create mode 100644 wg-voidships-builder/wg-voidships-builder.zip diff --git a/wg-voidships-builder/packs/wg-voidship-hulls.db b/wg-voidships-builder/packs/wg-voidship-hulls.db index 5991d98..7264856 100644 --- a/wg-voidships-builder/packs/wg-voidship-hulls.db +++ b/wg-voidships-builder/packs/wg-voidship-hulls.db @@ -1 +1,24 @@ -{"name":"Universe-class Mass Conveyor","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Universe-class Mass Conveyor","size":"Cruiser","slots":{"prow":0,"dorsal":0,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"cargo-bay","min":4,"label":"Cargo Holds"},{"tag":"aux-plasma","min":1,"label":"Auxiliary Plasma Generator"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":55},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Super-massive hauler for colony loads and crusade materiel; logistics linchpin.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"rt":{"speed":8,"manoeeuvrability":-10,"detection":0,"armour":12,"hullIntegrity":36,"space":60,"power":55,"weapons":{"prow":0,"dorsal":0,"port":1,"starboard":1,"keel":0},"turret":1,"source":"Battlefleet Koronus p.31 — to verify"},"source":"Battlefleet Koronus, p. 31"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Universe-class Mass Conveyor","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"6836e238cce8d9fc","systemId":"wrath-and-glory","systemVersion":"7.1.1"} +{"name":"Escort Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Escort Hull (Empty)","size":"Escort","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":9,"integrity":18,"manoeuvre":2,"detection":0,"speed":10,"space":30,"power":40},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":9,"hullIntegrity":18,"space":30,"power":40,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":2,"notes":"Empty hull for escorts.","combat":{"size":"colossal","speed":10,"wounds":{"max":18},"resilience":{"total":9},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Frigate Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Frigate Hull (Empty)","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":10,"integrity":24,"manoeuvre":1,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":10,"hullIntegrity":24,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":1,"notes":"Empty frigate hull.","combat":{"size":"colossal","speed":10,"wounds":{"max":24},"resilience":{"total":10},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Light Cruiser Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Light Cruiser Hull (Empty)","size":"Light Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0}},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":0,"speed":9,"space":55,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":9,"armour":11,"hullIntegrity":32,"space":55,"power":60,"weapons":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"turret":2},"mnvr":0,"notes":"Empty light cruiser hull.","combat":{"size":"colossal","speed":9,"wounds":{"max":32},"resilience":{"total":11},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Cruiser Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cruiser Hull (Empty)","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0}},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":0,"speed":8,"space":65,"power":75},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":40,"space":65,"power":75,"weapons":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0},"turret":2},"mnvr":-1,"notes":"Empty cruiser hull.","combat":{"size":"colossal","speed":8,"wounds":{"max":40},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Jericho-class Pilgrim Vessel","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Jericho-class Pilgrim Vessel","size":"Transport","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":55},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":36,"space":60,"power":55,"weapons":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"turret":1},"mnvr":-1,"notes":"Pilgrim conversions with reliquary holds.","combat":{"size":"colossal","speed":8,"wounds":{"max":36},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Vagabond-class Merchant Trader","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Vagabond-class Merchant Trader","size":"Transport","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":55},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":36,"space":60,"power":55,"weapons":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"turret":1},"mnvr":-1,"notes":"Modular cargobays; workhorse trader.","combat":{"size":"colossal","speed":8,"wounds":{"max":36},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Hazeroth-class Privateer","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Hazeroth-class Privateer","size":"Escort","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":9,"hullIntegrity":22,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":2,"notes":"Fast raider with stripped armour.","combat":{"size":"colossal","speed":10,"wounds":{"max":22},"resilience":{"total":9},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Havoc-class Merchant Raider","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Havoc-class Merchant Raider","size":"Escort","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":9,"hullIntegrity":22,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":2,"notes":"Hybrid carrier-raider.","combat":{"size":"colossal","speed":10,"wounds":{"max":22},"resilience":{"total":9},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Sword-class Frigate","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Sword-class Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":10,"hullIntegrity":28,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":1,"notes":"Escort with accurate dorsals.","combat":{"size":"colossal","speed":10,"wounds":{"max":28},"resilience":{"total":10},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Tempest-class Strike Frigate","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Tempest-class Strike Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":10,"hullIntegrity":28,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":1,"notes":"Close-action brawler.","combat":{"size":"colossal","speed":10,"wounds":{"max":28},"resilience":{"total":10},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Dauntless-class Light Cruiser","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Dauntless-class Light Cruiser","size":"Light Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0}},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":0,"speed":9,"space":55,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":9,"armour":11,"hullIntegrity":32,"space":55,"power":60,"weapons":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"turret":2},"mnvr":0,"notes":"Versatile patrol light cruiser.","combat":{"size":"colossal","speed":9,"wounds":{"max":32},"resilience":{"total":11},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Lunar-class Cruiser","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Lunar-class Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0}},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":0,"speed":8,"space":65,"power":75},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":40,"space":65,"power":75,"weapons":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0},"turret":2},"mnvr":-1,"notes":"Balanced line cruiser.","combat":{"size":"colossal","speed":8,"wounds":{"max":40},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Cobra-class Destroyer","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cobra-class Destroyer","size":"Escort","slots":{"prow":1,"dorsal":0,"port":0,"starboard":0,"keel":0}},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":0,"speed":10,"space":32,"power":42},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":9,"hullIntegrity":22,"space":32,"power":42,"weapons":{"prow":1,"dorsal":0,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":2,"notes":"Torpedo destroyer; hunts in packs.","combat":{"size":"colossal","speed":10,"wounds":{"max":22},"resilience":{"total":9},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Falchion-class Frigate","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Falchion-class Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":10,"hullIntegrity":28,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":1,"notes":"Modernised frigate for scouting.","combat":{"size":"colossal","speed":10,"wounds":{"max":28},"resilience":{"total":10},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Turbulent-class Heavy Frigate","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Turbulent-class Heavy Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":10,"integrity":30,"manoeuvre":1,"detection":0,"speed":10,"space":35,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":10,"hullIntegrity":30,"space":35,"power":45,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":1,"notes":"Strengthened frame; better arcs.","combat":{"size":"colossal","speed":10,"wounds":{"max":30},"resilience":{"total":10},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Defiant-class Light Cruiser (Carrier)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Defiant-class Light Cruiser (Carrier)","size":"Light Cruiser","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0}},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":0,"speed":9,"space":55,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":9,"armour":11,"hullIntegrity":32,"space":55,"power":60,"weapons":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"turret":2},"mnvr":0,"notes":"Light carrier with strong attack craft.","combat":{"size":"colossal","speed":9,"wounds":{"max":32},"resilience":{"total":11},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Universe-class Mass Conveyor","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Universe-class Mass Conveyor","size":"Transport","slots":{"prow":0,"dorsal":0,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"cargo-bay","min":4,"label":"Cargo Holds"},{"tag":"aux-plasma","min":1,"label":"Auxiliary Plasma Generator"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":55},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":36,"space":60,"power":55,"weapons":{"prow":0,"dorsal":0,"port":1,"starboard":1,"keel":0},"turret":1},"mnvr":-1,"notes":"Super-massive hauler; logistics linchpin.","combat":{"size":"colossal","speed":8,"wounds":{"max":36},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Goliath-class Factory Ship","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Goliath-class Factory Ship","size":"Transport","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":36,"space":60,"power":60,"weapons":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"turret":1},"mnvr":-1,"notes":"Roving manufactorum/refinery.","combat":{"size":"colossal","speed":8,"wounds":{"max":36},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Conquest-class Star Galleon","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Conquest-class Star Galleon","size":"Transport","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":0,"speed":8,"space":60,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":36,"space":60,"power":60,"weapons":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"turret":1},"mnvr":-1,"notes":"Heavily built merchant galleon.","combat":{"size":"colossal","speed":8,"wounds":{"max":36},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Orion-class Star Clipper","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Orion-class Star Clipper","size":"Transport","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0}},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":0,"speed":9,"space":45,"power":50},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":9,"armour":11,"hullIntegrity":32,"space":45,"power":50,"weapons":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":0,"notes":"Fast passenger/light cargo clipper.","combat":{"size":"colossal","speed":9,"wounds":{"max":32},"resilience":{"total":11},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Cobra-class Destroyer (Renegade)","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cobra-class Destroyer (Renegade)","size":"Escort","slots":{"prow":1,"dorsal":0,"port":0,"starboard":0,"keel":0}},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":0,"speed":10,"space":32,"power":42},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":10,"armour":9,"hullIntegrity":22,"space":32,"power":42,"weapons":{"prow":1,"dorsal":0,"port":0,"starboard":0,"keel":0},"turret":1},"mnvr":2,"notes":"Privateer refits on a Cobra hull.","combat":{"size":"colossal","speed":10,"wounds":{"max":22},"resilience":{"total":9},"defence":{"bonus":3}}}}},"system":{},"effects":[],"items":[]} +{"name":"Secutor-class Monitor-Cruiser","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Secutor-class Monitor-Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":0,"speed":8,"space":65,"power":80},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":40,"space":65,"power":80,"weapons":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"turret":2},"mnvr":-1,"notes":"Mechanicus monitor with heavy dorsals.","combat":{"size":"colossal","speed":8,"wounds":{"max":40},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Lathe-class Monitor-Cruiser","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Lathe-class Monitor-Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0}},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":0,"speed":8,"space":65,"power":80},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":40,"space":65,"power":80,"weapons":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"turret":2},"mnvr":-1,"notes":"Lathes-forged variant; resilient sanctified systems.","combat":{"size":"colossal","speed":8,"wounds":{"max":40},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} +{"name":"Tyrant-class Cruiser","type":"vehicle","img":"icons/svg/ship.svg","flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Tyrant-class Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0}},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":0,"speed":8,"space":65,"power":75},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"rt":{"speed":8,"armour":12,"hullIntegrity":40,"space":65,"power":75,"weapons":{"prow":1,"dorsal":1,"port":2,"starboard":2,"keel":0},"turret":2},"mnvr":-1,"notes":"Macrobattery-focused broadside cruiser.","combat":{"size":"colossal","speed":8,"wounds":{"max":40},"resilience":{"total":12},"defence":{"bonus":2}}}}},"system":{},"effects":[],"items":[]} diff --git a/wg-voidships-builder/packs/wg-voidship-items.db b/wg-voidships-builder/packs/wg-voidship-items.db index 2a3201f..11c7e14 100644 --- a/wg-voidships-builder/packs/wg-voidship-items.db +++ b/wg-voidships-builder/packs/wg-voidship-items.db @@ -1,7 +1,18 @@ -{"_id":"ITEMBRIDGECLV01","name":"Command Bridge (Civilian)","type":"gear","img":"icons/svg/book.svg","flags":{"wg-voidships-builder":{"component":{"kind":"bridge","subtype":"civilian","tags":["bridge"]},"voidOnly":true}},"system":{"description":"Standard command bridge and cogitators.","voidship":{"slot":"Internal","use":{"space":2,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":1,"detection":1,"speed":0,"space":0,"power":0}}},"effects":[]} -{"_id":"ITEMGELLARSTD01","name":"Gellar Field Projector","type":"gear","img":"icons/svg/temple.svg","flags":{"wg-voidships-builder":{"component":{"kind":"gellar","subtype":"standard","tags":["gellar"]},"voidOnly":true}},"system":{"description":"Essential protection in the Immaterium.","voidship":{"slot":"Internal","use":{"space":2,"power":3},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]} -{"_id":"ITEMDRIVEWRP001","name":"Miloslav Warp Drive (Civilian)","type":"gear","img":"icons/svg/wing.svg","flags":{"wg-voidships-builder":{"component":{"kind":"drive","subtype":"warp","tags":["drive","warp"]},"voidOnly":true}},"system":{"description":"Reliable civilian warp drive.","voidship":{"slot":"Internal","use":{"space":3,"power":6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]} -{"_id":"ITEMSENSORAG001","name":"Augur Array (Civilian)","type":"gear","img":"icons/svg/mystery-man.svg","flags":{"wg-voidships-builder":{"component":{"kind":"sensor","subtype":"augur","tags":["sensor","auspex"]},"voidOnly":true}},"system":{"description":"Standard augur/sensor package.","voidship":{"slot":"Aetherics","use":{"space":1,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":2,"speed":0,"space":0,"power":0}}},"effects":[]} -{"_id":"ITEMARMORHL001","name":"Reinforced Hull Plating","type":"gear","img":"icons/svg/armor.svg","flags":{"wg-voidships-builder":{"component":{"kind":"armour","subtype":"plating","tags":["hull","armour"]},"voidOnly":true}},"system":{"description":"Additional armour plates and bracing.","voidship":{"slot":"Internal","use":{"space":1,"power":0},"mods":{"armour":2,"integrity":4,"manoeuvre":-1,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]} -{"_id":"ITEMCARGOHOLD01","name":"Cargo Hold & Lighter Bay","type":"gear","img":"icons/svg/crate.svg","flags":{"wg-voidships-builder":{"component":{"kind":"cargo-bay","subtype":"standard","tags":["cargo-bay","hold"]},"voidOnly":true}},"system":{"description":"General-purpose cargo hold with lighter handling.","voidship":{"slot":"Internal","use":{"space":-5,"power":1},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":-5,"power":0}}},"effects":[]} -{"_id":"ITEMAUXPLASM01","name":"Auxiliary Plasma Generator","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"plasma","subtype":"aux-generator","tags":["aux-plasma"]},"voidOnly":true}},"system":{"description":"Auxiliary plasma reactor increasing available power.","voidship":{"slot":"Internal","use":{"space":2,"power":-6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":6}}},"effects":[]} +{"_id":"ITM0BRIDGECIV01","name":"Command Bridge (Civilian)","type":"gear","img":"icons/svg/book.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"bridge","tags":["bridge"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Standard command bridge and cogitators.","equipped":true,"voidship":{"slot":"Internal","use":{"space":2,"power":2},"mods":{"detection":1,"manoeuvre":1}}},"effects":[]} +{"_id":"ITM0LIFESUPP001","name":"Life Support","type":"gear","img":"icons/svg/poison.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"life-support","tags":["life-support"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Atmospheric recyclers, grav-amps, habitat.","equipped":true,"voidship":{"slot":"Internal","use":{"space":2,"power":2}}},"effects":[]} +{"_id":"ITM0GELLARSTD01","name":"Gellar Field Projector","type":"gear","img":"icons/svg/temple.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"gellar","tags":["gellar"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Essential protection in the Immaterium.","equipped":true,"voidship":{"slot":"Internal","use":{"space":2,"power":3}}},"effects":[]} +{"_id":"ITM0WARPDRIVE01","name":"Warp Drive (Miloslav)","type":"gear","img":"icons/svg/wing.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"warp-drive","tags":["warp-drive"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Reliable civilian warp drive.","equipped":true,"voidship":{"slot":"Internal","use":{"space":3,"power":6}}},"effects":[]} +{"_id":"ITM0REACTOR001A","name":"Plasma Reactor (Civilian)","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"reactor","tags":["reactor"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Primary plasma power generation.","equipped":true,"voidship":{"slot":"Internal","use":{"space":3,"power":-50},"mods":{"power":50}}},"effects":[]} +{"_id":"ITM0AUGURCIV001","name":"Augur Array (Civilian)","type":"gear","img":"icons/svg/mystery-man.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"augur","tags":["augur"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Standard augur/sensor package.","equipped":true,"voidship":{"slot":"Aetherics","use":{"space":1,"power":2},"mods":{"detection":2}}},"effects":[]} +{"_id":"ITM0VSHIELD001A","name":"Void Shields (Single)","type":"gear","img":"icons/svg/shield.svg","flags":{"wg-voidships-builder":{"component":{"kind":"core","subtype":"void-shields","tags":["void-shields"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Standard void shield emitters.","equipped":true,"voidship":{"slot":"Internal","use":{"space":1,"power":3},"mods":{"integrity":2}}},"effects":[]} + +{"_id":"ITM0CARGOHOLD01","name":"Cargo Hold & Lighter Bay","type":"gear","img":"icons/svg/crate.svg","flags":{"wg-voidships-builder":{"component":{"kind":"upgrade","subtype":"cargo-bay","tags":["cargo-bay"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"General-purpose cargo hold.","equipped":true,"voidship":{"slot":"Internal","use":{"space":-5,"power":1},"mods":{"space":-5}}},"effects":[]} +{"_id":"ITM0AUXPLASMA01","name":"Auxiliary Plasma Generator","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"upgrade","subtype":"aux-plasma","tags":["aux-plasma","reactor"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Auxiliary plasma reactor increasing available power.","equipped":true,"voidship":{"slot":"Internal","use":{"space":2,"power":-6},"mods":{"power":6}}},"effects":[]} +{"_id":"ITM0ARMOURPLT01","name":"Reinforced Hull Plating","type":"gear","img":"icons/svg/armor.svg","flags":{"wg-voidships-builder":{"component":{"kind":"upgrade","subtype":"plating","tags":["hull","armour"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Additional armour plates and bracing.","equipped":true,"voidship":{"slot":"Internal","use":{"space":1,"power":0},"mods":{"armour":2,"integrity":4,"manoeuvre":-1}}},"effects":[]} +{"_id":"ITM0MANEUVER01A","name":"High-Output Manoeuvring Thrusters","type":"gear","img":"icons/svg/wing.svg","flags":{"wg-voidships-builder":{"component":{"kind":"upgrade","subtype":"thrusters","tags":["thrusters"],"allowedSlots":[]},"voidOnly":true}},"system":{"description":"Vectorable RCS and trim jets.","equipped":true,"voidship":{"slot":"External","use":{"space":1,"power":2},"mods":{"manoeuvre":2}}},"effects":[]} + +{"_id":"ITM0MACROPORT01","name":"Mars-Pattern Macrobattery (Port)","type":"gear","img":"icons/svg/sword.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"macrobattery","tags":["weapon"],"allowedSlots":["port"]},"voidOnly":true}},"system":{"description":"Port broadside macro-cannons.","equipped":true,"voidship":{"slot":"Port","use":{"space":2,"power":3},"mods":{}}},"effects":[]} +{"_id":"ITM0MACROSTBD01","name":"Mars-Pattern Macrobattery (Starboard)","type":"gear","img":"icons/svg/sword.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"macrobattery","tags":["weapon"],"allowedSlots":["starboard"]},"voidOnly":true}},"system":{"description":"Starboard broadside macro-cannons.","equipped":true,"voidship":{"slot":"Starboard","use":{"space":2,"power":3},"mods":{}}},"effects":[]} +{"_id":"ITM0LANCEDORS01","name":"Sunsear Laser Battery (Dorsal)","type":"gear","img":"icons/svg/sun.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"laser-battery","tags":["weapon"],"allowedSlots":["dorsal"]},"voidOnly":true}},"system":{"description":"High-energy dorsal lasers.","equipped":true,"voidship":{"slot":"Dorsal","use":{"space":2,"power":4},"mods":{}}},"effects":[]} +{"_id":"ITM0PROWTORP01","name":"Prow Torpedo Tubes","type":"gear","img":"icons/svg/anchor.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"torpedoes","tags":["weapon"],"allowedSlots":["prow"]},"voidOnly":true}},"system":{"description":"Prow-mounted torpedo tubes.","equipped":true,"voidship":{"slot":"Prow","use":{"space":3,"power":3},"mods":{}}},"effects":[]} +{"_id":"ITM0KEELNOVA01","name":"Keel Nova Cannon","type":"gear","img":"icons/svg/explosion.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"nova-cannon","tags":["weapon"],"allowedSlots":["keel"]},"voidOnly":true}},"system":{"description":"Spinal nova cannon on the keel.","equipped":true,"voidship":{"slot":"Keel","use":{"space":4,"power":6},"mods":{}}},"effects":[]} diff --git a/wg-voidships-builder/scripts/voidship-actor.js b/wg-voidships-builder/scripts/voidship-actor.js index 2cebd21..054083f 100644 --- a/wg-voidships-builder/scripts/voidship-actor.js +++ b/wg-voidships-builder/scripts/voidship-actor.js @@ -1,10 +1,24 @@ // W&G Voidships Builder — Voidship Actor Sheet (vehicle+flag emulation) -// v1.0.1 — works on Foundry v11–v13 +// v1.1.0 (mapping weapons + core reqs + safer drops) — Foundry v11–v13 /* eslint-env es2021 */ const VB_MOD = "wg-voidships-builder"; -const VB_DEBUG = true; // debug; później zrobimy game.settings +const VB_DEBUG = true; // debug on; switch later via setting function dbg(...args) { if (VB_DEBUG) console.debug("[VB]", ...args); } +// Canonical slot keys +const SLOT_KEYS = ["prow","dorsal","port","starboard","keel"]; + +// Default RT-like core requirements (if hull.requirements is empty) +const DEFAULT_CORE_REQS = [ + { tag: "bridge", min: 1, label: "Bridge" }, + { tag: "life-support", min: 1, label: "Life Support" }, + { tag: "gellar", min: 1, label: "Gellar Field" }, + { tag: "warp-drive", min: 1, label: "Warp Drive" }, + { tag: "reactor", min: 1, label: "Plasma/Power Reactor" }, + { tag: "augur", min: 1, label: "Augur Array" }, + { tag: "void-shields", min: 1, label: "Void Shields" } +]; + class VoidshipSheet extends ActorSheet { static get defaultOptions() { const opts = super.defaultOptions; @@ -20,35 +34,49 @@ class VoidshipSheet extends ActorSheet { _canUserView(user) { return !!user; } get isVoidship() { return this.actor?.getFlag(VB_MOD, "isVoidship") === true; } - /** Prostszą agregację zostawiamy do walidacji ręcznej etc. */ + /** Sum flags.base + items mods + usage */ _computeDerived() { const a = this.actor; const b = a.getFlag(VB_MOD, "base") || {}; const base = { - armour: Number(b.armour) || 0, - integrity: Number(b.integrity) || 0, - manoeuvre: Number(b.manoeuvre) || 0, - detection: Number(b.detection) || 0, - speed: Number(b.speed) || 0, - space: Number(b.space) || 0, - power: Number(b.power) || 0 + armour: Number(b.armour)||0, + integrity: Number(b.integrity)||0, + manoeuvre: Number(b.manoeuvre)||0, + detection: Number(b.detection)||0, + speed: Number(b.speed)||0, + space: Number(b.space)||0, + power: Number(b.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); - const v = it.system?.voidship ?? {}; - const m = v.mods ?? {}; - if (eq) { + + const mods = { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 }; + const usage = { space:0, power:0 }; + const slotsUsed = { prow:0, dorsal:0, port:0, starboard:0, keel:0 }; + + for (const it of a.items.contents) { + const sys = it.system ?? {}; + const v = sys.voidship ?? {}; + const equipped = (sys.equipped ?? true) === true; // treat true/undefined as mounted + const comp = it.getFlag(VB_MOD, "component") || v.component || {}; // our kind/tags live in flags + + if (equipped) { + // accumulate mods + const m = v.mods || {}; for (const k of Object.keys(mods)) { - const val = Number(m[k]); - if (Number.isFinite(val)) mods[k] += val; + const n = Number(m[k]); + if (Number.isFinite(n)) mods[k] += n; } + // accumulate usage + const u = v.use || {}; + if (Number.isFinite(Number(u.space))) usage.space += Number(u.space); + if (Number.isFinite(Number(u.power))) usage.power += Number(u.power); + + // weapon slots count ONLY for kind === "weapon" + const kind = String(comp?.kind || "").toLowerCase(); + const slot = String(v.slot || "").toLowerCase(); + if (kind === "weapon" && SLOT_KEYS.includes(slot)) slotsUsed[slot] += 1; } - const slot = String((v.slot || it.getFlag(VB_MOD, "slot") || "")).toLowerCase(); - if (slot && Object.prototype.hasOwnProperty.call(slots, slot)) slots[slot]++; else slots.other++; } + const total = { armour: base.armour + mods.armour, integrity: base.integrity + mods.integrity, @@ -56,341 +84,229 @@ class VoidshipSheet extends ActorSheet { detection: base.detection + mods.detection, speed: base.speed + mods.speed, space: base.space + mods.space, - power: base.power + mods.power + power: base.power + mods.power, + spaceFree: base.space + mods.space - usage.space, + powerFree: base.power + mods.power - usage.power }; - total.spaceFree = total.space; - total.powerFree = total.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); - } - dbg("computeDerived: base=", base, "mods=", mods, "total=", total, "slots=", slots); - return { base, mods, total, slots }; + dbg("computeDerived:", { base, mods, usage, total, slotsUsed }); + return { base, mods, usage, total, slotsUsed }; } +} - async getData(options) { - const data = await super.getData(options); - const actor = this.actor; +/** helpers */ +Hooks.once("init", () => { + if (!Handlebars.helpers.signed) { + Handlebars.registerHelper("signed", v => { + const n = Number(v||0); if (!Number.isFinite(n) || n===0) return "±0"; + return (n>0?"+":"−")+Math.abs(n); + }); + } + // pretty-json (for debug blocks) + if (!Handlebars.helpers.tojson) { + Handlebars.registerHelper("tojson", v => JSON.stringify(v, null, 2)); + } +}); - // ——— FLAGS (cały namespace) - const ns = actor.flags?.[VB_MOD] || {}; - const hull = ns.hull || { class: actor.name, size: "Cruiser", slots: { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 } }; - const base = ns.base || { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 }; - const crew = ns.crew || { command: 1, pilot: 1, sensors: 1, engineering: 2, gunnery: 2, security: 2, flightDeck: 0, passengers: 0 }; - const rtRaw = ns.rtRaw || {}; - const rt = rtRaw.rt || {}; +VoidshipSheet.prototype.getData = async function(options) { + const data = await super.getData(options); + const actor = this.actor; - // ——— RT snapshot do karty - const rtCard = { - speed: rt.speed ?? rtRaw.combat?.speed ?? 0, - manoeuvrability: rt.manoeuvrability ?? rtRaw.mnvr ?? 0, - detection: rt.detection ?? 0, - hullIntegrity: rt.hullIntegrity ?? rtRaw.combat?.wounds?.max ?? 0, - armour: rt.armour ?? 0, - space: rt.space ?? 0, - power: rt.power ?? 0, - weapons: { - prow: rt.weapons?.prow ?? 0, - dorsal: rt.weapons?.dorsal ?? 0, - port: rt.weapons?.port ?? 0, - starboard: rt.weapons?.starboard ?? 0, - keel: rt.weapons?.keel ?? 0 - }, - turret: rt.turret ?? 0, - notes: rtRaw.notes ?? "", - source: rt.source ?? "" - }; + const flags = actor.getFlag(VB_MOD, "") || {}; + const hull = flags.hull || { class: actor.name, size: "Cruiser", slots: { prow:0,dorsal:0,port:0,starboard:0,keel:0 } }; + const base = flags.base || { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 }; + const crew = flags.crew || { command:1,pilot:1,sensors:1,engineering:2,gunnery:2,security:2,flightDeck:0,passengers:0 }; + const rtRaw = flags["rtRaw"] || {}; + const rt = rtRaw.rt || {}; - // ——— KOMPONENTY (tylko nasze) - const components = actor.items.contents - .filter(i => i.flags?.[VB_MOD]?.component?.kind) - .map(i => { - const vs = i.system?.voidship ?? i.flags?.[VB_MOD]?.voidship ?? {}; - const v = foundry.utils.deepClone(vs); - v.itemId = i.id; - v.name = i.name; - v.equipped = (i.system?.equipped ?? true); - v.slot = (v.slot || "Internal"); - v.use = Object.assign({ space: 0, power: 0 }, v.use); - v.mods = Object.assign({ armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 }, v.mods); - v.kind = i.flags?.[VB_MOD]?.component?.kind; - v.tags = i.flags?.[VB_MOD]?.component?.tags ?? []; - return v; - }); + const rtCard = { + speed: rt.speed ?? rtRaw.combat?.speed ?? 0, + manoeuvrability: rt.manoeuvrability ?? rtRaw.mnvr ?? 0, + detection: rt.detection ?? 0, + hullIntegrity: rt.hullIntegrity ?? rtRaw.combat?.wounds?.max ?? 0, + armour: rt.armour ?? 0, + space: rt.space ?? 0, + power: rt.power ?? 0, + weapons: { + prow: rt.weapons?.prow ?? 0, + dorsal: rt.weapons?.dorsal ?? 0, + port: rt.weapons?.port ?? 0, + starboard: rt.weapons?.starboard ?? 0, + keel: rt.weapons?.keel ?? 0 + }, + turret: rt.turret ?? 0, + notes: rtRaw.notes ?? "", + source: rt.source ?? "" + }; - // ——— SUMY MODYFIKATORÓW (equipped) - const modsSum = components.reduce((acc, c) => { - if (!c.equipped) return acc; - acc.armour += Number(c.mods.armour || 0); - acc.integrity += Number(c.mods.integrity || 0); - acc.manoeuvre += Number(c.mods.manoeuvre || 0); - acc.detection += Number(c.mods.detection || 0); - acc.speed += Number(c.mods.speed || 0); - acc.space += Number(c.mods.space || 0); - acc.power += Number(c.mods.power || 0); - return acc; - }, { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 }); + // components (only items having system.voidship) + const components = actor.items.contents + .filter(i => i.system?.voidship) + .map(i => { + const v = foundry.utils.deepClone(i.system.voidship || {}); + const comp = i.getFlag(VB_MOD, "component") || v.component || {}; + return { + itemId: i.id, + name: i.name, + equipped: (i.system?.equipped ?? true) === true, + slot: v.slot || "Internal", + use: Object.assign({ space:0, power:0 }, v.use), + mods: Object.assign({ armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 }, v.mods), + component: { kind: String(comp.kind||"").toLowerCase(), tags: comp.tags || [], allowedSlots: comp.allowedSlots || [] } + }; + }); - // ——— CAPACITY (use) dla equipped - const useTotal = components.reduce((acc, c) => { - if (!c.equipped) return acc; - acc.space += Number(c.use.space || 0); - acc.power += Number(c.use.power || 0); - return acc; - }, { space: 0, power: 0 }); + // derived totals and slotsUsed (weapons only) + const { base:baseCalc, mods, usage, total, slotsUsed } = this._computeDerived(); - // ——— TOTALS = BASE + MODS - const totals = { - armour: (Number(base.armour) || 0) + modsSum.armour, - integrity: (Number(base.integrity) || 0) + modsSum.integrity, - manoeuvre: (Number(base.manoeuvre) || 0) + modsSum.manoeuvre, - detection: (Number(base.detection) || 0) + modsSum.detection, - speed: (Number(base.speed) || 0) + modsSum.speed, - space: (Number(base.space) || 0) + modsSum.space, - power: (Number(base.power) || 0) + modsSum.power - }; - totals.spaceFree = totals.space - useTotal.space; - totals.powerFree = totals.power - useTotal.power; - - // ——— SLOTY (użyte vs limit) - const slotsUsed = components.reduce((acc, c) => { - if (!c.equipped) return acc; - const k = (c.slot || "Internal").toLowerCase(); - if (["prow","dorsal","port","starboard","keel"].includes(k)) acc[k] = (acc[k]||0)+1; - return acc; - }, { prow:0, dorsal:0, port:0, starboard:0, keel:0 }); - - // ——— Ostrzeżenia + wymagania - const warnings = {}; - const anySlotOver = + // Warnings: slots over-capacity (compare ONLY weapons vs hull slot caps) + const warnings = {}; + if (components.length) { + const over = (slotsUsed.prow > (hull.slots?.prow || 0)) || (slotsUsed.dorsal > (hull.slots?.dorsal || 0)) || (slotsUsed.port > (hull.slots?.port || 0)) || (slotsUsed.starboard > (hull.slots?.starboard || 0)) || (slotsUsed.keel > (hull.slots?.keel || 0)); - if (anySlotOver) warnings.slots = "One or more weapon slot groups exceed hull limits."; - if (totals.spaceFree < 0) warnings.capacity = "Not enough Space for installed components."; - if (totals.powerFree < 0) warnings.capacity = (warnings.capacity ? warnings.capacity + " " : "") + "Not enough Power for installed components."; + if (over) warnings.slots = "One or more weapon slot groups exceed hull limits."; + } + if (total.spaceFree < 0 || total.powerFree < 0) { + warnings.capacity = "Not enough Space/Power for installed components."; + } - const req = (hull.requirements ?? []).map(r => ({ tag: r.tag, min: Number(r.min||0), label: r.label || r.tag })); - const haveByTag = components.reduce((acc, c) => { - if (!c.equipped) return acc; - const tags = Array.isArray(c.tags) ? c.tags : []; - for (const t of tags) acc[t] = (acc[t]||0) + 1; - return acc; - }, {}); - const missing = req.filter(r => (haveByTag[r.tag]||0) < r.min); - if (missing.length) { - const list = missing.map(m => `${m.label}: ${haveByTag[m.tag]||0}/${m.min}`).join("; "); - warnings.requirements = `Missing required components → ${list}`; + // Validate allowedSlots for weapons (if provided) + const badMounts = components.filter(c => + c.equipped && + c.component?.kind === "weapon" && + Array.isArray(c.component.allowedSlots) && + c.component.allowedSlots.length && + !c.component.allowedSlots.map(s => String(s).toLowerCase()).includes(String(c.slot).toLowerCase()) + ); + if (badMounts.length) { + warnings.mounts = `Invalid weapon placement: ${badMounts.map(b=>`${b.name}→${b.slot}`).join(", ")}.`; + } + + // Requirements — gather tags from equipped components + const tagCounts = {}; + for (const c of components) { + if (!c.equipped) continue; + for (const t of (c.component?.tags || [])) { + const key = String(t).toLowerCase(); + tagCounts[key] = (tagCounts[key] || 0) + 1; } - - // ——— Dane do template - data.hull = hull; - data.base = base; - data.crew = crew; - data.rtCard = rtCard; - data.components = components; - data.modsSum = modsSum; - data.totals = totals; - data.slotsUsed = slotsUsed; - data.warnings = warnings; - data.flags = actor.flags; - - return data; } - - activateListeners(html) { - super.activateListeners(html); - - // Mark/Unmark - 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); - }); - - // Base config dialog - 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 patchBase = { - armour: get("armour"), - integrity: get("integrity"), - manoeuvre: get("manoeuvre"), - detection: get("detection"), - speed: get("speed"), - space: get("space"), - power: get("power") - }; - await this.actor.setFlag(VB_MOD, "base", patchBase); - this.render(true); - } - }, - cancel: { label: "Cancel" } - }, - default: "ok" - }, { jQuery: true }).render(true); - }); - - // Toggle equipped - html.on("click", "[data-action='comp-toggle']", async (ev) => { - const tr = ev.currentTarget.closest("tr[data-item-id]"); - const itemId = tr?.dataset.itemId; - if (!itemId) return; - const item = this.actor.items.get(itemId); - const cur = !!(item.system?.equipped ?? true); - await item.update({ "system.equipped": !cur }); - this.render(true); - }); - - // Edit component - html.on("click", "[data-action='comp-edit']", async (ev) => { - const tr = ev.currentTarget.closest("tr[data-item-id]"); - const itemId = tr?.dataset.itemId; - if (!itemId) return; - const item = this.actor.items.get(itemId); - const v = foundry.utils.deepClone(item.system?.voidship || {}); - v.slot = v.slot || "Internal"; - v.use = Object.assign({ space: 0, power: 0 }, v.use); - v.mods = Object.assign({ armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 }, v.mods); - const eq = (item.system?.equipped ?? true); - - const slotOptions = ["Prow", "Dorsal", "Port", "Starboard", "Keel", "Internal", "Aetherics"] - .map(s => ``).join(""); - - const content = ` -
-
-
-
Use -
-
-
-
Mods - ${["armour", "integrity", "manoeuvre", "detection", "speed", "space", "power"].map(k => ` -
- -
`).join("")} -
-
`; - - new Dialog({ - title: `Edit Component — ${item.name}`, - content, - buttons: { - ok: { - label: "Save", - callback: async (dlg) => { - const f = dlg[0].querySelector("form"); - const read = (sel, num = false) => { - const el = f.querySelector(`[name="${sel}"]`); - if (!el) return num ? 0 : ""; - if (el.type === "checkbox") return el.checked; - const val = el.value ?? (el.textContent ?? ""); - return num ? Number(val || 0) : val; - }; - const patch = { - "system.voidship.slot": read("slot"), - "system.voidship.use.space": read("use.space", true), - "system.voidship.use.power": read("use.power", true), - "system.voidship.mods.armour": read("mods.armour", true), - "system.voidship.mods.integrity": read("mods.integrity", true), - "system.voidship.mods.manoeuvre": read("mods.manoeuvre", true), - "system.voidship.mods.detection": read("mods.detection", true), - "system.voidship.mods.speed": read("mods.speed", true), - "system.voidship.mods.space": read("mods.space", true), - "system.voidship.mods.power": read("mods.power", true), - "system.equipped": !!read("equipped") - }; - await item.update(patch); - // miękka walidacja - const { total, slots } = this._computeDerived(); - const flags = this.actor.getFlag(VB_MOD, "") || {}; - const hull = flags.hull || { slots: { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 } }; - const overSlots = ["prow", "dorsal", "port", "starboard", "keel"].filter(k => slots[k] > (hull.slots?.[k] || 0)); - if (overSlots.length) ui.notifications.warn(`Slot limit exceeded: ${overSlots.join(", ")}`); - if (total.spaceFree < 0) ui.notifications.warn(`Not enough Space (over by ${Math.abs(total.spaceFree)})`); - if (total.powerFree < 0) ui.notifications.warn(`Not enough Power (over by ${Math.abs(total.powerFree)})`); - this.render(true); - } - }, - cancel: { label: "Cancel" } - }, - default: "ok" - }, { jQuery: true }).render(true); - }); - - // Remove - html.on("click", "[data-action='comp-remove']", async (ev) => { - const tr = ev.currentTarget.closest("tr[data-item-id]"); - const itemId = tr?.dataset.itemId; - if (!itemId) return; - const item = this.actor.items.get(itemId); - new Dialog({ - title: "Remove Component", - content: `

Remove ${foundry.utils.escapeHTML(item.name)} from this voidship?

`, - buttons: { - ok: { label: "Remove", callback: () => this.actor.deleteEmbeddedDocuments("Item", [itemId]) }, - cancel: { label: "Cancel" } - }, - default: "ok" - }).render(true); - }); - - // Auto-refresh po zmianach itemów tego aktora - const refreshIfMine = (doc) => { try { if (doc?.parent?.id === this.actor?.id) this.render(false); } catch(_) {} }; - Hooks.on('createItem', refreshIfMine); - Hooks.on('updateItem', refreshIfMine); - Hooks.on('deleteItem', refreshIfMine); - this.once("close", () => { - Hooks.off('createItem', refreshIfMine); - Hooks.off('updateItem', refreshIfMine); - Hooks.off('deleteItem', refreshIfMine); - }); + const reqs = (Array.isArray(hull.requirements) && hull.requirements.length) ? hull.requirements : DEFAULT_CORE_REQS; + const missing = []; + for (const r of reqs) { + const need = Number(r.min||0); + if (!need) continue; + const have = tagCounts[String(r.tag||"").toLowerCase()] || 0; + if (have < need) missing.push(`${r.label||r.tag}: ${have}/${need}`); } -} + if (missing.length) warnings.requirements = `Missing required systems — ${missing.join("; ")}`; + + // expose to hbs + data.hull = hull; + data.base = base; + data.crew = crew; + data.mods = mods; + data.usage = usage; + data.totals = total; + data.slotsUsed = slotsUsed; + data.rtCard = rtCard; + data.components = components; + data.warnings = warnings; + data.debug = { flags, tagCounts, reqs }; // handy raw blocks + + return data; +}; + +VoidshipSheet.prototype.activateListeners = function(html) { + super.activateListeners(html); + + // mark/unmark + 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); + }); + + // configure base + 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 (dlg) => { + const form = dlg[0].querySelector("form") || dlg[0]; + const get = (k) => Number(form.querySelector(`[name="${k}"]`)?.value || 0); + const patchBase = { + armour: get("armour"), + integrity: get("integrity"), + manoeuvre: get("manoeuvre"), + detection: get("detection"), + speed: get("speed"), + space: get("space"), + power: get("power") + }; + await this.actor.setFlag(VB_MOD, "base", patchBase); + this.render(true); + }}, + cancel: { label: "Cancel" } + }, default: "ok" + }, { jQuery: true }).render(true); + }); + + // toggle raw debug blocks + html.find("[data-action='toggle-raw']").on("click", (ev) => { + const el = html.find(".vb-raw")[0]; + if (el) el.style.display = (el.style.display === "none") ? "block" : "none"; + }); +}; + +// ACCEPT only items having system.voidship (and optionally flags[VB_MOD].component) +VoidshipSheet.prototype._onDropItem = async function(event, data) { + try { + const resolved = await Item.fromDropData(data); + if (!resolved) return ui.notifications.error("Drop failed: cannot resolve item (no UUID/DATA)."); + + const sys = resolved.system ?? {}; + if (!sys.voidship) return ui.notifications.warn("This item is not a voidship component."); + // optional hard guard: if not our 'voidOnly' flag, still allow — but warn + const vFlags = resolved.getFlag(VB_MOD, "component"); + if (!vFlags) dbg("Component missing flags; proceeding with system.voidship only."); + + // pass to Foundry to actually create the embedded item + const created = await this.actor.createEmbeddedDocuments("Item", [resolved.toObject()]); + if (created?.length) { + ui.notifications.info(`Installed component: ${created[0].name}`); + this.render(true); + } + } catch (err) { + console.error(err); + ui.notifications.error("Drop failed: cannot resolve item data (no UUID/DATA)."); + } +}; -// ———————————————————————————————————————————————————————————————— -// INIT: helper + rejestracja sheetu + domyślki/sanitizacja Hooks.once("init", () => { - if (!Handlebars.helpers.signed) { - Handlebars.registerHelper("signed", function (value) { - const n = Number(value || 0); - if (Number.isNaN(n) || n === 0) return "±0"; - return (n > 0 ? "+" : "−") + Math.abs(n); - }); - } - Actors.registerSheet(VB_MOD, VoidshipSheet, { types: ["vehicle"], makeDefault: false, label: "Voidship (W&G)" }); - game.wgVoidships = game.wgVoidships || {}; game.wgVoidships.createVoidship = async function (name = "New Voidship") { const actor = await Actor.create({ name, type: "vehicle", folder: null }); @@ -398,44 +314,5 @@ Hooks.once("init", () => { ui.notifications.info(`Voidship created: ${name}`); return actor; }; - - // Auto-domyślki dla itemów dodawanych do voidshipów - Hooks.on("preCreateItem", (item) => { - const parent = item?.parent; - if (!(parent instanceof Actor)) return; - if (parent.getFlag(VB_MOD, "isVoidship") !== true) return; - - const vs = item.system?.voidship; - if (!vs) { - item.updateSource({ - "system.voidship": { - slot: "Internal", - use: { space: 0, power: 0 }, - mods: { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 } - } - }); - } - if (item.system?.equipped === undefined) item.updateSource({ "system.equipped": true }); - }); - - // Sanitizacja liczb przy update (żeby nie trafiały NaN) - Hooks.on("preUpdateItem", (item, change) => { - const parent = item?.parent; - if (!(parent instanceof Actor)) return; - if (parent.getFlag(VB_MOD, "isVoidship") !== true) return; - - const fixNum = (path) => { - const v = foundry.utils.getProperty(change, path); - if (v === undefined) return; - const n = Number(v); - foundry.utils.setProperty(change, path, Number.isFinite(n) ? n : 0); - }; - [ - "system.voidship.use.space", "system.voidship.use.power", - "system.voidship.mods.armour", "system.voidship.mods.integrity", "system.voidship.mods.manoeuvre", - "system.voidship.mods.detection", "system.voidship.mods.speed", "system.voidship.mods.space", "system.voidship.mods.power" - ].forEach(fixNum); - }); - console.log(`${VB_MOD}: VoidshipSheet registered`); }); diff --git a/wg-voidships-builder/templates/voidship-sheet.hbs b/wg-voidships-builder/templates/voidship-sheet.hbs index 6a5a6a4..e4c9b11 100644 --- a/wg-voidships-builder/templates/voidship-sheet.hbs +++ b/wg-voidships-builder/templates/voidship-sheet.hbs @@ -5,6 +5,7 @@
+ {{#if (eq actor.flags.wg-voidships-builder.isVoidship true)}} {{else}} @@ -27,35 +28,27 @@

Totals

    -
  1. Armour: {{totals.armour}} (base {{base.armour}}, mods {{signed modsSum.armour}})
  2. -
  3. Integrity: {{totals.integrity}} (base {{base.integrity}}, mods {{signed modsSum.integrity}})
  4. -
  5. Manoeuvre: {{totals.manoeuvre}} (base {{base.manoeuvre}}, mods {{signed modsSum.manoeuvre}})
  6. -
  7. Detection: {{totals.detection}} (base {{base.detection}}, mods {{signed modsSum.detection}})
  8. -
  9. Speed: {{totals.speed}} (base {{base.speed}}, mods {{signed modsSum.speed}})
  10. -
  11. Space (free/total): {{totals.spaceFree}} / {{totals.space}}
  12. -
  13. Power (free/total): {{totals.powerFree}} / {{totals.power}}
  14. +
  15. Armour: {{totals.armour}}
  16. +
  17. Integrity: {{totals.integrity}}
  18. +
  19. Manoeuvre: {{totals.manoeuvre}}
  20. +
  21. Detection: {{totals.detection}}
  22. +
  23. Speed: {{totals.speed}}
  24. +
  25. Space (free / total): {{totals.spaceFree}} / {{totals.space}}
  26. +
  27. Power (free / total): {{totals.powerFree}} / {{totals.power}}

Hull

  1. Class: {{hull.class}} ({{hull.size}})
  2. -
  3. Slots Allowed — Prow: {{hull.slots.prow}}, Dorsal: {{hull.slots.dorsal}}, Port: - {{hull.slots.port}}, Starboard: {{hull.slots.starboard}}, Keel: {{hull.slots.keel}} -
  4. +
  5. Slots Allowed — Prow: {{hull.slots.prow}}, Dorsal: {{hull.slots.dorsal}}, Port: {{hull.slots.port}}, Starboard: {{hull.slots.starboard}}, Keel: {{hull.slots.keel}}
- - {{#if warnings.capacity}} -

{{warnings.capacity}}

- {{/if}} - {{#if warnings.slots}} -

{{warnings.slots}}

- {{/if}} - {{#if warnings.requirements}} -

{{warnings.requirements}}

- {{/if}} + {{#if warnings.capacity}}

{{warnings.capacity}}

{{/if}} + {{#if warnings.slots}}

{{warnings.slots}}

{{/if}} + {{#if warnings.mounts}}

{{warnings.mounts}}

{{/if}} + {{#if warnings.requirements}}

{{warnings.requirements}}

{{/if}}
@@ -74,19 +67,19 @@

Mods (items)

    -
  1. Armour: {{signed modsSum.armour}}
  2. -
  3. Integrity: {{signed modsSum.integrity}}
  4. -
  5. Manoeuvre: {{signed modsSum.manoeuvre}}
  6. -
  7. Detection: {{signed modsSum.detection}}
  8. -
  9. Speed: {{signed modsSum.speed}}
  10. -
  11. Space: {{signed modsSum.space}}
  12. -
  13. Power: {{signed modsSum.power}}
  14. +
  15. Armour: {{mods.armour}}
  16. +
  17. Integrity: {{mods.integrity}}
  18. +
  19. Manoeuvre: {{mods.manoeuvre}}
  20. +
  21. Detection: {{mods.detection}}
  22. +
  23. Speed: {{mods.speed}}
  24. +
  25. Space: {{mods.space}}
  26. +
  27. Power: {{mods.power}}
- +
@@ -101,9 +94,6 @@
  • Power {{rtCard.power}}
  • Turret {{rtCard.turret}}
  • - {{#if rtCard.source}} -

    Source: {{rtCard.source}}

    - {{/if}}

    RT — Weapon Capacity

    @@ -114,9 +104,8 @@
  • Starboard: {{rtCard.weapons.starboard}}
  • Keel: {{rtCard.weapons.keel}}
  • - {{#if rtCard.notes}} -

    {{rtCard.notes}}

    - {{/if}} + {{#if rtCard.notes}}

    {{rtCard.notes}}

    {{/if}} + {{#if rtCard.source}}

    Source: {{rtCard.source}}

    {{/if}}
    @@ -154,9 +143,8 @@
  • Space (free / total): {{totals.spaceFree}} / {{totals.space}}
  • Power (free / total): {{totals.powerFree}} / {{totals.power}}
  • - {{#if warnings.capacity}}

    {{warnings.capacity}}

    {{/if}}
    -

    Slots Used

    +

    Weapon Slots Used

    1. Prow: {{slotsUsed.prow}} / {{hull.slots.prow}}
    2. Dorsal: {{slotsUsed.dorsal}} / {{hull.slots.dorsal}}
    3. @@ -164,8 +152,10 @@
    4. Starboard: {{slotsUsed.starboard}} / {{hull.slots.starboard}}
    5. Keel: {{slotsUsed.keel}} / {{hull.slots.keel}}
    - {{#if warnings.slots}}

    {{warnings.slots}}

    {{/if}} - {{#if warnings.requirements}}

    {{warnings.requirements}}

    {{/if}} + {{#if warnings.capacity}}

    {{warnings.capacity}}

    {{/if}} + {{#if warnings.slots}}

    {{warnings.slots}}

    {{/if}} + {{#if warnings.mounts}}

    {{warnings.mounts}}

    {{/if}} + {{#if warnings.requirements}}

    {{warnings.requirements}}

    {{/if}}
    @@ -178,43 +168,46 @@ Slot Use Mods - Actions {{#each components as |c|}} - + {{#if c.equipped}}{{else}}{{/if}} {{c.name}} {{c.slot}} Space: {{c.use.space}}, Power: {{c.use.power}} - - - {{#if c.mods.armour}}Armour {{signed c.mods.armour}} {{/if}} - {{#if c.mods.integrity}}Integrity {{signed c.mods.integrity}} {{/if}} - {{#if c.mods.manoeuvre}}Manoeuvre {{signed c.mods.manoeuvre}} {{/if}} - {{#if c.mods.detection}}Detection {{signed c.mods.detection}} {{/if}} - {{#if c.mods.speed}}Speed {{signed c.mods.speed}} {{/if}} - {{#if c.mods.space}}Space {{signed c.mods.space}} {{/if}} - {{#if c.mods.power}}Power {{signed c.mods.power}} {{/if}} - - - - - - - + + {{#if c.mods.armour}}Armour {{signed c.mods.armour}} {{/if}} + {{#if c.mods.integrity}}Integrity {{signed c.mods.integrity}} {{/if}} + {{#if c.mods.manoeuvre}}Manoeuvre {{signed c.mods.manoeuvre}} {{/if}} + {{#if c.mods.detection}}Detection {{signed c.mods.detection}} {{/if}} + {{#if c.mods.speed}}Speed {{signed c.mods.speed}} {{/if}} + {{#if c.mods.space}}Space {{signed c.mods.space}} {{/if}} + {{#if c.mods.power}}Power {{signed c.mods.power}} {{/if}} + {{/each}} {{else}} -

    No components installed. Add items with item.system.voidship set.

    +

    No components installed. Drop items with system.voidship.

    {{/if}}
    + + + diff --git a/wg-voidships-builder/wg-voidships-builder.zip b/wg-voidships-builder/wg-voidships-builder.zip new file mode 100644 index 0000000000000000000000000000000000000000..c9a2863345017a0874afffe2dde96b3ce5a9807e GIT binary patch literal 22419 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