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 = ` - `; - - 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 @@{{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}}Source: {{rtCard.source}}
- {{/if}}{{rtCard.notes}}
- {{/if}} + {{#if rtCard.notes}}{{rtCard.notes}}
{{/if}} + {{#if rtCard.source}}Source: {{rtCard.source}}
{{/if}}{{warnings.capacity}}
{{/if}}{{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}}No components installed. Add items with item.system.voidship set.
No components installed. Drop items with system.voidship.