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,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)
|
||||
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
|
||||
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 (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
|
||||
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 (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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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,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,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,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,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,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,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,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,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,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,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,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"
|
||||
}
|
||||