2 Commits
+492 -21
View File
@@ -457,6 +457,286 @@ const ARCHETYPES = {
},
};
// ============================================================================
// Carrien Salvaged Weapons & Attacks Database
// ============================================================================
const CARRIEN_WEAPONS = [
{ name: 'Feral Claws & Bite', damage: '1d10+3', ammo: 0, shots: 0, useAmmo: false, description: 'Sharp dirt-clogged nails and jagged teeth. Aggressive close-quarters rending.' },
{ name: 'Rust-Machete', damage: '1d8+3', ammo: 0, shots: 0, useAmmo: false, description: 'A heavy piece of salvaged structural plating ground into a brutal, jagged cleaver.' },
{ name: 'Scrap-Iron Club', damage: '1d8+4', ammo: 0, shots: 0, useAmmo: false, description: 'A rusted concrete-reinforcement bar (rebar). High blunt force damage.' },
{ name: 'Rusted Harpoon', damage: '1d10+2', ammo: 0, shots: 0, useAmmo: false, description: 'A sharp, barbed sewage tip wired to a long pole. Allows reach/thrust attacks.' },
{ name: 'Spiked Chain', damage: '1d8+2', ammo: 0, shots: 0, useAmmo: false, description: 'A heavy industrial drive chain wrapped with jagged scrap and rusted bolts.' },
{ name: 'Feral Acid Spit', damage: '1d6', ammo: 0, shots: 0, useAmmo: false, description: 'A burning chemical runoff secretion. Direct corrosive biological projectile.' },
{ name: 'Scrap Blunderbuss', damage: '2d6', ammo: 1, shots: 1, useAmmo: true, ammoType: 'Shotgun', description: 'A pipe jammed with gunpowder and packed with gravel, bolts, and broken glass.' },
{ name: 'Undercity Cleaver', damage: '1d6+4', ammo: 0, shots: 0, useAmmo: false, description: 'An enormous rusted butcher blade scavenged from abandoned sector packaging plants.' },
{ name: 'Feral Shock Prod', damage: '1d8', ammo: 5, shots: 1, useAmmo: true, ammoType: 'Battery', description: 'A broken sector perimeter baton held together with copper wire. Emits electrical discharge.' },
{ name: 'Sewer Crossbow', damage: '1d10', ammo: 1, shots: 1, useAmmo: true, ammoType: 'Bolt', description: 'A heavy leaf-spring mechanical bow firing sharp scrap iron bolts.' },
{ name: 'Pneumatic Nail-Gun', damage: '1d8', ammo: 10, shots: 1, useAmmo: true, ammoType: 'Nails', description: 'Repurposed industrial nail-gun powered by a scrap gas canister.' },
{ name: 'Pipe-Bomb', damage: '2d10', ammo: 1, shots: 1, useAmmo: true, ammoType: 'Grenade', description: 'A metal pipe capped at both ends, filled with fertilizer explosive and matchheads.' },
{ name: 'Scavenged GA Pistol', damage: '1d10+2', ammo: 15, shots: 1, useAmmo: true, ammoType: 'Pistol', description: 'A standard Shiver sidearm, dirty, worn, but functional.' },
{ name: 'Razor-Wire Whip', damage: '1d6+3', ammo: 0, shots: 0, useAmmo: false, description: 'Coils of rusty barbed wire wrapped around a handle.' }
];
// ============================================================================
// SLA Industries Carrien Breeds Database
// ============================================================================
const CARRIEN_BREEDS = {
feral: {
name: 'Sewer Feral',
speciesLabel: 'Feral Carrien',
description: 'The standard, starved undercity scavenger. Highly aggressive and operates in packs.',
statMods: { strength: 0, speed: 0, intellect: 0, combat: 0, body: 0 },
skills: [{ name: 'Stealth', rank: 'Trained', bonus: 10 }],
allowedWeapons: ['Feral Claws & Bite', 'Scrap-Iron Club', 'Rust-Machete', 'Spiked Chain'],
dispositions: ['Feral', 'Starving', 'Aggressive', 'Territorial'],
threat: 'Moderate',
trait: {
name: 'Pack Tactics',
effects: 'Gain a bonus to melee attacks when another allied Carrien is close to the target.',
package: 'Carrien Breed Trait'
},
skulls: [
'a cracked pig skull tied with greasy wire',
'a yellowed dog skull with broken teeth',
'a mud-covered goat skull with one horn snapped off',
'a foul scavenger bird skull with trailing industrial ribbons'
],
heightRange: [1.5, 1.7],
weightRange: [50, 70],
ageRange: [3, 9]
},
scout: {
name: 'Undercity Scout',
speciesLabel: 'Feral Carrien',
description: 'A nimble, silent pack-runner specialized in exploring sector cracks and locating prey.',
statMods: { strength: -5, speed: 10, intellect: 2, combat: 5, body: -5 },
skills: [
{ name: 'Stealth', rank: 'Expert', bonus: 15 },
{ name: 'Observation', rank: 'Trained', bonus: 10 }
],
allowedWeapons: ['Sewer Crossbow', 'Feral Acid Spit', 'Rust-Machete'],
dispositions: ['Feral', 'Suspicious', 'Alert', 'Skittish'],
threat: 'Moderate',
trait: {
name: 'Undercity Camouflage',
effects: 'Virtually invisible in dark sewers and tunnels. Gain a +15 bonus to Stealth checks in dim light.',
package: 'Carrien Breed Trait'
},
skulls: [
'a sleek, elongated rodent skull',
'a small feral cat skull with copper wire whiskers',
'a bird skull with scrap industrial goggles wired over the eye sockets',
'a split badger skull bound in dirty insulation tape'
],
heightRange: [1.4, 1.6],
weightRange: [42, 60],
ageRange: [4, 10]
},
greater: {
name: 'Greater Hulk',
speciesLabel: 'Greater Carrien',
description: 'A massive, heavily muscled alpha breed that towers over standard feral pack-runners.',
statMods: { strength: 15, speed: -4, intellect: -2, combat: 10, body: 15 },
skills: [
{ name: 'Melee Combat', rank: 'Expert', bonus: 20 },
{ name: 'Intimidation', rank: 'Trained', bonus: 15 }
],
allowedWeapons: ['Undercity Cleaver', 'Rust-Machete', 'Rusted Harpoon', 'Scrap-Iron Club'],
dispositions: ['Aggressive', 'Hostile', 'Territorial', 'Feral'],
threat: 'High',
trait: {
name: 'Hulking Charge',
effects: 'If this creature moves at least 10 meters before a Melee hit, the target must make a Strength check or be knocked prone.',
package: 'Carrien Breed Trait'
},
skulls: [
'a huge, blood-stained bull skull with broad cracked horns',
'a massive armored boar skull with protruding rusted iron tusks',
'a thick-plated stormer head skull scavenged from a corporate landfill',
'a heavy industrial helmet welded over a split canine skull'
],
heightRange: [2.1, 2.4],
weightRange: [130, 185],
ageRange: [6, 12]
},
ursa: {
name: 'Ursa Raider',
speciesLabel: 'Ursa Carrien',
description: 'An evolved, highly intelligent strain of Carrien capable of complex tactics and mechanical salvage.',
statMods: { strength: 5, speed: 4, intellect: 12, combat: 8, body: 5 },
skills: [
{ name: 'Melee Combat', rank: 'Trained', bonus: 10 },
{ name: 'Firearms', rank: 'Trained', bonus: 10 }
],
allowedWeapons: ['Scrap Blunderbuss', 'Sewer Crossbow', 'Feral Shock Prod', 'Rust-Machete'],
dispositions: ['Cunning', 'Hostile', 'Disciplined', 'Aggressive'],
threat: 'High',
trait: {
name: 'Scrap Mastery',
effects: 'Expertly utilizes salvaged firearms. Weapons cannot jam due to critical failure, and reload speed is halved.',
package: 'Carrien Breed Trait'
},
skulls: [
'a human skull with SLA operative logo painted in white grease',
'a refined metal-reinforced dog skull with riveted copper plates',
'a split ram skull with thick coiled horns wrapped in barbed wire',
'a vintage industrial gas mask bolted directly to a pristine pig skull'
],
heightRange: [1.7, 1.9],
weightRange: [75, 95],
ageRange: [5, 14]
},
domesticated: {
name: 'Domesticated Operative',
speciesLabel: 'Advanced Carrien',
description: 'An advanced Carrien captured, trained, and conditioned by SLA to act as specialized contract support.',
statMods: { strength: 2, speed: 5, intellect: 15, combat: 10, body: 2 },
skills: [
{ name: 'Firearms', rank: 'Expert', bonus: 15 },
{ name: 'Unarmed Combat', rank: 'Trained', bonus: 10 }
],
allowedWeapons: ['Scavenged GA Pistol', 'Feral Shock Prod', 'Rust-Machete'],
dispositions: ['Disciplined', 'Professional', 'Cold', 'Alert'],
threat: 'Moderate',
trait: {
name: 'SLA Conditioning',
effects: 'Conditioned against mental fracture. Gain +15 to Resolve, and immune to Panic checks triggered by combat damage.',
package: 'Carrien Breed Trait'
},
skulls: [
'a sleek fiberglass replica skull branded with the SLA Industries seal',
'a pristine porcelain canine skull mask',
'a subdued tactical helmet with an integrated visor built into a small goat skull',
'no skull mask — wearing a standard tactical balaclava with carbon fiber cheek guards'
],
heightRange: [1.6, 1.8],
weightRange: [65, 85],
ageRange: [6, 16]
},
mutant: {
name: 'Mutant Scourge',
speciesLabel: 'Mutant Carrien',
description: 'A terrifying Carrien warped by high-level toxic sludge. Unpredictable and wildly dangerous.',
statMods: { strength: 10, speed: 6, intellect: -5, combat: 12, body: 10 },
skills: [
{ name: 'Melee Combat', rank: 'Expert', bonus: 15 },
{ name: 'Intimidation', rank: 'Expert', bonus: 15 }
],
allowedWeapons: ['Feral Claws & Bite', 'Feral Acid Spit', 'Feral Shock Prod', 'Spiked Chain'],
dispositions: ['Unstable', 'Hostile', 'Manic', 'Demented'],
threat: 'Extreme',
trait: {
name: 'Corrosive Backlash',
effects: 'When damaged by a melee slashing or piercing attack, deals 1d6 corrosive acid splash damage to the attacker.',
package: 'Carrien Breed Trait'
},
skulls: [
'a glowing, irradiated wolf skull showing weeping chemical pustules',
'a melted, half-fused mutant skull with wire staples',
'a hideous double-jawed dog skull mask',
'two bird skulls wired side-by-side to cover a bloated mutant face'
],
heightRange: [1.8, 2.1],
weightRange: [90, 130],
ageRange: [2, 6]
}
};
// ============================================================================
// Dossier & Biography generator for SLA Industries Sheet Integration
// ============================================================================
function buildDossierData(archetypeKey, species, name, background, breed = null) {
const cap = (s) => String(s ?? "").charAt(0).toUpperCase() + String(s ?? "").slice(1);
const speciesLabel = cap(species === 'human' && archetypeKey === 'carrien' ? 'Advanced Carrien' : species);
if (archetypeKey === 'carrien' && breed) {
return {
role: { value: breed.name },
archetype: { value: 'Carrien Threat' },
species: { value: breed.speciesLabel },
affiliation: { value: pick(['Hostile Sewer Swarm', 'Deep Undercity Feral Pack', 'Waste Sector Warrens', 'Undercity Marauders']) },
department: { value: pick(['Lower Sewer Grid', 'Pipeline Access Shafts', 'Abandoned Drainage Hubs']) },
sector: { value: pick(['Cannibal Sector 1', 'Cannibal Sector 2', 'Lower Sewer Levels', 'Deep Undercity']) },
disposition: { value: pick(breed.dispositions) },
threat: { value: breed.threat },
summary: { value: background }
};
}
const roles = {
civilian: ['Hab-block Clerk', 'Street Tech Merchant', 'Recycling Loader', 'Transit Gatekeeper', 'Off-duty Laborer', 'Dossier Runner'],
shiver: ['Precinct Patrol Officer', 'Armed Response Trooper', 'Cordon Watch Sergeant', 'Sector Patrol Ranger', 'Security Escort Detail'],
carrien: ['Pack Stalker', 'Undercity Runner', 'Tunnel Ambusher', 'Nest Scout', 'Nest Alpha'],
gang: ['Street Turf Lookout', 'Block Crew Muscle', 'Corner Runner', 'Street Debt Collector', 'Back-alley Brawler'],
corporate: ['Departmental Fixer', 'Karma Lead Scientist', 'Sponsor Asset Overseer', 'Case Auditor', 'Blue Corp Special Envoy'],
mutant: ['Canker Street Carver', 'Block Eight Ripper', 'Substation Bleeder', 'Duct Work Phantom', 'Basement Surgeon']
}[archetypeKey] ?? ['Dossier Subject'];
const affiliations = {
civilian: ['Mort Civil Sector', 'Block Hab Committee', 'Public Transit Utility', 'Independent Trader'],
shiver: ['SLA Industries (Shiver Division)', 'Precinct 88 Wardens', 'Sector Cordon Detail'],
carrien: ['Hostile Sewer Swarm', 'Deep Undercity Feral Pack', 'Waste Sector Marauders'],
gang: ['Chrome Jacks', 'Sump Dogs', 'Red Wire Crew', 'Murder Mile Kings', 'Ash Runners'],
corporate: ['SLA Industries Operations', 'MediCore Containment Division', 'Karma Special Projects', 'White Corp Administration'],
mutant: ['Independent / Disavowed', 'Active Outcast / Hostile', 'Unregistered Threat Profile']
}[archetypeKey] ?? ['Independent'];
const departments = {
civilian: ['District Admin', 'Sub-Level Hab Blocks', 'Transit Operations', 'Sector Maintenance Detail'],
shiver: ['Beat Patrol Squad B', 'High-Risk Tactical Unit', 'Precinct Watch Desk', 'Anti-Crime Division'],
carrien: ['Lower Pumping Station Nest', 'Maintenance Pipeline Grid', 'Deep Waste Warrens'],
gang: ['East Cut Corner Crew', 'Recycling Stack Block', 'Downtown Turf Detail', 'Monorail Corridor Raiders'],
corporate: ['Oversight Compliance Unit', 'Asset Reclamation Office', 'Research Project Containment', 'External Affairs Desk'],
mutant: ['Mutated Containment Breaker', 'Active Pattern Serial Cell', 'Feral Underlevel Scourge']
}[archetypeKey] ?? ['None'];
const sectors = {
civilian: ['Mort City Central', 'Downtown Mort', 'SubUrban District 4', 'Sector Nine Residential'],
shiver: ['Mort City Cordon', 'Downtown Precincts', 'Lower Transit Junctions', 'Perimeter Gates'],
carrien: ['Cannibal Sectors', 'Transit Underlevels', 'Deep Waste District', 'Sewer Runoff Tunnels'],
gang: ['Cannibal Sectors', 'Transit Underlevels', 'Mort Lower Tunnels', 'Industrial Belt Edge'],
corporate: ['Corporate High-SCL Arc', 'Downtown Executive Plaza', 'Bonded Warehouse Sectors'],
mutant: ['Cannibal Sectors', 'Transit Underlevels', 'Abandoned Tenement Block 8', 'Waste District']
}[archetypeKey] ?? ['Mort City'];
const dispositions = {
civilian: ['Nervous', 'Suspicious', 'Quiet', 'Helpful', 'Terrified'],
shiver: ['Professional', 'Alert', 'Disciplined', 'Aggressive', 'Suspicious'],
carrien: ['Feral', 'Hostile', 'Territorial', 'Starving', 'Aggressive'],
gang: ['Aggressive', 'Hostile', 'Suspicious', 'Fanatical', 'Cornered'],
corporate: ['Professional', 'Cold', 'Polite', 'Ruthless', 'Calculating'],
mutant: ['Unstable', 'Hostile', 'Fanatical', 'Manic', 'Demented']
}[archetypeKey] ?? ['Neutral'];
const threats = {
civilian: ['Low'],
shiver: ['Moderate', 'High'],
carrien: ['Moderate', 'High'],
gang: ['Moderate'],
corporate: ['Moderate', 'High'],
mutant: ['High', 'Extreme']
}[archetypeKey] ?? ['Moderate'];
const role = pick(roles);
const affiliation = pick(affiliations);
const department = pick(departments);
const sector = pick(sectors);
const disposition = pick(dispositions);
const threat = pick(threats);
return {
role: { value: role },
archetype: { value: ARCHETYPES[archetypeKey]?.label ?? cap(archetypeKey) },
species: { value: speciesLabel },
affiliation: { value: affiliation },
department: { value: department },
sector: { value: sector },
disposition: { value: disposition },
threat: { value: threat },
summary: { value: background }
};
}
const SPECIES_OPTIONS = [
{ value: 'random', label: 'Random (archetype-appropriate)' },
{ value: 'human', label: 'Human' },
@@ -472,12 +752,18 @@ const SPECIES_OPTIONS = [
// Name generation
// ============================================================================
function generateName(species, archetypeKey) {
function generateName(species, archetypeKey, breed = null) {
// Serial killers / mutants get a pre-built infamous identity
if (archetypeKey === 'mutant') {
return pick(SERIAL_KILLER_NAMES);
}
// Carrien names are quick animalistic gutter tags
if (archetypeKey === 'carrien') {
const carrienNames = ['Gnash', 'Skitter', 'Slink', 'Bite', 'Rip', 'Tear', 'Gouge', 'Mire', 'Slime', 'Pox', 'Sewer', 'Drip', 'Claw', 'Feral', 'Snarl', 'Howl', 'Gore', 'Barrow', 'Sump', 'Drain', 'Grime', 'Scrap', 'Spit', 'Rust', 'Fang', 'Shred', 'Mangle', 'Carcass', 'Vulture', 'Rot'];
return pick(carrienNames);
}
// Stormer names are alphanumeric designations
if (species === 'stormer') {
const designations = ['Storm','Bravo','Vector','Apex','Delta','Echo','Grid','Havoc','Iron','Jackal','Kilo','Lima'];
@@ -569,12 +855,36 @@ async function generateNpc(archetypeKey, speciesChoice, manager, folderId) {
? pick(def.defaultSpecies)
: speciesChoice;
// Resolve breed details if Carrien
let breed = null;
let carrienSkills = [...def.skills];
if (archetypeKey === 'carrien') {
const breedKey = pick(Object.keys(CARRIEN_BREEDS));
breed = CARRIEN_BREEDS[breedKey];
}
// Generate name
const name = generateName(species, archetypeKey);
const name = generateName(species, archetypeKey, breed);
// Generate stats
const stats = generateStats(archetypeKey);
// Apply breed-specific stat modifiers and skills if Carrien
if (breed) {
for (const [statKey, mod] of Object.entries(breed.statMods)) {
if (stats[statKey]) {
stats[statKey].value = Math.max(3, stats[statKey].value + mod);
}
}
if (breed.skills) {
for (const bs of breed.skills) {
if (!carrienSkills.some(s => s.name === bs.name)) {
carrienSkills.push(bs);
}
}
}
}
// Health = body stat (simplified; Foundry system will recalculate in some builds)
const bodyVal = stats.body?.value ?? 30;
const healthVal = Math.max(5, Math.floor(bodyVal / 2));
@@ -615,8 +925,43 @@ async function generateNpc(archetypeKey, speciesChoice, manager, folderId) {
portraitPath = portrait?.path ?? null;
}
// Background
const background = pick(def.backgrounds);
// Generate detailed dossier variables (gender, age, height, weight, SCL, credits, background description)
let gender = pick(['Male', 'Female', 'Undetermined']);
let age = roll(18, 55);
let height = `${(roll(160, 195) / 100).toFixed(2)}m`;
let weight = `${roll(60, 95)}kg`;
let scl = '10';
let credits = roll(20, 150);
let background = pick(def.backgrounds);
if (archetypeKey === 'carrien' && breed) {
gender = pick(['Male', 'Female', 'Undetermined']);
age = roll(breed.ageRange[0], breed.ageRange[1]);
height = `${(roll(breed.heightRange[0] * 100, breed.heightRange[1] * 100) / 100).toFixed(2)}m`;
weight = `${roll(breed.weightRange[0], breed.weightRange[1])}kg`;
scl = 'None';
credits = 0;
const skull = pick(breed.skulls);
background = `A feral Carrien of the ${breed.name} breed. Standing roughly ${height} and weighing ${weight}, this creature represents the primal terror of Mort's underlevels. It wears ${skull} to mask its expressive, human-like features. It is currently acting with a ${breed.threat.toLowerCase()}-threat profile. Behavior is noted as extremely ${pick(breed.dispositions).toLowerCase()} and aggressive when cornered.`;
} else if (archetypeKey === 'shiver') {
scl = pick(['9', '9', '9', '8']); // 8 is Sergeant
credits = roll(100, 450);
} else if (archetypeKey === 'corporate') {
scl = pick(['7', '7', '6', '5']);
credits = roll(1000, 5000);
} else if (archetypeKey === 'mutant') {
scl = 'Outlaw';
credits = roll(0, 20);
height = `${(roll(180, 240) / 100).toFixed(2)}m`;
weight = `${roll(85, 150)}kg`;
} else if (archetypeKey === 'civilian') {
scl = '10';
credits = roll(10, 150);
}
// Generate dossier data
const dossier = buildDossierData(archetypeKey, species, name, background, breed);
// Actor type: mutant/creature uses 'creature', everything else uses 'character'
const actorType = isCreature ? 'creature' : 'character';
@@ -631,7 +976,42 @@ async function generateNpc(archetypeKey, speciesChoice, manager, folderId) {
health: { value: healthVal, min: 0, max: healthVal },
biography: background,
notes: `Archetype: ${def.label}\nSpecies: ${species}\nOperative Type: ${def.operativeType}`,
stats,
// System-compatible dossier parameters
details: {
age: String(age),
height: String(height),
weight: String(weight),
scl: String(scl),
credits: Number(credits)
},
bio: {
species: species.charAt(0).toUpperCase() + species.slice(1),
scl: String(scl),
background: background,
appearance: breed ? `Wears ${pick(breed.skulls)}. Slouched, feral posture.` : `Civilian clothing, typical of Lower Mort sectors.`,
notes: `Threat Profile: ${dossier.threat.value}`
},
finance: {
credits: Number(credits),
unis: 0,
lad: 0
},
gender: gender,
age: age,
height: height,
weight: weight,
wealth: Number(credits),
stats: {
...stats,
armor: { value: armorVal, mod: armorVal, damageReduction: archetypeKey === 'shiver' ? 1 : (archetypeKey === 'mutant' ? 2 : 0), enabled: true },
combat: { ...stats.combat, enabled: true },
instinct: { ...stats.instinct, enabled: true },
speed: { ...stats.speed, enabled: true },
strength: { ...stats.strength, enabled: true },
sanity: { ...stats.sanity, enabled: true },
fear: { ...stats.fear, enabled: true },
body: { ...stats.body, enabled: true }
},
other: {
stress: { value: 2, min: 2, max: stressMax },
resolve: { value: resolveMax, min: 0, max: resolveMax },
@@ -639,7 +1019,10 @@ async function generateNpc(archetypeKey, speciesChoice, manager, folderId) {
sla: {
operativeType: { value: def.operativeType },
species: { value: species.charAt(0).toUpperCase() + species.slice(1) },
scl: String(scl),
credits: Number(credits)
},
slaNpc: dossier
},
prototypeToken: {
name,
@@ -658,11 +1041,91 @@ async function generateNpc(archetypeKey, speciesChoice, manager, folderId) {
// Add armor stat update (armor is a stat on the character)
await actor.update({ 'system.stats.armor.value': armorVal });
// Create embedded item documents (skills + weapons)
const itemsToCreate = [
...def.skills.map(s => makeSkillItemData(s)),
...def.weapons.map(w => makeWeaponItemData(w)),
];
// Resolve weapons
let npcWeapons = [...def.weapons];
if (archetypeKey === 'carrien' && breed) {
// Carriens get 1 to 2 random scrap weapons/attacks based on breed!
const allowed = breed.allowedWeapons;
const w1Name = pick(allowed);
const weapon1 = CARRIEN_WEAPONS.find(w => w.name === w1Name) || CARRIEN_WEAPONS[0];
let weapon2 = null;
if (Math.random() > 0.4 && allowed.length > 1) {
let w2Name = pick(allowed);
while (w2Name === w1Name) {
w2Name = pick(allowed);
}
weapon2 = CARRIEN_WEAPONS.find(w => w.name === w2Name);
}
npcWeapons = weapon2 ? [weapon1, weapon2] : [weapon1];
}
// Create embedded item documents (skills + weapons + breed traits!)
const validItemTypes = CONFIG.Item?.documentClass?.TYPES ?? [];
const hasType = (t) => validItemTypes.includes(t);
const itemsToCreate = [];
if (hasType('skill')) {
itemsToCreate.push(...carrienSkills.map(s => makeSkillItemData(s)));
}
if (hasType('weapon')) {
itemsToCreate.push(...npcWeapons.map(w => makeWeaponItemData(w)));
}
if (archetypeKey === 'carrien' && breed?.trait) {
// Zero-Engine specialty
if (hasType('specialty')) {
itemsToCreate.push({
name: breed.trait.name,
type: 'specialty',
img: 'icons/svg/star.svg',
system: {
effects: breed.trait.effects,
package: breed.trait.package,
category: 'general',
prerequisites: '',
healthMod: 0,
resolveMod: 0,
isActive: true
}
});
}
// S5S / Sla-Industries trait
if (hasType('trait')) {
itemsToCreate.push({
name: breed.trait.name,
type: 'trait',
img: 'icons/svg/statue.svg',
system: {
description: breed.trait.effects,
category: 'combat',
source: 'Carrien Breed'
}
});
}
// SLA-Mothership ability
if (hasType('ability')) {
itemsToCreate.push({
name: breed.trait.name,
type: 'ability',
img: 'icons/svg/bolt.svg',
system: {
text: breed.trait.effects,
roll: '',
sla: {
abilityType: 'special',
discipline: '',
fluxCost: 0,
tier: 'basic',
attack: false,
notes: [breed.trait.effects]
}
}
});
}
}
if (itemsToCreate.length > 0) {
await actor.createEmbeddedDocuments('Item', itemsToCreate);
@@ -720,6 +1183,10 @@ async function generateGangMembers(count, manager, folderId) {
// Pick a random weapon from the gang weapons list
const weapon = pick(def.gangWeapons);
// Generate background & dossier data
const background = `Street gang member. No past worth mentioning.`;
const dossier = buildDossierData('gang', 'human', handle, background);
const actorData = {
name: handle,
type: 'character',
@@ -727,17 +1194,17 @@ async function generateGangMembers(count, manager, folderId) {
folder: folderId ?? null,
system: {
health: { value: healthVal, min: 0, max: healthVal },
biography: `Street gang member. No past worth mentioning.`,
biography: background,
notes: `Archetype: Street Gang\nArmor: ${armorVal}\nCombat: ${combatVal}`,
stats: {
strength: { value: strengthVal, mod: 0 },
speed: { value: speedVal, mod: 0 },
intellect: { value: roll(15, 25), mod: 0 },
combat: { value: combatVal, mod: 0 },
sanity: { value: roll(12, 22), mod: 0 },
fear: { value: roll(12, 22), mod: 0 },
body: { value: bodyVal, mod: 0 },
armor: { value: armorVal, mod: 0, damageReduction: 0 },
strength: { value: strengthVal, mod: 0, enabled: true },
speed: { value: speedVal, mod: 0, enabled: true },
intellect: { value: roll(15, 25), mod: 0, enabled: true },
combat: { value: combatVal, mod: 0, enabled: true },
sanity: { value: roll(12, 22), mod: 0, enabled: true },
fear: { value: roll(12, 22), mod: 0, enabled: true },
body: { value: bodyVal, mod: 0, enabled: true },
armor: { value: armorVal, mod: armorVal, damageReduction: 0, enabled: true },
},
other: {
stress: { value: 2, min: 2, max: roll(2, 4) },
@@ -747,6 +1214,7 @@ async function generateGangMembers(count, manager, folderId) {
operativeType: { value: 'Gang Member' },
species: { value: 'Human' },
},
slaNpc: dossier
},
prototypeToken: {
name,
@@ -761,8 +1229,11 @@ async function generateGangMembers(count, manager, folderId) {
const actor = await Actor.create(actorData);
if (!actor) continue;
// Embed the weapon item
await actor.createEmbeddedDocuments('Item', [ makeWeaponItemData(weapon) ]);
// Embed the weapon item if valid for this system
const validItemTypes = CONFIG.Item?.documentClass?.TYPES ?? [];
if (validItemTypes.includes('weapon')) {
await actor.createEmbeddedDocuments('Item', [ makeWeaponItemData(weapon) ]);
}
created.push(actor);
}