302 lines
15 KiB
JavaScript
302 lines
15 KiB
JavaScript
export const SYSTEM_ID = "fvtt-machine-gods-noxian-expanse"
|
|
export const ASCII = `
|
|
Machine Gods of the Noxian Expanse
|
|
`
|
|
|
|
const dieChoiceLabels = values => Object.fromEntries(values.map(value => [value, value === "depleted" ? "Depleted" : value.toUpperCase()]))
|
|
const keyedNameChoices = (items, key) => Object.fromEntries(items.map(item => [item[key], item.name]))
|
|
const toIconSlug = name => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
|
|
const itemIcon = slug => `systems/${SYSTEM_ID}/assets/icons/${slug}.svg`
|
|
const withItemIcons = items => items.map(item => ({ ...item, img: itemIcon(toIconSlug(item.name)) }))
|
|
let localized = false
|
|
|
|
function localizeEntryLabels(entries, prefix) {
|
|
for (const [key, entry] of Object.entries(entries)) {
|
|
entry.label = game.i18n.localize(`${prefix}.${key}`)
|
|
}
|
|
}
|
|
|
|
function localizeChoiceLabels(entries, prefix) {
|
|
for (const key of Object.keys(entries)) {
|
|
entries[key] = game.i18n.localize(`${prefix}.${key}`)
|
|
}
|
|
}
|
|
|
|
export const SYSTEM = {
|
|
id: SYSTEM_ID,
|
|
actorTypes: {
|
|
character: { id: "character", label: "Character" },
|
|
creature: { id: "creature", label: "Creature" },
|
|
companion: { id: "companion", label: "Companion" },
|
|
party: { id: "party", label: "Party" },
|
|
},
|
|
itemTypes: {
|
|
weapon: { id: "weapon", label: "Weapon", icon: itemIcon("weapon") },
|
|
armor: { id: "armor", label: "Armor", icon: itemIcon("armor") },
|
|
shield: { id: "shield", label: "Shield", icon: itemIcon("shield") },
|
|
equipment: { id: "equipment", label: "Equipment", icon: itemIcon("equipment") },
|
|
"resonance-core": { id: "resonance-core", label: "Resonance Core", icon: itemIcon("resonance-core") },
|
|
artifact: { id: "artifact", label: "Artifact", icon: itemIcon("artifact") },
|
|
feature: { id: "feature", label: "Feature", icon: itemIcon("feature") },
|
|
"creature-trait": { id: "creature-trait", label: "Creature Trait", icon: itemIcon("creature-trait") },
|
|
},
|
|
abilities: {
|
|
agility: { id: "agility", label: "Agility" },
|
|
presence: { id: "presence", label: "Presence" },
|
|
strength: { id: "strength", label: "Strength" },
|
|
toughness: { id: "toughness", label: "Toughness" },
|
|
},
|
|
abilityOrder: ["agility", "presence", "strength", "toughness"],
|
|
conditions: {
|
|
bleeding: { id: "bleeding", label: "Bleeding", hasValue: true },
|
|
blinded: { id: "blinded", label: "Blinded" },
|
|
burning: { id: "burning", label: "Burning" },
|
|
fatigued: { id: "fatigued", label: "Fatigued", hasValue: true },
|
|
infected: { id: "infected", label: "Infected" },
|
|
poisoned: { id: "poisoned", label: "Poisoned" },
|
|
prone: { id: "prone", label: "Prone" },
|
|
restrained: { id: "restrained", label: "Restrained" },
|
|
starved: { id: "starved", label: "Starved" },
|
|
stunned: { id: "stunned", label: "Stunned", hasValue: true },
|
|
},
|
|
usageDice: ["d12", "d10", "d8", "d6", "d4", "depleted"],
|
|
usageDieChoices: dieChoiceLabels(["d12", "d10", "d8", "d6", "d4", "depleted"]),
|
|
armorDice: ["d12", "d10", "d8", "d6", "d4", "d2", "0"],
|
|
armorDieChoices: dieChoiceLabels(["d12", "d10", "d8", "d6", "d4", "d2", "0"]),
|
|
omenDice: ["d2", "d4", "d6", "d8"],
|
|
omenDieChoices: dieChoiceLabels(["d2", "d4", "d6", "d8"]),
|
|
weightCategories: {
|
|
trivial: "Trivial",
|
|
light: "Light",
|
|
normal: "Normal",
|
|
heavy: "Heavy",
|
|
},
|
|
weaponCategories: {
|
|
melee: "Melee",
|
|
ranged: "Ranged",
|
|
},
|
|
weaponProperties: {
|
|
ammo: { label: "Ammo", hint: "Requires ammunition. Improvised in melee or without ammo." },
|
|
awkward: { label: "Awkward", hint: "+1 DR to attacks." },
|
|
binding: { label: "Binding", hint: "Beat DR by 4+ on same-size or smaller target: inflict Restrained." },
|
|
durant: { label: "Durant", hint: "Durability die is D8." },
|
|
finesse: { label: "Finesse", hint: "Attacks can use either Presence or Strength." },
|
|
fling: { label: "Fling", hint: "Can make ranged attacks. Roll D4 Usage Die to avoid losing it." },
|
|
fragile: { label: "Fragile", hint: "Durability die is D4." },
|
|
glinting: { label: "Glinting", hint: "After attacking, next attack vs same target this combat gains -1 DR." },
|
|
overbearing:{ label: "Overbearing",hint: "+2 damage." },
|
|
parrying: { label: "Parrying", hint: "-1 DR to avoiding melee attacks while wielding." },
|
|
precise: { label: "Precise", hint: "-1 DR to hit." },
|
|
razored: { label: "Razored", hint: "Beat DR by 4+: inflict Bleeding (1)." },
|
|
ringing: { label: "Ringing", hint: "Beat DR by 4+ and target not Stunned: inflict Stunned (1)." },
|
|
"two-handed":{ label: "Two-Handed",hint: "Requires both hands." },
|
|
unwieldy: { label: "Unwieldy", hint: "Cannot attack more than once per Round." },
|
|
versatile: { label: "Versatile", hint: "Two-handed: -1 DR to hit and +1 damage." },
|
|
},
|
|
get weaponPropertyLabels() {
|
|
return Object.fromEntries(Object.entries(this.weaponProperties).map(([k, v]) => [k, v.label]))
|
|
},
|
|
get weaponPropertyHints() {
|
|
return Object.fromEntries(Object.entries(this.weaponProperties).map(([k, v]) => [k, v.hint]))
|
|
},
|
|
resonanceList: {
|
|
accelerate: "Accelerate",
|
|
blast: "Blast",
|
|
"breathe-water": "Breathe Water",
|
|
cauterize: "Cauterize",
|
|
"create-illusion": "Create Illusion",
|
|
distract: "Distract",
|
|
"eagle-eye": "Eagle Eye",
|
|
"empower-weapon": "Empower Weapon",
|
|
fireball: "Fireball",
|
|
hover: "Hover",
|
|
"influence-mind": "Influence Mind",
|
|
"knit-flesh": "Knit Flesh",
|
|
"light-construct": "Light Construct",
|
|
mirage: "Mirage",
|
|
"negate-injury": "Negate Injury",
|
|
paralyze: "Paralyze",
|
|
shield: "Shield",
|
|
shock: "Shock",
|
|
shroud: "Shroud",
|
|
"summon-mist": "Summon Mist",
|
|
},
|
|
starterWeapons: withItemIcons([
|
|
{ name: "Club", damage: "1d4", category: "melee", range: "Touch" },
|
|
{ name: "Dagger", damage: "1d4", category: "melee", range: "Touch" },
|
|
{ name: "Handaxe", damage: "1d6", category: "melee", range: "Touch" },
|
|
{ name: "Quarterstaff", damage: "1d4", category: "melee", range: "Touch" },
|
|
{ name: "Whip", damage: "1d4", category: "melee", range: "Near" },
|
|
{ name: "Shortbow", damage: "1d6", category: "ranged", range: "Near/Far" },
|
|
{ name: "Spear", damage: "1d6", category: "melee", range: "Near" },
|
|
{ name: "Longsword", damage: "1d8", category: "melee", range: "Touch" },
|
|
{ name: "Heavy Crossbow", damage: "1d10", category: "ranged", range: "Far" },
|
|
{ name: "Rapier", damage: "1d6", category: "melee", range: "Touch" },
|
|
{ name: "Halberd", damage: "1d10", category: "melee", range: "Near" },
|
|
{ name: "Maul", damage: "1d10", category: "melee", range: "Touch" },
|
|
]),
|
|
starterArmor: withItemIcons([
|
|
{ name: "Clothing (Average)", type: "armor", armorDie: "d2", penalty: 0 },
|
|
{ name: "Helm", type: "armor", armorDie: "d2", penalty: 0 },
|
|
{ name: "Medium Shield", type: "shield", armorDie: "d4", penalty: 0 },
|
|
{ name: "Gambeson", type: "armor", armorDie: "d4", penalty: 0 },
|
|
{ name: "Padded Leather", type: "armor", armorDie: "d4", penalty: 1 },
|
|
{ name: "Chain Shirt", type: "armor", armorDie: "d6", penalty: 1 },
|
|
{ name: "Half Plate", type: "armor", armorDie: "d8", penalty: 2 },
|
|
{ name: "Full Plate", type: "armor", armorDie: "d10", penalty: 2 },
|
|
]),
|
|
starterEquipment: withItemIcons([
|
|
{ name: "Rations", type: "equipment", subtype: "consumable", quantity: 1, usageDie: "d8", consumable: true },
|
|
{ name: "Waterskin", type: "equipment", subtype: "travel", quantity: 1, usageDie: "d8", consumable: true },
|
|
{ name: "Sack", type: "equipment", subtype: "container", quantity: 1, usageDie: "depleted", consumable: false },
|
|
{ name: "Backpack", type: "equipment", subtype: "container", quantity: 1, usageDie: "depleted", consumable: false },
|
|
{ name: "Rope", type: "equipment", subtype: "tool", quantity: 1, usageDie: "depleted", consumable: false },
|
|
{ name: "Medical Supplies", type: "equipment", subtype: "tool", quantity: 1, usageDie: "d6", consumable: true },
|
|
]),
|
|
equipmentSubtypes: {
|
|
gear: "Gear",
|
|
consumable: "Consumable",
|
|
travel: "Travel",
|
|
container: "Container",
|
|
tool: "Tool",
|
|
},
|
|
starterCores: withItemIcons([
|
|
{ name: "Accelerate Core", type: "resonance-core", resonationId: "accelerate", usageDie: "d6" },
|
|
{ name: "Blast Core", type: "resonance-core", resonationId: "blast", usageDie: "d6" },
|
|
{ name: "Cauterize Core", type: "resonance-core", resonationId: "cauterize", usageDie: "d6" },
|
|
{ name: "Empower Weapon Core", type: "resonance-core", resonationId: "empower-weapon", usageDie: "d6" },
|
|
]),
|
|
sampleArtifacts: withItemIcons([
|
|
{ name: "Shiver Lens", type: "artifact", artifactId: "shiver-lens", usageDie: "d6", description: "A cracked lens that reveals hidden heat signatures." },
|
|
{ name: "Processional Halo", type: "artifact", artifactId: "processional-halo", usageDie: "d6", description: "A floating crown of light that amplifies solemn commands." },
|
|
{ name: "Null Forge Spike", type: "artifact", artifactId: "null-forge-spike", usageDie: "d4", description: "A ritual spike used to jam malfunctioning relics into silence." },
|
|
{ name: "Lux Relay Idol", type: "artifact", artifactId: "lux-relay-idol", usageDie: "d6", description: "A palm-sized relay that stores and redirects ambient luminance." },
|
|
]),
|
|
artifactChoices: keyedNameChoices([
|
|
{ name: "Shiver Lens", artifactId: "shiver-lens" },
|
|
{ name: "Processional Halo", artifactId: "processional-halo" },
|
|
{ name: "Null Forge Spike", artifactId: "null-forge-spike" },
|
|
{ name: "Lux Relay Idol", artifactId: "lux-relay-idol" },
|
|
], "artifactId"),
|
|
sampleFeatures: withItemIcons([
|
|
{ name: "Akimbo Hit Priest", type: "feature", featureId: "akimbo-hit-priest", description: "Once per round after hitting with an attack, suffer Fatigued (1) to attack again." },
|
|
{ name: "Ambivalent Slouch", type: "feature", featureId: "ambivalent-slouch", description: "Gain +1 carrying capacity and reduce armor or shield Agility penalties by 1." },
|
|
{ name: "Avaricious Gubbingrifter", type: "feature", featureId: "avaricious-gubbingrifter", description: "Gain three random resonance cores and -1 DR to invoking resonations." },
|
|
{ name: "Backbiter", type: "feature", featureId: "backbiter", description: "When you win initiative, first-round attacks bypass 2d6 armor." },
|
|
{ name: "Chart Reading Maniac", type: "feature", featureId: "chart-reading-maniac", description: "Gain an extra exploration turn per day and reroll one exploration or ruin table per day." },
|
|
{ name: "Liturgicantal Blesswell", type: "feature", featureId: "liturgicantal-blesswell", description: "Restore d4 HP d4 times per full rest and repair one usage die by a step once per day." },
|
|
]),
|
|
featureChoices: keyedNameChoices([
|
|
{ name: "Akimbo Hit Priest", featureId: "akimbo-hit-priest" },
|
|
{ name: "Ambivalent Slouch", featureId: "ambivalent-slouch" },
|
|
{ name: "Avaricious Gubbingrifter", featureId: "avaricious-gubbingrifter" },
|
|
{ name: "Backbiter", featureId: "backbiter" },
|
|
{ name: "Chart Reading Maniac", featureId: "chart-reading-maniac" },
|
|
{ name: "Liturgicantal Blesswell", featureId: "liturgicantal-blesswell" },
|
|
], "featureId"),
|
|
companions: {
|
|
noble: {
|
|
name: "Beguiled Noble",
|
|
hp: 6,
|
|
morale: 9,
|
|
armorDie: "d4",
|
|
attackLabel: "Blade",
|
|
attackDamage: "1d6",
|
|
},
|
|
mercenary: {
|
|
name: "Dustland Mercenary",
|
|
hp: 8,
|
|
morale: 7,
|
|
armorDie: "d4",
|
|
attackLabel: "Cleaver",
|
|
attackDamage: "1d4+2",
|
|
},
|
|
pickpocket: {
|
|
name: "Scrapling Pickpocket",
|
|
hp: 3,
|
|
morale: 4,
|
|
armorDie: "0",
|
|
attackLabel: "Folding Knife",
|
|
attackDamage: "1d2",
|
|
},
|
|
cantor: {
|
|
name: "Silicon Cantor",
|
|
hp: 4,
|
|
morale: 6,
|
|
armorDie: "0",
|
|
attackLabel: "Book Thump",
|
|
attackDamage: "1",
|
|
},
|
|
},
|
|
tables: {
|
|
eucatastrophe: [
|
|
"Recover d4 HP.",
|
|
"Invoke the resonation again immediately.",
|
|
"Synchronize one additional artifact for the day.",
|
|
"Gain -1 DR to invoking resonations for five minutes.",
|
|
"Increase daily resonation uses by 2 for the day.",
|
|
"Gain +1 carrying capacity for the day.",
|
|
"Gain -4 DR to your next check within five minutes.",
|
|
"Gain +1 max HP for the day.",
|
|
"Recover one omen.",
|
|
"Borrow a random nearby feature or increase a random ability by 1 until end of day."
|
|
],
|
|
catastrophe: [
|
|
"You are reduced to 0 HP.",
|
|
"The resonation targets the wrong creature.",
|
|
"A random nearby artifact explodes.",
|
|
"The resonation is cast twice at random targets.",
|
|
"You become Restrained for five minutes.",
|
|
"You are knocked Prone and suffer Stunned (2).",
|
|
"Your worn armor is consumed.",
|
|
"Suffer Fatigued (1), doubling if repeated the same day.",
|
|
"Discard one omen.",
|
|
"All artifacts desynchronize and you cannot invoke resonations for the day."
|
|
],
|
|
triumphs: [
|
|
"Discover a useful resource.",
|
|
"Gain -3 DR to your next check within five minutes.",
|
|
"Discard a condition or recover d2 HP.",
|
|
"Grant a nearby ally -2 DR on their next check.",
|
|
"Ignore all penalties on your next check.",
|
|
"Discard Prone and Restrained, then move immediately.",
|
|
"Restore a weapon or armor usage die by one step.",
|
|
"Take an immediate action or find 3d10 coins.",
|
|
"Take an immediate action or gain -2 DR to your next check.",
|
|
"Move one point from one ability to another for one hour."
|
|
],
|
|
mishaps: [
|
|
"Downgrade involved equipment usage dice or suffer d4 damage bypassing armor.",
|
|
"A hostile random creature investigates.",
|
|
"Start an uncontrolled fire or suffer d4 damage bypassing armor.",
|
|
"Suffer Infected.",
|
|
"Suffer +1 DR on checks until you fully rest.",
|
|
"You cannot invoke resonations for d8 hours.",
|
|
"Suffer +3 DR on your next check this hour.",
|
|
"Suffer d6 damage bypassing armor, minimum 1 HP remaining.",
|
|
"Suffer Bleeding (1) in combat or d4 damage bypassing armor outside combat.",
|
|
"Roll twice on the mishaps table and take both results."
|
|
],
|
|
breakResults: {
|
|
1: "Unconscious for d4 rounds, then wake with d4 HP.",
|
|
2: "As above, and a random working limb becomes useless until treated.",
|
|
3: "As above, and without emergency treatment you die in d2 hours.",
|
|
4: "Immediate death."
|
|
}
|
|
}
|
|
}
|
|
|
|
export function localizeSystemConfig() {
|
|
if (localized) return
|
|
localized = true
|
|
|
|
localizeEntryLabels(SYSTEM.actorTypes, "MGNE.ActorTypes")
|
|
localizeEntryLabels(SYSTEM.itemTypes, "MGNE.ItemTypes")
|
|
localizeEntryLabels(SYSTEM.abilities, "MGNE.Abilities")
|
|
localizeEntryLabels(SYSTEM.conditions, "MGNE.Conditions")
|
|
localizeChoiceLabels(SYSTEM.weaponCategories, "MGNE.WeaponCategories")
|
|
localizeChoiceLabels(SYSTEM.resonanceList, "MGNE.Resonations")
|
|
localizeChoiceLabels(SYSTEM.equipmentSubtypes, "MGNE.EquipmentSubtypes")
|
|
SYSTEM.usageDieChoices.depleted = game.i18n.localize("MGNE.Common.Depleted")
|
|
}
|