sync: foundry v0.2
@@ -0,0 +1,9 @@
|
|||||||
|
Type,SP Tier,Availability
|
||||||
|
Supplemental,1,Scarce
|
||||||
|
Essential,Base,Scarce
|
||||||
|
Supplemental,2,Rare
|
||||||
|
Essential,Base+1 SP,Rare
|
||||||
|
Supplemental,3,Very Rare
|
||||||
|
Essential,Base+2 SP,Very Rare
|
||||||
|
Archeotech,Any,Extremely Rare
|
||||||
|
Xenotech,Any,Near Unique
|
||||||
|
@@ -65,3 +65,106 @@ Archeotech,Cogitator Interlink,All (unless noted),UNKN,UNKN,UNKN,Crew Rating +5
|
|||||||
Archeotech,Energistic Conversion Matrix,All (unless noted),UNKN,UNKN,UNKN,Divert Power to Speed as Free Action (limit +5 Speed); can depower Components.,Into the Storm (Archeotech Components)
|
Archeotech,Energistic Conversion Matrix,All (unless noted),UNKN,UNKN,UNKN,Divert Power to Speed as Free Action (limit +5 Speed); can depower Components.,Into the Storm (Archeotech Components)
|
||||||
Archeotech,Empyrean Mantle,All (unless noted),UNKN,0,UNKN,External; Silent Running detection is two steps harder; +50 EP to Criminal objectives.,Into the Storm (Archeotech Components)
|
Archeotech,Empyrean Mantle,All (unless noted),UNKN,0,UNKN,External; Silent Running detection is two steps harder; +50 EP to Criminal objectives.,Into the Storm (Archeotech Components)
|
||||||
Archeotech,Teleportarium (Teleportation Array),All (unless noted),1,1,1,Enables Hit & Run vs targets with Void Shields down; per ItS usage rules.,Into the Storm (Archeotech Components)
|
Archeotech,Teleportarium (Teleportation Array),All (unless noted),1,1,1,Enables Hit & Run vs targets with Void Shields down; per ItS usage rules.,Into the Storm (Archeotech Components)
|
||||||
|
Category,Component,Appropriate Hull Types,Power,Space,SP,Notes / Effects,Source
|
||||||
|
Cargo Holds and Passenger Compartments,Cargo Hold and Lighter Bay,Raider/Frigate/Light Cruiser/Cruiser,1,2,1,Standard cargo + lighter bay,Table 8-5
|
||||||
|
Cargo Holds and Passenger Compartments,Compartmentalized Cargo Hold,Raider/Frigate/Light Cruiser/Cruiser,2,5,1,Segmented holds; safer cargo,Table 8-5
|
||||||
|
Cargo Holds and Passenger Compartments,Main Cargo Hold,Transports,2,4,1,Primary cargo storage,Table 8-5
|
||||||
|
Cargo Holds and Passenger Compartments,Luxury Passenger Quarters,All Ships,2,1,1,Passenger comfort/morale,Table 8-5
|
||||||
|
Cargo Holds and Passenger Compartments,Barracks,All Ships,2,4,2,Troop capacity; boarding bonus,Table 8-5
|
||||||
|
|
||||||
|
Augments and Enhancements,Augmented Retro-thrusters,Raider/Frigate,3,0,2,Improved decel/turn,Table 8-5
|
||||||
|
Augments and Enhancements,Augmented Retro-thrusters,Transports/Light Cruisers,4,0,2,Improved decel/turn,Table 8-5
|
||||||
|
Augments and Enhancements,Augmented Retro-thrusters,Cruisers,5,0,2,Improved decel/turn,Table 8-5
|
||||||
|
Augments and Enhancements,Reinforced Interior Bulkheads,Transports/Raiders/Frigates,0,2,2,Structural survivability,Table 8-5
|
||||||
|
Augments and Enhancements,Reinforced Interior Bulkheads,Light Cruisers/Cruisers,0,3,2,Structural survivability,Table 8-5
|
||||||
|
Augments and Enhancements,Armour Plating†,Transports/Raiders/Frigates,0,1,2,"† Once per vessel",Table 8-5
|
||||||
|
Augments and Enhancements,Armour Plating†,Light Cruisers/Cruisers,0,2,2,"† Once per vessel",Table 8-5
|
||||||
|
Augments and Enhancements,Armoured Prow†,Cruisers,0,4,2,"† Once per vessel",Table 8-5
|
||||||
|
Augments and Enhancements,Tenebro-Maze†,Transports/Raiders/Frigates,1,2,2,"† Once per vessel",Table 8-5
|
||||||
|
Augments and Enhancements,Tenebro-Maze†,Light Cruisers/Cruisers,2,3,2,"† Once per vessel",Table 8-5
|
||||||
|
|
||||||
|
Additional Facilities,Extended Supply Vaults,All Ships,1,4,2,Extended endurance,Table 8-5
|
||||||
|
Additional Facilities,Crew Reclamation Facility,All Ships,1,1,1,Reclaims crew losses,Table 8-5
|
||||||
|
Additional Facilities,Munitorium,Transports/Raiders/Frigates,2,3,2,Ordnance logistics,Table 8-5
|
||||||
|
Additional Facilities,Munitorium,Light Cruisers/Cruisers,3,4,2,Ordnance logistics,Table 8-5
|
||||||
|
Additional Facilities,Temple-shrine to the God Emperor,All Ships,1,1,1,Morale/Creed benefits,Table 8-5
|
||||||
|
Additional Facilities,Librarium Vault,All Ships,1,1,1,Archives/intel,Table 8-5
|
||||||
|
Additional Facilities,Trophy Room,All Ships,1,1,1,Social/renown,Table 8-5
|
||||||
|
Additional Facilities,Observation Dome,All Ships,0,1,1,Observation/social,Table 8-5
|
||||||
|
Additional Facilities,Murder-Servitors,All Ships,1,1,2,Boarding bonus,Table 8-5
|
||||||
|
|
||||||
|
Cargo and Passenger Compartments,Brig,All Ships,1,1,1,Prison cells,Table 1-10
|
||||||
|
Cargo and Passenger Compartments,Ship’s Stores,Light Cruisers/Cruisers,1,5,2,Spares & supplies,Table 1-10
|
||||||
|
Cargo and Passenger Compartments,Ship’s Stores,Transports/Grand Cruisers,1,10,2,Spares & supplies,Table 1-10
|
||||||
|
|
||||||
|
Augments and Enhancements,Defensive Countermeasures†,All Ships,1,1,2,"† Once per vessel",Table 1-10
|
||||||
|
Augments and Enhancements,Flak Turrets†,All Ships,1,1,1,"† Once per vessel",Table 1-10
|
||||||
|
Augments and Enhancements,Field Bracing,All Ships,"1-3",1,2,Variable power by scope,Table 1-10
|
||||||
|
Augments and Enhancements,Fire Suppression System†,Transports/Raiders/Frigates,1,1,2,"† Once per vessel",Table 1-10
|
||||||
|
Augments and Enhancements,Fire Suppression System†,Light Cruisers/Cruisers,2,2,2,"† Once per vessel",Table 1-10
|
||||||
|
Augments and Enhancements,Fire Suppression System†,Grand Cruisers,3,3,2,"† Once per vessel",Table 1-10
|
||||||
|
Augments and Enhancements,Lux Net,All Ships,"10 generated",2,2,Generates Power; special rules,Table 1-10
|
||||||
|
Augments and Enhancements,Minelayer Bay,Transports/Light Cruisers/Cruisers,1,4,1,Deploys mines,Table 1-10
|
||||||
|
Augments and Enhancements,Power Ram†,Light Cruisers/Cruisers,2,0,2,"† Once per vessel",Table 1-10
|
||||||
|
|
||||||
|
Additional Facilities,Cloudmining Facility†,Transports,3,4,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Hydraphuran Jamming System†,All Ships,4,0,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Manufactorum†,Light Cruisers/Cruisers,2,1,2,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Medicae Deck†,All Ships,2,1,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Melodium†,All Ships,1,1,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Plasma Scoop†,Raiders/Frigates,2,3,3,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Plasma Scoop†,Light Cruisers/Cruisers,3,4,3,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Pilot’s Chamber†,All Ships,1,1,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Salvage Systems†,Transport/Light Cruiser/Cruiser,5,3,3,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Small Craft Repair Deck†,Light Cruisers/Cruisers,2,2,1,"† Once per vessel",Table 1-10
|
||||||
|
Additional Facilities,Spacedock Piers†,Transports (50+ Space)/Grand Cruisers,7,14,4,"† Once per vessel",Table 1-10
|
||||||
|
|
||||||
|
Cargo Holds and Passenger Compartments,Shadowblind Bays,All Ships,3,4,2,Stealth berths,Table 4-3
|
||||||
|
Cargo Holds and Passenger Compartments,Xenos Habitats,All Ships,2,1,1,Accommodations for xenos,Table 4-3
|
||||||
|
|
||||||
|
Augments and Enhancements,Auxiliary Plasma Banks,Transports/Raiders/Frigates,"8 generated",5,1,Generates Power; special rules,Table 4-3
|
||||||
|
Augments and Enhancements,Auxiliary Plasma Banks,Light Cruisers/Cruisers,"10 generated",6,1,Generates Power; special rules,Table 4-3
|
||||||
|
Augments and Enhancements,Drop Pod Launch Bays,All Ships,1,3,2,Assault insertion bays,Table 4-3
|
||||||
|
Augments and Enhancements,Empyrean Mantle,Transports/Raiders/Frigates,3,0,2,External; stealth vs detection,Table 4-3
|
||||||
|
Augments and Enhancements,Empyrean Mantle,Light Cruisers/Cruisers,5,0,2,External; stealth vs detection,Table 4-3
|
||||||
|
Augments and Enhancements,Reinforced Prow,Transports/Raiders/Frigates,0,2,1,Strengthened prow,Table 4-3
|
||||||
|
Augments and Enhancements,Reinforced Prow,Light Cruisers/Cruisers,0,3,1,Strengthened prow,Table 4-3
|
||||||
|
|
||||||
|
Additional Facilities,Arboretum,Transports/Raiders/Frigates,2,2,1,Life-supporting gardens,Table 4-3
|
||||||
|
Additional Facilities,Arboretum,Light Cruisers/Cruisers,2,3,1,Life-supporting gardens,Table 4-3
|
||||||
|
Additional Facilities,Asteroid Mining Facility,All Ships,6,10,3,Onboard mining/refinery,Table 4-3
|
||||||
|
Additional Facilities,Astropathic Choir-chambers,All Ships,1,1,1,Astropathic communications,Table 4-3
|
||||||
|
Additional Facilities,Broadband Hymn-casters,All Ships,3,0,1,Long-range vox-hymn projectors,Table 4-3
|
||||||
|
Category,Component,Appropriate Hull Types,Power,Space,SP,Notes / Effects,Source
|
||||||
|
Archeotech,Castellan Shield,All Ships,5,1,2,Archeotech void-shield variant.,Table 4-4
|
||||||
|
Archeotech,Castellan Shield Array,Cruisers,7,2,2,Counts as multiple shields; archeotech array.,Table 4-4
|
||||||
|
Archeotech,Cogitator Interlink†,All Ships,1,1,2,"† Once per vessel; advanced cogitator network.",Table 4-4
|
||||||
|
Archeotech,Energistic Conversion Matrix†,All Ships,1,1,1,"† Once per vessel; convert Power to Speed per rules.",Table 4-4
|
||||||
|
Archeotech,Gyro-stabilisation Matrix†,All Ships,1,1,1,"† Once per vessel; enhanced targeting stabilisation.",Table 4-4
|
||||||
|
Archeotech,Staravar Laser Macrobattery,All Ships,4,4,2,Archeotech laser macrobattery (see weapon statline in Table 4-5),Table 4-4
|
||||||
|
Archeotech,Star-flare Lance,Light Cruisers/Cruisers,12,6,3,Archeotech lance (see weapon statline in Table 4-5),Table 4-4
|
||||||
|
|
||||||
|
Archeotech,Cypra-pattern Class 1 Drive,Transports,"30 Generated",10,2,Archeotech plasma drive.,Table 1-11
|
||||||
|
Archeotech,Cypra-pattern Class 2 Drive,Raiders/Frigates,"40 Generated",12,2,Archeotech plasma drive.,Table 1-11
|
||||||
|
Archeotech,Jovian-pattern Nova Cannon,Cruisers,6,7,5,Component entry for Nova Cannon mount (see weapon on Table 1-12),Table 1-11
|
||||||
|
Archeotech,Plasma Accelerated Torpedo Tubes,All Ships,2,6,2,Archeotech torpedo tubes (see weapon on Table 1-12),Table 1-11
|
||||||
|
Archeotech,Warp Antenna,All Ships,1,1,2,Archeotech warp comms augment.,Table 1-11
|
||||||
|
Archeotech,Warp Sextant,All Ships,4,1,2,Archeotech warp navigation aid.,Table 1-11
|
||||||
|
|
||||||
|
Archeotech,Ancient Life Sustainer,Transports/Raiders/Frigates,2,1,2,Archeotech life support.,Table 8-6
|
||||||
|
Archeotech,Ancient Life Sustainer,Light Cruisers/Cruisers,2,2,2,Archeotech life support.,Table 8-6
|
||||||
|
Archeotech,Modifified Jovian Pattern Class 1 Drive,Transports,"35 Generated",4,3,Archeotech/modified drive.,Table 8-6
|
||||||
|
Archeotech,Modifified Lathe Pattern Class 1 Drive,Transports,"40 Generated",8,3,Archeotech/modified drive.,Table 8-6
|
||||||
|
Archeotech,Modifified Jovian Pattern Class 2 Drive,Raiders/Frigates,"45 Generated",6,3,Archeotech/modified drive.,Table 8-6
|
||||||
|
Archeotech,Modifified Jovian Pattern Class 3 Drive,Light Cruisers,"60 Generated",8,3,Archeotech/modified drive.,Table 8-6
|
||||||
|
Archeotech,Modifified Jovian Pattern Class 4 Drive,Cruisers,"75 Generated",10,3,Archeotech/modified drive.,Table 8-6
|
||||||
|
Archeotech,Bridge of Antiquity,Transports/Raiders/Frigates,1,1,2,Archeotech bridge.,Table 8-6
|
||||||
|
Archeotech,Bridge of Antiquity,Light Cruisers/Cruisers,2,1,2,Archeotech bridge.,Table 8-6
|
||||||
|
Archeotech,Auto-stabilised Logis-targeter,All ships,5,0,2,Archeotech targeting augmentation.,Table 8-6
|
||||||
|
Archeotech,Teleportarium,All ships,1,1,1,Archeotech teleport array (component entry),Table 8-6
|
||||||
|
Xenotech,Ghost Field,All ships,8,4,3,Xenos equivalent of advanced stealth shielding.,Table 8-7
|
||||||
|
Xenotech,Shard Cannon Battery,All ships,0,3,2,Xenos macrobattery analogue.,Table 8-7
|
||||||
|
Xenotech,Runecaster,All ships,0,1,2,Xenos augury/warding device.,Table 8-7
|
||||||
|
Xenotech,Micro Laser Defence Grid,All ships,2,0,2,Xenos PD grid.,Table 8-7
|
||||||
|
Xenotech,Gravity Sails,Transports/Raiders/Frigates,3,0,3,Ancient gravity sail rig.,Table 8-7
|
||||||
|
Xenotech,Gravity Sails,Light cruisers/Cruisers,5,0,3,Ancient gravity sail rig.,Table 8-7
|
||||||
|
|||||||
|
@@ -1,13 +1,49 @@
|
|||||||
component_name,category,mount,scale,strength,damage,crit_rating,range_vu,power,space,sp,availability,special_rules_notes,book_sources,needs_review
|
component_name,category,mount,scale,strength,damage,crit_rating,range_vu,power,space,sp,availability,special_rules_notes,book_sources,needs_review
|
||||||
Mars-pattern Macrocannon Broadside (Frigate Scale),Macrocannon,Dorsal,Raider/Frigate,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Standard macrobattery for escorts; broadside-equivalent on dorsal for small hulls.,Core,check_table;check_desc;check_presence
|
Mars-pattern Macrocannon Broadside (Frigate Scale),Macrocannon,Dorsal,Raider/Frigate,3,1d10+2,5,6,4,2,1,UNKN,"Standard macrobattery for escorts; broadside-equivalent on dorsal for small hulls.",Core (Table 8-4),
|
||||||
Mars-pattern Macrocannon Broadside (Cruiser Scale),Macrocannon,Port/Starboard,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Standard cruiser macrobroadside component.,Core,check_table;check_desc;check_presence
|
Mars-pattern Macrocannon Broadside (Cruiser Scale),Macrocannon,Port/Starboard,Cruiser/Battlecruiser,6,1d10+2,5,6,4,5,1,UNKN,Standard cruiser macrobroadside component.,Core (Table 8-4),
|
||||||
Sunsear Laser Battery (Frigate Scale),Laser Battery,Dorsal,Raider/Frigate,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,High accuracy laser macrobattery used by escorts.,Core,check_table;check_desc;check_presence
|
Sunsear Laser Battery (Frigate Scale),Laser Battery,Dorsal,Raider/Frigate,4,1d10+2,4,9,6,4,1,UNKN,High accuracy laser macrobattery used by escorts.,Core (Table 8-4),
|
||||||
Sunsear Laser Battery (Cruiser Scale),Laser Battery,Port/Starboard,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Cruiser-grade laser macrobattery.,Core,check_table;check_desc;check_presence
|
Sunsear Laser Battery (Cruiser Scale),Laser Battery,Port/Starboard,Cruiser/Battlecruiser,6,1d10+2,4,9,9,6,1,UNKN,Cruiser-grade laser macrobattery.,Supplemental Table 4-2,
|
||||||
Dorsal Lance Battery (Frigate Grade),Lance,Dorsal,Frigate,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Single/double lance mount for escorts.,Core,check_table;check_desc;check_presence
|
Dorsal Lance Battery (Frigate Grade),Lance,Dorsal,Frigate,1,1d10+2,3,5,6,4,2,UNKN,Single/double lance mount for escorts.,Core (Table 8-4),
|
||||||
Prow Lance Battery (Light Cruiser),Lance,Prow,Light Cruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Iconic Dauntless prow lance fit.,Core,check_table;check_desc;check_presence
|
Prow Lance Battery (Light Cruiser),Lance,Prow,Light Cruiser,1,1d10+4,3,6,9,4,2,UNKN,Iconic Dauntless prow lance fit.,Core (Table 8-4),
|
||||||
Port/Starboard Lance Battery (Cruiser),Lance,Port/Starboard,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Heavy lance broadside for line ships.,Core,check_table;check_desc;check_presence
|
Port/Starboard Lance Battery (Cruiser),Lance,Port/Starboard,Cruiser/Battlecruiser,2,1d10+4,3,6,13,6,2,UNKN,Heavy lance broadside for line ships.,Core (Table 8-4),
|
||||||
Voss-pattern Torpedo Tubes,Torpedo Tubes,Prow,Escort/Light Cruiser/Cruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,"As seen on Falchion (prow slot pre-equipped; half ammo, +1 Power).",BFK/Core,check_table
|
Voss-pattern Torpedo Tubes,Torpedo Tubes,Prow,Escort/Light Cruiser/Cruiser,2,UNKN,UNKN,UNKN,1,5,1,UNKN,"As seen on Falchion (prow slot pre-equipped; half ammo, +1 Power).",Core (Table 1-9),
|
||||||
Standard Torpedo Tubes (Cruiser Pattern),Torpedo Tubes,Prow,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Generic torpedo tube component for cruiser-sized hulls.,Core,check_table;check_desc;check_presence
|
Standard Torpedo Tubes (Cruiser Pattern),Torpedo Tubes,Prow,Cruiser/Battlecruiser,6,UNKN,UNKN,UNKN,2,8,2,UNKN,Generic torpedo tube component for cruiser-sized hulls.,Core (Table 1-9),
|
||||||
Jovian-pattern Launch Bay,Launch Bay,Port/Starboard,Light Cruiser/Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Carrier component for attack craft (Fury/Starhawk wings).,Core/BFK,check_table;check_presence
|
Jovian-pattern Launch Bay,Launch Bay,Port/Starboard,Light Cruiser/Cruiser/Battlecruiser,2,UNKN,UNKN,UNKN,1,6,2,UNKN,"Carrier component for attack craft (Fury/Starhawk wings).",Core (Table 1-9),
|
||||||
Nova Cannon,Nova Cannon,Prow,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,Superheavy prow weapon; unique ammo rules.,Core/BFK,check_table;check_desc;check_presence
|
Nova Cannon,Nova Cannon,Prow,Cruiser/Battlecruiser,SPECIAL,2d5+4,SPECIAL,6–40,3,7,3,UNKN,"Superheavy prow weapon; unique ammo rules.",Core (Table 1-9),check_table
|
||||||
Plasma Battery (Cruiser Grade),Plasma Battery,Port/Starboard,Cruiser/Battlecruiser,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,UNKN,If present in BFK as a distinct macrobattery type; verify exact name/statline.,BFK,check_table;check_desc;check_presence
|
Plasma Battery (Cruiser Grade),Plasma Battery,Port/Starboard,Cruiser/Battlecruiser,4,1d10+4,4,5,7,4,2,UNKN,"If present in BFK as a distinct macrobattery type; verify exact name/statline.",Core (Table 8-4),
|
||||||
|
Bombardment Cannons,Macrocannon,Port/Starboard,Light Cruiser/Cruiser,3,1d10+6,2,4,5,5,3,UNKN,Heavy bombardment macrobattery.,Core (Table 1-9),
|
||||||
|
Disruption Macrocannons,Macrocannon,Any,All Ships,3,1d10+1,0,5,4,2,2,UNKN,"Mount varies by hull: Dorsal on escorts; Port/Starboard on larger hulls.",Core (Table 1-9),
|
||||||
|
Disruption Macrocannon Broadside,Macrocannon,Port/Starboard,Light Cruiser/Cruiser,6,1d10+1,0,5,6,5,2,UNKN,Broadside variant.,Core (Table 1-9),
|
||||||
|
Stygies-pattern Macrocannons,Macrocannon,Any,All Ships,3,1d10+2,5,5,4,3,1,UNKN,"Mount varies by hull: Dorsal on escorts; Port/Starboard on larger hulls.",Core (Table 1-9),
|
||||||
|
Hecutor-pattern Plasma Battery,Plasma Battery,Port/Starboard,Light Cruiser/Cruiser,3,1d10+2,4,11,8,3,2,UNKN,High-velocity plasma battery.,Core (Table 1-9),
|
||||||
|
Hecutor-pattern Plasma Broadside,Plasma Battery,Port/Starboard,Battlecruiser/Grand Cruiser,5,1d10+2,4,11,12,5,2,UNKN,Heavy plasma broadside.,Core (Table 1-9),
|
||||||
|
|
||||||
|
Godsbane Lance,Lance,Any,Battlecruiser/Grand Cruiser,1,1d10+2,3,12,9,4,3,UNKN,Single long-range lance.,Core (Table 1-9),
|
||||||
|
Godsbane Lance Battery,Lance,Port/Starboard,Battlecruiser/Grand Cruiser,2,1d10+2,3,12,13,6,3,UNKN,Broadside lance battery.,Core (Table 1-9),
|
||||||
|
Las-burner,Lance,Any,All Ships,2,1d5+1,3,3,7,3,2,UNKN,Short-range cutting lance.,Core (Table 1-9),
|
||||||
|
Mezoa Lance Weapon,Lance,Any,All Ships,1,1d10+5,4,4,9,4,3,UNKN,High-output lance emitter.,Core (Table 1-9),
|
||||||
|
Mezoa Lance Battery,Lance,Port/Starboard,Light Cruiser/Cruiser,2,1d10+5,4,4,13,6,3,UNKN,Heavy lance battery.,Core (Table 1-9),
|
||||||
|
Voidsunder Lance Battery,Lance,Port/Starboard,Light Cruiser/Cruiser,1,1d10+4,3,6,15,8,3,UNKN,Shield-piercing lance battery.,Core (Table 1-9),
|
||||||
|
|
||||||
|
Ryza-pattern Nova Cannon,Nova Cannon,Prow,Cruisers,SPECIAL,2d5+5,SPECIAL,6–36,4,7,4,UNKN,"Nova weapon; special blast/aiming rules apply.",Core (Table 1-9),check_table
|
||||||
|
|
||||||
|
Fortis-pattern Torpedo Tubes,Torpedo Tubes,Prow,Light Cruiser/Cruiser,6,UNKN,UNKN,UNKN,2,8,3,UNKN,Standard cruiser/LC torpedo system.,Core (Table 1-9),
|
||||||
|
Gryphonne-pattern Torpedo Tubes,Torpedo Tubes,Prow,Raider/Frigate/Light Cruiser/Cruiser,4,UNKN,UNKN,UNKN,2,6,1,UNKN,Escort-through-cruiser compatible tubes.,Core (Table 1-9),
|
||||||
|
Mars-pattern Torpedo Tubes,Torpedo Tubes,Prow,Light Cruiser/Cruiser,6,UNKN,UNKN,UNKN,2,8,2,UNKN,Standard Navy cruiser torpedo fit.,Core (Table 1-9),
|
||||||
|
|
||||||
|
Hold Landing Bay,Launch Bay,Internal,Transports,2,UNKN,UNKN,UNKN,1,0,2,UNKN,"Cargo-hold conversion; Space shown as 0 for '—' per CSV convention.",Core (Table 1-9),
|
||||||
|
Jovian-pattern Escort Bay,Launch Bay,Port/Starboard,Light Cruiser/Cruiser,1,UNKN,UNKN,UNKN,1,4,1,UNKN,Escort-grade launch bay.,Core (Table 1-9),
|
||||||
|
Lathe-pattern Landing Bay,Launch Bay,Port/Starboard,Cruiser,2,UNKN,UNKN,UNKN,1,5,2,UNKN,Cruiser-grade launch bay (Lathe),Core (Table 1-9),
|
||||||
|
|
||||||
|
Jovian Missile Battery,Macrocannon,Any,All Ships,5,1d10+1,6,6,3,1,1,UNKN,"Missile-based macrobattery; mount varies by hull.",Supplemental Table 4-2,
|
||||||
|
Lathe Grav-culverin Broadside,Macrocannon,Port/Starboard,Light Cruiser/Cruiser,6,1d10+3,6,5,5,5,1,UNKN,High-velocity gravitic broadside.,Supplemental Table 4-2,
|
||||||
|
Mezoa Macrocannons,Macrocannon,Any,All Ships,4,1d10+3,5,5,4,4,1,UNKN,"Mount varies by hull: Dorsal on escorts; Port/Starboard on larger hulls.",Supplemental Table 4-2,
|
||||||
|
Pyros Melta-cannons,Macrocannon,Any,All Ships,3,1d10+4,4,4,4,3,2,UNKN,Short-range melta macrobattery.,Supplemental Table 4-2,
|
||||||
|
Sunhammer Lance,Lance,Any,All Ships,1,1d10+3,3,9,9,4,2,UNKN,Long-range lance emitter.,Supplemental Table 4-2,
|
||||||
|
Sunhammer Lance Battery,Lance,Port/Starboard,All Ships,2,1d10+3,3,9,13,6,2,UNKN,Long-range lance battery.,Supplemental Table 4-2,
|
||||||
|
Mars-pattern Nova Cannon,Nova Cannon,Prow,Cruisers,SPECIAL,2d5+4,SPECIAL,6–40,3,7,3,UNKN,"Nova weapon; special blast/aiming rules apply.",Core (Table 1-9),check_table
|
||||||
|
Staravar Laser Macrobattery,Macrocannon,Any,All Ships,4,1d10+2,4,12,4,4,2,Extremely Rare,"Archeotech laser macro; mount depends on hull (Dorsal/Port-Starboard).",Table 4-5,
|
||||||
|
Star-flare Lance,Lance,Any,Light Cruiser/Cruiser,3,1d10+3,3,7,12,6,3,Extremely Rare,Archeotech lance emitter.,Table 4-5,
|
||||||
|
|
||||||
|
Jovian-pattern Nova Cannon†,Nova Cannon,Prow,Cruisers,SPECIAL,2d5+7,SPECIAL,6–35,5,7,5,Extremely Rare,"† Special rules for Strength/Crit (see page 16).",Table 1-12,check_table
|
||||||
|
Plasma Accelerated Torpedo Tubes (Archeotech Weapon),Torpedo Tubes,Prow,All Ships,4,UNKN,UNKN,UNKN,1,4,4,Extremely Rare,"Archeotech torpedo mount; torpedo warhead stats handled separately.",Table 1-12,
|
||||||
|
|||||||
|
@@ -1 +1 @@
|
|||||||
0.1
|
0.2
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# generate_manifests.py
|
||||||
|
# Fortel Trappa — pełne manifesty ładunkowe (HOLD I/II/III) w ZIP
|
||||||
|
# Uruchom: python generate_manifests.py -> powstaną dwa pliki ZIP w bieżącym katalogu.
|
||||||
|
|
||||||
|
import random, zipfile, datetime
|
||||||
|
|
||||||
|
random.seed(40000) # deterministyczny „smak”
|
||||||
|
|
||||||
|
WORLDS = ["Ostia","Avachrus","Enoch","Gilead Prime","Nethreus","Ryza","Vostroya","Scintilla","Footfall"]
|
||||||
|
INN_NOTES = [
|
||||||
|
"Zwykły wyrób, zgodny z normą diecezjalną.",
|
||||||
|
"Do użytku cywilnego; dokumentacja pełna.",
|
||||||
|
"Na potrzeby konserwacji/edukacji polowej.",
|
||||||
|
"Brak zasilania, brak funkcji specjalnych.",
|
||||||
|
"Wersja eksportowa; gwinty zgodne z PL-3.",
|
||||||
|
"Na morale i porządek; bez cech bojowych.",
|
||||||
|
"Próbki kontrolne — wynik negatywny na scrapcode.",
|
||||||
|
"Rekwizyty katechetyczne; brak statusu kultowego.",
|
||||||
|
"Puste, czyste, opieczętowane.",
|
||||||
|
"Zabezpieczone audytem Ministorum.",
|
||||||
|
"Składane; bez ostrych krawędzi w papierach.",
|
||||||
|
"Zwykła jakość magazynowa; brak niespodzianek.",
|
||||||
|
"Brak reakcji psionicznej (pomiar: tło).",
|
||||||
|
"Standard przemysłowy; kompatybilność wysoka.",
|
||||||
|
"Do ekspozycji; nieotwierane przy inspekcji.",
|
||||||
|
"Zestaw muzealny; kojąco nudny.",
|
||||||
|
"Oznaczenie ‘cywilne’; instrukcja w komplecie.",
|
||||||
|
"Zgodne z przepisem P-77 (ładownie ogólne).",
|
||||||
|
"Pieczęć diecezjalna aktualna; datowanie bieżące.",
|
||||||
|
"Bezpieczne dla chóru i czytników vox."
|
||||||
|
]
|
||||||
|
INN_NAMES = [
|
||||||
|
"Ziarno kultywacyjne ‘Święty Oset’","Mąka ‘Hostia’","Kasza jęczmienna ‘Pielgrzym’","Groch ‘Kocioł’","Fasola ‘Pilgrim’",
|
||||||
|
"Sól żywnościowa","Olej lampowy","Smary maszynowe ‘Stałość’","Farba kadłubowa ‘Mgła’","Pasy mocujące ‘Ordo Cargo’",
|
||||||
|
"Złącza heksowe","Zawory nadciśnieniowe","Łożyska baryłkowe ‘Wytrwałość’","Łożyska kulkowe precyzyjne",
|
||||||
|
"Taśmy stalowe ‘Krzyżowe’","Plandeki światłochłonne ‘Cień’","Pudła ciszy (obudowy wygłuszające)","Lampiony procesyjne",
|
||||||
|
"Witraże polowe (składane)","Zwoje pergaminowe ‘Sztywna Karta’","Maski nabożne ‘Neutral’","Relikwie podręczne (skrzynki)",
|
||||||
|
"Szkatuły dewocyjne (stal miękka)","Szkła ochronne ‘Błękit Służbowy’","Barwniki okienne (błękit/purpura)",
|
||||||
|
"Kity hermetyczne ‘Głuchy Spaw’","Rury segmentowe PL-7","Kable PL-3 ‘Uniwersalne’","Siatki zabezpieczające ‘Drobne Oczko’",
|
||||||
|
"Zasuwy grodziowe PL-10","Zaczepy stalowe ‘Cierpliwość’","Kątowniki ciężkie ‘Prosto’","Nity kadłubowe ‘Amen’",
|
||||||
|
"Skrzynki na dźwięk (wygłuszenie)","Szkła różnobarwne (mix)","Amfory gliniane ‘E-Seria’","Szaty ‘Pokora’ (robocze)",
|
||||||
|
"Kopułki modlitewne (mini)","Płaszcze przeciwdeszczowe ‘Wachta’","Świece czarne ‘Negocjacje’","Świece woskowe ‘Cisza Nocna’",
|
||||||
|
"Zwoje kabli heksowych (ekranowane)","Sprężyny powrotne","Zawiesia wielokrążkowe","Przekaźniki proste ‘Miedź’",
|
||||||
|
"Łożyska stożkowe ‘Kierunek’","Szkatuły na krople (apteczne)","Ołtarze polowe (składane)","Figurki pobożne",
|
||||||
|
"Piety miniaturowe","Popiół posypny (liturgiczny)","Kleje statkowe ‘Głuchy Spaw’","Klemy zasilające ‘Moc’",
|
||||||
|
"Kołnierze izolacyjne PL-5","Beczki puste ‘Gotowość’","Rolki transportowe ‘Gładkie’","Słód jasny ‘Nocny’",
|
||||||
|
"Skrzydła drzwiowe ‘Jedność’","Skarbonki parafialne","Księgi wizualne (albumy)","Zegary harmoniczne (niski ton)",
|
||||||
|
"Baldachimy małe ‘Krem’","Puchar kapłański (rekwizyt)","Księgi zakładkowe","Pasy transmisyjne ‘Ciągłość’",
|
||||||
|
"Pudła akustyczne (puste)","Rękawice robocze ‘Skóra+’",
|
||||||
|
]
|
||||||
|
XENO_REAL = [
|
||||||
|
"fragmenty wraithbone Aeldari (martwe, obrobione ‘na szkło’)",
|
||||||
|
"odpryski spirit-stone (pozbawione ładunku)",
|
||||||
|
"ukryty vox-scrambler xeno (obudowa cywilna)",
|
||||||
|
"mikrofiolki po jadu Lhamaean (dokładnie wyparzone)",
|
||||||
|
"moduł holo-emisyjny Aeldari (matryca wycięta, nieaktywny)",
|
||||||
|
"kaseta rozliczeń cold trade (podwójne dno)",
|
||||||
|
"nekrotech — fragment stele ‘bezcielesnej’ (kamień ‘ozdobny’)",
|
||||||
|
"obudowy tłumiące sygnał (technologia nieidentyfikowana)",
|
||||||
|
"orb psioniczny — tłumik nastroju (źródło Aeldari)",
|
||||||
|
"halo-device: element oprawy (niekompletne, bez rdzenia)",
|
||||||
|
"resztki proscribed tomes (okładki, wkład wymieniony na blanki)",
|
||||||
|
"zegar-pikietaż dla transakcji cold trade (ukryty znacznik czasu)",
|
||||||
|
"ampułki po obscura klasy ceremonialnej (umyte; testy czyste)",
|
||||||
|
"fiolki po ‘ichorze’ drukhari (sterylnie czyste)",
|
||||||
|
"masker polowy Drukhari — tkanina z włóknem ziggurat (zdezaktywowana)",
|
||||||
|
]
|
||||||
|
|
||||||
|
def inn_note(): return random.choice(INN_NOTES)
|
||||||
|
def name(): return random.choice(INN_NAMES)
|
||||||
|
def world(): return random.choice(WORLDS)
|
||||||
|
def qty(): return random.randint(1, 8)
|
||||||
|
|
||||||
|
def make_line(idx, suspicious_ratio=0.34):
|
||||||
|
q = qty(); nm = name(); ow = world(); note = inn_note()
|
||||||
|
if random.random() < suspicious_ratio:
|
||||||
|
paren = f"({random.choice(XENO_REAL)})"
|
||||||
|
else:
|
||||||
|
paren = f"({random.choice(['zwykły ładunek cywilny','materiały szkoleniowe/rekonstrukcja','zestaw konserwatorski','komplet części serwisowych','puste i czyste','standard magazynowy','produkt spożywczy','wyposażenie pomocnicze','materiały dekoracyjne','narzędzia warsztatowe'])})"
|
||||||
|
return f"{idx}) {q}× — {nm} — {ow} — [{note}] {paren}", q
|
||||||
|
|
||||||
|
def make_section_text(hold_label, s_idx, s_cnt):
|
||||||
|
header = f"=== FORTEL TRAPPA — MANIFEST ŁADUNKOWY ===\n{hold_label} / SEKCJA {s_idx} z {s_cnt}\n(Format: [Liczba 1–10] × Standardowy Imperialny Kontener — Nazwa — Pochodzenie — [Uwaga kapitańska] (co to naprawdę jest))\n\n"
|
||||||
|
total = 0; entries = []
|
||||||
|
for i in range(1, 124):
|
||||||
|
line, q = make_line(i, suspicious_ratio=0.34)
|
||||||
|
entries.append([line, q]); total += q
|
||||||
|
if total > 1000:
|
||||||
|
excess = total - 1000
|
||||||
|
for i in range(len(entries)-1, -1, -1):
|
||||||
|
if excess <= 0: break
|
||||||
|
q = entries[i][1]
|
||||||
|
if q > 1:
|
||||||
|
red = min(q-1, excess); entries[i][1] -= red; excess -= red
|
||||||
|
prefix, rest = entries[i][0].split("× —", 1)
|
||||||
|
idx_str = prefix.split(")")[0] + ")"
|
||||||
|
entries[i][0] = f"{idx_str} {entries[i][1]}× —{rest}"
|
||||||
|
body = "\n".join(e[0] for e in entries)
|
||||||
|
footer = f"\n\n— KONIEC: {hold_label} / SEKCJA {s_idx}/{s_cnt} —"
|
||||||
|
return header + body + footer
|
||||||
|
|
||||||
|
def write_full_zip(path_full, path_hold1):
|
||||||
|
with zipfile.ZipFile(path_full, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for s in range(4, 17): # HOLD I 04–16
|
||||||
|
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||||||
|
for s in range(1, 16): # HOLD II 01–15
|
||||||
|
z.writestr(f"HOLD_II_SEKCJA_{s:02d}.txt", make_section_text("HOLD II", s, 15))
|
||||||
|
for s in range(1, 16): # HOLD III 01–15
|
||||||
|
z.writestr(f"HOLD_III_SEKCJA_{s:02d}.txt", make_section_text("HOLD III", s, 15))
|
||||||
|
z.writestr("README.txt", f"FORTEL TRAPPA — MANIFESTY ({datetime.datetime.utcnow().isoformat()}Z)\nHOLD I: 04–16; HOLD II: 01–15; HOLD III: 01–15\n~1/3 pozycji w ( ) ma real-tożsamość (xeno/kontrabanda), [ ] — noty ‘urzędowo niewinne’.\n")
|
||||||
|
with zipfile.ZipFile(path_hold1, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for s in range(4, 17):
|
||||||
|
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||||||
|
z.writestr("README_HOLD_I.txt", f"HOLD I (04–16) — {datetime.datetime.utcnow().isoformat()}Z\n")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
write_full_zip("FORTEL_HOLD_I_II_III_MANIFESTS.zip", "FORTEL_HOLD_I_SEKCJE_04-16.zip")
|
||||||
|
print("OK: FORTEL_HOLD_I_II_III_MANIFESTS.zip | FORTEL_HOLD_I_SEKCJE_04-16.zip")
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
# generate_manifests_v2.py
|
||||||
|
# Fortel Trappa — manifesty o wysokiej różnorodności (HOLD I/II/III) w dwóch ZIP-ach.
|
||||||
|
# Uruchom: python generate_manifests_v2.py
|
||||||
|
# Wynik: FORTEL_MANIFESTS_v2_FULL.zip oraz FORTEL_MANIFESTS_v2_HOLD_I.zip
|
||||||
|
|
||||||
|
import random, zipfile, datetime
|
||||||
|
|
||||||
|
# ===== USTAWIENIA =====
|
||||||
|
SEED = None # ustaw np. na liczbę, jeśli chcesz powtarzalny wynik
|
||||||
|
SUSP_RATIO = 0.34 # ~1/3 pozycji ma „prawdę” w ( ) o xeno/kontrabandzie
|
||||||
|
QTY_MIN, QTY_MAX = 1, 10
|
||||||
|
LINES_PER_SECTION = 123
|
||||||
|
CAP_PER_SECTION = 1000
|
||||||
|
|
||||||
|
if SEED is None:
|
||||||
|
random.seed() # świeża losowość
|
||||||
|
else:
|
||||||
|
random.seed(SEED)
|
||||||
|
|
||||||
|
# ===== PULI I KOMPONENTY NAZW =====
|
||||||
|
WORLDS = [
|
||||||
|
"Ostia","Avachrus","Enoch","Gilead Prime","Nethreus","Ryza","Vostroya","Scintilla","Footfall",
|
||||||
|
"Hydraphur","Malfi","Port Wander","Voss Prime","Lucius","Graia","Metalica","Sollex","Sibellus"
|
||||||
|
]
|
||||||
|
|
||||||
|
FOODS = [
|
||||||
|
"Ziarno kultywacyjne","Pszenica","Owies","Jęczmień","Mąka","Kasza jęczmienna","Kasza owsiana",
|
||||||
|
"Groch","Fasola","Ciecierzyca","Słód jasny","Słód ciemny","Suszone jarzyny","Sól żywnościowa","Olej lampowy"
|
||||||
|
]
|
||||||
|
INDUSTRY = [
|
||||||
|
"Złącza heksowe","Zawory nadciśnieniowe","Łożyska baryłkowe","Łożyska kulkowe","Łożyska stożkowe",
|
||||||
|
"Taśmy stalowe","Plandeki światłochłonne","Pudła ciszy (wygłuszenie)","Rury segmentowe PL","Kable PL",
|
||||||
|
"Siatki zabezpieczające","Zasuwy grodziowe","Zaczepy stalowe","Kątowniki ciężkie","Nity kadłubowe",
|
||||||
|
"Skrzynki na dźwięk (wygłuszenie)","Filtry rebreatherów","Transpondery nawigacyjne","Przekaźniki proste",
|
||||||
|
"Zawiesia wielokrążkowe","Sprężyny powrotne","Kołnierze izolacyjne","Kleje statkowe","Klemy zasilające",
|
||||||
|
"Rolki transportowe","Farba kadłubowa","Żywica naprawcza","Kity hermetyczne","Szkła ochronne",
|
||||||
|
"Panele grodziowe","Uszczelki retencyjne","Siłowniki serwo (obudowy)"
|
||||||
|
]
|
||||||
|
DEVOTION = [
|
||||||
|
"Lampiony procesyjne","Witraże polowe (składane)","Zwoje pergaminowe","Maski nabożne",
|
||||||
|
"Relikwie podręczne (skrzynki)","Szkatuły dewocyjne (stal miękka)","Amfory gliniane",
|
||||||
|
"Szaty ‘Pokora’ (robocze)","Kopułki modlitewne (mini)","Płaszcze ‘Wachta’",
|
||||||
|
"Świece czarne ‘Negocjacje’","Świece woskowe ‘Cisza Nocna’","Figurki pobożne",
|
||||||
|
"Piety miniaturowe","Popiół posypny (liturgiczny)","Ołtarze polowe (składane)",
|
||||||
|
"Skarbonki parafialne","Księgi wizualne (albumy)","Księgi zakładkowe",
|
||||||
|
"Ikony ścienne (odlewy)","Reliefy miedziane","Baldachimy ‘Krem’","Puchary kapłańskie (rekwizyt)"
|
||||||
|
]
|
||||||
|
|
||||||
|
ADJ = [
|
||||||
|
"‘Hostia’","‘Pielgrzym’","‘Kocioł’","‘Pilgrim’","‘Stałość’","‘Ordo Cargo’","‘Mgła’","‘Cień’",
|
||||||
|
"‘Błękit Służbowy’","‘Sztywna Karta’","‘Jedność’","‘Głuchy Spaw’","‘Kierunek’","‘Gotowość’","‘Neutral’",
|
||||||
|
"‘Mk II’","‘Mk III’","‘Mk IV’","‘Mk V’","‘Plus’","‘Premium’","‘Export’","‘Seria V’","‘Seria IX’","‘Nocny’"
|
||||||
|
]
|
||||||
|
|
||||||
|
INN_NOTES = [
|
||||||
|
"Zwykły wyrób, zgodny z normą diecezjalną.",
|
||||||
|
"Do użytku cywilnego; dokumentacja pełna.",
|
||||||
|
"Na potrzeby konserwacji/edukacji polowej.",
|
||||||
|
"Brak zasilania, brak funkcji specjalnych.",
|
||||||
|
"Wersja eksportowa; gwinty zgodne z PL-3.",
|
||||||
|
"Na morale i porządek; bez cech bojowych.",
|
||||||
|
"Próbki kontrolne — wynik negatywny na scrapcode.",
|
||||||
|
"Rekwizyty katechetyczne; brak statusu kultowego.",
|
||||||
|
"Puste, czyste, opieczętowane.",
|
||||||
|
"Zabezpieczone audytem Ministorum.",
|
||||||
|
"Składane; bez ostrych krawędzi w papierach.",
|
||||||
|
"Zwykła jakość magazynowa; brak niespodzianek.",
|
||||||
|
"Brak reakcji psionicznej (pomiar: tło).",
|
||||||
|
"Standard przemysłowy; kompatybilność wysoka.",
|
||||||
|
"Do ekspozycji; nieotwierane przy inspekcji.",
|
||||||
|
"Zestaw muzealny; kojąco nudny.",
|
||||||
|
"Oznaczenie ‘cywilne’; instrukcja w komplecie.",
|
||||||
|
"Zgodne z przepisem P-77 (ładownie ogólne).",
|
||||||
|
"Pieczęć diecezjalna aktualna; datowanie bieżące.",
|
||||||
|
"Bezpieczne dla chóru i czytników vox."
|
||||||
|
]
|
||||||
|
|
||||||
|
HARMLESS = [
|
||||||
|
"zwykły ładunek cywilny","materiały szkoleniowe/rekonstrukcja","zestaw konserwatorski",
|
||||||
|
"komplet części serwisowych","puste i czyste","standard magazynowy","produkt spożywczy",
|
||||||
|
"wyposażenie pomocnicze","materiały dekoracyjne","narzędzia warsztatowe"
|
||||||
|
]
|
||||||
|
|
||||||
|
XENO_REAL = [
|
||||||
|
"fragmenty wraithbone Aeldari (martwe, obrobione ‘na szkło’)",
|
||||||
|
"odpryski spirit-stone (pozbawione ładunku)",
|
||||||
|
"ukryty vox-scrambler xeno (obudowa cywilna)",
|
||||||
|
"mikrofiolki po jadu Lhamaean (dokładnie wyparzone)",
|
||||||
|
"moduł holo-emisyjny Aeldari (matryca wycięta, nieaktywny)",
|
||||||
|
"kaseta rozliczeń cold trade (podwójne dno)",
|
||||||
|
"nekrotech — fragment stele ‘bezcielesnej’ (kamień ‘ozdobny’)",
|
||||||
|
"obudowy tłumiące sygnał (technologia nieidentyfikowana)",
|
||||||
|
"orb psioniczny — tłumik nastroju (źródło Aeldari)",
|
||||||
|
"halo-device: element oprawy (niekompletne, bez rdzenia)",
|
||||||
|
"resztki proscribed tomes (okładki, wkład wymieniony na blanki)",
|
||||||
|
"zegar-pikietaż dla transakcji cold trade (ukryty znacznik czasu)",
|
||||||
|
"ampułki po obscura klasy ceremonialnej (umyte; testy czyste)",
|
||||||
|
"fiolki po ‘ichorze’ drukhari (sterylnie czyste)",
|
||||||
|
"masker polowy Drukhari — tkanina z włóknem ziggurat (zdezaktywowana)",
|
||||||
|
"matryca soniczna Laer (obudowana ‘pudłem ciszy’)",
|
||||||
|
"półprzezroczysta łuska necron — fragment dekoracyjny (brak reakcji)",
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose_name():
|
||||||
|
# wybór kategorii z wagami
|
||||||
|
genre = random.choices(["food","industry","devotion"], [4,5,3])[0]
|
||||||
|
base = random.choice({"food":FOODS, "industry":INDUSTRY, "devotion":DEVOTION}[genre])
|
||||||
|
# modyfikacje wersji/serii
|
||||||
|
if "PL" in base:
|
||||||
|
base = base.replace("PL", f"PL-{random.randint(3,12)}")
|
||||||
|
if "Kable PL" in base and random.random()<0.7:
|
||||||
|
base = base.replace("Kable PL", f"Kable PL-{random.randint(2,8)}")
|
||||||
|
flair=[]
|
||||||
|
if random.random()<0.7: flair.append(random.choice(ADJ))
|
||||||
|
if random.random()<0.25: flair.append(f"‘Seria {random.randint(2,12)}’")
|
||||||
|
return base if not flair else f"{base} " + " ".join(flair)
|
||||||
|
|
||||||
|
def make_line(idx, suspicious_ratio=SUSP_RATIO):
|
||||||
|
q = random.randint(QTY_MIN, QTY_MAX)
|
||||||
|
nm = compose_name()
|
||||||
|
ow = random.choice(WORLDS)
|
||||||
|
note = random.choice(INN_NOTES)
|
||||||
|
paren = f"({random.choice(XENO_REAL)})" if random.random()<suspicious_ratio else f"({random.choice(HARMLESS)})"
|
||||||
|
return f"{idx}) {q}× — {nm} — {ow} — [{note}] {paren}", q
|
||||||
|
|
||||||
|
def make_section_text(hold_label, s_idx, s_cnt):
|
||||||
|
header = (
|
||||||
|
"=== FORTEL TRAPPA — MANIFEST ŁADUNKOWY ===\n"
|
||||||
|
f"{hold_label} / SEKCJA {s_idx} z {s_cnt}\n"
|
||||||
|
"(Format: [Liczba 1–10] × Standardowy Imperialny Kontener — Nazwa — Pochodzenie — [Uwaga kapitańska] (co to naprawdę jest))\n\n"
|
||||||
|
)
|
||||||
|
total=0; entries=[]
|
||||||
|
# ogranicz powtórzenia nazw w obrębie sekcji
|
||||||
|
seen=set()
|
||||||
|
for i in range(1, LINES_PER_SECTION+1):
|
||||||
|
tries=0
|
||||||
|
while True:
|
||||||
|
line,q = make_line(i)
|
||||||
|
name_part = line.split("—")[1].strip() # „N× — NAZWA — ...”
|
||||||
|
if name_part not in seen or tries>7:
|
||||||
|
seen.add(name_part)
|
||||||
|
break
|
||||||
|
tries+=1
|
||||||
|
entries.append([line,q]); total+=q
|
||||||
|
# przytnij sumę kontenerów do CAP_PER_SECTION
|
||||||
|
if total>CAP_PER_SECTION:
|
||||||
|
excess=total-CAP_PER_SECTION
|
||||||
|
for i in range(len(entries)-1,-1,-1):
|
||||||
|
if excess<=0: break
|
||||||
|
q=entries[i][1]
|
||||||
|
if q>1:
|
||||||
|
red=min(q-1,excess); entries[i][1]-=red; excess-=red
|
||||||
|
prefix,rest=entries[i][0].split("× —",1)
|
||||||
|
idx_str=prefix.split(")")[0]+")"
|
||||||
|
entries[i][0]=f"{idx_str} {entries[i][1]}× —{rest}"
|
||||||
|
body="\n".join(e[0] for e in entries)
|
||||||
|
footer=f"\n\n— KONIEC: {hold_label} / SEKCJA {s_idx}/{s_cnt} —"
|
||||||
|
return header+body+footer
|
||||||
|
|
||||||
|
def build_zip(full_path, hold1_path):
|
||||||
|
# FULL: HOLD I 04–16; HOLD II 01–15; HOLD III 01–15
|
||||||
|
with zipfile.ZipFile(full_path,"w",compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for s in range(4,17): # HOLD I
|
||||||
|
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||||||
|
for s in range(1,16): # HOLD II
|
||||||
|
z.writestr(f"HOLD_II_SEKCJA_{s:02d}.txt", make_section_text("HOLD II", s, 15))
|
||||||
|
for s in range(1,16): # HOLD III
|
||||||
|
z.writestr(f"HOLD_III_SEKCJA_{s:02d}.txt", make_section_text("HOLD III", s, 15))
|
||||||
|
z.writestr("README.txt",
|
||||||
|
f"FORTEL TRAPPA — MANIFESTY v2 ({datetime.datetime.utcnow().isoformat()}Z)\n"
|
||||||
|
"HOLD I: 04–16; HOLD II: 01–15; HOLD III: 01–15\n"
|
||||||
|
f"~{int(SUSP_RATIO*100)}% pozycji w ( ) ujawnia real-tożsamość (xeno/kontrabanda); [ ] — noty urzędowo niewinne.\n"
|
||||||
|
"123 pozycji/sekcję; 1–10 kontenerów/pozycję; suma ≤1000/sekcję.\n")
|
||||||
|
# HOLD I only
|
||||||
|
with zipfile.ZipFile(hold1_path,"w",compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for s in range(4,17):
|
||||||
|
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||||||
|
z.writestr("README_HOLD_I.txt", f"HOLD I (04–16) v2 — {datetime.datetime.utcnow().isoformat()}Z\n")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
build_zip("FORTEL_MANIFESTS_v2_FULL.zip","FORTEL_MANIFESTS_v2_HOLD_I.zip")
|
||||||
|
print("OK -> FORTEL_MANIFESTS_v2_FULL.zip | FORTEL_MANIFESTS_v2_HOLD_I.zip")
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 934 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 794 B |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"id": "investigation-board",
|
||||||
|
"title": "Investigation Board",
|
||||||
|
"description": "A Foundry VTT module that lets everyone create...photo notes on the scene as collaborative investigation tools.",
|
||||||
|
"version": "1.3.2",
|
||||||
|
"authors": [{ "name": "Gus", "url": "", "email": "" }],
|
||||||
|
"compatibility": { "minimum": "12", "verified": "13" },
|
||||||
|
"esmodules": [
|
||||||
|
"scripts/settings.js",
|
||||||
|
"scripts/investigation-board.js"
|
||||||
|
],
|
||||||
|
"styles": ["styles/style.css"],
|
||||||
|
"packs": [],
|
||||||
|
"templates": ["templates/drawing-sheet.html"],
|
||||||
|
}
|
||||||
@@ -0,0 +1,471 @@
|
|||||||
|
//investigation-boards.js
|
||||||
|
|
||||||
|
import { registerSettings } from "./settings.js";
|
||||||
|
|
||||||
|
const MODULE_ID = "investigation-board";
|
||||||
|
const BASE_FONT_SIZE = 15;
|
||||||
|
const PIN_COLORS = ["redPin.webp", "bluePin.webp", "yellowPin.webp", "greenPin.webp"];
|
||||||
|
|
||||||
|
function getBaseCharacterLimits() {
|
||||||
|
return game.settings.get(MODULE_ID, "baseCharacterLimits") || {
|
||||||
|
sticky: 60,
|
||||||
|
photo: 15,
|
||||||
|
index: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDynamicCharacterLimits(noteType, currentFontSize) {
|
||||||
|
const baseLimits = getBaseCharacterLimits();
|
||||||
|
const scaleFactor = BASE_FONT_SIZE / currentFontSize;
|
||||||
|
const limits = baseLimits[noteType] || { sticky: 60, photo: 15, index: 200 };
|
||||||
|
return {
|
||||||
|
sticky: Math.round(limits.sticky * scaleFactor),
|
||||||
|
photo: Math.round(limits.photo * scaleFactor),
|
||||||
|
index: Math.round(limits.index * scaleFactor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDrawingSheet extends DrawingConfig {
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["custom-drawing-sheet"],
|
||||||
|
template: "modules/investigation-board/templates/drawing-sheet.html",
|
||||||
|
width: 400,
|
||||||
|
height: "auto",
|
||||||
|
title: "Note Configuration",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getData(options) {
|
||||||
|
const data = super.getData(options);
|
||||||
|
data.noteType = this.object.flags[MODULE_ID]?.type || "sticky";
|
||||||
|
data.text = this.object.flags[MODULE_ID]?.text || "Default Text";
|
||||||
|
data.image = this.object.flags[MODULE_ID]?.image || "modules/investigation-board/assets/placeholder.webp";
|
||||||
|
|
||||||
|
// Pass along the extra identityName for futuristic photo notes
|
||||||
|
data.identityName = this.object.flags[MODULE_ID]?.identityName || "";
|
||||||
|
|
||||||
|
// Include the board mode from settings for conditional display in the template
|
||||||
|
data.boardMode = game.settings.get(MODULE_ID, "boardMode");
|
||||||
|
|
||||||
|
data.noteTypes = {
|
||||||
|
sticky: "Sticky Note",
|
||||||
|
photo: "Photo Note",
|
||||||
|
index: "Index Card",
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _updateObject(event, formData) {
|
||||||
|
const updates = {
|
||||||
|
[`flags.${MODULE_ID}.type`]: formData.noteType,
|
||||||
|
[`flags.${MODULE_ID}.text`]: formData.text,
|
||||||
|
[`flags.${MODULE_ID}.image`]: formData.image || "modules/investigation-board/assets/placeholder.webp",
|
||||||
|
};
|
||||||
|
if (formData.identityName !== undefined) {
|
||||||
|
updates[`flags.${MODULE_ID}.identityName`] = formData.identityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await this.object.update(updates);
|
||||||
|
const drawing = canvas.drawings.get(this.object.id);
|
||||||
|
if (drawing) drawing.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add activateListeners to hook up the file-picker button.
|
||||||
|
activateListeners(element) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
html.find(".file-picker-button").click(ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
// Open Foundry's FilePicker for images; adjust the "current" directory if needed.
|
||||||
|
new FilePicker({
|
||||||
|
type: "image",
|
||||||
|
current: "modules/investigation-board/assets/",
|
||||||
|
callback: path => {
|
||||||
|
// Update the readonly input with the chosen image path.
|
||||||
|
const input = element.querySelector("input[name='image']"); if (input) input.value = path;
|
||||||
|
}
|
||||||
|
}).browse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomDrawing extends Drawing {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.bgSprite = null;
|
||||||
|
this.pinSprite = null;
|
||||||
|
this.noteText = null;
|
||||||
|
this.photoImageSprite = null;
|
||||||
|
this.identityNameText = null;
|
||||||
|
this.futuristicText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure sprites are created when the drawing is first rendered.
|
||||||
|
async draw() {
|
||||||
|
await super.draw();
|
||||||
|
await this._updateSprites();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure sprites update correctly on refresh.
|
||||||
|
async refresh() {
|
||||||
|
await super.refresh();
|
||||||
|
await this._updateSprites();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _updateSprites() {
|
||||||
|
const noteData = this.document.flags[MODULE_ID];
|
||||||
|
if (!noteData) return;
|
||||||
|
|
||||||
|
const isPhoto = noteData.type === "photo";
|
||||||
|
const isIndex = noteData.type === "index";
|
||||||
|
const mode = game.settings.get(MODULE_ID, "boardMode");
|
||||||
|
|
||||||
|
// FUTURISTIC PHOTO NOTE LAYOUT
|
||||||
|
if (isPhoto && mode === "futuristic") {
|
||||||
|
const fullWidth = game.settings.get(MODULE_ID, "photoNoteWidth");
|
||||||
|
const margin = 10;
|
||||||
|
const photoImgWidth = fullWidth * 0.4;
|
||||||
|
const photoImgHeight = photoImgWidth * (4 / 3);
|
||||||
|
const textAreaX = margin + photoImgWidth + margin;
|
||||||
|
const fullHeight = photoImgHeight + margin * 2;
|
||||||
|
|
||||||
|
// --- Background Frame ---
|
||||||
|
if (!this.bgSprite) {
|
||||||
|
this.bgSprite = new PIXI.Sprite();
|
||||||
|
this.addChildAt(this.bgSprite, 0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Always use the fixed photo frame image.
|
||||||
|
this.bgSprite.texture = PIXI.Texture.from("modules/investigation-board/assets/photoFrame.webp");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load photo frame texture", err);
|
||||||
|
this.bgSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
this.bgSprite.width = fullWidth;
|
||||||
|
this.bgSprite.height = fullHeight;
|
||||||
|
|
||||||
|
// --- Foreground (User-Assigned) Photo ---
|
||||||
|
if (!this.photoImageSprite) {
|
||||||
|
this.photoImageSprite = new PIXI.Sprite();
|
||||||
|
this.addChild(this.photoImageSprite);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Use a fallback if no image is assigned.
|
||||||
|
const imagePath = noteData.image || "modules/investigation-board/assets/placeholder.webp";
|
||||||
|
this.photoImageSprite.texture = PIXI.Texture.from(imagePath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load user photo: ${noteData.image}`, err);
|
||||||
|
this.photoImageSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
// Position the user photo inside the frame.
|
||||||
|
this.photoImageSprite.width = fullWidth * 0.9;
|
||||||
|
this.photoImageSprite.height = fullHeight * 0.9;
|
||||||
|
this.photoImageSprite.position.set(fullWidth * 0.05, fullHeight * 0.05);
|
||||||
|
|
||||||
|
// --- Identity Name and Additional Text (Futuristic) ---
|
||||||
|
const font = game.settings.get(MODULE_ID, "font");
|
||||||
|
const baseFontSize = game.settings.get(MODULE_ID, "baseFontSize");
|
||||||
|
const fontSize = (fullWidth / 200) * baseFontSize;
|
||||||
|
const textStyle = new PIXI.TextStyle({
|
||||||
|
fontFamily: font,
|
||||||
|
fontSize: fontSize,
|
||||||
|
fill: "#000000",
|
||||||
|
wordWrap: true,
|
||||||
|
wordWrapWidth: fullWidth - textAreaX - margin,
|
||||||
|
align: "left",
|
||||||
|
});
|
||||||
|
if (!this.identityNameText) {
|
||||||
|
this.identityNameText = new PIXI.Text("", textStyle);
|
||||||
|
this.addChild(this.identityNameText);
|
||||||
|
}
|
||||||
|
this.identityNameText.text = noteData.identityName || "Name";
|
||||||
|
this.identityNameText.style = textStyle;
|
||||||
|
this.identityNameText.position.set(textAreaX, margin);
|
||||||
|
|
||||||
|
if (!this.futuristicText) {
|
||||||
|
this.futuristicText = new PIXI.Text("", textStyle);
|
||||||
|
this.addChild(this.futuristicText);
|
||||||
|
}
|
||||||
|
this.futuristicText.text = noteData.text || "";
|
||||||
|
this.futuristicText.style = textStyle;
|
||||||
|
this.futuristicText.position.set(textAreaX, margin + this.identityNameText.height + 5);
|
||||||
|
|
||||||
|
// Remove default note text if present.
|
||||||
|
if (this.noteText) {
|
||||||
|
this.removeChild(this.noteText);
|
||||||
|
this.noteText.destroy();
|
||||||
|
this.noteText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Pin Handling (Futuristic) ---
|
||||||
|
const pinSetting = game.settings.get(MODULE_ID, "pinColor");
|
||||||
|
if (pinSetting === "none") {
|
||||||
|
if (this.pinSprite) {
|
||||||
|
this.removeChild(this.pinSprite);
|
||||||
|
this.pinSprite.destroy();
|
||||||
|
this.pinSprite = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.pinSprite) {
|
||||||
|
this.pinSprite = new PIXI.Sprite();
|
||||||
|
this.addChild(this.pinSprite);
|
||||||
|
}
|
||||||
|
let pinColor = noteData.pinColor;
|
||||||
|
if (!pinColor) {
|
||||||
|
pinColor = (pinSetting === "random")
|
||||||
|
? PIN_COLORS[Math.floor(Math.random() * PIN_COLORS.length)]
|
||||||
|
: `${pinSetting}Pin.webp`;
|
||||||
|
if (this.document.isOwner) {
|
||||||
|
await this.document.update({ [`flags.${MODULE_ID}.pinColor`]: pinColor });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pinImage = `modules/investigation-board/assets/${pinColor}`;
|
||||||
|
try {
|
||||||
|
this.pinSprite.texture = PIXI.Texture.from(pinImage);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load pin texture: ${pinImage}`, err);
|
||||||
|
this.pinSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
this.pinSprite.width = 40;
|
||||||
|
this.pinSprite.height = 40;
|
||||||
|
this.pinSprite.position.set(fullWidth / 2 - 20, 3);
|
||||||
|
}
|
||||||
|
return; // End early for futuristic photo notes.
|
||||||
|
}
|
||||||
|
|
||||||
|
// STANDARD LAYOUT (Modern photo notes, sticky, index, etc.)
|
||||||
|
const width = isPhoto
|
||||||
|
? game.settings.get(MODULE_ID, "photoNoteWidth")
|
||||||
|
: isIndex
|
||||||
|
? game.settings.get(MODULE_ID, "indexNoteWidth") || 600
|
||||||
|
: game.settings.get(MODULE_ID, "stickyNoteWidth");
|
||||||
|
|
||||||
|
const height = isPhoto
|
||||||
|
? Math.round(width / (225 / 290))
|
||||||
|
: isIndex
|
||||||
|
? Math.round(width / (600 / 400))
|
||||||
|
: width;
|
||||||
|
|
||||||
|
// Background Image based on board mode.
|
||||||
|
const getBackgroundImage = (noteType, mode) => {
|
||||||
|
if (mode === "futuristic") {
|
||||||
|
if (noteType === "photo") return "modules/investigation-board/assets/futuristic_photoFrame.webp";
|
||||||
|
if (noteType === "index") return "modules/investigation-board/assets/futuristic_note_index.webp";
|
||||||
|
return "modules/investigation-board/assets/futuristic_note_white.webp";
|
||||||
|
} else if (mode === "custom") {
|
||||||
|
if (noteType === "photo") return "modules/investigation-board/assets/custom_photoFrame.webp";
|
||||||
|
if (noteType === "index") return "modules/investigation-board/assets/custom_note_index.webp";
|
||||||
|
return "modules/investigation-board/assets/custom_note_white.webp";
|
||||||
|
}
|
||||||
|
// Default "modern" mode:
|
||||||
|
if (noteType === "photo") return "modules/investigation-board/assets/photoFrame.webp";
|
||||||
|
if (noteType === "index") return "modules/investigation-board/assets/note_index.webp";
|
||||||
|
return "modules/investigation-board/assets/note_white.webp";
|
||||||
|
};
|
||||||
|
const bgImage = getBackgroundImage(noteData.type, mode);
|
||||||
|
|
||||||
|
if (!this.bgSprite) {
|
||||||
|
this.bgSprite = new PIXI.Sprite();
|
||||||
|
this.addChild(this.bgSprite);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.bgSprite.texture = PIXI.Texture.from(bgImage);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load background texture: ${bgImage}`, err);
|
||||||
|
this.bgSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
this.bgSprite.width = width;
|
||||||
|
this.bgSprite.height = height;
|
||||||
|
|
||||||
|
// --- Foreground (User-Assigned) Photo for Modern Mode ---
|
||||||
|
// (This is the code missing from your current version.)
|
||||||
|
if (isPhoto) {
|
||||||
|
const fgImage = noteData.image || "modules/investigation-board/assets/placeholder.webp";
|
||||||
|
if (!this.photoImageSprite) {
|
||||||
|
this.photoImageSprite = new PIXI.Sprite();
|
||||||
|
this.addChild(this.photoImageSprite);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.photoImageSprite.texture = PIXI.Texture.from(fgImage);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load foreground texture: ${fgImage}`, err);
|
||||||
|
this.photoImageSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
// Use offsets similar to your old code.
|
||||||
|
const widthOffset = width * 0.13333;
|
||||||
|
const heightOffset = height * 0.30246;
|
||||||
|
this.photoImageSprite.width = width - widthOffset;
|
||||||
|
this.photoImageSprite.height = height - heightOffset;
|
||||||
|
this.photoImageSprite.position.set(widthOffset / 2, heightOffset / 2);
|
||||||
|
this.photoImageSprite.visible = true;
|
||||||
|
} else if (this.photoImageSprite) {
|
||||||
|
this.photoImageSprite.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Pin Handling (Standard) ---
|
||||||
|
{
|
||||||
|
const pinSetting = game.settings.get(MODULE_ID, "pinColor");
|
||||||
|
if (pinSetting === "none") {
|
||||||
|
if (this.pinSprite) {
|
||||||
|
this.removeChild(this.pinSprite);
|
||||||
|
this.pinSprite.destroy();
|
||||||
|
this.pinSprite = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.pinSprite) {
|
||||||
|
this.pinSprite = new PIXI.Sprite();
|
||||||
|
this.addChild(this.pinSprite);
|
||||||
|
}
|
||||||
|
let pinColor = noteData.pinColor;
|
||||||
|
if (!pinColor) {
|
||||||
|
pinColor = (pinSetting === "random")
|
||||||
|
? PIN_COLORS[Math.floor(Math.random() * PIN_COLORS.length)]
|
||||||
|
: `${pinSetting}Pin.webp`;
|
||||||
|
if (this.document.isOwner) {
|
||||||
|
await this.document.update({ [`flags.${MODULE_ID}.pinColor`]: pinColor });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pinImage = `modules/investigation-board/assets/${pinColor}`;
|
||||||
|
try {
|
||||||
|
this.pinSprite.texture = PIXI.Texture.from(pinImage);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load pin texture: ${pinImage}`, err);
|
||||||
|
this.pinSprite.texture = PIXI.Texture.EMPTY;
|
||||||
|
}
|
||||||
|
this.pinSprite.width = 40;
|
||||||
|
this.pinSprite.height = 40;
|
||||||
|
this.pinSprite.position.set(width / 2 - 20, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default text layout for non-futuristic notes.
|
||||||
|
const font = game.settings.get(MODULE_ID, "font");
|
||||||
|
const baseFontSize = game.settings.get(MODULE_ID, "baseFontSize");
|
||||||
|
const fontSize = (width / 200) * baseFontSize;
|
||||||
|
const textStyle = new PIXI.TextStyle({
|
||||||
|
fontFamily: font,
|
||||||
|
fontSize: fontSize,
|
||||||
|
fill: "#000000",
|
||||||
|
wordWrap: true,
|
||||||
|
wordWrapWidth: width - 15,
|
||||||
|
align: "center",
|
||||||
|
});
|
||||||
|
const truncatedText = this._truncateText(noteData.text || "Default Text", font, noteData.type, fontSize);
|
||||||
|
if (!this.noteText) {
|
||||||
|
this.noteText = new PIXI.Text(truncatedText, textStyle);
|
||||||
|
this.noteText.anchor.set(0.5);
|
||||||
|
this.addChild(this.noteText);
|
||||||
|
} else {
|
||||||
|
this.noteText.style = textStyle;
|
||||||
|
this.noteText.text = truncatedText;
|
||||||
|
}
|
||||||
|
this.noteText.position.set(width / 2, isPhoto ? height - 25 : height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_truncateText(text, font, noteType, currentFontSize) {
|
||||||
|
const limits = getDynamicCharacterLimits(font, currentFontSize);
|
||||||
|
const charLimit = limits[noteType] || 100;
|
||||||
|
return text.length <= charLimit ? text : text.slice(0, charLimit).trim() + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function createNote(noteType) {
|
||||||
|
const scene = canvas.scene;
|
||||||
|
if (!scene) {
|
||||||
|
console.error("Cannot create note: No active scene.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve width settings (or use defaults)
|
||||||
|
const stickyW = game.settings.get(MODULE_ID, "stickyNoteWidth") || 200;
|
||||||
|
const photoW = game.settings.get(MODULE_ID, "photoNoteWidth") || 225;
|
||||||
|
const indexW = game.settings.get(MODULE_ID, "indexNoteWidth") || 600;
|
||||||
|
|
||||||
|
const width = noteType === "photo" ? photoW
|
||||||
|
: noteType === "index" ? indexW
|
||||||
|
: stickyW;
|
||||||
|
const height = noteType === "photo" ? Math.round(photoW / (225 / 290))
|
||||||
|
: noteType === "index" ? Math.round(indexW / (600 / 400))
|
||||||
|
: stickyW;
|
||||||
|
|
||||||
|
const dims = canvas.dimensions;
|
||||||
|
const x = dims.width / 2;
|
||||||
|
const y = dims.height / 2;
|
||||||
|
|
||||||
|
// Get default text from settings (fallback if missing)
|
||||||
|
const defaultText = game.settings.get(MODULE_ID, `${noteType}NoteDefaultText`) || "Notes";
|
||||||
|
|
||||||
|
// Determine board mode and include identityName if note is a futuristic photo note
|
||||||
|
const boardMode = game.settings.get(MODULE_ID, "boardMode");
|
||||||
|
const extraFlags = {};
|
||||||
|
if (noteType === "photo" && boardMode === "futuristic") {
|
||||||
|
extraFlags.identityName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
await canvas.scene.createEmbeddedDocuments("Drawing", [
|
||||||
|
{
|
||||||
|
type: "r",
|
||||||
|
author: game.user.id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
shape: { width, height },
|
||||||
|
fillColor: "#ffffff",
|
||||||
|
fillAlpha: 1,
|
||||||
|
strokeColor: "transparent",
|
||||||
|
strokeAlpha: 0,
|
||||||
|
locked: false,
|
||||||
|
flags: {
|
||||||
|
[MODULE_ID]: {
|
||||||
|
type: noteType,
|
||||||
|
text: defaultText,
|
||||||
|
...extraFlags
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"flags.core.sheetClass": "investigation-board.CustomDrawingSheet",
|
||||||
|
"ownership": { default: 3 },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
canvas.drawings.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Hooks.on("getSceneControlButtons", (controls) => {
|
||||||
|
const journalControls = controls.find((c) => c.name === "notes");
|
||||||
|
if (!journalControls) return;
|
||||||
|
|
||||||
|
journalControls.tools.push(
|
||||||
|
{ name: "createStickyNote", title: "Create Sticky Note", icon: "fas fa-sticky-note", onClick: () => createNote("sticky"), button: true },
|
||||||
|
{ name: "createPhotoNote", title: "Create Photo Note", icon: "fa-solid fa-camera-polaroid", onClick: () => createNote("photo"), button: true },
|
||||||
|
{ name: "createIndexCard", title: "Create Index Card", icon: "fa-regular fa-subtitles", onClick: () => createNote("index"), button: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
registerSettings();
|
||||||
|
CONFIG.Drawing.objectClass = CustomDrawing;
|
||||||
|
|
||||||
|
DocumentSheetConfig.registerSheet(DrawingDocument, "investigation-board", CustomDrawingSheet, {
|
||||||
|
label: "Note Drawing Sheet",
|
||||||
|
types: ["base"],
|
||||||
|
makeDefault: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Investigation Board module initialized.");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export { CustomDrawing, CustomDrawingSheet };
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
export const MODULE_ID = "investigation-board";
|
||||||
|
|
||||||
|
export const registerSettings = function() {
|
||||||
|
|
||||||
|
const refreshAllDrawings = () => {
|
||||||
|
if (canvas.drawings) {
|
||||||
|
canvas.drawings.placeables.forEach(drawing => {
|
||||||
|
if (drawing.document.flags[MODULE_ID]) {
|
||||||
|
drawing.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the pinColor setting to include a "No Pins" option.
|
||||||
|
game.settings.register(MODULE_ID, "pinColor", {
|
||||||
|
name: "Pin Color",
|
||||||
|
hint: "Choose the color of the pin for notes. Selecting 'Random' will randomly assign a pin color. Select 'No Pins' to disable pin display.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
random: "Random",
|
||||||
|
red: "Red",
|
||||||
|
blue: "Blue",
|
||||||
|
yellow: "Yellow",
|
||||||
|
green: "Green",
|
||||||
|
none: "No Pins"
|
||||||
|
},
|
||||||
|
default: "random",
|
||||||
|
onChange: () => {
|
||||||
|
if (canvas.drawings) {
|
||||||
|
canvas.drawings.placeables.forEach(drawing => drawing.refresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register a new setting for board mode selection.
|
||||||
|
game.settings.register(MODULE_ID, "boardMode", {
|
||||||
|
name: "Board Mode",
|
||||||
|
hint: "Select the board mode to change the styling of notes.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
modern: "Modern",
|
||||||
|
futuristic: "Futuristic",
|
||||||
|
custom: "Custom"
|
||||||
|
},
|
||||||
|
default: "modern",
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Register existing settings
|
||||||
|
game.settings.register(MODULE_ID, "stickyNoteWidth", {
|
||||||
|
name: "Sticky Note Width",
|
||||||
|
hint: "The width (in pixels) for all newly created sticky notes (default: 200).",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 200,
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "photoNoteWidth", {
|
||||||
|
name: "Photo Note Width",
|
||||||
|
hint: "The width (in pixels) for all newly created photo notes (default: 225).",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 225,
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "indexNoteWidth", {
|
||||||
|
name: "Index Note Width",
|
||||||
|
hint: "The width (in pixels) for all newly created index cards (default: 600).",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 600,
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "baseFontSize", {
|
||||||
|
name: "Base Font Size",
|
||||||
|
hint: "The font size (in pixels) for text when the note width is at its default size (default: 16).",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 16,
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "font", {
|
||||||
|
name: "Font",
|
||||||
|
hint: "Choose the font to be used in notes.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
"Rock Salt": "Rock Salt",
|
||||||
|
"Courier New": "Courier New",
|
||||||
|
"Times New Roman": "Times New Roman",
|
||||||
|
"Signika": "Signika",
|
||||||
|
"Arial": "Arial"
|
||||||
|
},
|
||||||
|
default: "Rock Salt",
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "characterNameKey", {
|
||||||
|
name: "Character Name Key",
|
||||||
|
hint: "Specify the key path to retrieve the name (e.g., 'prototypeToken.name' or 'system.alias' for Blades in the Dark). If empty, defaults to 'name'.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
default: "prototypeToken.name",
|
||||||
|
type: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "stickyNoteDefaultText", {
|
||||||
|
name: "Default Sticky Note Text",
|
||||||
|
hint: "The default text to use for new sticky notes.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: "Clue",
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "photoNoteDefaultText", {
|
||||||
|
name: "Default Photo Note Text",
|
||||||
|
hint: "The default text to use for new photo notes.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: "Suspect/Place",
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(MODULE_ID, "indexNoteDefaultText", {
|
||||||
|
name: "Default Index Note Text",
|
||||||
|
hint: "The default text to use for new index notes.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: "Notes",
|
||||||
|
onChange: () => refreshAllDrawings()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register base font size and character limits
|
||||||
|
game.settings.register(MODULE_ID, "baseCharacterLimits", {
|
||||||
|
name: "Base Character Limits",
|
||||||
|
hint: "The base character limits for each font and note type. Edit this JSON to customize.",
|
||||||
|
scope: "world",
|
||||||
|
config: false, // Hidden from the settings UI
|
||||||
|
type: Object,
|
||||||
|
default: {
|
||||||
|
"Rock Salt": { sticky: 90, photo: 20, index: 210 },
|
||||||
|
"Courier New": { sticky: 250, photo: 30, index: 580 },
|
||||||
|
"Times New Roman": { sticky: 200, photo: 30, index: 800 },
|
||||||
|
"Signika": { sticky: 200, photo: 30, index: 650 },
|
||||||
|
"Arial": { sticky: 200, photo: 30, index: 650 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Rock Salt';
|
||||||
|
src: url('../assets/fonts/rock_salt.ttf') format('ttf');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-drawing-text {
|
||||||
|
font-family: 'Rock Salt', Arial, sans-serif;
|
||||||
|
color: #000000; /* Black text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-note {
|
||||||
|
background-image: url("modules/investigation-board/assets/note_white.webp");
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-note {
|
||||||
|
background-image: url("modules/investigation-board/assets/photoFrame.webp");
|
||||||
|
width: 250px;
|
||||||
|
height: 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* EDIT FORM */
|
||||||
|
/*
|
||||||
|
.note-config-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 400px;
|
||||||
|
min-height: 310px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper.sticky-mode {
|
||||||
|
background-image: url("../assets/note_white.webp");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper.photo-mode {
|
||||||
|
background-image: url("../assets/photoFrame.webp");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper .window-content {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper form {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-type-group {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-text-group {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper.sticky-mode .note-text-group {
|
||||||
|
top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper.photo-mode .note-text-group {
|
||||||
|
top: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-image-group {
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 20px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-save-group {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper input,
|
||||||
|
.note-config-wrapper textarea,
|
||||||
|
.note-config-wrapper select {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-config-wrapper label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-picker-button {
|
||||||
|
margin-left: 5px;
|
||||||
|
background: #eee;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 2px 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
background: #3f51b5;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.save-button i {
|
||||||
|
margin-right: 5px;
|
||||||
|
} */
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="text">Note Text:</label>
|
||||||
|
<textarea name="text" rows="4">{{text}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Only show the Image Path field for photo notes --}}
|
||||||
|
{{#if (eq noteType "photo")}}
|
||||||
|
<div class="form-group" style="display: block;">
|
||||||
|
<label for="image">Image Path:</label>
|
||||||
|
<div class="file-picker">
|
||||||
|
<input type="text" name="image" value="{{image}}" placeholder="Enter image path" readonly />
|
||||||
|
<button type="button" class="file-picker-button">
|
||||||
|
<i class="fas fa-folder-open"></i> Browse
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="form-group" style="display: none;"></div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{!-- If boardMode is futuristic and noteType is photo, show the extra Identity Name field --}}
|
||||||
|
{{#if (eq noteType "photo")}}
|
||||||
|
{{#if (eq boardMode "futuristic")}}
|
||||||
|
<div class="form-group" style="display: block;">
|
||||||
|
<label for="identityName">Identity Name:</label>
|
||||||
|
<input type="text" name="identityName" value="{{identityName}}" placeholder="Enter identity name" />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="save-button">
|
||||||
|
<i class="fas fa-save"></i> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Universe-class Mass Conveyor",
|
|
||||||
"type": "vehicle",
|
|
||||||
"img": "icons/svg/ship.svg",
|
|
||||||
"folder": null,
|
|
||||||
"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,
|
|
||||||
"manoeuvrability": -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": [
|
|
||||||
{
|
|
||||||
"_id": "ABCD1234EFGH5678",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": 2, "power": 2 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 1, "detection": 1, "speed": 0, "space": 0, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "IJKL9012MNOP3456",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": 2, "power": 3 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": 0, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "QRST7890UVWX1234",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": 3, "power": 6 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": 0, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "YZAB5678CDEF9012",
|
|
||||||
"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": { "voidship": { "slot": "Aetherics", "use": { "space": 1, "power": 2 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 2, "speed": 0, "space": 0, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "GHIJ3456KLMN7890",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": 1, "power": 0 }, "mods": { "armour": 2, "integrity": 4, "manoeuvre": -1, "detection": 0, "speed": 0, "space": 0, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "PQRS9012TUVW3456",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": -5, "power": 1 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": -5, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "WXYZ1234ABCD5678",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": -5, "power": 1 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": -5, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "EFGH9012IJKL3456",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": -5, "power": 1 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": -5, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "MNOP5678QRST9012",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": -5, "power": 1 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": -5, "power": 0 } } }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "UVWX3456YZAB7890",
|
|
||||||
"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": { "voidship": { "slot": "Internal", "use": { "space": 2, "power": -6 }, "mods": { "armour": 0, "integrity": 0, "manoeuvre": 0, "detection": 0, "speed": 0, "space": 0, "power": 6 } } }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||