diff --git a/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.pug b/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.pug index f80655c190..22a6483b9f 100644 --- a/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.pug +++ b/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.pug @@ -40,6 +40,19 @@ include pug/rolltemplate.pug | | script(type="text/worker") - include:uglify-es workers.js -| + | (function () { + | "use strict"; + include:uglify-es js/constants.js + include:uglify-es js/util.js + include:uglify-es js/calculations.js + include:uglify-es js/validations.js + include:uglify-es js/npc.js + include:uglify-es js/menuBuilders.js + include:uglify-es js/autofill.js + include:uglify-es js/translations.js + include:uglify-es js/ship.js + include:uglify-es js/drones.js + include:uglify-es js/migrations.js + include:uglify-es js/eventHandlers.js + | })(); | diff --git a/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.scss b/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.scss index cbb4c54f04..48e44dbf40 100644 --- a/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.scss +++ b/Stars_Without_Number_Revised/Source/Stars_Without_Number_Revised.scss @@ -93,7 +93,7 @@ button[type=roll].sheet-invisible-button { width: 100%; height: 100%; top: 0; - &:hover + div { + &:hover ~ div:not(.sheet-no-highlight) { color: $brightblue; border-color: $brightblue; } @@ -268,6 +268,7 @@ h3 { .sheet-col40 { width: 40% } .sheet-colthird { width: calc(100%/3) } .sheet-col25 { width: 25% } +.sheet-col20 { width: 20% } .sheet-colsixth { width: calc(100%/6) } /* containers */ @@ -293,7 +294,7 @@ input[type=text].sheet-inherit { color: inherit; font: inherit; } -input[type=number].sheet-inline-input { +.sheet-inline-input { position: relative; z-index: 2; vertical-align: baseline; @@ -633,6 +634,17 @@ input[type=number].sheet-inline-input { .sheet-slash { margin: 0 2px } } +/* Shells */ + +.sheet-shells { + .sheet-shells-grid { + grid: auto / 40px 35px 35px 35px 45px 1fr; + } +} + +.sheet-transhuman-setting:not([value="transhuman"]) ~ .sheet-column .sheet-transhuman-enabled { display: none } +.sheet-transhuman-setting[value="transhuman"] ~ .sheet-core .sheet-transhuman-disabled { display: none } + /* Weapons */ .sheet-weapons { .sheet-edit { @@ -782,6 +794,7 @@ input[type=number].sheet-inline-input { } } .sheet-input-details:not([value="1"]) ~ div .sheet-details { display: none } +.sheet-input-details:not([value="0"]) ~ div .sheet-notdetails { display: none } /* Supernatural section controls */ @@ -870,6 +883,25 @@ input[type=number].sheet-inline-input { &[value="adept"] ~ div .sheet-spellcaster { display: none } } +/* AI */ +.sheet-ai-processing { + display: grid; + grid: auto/ auto 45px 45px 45px 45px 80px; + align-items: center; + grid-column-gap: 4px; + h3 { line-height: 1 } +} + +.sheet-ai-processing-nodes { + .sheet-nodes-grid { + grid: auto / 65px 65px 1fr; + } +} + +.sheet-ai-setting:not([value="ai"]) ~ .sheet-ai-enabled { display: none } +.sheet-ai-setting[value="ai"] ~ .sheet-column .sheet-ai-enabled { display: block } +.sheet-ai-setting[value="ai"] ~ .sheet-core .sheet-ai-disabled { display: none } + /* Foci */ .sheet-foci-loc { &[value="left"] ~ .sheet-column .sheet-foci-right, diff --git a/Stars_Without_Number_Revised/Source/js/autofill.js b/Stars_Without_Number_Revised/Source/js/autofill.js new file mode 100644 index 0000000000..8385160717 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/autofill.js @@ -0,0 +1,202 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Autofill stuff */ +const fillClassStats = () => { + getAttrs(["class", "class_ability", "attack_bonus"], v => { + const label = v.class && reverseClasses[v.class.toLowerCase()]; + if (label && autofillData.classes.hasOwnProperty(label)) { + const data = Object.assign({}, autofillData.classes[label]); + Object.keys(data).forEach(key => { + if (!(["", "0"].includes(`${v[key]}`))) delete data[key]; + }); + mySetAttrs(data, v); + } + }); +}; +const getShipMultiplier = (shipClass) => { + if ((shipClass || "").toLowerCase() === "frigate") return 2; + else if ((shipClass || "").toLowerCase() === "cruiser") return 3; + else if ((shipClass || "").toLowerCase() === "capital") return 4; + else return 1; +}; +const getShipPriceMultiplier = (shipClass) => { + if ((shipClass || "").toLowerCase() === "frigate") return 10; + else if ((shipClass || "").toLowerCase() === "cruiser") return 25; + else if ((shipClass || "").toLowerCase() === "capital") return 100; + else return 1; +} +const getAutofillData = (sName, v, data, label) => { + // Transforms the stored data to be suitable for + // inclusion into the sheet. + const output = Object.assign({}, data); + if (sName === "ship-defenses") { + if (label) output.defense_name = translate(label.toUpperCase()); + if (output.defense_mass.includes("#")) { + output.defense_mass = `${parseInt(output.defense_mass) * v.ship_multiplier}`; + } + if (output.defense_price.includes("*")) { + output.defense_price = `${parseInt(output.defense_price) * v.ship_price_multiplier}`; + } + } + if (sName === "ship-fittings") { + if (label) { + output.fitting_name = translate(label.toUpperCase()); + output.fitting_effect = translate(`${label.toUpperCase()}_DESC`); + } + if (output.fitting_mass.includes("#")) { + output.fitting_mass = `${Math.round(parseFloat(output.fitting_mass) * v.ship_multiplier)}`; + } + if (output.fitting_power.includes("#")) { + output.fitting_power = `${parseInt(output.fitting_power) * v.ship_multiplier}`; + } + if (output.fitting_price.includes("*")) { + output.fitting_price = `${parseInt(output.fitting_price) * v.ship_price_multiplier}`; + } + } + if (sName === "ship-weapons") { + if (label) output.weapon_name = translate(label.toUpperCase()); + if (output.weapon_ammo) { + output.weapon_ammo_max = output.weapon_ammo; + output.weapon_use_ammo = "{{ammo=[[@{weapon_ammo} - 1]] / @{weapon_ammo_max}}}"; + } + } + if (sName === "weapons") { + if (label) { + output.weapon_name = translate(label.toUpperCase()); + output.weapon_description = translate(`${label.toUpperCase()}_DESC`); + } + if (output.weapon_ammo) { + output.weapon_ammo_max = output.weapon_ammo; + output.weapon_use_ammo = "{{ammo=[[0@{weapon_ammo} - (1 @{weapon_burst})]] / @{weapon_ammo|max}}}"; + } + if (output.weapon_shock_damage) { + output.weapon_shock = "{{shock=[[@{weapon_shock_damage} + @{weapon_attribute_mod}[Attribute] + @{weapon_skill_to_damage}[Skill]]] ^{SHOCK_DAMAGE_AGAINST_AC_LEQ} @{weapon_shock_ac}!}}"; + } + if (output.weapon_burst) { + output.weapon_burst = "@{burst_query}"; + } + if (!output.weapon_attribute_mod) { + output.weapon_attribute_mod = "@{dexterity_mod}"; + } + } + if (sName === "armor") { + if (label) { + output.armor_name = translate(label.toUpperCase()); + output.armor_description = translate(`${label.toUpperCase()}_DESC`) || ""; + } + } + if (sName === "cyberware") { + if (label) { + output.cyberware_name = translate(label.toUpperCase()); + output.cyberware_description = translate(`${label.toUpperCase()}_DESC`); + } + } + if (sName === "foci") { + if (label) { + output.focus_name = translate(label.toUpperCase()); + output.focus_level = "1"; + output.focus_description = translate(`${label.toUpperCase()}_DESC`); + } + } + if (sName === "techniques") { + if (label) { + output.technique_name = translate(label.toUpperCase()); + output.technique_description = translate(`${label.toUpperCase()}_DESC`); + } + } + if (sName === "gear") { + if (label) { + output.gear_name = translate(label.toUpperCase()); + output.gear_description = translate(`${label.toUpperCase()}_DESC`); + } + if (output.gear_encumbrance === "1#") { + output.gear_encumbrance = "1"; + output.gear_bundled = "on"; + } + } + return output; +}; +const getAutofillInfo = (sName, v, inputData, label) => { + const data = getAutofillData(sName, v, inputData, label); + const formatter = new Intl.NumberFormat(); + // Generates info text from the stored data + if (sName === "ship-defenses") { + return `${translate(data.class)}+. ${translate("POWER_INIT")}: ${data.defense_power}, ${ + translate("MASS_INIT")}: ${data.defense_mass}, ${translate("CREDITS")}: ${formatter.format(data.defense_price)}. ${data.defense_effect}`; + } + if (sName === "ship-fittings") { + return `${translate(data.class)}+. ${translate("POWER_INIT")}: ${data.fitting_power}, ${ + translate("MASS_INIT")}: ${data.fitting_mass}, ${translate("CREDITS")}: ${formatter.format(data.fitting_price)} ${data.fitting_effect}`; + } + if (sName === "ship-weapons") { + return `${translate(data.class)}+. ${ + translate("POWER_INIT")}: ${data.weapon_power}, ${ + translate("MASS_INIT")}: ${data.weapon_mass}, ${ + translate("HARDPOINTS_INIT")}: ${data.weapon_hardpoints}, ${ + translate("CREDITS")}: ${formatter.format(data.weapon_price)}. ${ + translate("DAMAGE_SHORT")} ${data.weapon_damage}. ${ + data.weapon_qualities}${ + (data.weapon_ammo ? `, ${translate("AMMO")}: ${data.weapon_ammo}`: "")}.`; + } + if (sName === "weapons") { + const getNamedAttrMod = expr => { + if (expr === "@{dexterity_mod}") return translate("DEXTERITY_SHORT"); + else if (expr === "@{strength_mod}") return translate("STRENGTH_SHORT"); + else if (expr === "@{str_dex_mod}") return translate("STR_DEX"); + }; + return `${translate("DAMAGE_SHORT")} ${data.weapon_damage}${data.weapon_burst ? ` (${translate("BURST")})` : ""}${ + data.weapon_ab ? `, ${translate("ATTACK_BONUS_SHORT")} +${data.weapon_ab}` : ""}${ + data.weapon_range ? `, ${translate("RANGE")} ${data.weapon_range}` : ""}${ + data.weapon_ammo ? `, ${translate("AMMO")} ${data.weapon_ammo}` : ""}${ + data.weapon_shock ? `, ${data.weapon_shock_damage} ${translate("SHOCK_DAMAGE_AGAINST_AC_LEQ")} ${data.weapon_shock_ac}` : ""}, +${getNamedAttrMod(data.weapon_attribute_mod)}${ + data.weapon_encumbrance ? `, ${translate("ENCUMBRANCE_SHORT")} ${data.weapon_encumbrance}` : ""}${ + data.weapon_price ? `, ${translate("CREDITS")}: ${formatter.format(data.weapon_price)}` : ""}.` + } + if (sName === "armor") { + return `${translate("AC")} ${data.armor_ac}, ${translate(data.armor_type) + }, ${translate("ENCUMBRANCE_SHORT")} ${data.armor_encumbrance}, ${translate("CREDITS")}: ${formatter.format(data.armor_price)}.`; + } + if (sName === "cyberware") { + return `${translate("STRAIN")}: ${data.cyberware_strain}, ${translate("CREDITS")}: ${formatter.format(data.cyberware_price)}.`; + } + if (sName === "techniques") { + if (data.level === "0") return `${translate("CORE_TECHNIQUE")}.`; + else return `${translate("LEVEL")}-${data.level}.`; + } + if (sName === "gear") { + return `${translate("ENCUMBRANCE_SHORT")} ${data.gear_encumbrance}${data.gear_bundled === "on" ? "#" : ""}${ + data.gear_price ? `, ${translate("CREDITS")}: ${formatter.format(data.gear_price)}` : "" + }.` + } + return ""; +}; +const generateAutofillRow = (sName) => { + // Event handler for generating a new row when button is pressed + getAttrs([`generate_${sName}_source`, "ship_class"], v => { + const label = v[`generate_${sName}_source`]; + v.ship_multiplier = getShipMultiplier(v.ship_class); + v.ship_price_multiplier = getShipPriceMultiplier(v.ship_class); + if (label && autofillData[sName].hasOwnProperty(label)) { + const data = getAutofillData(sName, v, autofillData[sName][label], label); + delete data.class; + delete data.level; + fillRepeatingSectionFromData(sName, data); + } + }); +}; +const generateAutofillInfo = (sName) => { + // Event handler for showing info about the selected item + getAttrs([`generate_${sName}_source`, "ship_class"], v => { + const label = v[`generate_${sName}_source`]; + v.ship_multiplier = getShipMultiplier(v.ship_class); + v.ship_price_multiplier = getShipPriceMultiplier(v.ship_class); + if (label && autofillData[sName].hasOwnProperty(label)) { + const info = getAutofillInfo(sName, v, autofillData[sName][label], label); + if (info) setAttrs({ + [`generate_${sName}_info`]: info + }); + } else setAttrs({ + [`generate_${sName}_info`]: " " + }); + }); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/calculations.js b/Stars_Without_Number_Revised/Source/js/calculations.js new file mode 100644 index 0000000000..6061fbe301 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/calculations.js @@ -0,0 +1,376 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Calculations */ +const calculateSaves = () => { + getAttrs([...attributes.map(attr => `${attr}_mod`), "level", + "homebrew_luck_save", "save_physical", "save_mental", "save_evasion", "save_luck" + ], v => { + const base = 16 - (parseInt(v.level) || 1); + const setting = { + save_physical: base - (Math.max(parseInt(v.strength_mod), parseInt(v.constitution_mod)) || 0), + save_mental: base - (Math.max(parseInt(v.charisma_mod), parseInt(v.wisdom_mod)) || 0), + save_evasion: base - (Math.max(parseInt(v.intelligence_mod), parseInt(v.dexterity_mod)) || 0) + }; + if (v.homebrew_luck_save === "1") setting.save_luck = base; + mySetAttrs(setting, v); + }); +}; + +const calculateEffort = () => { + getSectionIDs("repeating_psychic-skills", idArray => { + getAttrs([...effortAttributes, + "psionics_total_effort", + ...idArray.map(id => `repeating_psychic-skills_${id}_skill`)], v => { + const attrBonus = Math.max(parseInt(v.wisdom_mod), parseInt(v.constitution_mod)) || 0, + skillBonus = Math.max( + ...skills.psionic.map(x => parseInt(v[`skill_${x}`]) || 0), + ...idArray.map(id => parseInt(v[`repeating_psychic-skills_${id}_skill`]) || 0) + ); + const psionics_total_effort = Math.max(1 + attrBonus + skillBonus, 1) + parseInt(v.psionics_extra_effort) || 0 - + parseInt(v.psionics_committed_effort_current) || 0 - parseInt(v.psionics_committed_effort_scene) || 0 - + parseInt(v.psionics_committed_effort_day) || 0; + mySetAttrs({ + psionics_total_effort + }, v); + }); + }); +}; + +const calculateMagicEffort = () => { + getAttrs(["magic_total_effort", "magic_committed_effort_current", "magic_committed_effort_scene", "magic_committed_effort_day", "magic_uncommitted_effort"], function(v) { + const magic_uncommitted_effort = parseInt(v.magic_total_effort) - (parseInt(v.magic_committed_effort_current) + + parseInt(v.magic_committed_effort_scene) + (v.magic_committed_effort_day)) || 0; + mySetAttrs({ + magic_uncommitted_effort + }, v); + }); +}; + +const calculateProcessing = () => { + getSectionIDs("repeating_processing-nodes", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_processing-nodes_${id}_node_value`), + ...idArray.map(id => `repeating_processing-nodes_${id}_node_connected`), + "wisdom_mod", "intelligence_mod", "ai_extra_processing", "ai_committed_processing_current", "ai_committed_processing_scene", "ai_committed_processing_day", "ai_total_processing" + ]; + getAttrs(sourceAttrs, v => { + const maxProcessing = 1 + Math.max(parseInt(v.wisdom_mod) || 0, (parseInt(v.intelligence_mod)) || 0) + Math.max( + ...idArray.filter(id => v[`repeating_processing-nodes_${id}_node_connected`] === "1") + .map(id => parseInt(v[`repeating_processing-nodes_${id}_node_value`]) || 0), 0 + ); + const ai_total_processing = maxProcessing + parseInt(v.ai_extra_processing) - parseInt(v.ai_committed_processing_current) - + parseInt(v.ai_committed_processing_scene) - parseInt(v.ai_committed_processing_day) || 0; + mySetAttrs({ + ai_total_processing + }, v); + }); + }); +} + +const calculateAC = () => { + getSectionIDs("repeating_armor", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_armor_${id}_armor_ac`), + ...idArray.map(id => `repeating_armor_${id}_armor_active`), + ...idArray.map(id => `repeating_armor_${id}_armor_type`), + "npc", "AC", "innate_ac", "dexterity_mod", + ]; + getAttrs(sourceAttrs, v => { + if (v.npc === "1") return; + const baseAC = Math.max( + parseInt(v.innate_ac) || 0, + ...idArray.filter(id => v[`repeating_armor_${id}_armor_active`] === "1") + .filter(id => v[`repeating_armor_${id}_armor_type`] !== "SHIELD") + .map(id => parseInt(v[`repeating_armor_${id}_armor_ac`]) || 0) + ); + const shieldAC = Math.max( + 0, + ...idArray.filter(id => v[`repeating_armor_${id}_armor_active`] === "1") + .filter(id => v[`repeating_armor_${id}_armor_type`] === "SHIELD") + .map(id => parseInt(v[`repeating_armor_${id}_armor_ac`]) || 0) + ); + const AC = (shieldAC > 0 ? shieldAC <= baseAC ? (baseAC + 1) : shieldAC : baseAC) + + (parseInt(v.dexterity_mod) || 0); + + mySetAttrs({ + AC + }, v); + }); + }); +}; + +const calculateMaxStrain = () => { + getAttrs(["constitution", "strain_max"], v => { + mySetAttrs({ + strain_max: parseInt(v.constitution) || 0 + }, v); + }); +}; + +const calculatePermanentStrain = () => { + getAttrs(["cyberware_strain_total", "strain_permanent_extra", "strain_permanent"], v => { + const permStrain = parseInt(v.cyberware_strain_total) + parseInt(v.strain_permanent_extra) || 0; + mySetAttrs({ + strain_permanent: permStrain + }, v); + }); +}; + +const calculateCyberwareStrain = () => { + //Loop through the cyberware and add up the strain. + getSectionIDs("repeating_cyberware", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_cyberware_${id}_cyberware_strain`), + "cyberware_strain_total" + ]; + getAttrs(sourceAttrs, v => { + const cyberwareStrain = idArray.reduce((m, id) => { + m += parseInt(v[`repeating_cyberware_${id}_cyberware_strain`]) || 0; + return m + }, 0) + mySetAttrs({ + cyberware_strain_total: cyberwareStrain + }, v); + }); + }); +}; + +const calculateAttr = (attr) => { + getAttrs([attr, `${attr}_base`, `${attr}_boosts`], v => { + const setting = { + [`${attr}`]: `${(parseInt(v[`${attr}_base`]) || 10) + (parseInt(v[`${attr}_boosts`]) || 0)}` + }; + mySetAttrs(setting, v, null, () => { + calculateMod(attr); + }); + }); +} + +const calculateMod = (attr) => { + getAttrs([attr, `${attr}_bonus`, `${attr}_mod`], v => { + const mod = (value => { + if (value >= 18) return 2; + else if (value >= 14) return 1; + else if (value >= 8) return 0; + else if (value >= 4) return -1; + else return -2; + })(parseInt(v[attr]) || 10); + + const setting = { + [`${attr}_mod`]: `${mod + (parseInt(v[`${attr}_bonus`]) || 0)}` + }; + + mySetAttrs(setting, v, null, () => { + calculateSaves(); + generateWeaponDisplay(); + calculateStrDexMod(); + if (attr === "dexterity") calculateAC(); + }); + }); +}; +const calculateStrDexMod = () => { + getAttrs(["str_dex_mod", "strength_mod", "dexterity_mod"], v => { + const str_dex_mod = Math.max(parseInt(v.strength_mod) || 10, parseInt(v.dexterity_mod) || 10); + mySetAttrs({ + str_dex_mod + }, v); + }); +}; + +const calculateShellAttrs = () => { + const physicalAttrs = ["strength", "dexterity", "constitution"]; + getSectionIDs("repeating_shells", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_shells_${id}_shell_active`), + ...idArray.map(id => `repeating_shells_${id}_shell_strength`), + ...idArray.map(id => `repeating_shells_${id}_shell_dexterity`), + ...idArray.map(id => `repeating_shells_${id}_shell_constitution`), + ...physicalAttrs, "setting_transhuman_enable", "setting_ai_enable", + + ]; + getAttrs(sourceAttrs, v => { + if (v.setting_transhuman_enable === "transhuman" || v.setting_ai_enable === "ai"){ + let attributes = {}; + physicalAttrs.forEach(attr => attributes[attr] = idArray + .filter(id => v[`repeating_shells_${id}_shell_active`] === "1") + .map(id => v[`repeating_shells_${id}_shell_${attr}`])[0] || "None") + mySetAttrs(attributes, v, null, () => { + physicalAttrs.filter(attr => attributes[attr] !== "None").forEach(attr => calculateMod(attr)); + }); + } else { + physicalAttrs.forEach(attr => calculateAttr(attr)); + } + }) + }) +} + +const calculateNextLevelXP = () => { + const xp = [0, 3, 6, 12, 18, 27, 39, 54, 72, 93]; + getAttrs(["level", "setting_xp_scheme"], v => { + if (v.setting_xp_scheme === "xp") { + if (v.level < 10) { + setAttrs({ + xp_next: xp[v.level] + }); + } else { + setAttrs({ + xp_next: 93 + ((v.level - 9) * 24) + }); + } + } else if (v.setting_xp_scheme === "money") { + setAttrs({ + xp_next: 2500 * (2 * v.level) + }); + } + }); +}; + +const calculateGearReadiedStowed = () => { + const doCalc = (gearIDs, weaponIDs, armorIDs) => { + const attrs = [ + ...gearIDs.map(id => `repeating_gear_${id}_gear_amount`), + ...gearIDs.map(id => `repeating_gear_${id}_gear_encumbrance`), + ...gearIDs.map(id => `repeating_gear_${id}_gear_status`), + ...gearIDs.map(id => `repeating_gear_${id}_gear_bundled`), + ...armorIDs.map(id => `repeating_armor_${id}_armor_encumbrance`), + ...armorIDs.map(id => `repeating_armor_${id}_armor_encumbrance_bonus`), + ...armorIDs.map(id => `repeating_armor_${id}_armor_status`), + ...weaponIDs.map(id => `repeating_weapons_${id}_weapon_encumbrance`), + ...weaponIDs.map(id => `repeating_weapons_${id}_weapon_status`), + "gear_readied", "gear_stowed", "strength", "gear_readied_max", + "gear_readied_over", "gear_stowed_over", "gear_stowed_max" + ]; + getAttrs(attrs, v => { + const [gear_readied, gear_stowed] = armorIDs.reduce((m, id) => { + if (v[`repeating_armor_${id}_armor_status`] === "READIED") { + m[0] += parseInt(v[`repeating_armor_${id}_armor_encumbrance`]) || 0; + } else if (v[`repeating_armor_${id}_armor_status`] === "STOWED") + m[1] += parseInt(v[`repeating_armor_${id}_armor_encumbrance`]) || 0; + return m; + }, weaponIDs.reduce((m, id) => { + if (v[`repeating_weapons_${id}_weapon_status`] === "READIED") + m[0] += parseInt(v[`repeating_weapons_${id}_weapon_encumbrance`]) || 0; + else if (v[`repeating_weapons_${id}_weapon_status`] === "STOWED") + m[1] += parseInt(v[`repeating_weapons_${id}_weapon_encumbrance`]) || 0; + return m; + }, gearIDs.reduce((m, id) => { + const amount = parseInt(v[`repeating_gear_${id}_gear_amount`]) || 0; + let packingFactor = 1; + if (v[`repeating_gear_${id}_gear_bundled`] === "on") { + packingFactor = 3; + } + if (v[`repeating_gear_${id}_gear_status`] === "READIED") + m[0] += Math.ceil((amount * parseFloat(v[`repeating_gear_${id}_gear_encumbrance`]))/packingFactor) || 0; + else if (v[`repeating_gear_${id}_gear_status`] === "STOWED") + m[1] += Math.ceil((amount * parseFloat(v[`repeating_gear_${id}_gear_encumbrance`]))/packingFactor) || 0; + return m; + }, [0, 0]))); + + const armor_encumbrance_bonus = Math.max(0, + ...armorIDs.filter(id => v[`repeating_armor_${id}_armor_status`] === "READIED") + .map(id => parseInt(v[`repeating_armor_${id}_armor_encumbrance_bonus`]) || 0) + ); + const gear_stowed_max = parseInt(v.strength) + armor_encumbrance_bonus || 0; + const gear_readied_max = Math.floor(gear_stowed_max / 2); + const setting = { + gear_readied, + gear_stowed, + gear_readied_max, + gear_stowed_max, + gear_readied_over: (parseInt(v.gear_readied) > gear_readied_max) ? "1" : "0", + gear_stowed_over: (parseInt(v.gear_stowed) > gear_stowed_max) ? "1" : "0", + }; + + mySetAttrs(setting, v, { + silent: true + }); + }); + }; + + getSectionIDs("repeating_gear", gearIDs => { + getSectionIDs("repeating_weapons", weaponIDs => { + getSectionIDs("repeating_armor", armorIDs => doCalc(gearIDs, weaponIDs, armorIDs)); + }); + }); +}; + +const generateWeaponDisplay = () => { + // Generates the weapon menu and sets the display of weapons + // in display mode in one go. + getSectionIDs("repeating_weapons", idArray => { + const prefixes = idArray.map(id => `repeating_weapons_${id}`); + const sourceAttrs = [ + ...prefixes.map(prefix => `${prefix}_weapon_attack`), + ...prefixes.map(prefix => `${prefix}_weapon_name`), + ...prefixes.map(prefix => `${prefix}_weapon_skill_bonus`), + ...prefixes.map(prefix => `${prefix}_weapon_attribute_mod`), + ...prefixes.map(prefix => `${prefix}_weapon_damage`), + ...prefixes.map(prefix => `${prefix}_weapon_shock`), + ...prefixes.map(prefix => `${prefix}_weapon_shock_damage`), + ...prefixes.map(prefix => `${prefix}_weapon_shock_ac`), + ...prefixes.map(prefix => `${prefix}_weapon_skill_to_damage`), + ...prefixes.map(prefix => `${prefix}_weapon_attack_display`), + ...prefixes.map(prefix => `${prefix}_weapon_damage_display`), + ...attributes.map(attr => `${attr}_mod`), + ...weaponSkills, + "attack_bonus", + "str_dex_mod", + "macro_weapons" + ]; + getAttrs(sourceAttrs, v => { + const setting = {}; + const baseAttackBonus = parseInt(v.attack_bonus) || 0; + prefixes.forEach(prefix => { + const attrBonus = parseInt(v[(v[`${prefix}_weapon_attribute_mod`] || "").slice(2, -1)]) || 0; + const skillBonus = parseInt(v[(v[`${prefix}_weapon_skill_bonus`] || "").slice(2, -1)]) || + parseInt(v[`${prefix}_weapon_skill_bonus`]) || 0; + const damageBonus = attrBonus + + ((v[`${prefix}_weapon_skill_to_damage`] === "@{weapon_skill_bonus}") ? skillBonus : 0); + const weaponDamage = (v[`${prefix}_weapon_damage`] === "0") ? "" : v[`${prefix}_weapon_damage`]; + const shockString = (v[`${prefix}_weapon_shock`] !== "0") ? `, ${ + (parseInt(v[`${prefix}_weapon_shock_damage`])||0) + damageBonus + }\xa0${translate("SHOCK").toLowerCase()}${ + v[`${prefix}_weapon_shock_ac`] ? ` ${translate("VS_AC_LEQ")} ${v[`${prefix}_weapon_shock_ac`]}` : "" + }` : ""; + + const attack = baseAttackBonus + (parseInt(v[`${prefix}_weapon_attack`]) || 0) + + ((skillBonus === -1) ? -2 : skillBonus) + attrBonus; + const damage = weaponDamage + (weaponDamage ? + ((damageBonus === 0) ? "" : ((damageBonus > 0) ? ` + ${damageBonus}` : ` - ${-damageBonus}`)) : + damageBonus); + + setting[`${prefix}_weapon_attack_display`] = (attack >= 0) ? `+${attack}` : attack; + setting[`${prefix}_weapon_damage_display`] = `${damage || 0}\xa0${translate("DAMAGE").toLowerCase()}${shockString}`; + }); + setting.macro_weapons = prefixes.map((prefix, index) => { + const label = `${v[`${prefix}_weapon_name`]} (${setting[`${prefix}_weapon_attack_display`]})`; + return buildLink(label, `${prefix}_attack`, index === prefixes.length - 1); + }).join(" "); + mySetAttrs(setting, v, { + silent: true + }); + }); + }); +}; + +const handleAmmoAPI = (sName) => { + const formula = (sName === "weapons") ? "[[-1 - @{weapon_burst}]]" : "-1"; + getSectionIDs(`repeating_${sName}`, idArray => { + getAttrs([ + "setting_use_ammo", + ...idArray.map(id => `repeating_${sName}_${id}_weapon_use_ammo`), + ...idArray.map(id => `repeating_${sName}_${id}_weapon_api`) + ], v => { + const setting = idArray.reduce((m, id) => { + m[`repeating_${sName}_${id}_weapon_api`] = + (v.setting_use_ammo === "1" && v[`repeating_${sName}_${id}_weapon_use_ammo`] !== "0") ? + `\n!modattr --mute --charid @{character_id} --repeating_${sName}_${id}_weapon_ammo|${formula}` : + ""; + return m; + }, {}); + mySetAttrs(setting, v, { + silent: true + }); + }); + }); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/constants.js b/Stars_Without_Number_Revised/Source/js/constants.js new file mode 100644 index 0000000000..9489f91d44 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/constants.js @@ -0,0 +1,2036 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Data constants */ +const sheetName = "Stars Without Number (revised)"; +const sheetVersion = "2.4.10"; +const translate = getTranslationByKey; +const attributes = ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]; +const effortAttributes = ["wisdom_mod", "constitution_mod", "psionics_extra_effort", + "skill_biopsionics", "skill_precognition", "skill_telepathy", "skill_teleportation", + "skill_telekinesis", "skill_metapsionics", "psionics_committed_effort_current", + "psionics_committed_effort_scene", "psionics_committed_effort_day" +]; +const shipStatEvent = [ + ...["hardpoints", "power", "mass"].map(x => `change:repeating_ship-weapons:weapon_${x}`), + ...["power", "mass"].map(x => `change:repeating_ship-defenses:defense_${x}`), + ...["power", "mass"].map(x => `change:repeating_ship-fittings:fitting_${x}`), + "change:ship_power", "change:ship_mass", "change:ship_hardpoints", + "remove:repeating_ship-weapons", "remove:repeating_ship-defenses", + "remove:repeating_ship-fittings" +].join(" "); +const weaponSkills = [ + "skill_exert", "skill_punch", "skill_shoot", "skill_stab", "skill_combat_energy", + "skill_combat_gunnery", "skill_combat_primitive", "skill_combat_projectile", + "skill_combat_psitech", "skill_combat_unarmed", "skill_telekinesis" +]; +const weaponDisplayEvent = [ + ...["attack", "name", "skill_bonus", "attribute_mod", "damage", "shock", "shock_damage", + "shock_ac", "skill_to_damage" + ].map(x => `change:repeating_weapons:weapon_${x}`), + "remove:repeating_weapons", "change:attack_bonus", + ...weaponSkills.map(name => `change:${name}`), +].join(" "); +const skills = { + revised: ["administer", "connect", "exert", "fix", "heal", "know", "lead", + "notice", "perform", "pilot", "program", "punch", "shoot", "sneak", "stab", + "survive", "talk", "trade", "work" + ], + first: ["artist", "athletics", "bureaucracy", "business", "combat_energy", "combat_gunnery", + "combat_primitive", "combat_projectile", "combat_psitech", "combat_unarmed", "computer", + "culture_alien", "culture_criminal", "culture_spacer", "culture_traveller", "culture_one", + "culture_two", "culture_three", "exosuit", "gambling", "history", "instructor", "language", + "leadership", "navigation", "perception", "persuade", "profession", "religion", "science", + "security", "stealth", "steward", "survival", "tactics", "tech_astronautic", "tech_maltech", + "tech_medical", "tech_postech", "tech_pretech", "tech_psitech", "vehicle_air", "vehicle_grav", + "vehicle_land", "vehicle_space", "vehicle_water" + ], + psionic: ["biopsionics", "metapsionics", "precognition", "telekinesis", "telepathy", "teleportation"], +}; +const shipStats = ["ship_ac", "ship_armor", "ship_class", "ship_crew_max", "ship_crew_min", + "ship_hardpoints_max", "ship_hp", "ship_hp_max", "ship_mass_max", "ship_power_max", "ship_speed" +]; +const reverseHullTypes = { + [translate("BATTLESHIP").toLowerCase()]: "battleship", + [translate("BULK_FREIGHTER").toLowerCase()]: "bulk_freighter", + [translate("CARRIER").toLowerCase()]: "carrier", + [translate("CORVETTE").toLowerCase()]: "corvette", + [translate("FLEET_CRUISER").toLowerCase()]: "fleet_cruiser", + [translate("FREE_MERCHANT").toLowerCase()]: "free_merchant", + [translate("HEAVY_FRIGATE").toLowerCase()]: "heavy_frigate", + [translate("LARGE_STATION").toLowerCase()]: "large_station", + [translate("PATROL_BOAT").toLowerCase()]: "patrol_boat", + [translate("SMALL_STATION").toLowerCase()]: "small station", + [translate("STRIKE_FIGHTER").toLowerCase()]: "strike_fighter", + [translate("SHUTTLE").toLowerCase()]: "shuttle", +}; +const reverseClasses = { + [translate("ADVENTURER").toLowerCase()]: "adventurer", + [translate("EXPERT").toLowerCase()]: "expert", + [translate("PSYCHIC").toLowerCase()]: "psychic", + [translate("WARRIOR").toLowerCase()]: "warrior", +}; +const autofillSections = ["armor", "cyberware", "foci", "gear", "ship-defenses", "ship-fittings", "ship-weapons", "techniques", "weapons"]; +const autofillData = { + "classes": { + adventurer: { + class_ability: translate("ADVENTURER_CLASS_ABILITY"), + }, + expert: { + class_ability: translate("EXPERT_CLASS_ABILITY"), + }, + psychic: { + class_ability: translate("PSYCHIC_CLASS_ABILITY"), + }, + warrior: { + attack_bonus: "1", + class_ability: translate("WARRIOR_CLASS_ABILITY"), + }, + }, + "drones": { + alecto: { + drone_AC: "18", + drone_enc: "4", + drone_HP: "30", + drone_num_fittings: "4", + drone_range: "5000 km", + drone_price: "50000", + }, + cuttlefish: { + drone_AC: "13", + drone_enc: "2", + drone_HP: "10", + drone_num_fittings: "5", + drone_range: "1 km", + drone_price: "2000", + }, + ghostwalker: { + drone_AC: "15", + drone_enc: "3", + drone_fitting_1_desc: translate("SENSOR_TRANSPARENCY_DESC"), + drone_fitting_1_name: translate("SENSOR_TRANSPARENCY"), + drone_HP: "1", + drone_num_fittings: "3", + drone_range: "5 km", + drone_price: "3000", + }, + pax: { + drone_AC: "16", + drone_enc: "4", + drone_HP: "20", + drone_num_fittings: "4", + drone_range: "100 km", + drone_price: "10000", + }, + primitve_drone: { + drone_AC: "12", + drone_enc: "2", + drone_HP: "1", + drone_num_fittings: "1", + drone_range: "500 m", + drone_price: "250", + }, + sleeper: { + drone_AC: "12", + drone_enc: "2", + drone_fitting_1_desc: translate("STATIONKEEPING_DESC"), + drone_fitting_1_name: translate("STATIONKEEPING"), + drone_HP: "8", + drone_num_fittings: "5", + drone_range: "100 km", + drone_price: "2500", + }, + stalker: { + drone_AC: "13", + drone_enc: "2", + drone_HP: "5", + drone_num_fittings: "3", + drone_range: "2 km", + drone_price: "1000", + }, + void_hawk: { + drone_AC: "14", + drone_enc: "6", + drone_HP: "15", + drone_num_fittings: "4", + drone_range: "100 km", + drone_price: "5000", + }, + }, + "droneFittings": [ + "ammo_unit", + "bomber", + "environmental_power", + "expert_system", + "extended_flight", + "grav_muffles", + "heavy_lift", + "holoskin", + "medical_support", + "observation_suite", + "racing_gravitics", + "reinforced_structure", + "sensor_transparency", + "stationkeeping", + "suicide_charge", + "weapon_fitting" + ], + "hulltypes": { + battleship: { + ship_ac: "16", + ship_armor: "20", + ship_class: translate("CAPITAL"), + ship_crew_max: "1000", + ship_crew_min: "200", + ship_hardpoints_max: "15", + ship_hp: "100", + ship_mass_max: "50", + ship_power_max: "75", + ship_speed: "0", + ship_hull_price: "50000000", + }, + bulk_freighter: { + ship_ac: "11", + ship_armor: "0", + ship_class: translate("CRUISER"), + ship_crew_max: "40", + ship_crew_min: "10", + ship_hardpoints_max: "2", + ship_hp: "40", + ship_mass_max: "25", + ship_power_max: "15", + ship_speed: "0", + ship_hull_price: "5000000", + }, + carrier: { + ship_ac: "14", + ship_armor: "10", + ship_class: translate("CAPITAL"), + ship_crew_max: "1500", + ship_crew_min: "300", + ship_hardpoints_max: "4", + ship_hp: "75", + ship_mass_max: "100", + ship_power_max: "50", + ship_speed: "0", + ship_hull_price: "60000000", + }, + corvette: { + ship_ac: "13", + ship_armor: "10", + ship_class: translate("FRIGATE"), + ship_crew_max: "40", + ship_crew_min: "10", + ship_hardpoints_max: "6", + ship_hp: "40", + ship_mass_max: "15", + ship_power_max: "15", + ship_speed: "2", + ship_hull_price: "4000000", + }, + fleet_cruiser: { + ship_ac: "14", + ship_armor: "15", + ship_class: translate("CRUISER"), + ship_crew_max: "200", + ship_crew_min: "50", + ship_hardpoints_max: "10", + ship_hp: "60", + ship_mass_max: "30", + ship_power_max: "50", + ship_speed: "1", + ship_hull_price: "10000000", + }, + free_merchant: { + ship_ac: "14", + ship_armor: "2", + ship_class: translate("FRIGATE"), + ship_crew_max: "6", + ship_crew_min: "1", + ship_hardpoints_max: "2", + ship_hp: "20", + ship_mass_max: "15", + ship_power_max: "10", + ship_speed: "3", + ship_hull_price: "500000", + }, + heavy_frigate: { + ship_ac: "15", + ship_armor: "10", + ship_class: translate("FRIGATE"), + ship_crew_max: "120", + ship_crew_min: "30", + ship_hardpoints_max: "8", + ship_hp: "50", + ship_mass_max: "20", + ship_power_max: "25", + ship_speed: "1", + ship_hull_price: "7000000", + }, + large_station: { + ship_ac: "17", + ship_armor: "20", + ship_class: translate("CAPITAL"), + ship_crew_max: "1000", + ship_crew_min: "100", + ship_hardpoints_max: "30", + ship_hp: "120", + ship_mass_max: "75", + ship_power_max: "125", + ship_speed: "", + ship_hull_price: "40000000", + }, + patrol_boat: { + ship_ac: "14", + ship_armor: "5", + ship_class: translate("FRIGATE"), + ship_crew_max: "20", + ship_crew_min: "5", + ship_hardpoints_max: "4", + ship_hp: "25", + ship_mass_max: "10", + ship_power_max: "15", + ship_speed: "4", + ship_hull_price: "2500000", + }, + small_station: { + ship_ac: "11", + ship_armor: "5", + ship_class: translate("CRUISER"), + ship_crew_max: "200", + ship_crew_min: "20", + ship_hardpoints_max: "10", + ship_hp: "120", + ship_mass_max: "40", + ship_power_max: "50", + ship_speed: "", + ship_hull_price: "5000000", + }, + strike_fighter: { + ship_ac: "16", + ship_armor: "5", + ship_class: translate("FIGHTER"), + ship_crew_max: "1", + ship_crew_min: "1", + ship_hardpoints_max: "1", + ship_hp: "8", + ship_mass_max: "2", + ship_power_max: "5", + ship_speed: "5", + ship_hull_price: "200000", + }, + shuttle: { + ship_ac: "11", + ship_armor: "0", + ship_class: translate("FIGHTER"), + ship_crew_max: "10", + ship_crew_min: "1", + ship_hardpoints_max: "1", + ship_hp: "15", + ship_mass_max: "5", + ship_power_max: "3", + ship_speed: "3", + ship_hull_price: "200000", + }, + }, + // HD AC AB Damage #attacks move morale skills saves armor_type + // TODO: Clean this up into a maintainable format + "statblocks": { + barbarian_hero: ["6", "16", "8", "Weapon+3", "1", "10m", "11", "3", "12", "PRIMITIVE"], + barbarian_tribal: ["1", "12", "2", "Weapon", "1", "10m", "8", "1", "15", "PRIMITIVE"], + civilian_security_bot: ["1", "15", "1", "1d8[Stun]", "1", "10m", "12", "1", "15"], + companion_bot: ["1", "12", "0", "1d2[Unarmed]", "1", "10m", "6", "1", "15"], + elite_fighter: ["3", "16", "4", "Weapon+1", "1", "10m", "10", "2", "14", "COMBAT"], + gang_boss: ["3", "14", "4", "Weapon+1", "1", "10m", "9", "2", "15"], + gang_member: ["1", "12", "1", "Weapon", "1", "10m", "7", "1", "15"], + geneengineered_killer: ["4", "16", "5", "Weapon+1", "1", "15m", "10", "2", "13"], + geneengineered_murder_beast: ["10", "18", "10", "1d10", "4", "20m", "12", "3", "10"], + greater_lone_predator: ["5", "16", "6", "1d10", "2", "10m", "9", "2", "12"], + heavy_warbot: ["6", "18", "8", "2d8[Plasma]", "2", "15m", "10", "2", "12"], + heroic_fighter: ["6", "16", "8", "Weapon+3", "1", "10m", "11", "3", "12", "COMBAT"], + industrial_work_bot: ["2", "15", "0", "1d10[Crush]", "1", "5", "8", "1", "14"], + janitor_bot: ["1", "14", "0", "Unarmed", "1", "5m", "8", "1", "15"], + large_aggressive_prey_animal: ["5", "13", "4", "1d10", "1", "15m", "8", "1", "12"], + large_pack_hunter: ["2", "14", "2", "1d6", "1", "15m", "9", "1", "14"], + legendary_fighter: ["10", "20", "12", "Weapon+4", "2", "10m", "12", "5", "10", "POWERED"], + lesser_lone_predator: ["3", "14", "4", "1d8", "2", "15m", "8", "2", "14"], + martial_human: ["1", "10", "1", "Weapon", "1", "10m", "8", "1", "15"], + military_elite: ["3", "16", "4", "Weapon+1", "1", "10m", "10", "2", "14", "COMBAT"], + military_soldier: ["1", "16", "1", "Weapon", "1", "10m", "9", "1", "15", "COMBAT"], + normal_human: ["1", "10", "0", "Unarmed", "1", "10m", "6", "1", "15"], + peaceful_human: ["1", "10", "0", "Unarmed", "1", "10m", "6", "1", "15"], + pirate_king: ["7", "18", "9", "Weapon+2", "1", "10m", "11", "3", "12", "POWERED"], + police_officer: ["1", "14", "1", "Weapon", "1", "10m", "8", "1", "15"], + repair_bot: ["1", "14", "0", "1d6[Tool]", "1", "10m", "8", "1", "15"], + serial_killer: ["6", "12", "8", "Weapon+3", "1", "10m", "12", "3", "12"], + skilled_professional: ["1", "10", "0", "Weapon", "1", "10m", "6", "2", "15"], + small_pack_hunter: ["1", "13", "1", "1d4", "1", "15m", "8", "1", "15"], + small_vicious_beast: ["1hp", "14", "1", "1d2", "1", "10m", "7", "1", "15"], + soldier_bot: ["2", "16", "1", "Weapon", "1", "10m", "10", "1", "14"], + terrifying_apex_predator: ["8", "16", "8", "1d10", "2", "20m", "9", "2", "11"], + veteran_fighter: ["2", "14", "2", "Weapon+1", "1", "10m", "9", "1", "14"], + warrior_tyrant: ["8", "20", "10", "Weapon+3", "1", "10m", "11", "3", "11", "POWERED"] + }, + "armor": { + armored_undersuit: { + armor_ac: "13", + armor_encumbrance: "0", + armor_type: "STREET", + armor_price: "600", + }, + armored_vacc_suit: { + armor_ac: "13", + armor_encumbrance: "2", + armor_type: "STREET", + armor_price: "400", + }, + assault_suit: { + armor_ac: "18", + armor_encumbrance: "2", + armor_type: "POWERED", + armor_price: "10000", + }, + combat_field_uniform: { + armor_ac: "16", + armor_encumbrance: "1", + armor_type: "COMBAT", + armor_price: "1000", + }, + cuirass_brigandine_linothorax_half_plate: { + armor_ac: "15", + armor_encumbrance: "1", + armor_type: "PRIMITIVE", + armor_price: "50", + }, + deflector_array: { + armor_ac: "18", + armor_encumbrance: "0", + armor_type: "STREET", + armor_price: "30000", + }, + executive_security_suit: { + armor_ac: "15", + armor_encumbrance: "0", + armor_type: "ARTIFACT", + }, + field_emitter_panoply: { + armor_ac: "20", + armor_encumbrance: "1", + armor_encumbrance_bonus: "4", + armor_type: "POWERED", + armor_price: "40000", + }, + force_pavis: { + armor_ac: "15", + armor_ac_bonus: "1", + armor_encumbrance: "1", + armor_type: "SHIELD", + armor_price: "10000", + }, + full_plate_layered_mail: { + armor_ac: "17", + armor_encumbrance: "2", + armor_type: "PRIMITIVE", + armor_price: "100", + }, + ghost_mantle: { + armor_ac: "15", + armor_encumbrance: "0", + armor_type: "ARTIFACT", + }, + icarus_harness: { + armor_ac: "16", + armor_encumbrance: "1", + armor_type: "COMBAT", + armor_price: "8000", + }, + leather_jacks_thick_hides_quilted_armor: { + armor_ac: "13", + armor_encumbrance: "1", + armor_type: "PRIMITIVE", + armor_price: "10", + }, + polyplast_carapace: { + armor_ac: "18", + armor_encumbrance: "1", + armor_type: "ARTIFACT", + }, + psitech_combat_suit: { + armor_ac: "16", + armor_encumbrance: "0", + armor_type: "ARTIFACT", + }, + secure_clothing: { + armor_ac: "13", + armor_encumbrance: "1", + armor_type: "STREET", + armor_price: "300", + }, + security_armor: { + armor_ac: "14", + armor_encumbrance: "1", + armor_type: "COMBAT", + armor_price: "700", + }, + shield: { + armor_ac: "13", + armor_ac_bonus: "1", + armor_encumbrance: "1", + armor_type: "SHIELD", + armor_price: "10", + }, + storm_armor: { + armor_ac: "19", + armor_encumbrance: "2", + armor_encumbrance_bonus: "4", + armor_type: "POWERED", + armor_price: "20000", + }, + stutterjump_suit: { + armor_ac: "18", + armor_encumbrance: "0", + armor_type: "ARTIFACT", + }, + tempest_assault_array: { + armor_ac: "20", + armor_encumbrance: "1", + armor_encumbrance_bonus: "4", + armor_type: "ARTIFACT", + }, + titan_powered_armor: { + armor_ac: "21", + armor_encumbrance: "2", + armor_encumbrance_bonus: "4", + armor_type: "ARTIFACT", + }, + vestimentum: { + armor_ac: "18", + armor_encumbrance: "0", + armor_type: "POWERED", + armor_price: "15000", + }, + warpaint: { + armor_ac: "12", + armor_encumbrance: "0", + armor_type: "STREET", + armor_price: "300", + }, + woven_body_armor: { + armor_ac: "15", + armor_encumbrance: "2", + armor_type: "COMBAT", + armor_price: "400", + } + }, + "cyberware": { + adrenal_suppression_pump: { + cyberware_strain: "1", + cyberware_price: "30000" + }, + bioadaptation_augments: { + cyberware_strain: "1", + cyberware_price: "10000" + }, + body_arsenal_array: { + cyberware_strain: "1", + cyberware_price: "10000" + }, + body_sculpting: { + cyberware_strain: "1", + cyberware_price: "10000" + }, + dermal_armor: { + cyberware_strain: "2", + cyberware_price: "20000" + }, + drone_control_link: { + cyberware_strain: "1", + cyberware_price: "20000" + }, + eelskin_capacitor_mesh: { + cyberware_strain: "1", + cyberware_price: "25000" + }, + gecko_anchors: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + ghost_talker_transceiver: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + holdout_cavity: { + cyberware_strain: "1", + cyberware_price: "10000" + }, + holoskin_emitter: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + identity_submersion_trigger: { + cyberware_strain: "1", + cyberware_price: "25000" + }, + immunofiltration_systems: { + cyberware_strain: "2", + cyberware_price: "25000" + }, + induced_coma_trigger: { + cyberware_strain: "1", + cyberware_price: "20000" + }, + neurointruder_alert: { + cyberware_strain: "1", + cyberware_price: "50000" + }, + panspectral_optics: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + pressure_sheathing: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + prosthetic_limb: { + cyberware_strain: "1", + cyberware_price: "2500" + }, + revenant_wiring: { + cyberware_strain: "3", + cyberware_price: "50000" + }, + slowtime_window: { + cyberware_strain: "2", + cyberware_price: "30000" + }, + stabilization_overrides: { + cyberware_strain: "2", + cyberware_price: "25000" + }, + tagger_nanites: { + cyberware_strain: "1", + cyberware_price: "15000" + }, + toxin_injector: { + cyberware_strain: "2", + cyberware_price: "20000" + }, + twitchlock_actuators: { + cyberware_strain: "2", + cyberware_price: "30000" + }, + }, + "foci": { + alert: {}, + armsman: {}, + assassin: {}, + authority: {}, + close_combatant: {}, + connected: {}, + die_hard: {}, + diplomat: {}, + gunslinger: {}, + hacker: {}, + healer: {}, + henchkeeper: {}, + ironhide: {}, + psychic_training: {}, + savage_fray: {}, + shocking_assault: {}, + sniper: {}, + specialist: {}, + star_captain: {}, + starfarer: {}, + tinker: {}, + unarmed_combatant: {}, + unique_gift: {}, + wanderer: {}, + wild_psychic_talent: {}, + }, + "gear": { + ai_companion: { + gear_encumbrance: "0", + }, + ammo_20_rounds: { + gear_encumbrance: "1#", + gear_price: "10", + }, + ammo_missile: { + gear_encumbrance: "1", + gear_price: "50", + }, + atmofilter: { + gear_encumbrance: "1", + gear_price: "100", + }, + backpack: { + gear_encumbrance: "1", + gear_price: "5/50", + }, + bezoar: { + gear_encumbrance: "0", + gear_price: "200", + }, + binoculars: { + gear_encumbrance: "1", + gear_price: "20/200", + }, + bioscanner: { + gear_encumbrance: "1", + gear_price: "300", + }, + black_slab: { + gear_encumbrance: "1", + gear_price: "10000", + }, + brainwave: { + gear_encumbrance: "0", + gear_price: "1000", + }, + climbing_harness: { + gear_encumbrance: "1", + gear_price: "50", + }, + comm_server: { + gear_encumbrance: "3", + gear_price: "1000", + }, + compad: { + gear_encumbrance: "0", + gear_price: "100", + }, + data_phase_tab: { + gear_encumbrance: "1", + gear_price: "5000", + }, + data_protocol: { + gear_encumbrance: "0", + gear_price: "1000", + }, + dataslab: { + gear_encumbrance: "1", + gear_price: "300", + }, + field_radio: { + gear_encumbrance: "1", + gear_price: "200", + }, + glowbug: { + gear_encumbrance: "0", + gear_price: "5", + }, + grapnel_launcher: { + gear_encumbrance: "1", + gear_price: "200", + }, + grav_chute: { + gear_encumbrance: "1", + gear_price: "300/1000", + }, + grav_harness: { + gear_encumbrance: "3", + gear_price: "5000", + }, + hush: { + gear_encumbrance: "0", + gear_price: "200", + }, + instapanel: { + gear_encumbrance: "1#", + gear_price: "50", + }, + integrity_stims: { + gear_encumbrance: "0", + }, + juggernaut_stims: { + gear_encumbrance: "0", + }, + lazarus_patch: { + gear_encumbrance: "1#", + gear_price: "30", + }, + lift: { + gear_encumbrance: "0", + gear_price: "50", + }, + line_shunt: { + gear_encumbrance: "0", + gear_price: "100", + }, + low_light_goggles: { + gear_encumbrance: "1", + gear_price: "200", + }, + medkit: { + gear_encumbrance: "2", + gear_price: "100", + }, + metatool: { + gear_encumbrance: "1", + gear_price: "200", + }, + micropurgator_stims: { + gear_encumbrance: "0", + }, + mindwall_helmet: { + gear_encumbrance: "0", + }, + navcomp: { + gear_encumbrance: "1", + gear_price: "500", + }, + polymorphic_nanites: { + gear_encumbrance: "1", + }, + portabox: { + gear_encumbrance: "1", + gear_price: "50", + }, + pseudonuke: { + gear_encumbrance: "1", + }, + power_cell_type_a: { + gear_encumbrance: "1#", + gear_price: "10", + }, + power_cell_type_b: { + gear_encumbrance: "1", + gear_price: "100", + }, + pressure_tent: { + gear_encumbrance: "4", + gear_price: "100", + }, + pretech_cosmetic: { + gear_encumbrance: "0", + gear_price: "1000", + }, + psych: { + gear_encumbrance: "0", + gear_price: "25", + }, + rations_1_day: { + gear_encumbrance: "1#", + gear_price: "5", + }, + redivivus_sheath: { + gear_encumbrance: "1", + }, + remote_link_unit: { + gear_encumbrance: "1", + gear_price: "250", + }, + reverie: { + gear_encumbrance: "0", + gear_price: "100", + }, + rope_20_meters: { + gear_encumbrance: "2/1", + gear_price: "4/40", + }, + scout_report: { + gear_encumbrance: "0", + gear_price: "200", + }, + solar_recharger: { + gear_encumbrance: "3", + gear_price: "500", + }, + spare_parts: { + gear_encumbrance: "1#", + gear_price: "50", + }, + squeal: { + gear_encumbrance: "0", + gear_price: "300", + }, + stiletto_charge: { + gear_encumbrance: "1", + gear_price: "Priceless", + }, + storage_unit: { + gear_encumbrance: "3", + gear_price: "500", + }, + survey_scanner: { + gear_encumbrance: "1", + gear_price: "250", + }, + survival_kit: { + gear_encumbrance: "1", + gear_price: "60", + }, + system_reset_inductor: { + gear_encumbrance: "0", + }, + tailored_antiallergens: { + gear_encumbrance: "0", + gear_price: "5", + }, + telekinetic_generator: { + gear_encumbrance: "2", + gear_price: "250", + }, + telescoping_pole: { + gear_encumbrance: "0", + gear_price: "10", + }, + thermal_flare: { + gear_encumbrance: "0", + gear_price: "5", + }, + tightbeam_link_unit: { + gear_encumbrance: "1", + gear_price: "1000", + }, + toolkit_postech: { + gear_encumbrance: "3", + gear_price: "300", + }, + toolkit_pretech: { + gear_encumbrance: "1", + gear_price: "1000", + }, + trade_goods: { + gear_encumbrance: "1#", + gear_price: "50", + }, + trade_metals: { + gear_encumbrance: "1#", + gear_price: "10", + }, + translator_torc: { + gear_encumbrance: "0", + gear_price: "200", + }, + tsunami: { + gear_encumbrance: "0", + gear_price: "50", + }, + vacc_fresher: { + gear_encumbrance: "1", + gear_price: "400", + }, + vacc_skin: { + gear_encumbrance: "1", + gear_price: "1000", + }, + vacc_suit: { + gear_encumbrance: "2", + gear_price: "100", + }, + wideawake_serum: { + gear_encumbrance: "0", + }, + }, + "ship-defenses": { + ablative_hull_compartments: { + class: "CAPITAL", + defense_effect: `+1 ${translate("AC")}, +20 ${translate("MAXIMUM_HIT_POINTS").toLowerCase()}.`, + defense_mass: "2#", + defense_power: "5", + defense_price: "100000*", + }, + augmented_plating: { + class: "FIGHTER", + defense_effect: `+2 ${translate("AC")}, -1 ${translate("SPEED")}.`, + defense_mass: "1#", + defense_power: "0", + defense_price: "25000*", + }, + boarding_countermeasures: { + class: "FRIGATE", + defense_effect: translate("BOARDING_COUNTERMEASURES_DESC"), + defense_mass: "1#", + defense_power: "2", + defense_price: "25000*", + }, + burst_ecm_generator: { + class: "FRIGATE", + defense_effect: translate("BURST_ECM_GENERATOR_DESC"), + defense_mass: "1#", + defense_power: "2", + defense_price: "25000*", + }, + foxer_drones: { + class: "CRUISER", + defense_effect: translate("FOXER_DRONES_DESC"), + defense_mass: "1#", + defense_power: "2", + defense_price: "10000*", + }, + grav_eddy_displacer: { + class: "FRIGATE", + defense_effect: translate("GRAV_EDDY_DISPLACER_DESC"), + defense_mass: "2#", + defense_power: "5", + defense_price: "50000*", + }, + hardened_polyceramic_overlay: { + class: "FIGHTER", + defense_effect: translate("HARDENED_POLYCERAMIC_OVERLAY_DESC"), + defense_mass: "1#", + defense_power: "0", + defense_price: "25000*", + }, + planetary_defense_array: { + class: "FRIGATE", + defense_effect: translate("PLANETARY_DEFENSE_ARRAY_DESC"), + defense_mass: "2#", + defense_power: "4", + defense_price: "50000*", + }, + point_defense_lasers: { + class: "FRIGATE", + defense_effect: translate("POINT_DEFENSE_LASERS_DESC"), + defense_mass: "2#", + defense_power: "3", + defense_price: "10000*", + }, + }, + "ship-fittings": { + advanced_lab: { + class: "FRIGATE", + fitting_mass: "2", + fitting_power: "1#", + fitting_price: "10000*", + }, + advanced_nav_computer: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "1#", + fitting_price: "10000*", + }, + amphibious_operation: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "1", + fitting_price: "25000*", + }, + armory: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "0", + fitting_price: "10000*", + }, + atmospheric_configuration: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "0", + fitting_price: "5000*", + }, + auto_targeting_system: { + class: "FIGHTER", + fitting_mass: "0", + fitting_power: "1", + fitting_price: "50000", + }, + automation_support: { + class: "FIGHTER", + fitting_mass: "1", + fitting_power: "2", + fitting_price: "10000*", + }, + boarding_tubes: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "0", + fitting_price: "5000*", + }, + cargo_lighter: { + class: "FRIGATE", + fitting_mass: "2", + fitting_power: "0", + fitting_price: "25000", + }, + cargo_space: { + class: "FIGHTER", + fitting_mass: "1", + fitting_power: "0", + fitting_price: "", + }, + cold_sleep_pods: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "1", + fitting_price: "5000*", + }, + colony_core: { + class: "FRIGATE", + fitting_mass: "2#", + fitting_power: "4", + fitting_price: "100000*", + }, + drill_course_regulator: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "1#", + fitting_price: "25000*", + }, + spike_drive_2: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "1#", + fitting_price: "10000*", + }, + spike_drive_3: { + class: "FIGHTER", + fitting_mass: "2#", + fitting_power: "2#", + fitting_price: "20000*", + }, + spike_drive_4: { + class: "FRIGATE", + fitting_mass: "3#", + fitting_power: "2#", + fitting_price: "40000*", + }, + spike_drive_5: { + class: "FRIGATE", + fitting_mass: "3#", + fitting_power: "3#", + fitting_price: "100000*", + }, + spike_drive_6: { + class: "CRUISER", + fitting_mass: "4#", + fitting_power: "3#", + fitting_price: "500000*", + }, + drop_pod: { + class: "FRIGATE", + fitting_mass: "2", + fitting_power: "0", + fitting_price: "300000", + }, + emissions_dampers: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "1#", + fitting_price: "25000*", + }, + exodus_bay: { + class: "CRUISER", + fitting_mass: "2#", + fitting_power: "1#", + fitting_price: "50000*", + }, + extended_life_support: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "1#", + fitting_price: "5000*", + }, + extended_medbay: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "1", + fitting_price: "5000*", + }, + extended_store: { + class: "FIGHTER", + fitting_mass: "1#", + fitting_power: "0", + fitting_price: "2500*", + }, + fuel_bunkers: { + class: "FIGHTER", + fitting_mass: "1", + fitting_power: "0", + fitting_price: "2500*", + }, + fuel_scoops: { + class: "FRIGATE", + fitting_mass: "1#", + fitting_power: "2", + fitting_price: "5000*", + }, + hydroponic_production: { + class: "CRUISER", + fitting_mass: "2#", + fitting_power: "1#", + fitting_price: "10000*", + }, + lifeboats: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "0", + fitting_price: "2500*", + }, + luxury_cabins: { + class: "FRIGATE", + fitting_mass: "1#", + fitting_power: "1", + fitting_price: "10000*", + }, + mobile_extractor: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "2", + fitting_price: "50000", + }, + mobile_factory: { + class: "CRUISER", + fitting_mass: "2#", + fitting_power: "3", + fitting_price: "50000*", + }, + precognitive_nav_chamber: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "1", + fitting_price: "100000*", + }, + psionic_anchorpoint: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "3", + fitting_price: "Special", + }, + sensor_mask: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "1#", + fitting_price: "10000*", + }, + ship_bay_fighter: { + class: "CRUISER", + fitting_mass: "2", + fitting_power: "0", + fitting_price: "200000", + }, + ship_bay_frigate: { + class: "CAPITAL", + fitting_mass: "4", + fitting_power: "1", + fitting_price: "1000000", + }, + ships_locker: { + class: "FRIGATE", + fitting_mass: "0", + fitting_power: "0", + fitting_price: "2000*", + }, + shiptender_mount: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "1", + fitting_price: "25000*", + }, + smugglers_hold: { + class: "FIGHTER", + fitting_mass: "1", + fitting_power: "0", + fitting_price: "2500*", + }, + survey_sensor_array: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "2", + fitting_price: "5000*", + }, + system_drive: { + class: "FIGHTER", + fitting_mass: "-2#", + fitting_power: "-1#", + fitting_price: "Special", + }, + teleportation_pads: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "1", + fitting_price: "Special", + }, + tractor_beams: { + class: "FRIGATE", + fitting_mass: "1", + fitting_power: "2", + fitting_price: "10000*", + }, + vehicle_transport_fittings: { + class: "FRIGATE", + fitting_mass: "1#", + fitting_power: "0", + fitting_price: "2500*", + }, + workshop: { + class: "FRIGATE", + fitting_mass: "0.5#", + fitting_power: "1", + fitting_price: "500*", + }, + }, + "ship-weapons": { + charged_particle_caster: { + class: "FRIGATE", + weapon_damage: "3d6", + weapon_hardpoints: "2", + weapon_mass: "1", + weapon_power: "10", + weapon_price: "800000", + weapon_qualities: `${translate("AP")} 15, ${translate("CLUMSY")}`, + }, + flak_emitter_battery: { + class: "FRIGATE", + weapon_damage: "2d6", + weapon_hardpoints: "1", + weapon_mass: "3", + weapon_power: "5", + weapon_price: "500000", + weapon_qualities: `${translate("AP")} 10, ${translate("FLAK")}`, + }, + fractal_impact_charge: { + weapon_ammo: "4", + weapon_ammo_price: "500", + class: "FIGHTER", + weapon_damage: "2d6", + weapon_hardpoints: "1", + weapon_mass: "1", + weapon_power: "5", + weapon_price: "200000", + weapon_qualities: `${translate("AP")} 15`, + }, + gravcannon: { + class: "CRUISER", + weapon_damage: "4d6", + weapon_hardpoints: "3", + weapon_mass: "4", + weapon_power: "15", + weapon_price: "2000000", + weapon_qualities: `${translate("AP")} 20`, + }, + lightning_charge_mantle: { + class: "CAPITAL", + weapon_damage: "1d20", + weapon_hardpoints: "2", + weapon_mass: "5", + weapon_power: "15", + weapon_price: "4000000", + weapon_qualities: `${translate("AP")} 5, ${translate("CLOUD")}`, + }, + mag_spike_array: { + weapon_ammo: "5", + weapon_ammo_price: "5000", + class: "FRIGATE", + weapon_damage: "2d6+2", + weapon_hardpoints: "2", + weapon_mass: "2", + weapon_power: "5", + weapon_price: "1000000", + weapon_qualities: `${translate("AP")} 10, ${translate("FLAK")}`, + }, + mass_cannon: { + weapon_ammo: "4", + weapon_ammo_price: "50000", + class: "CAPITAL", + weapon_damage: "2d20", + weapon_hardpoints: "4", + weapon_mass: "5", + weapon_power: "10", + weapon_price: "5000000", + weapon_qualities: `${translate("AP")} 20`, + }, + multifocal_laser: { + class: "FIGHTER", + weapon_damage: "1d4", + weapon_hardpoints: "1", + weapon_mass: "1", + weapon_power: "5", + weapon_price: "100000", + weapon_qualities: `${translate("AP")} 20`, + }, + nuclear_missiles: { + weapon_ammo: "5", + weapon_ammo_price: "5000", + class: "FRIGATE", + weapon_damage: "", + weapon_hardpoints: "2", + weapon_mass: "1", + weapon_power: "5", + weapon_price: "50000", + weapon_qualities: "", + }, + plasma_beam: { + class: "FRIGATE", + weapon_damage: "3d6", + weapon_hardpoints: "2", + weapon_mass: "2", + weapon_power: "5", + weapon_price: "700000", + weapon_qualities: `${translate("AP")} 10`, + }, + polyspectral_mes_beam: { + class: "FIGHTER", + weapon_damage: "2d4", + weapon_hardpoints: "1", + weapon_mass: "1", + weapon_power: "5", + weapon_price: "2000000", + weapon_qualities: `${translate("AP")} 25`, + }, + reaper_battery: { + class: "FIGHTER", + weapon_damage: "3d4", + weapon_hardpoints: "1", + weapon_mass: "1", + weapon_power: "4", + weapon_price: "100000", + weapon_qualities: translate("CLUMSY"), + }, + sandthrower: { + class: "FIGHTER", + weapon_damage: "2d4", + weapon_hardpoints: "1", + weapon_mass: "1", + weapon_power: "3", + weapon_price: "50000", + weapon_qualities: translate("FLAK"), + }, + singularity_gun: { + class: "CAPITAL", + weapon_damage: "5d20", + weapon_hardpoints: "5", + weapon_mass: "10", + weapon_power: "25", + weapon_price: "20000000", + weapon_qualities: `${translate("AP")} 25`, + }, + smart_cloud: { + class: "CRUISER", + weapon_damage: "3d10", + weapon_hardpoints: "2", + weapon_mass: "5", + weapon_power: "10", + weapon_price: "2000000", + weapon_qualities: `${translate("CLOUD")}, ${translate("CLUMSY")}`, + }, + spike_inversion_projector: { + class: "CRUISER", + weapon_damage: "3d8", + weapon_hardpoints: "3", + weapon_mass: "3", + weapon_power: "10", + weapon_price: "2500000", + weapon_qualities: `${translate("AP")} 15`, + }, + spinal_beam_cannon: { + class: "CRUISER", + weapon_damage: "3d10", + weapon_hardpoints: "3", + weapon_mass: "5", + weapon_power: "10", + weapon_price: "1500000", + weapon_qualities: `${translate("AP")} 15, ${translate("CLUMSY")}`, + }, + torpedo_launcher: { + weapon_ammo: "4", + weapon_ammo_price: "2500", + class: "FRIGATE", + weapon_damage: "3d8", + weapon_hardpoints: "1", + weapon_mass: "3", + weapon_power: "10", + weapon_price: "500000", + weapon_qualities: `${translate("AP")} 20`, + }, + vortex_tunnel_inductor: { + class: "CAPITAL", + weapon_damage: "3d20", + weapon_hardpoints: "4", + weapon_mass: "10", + weapon_power: "20", + weapon_price: "5000000", + weapon_qualities: `${translate("AP")} 20, ${translate("CLUMSY")}`, + }, + }, + "techniques": { + accelerated_succor: { + level: "3", + technique_discipline: translate("BIOPSIONICS"), + }, + alternate_outcome: { + level: "2", + technique_discipline: translate("PRECOGNITION"), + }, + anguished_vision: { + level: "3", + technique_discipline: translate("PRECOGNITION"), + }, + burdened_apportation: { + level: "2", + technique_discipline: translate("TELEPORTATION"), + }, + cloak_powers: { + level: "1", + technique_discipline: translate("METAPSIONICS"), + }, + concert_of_minds: { + level: "3", + technique_discipline: translate("METAPSIONICS"), + }, + cursed_luck: { + level: "3", + technique_discipline: translate("PRECOGNITION"), + }, + deep_intrusion: { + level: "4", + technique_discipline: translate("TELEPORTATION"), + }, + destinys_shield: { + level: "2", + technique_discipline: translate("PRECOGNITION"), + }, + effortless_apportation: { + level: "3", + technique_discipline: translate("TELEPORTATION"), + }, + facile_mind: { + level: "1", + technique_discipline: translate("TELEPATHY"), + }, + far_thought: { + level: "2", + technique_discipline: translate("TELEPATHY"), + }, + flawless_mastery: { + level: "4", + technique_discipline: translate("METAPSIONICS"), + }, + force_puppetry: { + level: "4", + technique_discipline: translate("TELEKINESIS"), + }, + forced_outcome: { + level: "3", + technique_discipline: translate("PRECOGNITION"), + }, + holistic_optimization_patterning: { + level: "4", + technique_discipline: translate("BIOPSIONICS"), + }, + impact_sump: { + level: "2", + technique_discipline: translate("TELEKINESIS"), + }, + impervious_pavis_of_will: { + level: "4", + technique_discipline: translate("METAPSIONICS"), + }, + intuitive_response: { + level: "1", + technique_discipline: translate("PRECOGNITION"), + }, + invincible_stand: { + level: "2", + technique_discipline: translate("BIOPSIONICS"), + }, + kinetic_transversal: { + level: "1", + technique_discipline: translate("TELEKINESIS"), + }, + major_organ_restoration: { + level: "2", + technique_discipline: translate("BIOPSIONICS"), + }, + mastered_succor: { + level: "1", + technique_discipline: translate("BIOPSIONICS"), + }, + memory_editing: { + level: "4", + technique_discipline: translate("TELEPATHY"), + }, + metadimensional_friction: { + level: "3", + technique_discipline: translate("METAPSIONICS"), + }, + metamorph: { + level: "3", + technique_discipline: translate("BIOPSIONICS"), + }, + mindtracing: { + level: "1", + technique_discipline: translate("METAPSIONICS"), + }, + neural_trap: { + level: "2", + technique_discipline: translate("METAPSIONICS"), + }, + not_my_time: { + level: "4", + technique_discipline: translate("PRECOGNITION"), + }, + offensive_apportation: { + level: "4", + technique_discipline: translate("TELEPORTATION"), + }, + oracle: { + level: "0", + technique_discipline: translate("PRECOGNITION"), + }, + organic_purification_protocols: { + level: "1", + technique_discipline: translate("BIOPSIONICS"), + }, + perceptive_dislocation: { + level: "2", + technique_discipline: translate("TELEPORTATION"), + }, + personal_apportation: { + level: "0", + technique_discipline: translate("TELEPORTATION"), + }, + pressure_field: { + level: "1", + technique_discipline: translate("TELEKINESIS"), + }, + proficient_apportation: { + level: "1", + technique_discipline: translate("TELEPORTATION"), + }, + prophecy: { + level: "4", + technique_discipline: translate("PRECOGNITION"), + }, + psychic_refinement: { + level: "0", + technique_discipline: translate("METAPSIONICS"), + }, + psychic_static: { + level: "2", + technique_discipline: translate("METAPSIONICS"), + }, + psychic_succor: { + level: "0", + technique_discipline: translate("BIOPSIONICS"), + }, + psychic_tutelage: { + level: "3", + technique_discipline: translate("METAPSIONICS"), + }, + quintessential_reconstruction: { + level: "4", + technique_discipline: translate("BIOPSIONICS"), + }, + reactive_telekinesis: { + level: "3", + technique_discipline: translate("TELEKINESIS"), + }, + reflex_response: { + level: "3", + technique_discipline: translate("TELEPATHY"), + }, + remote_repair: { + level: "1", + technique_discipline: translate("BIOPSIONICS"), + }, + rift_reduplication: { + level: "3", + technique_discipline: translate("TELEPORTATION"), + }, + sense_the_need: { + level: "1", + technique_discipline: translate("PRECOGNITION"), + }, + slip_field: { + level: "2", + technique_discipline: translate("TELEKINESIS"), + }, + spatial_awareness: { + level: "1", + technique_discipline: translate("TELEPORTATION"), + }, + spatial_synchrony_mandala: { + level: "2", + technique_discipline: translate("TELEPORTATION"), + }, + stutterjump: { + level: "3", + technique_discipline: translate("TELEPORTATION"), + }, + suppress_cognition: { + level: "2", + technique_discipline: translate("TELEPATHY"), + }, + surge_momentum: { + level: "3", + technique_discipline: translate("METAPSIONICS"), + }, + suspended_manifestation: { + level: "2", + technique_discipline: translate("METAPSIONICS"), + }, + synthetic_adaptation: { + level: "1", + technique_discipline: translate("METAPSIONICS"), + }, + tangible_force_construct: { + level: "3", + technique_discipline: translate("TELEKINESIS"), + }, + telekinetic_armory: { + level: "1", + technique_discipline: translate("TELEKINESIS"), + }, + telekinetic_expertise: { + level: "2", + technique_discipline: translate("TELEKINESIS"), + }, + telekinetic_flight: { + level: "4", + technique_discipline: translate("TELEKINESIS"), + }, + telekinetic_manipulation: { + level: "0", + technique_discipline: translate("TELEKINESIS"), + }, + telekinetic_ram: { + level: "3", + technique_discipline: translate("TELEKINESIS"), + }, + telepathic_assault: { + level: "3", + technique_discipline: translate("TELEPATHY"), + }, + telepathic_contact: { + level: "0", + technique_discipline: translate("TELEPATHY"), + }, + teratic_overload: { + level: "3", + technique_discipline: translate("BIOPSIONICS"), + }, + terminal_reflection: { + level: "1", + technique_discipline: translate("PRECOGNITION"), + }, + thermokinesis: { + level: "2", + technique_discipline: translate("TELEKINESIS"), + }, + tissue_integrity_field: { + level: "2", + technique_discipline: translate("BIOPSIONICS"), + }, + transmit_thought: { + level: "1", + technique_discipline: translate("TELEPATHY"), + }, + unity_of_thought: { + level: "4", + technique_discipline: translate("TELEPATHY"), + }, + }, + "weapons": { + advanced_bow: { + weapon_ammo: "1", + weapon_damage: "1d6", + weapon_encumbrance: "2", + weapon_range: "100/150", + weapon_price: "50", + }, + anti_vehicle_laser: { + weapon_ammo: "15", + weapon_damage: "3d10", + weapon_range: "2000/4000", + weapon_price: "10000", + }, + combat_rifle: { + weapon_ammo: "30", + weapon_burst: true, + weapon_damage: "1d12", + weapon_encumbrance: "2", + weapon_range: "100/300", + weapon_price: "300", + }, + combat_shotgun: { + weapon_ammo: "12", + weapon_burst: true, + weapon_damage: "3d4", + weapon_encumbrance: "2", + weapon_range: "10/30", + weapon_price: "300", + }, + conversion_bow: { + weapon_ammo: "1", + weapon_damage: "1d8", + weapon_encumbrance: "2", + weapon_range: "150/300", + weapon_price: "500", + }, + crude_pistol: { + weapon_ammo: "1", + weapon_damage: "1d6", + weapon_encumbrance: "1", + weapon_range: "5/15", + weapon_price: "20", + }, + demo_charge: { + weapon_damage: "3d10", + weapon_encumbrance: "1", + weapon_range: "20/40", + weapon_price: "250", + }, + distortion_cannon: { + weapon_attack: "1", + weapon_ammo: "6", + weapon_damage: "2d12", + weapon_encumbrance: "2", + weapon_range: "100/300", + weapon_price: "1250", + }, + grenade: { + weapon_damage: "2d6", + weapon_encumbrance: "1", + weapon_range: "10/30", + weapon_price: "25", + }, + heavy_machine_gun: { + weapon_ammo: "10", + weapon_damage: "3d6", + weapon_encumbrance: "3", + weapon_range: "500/2000", + weapon_price: "5000", + }, + hydra_array: { + weapon_ammo: "10", + weapon_damage: "3d6", + weapon_range: "4000/8000", + weapon_price: "20000", + }, + large_advanced_weapon: { + weapon_attribute_mod: "@{strength_mod}", + weapon_damage: "1d10+1", + weapon_encumbrance: "2", + weapon_shock_ac: "15", + weapon_shock_damage: "2", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "80", + }, + large_primitive_weapon: { + weapon_attribute_mod: "@{strength_mod}", + weapon_damage: "1d8+1", + weapon_encumbrance: "2", + weapon_shock_ac: "15", + weapon_shock_damage: "2", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "30", + }, + laser_pistol: { + weapon_attack: "1", + weapon_ammo: "10", + weapon_damage: "1d6", + weapon_encumbrance: "1", + weapon_range: "100/300", + weapon_price: "200", + }, + laser_rifle: { + weapon_attack: "1", + weapon_ammo: "20", + weapon_burst: true, + weapon_damage: "1d10", + weapon_encumbrance: "2", + weapon_range: "300/500", + weapon_price: "300", + }, + mag_pistol: { + weapon_ammo: "6", + weapon_damage: "2d6+2", + weapon_encumbrance: "1", + weapon_range: "100/300", + weapon_price: "400", + }, + mag_rifle: { + weapon_ammo: "10", + weapon_damage: "2d8+2", + weapon_encumbrance: "2", + weapon_range: "300/600", + weapon_price: "500", + }, + medium_advanced_weapon: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d8+1", + weapon_encumbrance: "1", + weapon_shock_ac: "13", + weapon_shock_damage: "2", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "60", + }, + medium_primitive_weapon: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d6+1", + weapon_encumbrance: "1", + weapon_shock_ac: "13", + weapon_shock_damage: "2", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "20", + }, + musket: { + weapon_ammo: "1", + weapon_damage: "1d12", + weapon_encumbrance: "2", + weapon_range: "25/50", + weapon_price: "30", + }, + plasma_projector: { + weapon_attack: "1", + weapon_ammo: "6", + weapon_damage: "2d8", + weapon_encumbrance: "2", + weapon_range: "50/100", + weapon_price: "400", + }, + primitive_bow: { + weapon_ammo: "1", + weapon_damage: "1d6", + weapon_encumbrance: "2", + weapon_range: "50/75", + weapon_price: "15", + }, + punch: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d2", + weapon_skill_bonus: "@{skill_punch}", + }, + railgun: { + weapon_ammo: "20", + weapon_damage: "3d8", + weapon_range: "4000/8000", + weapon_price: "8000", + }, + revolver: { + weapon_ammo: "6", + weapon_damage: "1d8", + weapon_encumbrance: "1", + weapon_range: "30/100", + weapon_price: "50", + }, + rifle: { + weapon_ammo: "6", + weapon_damage: "1d10+2", + weapon_encumbrance: "2", + weapon_range: "200/400", + weapon_price: "75", + }, + rocket_launcher: { + weapon_ammo: "1", + weapon_damage: "3d10", + weapon_encumbrance: "2", + weapon_range: "2000/4000", + weapon_price: "4000", + }, + semi_auto_pistol: { + weapon_ammo: "12", + weapon_damage: "1d6+1", + weapon_encumbrance: "1", + weapon_range: "30/100", + weapon_price: "75", + }, + shear_rifle: { + weapon_attack: "1", + weapon_ammo: "10", + weapon_burst: true, + weapon_damage: "2d8", + weapon_encumbrance: "2", + weapon_range: "100/300", + weapon_price: "600", + }, + shotgun: { + weapon_ammo: "2", + weapon_damage: "3d4", + weapon_encumbrance: "2", + weapon_range: "10/30", + weapon_price: "50", + }, + small_advanced_weapon: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d6", + weapon_encumbrance: "1", + weapon_shock_ac: "15", + weapon_shock_damage: "1", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "40", + }, + small_primitive_weapon: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d4", + weapon_encumbrance: "1", + weapon_shock_ac: "15", + weapon_shock_damage: "1", + weapon_skill_bonus: "@{skill_stab}", + }, + sniper_rifle: { + weapon_ammo: "1", + weapon_damage: "2d8", + weapon_encumbrance: "2", + weapon_range: "1000/2000", + weapon_price: "400", + }, + spike_thrower: { + weapon_ammo: "15", + weapon_burst: true, + weapon_damage: "3d8", + weapon_encumbrance: "2", + weapon_range: "20/40", + weapon_price: "600", + }, + stun_baton: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d8", + weapon_encumbrance: "1", + weapon_shock_ac: "15", + weapon_shock_damage: "1", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "75", + }, + submachine_gun: { + weapon_ammo: "20", + weapon_burst: true, + weapon_damage: "1d8", + weapon_encumbrance: "1", + weapon_range: "30/100", + weapon_price: "200", + }, + suit_ripper: { + weapon_attribute_mod: "@{str_dex_mod}", + weapon_damage: "1d6", + weapon_encumbrance: "1", + weapon_skill_bonus: "@{skill_stab}", + weapon_price: "75", + }, + thermal_pistol: { + weapon_attack: "1", + weapon_ammo: "5", + weapon_damage: "2d6", + weapon_encumbrance: "1", + weapon_range: "25/50", + weapon_price: "300", + }, + thunder_gun: { + weapon_attack: "1", + weapon_ammo: "6", + weapon_damage: "2d10", + weapon_encumbrance: "2", + weapon_range: "100/300", + weapon_price: "1000", + }, + void_carbine: { + weapon_ammo: "10", + weapon_damage: "2d6", + weapon_encumbrance: "2", + weapon_range: "100/300", + weapon_price: "400", + }, + vortex_cannon: { + weapon_ammo: "5", + weapon_damage: "5d12", + weapon_range: "1000/2000", + weapon_price: "75000", + }, + wheatcutter_belt: { + weapon_ammo: "5", + weapon_damage: "2d12", + weapon_range: "10/20", + weapon_price: "1000", + }, + }, +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/drones.js b/Stars_Without_Number_Revised/Source/js/drones.js new file mode 100644 index 0000000000..0596efe322 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/drones.js @@ -0,0 +1,59 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Drones */ +const fillDroneStats = () => { + // This must be run from a repeating event, otherwise it will not know where to draw the data from. + getAttrs(["repeating_drones_drone_model"], v => { + const model = (v.repeating_drones_drone_model || "").toLowerCase().trim().replace(/ /g, "_"); + if (autofillData.drones.hasOwnProperty(model)) { + const setting = Object.entries(autofillData.drones[model]).reduce((m, [key, value]) => { + m[`repeating_drones_${key}`] = value; + return m; + }, {}); + setting.repeating_drones_drone_HP_max = setting.repeating_drones_drone_HP; + setAttrs(setting); + } + }); +}; +const fillDroneFitting = (num) => { + const prefix = `repeating_drones_drone_fitting_${num}`; + getAttrs([`${prefix}_desc`, `${prefix}_name`], v => { + const fittingName = (v[`${prefix}_name`] || "").toLowerCase().trim().replace(/ /g, "_"); + if (v[`${prefix}_desc`] === "" && autofillData.droneFittings.includes(fittingName)) { + setAttrs({ + [`${prefix}_desc`]: translate(`${fittingName.toUpperCase()}_DESC`) + }); + } + }); +}; +const calculateDroneAttack = (prefixes, callback) => { + const sourceAttrs = prefixes.reduce((m, prefix) => { + return m.concat([ + `${prefix}_drone_weapon1_ab`, + `${prefix}_drone_weapon1_active`, + `${prefix}_drone_weapon1_attack`, + `${prefix}_drone_weapon2_ab`, + `${prefix}_drone_weapon2_active`, + `${prefix}_drone_weapon2_attack`, + `${prefix}_drone_weapon3_ab`, + `${prefix}_drone_weapon3_active`, + `${prefix}_drone_weapon3_attack`, + ]); + }, ["attack_bonus", "intelligence_mod", "skill_pilot", "skill_program"]); + getAttrs(sourceAttrs, v => { + const skillMod = Math.max(parseInt(v.skill_pilot), parseInt(v.skill_program)) || 0, + intMod = parseInt(v.intelligence_mod) || 0, + attackBonus = parseInt(v.attack_bonus) || 0; + + const setting = prefixes.reduce((m, prefix) => { + [1, 2, 3].filter(num => v[`${prefix}_drone_weapon${num}_active`] === "1") + .forEach(num => { + m[`${prefix}_drone_weapon${num}_attack`] = intMod + + ((skillMod === -1) ? -2 : skillMod) + attackBonus + + parseInt(v[[`${prefix}_drone_weapon${num}_ab`]] || 0); + }); + return m; + }, {}); + mySetAttrs(setting, v, callback); + }); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/eventHandlers.js b/Stars_Without_Number_Revised/Source/js/eventHandlers.js new file mode 100644 index 0000000000..4e1e958614 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/eventHandlers.js @@ -0,0 +1,126 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/** + * Register Event handlers + */ + +on("sheet:opened", handleUpgrade); + +on("sheet:opened change:npc", validateTab); +on("sheet:opened", setTranslatedQueries); +on("sheet:opened change:setting_skill_query", handleAttributeQueries); +on("sheet:opened change:setting_modifier_query", handleModifierQuery); +on("change:homebrew_skill_list", setTranslatedDefaults); + +/* API use ammo boilerplate */ +["weapons", "ship-weapons"].forEach(sName => { + on(`change:repeating_${sName}:weapon_use_ammo`, () => handleAmmoAPI(sName)); +}); +on("change:setting_use_ammo", () => { + handleAmmoAPI("weapons"); + handleAmmoAPI("ship-weapons"); +}); + +/* Character sheet */ +on("change:class", fillClassStats); +attributes.forEach(attr => on(`change:${attr}_base change:${attr}_boosts`, () => calculateAttr(attr))); +attributes.forEach(attr => on(`change:${attr} change:${attr}_bonus`, () => calculateMod(attr))); + +on("change:repeating_shells", function(eventInfo) { + if (eventInfo.sourceType === "player") { + validateShells(eventInfo); + } +}); + +on("change:setting_transhuman_enable change:setting_ai_enable", calculateShellAttrs) + +on(weaponDisplayEvent, generateWeaponDisplay); + +on("change:repeating_weapons:weapon_name", () => validateWeaponSkills()); +on("change:homebrew_skill_list", () => getSectionIDs("repeating_weapons", validateWeaponSkills)); + +on("change:strain change:strain_permanent", validateStrain); +on("change:constitution", calculateMaxStrain); +on("change:repeating_cyberware", calculateCyberwareStrain); +on("change:strain_permanent_extra change:cyberware_strain_total", calculatePermanentStrain); + +on("change:level", calculateSaves); + +on([...effortAttributes, "repeating_psychic-skills:skill"].map(x => `change:${x}`).join(" "), calculateEffort); + +on("change:magic_committed_effort_current change:magic_committed_effort_scene change:magic_committed_effort_day change:magic_total_effort", calculateMagicEffort); + +on("change:ai_committed_processing_current change:ai_committed_processing_scene change:ai_committed_processing_day change:ai_extra_processing change:repeating_processing-nodes", calculateProcessing); + +on("change:repeating_armor change:innate_ac remove:repeating_armor", calculateAC); + +on("change:strength change:repeating_gear remove:repeating_gear change:repeating_weapons " + + "remove:repeating_weapons change:repeating_armor remove:repeating_armor", calculateGearReadiedStowed); + +on("change:level change:setting_xp_scheme", calculateNextLevelXP); + +on("change:setting_super_type", validateSuperTab); + +/* Character chat macros */ +on("change:homebrew_luck_save", buildSaveMenu); + +on([ + ...skills.revised.map(x => `change:skill_${x}`), + ...skills.first.map(x => `change:skill_${x}`), + "change:homebrew_skill_list", + "change:repeating_skills", + "change:setting_show_untrained_skills", + "remove:repeating_skills", +].join(" "), buildSkillMenu); + +on([ + ...skills.psionic.map(x => `change:skill_${x}`), + "change:setting_super_type change:repeating_techniques remove:repeating_techniques", + "change:repeating_psychic-skills remove:repeating_psychic-skills" +].join(" "), buildPsionicsMenu); + +on("change:setting_super_type change:repeating_spells remove:repeating_spells " + + "change:repeating_magic-skills remove:repeating_magic-skills " + + "change:skill_magic change:skill_magic2_name change:skill_magic2", buildMagicMenu); + +/* Repeating autofill */ +autofillSections.forEach(sName => { + on(`change:generate_${sName}_source`, () => generateAutofillInfo(sName)); + on(`change:generate_${sName}_button`, () => generateAutofillRow(sName)); +}); + + +/* Ship sheet */ +on("change:ship_hulltype", fillShipStats); +on("change:ship_class", () => { + setShipClass(); + ["ship-fittings", "ship-defenses"].forEach(sName => generateAutofillInfo(sName)); +}); +on(shipStatEvent, calculateShipStats); +on("change:repeating_ship-weapons:weapon_name change:repeating_ship-weapons:weapon_attack_bonus " + + "remove:repeating_ship-weapons", buildShipWeaponsMenu); + +/* Drones */ +[1,2,3,4,5].forEach(num => { + on(`change:repeating_drones:drone_fitting_${num}_name`, () => fillDroneFitting(num)); +}); +on("change:repeating_drones:drone_model", fillDroneStats); +on("change:attack_bonus change:intelligence_mod change:skill_pilot change:skill_program", + () => getSectionIDs("repeating_drones", idArray => { + calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`)); + })); +on([ + ...[1, 2, 3].map(n => `change:repeating_drones:drone_weapon${n}_ab`), + ...[1, 2, 3].map(n => `change:repeating_drones:drone_weapon${n}_active`) +].join(" "), () => calculateDroneAttack(["repeating_drones"])); + +/* NPC sheet */ +on("change:npc_stat_block", fillNPC); +on("change:npc_rolls_hidden", handleNPCRollHide); +on("change:repeating_npc-attacks:attack_name", addNPCAttackBonus); +on("change:npc_roll_full_attack change:repeating_npc-attacks:attack_number", setNPCMultiAttacks); + +on("change:repeating_npc-abilities:ability_name remove:repeating_npc-abilities", buildAbilitiesMenu); +on("change:repeating_npc-attacks:attack_name change:repeating_npc-attacks:attack_ab " + + "change:repeating_npc-attacks:attack_number remove:repeating_npc-attacks", buildAttacksMenu); +on("change:npc change:npc_armor_type change:macro_npc_attacks change:macro_npc_abilities", buildStatblock); \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/menuBuilders.js b/Stars_Without_Number_Revised/Source/js/menuBuilders.js new file mode 100644 index 0000000000..b6f9afee04 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/menuBuilders.js @@ -0,0 +1,196 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Menu builders */ +/* Helper functions for producing skill chat menus */ +const skillToMacro = v => skill => { + const namedSkills = [ + "culture_one", "culture_two", "culture_three", + "culture_alien", "profession", "magic2" + ]; + if (namedSkills.includes(skill)) + return [`@{skill_${skill}_name} (${sign(v[`skill_${skill}`])})`, `skill_${skill}`]; + else + return [`^{${skill.toUpperCase()}} (${sign(v[`skill_${skill}`])})`, `skill_${skill}`]; +}; +const idToSkillMacro = (v, sName) => id => { + const prefix = `repeating_${sName}_${id}_skill`; + return [`${v[`${prefix}_name`]} (${sign(v[prefix])})`, prefix]; +}; +const buildSkillMacro = ([name, command], index, allSkills) => { + return buildLink(name, command, index + 1 === allSkills.length); +}; +/* End helper functions */ +const buildSaveMenu = () => { + getAttrs(["homebrew_luck_save", "macro_saves"], v => { + const macro_saves = buildLink("^{PHYSICAL} (v@{save_physical})", ("save_physical")) + " " + + buildLink("^{MENTAL} (v@{save_mental})", ("save_mental")) + " " + + buildLink("^{EVASION} (v@{save_evasion})", ("save_evasion"), v.homebrew_luck_save !== "1") + + ((v.homebrew_luck_save === "1") ? + (" " + buildLink("^{LUCK} (v@{save_luck})", ("save_luck"), true)) : ""); + mySetAttrs({ + macro_saves + }, v); + }); +}; +const buildSkillMenu = () => { + getSectionIDs("repeating_skills", idArray => { + const sourceAttrs = [ + ...skills.revised.map(sk => `skill_${sk}`), + ...skills.first.map(sk => `skill_${sk}`), + "homebrew_skill_list", + "setting_show_untrained_skills", + "macro_skills", + ...idArray.map(id => `repeating_skills_${id}_skill_name`), + ...idArray.map(id => `repeating_skills_${id}_skill`), + ]; + getAttrs(sourceAttrs, v => { + const skillList = skills[v.homebrew_skill_list] || []; + const hasSkills = skillList.filter(skill => v[`skill_${skill}`] !== "-1").length || + idArray.filter(id => v[`repeating_skills_${id}_skill`] !== "-1").length; + + const macro_skills = `${[ + ...skillList.filter(skill => v[`skill_${skill}`] !== "-1").map(skillToMacro(v)), + ...idArray.filter(id => v[`repeating_skills_${id}_skill`] !== "-1").map(idToSkillMacro(v, "skills")) + ].map(buildSkillMacro).join(" ")}${v.setting_show_untrained_skills === "1" ? `${ + hasSkills ? "\n\n**^{UNTRAINED}**\n" : ""}${[ + ...skillList.filter(skill => v[`skill_${skill}`] === "-1").map(skillToMacro(v)), + ...idArray.filter(id => v[`repeating_skills_${id}_skill`] === "-1").map(idToSkillMacro(v, "skills")) + ].map(buildSkillMacro).join(" ")}` : ""}`; + mySetAttrs({ + macro_skills + }, v); + }); + }); +}; +const buildPsionicsMenu = () => { + getSectionIDs("repeating_techniques", techniqueIDs => { + getSectionIDs("repeating_psychic-skills", skillIDs => { + const sourceAttrs = [ + ...skills.psionic.map(sk => `skill_${sk}`), + "setting_super_type", + "macro_psionics", + ...skillIDs.map(id => `repeating_psychic-skills_${id}_skill_name`), + ...skillIDs.map(id => `repeating_psychic-skills_${id}_skill`), + ...techniqueIDs.map(id => `repeating_techniques_${id}_technique_name`), + ]; + getAttrs(sourceAttrs, v => { + if (v.setting_super_type === "magic" || v.setting_super_type === "neither") return; + const macro_psionics = `${[ + ...skills.psionic.filter(skill => v[`skill_${skill}`] !== "-1").map(skillToMacro(v)), + ...skillIDs.filter(id => v[`repeating_psychic-skills_${id}_skill`] !== "-1") + .map(idToSkillMacro(v, "psychic-skills")) + ].map(buildSkillMacro).join(" ")}${(techniqueIDs.length ? "\n\n" : "")}${ + techniqueIDs.map((id, index, allIDs) => buildLink(v[`repeating_techniques_${id}_technique_name`], + `repeating_techniques_${id}_technique`, index + 1 === allIDs.length)).join(" ")}`; + mySetAttrs({ + macro_psionics + }, v); + }); + }); + }); +}; +const buildMagicMenu = () => { + getSectionIDs("repeating_spells", spellIDs => { + getSectionIDs("repeating_magic-skills", skillIDs => { + const sourceAttrs = [ + "skill_magic", "skill_magic2", "skill_magic2_name", + "setting_super_type", "macro_magic", + ...skillIDs.map(id => `repeating_magic-skills_${id}_skill_name`), + ...skillIDs.map(id => `repeating_magic-skills_${id}_skill`), + ...spellIDs.map(id => `repeating_spells_${id}_spell_name`), + ]; + getAttrs(sourceAttrs, v => { + if (v.setting_super_type === "psionics" || v.setting_super_type === "neither") return; + const macro_magic = `${[ + ...(v.skill_magic2_name ? ["magic", "magic2"] : ["magic"]).map(skillToMacro(v)), + ...skillIDs.map(idToSkillMacro(v, "magic-skills")) + ].map(buildSkillMacro).join(" ")}${spellIDs.length ? "\n\n" : ""}${ + spellIDs.map(id => buildLink(v[`repeating_spells_${id}_spell_name`], `repeating_spells_${id}_spell`)) + .join(" ")}`; + mySetAttrs({ + macro_magic + }, v); + }); + }); + }); +}; +const buildShipWeaponsMenu = () => { + getSectionIDs("repeating_ship-weapons", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_ship-weapons_${id}_weapon_name`), + ...idArray.map(id => `repeating_ship-weapons_${id}_weapon_attack_bonus`), + "macro_ship_weapons" + ]; + getAttrs(sourceAttrs, v => { + const macro_ship_weapons = idArray.map((id, index) => { + const title = v[`repeating_ship-weapons_${id}_weapon_name`] + + ` ${sign(v[`repeating_ship-weapons_${id}_weapon_attack_bonus`])}`; + return buildLink(title, `repeating_ship-weapons_${id}_attack`, index + 1 === idArray.length); + }).join(" "); + mySetAttrs({ + macro_ship_weapons + }, v); + }); + }); +}; +const buildAttacksMenu = () => { + getSectionIDs("repeating_npc-attacks", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_npc-attacks_${id}_attack_name`), + ...idArray.map(id => `repeating_npc-attacks_${id}_attack_ab`), + ...idArray.map(id => `repeating_npc-attacks_${id}_attack_number`), + "macro_npc_attacks" + ]; + getAttrs(sourceAttrs, v => { + const macro_npc_attacks = idArray.map((id, index) => { + const title = v[`repeating_npc-attacks_${id}_attack_name`] + + ` ${sign(v[`repeating_npc-attacks_${id}_attack_ab`])}` + + ((v[`repeating_npc-attacks_${id}_attack_number`] !== "1") ? + ` (${v[`repeating_npc-attacks_${id}_attack_number`]} attacks)` : ""); + return buildLink(title, `repeating_npc-attacks_${id}_attack`, index + 1 === idArray.length); + }).join(" "); + mySetAttrs({ + macro_npc_attacks + }, v); + }); + }); +}; +const buildAbilitiesMenu = () => { + getSectionIDs("repeating_npc-abilities", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_npc-abilities_${id}_ability_name`), + "macro_npc_abilities" + ]; + getAttrs(sourceAttrs, v => { + const macro_npc_abilities = idArray.map((id, index) => { + return buildLink(v[`repeating_npc-abilities_${id}_ability_name`], + `repeating_npc-abilities_${id}_ability`, index + 1 === idArray.length); + }).join(" "); + mySetAttrs({ + macro_npc_abilities + }, v); + }); + }); +}; +const buildStatblock = () => { + const sourceAttrs = [ + "npc", + "macro_npc_abilities", + "macro_npc_attacks", + "macro_statblock", + ]; + getAttrs(sourceAttrs, v => { + if (v.npc !== "1") return; + const macroList = [ + "[**^{SAVES}** v@{npc_saves},](~npc_save) [**^{SKILLS}** +@{npc_skills},](~npc_skill) ", + "[**^{MORALE}** v@{npc_morale}](~npc_morale)\n", + "[**^{INITIATIVE_FIXED}** d8,](~npc_initiative) [**^{REACTION}** 2d6,](~npc_reaction) ", + "**Move** @{npc_move}\n" + ]; + if (v.macro_npc_attacks) macroList.push("\n**Attacks:** @{macro_npc_attacks}"); + if (v.macro_npc_abilities) macroList.push("\n**Abilities:** @{macro_npc_abilities}"); + mySetAttrs({ + macro_statblock: macroList.join("") + }, v); + }); +}; diff --git a/Stars_Without_Number_Revised/Source/js/migrations.js b/Stars_Without_Number_Revised/Source/js/migrations.js new file mode 100644 index 0000000000..0f10724d23 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/migrations.js @@ -0,0 +1,574 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/** + * Migrations + */ +/* Upgrade event handler */ +const handleUpgrade = () => { + getAttrs(["character_sheet"], v => { + if (!v.character_sheet || v.character_sheet.indexOf(sheetName) !== 0) + upgradeFrom162(); + else if (v.character_sheet.slice(32) !== sheetVersion) + upgradeSheet(v.character_sheet.slice(32), true); + }); +}; + +/* Versioned upgrade */ +const upgradeSheet = (version, firstTime = false, finalTime = false) => { + // Any version upgrade code should go here + const performUpgrade = (version) => { + const [major, minor, patch] = version.split(".").map(x => parseInt(x)); + console.log(`Upgrading from version ${version}.`); + + /** v2.1.0 + * convert old format for burst settings for weapons and attacks + * set ammo and shock checkboxes to reasonable values + * convert old format for gear readied/stowed + **/ + if (major === 2 && minor < 1) { + const upgradeFunction = _.after(4, () => { + // recalculate these things just to be sure, in case the v1.6.2 update + // missed them. + buildShipWeaponsMenu(); + buildAttacksMenu(); + buildMagicMenu(); + generateWeaponDisplay(); + attributes.forEach(calculateMod); + + upgradeSheet("2.1.0"); + }); + + getSectionIDs("repeating_weapons", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_weapons_${id}_weapon_burst`), + ...idArray.map(id => `repeating_weapons_${id}_weapon_shock_damage`), + ...idArray.map(id => `repeating_weapons_${id}_weapon_ammo`) + ]; + getAttrs(sourceAttrs, v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_weapons_${id}_weapon_burst`] === "0") + m[`repeating_weapons_${id}_weapon_burst`] = ""; + else if (v[`repeating_weapons_${id}_weapon_burst`] === "2") + m[`repeating_weapons_${id}_weapon_burst`] = "+ 2[Burst]"; + if (v[`repeating_weapons_${id}_weapon_shock_damage`] !== "0") + m[`repeating_weapons_${id}_weapon_shock`] = "{{shock=[[@{weapon_shock_damage} + @{weapon_attribute_mod}[Attribute] + @{weapon_skill_to_damage}[Skill]]] ^{SHOCK_DAMAGE_AGAINST_AC_LEQ} @{weapon_shock_ac}!}}"; + if (v[`repeating_weapons_${id}_weapon_ammo`] && v[`repeating_weapons_${id}_weapon_ammo`] !== "0") { + m[`repeating_weapons_${id}_weapon_use_ammo`] = "{{ammo=[[0@{weapon_ammo} - (1 @{weapon_burst})]] / @{weapon_ammo|max}}}"; + } + return m; + }, {}); + setAttrs(setting, {}, upgradeFunction); + }); + }); + getSectionIDs("repeating_ship-weapons", idArray => { + getAttrs(idArray.map(id => `repeating_ship-weapons_${id}_weapon_ammo_max`), v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_ship-weapons_${id}_weapon_ammo_max`] && v[`repeating_ship-weapons_${id}_weapon_ammo_max`] !== "0") { + m[`repeating_ship-weapons_${id}_weapon_use_ammo`] = "{{ammo=[[@{weapon_ammo} - 1]] / @{weapon_ammo_max}}}"; + } + return m; + }, {}); + setAttrs(setting, {}, upgradeFunction); + }); + }); + getSectionIDs("repeating_npc-attacks", idArray => { + getAttrs(idArray.map(id => `repeating_npc-attacks_${id}_attack_burst`), v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_npc-attacks_${id}_attack_burst`] === "0") + m[`repeating_npc-attacks_${id}_attack_burst`] = ""; + else if (v[`repeating_npc-attacks_${id}_attack_burst`] === "2") + m[`repeating_npc-attacks_${id}_attack_burst`] = "+ 2[Burst]"; + return m; + }, {}); + setAttrs(setting, {}, upgradeFunction); + }); + }); + getSectionIDs("repeating_gear", idArray => { + getAttrs(idArray.map(id => `repeating_gear_${id}_gear_status`), v => { + const setting = idArray.reduce((m, id) => { + m[`repeating_gear_${id}_gear_status`] = + (v[`repeating_gear_${id}_gear_status`] || "").toUpperCase(); + return m; + }, {}); + mySetAttrs(setting, v, {}, upgradeFunction); + }); + }); + } + /** v2.2.0 + * convert single armor line to repeating armor + * Change @{attribute_query_none} to @{attribute_query} + **/ + else if (major === 2 && minor < 2) { + const upgradeFunction = _.after(2, () => { + calculateStrDexMod(); + calculateEffort(); + buildPsionicsMenu(); + buildSkillMenu(); + + upgradeSheet("2.2.0"); + }); + getAttrs(["armor_name", "armor_ac", "armor_encumbrance", "armor_type", "setting_ship_tab_name"], v => { + if (v.armor_ac) { + const data = { + armor_active: "1", + armor_ac: v.armor_ac, + armor_encumbrance: v.armor_encumbrance || "0", + armor_name: v.armor_name || "", + armor_status: "READIED", + armor_type: (v.armor_type || "").toUpperCase() + }; + fillRepeatingSectionFromData("armor", data, upgradeFunction); + } else upgradeFunction(); + if (v.setting_ship_tab_name === "MECH") setAttrs({ + ship_vehicle_type: "MECH" + }); + }); + getSectionIDs("repeating_skills", skillIDs => getSectionIDs("repeating_magic-skills", magicIDs => { + getSectionIDs("repeating_psychic-skills", psychicIDs => { + const sourceAttrs = [ + ...skillIDs.map(id => `repeating_skills_${id}_skill_query`), + ...magicIDs.map(id => `repeating_magic-skills_${id}_skill_query`), + ...psychicIDs.map(id => `repeating_psychic-skills_${id}_skill_query`), + ...skills.revised.map(skill => `skill_${skill}_query`), + ...skills.first.map(skill => `skill_${skill}_query`), + ...skills.psionic.map(skill => `skill_${skill}_query`), + "skill_magic_query", "skill_magic2_query" + ]; + getAttrs(sourceAttrs, v => { + const setting = sourceAttrs.reduce((m, attrName) => { + if (v[attrName] === "@{attribute_query_none}") m[attrName] = "@{attribute_query}"; + return m; + }, {}); + mySetAttrs(setting, v, {}, upgradeFunction); + }); + }); + })); + } + /** v2.3.1 + * Regenerate drone and weapon ABs + **/ + else if (major === 2 && (minor < 3 || (minor === 3 && patch === 0))) { + const upgradeFunction = _.after(1, () => { + upgradeSheet("2.3.1"); + }); + generateWeaponDisplay(); + getSectionIDs("repeating_drones", idArray => { + calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`), upgradeFunction); + }); + } + /** v2.4.3 + * Regenerate Cyberware strain because it was bugged + **/ + else if (major === 2 && (minor < 4 || (minor === 4 && patch < 3))) { + calculateCyberwareStrain(); + upgradeSheet("2.4.3"); + } + /** v2.4.7 + * Move attr to attr_base, and recalculate attr + **/ + else if (major === 2 && minor === 4 && patch < 7) { + attributes.forEach(attr => { + getAttrs([attr, `${attr}_base`], v => { + mySetAttrs({[`${attr}_base`]: parseInt(v[`${attr}`] || 10)}, v, null, () => { + calculateAttr(attr); + }); + }); + }); + upgradeSheet("2.4.7"); + } + + /** v2.4.11 + * Recalculate drone attack to remove incorrect double Int bonus + */ + else if (major == 2 && minor == 4 && patch < 12) { + getSectionIDs("repeating_drones", idArray => { + calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`)); + }) + upgradeSheet("2.4.12"); + } + /** Final upgrade clause, always leave this around */ + else upgradeSheet(sheetVersion, false, true); + }; + + if (firstTime) performUpgrade(version); + else setAttrs({ + character_sheet: `${sheetName} v${version}`, + }, {}, () => { + if (!finalTime) performUpgrade(version); + }); +}; +/* Main upgrade from pre-worker versioning */ +const upgradeFrom162 = () => { + console.log("Upgrading from versionless sheet (assumed to be fresh or v1.6.2)."); + const upgradeFunction = _.after(13, () => { + upgradeSheet("2.0.1"); + }); + + // Legacy migration + getAttrs([1, 2, 3, 4, 5, 6, 7, 8].map(i => `psionics_mastered_${i}`), v => { + const setting = {}; + for (let i = 1; i < 9; i++) { + const technique = v[`psionics_mastered_${i}`]; + if (technique) { + const newRowId = generateRowID(); + setting[`repeating_techniques_${newRowId}_technique_name`] = technique; + } + } + setAttrs(setting); + }); + getAttrs([1, 2, 3, 4].map(i => `cyberware_${i}`), v => { + const setting = {}; + for (let i = 1; i < 5; i++) { + const cyberware = v[`cyberware_${i}`]; + if (cyberware) { + const newRowId = generateRowID(); + setting[`repeating_cyberware_${newRowId}_cyberware_name`] = cyberware; + } + } + setAttrs(setting); + }); + getAttrs(["languages"], v => { + if (v.languages) { + const setting = {}; + + v.languages.split(/\r?\n/).filter(l => !!l).forEach(language => { + const newRowId = generateRowID(); + setting[`repeating_languages_${newRowId}_language`] = language; + }); + setAttrs(setting); + } + }); + + const attrConversionData = { + armor_enc: "armor_encumbrance", + cha: "charisma", + cha_misc: "charisma_bonus", + con: "constitution", + con_misc: "constitution_bonus", + dex: "dexterity", + dex_misc: "dexterity_bonus", + gender: "species_gender", + hd: "npc_hd", + int: "intelligence", + int_misc: "intelligence_bonus", + morale: "npc_morale", + move: "npc_move", + name: "npc_name", + notes: "npc_notes", + npc_ac: "AC", + saves: "npc_saves", + ship_hp_min: "ship_hp", + ship_current_crew: "ship_crew", + ship_last_maintenance_cost: "ship_last_maintenance", + skills: "npc_skills", + skill_biopsion: "skill_biopsionics", + skill_metapsion: "skill_metapsionics", + skill_points: "unspent_skill_points", + str: "strength", + strain_perm: "strain_permanent", + str_misc: "strength_bonus", + wis: "wisdom", + wis_misc: "wisdom_bonus", + }; + const attrsToConvertFromOnTo1 = [ + "homebrew_luck_save", + "homebrew_extra_skills", + "setting_heroic_enable", + ]; + const customConversionAttrs = [ + "damage", + "homebrew_psionics_disable", + "npc_attacks", + "npc_attack_bonus", + "setting_space_magic_enable", + "ship_other_notes", + "ship_free_hardpoints", + "ship_free_mass", + "ship_free_power", + "skill_culture_alien_type", + "skill_culture_one_value", + "skill_culture_two_value", + "skill_culture_three_value", + "profession_type", + "tab", + ...[1, 2, 3, 4].map(n => `homebrew_custom_counter_${n}_name`), + ...[1, 2, 3, 4].map(n => `homebrew_custom_counter_${n}_counter`), + ]; + // convert non-repeating attributes + getAttrs([ + ...Object.keys(attrConversionData), + ...Object.values(attrConversionData), + ...attrsToConvertFromOnTo1, + ...customConversionAttrs, + ], v => { + const setting = Object.entries(attrConversionData).reduce((m, [oldName, newName]) => { + if (v[oldName] && v[oldName] !== "" && `${v[newName]}` !== `${v[oldName]}`) + m[newName] = v[oldName]; + return m; + }, {}); + attrsToConvertFromOnTo1.forEach(name => { + if (v[name] === "on") setting[name] = "1"; + }); + + // convert skill name format + ["one", "two", "three"].forEach(num => { + if (v[`skill_culture_${num}_value`]) + setting[`skill_culture_${num}_name`] = `Culture/${v[`skill_culture_${num}_value`]}`; + }); + if (v.profession_type) setting.skill_profession_name = `Profession/${v.profession_type}`; + if (v.skill_culture_alien_type) + setting.skill_culture_alien_name = `Culture/Alien/${v.skill_culture_alien_type}`; + + // Write legacy ship data + if (v.ship_free_hardpoints || v.ship_free_mass || v.ship_free_power) { + setting.ship_other_notes = `\nLegacy attributes${ + v.ship_free_power ? `\nFree Power: ${v.ship_free_power}` : ""}${ + v.ship_free_mass ? `\nFree Mass: ${v.ship_free_mass}` : ""}${ + v.ship_free_hardpoints ? `\nFree Hardpoints: ${v.ship_free_hardpoints}` : ""} + ${v.ship_other_notes || ""}`; + } + // convert homebrew custom counter stuff + const customCounterData = [1, 2, 3, 4].reduce((m, num) => { + if (v[`homebrew_custom_counter_${num}_name`]) m.push({ + resource_name: v[`homebrew_custom_counter_${num}_name`], + resource_count: v[`homebrew_custom_counter_${num}_counter`] || 0 + }); + return m; + }, []); + fillRepeatingSectionFromData("resources", customCounterData, upgradeFunction); + + // Tab + if (`${v.tab}` === "1" || `${v.tab}` === "4") setting.tab = "character"; + if (`${v.tab}` === "2") setting.tab = "ship"; + if (`${v.tab}` === "3") { + setting.tab = "character"; + setting.npc = "1"; + } + + // NPC attack + if (v.damage) { + const newAttack = { + attack_damage: v.damage, + attack_name: translate("ATTACK"), + attack_number: v.npc_attacks || "1" + }; + fillRepeatingSectionFromData("npc-attacks", newAttack, upgradeFunction); + } else upgradeFunction(); + + // Psionics/Space Magic toggle conversion + if (v.setting_space_magic_enable === "on" && v.homebrew_psionics_disable !== "on") + setting.setting_super_type = "both"; + else if (v.setting_space_magic_enable === "on") + setting.setting_super_type = "magic"; + else if (v.homebrew_psionics_disable === "on") + setting.setting_super_type = "neither"; + + setAttrs(setting, {}, upgradeFunction); + }); + // convert weapon attributes, and extract ship weapons + getSectionIDs("repeating_weapons", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_weapons_${id}_attribute_mod`), + ...idArray.map(id => `repeating_weapons_${id}_add_skill`), + ...idArray.map(id => `repeating_weapons_${id}_weapon_shock`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_name`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_power`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_ab`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_damage`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_ammo`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_special_effects`), + ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_broken`), + ]; + getAttrs(oldAttrs, v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_weapons_${id}_add_skill`] === "@{weapon_skill_bonus}") + m[`repeating_weapons_${id}_weapon_skill_to_damage`] = "@{weapon_skill_bonus}"; + if (v[`repeating_weapons_${id}_weapon_shock`]) + m[`repeating_weapons_${id}_weapon_shock_damage`] = v[`repeating_weapons_${id}_weapon_shock`]; + const modValue = v[`repeating_weapons_${id}_attribute_mod`]; + switch (modValue) { + case "@{dex_bonus}": + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{dexterity_mod}"; + break; + case "@{con_bonus}": + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{constitution_mod}"; + break; + case "@{int_bonus}": + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{intelligence_mod}"; + break; + case "@{wis_bonus}": + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{wisdom_mod}"; + break; + case "@{cha_bonus}": + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{charisma_mod}"; + break; + default: + m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{strength_mod}"; + } + return m; + }, {}); + const data = idArray.filter(id => v[`repeating_weapons_${id}_ship_weapon_name`]) + .map(id => { + const row = {}; + row.weapon_name = v[`repeating_weapons_${id}_ship_weapon_name`]; + if (v[`repeating_weapons_${id}_ship_weapon_power`]) + row.weapon_power = v[`repeating_weapons_${id}_ship_weapon_power`]; + if (v[`repeating_weapons_${id}_ship_weapon_ab`]) + row.weapon_attack_bonus = v[`repeating_weapons_${id}_ship_weapon_ab`]; + if (v[`repeating_weapons_${id}_ship_weapon_damage`]) + row.weapon_damage = v[`repeating_weapons_${id}_ship_weapon_damage`]; + if (v[`repeating_weapons_${id}_ship_weapon_ammo`]) + row.weapon_ammo = v[`repeating_weapons_${id}_ship_weapon_ammo`]; + if (v[`repeating_weapons_${id}_ship_weapon_special_effects`]) + row.weapon_qualities = v[`repeating_weapons_${id}_ship_weapon_special_effects`]; + if (v[`repeating_weapons_${id}_ship_weapon_broken`] === "on") + row.weapon_broken = "1"; + return row; + }); + fillRepeatingSectionFromData("ship-weapons", data, upgradeFunction); + setAttrs(setting, null, upgradeFunction); + }); + }); + // convert skills + getSectionIDs("repeating_skills", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_name`), + ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_name`), + ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_level`), + ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_level`), + ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_specialist`), + ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_specialist`), + ]; + getAttrs(oldAttrs, v => { + const data = idArray.reduce((m, id) => { + [1, 2].forEach(i => { + if (v[`repeating_skills_${id}_custom_skill_${i}_name`]) { + const skillLevel = (typeof v[`repeating_skills_${id}_custom_skill_${i}_level`] === "undefined") ? + "-1" : v[`repeating_skills_${id}_custom_skill_${i}_level`]; + m.push({ + skill_name: v[`repeating_skills_${id}_custom_skill_${i}_name`], + skill: skillLevel, + skill_specialist: v[`repeating_skills_${id}_custom_skill_${i}_specialist`] || "2d6" + }); + } + }); + return m; + }, []); + idArray.forEach(id => removeRepeatingRow(`repeating_skills_${id}`)); + fillRepeatingSectionFromData("skills", data, upgradeFunction); + }); + }); + // convert techniques + getSectionIDs("repeating_technique", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_technique_${id}_technique`), + ...idArray.map(id => `repeating_technique_${id}_technique_description`), + ]; + getAttrs(oldAttrs, v => { + const data = idArray.reduce((m, id) => { + if (v[`repeating_technique_${id}_technique`]) + m.push({ + technique_name: v[`repeating_technique_${id}_technique`], + technique_description: v[`repeating_technique_${id}_technique_description`] || "" + }); + return m; + }, []); + fillRepeatingSectionFromData("techniques", data, upgradeFunction); + idArray.forEach(id => removeRepeatingRow(`repeating_technique_${id}`)); + }); + }); + // convert cyberware name + getSectionIDs("repeating_cyberware", idArray => { + getAttrs(idArray.map(id => `repeating_cyberware_${id}_cyberware`), v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_cyberware_${id}_cyberware`]) + m[`repeating_cyberware_${id}_cyberware_name`] = v[`repeating_cyberware_${id}_cyberware`]; + return m; + }, {}); + setAttrs(setting, null, upgradeFunction); + }); + }); + // convert goals + getSectionIDs("repeating_goals", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_goals_${id}_misc_goal`), + ...idArray.map(id => `repeating_goals_${id}_misc_goal_xp`), + ]; + getAttrs(oldAttrs, v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_goals_${id}_misc_goal`]) + m[`repeating_goals_${id}_goal_name`] = v[`repeating_goals_${id}_misc_goal`]; + if (v[`repeating_goals_${id}_misc_goal_xp`]) + m[`repeating_goals_${id}_goal_xp`] = v[`repeating_goals_${id}_misc_goal_xp`]; + return m; + }, {}); + setAttrs(setting, null, upgradeFunction); + }); + }); + // convert languages + getSectionIDs("repeating_languages", idArray => { + getAttrs(idArray.map(id => `repeating_languages_${id}_languages`), v => { + const setting = idArray.reduce((m, id) => { + if (v[`repeating_languages_${id}_languages`]) + m[`repeating_languages_${id}_language`] = v[`repeating_languages_${id}_languages`]; + return m; + }, {}); + setAttrs(setting, null, upgradeFunction); + }); + }); + // convert gear status + getSectionIDs("repeating_gear", idArray => { + getAttrs(idArray.map(id => `repeating_gear_${id}_gear_readied`), v => { + const setting = idArray.reduce((m, id) => { + if (`${v[`repeating_gear_${id}_gear_readied`]}` === "1") + m[`repeating_gear_${id}_gear_status`] = "readied"; + else if (`${v[`repeating_gear_${id}_gear_readied`]}` === "2") + m[`repeating_gear_${id}_gear_status`] = "stowed"; + return m; + }, {}); + setAttrs(setting, null, upgradeFunction); + }); + }); + // convert defenses + getSectionIDs("repeating_defenses", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_defenses_${id}_ship_defense_name`), + ...idArray.map(id => `repeating_defenses_${id}_ship_defense_special_effects`), + ...idArray.map(id => `repeating_defenses_${id}_ship_defense_broken`), + ]; + getAttrs(oldAttrs, v => { + const data = idArray.map(id => { + const row = {}; + if (v[`repeating_defenses_${id}_ship_defense_name`]) + row.defense_name = v[`repeating_defenses_${id}_ship_defense_name`]; + if (v[`repeating_defenses_${id}_ship_defense_special_effects`]) + row.defense_effect = v[`repeating_defenses_${id}_ship_defense_special_effects`]; + if (v[`repeating_defenses_${id}_ship_defense_broken`]) + row.defense_broken = "1"; + return row; + }); + fillRepeatingSectionFromData("ship-defenses", data, upgradeFunction); + }); + }); + // convert fittings + getSectionIDs("repeating_fittings", idArray => { + const oldAttrs = [ + ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_name`), + ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_special_effects`), + ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_broken`), + ]; + getAttrs(oldAttrs, v => { + const data = idArray.map(id => { + const row = {}; + if (v[`repeating_fittings_${id}_ship_fitting_name`]) + row.fitting_name = v[`repeating_fittings_${id}_ship_fitting_name`]; + if (v[`repeating_fittings_${id}_ship_fitting_special_effects`]) + row.fitting_effect = v[`repeating_fittings_${id}_ship_fitting_special_effects`]; + if (v[`repeating_fittings_${id}_ship_fitting_broken`]) + row.fitting_broken = "1"; + return row; + }); + fillRepeatingSectionFromData("ship-fittings", data, upgradeFunction); + }); + }); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/npc.js b/Stars_Without_Number_Revised/Source/js/npc.js new file mode 100644 index 0000000000..fa99728a2a --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/npc.js @@ -0,0 +1,82 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* NPC */ +const fillNPC = () => { + getAttrs(["npc_stat_block"], (v) => { + if (v.npc_stat_block && autofillData.statblocks[v.npc_stat_block]) { + const [ + HD, AC, npc_attack_bonus, damage, attacks, npc_move, + npc_morale, npc_skills, npc_saves, armor_type + ] = autofillData.statblocks[v.npc_stat_block]; + + const setting = { + AC, + npc_attack_bonus, + npc_move, + npc_morale, + npc_skills, + npc_saves + }; + + if (armor_type) setting.npc_armor_type = armor_type; + if (HD.includes("hp")) setting.HP = HD.replace("hp", ""); + else setting.npc_hd = HD; + + setAttrs(setting); + + if (damage !== "Unarmed") { + const newAttack = { + attack_ab: npc_attack_bonus, + attack_damage: damage, + attack_name: translate("ATTACK"), + attack_number: attacks + }; + fillRepeatingSectionFromData("npc-attacks", newAttack); + } + } + }); +}; +const addNPCAttackBonus = () => { + getAttrs(["repeating_npc-attacks_attack_ab", "npc_attack_bonus"], v => { + if (`${v["repeating_npc-attacks_attack_ab"]}` === "0") { + setAttrs({ + ["repeating_npc-attacks_attack_ab"]: v.npc_attack_bonus + }); + } + }); +}; +const setNPCMultiAttacks = () => { + getSectionIDs("repeating_npc-attacks", idArray => { + const sourceAttrs = [ + ...idArray.map(id => `repeating_npc-attacks_${id}_attack_number`), + "npc_roll_full_attack" + ]; + getAttrs(sourceAttrs, v => { + const setting = idArray.reduce((m, id) => { + if (v.npc_roll_full_attack === "1") { + const num = parseInt(v[`repeating_npc-attacks_${id}_attack_number`]) || 1; + m[`repeating_npc-attacks_${id}_attack_extra_macro`] = [2, 3, 4, 5, 6, 7, 8].map(n => { + if (n <= num) + return `{{attack${n}=[[1d20 + @{attack_ab} @{attack_burst} @{modifier_query}]]}} ` + + `{{damage${n}=[[@{attack_damage} @{attack_burst}]]}} `; + else return ""; + }).join(""); + } else { + m[`repeating_npc-attacks_${id}_attack_extra_macro`] = ""; + } + return m; + }, {}); + setAttrs(setting); + }); + }); +}; +const handleNPCRollHide = () => { + const types = ["hp", "initiative", "save", "skill", "morale", "reaction"]; + getAttrs(["npc_rolls_hidden", ...types.map(x => `npc_${x}_hidden`)], v => { + const setting = types.reduce((m, n) => { + m[`npc_${n}_hidden`] = v.npc_rolls_hidden; + return m; + }, {}); + mySetAttrs(setting, v); + }); +}; diff --git a/Stars_Without_Number_Revised/Source/js/ship.js b/Stars_Without_Number_Revised/Source/js/ship.js new file mode 100644 index 0000000000..1cf52f7896 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/ship.js @@ -0,0 +1,88 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Ship Code */ +const fillShipStats = () => { + getAttrs(["ship_hulltype", ...shipStats], v => { + const label = v.ship_hulltype && reverseHullTypes[v.ship_hulltype.toLowerCase()]; + if (label && autofillData.hulltypes.hasOwnProperty(label)) { + const data = Object.assign({}, autofillData.hulltypes[label]); + data.ship_hp_max = data.ship_hp; + Object.keys(data).forEach(key => { + //Might remove later. Unsure if desired feature. + if (!(["", 0, "0"].includes(v[key]))) delete data[key]; //Prevents changing Hull Type + }); + mySetAttrs(data, v); + calculateShipStats(); + } + }); +}; +const setShipClass = () => { + // Sets the internal ship_class_normalised attribute responsible + // for filtering ship modules according to class. + getAttrs(["ship_class", "ship_class_normalised"], v => { + if (["fighter", translate("FIGHTER").toLowerCase()].includes(v.ship_class.toLowerCase())) + setAttrs({ + ship_class_normalised: "fighter" + }); + else if (["frigate", translate("FRIGATE").toLowerCase()].includes(v.ship_class.toLowerCase())) + setAttrs({ + ship_class_normalised: "frigate" + }); + else if (["cruiser", translate("CRUISER").toLowerCase()].includes(v.ship_class.toLowerCase())) + setAttrs({ + ship_class_normalised: "cruiser" + }); + else mySetAttrs({ + ship_class_normalised: "" + }, v); + }); +}; +const calculateShipStats = () => { + // Calculates power, mass, and hardpoints remaining. + const doCalc = (weaponIDs, fittingIDs, defenseIDs) => { + const oldAttrs = [ + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_power`), + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_mass`), + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_hardpoints`), + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_price`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_power`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_mass`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_price`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_power`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_mass`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_price`), + "ship_power_max", "ship_mass_max", "ship_hardpoints_max", + "ship_power", "ship_mass", "ship_hardpoints", "ship_price", "ship_hull_price" + ]; + getAttrs(oldAttrs, v => { + const ship_power = v.ship_power_max - sum([ + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_power`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_power`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_power`), + ].map(x => v[x])); + const ship_mass = v.ship_mass_max - sum([ + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_mass`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_mass`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_mass`), + ].map(x => v[x])); + const ship_hardpoints = v.ship_hardpoints_max - + sum(weaponIDs.map(id => v[`repeating_ship-weapons_${id}_weapon_hardpoints`])); + const ship_price = new Intl.NumberFormat().format(parseInt(v.ship_hull_price) + sum([ + ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_price`), + ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_price`), + ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_price`), + ].map(x => v[x]))); + mySetAttrs({ + ship_power, + ship_mass, + ship_hardpoints, + ship_price + }, v, { + silent: true + }); + }); + }; + getSectionIDs("repeating_ship-weapons", A => getSectionIDs("repeating_ship-fittings", B => { + getSectionIDs("repeating_ship-defenses", C => doCalc(A, B, C)); + })); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/translations.js b/Stars_Without_Number_Revised/Source/js/translations.js new file mode 100644 index 0000000000..ded55d2010 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/translations.js @@ -0,0 +1,96 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Translations */ +const setTranslatedDefaults = () => { + const specialSkills = { + skill_culture_alien_name: { + trans: `${translate("CULTURE_ALIEN")}/`, + default: "Culture/Alien/" + }, + skill_culture_one_name: { + trans: `${translate("CULTURE")}/`, + default: "Culture/" + }, + skill_culture_two_name: { + trans: `${translate("CULTURE")}/`, + default: "Culture/" + }, + skill_culture_three_name: { + trans: `${translate("CULTURE")}/`, + default: "Culture/" + }, + skill_profession_name: { + trans: `${translate("PROFESSION")}/`, + default: "Profession/" + }, + }; + getAttrs([...Object.keys(specialSkills), "homebrew_skill_list", "innate_ac_name"], v => { + const setting = {}; + if (v.homebrew_skill_list === "first") { + Object.entries(specialSkills).forEach(([name, data]) => { + if (v[name] === data.default && v[name] !== data.trans) setting[name] = data.trans; + }); + } + setting.innate_ac_name = translate("INNATE_AC"); + mySetAttrs(setting, v); + }); +}; +const handleAttributeQueries = () => { + // Attribute query magic to set the global query variables according to + // translations and the state of the setting_skill_query attribute. + const attrQueries = attributes.map(attr => { + const translated = translate(attr.toUpperCase()); + return `${translated},+ @{${attr}_mod}[${translated}]]]}} ` + + `{{attribute= + ${translate(`${attr.toUpperCase()}_SHORT`)}}}`; + }); + + getAttrs(["attribute_query", "setting_skill_query", + ...attributes.map(a => `attribute_query_${a.slice(0,3)}`) + ], v => { + + if (v.setting_skill_query === "hover" || v.setting_skill_query === "hide") { + mySetAttrs({ + attribute_query: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, + attribute_query_str: `+ @{strength_mod}[${translate("STRENGTH")}]]]}} {{attribute= + ${translate("STRENGTH_SHORT")}}}`, + attribute_query_dex: `+ @{dexterity_mod}[${translate("DEXTERITY")}]]]}} {{attribute= + ${translate("DEXTERITY_SHORT")}}}`, + attribute_query_con: `+ @{constitution_mod}[${translate("CONSTITUTION")}]]]}} {{attribute= + ${translate("CONSTITUTION_SHORT")}}}`, + attribute_query_int: `+ @{intelligence_mod}[${translate("INTELLIGENCE")}]]]}} {{attribute= + ${translate("INTELLIGENCE_SHORT")}}}`, + attribute_query_wis: `+ @{wisdom_mod}[${translate("WISDOM")}]]]}} {{attribute= + ${translate("WISDOM_SHORT")}}}`, + attribute_query_cha: `+ @{charisma_mod}[${translate("CHARISMA")}]]]}} {{attribute= + ${translate("CHARISMA_SHORT")}}}`, + }, v); + } else if (v.setting_skill_query === "query") { + mySetAttrs({ + attribute_query: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, + attribute_query_str: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, + attribute_query_dex: `?{${translate("ATTRIBUTE")}|${[attrQueries[1], attrQueries[0], ...attrQueries.slice(2)].join("|")}}`, + attribute_query_con: `?{${translate("ATTRIBUTE")}|${[attrQueries[2], ...attrQueries.slice(0,2), ...attrQueries.slice(3)].join("|")}}`, + attribute_query_int: `?{${translate("ATTRIBUTE")}|${[attrQueries[3], ...attrQueries.slice(0,3), ...attrQueries.slice(4)].join("|")}}`, + attribute_query_wis: `?{${translate("ATTRIBUTE")}|${[attrQueries[4], ...attrQueries.slice(0,4), ...attrQueries.slice(5)].join("|")}}`, + attribute_query_cha: `?{${translate("ATTRIBUTE")}|${[attrQueries[5], ...attrQueries.slice(0,5)].join("|")}}`, + }, v); + } + }); +}; +const handleModifierQuery = () => { + getAttrs(["modifier_query", "setting_modifier_query"], v => { + if (`${v.setting_modifier_query}` === "1") { + mySetAttrs({ + modifier_query: `+ ?{${translate("MODIFIER")}|0}[${translate("MODIFIER_SHORT")}]`, + }, v); + } else mySetAttrs({ + modifier_query: " " + }, v); + }); +}; +const setTranslatedQueries = () => { + getAttrs(["burst_query", "extra_hp_query", "translation_numdice", "proficient_query", "skill_name_query"], v => { + const setting = { + burst_query: `?{${translate("BURST")}|${translate("YES")},+ 2[${translate("BURST")}]|${translate("NO")},&` + "#" + "32;}", + extra_hp_query: `?{${translate("EXTRA_HP_QUERY")}|0}[${translate("BONUS")}]`, + proficient_query: `?{${translate("PROFICIENT")}|${translate("YES")}, @{npc_skills}|${translate("NO")}, 0}[${translate("SKILL")}]`, + skill_name_query: `?{${translate("SKILL_NAME")}|${translate("SKILL")}}`, + translation_numdice: translate("NUMBER_OF_DICE") + }; + mySetAttrs(setting, v); + }); +}; diff --git a/Stars_Without_Number_Revised/Source/js/util.js b/Stars_Without_Number_Revised/Source/js/util.js new file mode 100644 index 0000000000..6618bb0cf3 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/util.js @@ -0,0 +1,48 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/* Utility functions */ +const sign = (value) => { + const val = parseInt(value) || 0; + if (val >= 0) return `+${val}`; + else return `${val}`; +}; +const sum = (list) => list.reduce((m, c) => m + (parseInt(c) || 0), 0); +const buildLink = (caption, ability, last) => `[${caption}${!last ? "," : ""}](~${ability})`; +const mySetAttrs = (setting, values, ...rest) => { + // This is a version of setAttrs that expects an extra values parameter + // (as received from getAttrs). It will only set values in setting that differ + // from their current value on the sheet. The intention is to not + // set values unnecessarily (it's expensive) and to reduce bloat + // in the Attributes & Abilities tab. + Object.keys(setting).forEach(k => { + if (`${values[k]}` === `${setting[k]}`) delete setting[k]; + }); + setAttrs(setting, ...rest); +}; +const fillRepeatingSectionFromData = (sName, data, callback) => { + // Populates the repeating section repeating_${SName} with new + // rows from the data array. Every entry of the array is expected + // to be an object, and its key/value pairs will be written into + // the repeating section as a new row. If data is not an array + // but a single object, it will be treated like an array with + // a single element. + callback = callback || (() => {}); + const createdIDs = [], + getRowID = () => { + while (true) { + let newID = generateRowID(); + if (!createdIDs.includes(newID)) { + createdIDs.push(newID); + return newID; + } + } + }; + const setting = (Array.isArray(data) ? data : [data]).map(o => { + const newID = getRowID(); + return Object.entries(o).reduce((m, [key, value]) => { + m[`repeating_${sName}_${newID}_${key}`] = `${value}`; + return m; + }, {}); + }).reduce((m, o) => Object.assign(m, o), {}); + setAttrs(setting, {}, callback); +}; \ No newline at end of file diff --git a/Stars_Without_Number_Revised/Source/js/validations.js b/Stars_Without_Number_Revised/Source/js/validations.js new file mode 100644 index 0000000000..02441e9f05 --- /dev/null +++ b/Stars_Without_Number_Revised/Source/js/validations.js @@ -0,0 +1,76 @@ +/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ + +/** + * Validations + */ +const validateTab = () => { + getAttrs(["tab", "npc"], v => { + if (v.tab === "character" && v.npc === "1") setAttrs({ + tab: "npc" + }); + if (v.tab === "npc" && v.npc === "0") setAttrs({ + tab: "character" + }); + }); +}; +const validateSuperTab = () => { + getAttrs(["setting_super_type", "tab_super"], v => { + const setting = {}; + if (v.setting_super_type === "magic") setting.tab_super = "magic"; + if (v.setting_super_type === "psionics") setting.tab_super = "psionics"; + mySetAttrs(setting, v); + }); +}; +const validateStrain = () => { + getAttrs(["strain", "strain_permanent", "strain_max"], v => { + const currentStrain = parseInt(v.strain) || 0, + permanentStrain = parseInt(v.strain_permanent) || 0, + strain = Math.min(parseInt(v.strain_max), Math.max(currentStrain, permanentStrain)) || 0; + + if (strain !== currentStrain) setAttrs({ + strain + }); + }); +}; +const validateWeaponSkills = (ids) => { + // Makes sure that the select for the weapon skill is never in an invalid state. + const prefixes = (ids && ids.map(id => `repeating_weapons_${id}`)) || ["repeating_weapons"]; + getAttrs(["homebrew_skill_list", ...prefixes.map(p => `${p}_weapon_skill_bonus`)], v => { + const revisedList = ["@{skill_exert}", "@{skill_punch}", "@{skill_shoot}", "@{skill_stab}", + "@{skill_telekinesis}", "0" + ], + firstList = ["@{skill_combat_energy}", "@{skill_combat_gunnery}", "@{skill_combat_primitive}", "@{skill_combat_projectile}", + "@{skill_combat_psitech}", "@{skill_combat_unarmed}", "@{skill_telekinesis}", "0" + ], + type = v.homebrew_skill_list, + setting = {}; + prefixes.forEach(prefix => { + if (type === "revised" && !revisedList.includes(v[`${prefix}_weapon_skill_bonus`])) + setting[`${prefix}_weapon_skill_bonus`] = "@{skill_shoot}"; + if (type === "first" && !firstList.includes(v[`${prefix}_weapon_skill_bonus`])) + setting[`${prefix}_weapon_skill_bonus`] = "@{skill_combat_energy}"; + }); + setAttrs(setting); + }); +}; +const validateShells = (e) => { + //Unchecks every shell 'active' toggle except the one toggle now. + console.log(e.sourceAttribute); + console.log(e.sourceAttribute.slice(17, -13)); + console.log(e.sourceAttribute.slice(-6)); + if (e.sourceAttribute.slice(-6) === "active") { + console.log("Changing"); + getSectionIDs("repeating_shells", idArray => { + idArray = idArray.filter(item => item !== e.sourceAttribute.slice(17, -13)) + const setting = {} + idArray.forEach(id => setting[`repeating_shells_${id}_shell_active`] = "0"); + setAttrs(setting, () => { + calculateShellAttrs(); + }) + }) + } else { + console.log("No change needed"); + calculateShellAttrs(); + } + +} diff --git a/Stars_Without_Number_Revised/Source/pug/character.pug b/Stars_Without_Number_Revised/Source/pug/character.pug index 0ca7344a5a..cb228f6117 100644 --- a/Stars_Without_Number_Revised/Source/pug/character.pug +++ b/Stars_Without_Number_Revised/Source/pug/character.pug @@ -96,6 +96,8 @@ input(type="hidden", name="attr_setting_super_type", value="psionics").super-typ input(type="hidden", name="attr_setting_foci_location", value="left").foci-loc input(type="hidden", name="attr_setting_skill_query", value="hover").skills-query input(type="hidden", name="attr_magic_type", value="arcanist").magic-type +input(type="hidden", name="attr_setting_ai_enable", value="ai").ai-setting +input(type="hidden", name="attr_setting_transhuman_enable", value="transhuman").transhuman-setting //- Core .core.core-info.cols-2 @@ -165,10 +167,16 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type tr td(data-i18n=attr.toUpperCase(), data-i18n-title=`${attr.toUpperCase()}_DESC`) td.edit - input(type="number", name=`attr_${attr}_base`, value="10") + if ["strength", "dexterity", "constitution"].includes(attr) + input(type="number", name=`attr_${attr}_base`, value="10").ai-disabled.transhuman-disabled + else + input(type="number", name=`attr_${attr}_base`, value="10") //span(name=`attr_${attr}`).display td.edit - input(type="number", name=`attr_${attr}_boosts`, value="0") + if ["strength", "dexterity", "constitution"].includes(attr) + input(type="number", name=`attr_${attr}_boosts`, value="0").ai-disabled.transhuman-disabled + else + input(type="number", name=`attr_${attr}_boosts`, value="0") //span(name=`attr_${attr}_boosts`).display td //input(type="number", name=`attr_${attr}_boosts`, value="0").edit @@ -317,10 +325,58 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type span(data-i18n="AMMO") +text-input("weapon_encumbrance", "ENCUMBRANCE_SHORT")(type="number", value="0").enc.center + //- Shells + .section.ai-enabled.transhuman-enabled + +sectionheader("show_shells", "SHELLS") + .shells + .grey-box + .flex-center + h3(data-i18n="SHELLS") + label.details-button + input(type="checkbox", name="attr_show_shell_details", value="1", checked) + span(data-i18n="EXPAND") + input(type="hidden", name="attr_show_shell_details", value="1").input-details + .table-header.shells-grid.rep-grid.notdetails + .active(data-i18n="ACTIVE?").notdetails + .str(data-i18n="STRENGTH_SHORT").notdetails + .dex(data-i18n="DEXTERITY_SHORT").notdetails + .con(data-i18n="CONSTITUTION_SHORT").notdetails + .affinity(data-i18n="AFFINITY?").notdetails + .name(data-i18n="NAME").notdetails + fieldset.repeating_shells + button.display.invisible-button(name="roll_Shell", type="roll", value!="&{template:swn-default} {{charname=@{character_name}}} {{info=^{SHELL} — STR @{shell_strength} - DEX @{shell_dexterity} - CON @{shell_constitution}}} {{title=@{shell_name}}} {{desc=^{BASIC_ABILITIES}:\n @{shell_ability} @{shell_affinity_active}}}") + .table-header.shells-grid.rep-grid.details.no-highlight + .active(data-i18n="ACTIVE?") + .str(data-i18n="STRENGTH_SHORT") + .dex(data-i18n="DEXTERITY_SHORT") + .con(data-i18n="CONSTITUTION_SHORT") + .affinity(data-i18n="AFFINITY?") + .name(data-i18n="NAME") + .shells-grid.rep-grid + +fake-text-input().center + input(type="checkbox", name="attr_shell_active", value="1").inline-input.rep-radio + +text-input("shell_strength")(type="number", value="0").center.inline-input + +text-input("shell_dexterity")(type="number", value="0").center.inline-input + +text-input("shell_constitution")(type="number", value="0").center.inline-input + +fake-text-input().center + input(type="checkbox", name="attr_shell_affinity_active" value="\n\n^{AFFINITY_ABILITIES}:\n @{shell_affinity}").inline-input + +text-input("shell_name").inline-input.edit + +text-input("shell_name").display + input(type="hidden", name="attr_shell_ability", value="").hide-empty + .rep-grid.details + .table-header + .shell-ability(data-i18n="BASIC_ABILITIES") + span(name="attr_shell_ability").display + textarea(name="attr_shell_ability").edit + .rep-grid.details + .table-header + .shell-affinity(data-i18n="AFFINITY_ABILITIES") + span(name="attr_shell_affinity").display + textarea(name="attr_shell_affinity").edit //- Foci .section.foci-left +foci - + //- Psionics and/or Space Magic .section.super +sectionheader("show_psionics", false) span(data-i18n="PSYCHIC_SKILLS_TECHNIQUES").super-psionics-only @@ -534,6 +590,7 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type input.damage(type="text", name="attr_spell_damage", value="0") textarea.details(name="attr_spell_description") + .column //- Skills .section @@ -569,6 +626,48 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type .section.foci-right +foci +//- AI +.section.cols-2.ai-enabled + +sectionheader("show_ai", "AI") + .ai.halfgrid + .column + .grey-box.ai-processing + h3(data-i18n="COMMITTED_PROCESSING") + +text-input("ai_extra_processing", "BONUS")(type="number", value="0").center + +text-input("ai_committed_processing_current", "CURRENT")(type="number", value="0", min="0").center + +text-input("ai_committed_processing_scene", "SCENE")(type="number", value="0", min="0").center + +text-input("ai_committed_processing_day", "DAY")(type="number", value="0", min="0").center + +text-input("ai_total_processing", "UNCOMMITTED")(type="number", value="1", readonly, data-i18n-title="MAXIMUM_EFFORT_DESC").center + .grey-box.ai-processing-nodes + h3(data-i18n="PROCESSING_NODES") Processing Nodes + .table-header.nodes-grid.rep-grid + .connected(data-i18n="CONNECTED?") + .value(data-i18n="PROCESSING") + .location(data-i18n="LOCATION") + fieldset.repeating_processing-nodes + .nodes-grid.rep-grid + +fake-text-input().center + input(type="checkbox" value="1" name="attr_node_connected") + +text-input("node_value")(type="number" value="0") + +text-input("node_location") + .column + .routines.grey-box.simple-repsection + .flex-center + h3(data-i18n="AI_ROUTINES") AI Routines + label.details-button + input(type="checkbox", name="attr_show_routines_details", value="1", checked) + span(data-i18n="EXPAND") + input(type="hidden", name="attr_show_routines_details", value="1").input-details + fieldset.repeating_routines + button.display.invisible-button(name="roll_Routine", type="roll", value!="&{template:swn-default} {{charname=@{character_name}}} {{info=^{AI_ROUTINE}}} {{title=@{routine_name}}} {{desc=@{routine_description}}}") + .display + .name.flex + span(name="attr_routine_name") + .details #[span(name="attr_routine_description")] + .edit.flex-wrap + input.name.grow(type="text", name="attr_routine_name", data-i18n-placeholder="ROUTINE") + textarea.details(name="attr_routine_description", data-i18n-placeholder="DESCRIPTION") + //- Gear .section.cols-2 +sectionheader("show_gear", "GEAR") @@ -672,7 +771,7 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type .amount # .name(data-i18n="NAME") .details(data-i18n="DESCRIPTION") - .enc(data-i18n="ENCUMBRANCE_SHORT") + .enc(data-i18n="ENCUMBRANCE_SHORT" data-i18n-title="ENCUMBRANCE") .status(data-i18n="STATUS") .bundled(data-i18n="BUNDLED") Bundled @@ -695,7 +794,7 @@ input(type="hidden", name="attr_magic_type", value="arcanist").magic-type +text-input("gear_name")(data-i18n-placeholder="ITEM") +fake-text-input() textarea(name="attr_gear_description") - +text-input("gear_encumbrance")(type="number", value="0", step="any", "data-i18n-title"="ENCUMBRANCE").center + +text-input("gear_encumbrance")(type="number", value="0", step="any").center +fake-text-input() select.status(name="attr_gear_status") option(value="", data-i18n="OTHER", selected) diff --git a/Stars_Without_Number_Revised/Source/pug/header.pug b/Stars_Without_Number_Revised/Source/pug/header.pug index 596e5b3a27..6f5abc8178 100644 --- a/Stars_Without_Number_Revised/Source/pug/header.pug +++ b/Stars_Without_Number_Revised/Source/pug/header.pug @@ -74,11 +74,14 @@ input(type="hidden", name="attr_show_settings" value="0").hider span(data-i18n="XP_SCHEME_DESC") p label - span(data-i18n="SKILL_ATTRIBUTE_CHOICE").colon-after - select(name="attr_setting_skill_query") - option(value="hover", data-i18n="CHOOSE_ON_HOVER", selected) - option(value="query", data-i18n="QUERY") - option(value="hide", data-i18n="EDIT_MODE_ONLY") + span(data-i18n="AI_CHARACTER").colon-after AI Character + input(type="checkbox", name="attr_setting_ai_enable", value="ai") + span(data-i18n="AI_CHARACTER_DESC") Play as an AI, with mechanical Shells and advanced Processing and Routines. Rules for this are in the Deluxe Edition. + p + label + span(data-i18n="TRANSHUMAN_CHARACTERS").colon-after Transhuman Characters + input(type="checkbox", name="attr_setting_transhuman_enable", value="transhuman") + span(data-i18n="TRANSHUMAN_CHARACTERS_DESC") Run a Transhuman campaign with body-swapping. Rules for this are in the Deluxe edition. div h2(data-i18n="GENERAL SETTINGS") p @@ -97,6 +100,13 @@ input(type="hidden", name="attr_show_settings" value="0").hider span(data-i18n="QUERY_FOR_MODIFIERS").colon-after input(type="checkbox", name="attr_setting_modifier_query", value="1", checked) span(data-i18n="QUERY_FOR_MODIFIERS_DESC") + p + label + span(data-i18n="SKILL_ATTRIBUTE_CHOICE").colon-after + select(name="attr_setting_skill_query") + option(value="hover", data-i18n="CHOOSE_ON_HOVER", selected) + option(value="query", data-i18n="QUERY") + option(value="hide", data-i18n="EDIT_MODE_ONLY") p label span(data-i18n="FINE_NPC_WHISPERING").colon-after diff --git a/Stars_Without_Number_Revised/Source/pug/ship.pug b/Stars_Without_Number_Revised/Source/pug/ship.pug index d504e21c01..6e5c53b839 100644 --- a/Stars_Without_Number_Revised/Source/pug/ship.pug +++ b/Stars_Without_Number_Revised/Source/pug/ship.pug @@ -25,10 +25,11 @@ input(type="hidden", name="attr_ship_vehicle_type", value="SHIP").input-type +text-input("ship_size", "SIZE").colsixth.vehicle-type.vehicle-type-custom +text-input("ship_cargo_tonnage", "CARGO").colsixth.vehicle-type.vehicle-type-ship +text-input("ship_ground_speed", "GROUND_SPEED").col25.vehicle-type.vehicle-type-custom - +text-input("ship_home_port", "HOME_PORT").col25.vehicle-type.vehicle-type-ship - +text-input("ship_operating_cost", "OPERATING_COST").col25.grow - +text-input("ship_maintenance_cost", "MAINTENANCE COST").col25.grow - +text-input("ship_last_maintenance", "LAST_MAINTENANCE").col25.grow + +text-input("ship_home_port", "HOME_PORT").col20.vehicle-type.vehicle-type-ship + +text-input("ship_price", "PRICE").col20.grow + +text-input("ship_operating_cost", "OPERATING_COST").col20.grow + +text-input("ship_maintenance_cost", "MAINTENANCE COST").col20.grow + +text-input("ship_last_maintenance", "LAST_MAINTENANCE").col20.grow .data.core-box-text +text-input("ship_speed", "SPEED")(type="number", min="0") +text-input("ship_ac", "AC")(type="number", min="0") @@ -77,6 +78,7 @@ input(type="hidden", name="attr_ship_vehicle_type", value="SHIP").input-type span(data-i18n="ADD") fieldset.repeating_ship-weapons input(type="hidden", name="attr_weapon_api", value="") + input(type="hidden", name="attr_weapon_price") button(type="roll", name="roll_Attack", value!="&{template:swn-attack} {{charname=@{ship_name}}} {{title=@{weapon_name} – @{weapon_qualities}}} {{attack=[[1d20 + @{weapon_attack_bonus}[Attack Bonus] @{modifier_query}]]}} {{damage=[[@{weapon_damage}]]}} @{weapon_use_ammo} @{weapon_api}").display.invisible-button .display.grey-box-bordered input(type="hidden", name="attr_weapon_broken", value="0").hider @@ -179,6 +181,7 @@ input(type="hidden", name="attr_ship_vehicle_type", value="SHIP").input-type input(type="checkbox", name="attr_generate_ship-fittings_button") span(data-i18n="ADD") fieldset.repeating_ship-fittings + input(type="hidden", name="attr_fitting_price") .display input(type="hidden", name="attr_fitting_broken", value="0").hider span.broken.space-after #[span(data-i18n="BROKEN")]: @@ -220,6 +223,7 @@ input(type="hidden", name="attr_ship_vehicle_type", value="SHIP").input-type input(type="checkbox", name="attr_generate_ship-defenses_button") span(data-i18n="ADD") fieldset.repeating_ship-defenses + input(type="hidden", name="attr_defense_price") .display input(type="hidden", name="attr_defense_broken", value="0").hider span.broken.space-after #[span(data-i18n="BROKEN")]: diff --git a/Stars_Without_Number_Revised/Source/workers.js b/Stars_Without_Number_Revised/Source/workers.js deleted file mode 100644 index 71a06d17aa..0000000000 --- a/Stars_Without_Number_Revised/Source/workers.js +++ /dev/null @@ -1,3701 +0,0 @@ -/* global getAttrs, setAttrs, getSectionIDs, generateRowID, on, removeRepeatingRow, _, getTranslationByKey */ -(function () { - "use strict"; - /* Data constants */ - const sheetName = "Stars Without Number (revised)"; - const sheetVersion = "2.4.12"; - const translate = getTranslationByKey; - const attributes = ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]; - const effortAttributes = ["wisdom_mod", "constitution_mod", "psionics_extra_effort", - "skill_biopsionics", "skill_precognition", "skill_telepathy", "skill_teleportation", - "skill_telekinesis", "skill_metapsionics", "psionics_committed_effort_current", - "psionics_committed_effort_scene", "psionics_committed_effort_day" - ]; - const shipStatEvent = [ - ...["hardpoints", "power", "mass"].map(x => `change:repeating_ship-weapons:weapon_${x}`), - ...["power", "mass"].map(x => `change:repeating_ship-defenses:defense_${x}`), - ...["power", "mass"].map(x => `change:repeating_ship-fittings:fitting_${x}`), - "change:ship_power", "change:ship_mass", "change:ship_hardpoints", - "remove:repeating_ship-weapons", "remove:repeating_ship-defenses", - "remove:repeating_ship-fittings" - ].join(" "); - const weaponSkills = [ - "skill_exert", "skill_punch", "skill_shoot", "skill_stab", "skill_combat_energy", - "skill_combat_gunnery", "skill_combat_primitive", "skill_combat_projectile", - "skill_combat_psitech", "skill_combat_unarmed", "skill_telekinesis" - ]; - const weaponDisplayEvent = [ - ...["attack", "name", "skill_bonus", "attribute_mod", "damage", "shock", "shock_damage", - "shock_ac", "skill_to_damage" - ].map(x => `change:repeating_weapons:weapon_${x}`), - "remove:repeating_weapons", "change:attack_bonus", - ...weaponSkills.map(name => `change:${name}`), - ].join(" "); - const skills = { - revised: ["administer", "connect", "exert", "fix", "heal", "know", "lead", - "notice", "perform", "pilot", "program", "punch", "shoot", "sneak", "stab", - "survive", "talk", "trade", "work" - ], - first: ["artist", "athletics", "bureaucracy", "business", "combat_energy", "combat_gunnery", - "combat_primitive", "combat_projectile", "combat_psitech", "combat_unarmed", "computer", - "culture_alien", "culture_criminal", "culture_spacer", "culture_traveller", "culture_one", - "culture_two", "culture_three", "exosuit", "gambling", "history", "instructor", "language", - "leadership", "navigation", "perception", "persuade", "profession", "religion", "science", - "security", "stealth", "steward", "survival", "tactics", "tech_astronautic", "tech_maltech", - "tech_medical", "tech_postech", "tech_pretech", "tech_psitech", "vehicle_air", "vehicle_grav", - "vehicle_land", "vehicle_space", "vehicle_water" - ], - psionic: ["biopsionics", "metapsionics", "precognition", "telekinesis", "telepathy", "teleportation"], - }; - const shipStats = ["ship_ac", "ship_armor", "ship_class", "ship_crew_max", "ship_crew_min", - "ship_hardpoints_max", "ship_hp", "ship_hp_max", "ship_mass_max", "ship_power_max", "ship_speed" - ]; - const reverseHullTypes = { - [translate("BATTLESHIP").toLowerCase()]: "battleship", - [translate("BULK_FREIGHTER").toLowerCase()]: "bulk_freighter", - [translate("CARRIER").toLowerCase()]: "carrier", - [translate("CORVETTE").toLowerCase()]: "corvette", - [translate("FLEET_CRUISER").toLowerCase()]: "fleet_cruiser", - [translate("FREE_MERCHANT").toLowerCase()]: "free_merchant", - [translate("HEAVY_FRIGATE").toLowerCase()]: "heavy_frigate", - [translate("LARGE_STATION").toLowerCase()]: "large_station", - [translate("PATROL_BOAT").toLowerCase()]: "patrol_boat", - [translate("SMALL_STATION").toLowerCase()]: "small station", - [translate("STRIKE_FIGHTER").toLowerCase()]: "strike_fighter", - [translate("SHUTTLE").toLowerCase()]: "shuttle", - }; - const reverseClasses = { - [translate("ADVENTURER").toLowerCase()]: "adventurer", - [translate("EXPERT").toLowerCase()]: "expert", - [translate("PSYCHIC").toLowerCase()]: "psychic", - [translate("WARRIOR").toLowerCase()]: "warrior", - }; - const autofillSections = ["armor", "cyberware", "foci", "gear", "ship-defenses", "ship-fittings", "ship-weapons", "techniques", "weapons"]; - const autofillData = { - "classes": { - adventurer: { - class_ability: translate("ADVENTURER_CLASS_ABILITY"), - }, - expert: { - class_ability: translate("EXPERT_CLASS_ABILITY"), - }, - psychic: { - class_ability: translate("PSYCHIC_CLASS_ABILITY"), - }, - warrior: { - attack_bonus: "1", - class_ability: translate("WARRIOR_CLASS_ABILITY"), - }, - }, - "drones": { - alecto: { - drone_AC: "18", - drone_enc: "4", - drone_HP: "30", - drone_num_fittings: "4", - drone_range: "5000 km", - }, - cuttlefish: { - drone_AC: "13", - drone_enc: "2", - drone_HP: "10", - drone_num_fittings: "5", - drone_range: "1 km", - }, - ghostwalker: { - drone_AC: "15", - drone_enc: "3", - drone_fitting_1_desc: translate("SENSOR_TRANSPARENCY_DESC"), - drone_fitting_1_name: translate("SENSOR_TRANSPARENCY"), - drone_HP: "1", - drone_num_fittings: "3", - drone_range: "5 km", - }, - pax: { - drone_AC: "16", - drone_enc: "4", - drone_HP: "20", - drone_num_fittings: "4", - drone_range: "100 km", - }, - primitve_drone: { - drone_AC: "12", - drone_enc: "2", - drone_HP: "1", - drone_num_fittings: "1", - drone_range: "500 m", - }, - sleeper: { - drone_AC: "12", - drone_enc: "2", - drone_fitting_1_desc: translate("STATIONKEEPING_DESC"), - drone_fitting_1_name: translate("STATIONKEEPING"), - drone_HP: "8", - drone_num_fittings: "5", - drone_range: "100 km", - }, - stalker: { - drone_AC: "13", - drone_enc: "2", - drone_HP: "5", - drone_num_fittings: "3", - drone_range: "2 km", - }, - void_hawk: { - drone_AC: "14", - drone_enc: "6", - drone_HP: "15", - drone_num_fittings: "4", - drone_range: "100 km", - }, - }, - "droneFittings": [ - "ammo_unit", - "bomber", - "environmental_power", - "expert_system", - "extended_flight", - "grav_muffles", - "heavy_lift", - "holoskin", - "medical_support", - "observation_suite", - "racing_gravitics", - "reinforced_structure", - "sensor_transparency", - "stationkeeping", - "suicide_charge", - "weapon_fitting" - ], - "hulltypes": { - battleship: { - ship_ac: "16", - ship_armor: "20", - ship_class: translate("CAPITAL"), - ship_crew_max: "1000", - ship_crew_min: "200", - ship_hardpoints_max: "15", - ship_hp: "100", - ship_mass_max: "50", - ship_power_max: "75", - ship_speed: "0", - ship_hull_price: "50000000", - }, - bulk_freighter: { - ship_ac: "11", - ship_armor: "0", - ship_class: translate("CRUISER"), - ship_crew_max: "40", - ship_crew_min: "10", - ship_hardpoints_max: "2", - ship_hp: "40", - ship_mass_max: "25", - ship_power_max: "15", - ship_speed: "0", - ship_hull_price: "5000000", - }, - carrier: { - ship_ac: "14", - ship_armor: "10", - ship_class: translate("CAPITAL"), - ship_crew_max: "1500", - ship_crew_min: "300", - ship_hardpoints_max: "4", - ship_hp: "75", - ship_mass_max: "100", - ship_power_max: "50", - ship_speed: "0", - ship_hull_price: "60000000", - }, - corvette: { - ship_ac: "13", - ship_armor: "10", - ship_class: translate("FRIGATE"), - ship_crew_max: "40", - ship_crew_min: "10", - ship_hardpoints_max: "6", - ship_hp: "40", - ship_mass_max: "15", - ship_power_max: "15", - ship_speed: "2", - ship_hull_price: "4000000", - }, - fleet_cruiser: { - ship_ac: "14", - ship_armor: "15", - ship_class: translate("CRUISER"), - ship_crew_max: "200", - ship_crew_min: "50", - ship_hardpoints_max: "10", - ship_hp: "60", - ship_mass_max: "30", - ship_power_max: "50", - ship_speed: "1", - ship_hull_price: "10000000", - }, - free_merchant: { - ship_ac: "14", - ship_armor: "2", - ship_class: translate("FRIGATE"), - ship_crew_max: "6", - ship_crew_min: "1", - ship_hardpoints_max: "2", - ship_hp: "20", - ship_mass_max: "15", - ship_power_max: "10", - ship_speed: "3", - ship_hull_price: "500000", - }, - heavy_frigate: { - ship_ac: "15", - ship_armor: "10", - ship_class: translate("FRIGATE"), - ship_crew_max: "120", - ship_crew_min: "30", - ship_hardpoints_max: "8", - ship_hp: "50", - ship_mass_max: "20", - ship_power_max: "25", - ship_speed: "1", - ship_hull_price: "7000000", - }, - large_station: { - ship_ac: "17", - ship_armor: "20", - ship_class: translate("CAPITAL"), - ship_crew_max: "1000", - ship_crew_min: "100", - ship_hardpoints_max: "30", - ship_hp: "120", - ship_mass_max: "75", - ship_power_max: "125", - ship_speed: "", - ship_hull_price: "40000000", - }, - patrol_boat: { - ship_ac: "14", - ship_armor: "5", - ship_class: translate("FRIGATE"), - ship_crew_max: "20", - ship_crew_min: "5", - ship_hardpoints_max: "4", - ship_hp: "25", - ship_mass_max: "10", - ship_power_max: "15", - ship_speed: "4", - ship_hull_price: "2500000", - }, - small_station: { - ship_ac: "11", - ship_armor: "5", - ship_class: translate("CRUISER"), - ship_crew_max: "200", - ship_crew_min: "20", - ship_hardpoints_max: "10", - ship_hp: "120", - ship_mass_max: "40", - ship_power_max: "50", - ship_speed: "", - ship_hull_price: "5000000", - }, - strike_fighter: { - ship_ac: "16", - ship_armor: "5", - ship_class: translate("FIGHTER"), - ship_crew_max: "1", - ship_crew_min: "1", - ship_hardpoints_max: "1", - ship_hp: "8", - ship_mass_max: "2", - ship_power_max: "5", - ship_speed: "5", - ship_hull_price: "200000", - }, - shuttle: { - ship_ac: "11", - ship_armor: "0", - ship_class: translate("FIGHTER"), - ship_crew_max: "10", - ship_crew_min: "1", - ship_hardpoints_max: "1", - ship_hp: "15", - ship_mass_max: "5", - ship_power_max: "3", - ship_speed: "3", - ship_hull_price: "200000", - }, - }, - // HD AC AB Damage #attacks move morale skills saves armor_type - // TODO: Clean this up into a maintainable format - "statblocks": { - barbarian_hero: ["6", "16", "8", "Weapon+3", "1", "10m", "11", "3", "12", "PRIMITIVE"], - barbarian_tribal: ["1", "12", "2", "Weapon", "1", "10m", "8", "1", "15", "PRIMITIVE"], - civilian_security_bot: ["1", "15", "1", "1d8[Stun]", "1", "10m", "12", "1", "15"], - companion_bot: ["1", "12", "0", "1d2[Unarmed]", "1", "10m", "6", "1", "15"], - elite_fighter: ["3", "16", "4", "Weapon+1", "1", "10m", "10", "2", "14", "COMBAT"], - gang_boss: ["3", "14", "4", "Weapon+1", "1", "10m", "9", "2", "15"], - gang_member: ["1", "12", "1", "Weapon", "1", "10m", "7", "1", "15"], - geneengineered_killer: ["4", "16", "5", "Weapon+1", "1", "15m", "10", "2", "13"], - geneengineered_murder_beast: ["10", "18", "10", "1d10", "4", "20m", "12", "3", "10"], - greater_lone_predator: ["5", "16", "6", "1d10", "2", "10m", "9", "2", "12"], - heavy_warbot: ["6", "18", "8", "2d8[Plasma]", "2", "15m", "10", "2", "12"], - heroic_fighter: ["6", "16", "8", "Weapon+3", "1", "10m", "11", "3", "12", "COMBAT"], - industrial_work_bot: ["2", "15", "0", "1d10[Crush]", "1", "5", "8", "1", "14"], - janitor_bot: ["1", "14", "0", "Unarmed", "1", "5m", "8", "1", "15"], - large_aggressive_prey_animal: ["5", "13", "4", "1d10", "1", "15m", "8", "1", "12"], - large_pack_hunter: ["2", "14", "2", "1d6", "1", "15m", "9", "1", "14"], - legendary_fighter: ["10", "20", "12", "Weapon+4", "2", "10m", "12", "5", "10", "POWERED"], - lesser_lone_predator: ["3", "14", "4", "1d8", "2", "15m", "8", "2", "14"], - martial_human: ["1", "10", "1", "Weapon", "1", "10m", "8", "1", "15"], - military_elite: ["3", "16", "4", "Weapon+1", "1", "10m", "10", "2", "14", "COMBAT"], - military_soldier: ["1", "16", "1", "Weapon", "1", "10m", "9", "1", "15", "COMBAT"], - normal_human: ["1", "10", "0", "Unarmed", "1", "10m", "6", "1", "15"], - peaceful_human: ["1", "10", "0", "Unarmed", "1", "10m", "6", "1", "15"], - pirate_king: ["7", "18", "9", "Weapon+2", "1", "10m", "11", "3", "12", "POWERED"], - police_officer: ["1", "14", "1", "Weapon", "1", "10m", "8", "1", "15"], - repair_bot: ["1", "14", "0", "1d6[Tool]", "1", "10m", "8", "1", "15"], - serial_killer: ["6", "12", "8", "Weapon+3", "1", "10m", "12", "3", "12"], - skilled_professional: ["1", "10", "0", "Weapon", "1", "10m", "6", "2", "15"], - small_pack_hunter: ["1", "13", "1", "1d4", "1", "15m", "8", "1", "15"], - small_vicious_beast: ["1hp", "14", "1", "1d2", "1", "10m", "7", "1", "15"], - soldier_bot: ["2", "16", "1", "Weapon", "1", "10m", "10", "1", "14"], - terrifying_apex_predator: ["8", "16", "8", "1d10", "2", "20m", "9", "2", "11"], - veteran_fighter: ["2", "14", "2", "Weapon+1", "1", "10m", "9", "1", "14"], - warrior_tyrant: ["8", "20", "10", "Weapon+3", "1", "10m", "11", "3", "11", "POWERED"] - }, - "armor": { - armored_undersuit: { - armor_ac: "13", - armor_encumbrance: "0", - armor_type: "STREET", - }, - armored_vacc_suit: { - armor_ac: "13", - armor_encumbrance: "2", - armor_type: "STREET", - }, - assault_suit: { - armor_ac: "18", - armor_encumbrance: "2", - armor_type: "POWERED", - }, - combat_field_uniform: { - armor_ac: "16", - armor_encumbrance: "1", - armor_type: "COMBAT", - }, - cuirass_brigandine_linothorax_half_plate: { - armor_ac: "15", - armor_encumbrance: "1", - armor_type: "PRIMITIVE", - }, - deflector_array: { - armor_ac: "18", - armor_encumbrance: "0", - armor_type: "STREET", - }, - executive_security_suit: { - armor_ac: "15", - armor_encumbrance: "0", - armor_type: "ARTIFACT", - }, - field_emitter_panoply: { - armor_ac: "20", - armor_encumbrance: "1", - armor_encumbrance_bonus: "4", - armor_type: "POWERED", - }, - force_pavis: { - armor_ac: "15", - armor_ac_bonus: "1", - armor_encumbrance: "1", - armor_type: "SHIELD", - }, - full_plate_layered_mail: { - armor_ac: "17", - armor_encumbrance: "2", - armor_type: "PRIMITIVE", - }, - ghost_mantle: { - armor_ac: "15", - armor_encumbrance: "0", - armor_type: "ARTIFACT", - }, - icarus_harness: { - armor_ac: "16", - armor_encumbrance: "1", - armor_type: "COMBAT", - }, - leather_jacks_thick_hides_quilted_armor: { - armor_ac: "13", - armor_encumbrance: "1", - armor_type: "PRIMITIVE", - }, - polyplast_carapace: { - armor_ac: "18", - armor_encumbrance: "1", - armor_type: "ARTIFACT", - }, - psitech_combat_suit: { - armor_ac: "16", - armor_encumbrance: "0", - armor_type: "ARTIFACT", - }, - secure_clothing: { - armor_ac: "13", - armor_encumbrance: "1", - armor_type: "STREET", - }, - security_armor: { - armor_ac: "14", - armor_encumbrance: "1", - armor_type: "COMBAT", - }, - shield: { - armor_ac: "13", - armor_ac_bonus: "1", - armor_encumbrance: "1", - armor_type: "SHIELD", - }, - storm_armor: { - armor_ac: "19", - armor_encumbrance: "2", - armor_encumbrance_bonus: "4", - armor_type: "POWERED", - }, - stutterjump_suit: { - armor_ac: "18", - armor_encumbrance: "0", - armor_type: "ARTIFACT", - }, - tempest_assault_array: { - armor_ac: "20", - armor_encumbrance: "1", - armor_encumbrance_bonus: "4", - armor_type: "ARTIFACT", - }, - titan_powered_armor: { - armor_ac: "21", - armor_encumbrance: "2", - armor_encumbrance_bonus: "4", - armor_type: "ARTIFACT", - }, - vestimentum: { - armor_ac: "18", - armor_encumbrance: "0", - armor_type: "POWERED", - }, - warpaint: { - armor_ac: "12", - armor_encumbrance: "0", - armor_type: "STREET", - }, - woven_body_armor: { - armor_ac: "15", - armor_encumbrance: "2", - armor_type: "COMBAT", - } - }, - "cyberware": { - adrenal_suppression_pump: { - cyberware_strain: "1" - }, - bioadaptation_augments: { - cyberware_strain: "1" - }, - body_arsenal_array: { - cyberware_strain: "1" - }, - body_sculpting: { - cyberware_strain: "1" - }, - dermal_armor: { - cyberware_strain: "2" - }, - drone_control_link: { - cyberware_strain: "1" - }, - eelskin_capacitor_mesh: { - cyberware_strain: "1" - }, - gecko_anchors: { - cyberware_strain: "1" - }, - ghost_talker_transceiver: { - cyberware_strain: "1" - }, - holdout_cavity: { - cyberware_strain: "1" - }, - holoskin_emitter: { - cyberware_strain: "1" - }, - identity_submersion_trigger: { - cyberware_strain: "1" - }, - immunofiltration_systems: { - cyberware_strain: "2" - }, - induced_coma_trigger: { - cyberware_strain: "1" - }, - neurointruder_alert: { - cyberware_strain: "1" - }, - panspectral_optics: { - cyberware_strain: "1" - }, - pressure_sheathing: { - cyberware_strain: "1" - }, - prosthetic_limb: { - cyberware_strain: "1" - }, - revenant_wiring: { - cyberware_strain: "3" - }, - slowtime_window: { - cyberware_strain: "2" - }, - stabilization_overrides: { - cyberware_strain: "2" - }, - tagger_nanites: { - cyberware_strain: "1" - }, - toxin_injector: { - cyberware_strain: "2" - }, - twitchlock_actuators: { - cyberware_strain: "2" - }, - }, - "foci": { - alert: {}, - armsman: {}, - assassin: {}, - authority: {}, - close_combatant: {}, - connected: {}, - die_hard: {}, - diplomat: {}, - gunslinger: {}, - hacker: {}, - healer: {}, - henchkeeper: {}, - ironhide: {}, - psychic_training: {}, - savage_fray: {}, - shocking_assault: {}, - sniper: {}, - specialist: {}, - star_captain: {}, - starfarer: {}, - tinker: {}, - unarmed_combatant: {}, - unique_gift: {}, - wanderer: {}, - wild_psychic_talent: {}, - }, - "gear": { - ai_companion: { - gear_encumbrance: "0", - }, - ammo_20_rounds: { - gear_encumbrance: "1#", - }, - ammo_missile: { - gear_encumbrance: "1", - }, - atmofilter: { - gear_encumbrance: "1", - }, - backpack: { - gear_encumbrance: "1", - }, - bezoar: { - gear_encumbrance: "0", - }, - binoculars: { - gear_encumbrance: "1", - }, - bioscanner: { - gear_encumbrance: "1", - }, - black_slab: { - gear_encumbrance: "1", - }, - brainwave: { - gear_encumbrance: "0", - }, - climbing_harness: { - gear_encumbrance: "1", - }, - comm_server: { - gear_encumbrance: "3", - }, - compad: { - gear_encumbrance: "0", - }, - data_phase_tab: { - gear_encumbrance: "1", - }, - data_protocol: { - gear_encumbrance: "0", - }, - dataslab: { - gear_encumbrance: "1", - }, - field_radio: { - gear_encumbrance: "1", - }, - glowbug: { - gear_encumbrance: "0", - }, - grapnel_launcher: { - gear_encumbrance: "1", - }, - grav_chute: { - gear_encumbrance: "1", - }, - grav_harness: { - gear_encumbrance: "3", - }, - hush: { - gear_encumbrance: "0", - }, - instapanel: { - gear_encumbrance: "1#", - }, - integrity_stims: { - gear_encumbrance: "0", - }, - juggernaut_stims: { - gear_encumbrance: "0", - }, - lazarus_patch: { - gear_encumbrance: "1#", - }, - lift: { - gear_encumbrance: "0", - }, - line_shunt: { - gear_encumbrance: "0", - }, - low_light_goggles: { - gear_encumbrance: "1", - }, - medkit: { - gear_encumbrance: "2", - }, - metatool: { - gear_encumbrance: "1", - }, - micropurgator_stims: { - gear_encumbrance: "0", - }, - mindwall_helmet: { - gear_encumbrance: "0", - }, - navcomp: { - gear_encumbrance: "1", - }, - polymorphic_nanites: { - gear_encumbrance: "1", - }, - portabox: { - gear_encumbrance: "1", - }, - pseudonuke: { - gear_encumbrance: "1", - }, - power_cell_type_a: { - gear_encumbrance: "1#", - }, - power_cell_type_b: { - gear_encumbrance: "1", - }, - pressure_tent: { - gear_encumbrance: "4", - }, - pretech_cosmetic: { - gear_encumbrance: "0", - }, - psych: { - gear_encumbrance: "0", - }, - rations_1_day: { - gear_encumbrance: "1#", - }, - redivivus_sheath: { - gear_encumbrance: "1", - }, - remote_link_unit: { - gear_encumbrance: "1", - }, - reverie: { - gear_encumbrance: "0", - }, - rope_20_meters: { - gear_encumbrance: "2/1", - }, - scout_report: { - gear_encumbrance: "0", - }, - solar_recharger: { - gear_encumbrance: "3", - }, - spare_parts: { - gear_encumbrance: "1#", - }, - squeal: { - gear_encumbrance: "0", - }, - stiletto_charge: { - gear_encumbrance: "1", - }, - storage_unit: { - gear_encumbrance: "3", - }, - survey_scanner: { - gear_encumbrance: "1", - }, - survival_kit: { - gear_encumbrance: "1", - }, - system_reset_inductor: { - gear_encumbrance: "0", - }, - tailored_antiallergens: { - gear_encumbrance: "0", - }, - telekinetic_generator: { - gear_encumbrance: "2", - }, - telescoping_pole: { - gear_encumbrance: "0", - }, - thermal_flare: { - gear_encumbrance: "0", - }, - tightbeam_link_unit: { - gear_encumbrance: "1", - }, - toolkit_postech: { - gear_encumbrance: "3", - }, - toolkit_pretech: { - gear_encumbrance: "1", - }, - trade_goods: { - gear_encumbrance: "1#", - }, - trade_metals: { - gear_encumbrance: "1#", - }, - translator_torc: { - gear_encumbrance: "0", - }, - tsunami: { - gear_encumbrance: "0", - }, - vacc_fresher: { - gear_encumbrance: "1", - }, - vacc_skin: { - gear_encumbrance: "1", - }, - vacc_suit: { - gear_encumbrance: "2", - }, - wideawake_serum: { - gear_encumbrance: "0", - }, - }, - "ship-defenses": { - ablative_hull_compartments: { - class: "CAPITAL", - defense_effect: `+1 ${translate("AC")}, +20 ${translate("MAXIMUM_HIT_POINTS").toLowerCase()}.`, - defense_mass: "2#", - defense_power: "5", - defense_price: "100000*", - }, - augmented_plating: { - class: "FIGHTER", - defense_effect: `+2 ${translate("AC")}, -1 ${translate("SPEED")}.`, - defense_mass: "1#", - defense_power: "0", - defense_price: "25000*", - }, - boarding_countermeasures: { - class: "FRIGATE", - defense_effect: translate("BOARDING_COUNTERMEASURES_DESC"), - defense_mass: "1#", - defense_power: "2", - defense_price: "25000*", - }, - burst_ecm_generator: { - class: "FRIGATE", - defense_effect: translate("BURST_ECM_GENERATOR_DESC"), - defense_mass: "1#", - defense_power: "2", - defense_price: "25000*", - }, - foxer_drones: { - class: "CRUISER", - defense_effect: translate("FOXER_DRONES_DESC"), - defense_mass: "1#", - defense_power: "2", - defense_price: "10000*", - }, - grav_eddy_displacer: { - class: "FRIGATE", - defense_effect: translate("GRAV_EDDY_DISPLACER_DESC"), - defense_mass: "2#", - defense_power: "5", - defense_price: "50000*", - }, - hardened_polyceramic_overlay: { - class: "FIGHTER", - defense_effect: translate("HARDENED_POLYCERAMIC_OVERLAY_DESC"), - defense_mass: "1#", - defense_power: "0", - defense_price: "25000*", - }, - planetary_defense_array: { - class: "FRIGATE", - defense_effect: translate("PLANETARY_DEFENSE_ARRAY_DESC"), - defense_mass: "2#", - defense_power: "4", - defense_price: "50000*", - }, - point_defense_lasers: { - class: "FRIGATE", - defense_effect: translate("POINT_DEFENSE_LASERS_DESC"), - defense_mass: "2#", - defense_power: "3", - defense_price: "10000*", - }, - }, - "ship-fittings": { - advanced_lab: { - class: "FRIGATE", - fitting_mass: "2", - fitting_power: "1#", - fitting_price: "10000*", - }, - advanced_nav_computer: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "1#", - fitting_price: "10000*", - }, - amphibious_operation: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "1", - fitting_price: "25000*", - }, - armory: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "0", - fitting_price: "10000*", - }, - atmospheric_configuration: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "0", - fitting_price: "5000*", - }, - auto_targeting_system: { - class: "FIGHTER", - fitting_mass: "0", - fitting_power: "1", - fitting_price: "50000", - }, - automation_support: { - class: "FIGHTER", - fitting_mass: "1", - fitting_power: "2", - fitting_price: "10000*", - }, - boarding_tubes: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "0", - fitting_price: "5000*", - }, - cargo_lighter: { - class: "FRIGATE", - fitting_mass: "2", - fitting_power: "0", - fitting_price: "25000", - }, - cargo_space: { - class: "FIGHTER", - fitting_mass: "1", - fitting_power: "0", - fitting_price: "", - }, - cold_sleep_pods: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "1", - fitting_price: "5000*", - }, - colony_core: { - class: "FRIGATE", - fitting_mass: "2#", - fitting_power: "4", - fitting_price: "100000*", - }, - drill_course_regulator: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "1#", - fitting_price: "25000*", - }, - spike_drive_2: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "1#", - fitting_price: "10000*", - }, - spike_drive_3: { - class: "FIGHTER", - fitting_mass: "2#", - fitting_power: "2#", - fitting_price: "20000*", - }, - spike_drive_4: { - class: "FRIGATE", - fitting_mass: "3#", - fitting_power: "2#", - fitting_price: "40000*", - }, - spike_drive_5: { - class: "FRIGATE", - fitting_mass: "3#", - fitting_power: "3#", - fitting_price: "100000*", - }, - spike_drive_6: { - class: "CRUISER", - fitting_mass: "4#", - fitting_power: "3#", - fitting_price: "500000*", - }, - drop_pod: { - class: "FRIGATE", - fitting_mass: "2", - fitting_power: "0", - fitting_price: "300000", - }, - emissions_dampers: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "1#", - fitting_price: "25000*", - }, - exodus_bay: { - class: "CRUISER", - fitting_mass: "2#", - fitting_power: "1#", - fitting_price: "50000*", - }, - extended_life_support: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "1#", - fitting_price: "5000*", - }, - extended_medbay: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "1", - fitting_price: "5000*", - }, - extended_store: { - class: "FIGHTER", - fitting_mass: "1#", - fitting_power: "0", - fitting_price: "2500*", - }, - fuel_bunkers: { - class: "FIGHTER", - fitting_mass: "1", - fitting_power: "0", - fitting_price: "2500*", - }, - fuel_scoops: { - class: "FRIGATE", - fitting_mass: "1#", - fitting_power: "2", - fitting_price: "5000*", - }, - hydroponic_production: { - class: "CRUISER", - fitting_mass: "2#", - fitting_power: "1#", - fitting_price: "10000*", - }, - lifeboats: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "0", - fitting_price: "2500*", - }, - luxury_cabins: { - class: "FRIGATE", - fitting_mass: "1#", - fitting_power: "1", - fitting_price: "10000*", - }, - mobile_extractor: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "2", - fitting_price: "50000", - }, - mobile_factory: { - class: "CRUISER", - fitting_mass: "2#", - fitting_power: "3", - fitting_price: "50000*", - }, - precognitive_nav_chamber: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "1", - fitting_price: "100000*", - }, - psionic_anchorpoint: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "3", - fitting_price: "Special", - }, - sensor_mask: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "1#", - fitting_price: "10000*", - }, - ship_bay_fighter: { - class: "CRUISER", - fitting_mass: "2", - fitting_power: "0", - fitting_price: "200000", - }, - ship_bay_frigate: { - class: "CAPITAL", - fitting_mass: "4", - fitting_power: "1", - fitting_price: "1000000", - }, - ships_locker: { - class: "FRIGATE", - fitting_mass: "0", - fitting_power: "0", - fitting_price: "2000*", - }, - shiptender_mount: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "1", - fitting_price: "25000*", - }, - smugglers_hold: { - class: "FIGHTER", - fitting_mass: "1", - fitting_power: "0", - fitting_price: "2500*", - }, - survey_sensor_array: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "2", - fitting_price: "5000*", - }, - system_drive: { - class: "FIGHTER", - fitting_mass: "-2#", - fitting_power: "-1#", - fitting_price: "Special", - }, - teleportation_pads: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "1", - fitting_price: "Special", - }, - tractor_beams: { - class: "FRIGATE", - fitting_mass: "1", - fitting_power: "2", - fitting_price: "10000*", - }, - vehicle_transport_fittings: { - class: "FRIGATE", - fitting_mass: "1#", - fitting_power: "0", - fitting_price: "2500*", - }, - workshop: { - class: "FRIGATE", - fitting_mass: "0.5#", - fitting_power: "1", - fitting_price: "500*", - }, - }, - "ship-weapons": { - charged_particle_caster: { - class: "FRIGATE", - weapon_damage: "3d6", - weapon_hardpoints: "2", - weapon_mass: "1", - weapon_power: "10", - weapon_price: "800000", - weapon_qualities: `${translate("AP")} 15, ${translate("CLUMSY")}`, - }, - flak_emitter_battery: { - class: "FRIGATE", - weapon_damage: "2d6", - weapon_hardpoints: "1", - weapon_mass: "3", - weapon_power: "5", - weapon_price: "500000", - weapon_qualities: `${translate("AP")} 10, ${translate("FLAK")}`, - }, - fractal_impact_charge: { - weapon_ammo: "4", - weapon_ammo_price: "500", - class: "FIGHTER", - weapon_damage: "2d6", - weapon_hardpoints: "1", - weapon_mass: "1", - weapon_power: "5", - weapon_price: "200000", - weapon_qualities: `${translate("AP")} 15`, - }, - gravcannon: { - class: "CRUISER", - weapon_damage: "4d6", - weapon_hardpoints: "3", - weapon_mass: "4", - weapon_power: "15", - weapon_price: "2000000", - weapon_qualities: `${translate("AP")} 20`, - }, - lightning_charge_mantle: { - class: "CAPITAL", - weapon_damage: "1d20", - weapon_hardpoints: "2", - weapon_mass: "5", - weapon_power: "15", - weapon_price: "4000000", - weapon_qualities: `${translate("AP")} 5, ${translate("CLOUD")}`, - }, - mag_spike_array: { - weapon_ammo: "5", - weapon_ammo_price: "5000", - class: "FRIGATE", - weapon_damage: "2d6+2", - weapon_hardpoints: "2", - weapon_mass: "2", - weapon_power: "5", - weapon_price: "1000000", - weapon_qualities: `${translate("AP")} 10, ${translate("FLAK")}`, - }, - mass_cannon: { - weapon_ammo: "4", - weapon_ammo_price: "50000", - class: "CAPITAL", - weapon_damage: "2d20", - weapon_hardpoints: "4", - weapon_mass: "5", - weapon_power: "10", - weapon_price: "5000000", - weapon_qualities: `${translate("AP")} 20`, - }, - multifocal_laser: { - class: "FIGHTER", - weapon_damage: "1d4", - weapon_hardpoints: "1", - weapon_mass: "1", - weapon_power: "5", - weapon_price: "100000", - weapon_qualities: `${translate("AP")} 20`, - }, - nuclear_missiles: { - weapon_ammo: "5", - weapon_ammo_price: "5000", - class: "FRIGATE", - weapon_damage: "", - weapon_hardpoints: "2", - weapon_mass: "1", - weapon_power: "5", - weapon_price: "50000", - weapon_qualities: "", - }, - plasma_beam: { - class: "FRIGATE", - weapon_damage: "3d6", - weapon_hardpoints: "2", - weapon_mass: "2", - weapon_power: "5", - weapon_price: "700000", - weapon_qualities: `${translate("AP")} 10`, - }, - polyspectral_mes_beam: { - class: "FIGHTER", - weapon_damage: "2d4", - weapon_hardpoints: "1", - weapon_mass: "1", - weapon_power: "5", - weapon_price: "2000000", - weapon_qualities: `${translate("AP")} 25`, - }, - reaper_battery: { - class: "FIGHTER", - weapon_damage: "3d4", - weapon_hardpoints: "1", - weapon_mass: "1", - weapon_power: "4", - weapon_price: "100000", - weapon_qualities: translate("CLUMSY"), - }, - sandthrower: { - class: "FIGHTER", - weapon_damage: "2d4", - weapon_hardpoints: "1", - weapon_mass: "1", - weapon_power: "3", - weapon_price: "50000", - weapon_qualities: translate("FLAK"), - }, - singularity_gun: { - class: "CAPITAL", - weapon_damage: "5d20", - weapon_hardpoints: "5", - weapon_mass: "10", - weapon_power: "25", - weapon_price: "20000000", - weapon_qualities: `${translate("AP")} 25`, - }, - smart_cloud: { - class: "CRUISER", - weapon_damage: "3d10", - weapon_hardpoints: "2", - weapon_mass: "5", - weapon_power: "10", - weapon_price: "2000000", - weapon_qualities: `${translate("CLOUD")}, ${translate("CLUMSY")}`, - }, - spike_inversion_projector: { - class: "CRUISER", - weapon_damage: "3d8", - weapon_hardpoints: "3", - weapon_mass: "3", - weapon_power: "10", - weapon_price: "2500000", - weapon_qualities: `${translate("AP")} 15`, - }, - spinal_beam_cannon: { - class: "CRUISER", - weapon_damage: "3d10", - weapon_hardpoints: "3", - weapon_mass: "5", - weapon_power: "10", - weapon_price: "1500000", - weapon_qualities: `${translate("AP")} 15, ${translate("CLUMSY")}`, - }, - torpedo_launcher: { - weapon_ammo: "4", - weapon_ammo_price: "2500", - class: "FRIGATE", - weapon_damage: "3d8", - weapon_hardpoints: "1", - weapon_mass: "3", - weapon_power: "10", - weapon_price: "500000", - weapon_qualities: `${translate("AP")} 20`, - }, - vortex_tunnel_inductor: { - class: "CAPITAL", - weapon_damage: "3d20", - weapon_hardpoints: "4", - weapon_mass: "10", - weapon_power: "20", - weapon_price: "5000000", - weapon_qualities: `${translate("AP")} 20, ${translate("CLUMSY")}`, - }, - }, - "techniques": { - accelerated_succor: { - level: "3", - technique_discipline: translate("BIOPSIONICS"), - }, - alternate_outcome: { - level: "2", - technique_discipline: translate("PRECOGNITION"), - }, - anguished_vision: { - level: "3", - technique_discipline: translate("PRECOGNITION"), - }, - burdened_apportation: { - level: "2", - technique_discipline: translate("TELEPORTATION"), - }, - cloak_powers: { - level: "1", - technique_discipline: translate("METAPSIONICS"), - }, - concert_of_minds: { - level: "3", - technique_discipline: translate("METAPSIONICS"), - }, - cursed_luck: { - level: "3", - technique_discipline: translate("PRECOGNITION"), - }, - deep_intrusion: { - level: "4", - technique_discipline: translate("TELEPORTATION"), - }, - destinys_shield: { - level: "2", - technique_discipline: translate("PRECOGNITION"), - }, - effortless_apportation: { - level: "3", - technique_discipline: translate("TELEPORTATION"), - }, - facile_mind: { - level: "1", - technique_discipline: translate("TELEPATHY"), - }, - far_thought: { - level: "2", - technique_discipline: translate("TELEPATHY"), - }, - flawless_mastery: { - level: "4", - technique_discipline: translate("METAPSIONICS"), - }, - force_puppetry: { - level: "4", - technique_discipline: translate("TELEKINESIS"), - }, - forced_outcome: { - level: "3", - technique_discipline: translate("PRECOGNITION"), - }, - holistic_optimization_patterning: { - level: "4", - technique_discipline: translate("BIOPSIONICS"), - }, - impact_sump: { - level: "2", - technique_discipline: translate("TELEKINESIS"), - }, - impervious_pavis_of_will: { - level: "4", - technique_discipline: translate("METAPSIONICS"), - }, - intuitive_response: { - level: "1", - technique_discipline: translate("PRECOGNITION"), - }, - invincible_stand: { - level: "2", - technique_discipline: translate("BIOPSIONICS"), - }, - kinetic_transversal: { - level: "1", - technique_discipline: translate("TELEKINESIS"), - }, - major_organ_restoration: { - level: "2", - technique_discipline: translate("BIOPSIONICS"), - }, - mastered_succor: { - level: "1", - technique_discipline: translate("BIOPSIONICS"), - }, - memory_editing: { - level: "4", - technique_discipline: translate("TELEPATHY"), - }, - metadimensional_friction: { - level: "3", - technique_discipline: translate("METAPSIONICS"), - }, - metamorph: { - level: "3", - technique_discipline: translate("BIOPSIONICS"), - }, - mindtracing: { - level: "1", - technique_discipline: translate("METAPSIONICS"), - }, - neural_trap: { - level: "2", - technique_discipline: translate("METAPSIONICS"), - }, - not_my_time: { - level: "4", - technique_discipline: translate("PRECOGNITION"), - }, - offensive_apportation: { - level: "4", - technique_discipline: translate("TELEPORTATION"), - }, - oracle: { - level: "0", - technique_discipline: translate("PRECOGNITION"), - }, - organic_purification_protocols: { - level: "1", - technique_discipline: translate("BIOPSIONICS"), - }, - perceptive_dislocation: { - level: "2", - technique_discipline: translate("TELEPORTATION"), - }, - personal_apportation: { - level: "0", - technique_discipline: translate("TELEPORTATION"), - }, - pressure_field: { - level: "1", - technique_discipline: translate("TELEKINESIS"), - }, - proficient_apportation: { - level: "1", - technique_discipline: translate("TELEPORTATION"), - }, - prophecy: { - level: "4", - technique_discipline: translate("PRECOGNITION"), - }, - psychic_refinement: { - level: "0", - technique_discipline: translate("METAPSIONICS"), - }, - psychic_static: { - level: "2", - technique_discipline: translate("METAPSIONICS"), - }, - psychic_succor: { - level: "0", - technique_discipline: translate("BIOPSIONICS"), - }, - psychic_tutelage: { - level: "3", - technique_discipline: translate("METAPSIONICS"), - }, - quintessential_reconstruction: { - level: "4", - technique_discipline: translate("BIOPSIONICS"), - }, - reactive_telekinesis: { - level: "3", - technique_discipline: translate("TELEKINESIS"), - }, - reflex_response: { - level: "3", - technique_discipline: translate("TELEPATHY"), - }, - remote_repair: { - level: "1", - technique_discipline: translate("BIOPSIONICS"), - }, - rift_reduplication: { - level: "3", - technique_discipline: translate("TELEPORTATION"), - }, - sense_the_need: { - level: "1", - technique_discipline: translate("PRECOGNITION"), - }, - slip_field: { - level: "2", - technique_discipline: translate("TELEKINESIS"), - }, - spatial_awareness: { - level: "1", - technique_discipline: translate("TELEPORTATION"), - }, - spatial_synchrony_mandala: { - level: "2", - technique_discipline: translate("TELEPORTATION"), - }, - stutterjump: { - level: "3", - technique_discipline: translate("TELEPORTATION"), - }, - suppress_cognition: { - level: "2", - technique_discipline: translate("TELEPATHY"), - }, - surge_momentum: { - level: "3", - technique_discipline: translate("METAPSIONICS"), - }, - suspended_manifestation: { - level: "2", - technique_discipline: translate("METAPSIONICS"), - }, - synthetic_adaptation: { - level: "1", - technique_discipline: translate("METAPSIONICS"), - }, - tangible_force_construct: { - level: "3", - technique_discipline: translate("TELEKINESIS"), - }, - telekinetic_armory: { - level: "1", - technique_discipline: translate("TELEKINESIS"), - }, - telekinetic_expertise: { - level: "2", - technique_discipline: translate("TELEKINESIS"), - }, - telekinetic_flight: { - level: "4", - technique_discipline: translate("TELEKINESIS"), - }, - telekinetic_manipulation: { - level: "0", - technique_discipline: translate("TELEKINESIS"), - }, - telekinetic_ram: { - level: "3", - technique_discipline: translate("TELEKINESIS"), - }, - telepathic_assault: { - level: "3", - technique_discipline: translate("TELEPATHY"), - }, - telepathic_contact: { - level: "0", - technique_discipline: translate("TELEPATHY"), - }, - teratic_overload: { - level: "3", - technique_discipline: translate("BIOPSIONICS"), - }, - terminal_reflection: { - level: "1", - technique_discipline: translate("PRECOGNITION"), - }, - thermokinesis: { - level: "2", - technique_discipline: translate("TELEKINESIS"), - }, - tissue_integrity_field: { - level: "2", - technique_discipline: translate("BIOPSIONICS"), - }, - transmit_thought: { - level: "1", - technique_discipline: translate("TELEPATHY"), - }, - unity_of_thought: { - level: "4", - technique_discipline: translate("TELEPATHY"), - }, - }, - "weapons": { - advanced_bow: { - weapon_ammo: "1", - weapon_damage: "1d6", - weapon_encumbrance: "2", - weapon_range: "100/150", - }, - anti_vehicle_laser: { - weapon_ammo: "15", - weapon_damage: "3d10", - weapon_range: "2000/4000", - }, - combat_rifle: { - weapon_ammo: "30", - weapon_burst: true, - weapon_damage: "1d12", - weapon_encumbrance: "2", - weapon_range: "100/300", - }, - combat_shotgun: { - weapon_ammo: "12", - weapon_burst: true, - weapon_damage: "3d4", - weapon_encumbrance: "2", - weapon_range: "10/30", - }, - conversion_bow: { - weapon_ammo: "1", - weapon_damage: "1d8", - weapon_encumbrance: "2", - weapon_range: "150/300", - }, - crude_pistol: { - weapon_ammo: "1", - weapon_damage: "1d6", - weapon_encumbrance: "1", - weapon_range: "5/15", - }, - demo_charge: { - weapon_damage: "3d10", - weapon_encumbrance: "1", - weapon_range: "20/40", - }, - distortion_cannon: { - weapon_attack: "1", - weapon_ammo: "6", - weapon_damage: "2d12", - weapon_encumbrance: "2", - weapon_range: "100/300", - }, - grenade: { - weapon_damage: "2d6", - weapon_encumbrance: "1", - weapon_range: "10/30", - }, - heavy_machine_gun: { - weapon_ammo: "10", - weapon_damage: "3d6", - weapon_encumbrance: "3", - weapon_range: "500/2000", - }, - hydra_array: { - weapon_ammo: "10", - weapon_damage: "3d6", - weapon_range: "4000/8000", - }, - large_advanced_weapon: { - weapon_attribute_mod: "@{strength_mod}", - weapon_damage: "1d10+1", - weapon_encumbrance: "2", - weapon_shock_ac: "15", - weapon_shock_damage: "2", - weapon_skill_bonus: "@{skill_stab}", - }, - large_primitive_weapon: { - weapon_attribute_mod: "@{strength_mod}", - weapon_damage: "1d8+1", - weapon_encumbrance: "2", - weapon_shock_ac: "15", - weapon_shock_damage: "2", - weapon_skill_bonus: "@{skill_stab}", - }, - laser_pistol: { - weapon_attack: "1", - weapon_ammo: "10", - weapon_damage: "1d6", - weapon_encumbrance: "1", - weapon_range: "100/300", - }, - laser_rifle: { - weapon_attack: "1", - weapon_ammo: "20", - weapon_burst: true, - weapon_damage: "1d10", - weapon_encumbrance: "2", - weapon_range: "300/500", - }, - mag_pistol: { - weapon_ammo: "6", - weapon_damage: "2d6+2", - weapon_encumbrance: "1", - weapon_range: "100/300", - }, - mag_rifle: { - weapon_ammo: "10", - weapon_damage: "2d8+2", - weapon_encumbrance: "2", - weapon_range: "300/600", - }, - medium_advanced_weapon: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d8+1", - weapon_encumbrance: "1", - weapon_shock_ac: "13", - weapon_shock_damage: "2", - weapon_skill_bonus: "@{skill_stab}", - }, - medium_primitive_weapon: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d6+1", - weapon_encumbrance: "1", - weapon_shock_ac: "13", - weapon_shock_damage: "2", - weapon_skill_bonus: "@{skill_stab}", - }, - musket: { - weapon_ammo: "1", - weapon_damage: "1d12", - weapon_encumbrance: "2", - weapon_range: "25/50", - }, - plasma_projector: { - weapon_attack: "1", - weapon_ammo: "6", - weapon_damage: "2d8", - weapon_encumbrance: "2", - weapon_range: "50/100", - }, - primitive_bow: { - weapon_ammo: "1", - weapon_damage: "1d6", - weapon_encumbrance: "2", - weapon_range: "50/75", - }, - punch: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d2", - weapon_skill_bonus: "@{skill_punch}", - }, - railgun: { - weapon_ammo: "20", - weapon_damage: "3d8", - weapon_range: "4000/8000", - }, - revolver: { - weapon_ammo: "6", - weapon_damage: "1d8", - weapon_encumbrance: "1", - weapon_range: "30/100", - }, - rifle: { - weapon_ammo: "6", - weapon_damage: "1d10+2", - weapon_encumbrance: "2", - weapon_range: "200/400", - }, - rocket_launcher: { - weapon_ammo: "1", - weapon_damage: "3d10", - weapon_encumbrance: "2", - weapon_range: "2000/4000", - }, - semi_auto_pistol: { - weapon_ammo: "12", - weapon_damage: "1d6+1", - weapon_encumbrance: "1", - weapon_range: "30/100", - }, - shear_rifle: { - weapon_attack: "1", - weapon_ammo: "10", - weapon_burst: true, - weapon_damage: "2d8", - weapon_encumbrance: "2", - weapon_range: "100/300", - }, - shotgun: { - weapon_ammo: "2", - weapon_damage: "3d4", - weapon_encumbrance: "2", - weapon_range: "10/30", - }, - small_advanced_weapon: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d6", - weapon_encumbrance: "1", - weapon_shock_ac: "15", - weapon_shock_damage: "1", - weapon_skill_bonus: "@{skill_stab}", - }, - small_primitive_weapon: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d4", - weapon_encumbrance: "1", - weapon_shock_ac: "15", - weapon_shock_damage: "1", - weapon_skill_bonus: "@{skill_stab}", - }, - sniper_rifle: { - weapon_ammo: "1", - weapon_damage: "2d8", - weapon_encumbrance: "2", - weapon_range: "1000/2000", - }, - spike_thrower: { - weapon_ammo: "15", - weapon_burst: true, - weapon_damage: "3d8", - weapon_encumbrance: "2", - weapon_range: "20/40", - }, - stun_baton: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d8", - weapon_encumbrance: "1", - weapon_shock_ac: "15", - weapon_shock_damage: "1", - weapon_skill_bonus: "@{skill_stab}", - }, - submachine_gun: { - weapon_ammo: "20", - weapon_burst: true, - weapon_damage: "1d8", - weapon_encumbrance: "1", - weapon_range: "30/100", - }, - suit_ripper: { - weapon_attribute_mod: "@{str_dex_mod}", - weapon_damage: "1d6", - weapon_encumbrance: "1", - weapon_skill_bonus: "@{skill_stab}", - }, - thermal_pistol: { - weapon_attack: "1", - weapon_ammo: "5", - weapon_damage: "2d6", - weapon_encumbrance: "1", - weapon_range: "25/50", - }, - thunder_gun: { - weapon_attack: "1", - weapon_ammo: "6", - weapon_damage: "2d10", - weapon_encumbrance: "2", - weapon_range: "100/300", - }, - void_carbine: { - weapon_ammo: "10", - weapon_damage: "2d6", - weapon_encumbrance: "2", - weapon_range: "100/300", - }, - vortex_cannon: { - weapon_ammo: "5", - weapon_damage: "5d12", - weapon_range: "1000/2000", - }, - wheatcutter_belt: { - weapon_ammo: "5", - weapon_damage: "2d12", - weapon_range: "10/20", - }, - }, - }; - - /* Utility functions */ - const sign = (value) => { - const val = parseInt(value) || 0; - if (val >= 0) return `+${val}`; - else return `${val}`; - }; - const sum = (list) => list.reduce((m, c) => m + (parseInt(c) || 0), 0); - const buildLink = (caption, ability, last) => `[${caption}${!last ? "," : ""}](~${ability})`; - const mySetAttrs = (setting, values, ...rest) => { - // This is a version of setAttrs that expects an extra values parameter - // (as received from getAttrs). It will only set values in setting that differ - // from their current value on the sheet. The intention is to not - // set values unnecessarily (it's expensive) and to reduce bloat - // in the Attributes & Abilities tab. - Object.keys(setting).forEach(k => { - if (`${values[k]}` === `${setting[k]}`) delete setting[k]; - }); - setAttrs(setting, ...rest); - }; - const fillRepeatingSectionFromData = (sName, data, callback) => { - // Populates the repeating section repeating_${SName} with new - // rows from the data array. Every entry of the array is expected - // to be an object, and its key/value pairs will be written into - // the repeating section as a new row. If data is not an array - // but a single object, it will be treated like an array with - // a single element. - callback = callback || (() => {}); - const createdIDs = [], - getRowID = () => { - while (true) { - let newID = generateRowID(); - if (!createdIDs.includes(newID)) { - createdIDs.push(newID); - return newID; - } - } - }; - const setting = (Array.isArray(data) ? data : [data]).map(o => { - const newID = getRowID(); - return Object.entries(o).reduce((m, [key, value]) => { - m[`repeating_${sName}_${newID}_${key}`] = `${value}`; - return m; - }, {}); - }).reduce((m, o) => Object.assign(m, o), {}); - setAttrs(setting, {}, callback); - }; - - /* Calculations */ - const calculateSaves = () => { - getAttrs([...attributes.map(attr => `${attr}_mod`), "level", - "homebrew_luck_save", "save_physical", "save_mental", "save_evasion", "save_luck" - ], v => { - const base = 16 - (parseInt(v.level) || 1); - const setting = { - save_physical: base - (Math.max(parseInt(v.strength_mod), parseInt(v.constitution_mod)) || 0), - save_mental: base - (Math.max(parseInt(v.charisma_mod), parseInt(v.wisdom_mod)) || 0), - save_evasion: base - (Math.max(parseInt(v.intelligence_mod), parseInt(v.dexterity_mod)) || 0) - }; - if (v.homebrew_luck_save === "1") setting.save_luck = base; - mySetAttrs(setting, v); - }); - }; - - const calculateEffort = () => { - getSectionIDs("repeating_psychic-skills", idArray => { - getAttrs([...effortAttributes, - "psionics_total_effort", - ...idArray.map(id => `repeating_psychic-skills_${id}_skill`)], v => { - const attrBonus = Math.max(parseInt(v.wisdom_mod), parseInt(v.constitution_mod)) || 0, - skillBonus = Math.max( - ...skills.psionic.map(x => parseInt(v[`skill_${x}`]) || 0), - ...idArray.map(id => parseInt(v[`repeating_psychic-skills_${id}_skill`]) || 0) - ); - const psionics_total_effort = Math.max(1 + attrBonus + skillBonus, 1) + parseInt(v.psionics_extra_effort) - - parseInt(v.psionics_committed_effort_current) - parseInt(v.psionics_committed_effort_scene) - - parseInt(v.psionics_committed_effort_day); - mySetAttrs({ - psionics_total_effort - }, v); - }); - }); - }; - - const calculateMagicEffort = () => { - getAttrs(["magic_total_effort", "magic_committed_effort_current", "magic_committed_effort_scene", "magic_committed_effort_day", "magic_uncommitted_effort"], function(v) { - const magic_uncommitted_effort = parseInt(v.magic_total_effort) - (parseInt(v.magic_committed_effort_current) + parseInt(v.magic_committed_effort_scene) + parseInt(v.magic_committed_effort_day)); - mySetAttrs({ - magic_uncommitted_effort - }, v); - }); - }; - - const calculateAC = () => { - getSectionIDs("repeating_armor", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_armor_${id}_armor_ac`), - ...idArray.map(id => `repeating_armor_${id}_armor_active`), - ...idArray.map(id => `repeating_armor_${id}_armor_type`), - "npc", "AC", "innate_ac", "dexterity_mod", - ]; - getAttrs(sourceAttrs, v => { - if (v.npc === "1") return; - const baseAC = Math.max( - parseInt(v.innate_ac) || 0, - ...idArray.filter(id => v[`repeating_armor_${id}_armor_active`] === "1") - .filter(id => v[`repeating_armor_${id}_armor_type`] !== "SHIELD") - .map(id => parseInt(v[`repeating_armor_${id}_armor_ac`]) || 0) - ); - const shieldAC = Math.max( - 0, - ...idArray.filter(id => v[`repeating_armor_${id}_armor_active`] === "1") - .filter(id => v[`repeating_armor_${id}_armor_type`] === "SHIELD") - .map(id => parseInt(v[`repeating_armor_${id}_armor_ac`]) || 0) - ); - const AC = (shieldAC > 0 ? shieldAC <= baseAC ? (baseAC + 1) : shieldAC : baseAC) + - (parseInt(v.dexterity_mod) || 0); - - mySetAttrs({ - AC - }, v); - }); - }); - }; - - const calculateMaxStrain = () => { - getAttrs(["constitution", "strain_max"], v => { - mySetAttrs({ - strain_max: parseInt(v.constitution) - }, v); - }); - }; - - const calculatePermanentStrain = () => { - getAttrs(["cyberware_strain_total", "strain_permanent_extra", "strain_permanent"], v => { - const permStrain = parseInt(v.cyberware_strain_total) + parseInt(v.strain_permanent_extra); - mySetAttrs({ - strain_permanent: permStrain - }, v); - }); - }; - - const calculateCyberwareStrain = () => { - //Loop through the cyberware and add up the strain. - getSectionIDs("repeating_cyberware", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_cyberware_${id}_cyberware_strain`), - "cyberware_strain_total" - ]; - getAttrs(sourceAttrs, v => { - const cyberwareStrain = idArray.reduce((m, id) => { - m += parseInt(v[`repeating_cyberware_${id}_cyberware_strain`]) || 0; - return m - }, 0) - mySetAttrs({ - cyberware_strain_total: cyberwareStrain - }, v); - }); - }); - }; - - const calculateAttr = (attr) => { - getAttrs([attr, `${attr}_base`, `${attr}_boosts`], v => { - const setting = { - [`${attr}`]: `${(parseInt(v[`${attr}_base`]) || 10) + (parseInt(v[`${attr}_boosts`]) || 0)}` - }; - mySetAttrs(setting, v, null, () => { - calculateMod(attr); - }); - }); - } - - const calculateMod = (attr) => { - getAttrs([attr, `${attr}_bonus`, `${attr}_mod`], v => { - const mod = (value => { - if (value >= 18) return 2; - else if (value >= 14) return 1; - else if (value >= 8) return 0; - else if (value >= 4) return -1; - else return -2; - })(parseInt(v[attr]) || 10); - - const setting = { - [`${attr}_mod`]: `${mod + (parseInt(v[`${attr}_bonus`]) || 0)}` - }; - - mySetAttrs(setting, v, null, () => { - calculateSaves(); - generateWeaponDisplay(); - calculateStrDexMod(); - if (attr === "dexterity") calculateAC(); - }); - }); - }; - const calculateStrDexMod = () => { - getAttrs(["str_dex_mod", "strength_mod", "dexterity_mod"], v => { - const str_dex_mod = Math.max(parseInt(v.strength_mod), parseInt(v.dexterity_mod)); - mySetAttrs({ - str_dex_mod - }, v); - }); - }; - - const calculateNextLevelXP = () => { - const xp = [0, 3, 6, 12, 18, 27, 39, 54, 72, 93]; - getAttrs(["level", "setting_xp_scheme"], v => { - if (v.setting_xp_scheme === "xp") { - if (v.level < 10) { - setAttrs({ - xp_next: xp[v.level] - }); - } else { - setAttrs({ - xp_next: 93 + ((v.level - 9) * 24) - }); - } - } else if (v.setting_xp_scheme === "money") { - setAttrs({ - xp_next: 2500 * (2 * v.level) - }); - } - }); - }; - - const calculateGearReadiedStowed = () => { - const doCalc = (gearIDs, weaponIDs, armorIDs) => { - const attrs = [ - ...gearIDs.map(id => `repeating_gear_${id}_gear_amount`), - ...gearIDs.map(id => `repeating_gear_${id}_gear_encumbrance`), - ...gearIDs.map(id => `repeating_gear_${id}_gear_status`), - ...gearIDs.map(id => `repeating_gear_${id}_gear_bundled`), - ...armorIDs.map(id => `repeating_armor_${id}_armor_encumbrance`), - ...armorIDs.map(id => `repeating_armor_${id}_armor_encumbrance_bonus`), - ...armorIDs.map(id => `repeating_armor_${id}_armor_status`), - ...weaponIDs.map(id => `repeating_weapons_${id}_weapon_encumbrance`), - ...weaponIDs.map(id => `repeating_weapons_${id}_weapon_status`), - "gear_readied", "gear_stowed", "strength", "gear_readied_max", - "gear_readied_over", "gear_stowed_over", "gear_stowed_max" - ]; - getAttrs(attrs, v => { - const [gear_readied, gear_stowed] = armorIDs.reduce((m, id) => { - if (v[`repeating_armor_${id}_armor_status`] === "READIED") { - m[0] += parseInt(v[`repeating_armor_${id}_armor_encumbrance`]) || 0; - } else if (v[`repeating_armor_${id}_armor_status`] === "STOWED") - m[1] += parseInt(v[`repeating_armor_${id}_armor_encumbrance`]) || 0; - return m; - }, weaponIDs.reduce((m, id) => { - if (v[`repeating_weapons_${id}_weapon_status`] === "READIED") - m[0] += parseInt(v[`repeating_weapons_${id}_weapon_encumbrance`]) || 0; - else if (v[`repeating_weapons_${id}_weapon_status`] === "STOWED") - m[1] += parseInt(v[`repeating_weapons_${id}_weapon_encumbrance`]) || 0; - return m; - }, gearIDs.reduce((m, id) => { - const amount = parseInt(v[`repeating_gear_${id}_gear_amount`]) || 0; - let packingFactor = 1; - if (v[`repeating_gear_${id}_gear_bundled`] === "on") { - packingFactor = 3; - } - if (v[`repeating_gear_${id}_gear_status`] === "READIED") - m[0] += Math.ceil((amount * parseFloat(v[`repeating_gear_${id}_gear_encumbrance`]))/packingFactor) || 0; - else if (v[`repeating_gear_${id}_gear_status`] === "STOWED") - m[1] += Math.ceil((amount * parseFloat(v[`repeating_gear_${id}_gear_encumbrance`]))/packingFactor) || 0; - return m; - }, [0, 0]))); - - const armor_encumbrance_bonus = Math.max(0, - ...armorIDs.filter(id => v[`repeating_armor_${id}_armor_status`] === "READIED") - .map(id => parseInt(v[`repeating_armor_${id}_armor_encumbrance_bonus`]) || 0) - ); - const gear_stowed_max = parseInt(v.strength) + armor_encumbrance_bonus; - const gear_readied_max = Math.floor(gear_stowed_max / 2); - const setting = { - gear_readied, - gear_stowed, - gear_readied_max, - gear_stowed_max, - gear_readied_over: (parseInt(v.gear_readied) > gear_readied_max) ? "1" : "0", - gear_stowed_over: (parseInt(v.gear_stowed) > gear_stowed_max) ? "1" : "0", - }; - - mySetAttrs(setting, v, { - silent: true - }); - }); - }; - - getSectionIDs("repeating_gear", gearIDs => { - getSectionIDs("repeating_weapons", weaponIDs => { - getSectionIDs("repeating_armor", armorIDs => doCalc(gearIDs, weaponIDs, armorIDs)); - }); - }); - }; - - const generateWeaponDisplay = () => { - // Generates the weapon menu and sets the display of weapons - // in display mode in one go. - getSectionIDs("repeating_weapons", idArray => { - const prefixes = idArray.map(id => `repeating_weapons_${id}`); - const sourceAttrs = [ - ...prefixes.map(prefix => `${prefix}_weapon_attack`), - ...prefixes.map(prefix => `${prefix}_weapon_name`), - ...prefixes.map(prefix => `${prefix}_weapon_skill_bonus`), - ...prefixes.map(prefix => `${prefix}_weapon_attribute_mod`), - ...prefixes.map(prefix => `${prefix}_weapon_damage`), - ...prefixes.map(prefix => `${prefix}_weapon_shock`), - ...prefixes.map(prefix => `${prefix}_weapon_shock_damage`), - ...prefixes.map(prefix => `${prefix}_weapon_shock_ac`), - ...prefixes.map(prefix => `${prefix}_weapon_skill_to_damage`), - ...prefixes.map(prefix => `${prefix}_weapon_attack_display`), - ...prefixes.map(prefix => `${prefix}_weapon_damage_display`), - ...attributes.map(attr => `${attr}_mod`), - ...weaponSkills, - "attack_bonus", - "str_dex_mod", - "macro_weapons" - ]; - getAttrs(sourceAttrs, v => { - const setting = {}; - const baseAttackBonus = parseInt(v.attack_bonus) || 0; - prefixes.forEach(prefix => { - const attrBonus = parseInt(v[(v[`${prefix}_weapon_attribute_mod`] || "").slice(2, -1)]) || 0; - const skillBonus = parseInt(v[(v[`${prefix}_weapon_skill_bonus`] || "").slice(2, -1)]) || - parseInt(v[`${prefix}_weapon_skill_bonus`]) || 0; - const damageBonus = attrBonus + - ((v[`${prefix}_weapon_skill_to_damage`] === "@{weapon_skill_bonus}") ? skillBonus : 0); - const weaponDamage = (v[`${prefix}_weapon_damage`] === "0") ? "" : v[`${prefix}_weapon_damage`]; - const shockString = (v[`${prefix}_weapon_shock`] !== "0") ? `, ${ - (parseInt(v[`${prefix}_weapon_shock_damage`])||0) + damageBonus - }\xa0${translate("SHOCK").toLowerCase()}${ - v[`${prefix}_weapon_shock_ac`] ? ` ${translate("VS_AC_LEQ")} ${v[`${prefix}_weapon_shock_ac`]}` : "" - }` : ""; - - const attack = baseAttackBonus + (parseInt(v[`${prefix}_weapon_attack`]) || 0) + - ((skillBonus === -1) ? -2 : skillBonus) + attrBonus; - const damage = weaponDamage + (weaponDamage ? - ((damageBonus === 0) ? "" : ((damageBonus > 0) ? ` + ${damageBonus}` : ` - ${-damageBonus}`)) : - damageBonus); - - setting[`${prefix}_weapon_attack_display`] = (attack >= 0) ? `+${attack}` : attack; - setting[`${prefix}_weapon_damage_display`] = `${damage || 0}\xa0${translate("DAMAGE").toLowerCase()}${shockString}`; - }); - setting.macro_weapons = prefixes.map((prefix, index) => { - const label = `${v[`${prefix}_weapon_name`]} (${setting[`${prefix}_weapon_attack_display`]})`; - return buildLink(label, `${prefix}_attack`, index === prefixes.length - 1); - }).join(" "); - mySetAttrs(setting, v, { - silent: true - }); - }); - }); - }; - - const handleAmmoAPI = (sName) => { - const formula = (sName === "weapons") ? "[[-1 - @{weapon_burst}]]" : "-1"; - getSectionIDs(`repeating_${sName}`, idArray => { - getAttrs([ - "setting_use_ammo", - ...idArray.map(id => `repeating_${sName}_${id}_weapon_use_ammo`), - ...idArray.map(id => `repeating_${sName}_${id}_weapon_api`) - ], v => { - const setting = idArray.reduce((m, id) => { - m[`repeating_${sName}_${id}_weapon_api`] = - (v.setting_use_ammo === "1" && v[`repeating_${sName}_${id}_weapon_use_ammo`] !== "0") ? - `\n!modattr --mute --charid @{character_id} --repeating_${sName}_${id}_weapon_ammo|${formula}` : - ""; - return m; - }, {}); - mySetAttrs(setting, v, { - silent: true - }); - }); - }); - }; - - /** - * Validations - */ - const validateTab = () => { - getAttrs(["tab", "npc"], v => { - if (v.tab === "character" && v.npc === "1") setAttrs({ - tab: "npc" - }); - if (v.tab === "npc" && v.npc === "0") setAttrs({ - tab: "character" - }); - }); - }; - const validateSuperTab = () => { - getAttrs(["setting_super_type", "tab_super"], v => { - const setting = {}; - if (v.setting_super_type === "magic") setting.tab_super = "magic"; - if (v.setting_super_type === "psionics") setting.tab_super = "psionics"; - mySetAttrs(setting, v); - }); - }; - const validateStrain = () => { - getAttrs(["strain", "strain_permanent", "strain_max"], v => { - const currentStrain = parseInt(v.strain) || 0, - permanentStrain = parseInt(v.strain_permanent) || 0, - strain = Math.min(parseInt(v.strain_max), Math.max(currentStrain, permanentStrain)); - - if (strain !== currentStrain) setAttrs({ - strain - }); - }); - }; - const validateWeaponSkills = (ids) => { - // Makes sure that the select for the weapon skill is never in an invalid state. - const prefixes = (ids && ids.map(id => `repeating_weapons_${id}`)) || ["repeating_weapons"]; - getAttrs(["homebrew_skill_list", ...prefixes.map(p => `${p}_weapon_skill_bonus`)], v => { - const revisedList = ["@{skill_exert}", "@{skill_punch}", "@{skill_shoot}", "@{skill_stab}", - "@{skill_telekinesis}", "0" - ], - firstList = ["@{skill_combat_energy}", "@{skill_combat_gunnery}", "@{skill_combat_primitive}", "@{skill_combat_projectile}", - "@{skill_combat_psitech}", "@{skill_combat_unarmed}", "@{skill_telekinesis}", "0" - ], - type = v.homebrew_skill_list, - setting = {}; - prefixes.forEach(prefix => { - if (type === "revised" && !revisedList.includes(v[`${prefix}_weapon_skill_bonus`])) - setting[`${prefix}_weapon_skill_bonus`] = "@{skill_shoot}"; - if (type === "first" && !firstList.includes(v[`${prefix}_weapon_skill_bonus`])) - setting[`${prefix}_weapon_skill_bonus`] = "@{skill_combat_energy}"; - }); - setAttrs(setting); - }); - }; - - /* NPC */ - const fillNPC = () => { - getAttrs(["npc_stat_block"], (v) => { - if (v.npc_stat_block && autofillData.statblocks[v.npc_stat_block]) { - const [ - HD, AC, npc_attack_bonus, damage, attacks, npc_move, - npc_morale, npc_skills, npc_saves, armor_type - ] = autofillData.statblocks[v.npc_stat_block]; - - const setting = { - AC, - npc_attack_bonus, - npc_move, - npc_morale, - npc_skills, - npc_saves - }; - - if (armor_type) setting.npc_armor_type = armor_type; - if (HD.includes("hp")) setting.HP = HD.replace("hp", ""); - else setting.npc_hd = HD; - - setAttrs(setting); - - if (damage !== "Unarmed") { - const newAttack = { - attack_ab: npc_attack_bonus, - attack_damage: damage, - attack_name: translate("ATTACK"), - attack_number: attacks - }; - fillRepeatingSectionFromData("npc-attacks", newAttack); - } - } - }); - }; - const addNPCAttackBonus = () => { - getAttrs(["repeating_npc-attacks_attack_ab", "npc_attack_bonus"], v => { - if (`${v["repeating_npc-attacks_attack_ab"]}` === "0") { - setAttrs({ - ["repeating_npc-attacks_attack_ab"]: v.npc_attack_bonus - }); - } - }); - }; - const setNPCMultiAttacks = () => { - getSectionIDs("repeating_npc-attacks", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_npc-attacks_${id}_attack_number`), - "npc_roll_full_attack" - ]; - getAttrs(sourceAttrs, v => { - const setting = idArray.reduce((m, id) => { - if (v.npc_roll_full_attack === "1") { - const num = parseInt(v[`repeating_npc-attacks_${id}_attack_number`]) || 1; - m[`repeating_npc-attacks_${id}_attack_extra_macro`] = [2, 3, 4, 5, 6, 7, 8].map(n => { - if (n <= num) - return `{{attack${n}=[[1d20 + @{attack_ab} @{attack_burst} @{modifier_query}]]}} ` + - `{{damage${n}=[[@{attack_damage} @{attack_burst}]]}} `; - else return ""; - }).join(""); - } else { - m[`repeating_npc-attacks_${id}_attack_extra_macro`] = ""; - } - return m; - }, {}); - setAttrs(setting); - }); - }); - }; - const handleNPCRollHide = () => { - const types = ["hp", "initiative", "save", "skill", "morale", "reaction"]; - getAttrs(["npc_rolls_hidden", ...types.map(x => `npc_${x}_hidden`)], v => { - const setting = types.reduce((m, n) => { - m[`npc_${n}_hidden`] = v.npc_rolls_hidden; - return m; - }, {}); - mySetAttrs(setting, v); - }); - }; - - /* Menu builders */ - /* Helper functions for producing skill chat menus */ - const skillToMacro = v => skill => { - const namedSkills = [ - "culture_one", "culture_two", "culture_three", - "culture_alien", "profession", "magic2" - ]; - if (namedSkills.includes(skill)) - return [`@{skill_${skill}_name} (${sign(v[`skill_${skill}`])})`, `skill_${skill}`]; - else - return [`^{${skill.toUpperCase()}} (${sign(v[`skill_${skill}`])})`, `skill_${skill}`]; - }; - const idToSkillMacro = (v, sName) => id => { - const prefix = `repeating_${sName}_${id}_skill`; - return [`${v[`${prefix}_name`]} (${sign(v[prefix])})`, prefix]; - }; - const buildSkillMacro = ([name, command], index, allSkills) => { - return buildLink(name, command, index + 1 === allSkills.length); - }; - /* End helper functions */ - const buildSaveMenu = () => { - getAttrs(["homebrew_luck_save", "macro_saves"], v => { - const macro_saves = buildLink("^{PHYSICAL} (v@{save_physical})", ("save_physical")) + " " + - buildLink("^{MENTAL} (v@{save_mental})", ("save_mental")) + " " + - buildLink("^{EVASION} (v@{save_evasion})", ("save_evasion"), v.homebrew_luck_save !== "1") + - ((v.homebrew_luck_save === "1") ? - (" " + buildLink("^{LUCK} (v@{save_luck})", ("save_luck"), true)) : ""); - mySetAttrs({ - macro_saves - }, v); - }); - }; - const buildSkillMenu = () => { - getSectionIDs("repeating_skills", idArray => { - const sourceAttrs = [ - ...skills.revised.map(sk => `skill_${sk}`), - ...skills.first.map(sk => `skill_${sk}`), - "homebrew_skill_list", - "setting_show_untrained_skills", - "macro_skills", - ...idArray.map(id => `repeating_skills_${id}_skill_name`), - ...idArray.map(id => `repeating_skills_${id}_skill`), - ]; - getAttrs(sourceAttrs, v => { - const skillList = skills[v.homebrew_skill_list] || []; - const hasSkills = skillList.filter(skill => v[`skill_${skill}`] !== "-1").length || - idArray.filter(id => v[`repeating_skills_${id}_skill`] !== "-1").length; - - const macro_skills = `${[ - ...skillList.filter(skill => v[`skill_${skill}`] !== "-1").map(skillToMacro(v)), - ...idArray.filter(id => v[`repeating_skills_${id}_skill`] !== "-1").map(idToSkillMacro(v, "skills")) - ].map(buildSkillMacro).join(" ")}${v.setting_show_untrained_skills === "1" ? `${ - hasSkills ? "\n\n**^{UNTRAINED}**\n" : ""}${[ - ...skillList.filter(skill => v[`skill_${skill}`] === "-1").map(skillToMacro(v)), - ...idArray.filter(id => v[`repeating_skills_${id}_skill`] === "-1").map(idToSkillMacro(v, "skills")) - ].map(buildSkillMacro).join(" ")}` : ""}`; - mySetAttrs({ - macro_skills - }, v); - }); - }); - }; - const buildPsionicsMenu = () => { - getSectionIDs("repeating_techniques", techniqueIDs => { - getSectionIDs("repeating_psychic-skills", skillIDs => { - const sourceAttrs = [ - ...skills.psionic.map(sk => `skill_${sk}`), - "setting_super_type", - "macro_psionics", - ...skillIDs.map(id => `repeating_psychic-skills_${id}_skill_name`), - ...skillIDs.map(id => `repeating_psychic-skills_${id}_skill`), - ...techniqueIDs.map(id => `repeating_techniques_${id}_technique_name`), - ]; - getAttrs(sourceAttrs, v => { - if (v.setting_super_type === "magic" || v.setting_super_type === "neither") return; - const macro_psionics = `${[ - ...skills.psionic.filter(skill => v[`skill_${skill}`] !== "-1").map(skillToMacro(v)), - ...skillIDs.filter(id => v[`repeating_psychic-skills_${id}_skill`] !== "-1") - .map(idToSkillMacro(v, "psychic-skills")) - ].map(buildSkillMacro).join(" ")}${(techniqueIDs.length ? "\n\n" : "")}${ - techniqueIDs.map((id, index, allIDs) => buildLink(v[`repeating_techniques_${id}_technique_name`], - `repeating_techniques_${id}_technique`, index + 1 === allIDs.length)).join(" ")}`; - mySetAttrs({ - macro_psionics - }, v); - }); - }); - }); - }; - const buildMagicMenu = () => { - getSectionIDs("repeating_spells", spellIDs => { - getSectionIDs("repeating_magic-skills", skillIDs => { - const sourceAttrs = [ - "skill_magic", "skill_magic2", "skill_magic2_name", - "setting_super_type", "macro_magic", - ...skillIDs.map(id => `repeating_magic-skills_${id}_skill_name`), - ...skillIDs.map(id => `repeating_magic-skills_${id}_skill`), - ...spellIDs.map(id => `repeating_spells_${id}_spell_name`), - ]; - getAttrs(sourceAttrs, v => { - if (v.setting_super_type === "psionics" || v.setting_super_type === "neither") return; - const macro_magic = `${[ - ...(v.skill_magic2_name ? ["magic", "magic2"] : ["magic"]).map(skillToMacro(v)), - ...skillIDs.map(idToSkillMacro(v, "magic-skills")) - ].map(buildSkillMacro).join(" ")}${spellIDs.length ? "\n\n" : ""}${ - spellIDs.map(id => buildLink(v[`repeating_spells_${id}_spell_name`], `repeating_spells_${id}_spell`)) - .join(" ")}`; - mySetAttrs({ - macro_magic - }, v); - }); - }); - }); - }; - const buildShipWeaponsMenu = () => { - getSectionIDs("repeating_ship-weapons", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_ship-weapons_${id}_weapon_name`), - ...idArray.map(id => `repeating_ship-weapons_${id}_weapon_attack_bonus`), - "macro_ship_weapons" - ]; - getAttrs(sourceAttrs, v => { - const macro_ship_weapons = idArray.map((id, index) => { - const title = v[`repeating_ship-weapons_${id}_weapon_name`] + - ` ${sign(v[`repeating_ship-weapons_${id}_weapon_attack_bonus`])}`; - return buildLink(title, `repeating_ship-weapons_${id}_attack`, index + 1 === idArray.length); - }).join(" "); - mySetAttrs({ - macro_ship_weapons - }, v); - }); - }); - }; - const buildAttacksMenu = () => { - getSectionIDs("repeating_npc-attacks", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_npc-attacks_${id}_attack_name`), - ...idArray.map(id => `repeating_npc-attacks_${id}_attack_ab`), - ...idArray.map(id => `repeating_npc-attacks_${id}_attack_number`), - "macro_npc_attacks" - ]; - getAttrs(sourceAttrs, v => { - const macro_npc_attacks = idArray.map((id, index) => { - const title = v[`repeating_npc-attacks_${id}_attack_name`] + - ` ${sign(v[`repeating_npc-attacks_${id}_attack_ab`])}` + - ((v[`repeating_npc-attacks_${id}_attack_number`] !== "1") ? - ` (${v[`repeating_npc-attacks_${id}_attack_number`]} attacks)` : ""); - return buildLink(title, `repeating_npc-attacks_${id}_attack`, index + 1 === idArray.length); - }).join(" "); - mySetAttrs({ - macro_npc_attacks - }, v); - }); - }); - }; - const buildAbilitiesMenu = () => { - getSectionIDs("repeating_npc-abilities", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_npc-abilities_${id}_ability_name`), - "macro_npc_abilities" - ]; - getAttrs(sourceAttrs, v => { - const macro_npc_abilities = idArray.map((id, index) => { - return buildLink(v[`repeating_npc-abilities_${id}_ability_name`], - `repeating_npc-abilities_${id}_ability`, index + 1 === idArray.length); - }).join(" "); - mySetAttrs({ - macro_npc_abilities - }, v); - }); - }); - }; - const buildStatblock = () => { - const sourceAttrs = [ - "npc", - "macro_npc_abilities", - "macro_npc_attacks", - "macro_statblock", - ]; - getAttrs(sourceAttrs, v => { - if (v.npc !== "1") return; - const macroList = [ - "[**^{SAVES}** v@{npc_saves},](~npc_save) [**^{SKILLS}** +@{npc_skills},](~npc_skill) ", - "[**^{MORALE}** v@{npc_morale}](~npc_morale)\n", - "[**^{INITIATIVE_FIXED}** d8,](~npc_initiative) [**^{REACTION}** 2d6,](~npc_reaction) ", - "**Move** @{npc_move}\n" - ]; - if (v.macro_npc_attacks) macroList.push("\n**Attacks:** @{macro_npc_attacks}"); - if (v.macro_npc_abilities) macroList.push("\n**Abilities:** @{macro_npc_abilities}"); - mySetAttrs({ - macro_statblock: macroList.join("") - }, v); - }); - }; - - /* Autofill stuff */ - const fillClassStats = () => { - getAttrs(["class", "class_ability", "attack_bonus"], v => { - const label = v.class && reverseClasses[v.class.toLowerCase()]; - if (label && autofillData.classes.hasOwnProperty(label)) { - const data = Object.assign({}, autofillData.classes[label]); - Object.keys(data).forEach(key => { - if (!(["", "0"].includes(`${v[key]}`))) delete data[key]; - }); - mySetAttrs(data, v); - } - }); - }; - const getShipMultiplier = (shipClass) => { - if ((shipClass || "").toLowerCase() === "frigate") return 2; - else if ((shipClass || "").toLowerCase() === "cruiser") return 3; - else if ((shipClass || "").toLowerCase() === "capital") return 4; - else return 1; - }; - const getShipPriceMultiplier = (shipClass) => { - if ((shipClass || "").toLowerCase() === "frigate") return 10; - else if ((shipClass || "").toLowerCase() === "cruiser") return 25; - else if ((shipClass || "").toLowerCase() === "capital") return 100; - else return 1; - } - const getAutofillData = (sName, v, data, label) => { - // Transforms the stored data to be suitable for - // inclusion into the sheet. - const output = Object.assign({}, data); - if (sName === "ship-defenses") { - if (label) output.defense_name = translate(label.toUpperCase()); - if (output.defense_mass.includes("#")) { - output.defense_mass = `${parseInt(output.defense_mass) * v.ship_multiplier}`; - } - } - if (sName === "ship-fittings") { - if (label) { - output.fitting_name = translate(label.toUpperCase()); - output.fitting_effect = translate(`${label.toUpperCase()}_DESC`); - } - if (output.fitting_mass.includes("#")) { - output.fitting_mass = `${Math.round(parseFloat(output.fitting_mass) * v.ship_multiplier)}`; - } - if (output.fitting_power.includes("#")) { - output.fitting_power = `${parseInt(output.fitting_power) * v.ship_multiplier}`; - } - } - if (sName === "ship-weapons") { - if (label) output.weapon_name = translate(label.toUpperCase()); - if (output.weapon_ammo) { - output.weapon_ammo_max = output.weapon_ammo; - output.weapon_use_ammo = "{{ammo=[[@{weapon_ammo} - 1]] / @{weapon_ammo_max}}}"; - } - } - if (sName === "weapons") { - if (label) { - output.weapon_name = translate(label.toUpperCase()); - output.weapon_description = translate(`${label.toUpperCase()}_DESC`); - } - if (output.weapon_ammo) { - output.weapon_ammo_max = output.weapon_ammo; - output.weapon_use_ammo = "{{ammo=[[0@{weapon_ammo} - (1 @{weapon_burst})]] / @{weapon_ammo|max}}}"; - } - if (output.weapon_shock_damage) { - output.weapon_shock = "{{shock=[[@{weapon_shock_damage} + @{weapon_attribute_mod}[Attribute] + @{weapon_skill_to_damage}[Skill]]] ^{SHOCK_DAMAGE_AGAINST_AC_LEQ} @{weapon_shock_ac}!}}"; - } - if (output.weapon_burst) { - output.weapon_burst = "@{burst_query}"; - } - if (!output.weapon_attribute_mod) { - output.weapon_attribute_mod = "@{dexterity_mod}"; - } - } - if (sName === "armor") { - if (label) { - output.armor_name = translate(label.toUpperCase()); - output.armor_description = translate(`${label.toUpperCase()}_DESC`) || ""; - } - } - if (sName === "cyberware") { - if (label) { - output.cyberware_name = translate(label.toUpperCase()); - output.cyberware_description = translate(`${label.toUpperCase()}_DESC`); - } - } - if (sName === "foci") { - if (label) { - output.focus_name = translate(label.toUpperCase()); - output.focus_level = "1"; - output.focus_description = translate(`${label.toUpperCase()}_DESC`); - } - } - if (sName === "techniques") { - if (label) { - output.technique_name = translate(label.toUpperCase()); - output.technique_description = translate(`${label.toUpperCase()}_DESC`); - } - } - if (sName === "gear") { - if (label) { - output.gear_name = translate(label.toUpperCase()); - output.gear_description = translate(`${label.toUpperCase()}_DESC`); - } - if (output.gear_encumbrance === "1#") { - output.gear_encumbrance = "1"; - output.gear_bundled = "on"; - } - } - return output; - }; - const getAutofillInfo = (sName, v, inputData, label) => { - const data = getAutofillData(sName, v, inputData, label); - // Generates info text from the stored data - if (sName === "ship-defenses") { - return `${translate(data.class)}+. ${translate("POWER_INIT")}: ${data.defense_power}, ${ - translate("MASS_INIT")}: ${data.defense_mass}. ${data.defense_effect}`; - } - if (sName === "ship-fittings") { - return `${translate(data.class)}+. ${translate("POWER_INIT")}: ${data.fitting_power}, ${ - translate("MASS_INIT")}: ${data.fitting_mass}. ${data.fitting_effect}`; - } - if (sName === "ship-weapons") { - return `${translate(data.class)}+. ${ - translate("POWER_INIT")}: ${data.weapon_power}, ${ - translate("MASS_INIT")}: ${data.weapon_mass}, ${ - translate("HARDPOINTS_INIT")}: ${data.weapon_hardpoints}. ${ - translate("DAMAGE_SHORT")} ${data.weapon_damage}. ${ - data.weapon_qualities}${ - (data.weapon_ammo ? `, ${translate("AMMO")}: ${data.weapon_ammo}`: "")}.`; - } - if (sName === "weapons") { - const getNamedAttrMod = expr => { - if (expr === "@{dexterity_mod}") return translate("DEXTERITY_SHORT"); - else if (expr === "@{strength_mod}") return translate("STRENGTH_SHORT"); - else if (expr === "@{str_dex_mod}") return translate("STR_DEX"); - }; - return `${translate("DAMAGE_SHORT")} ${data.weapon_damage}${data.weapon_burst ? ` (${translate("BURST")})` : ""}${ - data.weapon_ab ? `, ${translate("ATTACK_BONUS_SHORT")} +${data.weapon_ab}` : ""}${ - data.weapon_range ? `, ${translate("RANGE")} ${data.weapon_range}` : ""}${ - data.weapon_ammo ? `, ${translate("AMMO")} ${data.weapon_ammo}` : ""}${ - data.weapon_shock ? `, ${data.weapon_shock_damage} ${translate("SHOCK_DAMAGE_AGAINST_AC_LEQ")} ${data.weapon_shock_ac}` : ""}, +${getNamedAttrMod(data.weapon_attribute_mod)}${ - data.weapon_encumbrance ? `, ${translate("ENCUMBRANCE_SHORT")} ${data.weapon_encumbrance}` : ""}.`; - } - if (sName === "armor") { - return `${translate("AC")} ${data.armor_ac}, ${translate(data.armor_type) - }, ${translate("ENCUMBRANCE_SHORT")} ${data.armor_encumbrance}.`; - } - if (sName === "cyberware") { - return `${translate("STRAIN")}: ${data.cyberware_strain}.`; - } - if (sName === "techniques") { - if (data.level === "0") return `${translate("CORE_TECHNIQUE")}.`; - else return `${translate("LEVEL")}-${data.level}.`; - } - if (sName === "gear") { - if (data.gear_bundled === "on") { - return `${translate("ENCUMBRANCE_SHORT")} ${data.gear_encumbrance}#.`; - } - return `${translate("ENCUMBRANCE_SHORT")} ${data.gear_encumbrance}.`; - } - return ""; - }; - const generateAutofillRow = (sName) => { - // Event handler for generating a new row when button is pressed - getAttrs([`generate_${sName}_source`, "ship_class"], v => { - const label = v[`generate_${sName}_source`]; - v.ship_multiplier = getShipMultiplier(v.ship_class); - if (label && autofillData[sName].hasOwnProperty(label)) { - const data = getAutofillData(sName, v, autofillData[sName][label], label); - delete data.class; - delete data.level; - fillRepeatingSectionFromData(sName, data); - } - }); - }; - const generateAutofillInfo = (sName) => { - // Event handler for showing info about the selected item - getAttrs([`generate_${sName}_source`, "ship_class"], v => { - const label = v[`generate_${sName}_source`]; - v.ship_multiplier = getShipMultiplier(v.ship_class); - if (label && autofillData[sName].hasOwnProperty(label)) { - const info = getAutofillInfo(sName, v, autofillData[sName][label], label); - if (info) setAttrs({ - [`generate_${sName}_info`]: info - }); - } else setAttrs({ - [`generate_${sName}_info`]: " " - }); - }); - }; - - /* Translations */ - const setTranslatedDefaults = () => { - const specialSkills = { - skill_culture_alien_name: { - trans: `${translate("CULTURE_ALIEN")}/`, - default: "Culture/Alien/" - }, - skill_culture_one_name: { - trans: `${translate("CULTURE")}/`, - default: "Culture/" - }, - skill_culture_two_name: { - trans: `${translate("CULTURE")}/`, - default: "Culture/" - }, - skill_culture_three_name: { - trans: `${translate("CULTURE")}/`, - default: "Culture/" - }, - skill_profession_name: { - trans: `${translate("PROFESSION")}/`, - default: "Profession/" - }, - }; - getAttrs([...Object.keys(specialSkills), "homebrew_skill_list", "innate_ac_name"], v => { - const setting = {}; - if (v.homebrew_skill_list === "first") { - Object.entries(specialSkills).forEach(([name, data]) => { - if (v[name] === data.default && v[name] !== data.trans) setting[name] = data.trans; - }); - } - setting.innate_ac_name = translate("INNATE_AC"); - mySetAttrs(setting, v); - }); - }; - const handleAttributeQueries = () => { - // Attribute query magic to set the global query variables according to - // translations and the state of the setting_skill_query attribute. - const attrQueries = attributes.map(attr => { - const translated = translate(attr.toUpperCase()); - return `${translated},+ @{${attr}_mod}[${translated}]]]}} ` + - `{{attribute= + ${translate(`${attr.toUpperCase()}_SHORT`)}}}`; - }); - - getAttrs(["attribute_query", "setting_skill_query", - ...attributes.map(a => `attribute_query_${a.slice(0,3)}`) - ], v => { - - if (v.setting_skill_query === "hover" || v.setting_skill_query === "hide") { - mySetAttrs({ - attribute_query: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, - attribute_query_str: `+ @{strength_mod}[${translate("STRENGTH")}]]]}} {{attribute= + ${translate("STRENGTH_SHORT")}}}`, - attribute_query_dex: `+ @{dexterity_mod}[${translate("DEXTERITY")}]]]}} {{attribute= + ${translate("DEXTERITY_SHORT")}}}`, - attribute_query_con: `+ @{constitution_mod}[${translate("CONSTITUTION")}]]]}} {{attribute= + ${translate("CONSTITUTION_SHORT")}}}`, - attribute_query_int: `+ @{intelligence_mod}[${translate("INTELLIGENCE")}]]]}} {{attribute= + ${translate("INTELLIGENCE_SHORT")}}}`, - attribute_query_wis: `+ @{wisdom_mod}[${translate("WISDOM")}]]]}} {{attribute= + ${translate("WISDOM_SHORT")}}}`, - attribute_query_cha: `+ @{charisma_mod}[${translate("CHARISMA")}]]]}} {{attribute= + ${translate("CHARISMA_SHORT")}}}`, - }, v); - } else if (v.setting_skill_query === "query") { - mySetAttrs({ - attribute_query: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, - attribute_query_str: `?{${translate("ATTRIBUTE")}|${[attrQueries[0], ...attrQueries.slice(1)].join("|")}}`, - attribute_query_dex: `?{${translate("ATTRIBUTE")}|${[attrQueries[1], attrQueries[0], ...attrQueries.slice(2)].join("|")}}`, - attribute_query_con: `?{${translate("ATTRIBUTE")}|${[attrQueries[2], ...attrQueries.slice(0,2), ...attrQueries.slice(3)].join("|")}}`, - attribute_query_int: `?{${translate("ATTRIBUTE")}|${[attrQueries[3], ...attrQueries.slice(0,3), ...attrQueries.slice(4)].join("|")}}`, - attribute_query_wis: `?{${translate("ATTRIBUTE")}|${[attrQueries[4], ...attrQueries.slice(0,4), ...attrQueries.slice(5)].join("|")}}`, - attribute_query_cha: `?{${translate("ATTRIBUTE")}|${[attrQueries[5], ...attrQueries.slice(0,5)].join("|")}}`, - }, v); - } - }); - }; - const handleModifierQuery = () => { - getAttrs(["modifier_query", "setting_modifier_query"], v => { - if (`${v.setting_modifier_query}` === "1") { - mySetAttrs({ - modifier_query: `+ ?{${translate("MODIFIER")}|0}[${translate("MODIFIER_SHORT")}]`, - }, v); - } else mySetAttrs({ - modifier_query: " " - }, v); - }); - }; - const setTranslatedQueries = () => { - getAttrs(["burst_query", "extra_hp_query", "translation_numdice", "proficient_query", "skill_name_query"], v => { - const setting = { - burst_query: `?{${translate("BURST")}|${translate("YES")},+ 2[${translate("BURST")}]|${translate("NO")},&` + "#" + "32;}", - extra_hp_query: `?{${translate("EXTRA_HP_QUERY")}|0}[${translate("BONUS")}]`, - proficient_query: `?{${translate("PROFICIENT")}|${translate("YES")}, @{npc_skills}|${translate("NO")}, 0}[${translate("SKILL")}]`, - skill_name_query: `?{${translate("SKILL_NAME")}|${translate("SKILL")}}`, - translation_numdice: translate("NUMBER_OF_DICE") - }; - mySetAttrs(setting, v); - }); - }; - - /* Ship Code */ - const fillShipStats = () => { - getAttrs(["ship_hulltype", ...shipStats], v => { - const label = v.ship_hulltype && reverseHullTypes[v.ship_hulltype.toLowerCase()]; - if (label && autofillData.hulltypes.hasOwnProperty(label)) { - const data = Object.assign({}, autofillData.hulltypes[label]); - data.ship_hp_max = data.ship_hp; - Object.keys(data).forEach(key => { - if (!(["", 0, "0"].includes(v[key]))) delete data[key]; - }); - mySetAttrs(data, v); - } - }); - }; - const setShipClass = () => { - // Sets the internal ship_class_normalised attribute responsible - // for filtering ship modules according to class. - getAttrs(["ship_class", "ship_class_normalised"], v => { - if (["fighter", translate("FIGHTER").toLowerCase()].includes(v.ship_class.toLowerCase())) - setAttrs({ - ship_class_normalised: "fighter" - }); - else if (["frigate", translate("FRIGATE").toLowerCase()].includes(v.ship_class.toLowerCase())) - setAttrs({ - ship_class_normalised: "frigate" - }); - else if (["cruiser", translate("CRUISER").toLowerCase()].includes(v.ship_class.toLowerCase())) - setAttrs({ - ship_class_normalised: "cruiser" - }); - else mySetAttrs({ - ship_class_normalised: "" - }, v); - }); - }; - const calculateShipStats = () => { - // Calculates power, mass, and hardpoints remaining. - const doCalc = (weaponIDs, fittingIDs, defenseIDs) => { - const oldAttrs = [ - ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_power`), - ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_mass`), - ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_hardpoints`), - ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_power`), - ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_mass`), - ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_power`), - ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_mass`), - "ship_power_max", "ship_mass_max", "ship_hardpoints_max", - "ship_power", "ship_mass", "ship_hardpoints" - ]; - getAttrs(oldAttrs, v => { - const ship_power = v.ship_power_max - sum([ - ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_power`), - ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_power`), - ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_power`), - ].map(x => v[x])); - const ship_mass = v.ship_mass_max - sum([ - ...weaponIDs.map(id => `repeating_ship-weapons_${id}_weapon_mass`), - ...fittingIDs.map(id => `repeating_ship-fittings_${id}_fitting_mass`), - ...defenseIDs.map(id => `repeating_ship-defenses_${id}_defense_mass`), - ].map(x => v[x])); - const ship_hardpoints = v.ship_hardpoints_max - - sum(weaponIDs.map(id => v[`repeating_ship-weapons_${id}_weapon_hardpoints`])); - mySetAttrs({ - ship_power, - ship_mass, - ship_hardpoints - }, v, { - silent: true - }); - }); - }; - getSectionIDs("repeating_ship-weapons", A => getSectionIDs("repeating_ship-fittings", B => { - getSectionIDs("repeating_ship-defenses", C => doCalc(A, B, C)); - })); - }; - - /* Drones */ - const fillDroneStats = () => { - // This must be run from a repeating event, otherwise it will not know where to draw the data from. - getAttrs(["repeating_drones_drone_model"], v => { - const model = (v.repeating_drones_drone_model || "").toLowerCase().trim().replace(/ /g, "_"); - if (autofillData.drones.hasOwnProperty(model)) { - const setting = Object.entries(autofillData.drones[model]).reduce((m, [key, value]) => { - m[`repeating_drones_${key}`] = value; - return m; - }, {}); - setting.repeating_drones_drone_HP_max = setting.repeating_drones_drone_HP; - setAttrs(setting); - } - }); - }; - const fillDroneFitting = (num) => { - const prefix = `repeating_drones_drone_fitting_${num}`; - getAttrs([`${prefix}_desc`, `${prefix}_name`], v => { - const fittingName = (v[`${prefix}_name`] || "").toLowerCase().trim().replace(/ /g, "_"); - if (v[`${prefix}_desc`] === "" && autofillData.droneFittings.includes(fittingName)) { - setAttrs({ - [`${prefix}_desc`]: translate(`${fittingName.toUpperCase()}_DESC`) - }); - } - }); - }; - const calculateDroneAttack = (prefixes, callback) => { - const sourceAttrs = prefixes.reduce((m, prefix) => { - return m.concat([ - `${prefix}_drone_weapon1_ab`, - `${prefix}_drone_weapon1_active`, - `${prefix}_drone_weapon1_attack`, - `${prefix}_drone_weapon1_skill`, - `${prefix}_drone_weapon2_ab`, - `${prefix}_drone_weapon2_active`, - `${prefix}_drone_weapon2_attack`, - `${prefix}_drone_weapon2_skill`, - `${prefix}_drone_weapon3_ab`, - `${prefix}_drone_weapon3_active`, - `${prefix}_drone_weapon3_attack`, - `${prefix}_drone_weapon3_skill`, - ]); - }, ["attack_bonus", "intelligence_mod", "skill_pilot", "skill_program"]); - getAttrs(sourceAttrs, v => { - const skillMod = Math.max(parseInt(v.skill_pilot), parseInt(v.skill_program)) || 0, - intMod = parseInt(v.intelligence_mod) || 0, - attackBonus = parseInt(v.attack_bonus) || 0; - - const setting = prefixes.reduce((m, prefix) => { - [1, 2, 3].filter(num => v[`${prefix}_drone_weapon${num}_active`] === "1") - .forEach(num => { - m[`${prefix}_drone_weapon${num}_attack`] = intMod + attackBonus + - ((skillMod === -1) ? -2 : skillMod) + - parseInt(v[[`${prefix}_drone_weapon${num}_ab`]] || 0); - m[`${prefix}_drone_weapon${num}_skill`] = - ((skillMod === -1) ? -2 : skillMod); - }); - return m; - }, {}); - mySetAttrs(setting, v, callback); - }); - }; - - /** - * Migrations - */ - /* Upgrade event handler */ - const handleUpgrade = () => { - getAttrs(["character_sheet"], v => { - if (!v.character_sheet || v.character_sheet.indexOf(sheetName) !== 0) - upgradeFrom162(); - else if (v.character_sheet.slice(32) !== sheetVersion) - upgradeSheet(v.character_sheet.slice(32), true); - }); - }; - - /* Versioned upgrade */ - const upgradeSheet = (version, firstTime = false, finalTime = false) => { - // Any version upgrade code should go here - const performUpgrade = (version) => { - const [major, minor, patch] = version.split(".").map(x => parseInt(x)); - console.log(`Upgrading from version ${version}.`); - - /** v2.1.0 - * convert old format for burst settings for weapons and attacks - * set ammo and shock checkboxes to reasonable values - * convert old format for gear readied/stowed - **/ - if (major === 2 && minor < 1) { - const upgradeFunction = _.after(4, () => { - // recalculate these things just to be sure, in case the v1.6.2 update - // missed them. - buildShipWeaponsMenu(); - buildAttacksMenu(); - buildMagicMenu(); - generateWeaponDisplay(); - attributes.forEach(calculateMod); - - upgradeSheet("2.1.0"); - }); - - getSectionIDs("repeating_weapons", idArray => { - const sourceAttrs = [ - ...idArray.map(id => `repeating_weapons_${id}_weapon_burst`), - ...idArray.map(id => `repeating_weapons_${id}_weapon_shock_damage`), - ...idArray.map(id => `repeating_weapons_${id}_weapon_ammo`) - ]; - getAttrs(sourceAttrs, v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_weapons_${id}_weapon_burst`] === "0") - m[`repeating_weapons_${id}_weapon_burst`] = ""; - else if (v[`repeating_weapons_${id}_weapon_burst`] === "2") - m[`repeating_weapons_${id}_weapon_burst`] = "+ 2[Burst]"; - if (v[`repeating_weapons_${id}_weapon_shock_damage`] !== "0") - m[`repeating_weapons_${id}_weapon_shock`] = "{{shock=[[@{weapon_shock_damage} + @{weapon_attribute_mod}[Attribute] + @{weapon_skill_to_damage}[Skill]]] ^{SHOCK_DAMAGE_AGAINST_AC_LEQ} @{weapon_shock_ac}!}}"; - if (v[`repeating_weapons_${id}_weapon_ammo`] && v[`repeating_weapons_${id}_weapon_ammo`] !== "0") { - m[`repeating_weapons_${id}_weapon_use_ammo`] = "{{ammo=[[0@{weapon_ammo} - (1 @{weapon_burst})]] / @{weapon_ammo|max}}}"; - } - return m; - }, {}); - setAttrs(setting, {}, upgradeFunction); - }); - }); - getSectionIDs("repeating_ship-weapons", idArray => { - getAttrs(idArray.map(id => `repeating_ship-weapons_${id}_weapon_ammo_max`), v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_ship-weapons_${id}_weapon_ammo_max`] && v[`repeating_ship-weapons_${id}_weapon_ammo_max`] !== "0") { - m[`repeating_ship-weapons_${id}_weapon_use_ammo`] = "{{ammo=[[@{weapon_ammo} - 1]] / @{weapon_ammo_max}}}"; - } - return m; - }, {}); - setAttrs(setting, {}, upgradeFunction); - }); - }); - getSectionIDs("repeating_npc-attacks", idArray => { - getAttrs(idArray.map(id => `repeating_npc-attacks_${id}_attack_burst`), v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_npc-attacks_${id}_attack_burst`] === "0") - m[`repeating_npc-attacks_${id}_attack_burst`] = ""; - else if (v[`repeating_npc-attacks_${id}_attack_burst`] === "2") - m[`repeating_npc-attacks_${id}_attack_burst`] = "+ 2[Burst]"; - return m; - }, {}); - setAttrs(setting, {}, upgradeFunction); - }); - }); - getSectionIDs("repeating_gear", idArray => { - getAttrs(idArray.map(id => `repeating_gear_${id}_gear_status`), v => { - const setting = idArray.reduce((m, id) => { - m[`repeating_gear_${id}_gear_status`] = - (v[`repeating_gear_${id}_gear_status`] || "").toUpperCase(); - return m; - }, {}); - mySetAttrs(setting, v, {}, upgradeFunction); - }); - }); - } - /** v2.2.0 - * convert single armor line to repeating armor - * Change @{attribute_query_none} to @{attribute_query} - **/ - else if (major === 2 && minor < 2) { - const upgradeFunction = _.after(2, () => { - calculateStrDexMod(); - calculateEffort(); - buildPsionicsMenu(); - buildSkillMenu(); - - upgradeSheet("2.2.0"); - }); - getAttrs(["armor_name", "armor_ac", "armor_encumbrance", "armor_type", "setting_ship_tab_name"], v => { - if (v.armor_ac) { - const data = { - armor_active: "1", - armor_ac: v.armor_ac, - armor_encumbrance: v.armor_encumbrance || "0", - armor_name: v.armor_name || "", - armor_status: "READIED", - armor_type: (v.armor_type || "").toUpperCase() - }; - fillRepeatingSectionFromData("armor", data, upgradeFunction); - } else upgradeFunction(); - if (v.setting_ship_tab_name === "MECH") setAttrs({ - ship_vehicle_type: "MECH" - }); - }); - getSectionIDs("repeating_skills", skillIDs => getSectionIDs("repeating_magic-skills", magicIDs => { - getSectionIDs("repeating_psychic-skills", psychicIDs => { - const sourceAttrs = [ - ...skillIDs.map(id => `repeating_skills_${id}_skill_query`), - ...magicIDs.map(id => `repeating_magic-skills_${id}_skill_query`), - ...psychicIDs.map(id => `repeating_psychic-skills_${id}_skill_query`), - ...skills.revised.map(skill => `skill_${skill}_query`), - ...skills.first.map(skill => `skill_${skill}_query`), - ...skills.psionic.map(skill => `skill_${skill}_query`), - "skill_magic_query", "skill_magic2_query" - ]; - getAttrs(sourceAttrs, v => { - const setting = sourceAttrs.reduce((m, attrName) => { - if (v[attrName] === "@{attribute_query_none}") m[attrName] = "@{attribute_query}"; - return m; - }, {}); - mySetAttrs(setting, v, {}, upgradeFunction); - }); - }); - })); - } - /** v2.3.1 - * Regenerate drone and weapon ABs - **/ - else if (major === 2 && (minor < 3 || (minor === 3 && patch === 0))) { - const upgradeFunction = _.after(1, () => { - upgradeSheet("2.3.1"); - }); - generateWeaponDisplay(); - getSectionIDs("repeating_drones", idArray => { - calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`), upgradeFunction); - }); - } - /** v2.4.3 - * Regenerate Cyberware strain because it was bugged - **/ - else if (major === 2 && (minor < 4 || (minor === 4 && patch < 3))) { - calculateCyberwareStrain(); - upgradeSheet("2.4.3"); - } - /** v2.4.7 - * Move attr to attr_base, and recalculate attr - **/ - else if (major === 2 && minor === 4 && patch < 7) { - attributes.forEach(attr => { - getAttrs([attr, `${attr}_base`], v => { - mySetAttrs({[`${attr}_base`]: parseInt(v[`${attr}`] || 10)}, v, null, () => { - calculateAttr(attr); - }); - }); - }); - upgradeSheet("2.4.7"); - } - - /** v2.4.11 - * Recalculate drone attack to remove inncorrect double Int bonus - */ - else if (major == 2 && minor == 4 && patch < 12) { - getSectionIDs("repeating_drones", idArray => { - calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`)); - }) - upgradeSheet("2.4.12"); - } - /** Final upgrade clause, always leave this around */ - else upgradeSheet(sheetVersion, false, true); - }; - - if (firstTime) performUpgrade(version); - else setAttrs({ - character_sheet: `${sheetName} v${version}`, - }, {}, () => { - if (!finalTime) performUpgrade(version); - }); - }; - /* Main upgrade from pre-worker versioning */ - const upgradeFrom162 = () => { - console.log("Upgrading from versionless sheet (assumed to be fresh or v1.6.2)."); - const upgradeFunction = _.after(13, () => { - upgradeSheet("2.0.1"); - }); - - // Legacy migration - getAttrs([1, 2, 3, 4, 5, 6, 7, 8].map(i => `psionics_mastered_${i}`), v => { - const setting = {}; - for (let i = 1; i < 9; i++) { - const technique = v[`psionics_mastered_${i}`]; - if (technique) { - const newRowId = generateRowID(); - setting[`repeating_techniques_${newRowId}_technique_name`] = technique; - } - } - setAttrs(setting); - }); - getAttrs([1, 2, 3, 4].map(i => `cyberware_${i}`), v => { - const setting = {}; - for (let i = 1; i < 5; i++) { - const cyberware = v[`cyberware_${i}`]; - if (cyberware) { - const newRowId = generateRowID(); - setting[`repeating_cyberware_${newRowId}_cyberware_name`] = cyberware; - } - } - setAttrs(setting); - }); - getAttrs(["languages"], v => { - if (v.languages) { - const setting = {}; - - v.languages.split(/\r?\n/).filter(l => !!l).forEach(language => { - const newRowId = generateRowID(); - setting[`repeating_languages_${newRowId}_language`] = language; - }); - setAttrs(setting); - } - }); - - const attrConversionData = { - armor_enc: "armor_encumbrance", - cha: "charisma", - cha_misc: "charisma_bonus", - con: "constitution", - con_misc: "constitution_bonus", - dex: "dexterity", - dex_misc: "dexterity_bonus", - gender: "species_gender", - hd: "npc_hd", - int: "intelligence", - int_misc: "intelligence_bonus", - morale: "npc_morale", - move: "npc_move", - name: "npc_name", - notes: "npc_notes", - npc_ac: "AC", - saves: "npc_saves", - ship_hp_min: "ship_hp", - ship_current_crew: "ship_crew", - ship_last_maintenance_cost: "ship_last_maintenance", - skills: "npc_skills", - skill_biopsion: "skill_biopsionics", - skill_metapsion: "skill_metapsionics", - skill_points: "unspent_skill_points", - str: "strength", - strain_perm: "strain_permanent", - str_misc: "strength_bonus", - wis: "wisdom", - wis_misc: "wisdom_bonus", - }; - const attrsToConvertFromOnTo1 = [ - "homebrew_luck_save", - "homebrew_extra_skills", - "setting_heroic_enable", - ]; - const customConversionAttrs = [ - "damage", - "homebrew_psionics_disable", - "npc_attacks", - "npc_attack_bonus", - "setting_space_magic_enable", - "ship_other_notes", - "ship_free_hardpoints", - "ship_free_mass", - "ship_free_power", - "skill_culture_alien_type", - "skill_culture_one_value", - "skill_culture_two_value", - "skill_culture_three_value", - "profession_type", - "tab", - ...[1, 2, 3, 4].map(n => `homebrew_custom_counter_${n}_name`), - ...[1, 2, 3, 4].map(n => `homebrew_custom_counter_${n}_counter`), - ]; - // convert non-repeating attributes - getAttrs([ - ...Object.keys(attrConversionData), - ...Object.values(attrConversionData), - ...attrsToConvertFromOnTo1, - ...customConversionAttrs, - ], v => { - const setting = Object.entries(attrConversionData).reduce((m, [oldName, newName]) => { - if (v[oldName] && v[oldName] !== "" && `${v[newName]}` !== `${v[oldName]}`) - m[newName] = v[oldName]; - return m; - }, {}); - attrsToConvertFromOnTo1.forEach(name => { - if (v[name] === "on") setting[name] = "1"; - }); - - // convert skill name format - ["one", "two", "three"].forEach(num => { - if (v[`skill_culture_${num}_value`]) - setting[`skill_culture_${num}_name`] = `Culture/${v[`skill_culture_${num}_value`]}`; - }); - if (v.profession_type) setting.skill_profession_name = `Profession/${v.profession_type}`; - if (v.skill_culture_alien_type) - setting.skill_culture_alien_name = `Culture/Alien/${v.skill_culture_alien_type}`; - - // Write legacy ship data - if (v.ship_free_hardpoints || v.ship_free_mass || v.ship_free_power) { - setting.ship_other_notes = `\nLegacy attributes${ - v.ship_free_power ? `\nFree Power: ${v.ship_free_power}` : ""}${ - v.ship_free_mass ? `\nFree Mass: ${v.ship_free_mass}` : ""}${ - v.ship_free_hardpoints ? `\nFree Hardpoints: ${v.ship_free_hardpoints}` : ""} - ${v.ship_other_notes || ""}`; - } - // convert homebrew custom counter stuff - const customCounterData = [1, 2, 3, 4].reduce((m, num) => { - if (v[`homebrew_custom_counter_${num}_name`]) m.push({ - resource_name: v[`homebrew_custom_counter_${num}_name`], - resource_count: v[`homebrew_custom_counter_${num}_counter`] || 0 - }); - return m; - }, []); - fillRepeatingSectionFromData("resources", customCounterData, upgradeFunction); - - // Tab - if (`${v.tab}` === "1" || `${v.tab}` === "4") setting.tab = "character"; - if (`${v.tab}` === "2") setting.tab = "ship"; - if (`${v.tab}` === "3") { - setting.tab = "character"; - setting.npc = "1"; - } - - // NPC attack - if (v.damage) { - const newAttack = { - attack_damage: v.damage, - attack_name: translate("ATTACK"), - attack_number: v.npc_attacks || "1" - }; - fillRepeatingSectionFromData("npc-attacks", newAttack, upgradeFunction); - } else upgradeFunction(); - - // Psionics/Space Magic toggle conversion - if (v.setting_space_magic_enable === "on" && v.homebrew_psionics_disable !== "on") - setting.setting_super_type = "both"; - else if (v.setting_space_magic_enable === "on") - setting.setting_super_type = "magic"; - else if (v.homebrew_psionics_disable === "on") - setting.setting_super_type = "neither"; - - setAttrs(setting, {}, upgradeFunction); - }); - // convert weapon attributes, and extract ship weapons - getSectionIDs("repeating_weapons", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_weapons_${id}_attribute_mod`), - ...idArray.map(id => `repeating_weapons_${id}_add_skill`), - ...idArray.map(id => `repeating_weapons_${id}_weapon_shock`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_name`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_power`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_ab`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_damage`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_ammo`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_special_effects`), - ...idArray.map(id => `repeating_weapons_${id}_ship_weapon_broken`), - ]; - getAttrs(oldAttrs, v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_weapons_${id}_add_skill`] === "@{weapon_skill_bonus}") - m[`repeating_weapons_${id}_weapon_skill_to_damage`] = "@{weapon_skill_bonus}"; - if (v[`repeating_weapons_${id}_weapon_shock`]) - m[`repeating_weapons_${id}_weapon_shock_damage`] = v[`repeating_weapons_${id}_weapon_shock`]; - const modValue = v[`repeating_weapons_${id}_attribute_mod`]; - switch (modValue) { - case "@{dex_bonus}": - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{dexterity_mod}"; - break; - case "@{con_bonus}": - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{constitution_mod}"; - break; - case "@{int_bonus}": - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{intelligence_mod}"; - break; - case "@{wis_bonus}": - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{wisdom_mod}"; - break; - case "@{cha_bonus}": - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{charisma_mod}"; - break; - default: - m[`repeating_weapons_${id}_weapon_attribute_mod`] = "@{strength_mod}"; - } - return m; - }, {}); - const data = idArray.filter(id => v[`repeating_weapons_${id}_ship_weapon_name`]) - .map(id => { - const row = {}; - row.weapon_name = v[`repeating_weapons_${id}_ship_weapon_name`]; - if (v[`repeating_weapons_${id}_ship_weapon_power`]) - row.weapon_power = v[`repeating_weapons_${id}_ship_weapon_power`]; - if (v[`repeating_weapons_${id}_ship_weapon_ab`]) - row.weapon_attack_bonus = v[`repeating_weapons_${id}_ship_weapon_ab`]; - if (v[`repeating_weapons_${id}_ship_weapon_damage`]) - row.weapon_damage = v[`repeating_weapons_${id}_ship_weapon_damage`]; - if (v[`repeating_weapons_${id}_ship_weapon_ammo`]) - row.weapon_ammo = v[`repeating_weapons_${id}_ship_weapon_ammo`]; - if (v[`repeating_weapons_${id}_ship_weapon_special_effects`]) - row.weapon_qualities = v[`repeating_weapons_${id}_ship_weapon_special_effects`]; - if (v[`repeating_weapons_${id}_ship_weapon_broken`] === "on") - row.weapon_broken = "1"; - return row; - }); - fillRepeatingSectionFromData("ship-weapons", data, upgradeFunction); - setAttrs(setting, null, upgradeFunction); - }); - }); - // convert skills - getSectionIDs("repeating_skills", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_name`), - ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_name`), - ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_level`), - ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_level`), - ...idArray.map(id => `repeating_skills_${id}_custom_skill_1_specialist`), - ...idArray.map(id => `repeating_skills_${id}_custom_skill_2_specialist`), - ]; - getAttrs(oldAttrs, v => { - const data = idArray.reduce((m, id) => { - [1, 2].forEach(i => { - if (v[`repeating_skills_${id}_custom_skill_${i}_name`]) { - const skillLevel = (typeof v[`repeating_skills_${id}_custom_skill_${i}_level`] === "undefined") ? - "-1" : v[`repeating_skills_${id}_custom_skill_${i}_level`]; - m.push({ - skill_name: v[`repeating_skills_${id}_custom_skill_${i}_name`], - skill: skillLevel, - skill_specialist: v[`repeating_skills_${id}_custom_skill_${i}_specialist`] || "2d6" - }); - } - }); - return m; - }, []); - idArray.forEach(id => removeRepeatingRow(`repeating_skills_${id}`)); - fillRepeatingSectionFromData("skills", data, upgradeFunction); - }); - }); - // convert techniques - getSectionIDs("repeating_technique", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_technique_${id}_technique`), - ...idArray.map(id => `repeating_technique_${id}_technique_description`), - ]; - getAttrs(oldAttrs, v => { - const data = idArray.reduce((m, id) => { - if (v[`repeating_technique_${id}_technique`]) - m.push({ - technique_name: v[`repeating_technique_${id}_technique`], - technique_description: v[`repeating_technique_${id}_technique_description`] || "" - }); - return m; - }, []); - fillRepeatingSectionFromData("techniques", data, upgradeFunction); - idArray.forEach(id => removeRepeatingRow(`repeating_technique_${id}`)); - }); - }); - // convert cyberware name - getSectionIDs("repeating_cyberware", idArray => { - getAttrs(idArray.map(id => `repeating_cyberware_${id}_cyberware`), v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_cyberware_${id}_cyberware`]) - m[`repeating_cyberware_${id}_cyberware_name`] = v[`repeating_cyberware_${id}_cyberware`]; - return m; - }, {}); - setAttrs(setting, null, upgradeFunction); - }); - }); - // convert goals - getSectionIDs("repeating_goals", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_goals_${id}_misc_goal`), - ...idArray.map(id => `repeating_goals_${id}_misc_goal_xp`), - ]; - getAttrs(oldAttrs, v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_goals_${id}_misc_goal`]) - m[`repeating_goals_${id}_goal_name`] = v[`repeating_goals_${id}_misc_goal`]; - if (v[`repeating_goals_${id}_misc_goal_xp`]) - m[`repeating_goals_${id}_goal_xp`] = v[`repeating_goals_${id}_misc_goal_xp`]; - return m; - }, {}); - setAttrs(setting, null, upgradeFunction); - }); - }); - // convert languages - getSectionIDs("repeating_languages", idArray => { - getAttrs(idArray.map(id => `repeating_languages_${id}_languages`), v => { - const setting = idArray.reduce((m, id) => { - if (v[`repeating_languages_${id}_languages`]) - m[`repeating_languages_${id}_language`] = v[`repeating_languages_${id}_languages`]; - return m; - }, {}); - setAttrs(setting, null, upgradeFunction); - }); - }); - // convert gear status - getSectionIDs("repeating_gear", idArray => { - getAttrs(idArray.map(id => `repeating_gear_${id}_gear_readied`), v => { - const setting = idArray.reduce((m, id) => { - if (`${v[`repeating_gear_${id}_gear_readied`]}` === "1") - m[`repeating_gear_${id}_gear_status`] = "readied"; - else if (`${v[`repeating_gear_${id}_gear_readied`]}` === "2") - m[`repeating_gear_${id}_gear_status`] = "stowed"; - return m; - }, {}); - setAttrs(setting, null, upgradeFunction); - }); - }); - // convert defenses - getSectionIDs("repeating_defenses", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_defenses_${id}_ship_defense_name`), - ...idArray.map(id => `repeating_defenses_${id}_ship_defense_special_effects`), - ...idArray.map(id => `repeating_defenses_${id}_ship_defense_broken`), - ]; - getAttrs(oldAttrs, v => { - const data = idArray.map(id => { - const row = {}; - if (v[`repeating_defenses_${id}_ship_defense_name`]) - row.defense_name = v[`repeating_defenses_${id}_ship_defense_name`]; - if (v[`repeating_defenses_${id}_ship_defense_special_effects`]) - row.defense_effect = v[`repeating_defenses_${id}_ship_defense_special_effects`]; - if (v[`repeating_defenses_${id}_ship_defense_broken`]) - row.defense_broken = "1"; - return row; - }); - fillRepeatingSectionFromData("ship-defenses", data, upgradeFunction); - }); - }); - // convert fittings - getSectionIDs("repeating_fittings", idArray => { - const oldAttrs = [ - ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_name`), - ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_special_effects`), - ...idArray.map(id => `repeating_fittings_${id}_ship_fitting_broken`), - ]; - getAttrs(oldAttrs, v => { - const data = idArray.map(id => { - const row = {}; - if (v[`repeating_fittings_${id}_ship_fitting_name`]) - row.fitting_name = v[`repeating_fittings_${id}_ship_fitting_name`]; - if (v[`repeating_fittings_${id}_ship_fitting_special_effects`]) - row.fitting_effect = v[`repeating_fittings_${id}_ship_fitting_special_effects`]; - if (v[`repeating_fittings_${id}_ship_fitting_broken`]) - row.fitting_broken = "1"; - return row; - }); - fillRepeatingSectionFromData("ship-fittings", data, upgradeFunction); - }); - }); - }; - - /** - * Register Event handlers - */ - - on("sheet:opened", handleUpgrade); - - on("sheet:opened change:npc", validateTab); - on("sheet:opened", setTranslatedQueries); - on("sheet:opened change:setting_skill_query", handleAttributeQueries); - on("sheet:opened change:setting_modifier_query", handleModifierQuery); - on("change:homebrew_skill_list", setTranslatedDefaults); - - /* API use ammo boilerplate */ - ["weapons", "ship-weapons"].forEach(sName => { - on(`change:repeating_${sName}:weapon_use_ammo`, () => handleAmmoAPI(sName)); - }); - on("change:setting_use_ammo", () => { - handleAmmoAPI("weapons"); - handleAmmoAPI("ship-weapons"); - }); - - /* Character sheet */ - on("change:class", fillClassStats); - attributes.forEach(attr => on(`change:${attr}_base change:${attr}_boosts`, () => calculateAttr(attr))); - attributes.forEach(attr => on(`change:${attr} change:${attr}_bonus`, () => calculateMod(attr))); - - on(weaponDisplayEvent, generateWeaponDisplay); - - on("change:repeating_weapons:weapon_name", () => validateWeaponSkills()); - on("change:homebrew_skill_list", () => getSectionIDs("repeating_weapons", validateWeaponSkills)); - - on("change:strain change:strain_permanent", validateStrain); - on("change:constitution", calculateMaxStrain); - on("change:repeating_cyberware remove:repeating_cyberware", calculateCyberwareStrain); - on("change:strain_permanent_extra change:cyberware_strain_total", calculatePermanentStrain); - - on("change:level", calculateSaves); - - on([...effortAttributes, "repeating_psychic-skills:skill"].map(x => `change:${x}`).join(" "), calculateEffort); - - on("change:magic_committed_effort_current change:magic_committed_effort_scene change:magic_committed_effort_day change:magic_total_effort", calculateMagicEffort); - - on("change:repeating_armor change:innate_ac remove:repeating_armor", calculateAC); - - on("change:strength change:repeating_gear remove:repeating_gear change:repeating_weapons " + - "remove:repeating_weapons change:repeating_armor remove:repeating_armor", calculateGearReadiedStowed); - - on("change:level change:setting_xp_scheme", calculateNextLevelXP); - - on("change:setting_super_type", validateSuperTab); - - /* Character chat macros */ - on("change:homebrew_luck_save", buildSaveMenu); - - on([ - ...skills.revised.map(x => `change:skill_${x}`), - ...skills.first.map(x => `change:skill_${x}`), - "change:homebrew_skill_list", - "change:repeating_skills", - "change:setting_show_untrained_skills", - "remove:repeating_skills", - ].join(" "), buildSkillMenu); - - on([ - ...skills.psionic.map(x => `change:skill_${x}`), - "change:setting_super_type change:repeating_techniques remove:repeating_techniques", - "change:repeating_psychic-skills remove:repeating_psychic-skills" - ].join(" "), buildPsionicsMenu); - - on("change:setting_super_type change:repeating_spells remove:repeating_spells " + - "change:repeating_magic-skills remove:repeating_magic-skills " + - "change:skill_magic change:skill_magic2_name change:skill_magic2", buildMagicMenu); - - /* Repeating autofill */ - autofillSections.forEach(sName => { - on(`change:generate_${sName}_source`, () => generateAutofillInfo(sName)); - on(`change:generate_${sName}_button`, () => generateAutofillRow(sName)); - }); - - - /* Ship sheet */ - on("change:ship_hulltype", fillShipStats); - on("change:ship_class", setShipClass); - on(shipStatEvent, calculateShipStats); - on("change:repeating_ship-weapons:weapon_name change:repeating_ship-weapons:weapon_attack_bonus " + - "remove:repeating_ship-weapons", buildShipWeaponsMenu); - - /* Drones */ - [1,2,3,4,5].forEach(num => { - on(`change:repeating_drones:drone_fitting_${num}_name`, () => fillDroneFitting(num)); - }); - on("change:repeating_drones:drone_model", fillDroneStats); - on("change:attack_bonus change:intelligence_mod change:skill_pilot change:skill_program", - () => getSectionIDs("repeating_drones", idArray => { - calculateDroneAttack(idArray.map(id => `repeating_drones_${id}`)); - })); - on([ - ...[1, 2, 3].map(n => `change:repeating_drones:drone_weapon${n}_ab`), - ...[1, 2, 3].map(n => `change:repeating_drones:drone_weapon${n}_active`) - ].join(" "), () => calculateDroneAttack(["repeating_drones"])); - - /* NPC sheet */ - on("change:npc_stat_block", fillNPC); - on("change:npc_rolls_hidden", handleNPCRollHide); - on("change:repeating_npc-attacks:attack_name", addNPCAttackBonus); - on("change:npc_roll_full_attack change:repeating_npc-attacks:attack_number", setNPCMultiAttacks); - - on("change:repeating_npc-abilities:ability_name remove:repeating_npc-abilities", buildAbilitiesMenu); - on("change:repeating_npc-attacks:attack_name change:repeating_npc-attacks:attack_ab " + - "change:repeating_npc-attacks:attack_number remove:repeating_npc-attacks", buildAttacksMenu); - on("change:npc change:npc_armor_type change:macro_npc_attacks change:macro_npc_abilities", buildStatblock); - -})(); diff --git a/Stars_Without_Number_Revised/Stars_Without_Number_Revised.css b/Stars_Without_Number_Revised/Stars_Without_Number_Revised.css index f4cda37e6f..46de33faf2 100644 --- a/Stars_Without_Number_Revised/Stars_Without_Number_Revised.css +++ b/Stars_Without_Number_Revised/Stars_Without_Number_Revised.css @@ -1 +1 @@ -.charsheet{background-color:#fff;padding:0 !important;font-size:12px;line-height:18px;min-width:800px;color:#000}div,span,label,textarea,button[type=roll],h1,h2,h3{box-sizing:border-box}label{margin:0;padding:0;color:inherit;font-size:1em;font-weight:normal;width:auto}p{font-size:1em;margin:0 0 5px;line-height:inherit}textarea{resize:vertical;padding:1px 4px;font-size:12px;margin:0;color:#000;display:block}input[type=text],input[type=number]{padding:1px 4px;font-size:12px;height:20px;color:#000}input[type=number][readonly=readonly],input[type=number].sheet-no-spinners{-moz-appearance:textfield}input[type=number][readonly=readonly]::-webkit-inner-spin-button,input[type=number][readonly=readonly]::-webkit-outer-spin-button,input[type=number].sheet-no-spinners::-webkit-inner-spin-button,input[type=number].sheet-no-spinners::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}select{margin:0;color:#000;font-size:12px;padding:1px 2px;line-height:18px;height:22px}button[type=roll].btn{margin:0}button[type=roll].sheet-unstyled-button{text-shadow:none;box-shadow:none;color:inherit;text-align:inherit;padding:0;border:none;border-radius:0;font-size:inherit;background:transparent}button[type=roll].sheet-unstyled-button::before{content:""}button[type=roll].sheet-invisible-button{position:absolute;z-index:1;opacity:0;left:0;width:100%;height:100%;top:0}button[type=roll].sheet-invisible-button:hover+div{color:#2b3fea;border-color:#2b3fea}.sheet-hider[value="0"]+div,.sheet-hider[value="0"]+label,.sheet-hider[value="0"]+button,.sheet-hider[value="0"]+span{display:none}.sheet-hider[value=""]+button{display:none}.sheet-editmode[value="0"]~.sheet-content .sheet-empty-hider[value=""]+div{display:none}h1{font-family:"Contrail One",sans-serif;background-color:#9b9b9b;padding-left:5px;color:#fff;font-size:1.3em;line-height:22px;font-weight:normal}h1::after{content:"";position:absolute;right:0;background-color:#223297;background-position:center;background-size:100%;width:22px;height:22px}h2{font-family:"Contrail One",sans-serif;padding-left:5px;border:2px solid #000;background-color:#ccc;color:#000;font-size:1.1em;margin:5px 0;width:175px;line-height:18px}h3{font-size:1.2em;font-family:"Contrail One",sans-serif;color:#6b6b6b;line-height:1.5;font-weight:normal}.sheet-npc-input[value="0"]~.sheet-tabs .sheet-npc-tab,.sheet-npc-input[value="1"]~.sheet-tabs .sheet-pc-tab,.sheet-tab-input:not([value=character])~.sheet-character,.sheet-tab-input:not([value=ship])~.sheet-ship,.sheet-tab-input:not([value=drones])~.sheet-drones,.sheet-tab-input:not([value=npc])~.sheet-npc{display:none}.sheet-content{min-width:800px;display:grid;grid-gap:5px}.sheet-editmode[value="0"]~.sheet-content .sheet-edit,.sheet-editmode[value="0"]~.sheet-content .repcontrol,.sheet-editmode[value="1"]~.sheet-content .sheet-display{display:none}.sheet-header{margin-bottom:10px}.sheet-header input{opacity:0;position:absolute}.sheet-header .sheet-logo{width:480px}.sheet-header .sheet-npc-chooser{cursor:pointer;margin-left:auto;color:#fff}.sheet-header .sheet-npc-chooser span{padding:3px 6px}.sheet-header .sheet-npc-chooser input:not(:checked)~.sheet-pc-label,.sheet-header .sheet-npc-chooser input:checked~.sheet-npc-label{background:#11194b}.sheet-header .sheet-npc-chooser input:checked~.sheet-pc-label,.sheet-header .sheet-npc-chooser input:not(:checked)~.sheet-npc-label{background:#223297}.sheet-header .sheet-edit-button{margin:0 5px 0 20px}.sheet-header .sheet-settings-gear,.sheet-header .sheet-edit-button{cursor:pointer;font-size:16px;font-family:pictos}.sheet-header .sheet-settings-gear input:not(:checked)+span,.sheet-header .sheet-edit-button input:not(:checked)+span{color:#888}.sheet-header .sheet-settings-gear input:checked+span,.sheet-header .sheet-edit-button input:checked+span{color:#223297}.sheet-footer{font-size:10px;border-top:1px solid #ccc;margin-top:10px;color:#666}.sheet-footer button[type=roll].btn:hover{color:#223297}.sheet-tabs{margin-bottom:10px;width:100%;position:relative;z-index:2;border-top:1px solid #333;font-family:"Contrail One",sans-serif;flex-flow:row-reverse;justify-content:flex-end}.sheet-tabs input{opacity:0;position:absolute}.sheet-tabs input:checked+span{background:#11194b}.sheet-tabs input:not(:checked)+span{background:#223297}.sheet-tabs span{padding:1px 25px 0}.sheet-tabs label{cursor:pointer;line-height:17px;display:flex;margin:-1px -10px 0 0;color:#fff;clip-path:polygon(0 0, 100% 0, 100% 10px, calc(100% - 10px) 100%, 0 100%)}.sheet-flex{display:flex}.sheet-flex-start{display:flex;align-items:flex-start}.sheet-flex-center{display:flex;align-items:center}.sheet-flex-between{display:flex;align-items:center;justify-content:space-between}.sheet-flex-wrap{display:flex;flex-wrap:wrap;align-content:flex-start;align-items:flex-start}.sheet-grow{flex-grow:1}.sheet-halfgrid{display:grid;grid:auto/1fr 1fr;grid-gap:5px}.sheet-thirdsgrid{display:grid;grid:auto/repeat(3, 1fr);grid-column-gap:1%}.sheet-column>div:not(:first-child){margin-top:5px}.sheet-col-2{grid-column:2}.sheet-col-3{grid-column:3}.sheet-col-4{grid-column:4}.sheet-cols-2{grid-column:span 2}.sheet-cols-3{grid-column:span 3}.sheet-cols-4{grid-column:span 4}.sheet-rows-2{grid-row:span 2}.sheet-col55{width:55%}.sheet-col50{width:50%}.sheet-col40{width:40%}.sheet-colthird{width:calc(100%/3)}.sheet-col25{width:25%}.sheet-colsixth{width:calc(100%/6)}.sheet-grey-box,.sheet-grey-box-bordered{padding:4px;background:#f0f0f0}.sheet-grey-box-bordered{border:1px solid #aaa}.sheet-core-box{padding:8px;background:#f0f0f0}.sheet-core-box-text{padding:8px 3px 3px 8px;background:#f0f0f0}input[type=text].sheet-inherit{border-radius:0;border:none;background:transparent;box-shadow:none;padding:0;color:inherit;font:inherit}input[type=number].sheet-inline-input{position:relative;z-index:2;vertical-align:baseline;padding:0 3px}.sheet-text-input{color:#666;padding-right:5px;line-height:1}.sheet-text-input .sheet-label{font-size:.9em;text-align:inherit;line-height:14px;border-top:1px solid #aaa}.sheet-text-input .sheet-label input[type=checkbox]{height:14px;margin:0 3px 0 0}.sheet-text-input input[type=text],.sheet-text-input input[type=number],.sheet-text-input textarea{background:transparent;padding:1px 0;width:100%;border-radius:0;color:#223297;border:none;text-align:inherit;font-weight:inherit;box-shadow:none}.sheet-text-input input[type=text][readonly=readonly],.sheet-text-input input[type=number][readonly=readonly],.sheet-text-input textarea[readonly=readonly]{cursor:default}.sheet-text-input input[type=text],.sheet-text-input input[type=number]{font-size:1.1em}.sheet-text-input textarea{height:20px;min-height:20px}.sheet-text-input>input[type=checkbox]{height:14px;margin:3px 0}.sheet-text-input select{width:100%;height:20px;color:#223297;background:transparent;border:none}.sheet-text-input.sheet-important{font-weight:bold;font-size:1.1em}.sheet-text-input.sheet-center{text-align:center}.sheet-text-input.sheet-duo{display:grid;text-align:center;grid:auto/1fr 5px 1fr;align-items:center;align-content:start;grid-column-gap:4px}.sheet-text-input.sheet-duo>span{font-size:16px;color:#333}.sheet-text-input.sheet-duo .sheet-label{grid-column:1/4}.sheet-text-input button[type=roll].btn{width:100%}.sheet-text-input button[type=roll].btn:hover{color:red}.sheet-table-header{font-size:.9em;color:#666}.sheet-table-header .sheet-center{text-align:center}.sheet-table-header>div{padding-left:2px;border-bottom:1px solid #aaa}.sheet-blue-button{text-align:center;line-height:1.5}.sheet-blue-button button[type=roll].btn{background:#2b3fea;width:35px;height:35px;display:block;margin:0 auto;color:#fff}.sheet-blue-button button[type=roll].btn::before{content:""}.sheet-focusinfo{position:relative}.sheet-focusinfo .sheet-details{position:absolute;z-index:1;opacity:1;width:250px;top:22px;left:calc(50% - 125px);transition:all .3s;background:#fff;border:1px solid #777;border-radius:8px;padding:5px}.sheet-focusinfo input:not(:focus)~.sheet-details{visibility:hidden;opacity:0}.sheet-lower{text-transform:lowercase}.sheet-space-before::before,.sheet-space-after::after{content:" "}.sheet-colon-after::after{content:":"}.sheet-hide-empty[value=""]+span{display:none}.sheet-add-plus:not([value^="-"])+span::before{content:"+"}.sheet-api{font-size:.8em;background:#f0f0f0;border-radius:5px;padding:1px;line-height:1;vertical-align:super}.sheet-simple-repsection .repitem{margin-bottom:3px}.sheet-simple-repsection textarea{margin:3px 0 0;height:50px}.sheet-simple-repsection .sheet-display .sheet-name{font-weight:bold;font-size:1.1em;border-bottom:1px solid #777}.sheet-simple-repsection .sheet-display .sheet-details{line-height:1.4;text-align:justify;white-space:pre-wrap}.sheet-rep-grid{display:grid;align-items:start;grid-column-gap:3px;margin-bottom:3px}.sheet-rep-grid .sheet-text-input{padding:0}.sheet-skills-type[value=neither]~div .sheet-skills-either,.sheet-skills-type:not([value=first])~div .sheet-skills-first,.sheet-skills-type:not([value=revised])~div .sheet-skills-revised,.sheet-skills-type:not([value=neither])~div .sheet-skills-neither,.sheet-super-type:not([value=psionics]):not([value=both])~div .sheet-skills-psionics,.sheet-super-type:not([value=magic]):not([value=both])~div .sheet-skills-magic,.sheet-skills-query:not([value=hover])~div .sheet-skills-query-hover,.sheet-skills-query[value=query]~div .sheet-skills-query-display,.sheet-skills-query[value=query]~div .sheet-skills-query-ask{display:none}.sheet-skills-query-display input:not([value*=str])~.sheet-str,.sheet-skills-query-display input:not([value*=dex])~.sheet-dex,.sheet-skills-query-display input:not([value*=con])~.sheet-con,.sheet-skills-query-display input:not([value*=int])~.sheet-int,.sheet-skills-query-display input:not([value*=wis])~.sheet-wis,.sheet-skills-query-display input:not([value*=cha])~.sheet-cha{display:none}.sheet-skills-query[value=hover]~div .sheet-skill:hover .sheet-skills-query-display,.sheet-skill .sheet-display .sheet-query:focus+.sheet-skills-query-display{display:none}.repcontrol{display:flex;margin-top:4px}.repcontrol button{padding:3px 10px}.repcontrol_edit{order:1;margin-left:auto}.repcontrol button{background:#223297;color:#fff;box-shadow:none;text-shadow:none}.repcontrol_move,.repcontrol_del{padding:1px 10px}.itemcontrol{z-index:100}.sheet-section-toggle-header{position:relative}.sheet-section-toggle-header input{cursor:pointer;position:absolute;width:22px;height:100%;z-index:2;right:0;opacity:0}.sheet-section-toggle-header input+h1::after{background-image:url("https://svgshare.com/i/6Vz.svg")}.sheet-section-toggle-header input:not(:checked)+h1::after{transform:rotate(180deg)}.sheet-section-toggle-header+input+div{margin-top:5px}.sheet-settings label{font-weight:bold}.sheet-settings label span{margin-right:4px}.sheet-settings select{width:120px;display:inline-block}.sheet-settings select.sheet-wide{width:170px}.sheet-generate{margin-bottom:5px}.sheet-generate .sheet-generate-source{height:20px;width:170px}.sheet-generate .sheet-generate-info{margin:0 5px;min-height:20px;flex-grow:1;background:#f0f0f0;border-radius:4px;padding:1px 4px}.sheet-generate .sheet-generate-button{padding:1px 8px;border-radius:4px;cursor:pointer;background:#223297;color:#fff}.sheet-generate .sheet-generate-button input[type=checkbox]{position:absolute;visibility:hidden}.sheet-generate .sheet-generate-button:active{outline:5px auto;outline-offset:-2px}.sheet-core{display:grid;grid-column-gap:5px}.sheet-core.sheet-core-info{grid:auto/2.5fr 1fr .8fr}.sheet-core.sheet-core-info .sheet-info{flex-wrap:wrap;padding-right:3px}.sheet-core.sheet-core-info .sheet-info .sheet-important{width:calc(60% - 22px)}.sheet-core.sheet-core-info .sheet-info .sheet-level{width:44px}.sheet-core.sheet-core-info .sheet-info .sheet-level-rest{width:calc(40% - 22px)}.sheet-core.sheet-core-stats{grid:auto/minmax(310px, 1.2fr) minmax(260px, 1fr) minmax(220px, 0.8fr)}.sheet-core .sheet-main-content{margin-top:3px;display:grid;grid:auto/repeat(3, 1fr);grid-row-gap:3px}.sheet-core .sheet-main-content .sheet-left-margin{margin-left:15px}.sheet-saves .sheet-flex{margin-top:4px}.sheet-menus-content{display:grid;grid:auto/1fr 1fr}.sheet-menus button[type=roll].btn{color:#2b3fea;font-size:1.1em}.sheet-menus button[type=roll].btn:hover{color:red}.sheet-character .sheet-menus button[type=roll].btn{margin-right:auto}.sheet-attributes{font-size:1.1em;line-height:1.2}.sheet-attributes table{width:100%;height:100%}.sheet-attributes td:first-child,.sheet-attributes .sheet-mod{font-weight:bold}.sheet-attributes td:not(:first-child){text-align:center}.sheet-attributes input[type=number]{width:45px}.sheet-attributes td:nth-child(2n+3) input[type=number]{width:37px}.sheet-attributes .sheet-mod{font-size:1.1em}.sheet-editmode[value="0"]~.sheet-content .sheet-resources .repcontainer{grid:auto/1fr 1fr}.sheet-resources .sheet-credits{margin-left:auto}.sheet-resources .repcontainer{display:grid;grid-row-gap:3px;margin-top:3px}.sheet-resources input[type=text]{width:calc(100% - 8em);margin-right:auto}.sheet-resources .sheet-slash{margin:0 2px}.sheet-weapons .sheet-edit{justify-content:space-between;padding-right:1px}.sheet-weapons .sheet-edit .sheet-name{width:138px}.sheet-weapons .sheet-edit .sheet-ab{width:45px}.sheet-weapons .sheet-edit .sheet-weapon-skill{width:80px}.sheet-weapons .sheet-edit .sheet-attribute{width:60px}.sheet-weapons .sheet-edit .sheet-burst{width:65px}.sheet-weapons .sheet-edit .sheet-damage{width:60px}.sheet-weapons .sheet-edit .sheet-shock-damage{width:55px}.sheet-weapons .sheet-edit .sheet-shock-ac{width:55px}.sheet-weapons .sheet-edit .sheet-skill-to-damage{width:30px}.sheet-weapons .sheet-edit .sheet-range{width:60px}.sheet-weapons .sheet-edit .sheet-ammo{width:80px}.sheet-weapons .sheet-edit .sheet-enc{width:40px}.sheet-weapons .sheet-text-input{padding-right:3px}.sheet-weapons .repitem{margin-bottom:3px}.sheet-weapons .sheet-display{font-size:1.1em;border:1px solid #888;text-indent:-10px;padding:2px 4px 2px 14px}.sheet-weapons .sheet-display .sheet-name{font-weight:bold}.sheet-weapons .repitem:nth-child(odd) .sheet-display{background:#d3d3d3}.sheet-skill-container,.sheet-skills-repeating .repcontainer{display:grid;grid:auto/1fr 1fr;grid-gap:4px 1%}.sheet-skill{position:relative}.sheet-skill .sheet-display,.sheet-skill .sheet-edit{border:1px solid #888;padding:2px 4px;height:100%}.sheet-skill .sheet-display{font-weight:bold;font-size:1.1em}.sheet-skill .sheet-display .sheet-dice[value="3d6d1"]+.sheet-name::after{content:"3d6"}.sheet-skill .sheet-display .sheet-dice[value="4d6d2"]+.sheet-name::after{content:"4d6"}.sheet-skill .sheet-display .sheet-name{margin-right:auto}.sheet-skill .sheet-display .sheet-name::after{vertical-align:super;margin-left:2px;font-weight:normal;font-size:.7em}.sheet-skill .sheet-display .sheet-query{width:55px;background:transparent;border-color:transparent;position:relative;height:18px;z-index:2;margin:0 2px;outline:none;color:inherit;flex-shrink:0;line-height:16px;padding:0}.sheet-skill:not(:hover) .sheet-display .sheet-query:not(:focus){display:none}.sheet-skill .sheet-skills-query-display{margin:0 3px;flex-shrink:0;font-weight:normal}.sheet-skill .sheet-edit input[type=text].sheet-name{width:100%;margin-bottom:2px}.sheet-skill .sheet-edit div.sheet-name{flex-grow:1;width:0;padding:1px 2px}.sheet-skill .sheet-edit .sheet-level{margin:0 2px;padding:1px 2px;width:40px}.sheet-skill .sheet-edit .sheet-query{width:50px}.sheet-skill .sheet-edit .sheet-dice{width:35px}.sheet-skill .sheet-edit.sheet-flex-wrap select,.sheet-skill .sheet-edit.sheet-flex-wrap input{flex-grow:1}.sheet-skill.sheet-skill-points .sheet-display{font-size:1.1em;font-weight:bold;width:100%}.sheet-skill.sheet-skill-points .sheet-name{background:#000;color:#fff;flex-grow:0;margin-right:auto;width:auto;padding:0 4px}.sheet-skill.sheet-skill-points .sheet-edit .sheet-level{margin:0}.sheet-skill-container .sheet-skill:nth-of-type(4n+1),.sheet-skill-container .sheet-skill:nth-of-type(4n+2){background:#d3d3d3}.repcontainer[data-groupname=repeating_skills] .repitem:nth-of-type(4n+1) .sheet-skill,.repcontainer[data-groupname=repeating_skills] .repitem:nth-of-type(4n+2) .sheet-skill{background:#d3d3d3}.sheet-skills-type:not([value=neither])~div .sheet-skills-repeating,.sheet-psionics .sheet-skills-repeating,.sheet-magic .sheet-skills-repeating{margin-top:4px}.sheet-skill-blurb{margin-top:3px;font-style:italic;text-align:center}.sheet-details-button{margin-left:auto;font-weight:bold;color:#999;letter-spacing:1px;cursor:pointer;text-transform:uppercase;font-size:10px}.sheet-details-button input{visibility:hidden;position:absolute}.sheet-details-button input:checked+span{color:#333}.sheet-input-details:not([value="1"])~div .sheet-details{display:none}.sheet-super-type:not([value=psionics])~div .sheet-super-psionics-only,.sheet-super-type:not([value=magic])~div .sheet-super-magic-only,.sheet-super-type:not([value=both])~div .sheet-super-both,.sheet-super-type[value=magic]~div .sheet-super-psionics,.sheet-super-type[value=psionics]~div .sheet-super-magic,.sheet-super-type[value=neither]~div .sheet-super{display:none}.sheet-tab-input:not([value=psionics])~.sheet-psionics,.sheet-tab-input:not([value=magic])~.sheet-magic{display:none}.sheet-psionics-effort{display:grid;grid:auto/auto 45px 45px 45px 45px 80px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-psionics-effort h3{line-height:1}.sheet-techniques{margin-top:5px}.sheet-techniques>.sheet-flex-center{margin-bottom:5px}.sheet-techniques .sheet-display .sheet-discipline{margin-left:auto}.sheet-techniques .sheet-edit .sheet-discipline{margin-left:5px;width:90px}.sheet-magic-effort{display:grid;grid:auto/auto 45px 45px 45px 45px 80px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-magic-effort h3{line-height:1}.sheet-spells{margin-top:5px}.sheet-spells>.sheet-flex-center{margin-bottom:5px}.sheet-spells>.sheet-flex{border-bottom:1px solid #666;margin-bottom:3px}.sheet-spells>.sheet-flex :not(:first-child){text-align:center}.sheet-spells .sheet-edit .sheet-name{width:220px;flex-grow:1}.sheet-spells .sheet-edit .sheet-prepared{margin:0 3px}.sheet-spells .sheet-edit .sheet-prepared,.sheet-spells .sheet-edit .sheet-damage{width:60px}.sheet-spells .sheet-display .sheet-name{padding-bottom:1px}.sheet-spells .sheet-display span.sheet-prepared{margin:0 4px 0 auto}.sheet-spells .sheet-display input[type=number].sheet-prepared{padding:0 4px;position:relative;z-index:2}.sheet-spell-slots{display:grid;grid:auto/auto 55px 55px 55px 55px 55px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-spell-slots h3{line-height:1}.sheet-magic-type:not([value=adept])~div .sheet-adept,.sheet-magic-type:not([value=magister])~div .sheet-magister,.sheet-magic-type:not([value=arcanist])~div .sheet-arcanist,.sheet-magic-type[value=adept]~div .sheet-spellcaster{display:none}.sheet-foci-loc[value=left]~.sheet-column .sheet-foci-right,.sheet-foci-loc[value=right]~.sheet-column .sheet-foci-left{display:none}.sheet-foci .sheet-details-button{position:relative;z-index:2}.sheet-foci .sheet-class-ability{position:relative}.sheet-foci .sheet-class-ability .sheet-edit .sheet-name{font-weight:bold;font-size:1.1em;border-bottom:1px solid #777}.sheet-foci .sheet-class-ability .sheet-name,.sheet-foci .sheet-class-ability .sheet-edit textarea,.sheet-foci .sheet-class-ability .sheet-display{margin-bottom:3px}.sheet-foci .sheet-edit .sheet-name{flex-grow:1}.sheet-foci .sheet-edit .sheet-level{margin-left:3px;width:60px}.sheet-foci .sheet-display .sheet-level{margin-left:auto}.sheet-armor .sheet-armor-grid{grid:auto/40px 1fr 40px 75px 35px 35px 70px}.sheet-armor .sheet-details div,.sheet-armor .sheet-details label{grid-column:2/8}.sheet-armor .sheet-details .sheet-display{line-height:1.3}.sheet-readied-stowed{display:grid;align-items:center;grid:auto/1fr 60px 60px 80px 80px;grid-column-gap:7px}.sheet-readied-stowed .sheet-input-gear-over[value="1"]+.sheet-text-input input:first-child{color:red}.sheet-cyberware h3{margin:5px 0}.sheet-cyberware .sheet-edit{flex-wrap:wrap}.sheet-cyberware .sheet-edit input[type=text]{width:0;flex-grow:1}.sheet-cyberware .sheet-edit>span{margin:0 3px}.sheet-equipment .repcontainer[data-groupname=repeating_weapons]+.repcontrol{display:none}.sheet-equipment .sheet-gear-grid{grid:auto/35px minmax(130px, 1fr) 2fr 35px 70px 40px}.sheet-equipment .sheet-other h3{margin-bottom:5px}.sheet-equipment .sheet-other-gear{height:100px}.sheet-misc .sheet-text-input input{padding-left:2px}.sheet-misc .repitem{margin-bottom:2px}.sheet-misc .sheet-xp{width:50px}.sheet-notes{margin-top:5px}.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-frigate,.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-cruiser,.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship-class[value=frigate]~.sheet-section .sheet-generate-source .sheet-cruiser,.sheet-ship-class[value=frigate]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship-class[value=cruiser]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship{margin-top:-20px;grid:auto/2fr 1fr 1fr}.sheet-ship .sheet-menus h3{line-height:1}.sheet-ship .sheet-menus button[type=roll].btn{line-height:1}.sheet-ship .sheet-type{padding:5px 8px}.sheet-ship .sheet-type h3{line-height:1}.sheet-ship .sheet-type select{height:20px;width:80px;background:transparent}.sheet-ship .sheet-info{flex-wrap:wrap}.sheet-ship .sheet-info .sheet-text-input{margin-bottom:3px}.sheet-ship .sheet-data{display:grid;grid:auto/repeat(3, 1fr)}.sheet-ship .sheet-data .sheet-text-input,.sheet-ship .sheet-data .sheet-text-input input[type=number]{text-align:center}.sheet-ship .sheet-stats{display:grid;grid:auto/1fr 1fr}.sheet-ship .sheet-input-color-negative[value^="-"]+.sheet-text-input input:first-child{color:red}.sheet-ship .repcontainer{display:grid;grid-row-gap:3px}.sheet-ship .repitem,.sheet-ship .repitem .sheet-edit,.sheet-ship .repitem .sheet-display{width:100%}.sheet-ship .sheet-grey-box-bordered{padding-right:0}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-name{width:calc(100% - 200px)}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-damage{width:65px}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-duo{width:80px}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-qualities{width:calc(100% - 195px)}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-range{width:70px}.sheet-ship .sheet-ship-weapons .sheet-display{text-indent:-10px;padding-left:14px}.sheet-ship .sheet-ship-defenses .sheet-edit .sheet-name,.sheet-ship .sheet-ship-defenses .sheet-edit .sheet-effect,.sheet-ship .sheet-ship-fitting .sheet-edit .sheet-name,.sheet-ship .sheet-ship-fitting .sheet-edit .sheet-effect{flex:1 1 auto;width:0}.sheet-ship .sheet-ship-defenses .sheet-display,.sheet-ship .sheet-ship-fitting .sheet-display{text-indent:-10px;padding:2px 4px 2px 14px}.sheet-ship .sheet-edit .sheet-broken{width:50px}.sheet-ship .sheet-edit .sheet-number{width:50px}.sheet-ship .sheet-display .sheet-name{font-weight:bold}.sheet-ship .sheet-display .sheet-broken{color:red;text-transform:uppercase}.sheet-ship .sheet-display .sheet-description{font-style:italic}.sheet-ship .sheet-input-type[value=SHIP]~div .sheet-vehicle-type:not(.sheet-vehicle-type-ship),.sheet-ship .sheet-input-type[value=MECH]~div .sheet-vehicle-type:not(.sheet-vehicle-type-mech),.sheet-ship .sheet-input-type[value=VEHICLE]~div .sheet-vehicle-type:not(.sheet-vehicle-type-custom){display:none}.sheet-editmode[value="0"]~.sheet-ship .sheet-ship-defenses,.sheet-editmode[value="0"]~.sheet-ship .sheet-ship-fitting{background:#f0f0f0;padding:3px 0}.sheet-npc{margin-top:-20px;grid:auto/3fr .8fr 2.2fr}.sheet-npc .sheet-input-individual-hide:not([value="1"])~.sheet-rolls .sheet-individual-hide{display:none}.sheet-npc .sheet-menus h3{line-height:1}.sheet-npc .sheet-menus button[type=roll].btn{line-height:1}.sheet-npc .sheet-info{display:grid;grid:auto/repeat(5, 1fr) 1.3fr}.sheet-npc .sheet-npc-hp{display:grid;grid:auto/40px 1fr}.sheet-npc .sheet-individual-hide input[type=checkbox]{margin-right:2px}.sheet-npc .sheet-npc-rep-header{margin:4px}.sheet-npc .sheet-rolls>:first-child{margin-bottom:5px}.sheet-npc .sheet-npc-checkbox{font-size:.9em}.sheet-npc .sheet-npc-checkbox span{vertical-align:middle;margin-right:4px}.sheet-npc .repcontainer{display:grid;grid-gap:3px}.sheet-npc .sheet-full-attacks{margin:0 6px 0 auto}.sheet-npc .sheet-grey-box-bordered{padding-right:1px}.sheet-npc .sheet-text-input{padding-right:3px}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-name{width:0;flex-grow:1}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-attacks,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-ab,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-range{width:50px}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-burst,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-damage{width:65px}.sheet-npc .sheet-npc-attacks .sheet-display{font-size:1.1em;font-weight:bold;padding:4px 8px}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-range[value="0"]+span{display:none}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-attacks[value="1"]+span{display:none}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-attacks{text-transform:lowercase}.sheet-npc .sheet-npc-abilities .sheet-npc-checkbox{margin-left:auto}.sheet-npc .sheet-npc-abilities .sheet-edit .sheet-name{width:100px}.sheet-npc .sheet-npc-abilities .sheet-edit .sheet-description{width:0;flex-grow:1}.sheet-npc .sheet-npc-abilities .sheet-display{text-indent:-10px;padding:4px 8px 4px 18px}.sheet-npc .sheet-npc-abilities .sheet-display .sheet-name{font-weight:bold}.sheet-npc .sheet-npc-abilities .sheet-display .sheet-description{font-style:italic}.sheet-drone{display:grid;grid:86px 1fr/1fr 1fr;grid-gap:5px;align-items:start;border-top:1px solid #555;padding:5px 0}.sheet-drone .sheet-stats{display:grid;align-content:start;grid:auto/repeat(7, 1fr)}.sheet-drone .sheet-fittings .sheet-name{font-weight:bold}.sheet-drone .sheet-fittings .sheet-name:not(:empty)::after{content:". "}.sheet-drone .sheet-fittings .sheet-desc{font-style:italic}.sheet-drone .sheet-fittings .sheet-display{margin-bottom:3px}.sheet-drone .sheet-fitting-grid{grid:auto/0.8fr 1.2fr}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="3"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="4"]~.sheet-fitting-5{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-4,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-4,.sheet-drone .sheet-num-fittings[value="3"]~.sheet-fitting-4{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-3,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-3{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-2{display:none}.sheet-drone .sheet-weapon-grid{grid:auto/35px 1fr 50px 60px 35px 70px}.sheet-drone .sheet-weapon{position:relative}.sheet-drone .sheet-weapon .sheet-name{font-weight:bold}.sheet-drone .sheet-weapon .sheet-display{margin-right:4px}.sheet-drone .sheet-weapon .sheet-display div::after{content:","}.sheet-editmode[value="0"]~.sheet-content .sheet-drone .sheet-drone-weapons{display:flex;flex-wrap:wrap}.sheet-rolltemplate-swn-default,.sheet-rolltemplate-swn-attack,.sheet-rolltemplate-swn-skill,.sheet-rolltemplate-swn-save{margin-left:-41px;width:292px;color:#fff;font-family:"Arial",sans-serif}.sheet-rolltemplate-swn-default div,.sheet-rolltemplate-swn-attack div,.sheet-rolltemplate-swn-skill div,.sheet-rolltemplate-swn-save div{box-sizing:border-box}.withoutavatars .sheet-rolltemplate-swn-default,.withoutavatars .sheet-rolltemplate-swn-attack,.withoutavatars .sheet-rolltemplate-swn-skill,.withoutavatars .sheet-rolltemplate-swn-save{margin-left:-11px}.sheet-rolltemplate-swn-default .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-attack .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-skill .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-save .sheet-container .inlinerollresult{padding:0;font-family:inherit;background:transparent;border-color:transparent;border:none}.sheet-rolltemplate-swn-default .sheet-container,.sheet-rolltemplate-swn-attack .sheet-container,.sheet-rolltemplate-swn-skill .sheet-container,.sheet-rolltemplate-swn-save .sheet-container{min-height:175px;position:relative;padding:18px 20px;clip-path:polygon(0 7px, 7px 0, 173px 0, 177px 3px, 281px 3px, 289px 11px, 289px 82px, 100% 87px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 22px 100%, 0 calc(100% - 22px))}.sheet-rolltemplate-swn-default .sheet-header,.sheet-rolltemplate-swn-attack .sheet-header,.sheet-rolltemplate-swn-skill .sheet-header,.sheet-rolltemplate-swn-save .sheet-header{width:198px}.sheet-rolltemplate-swn-default .sheet-header .sheet-name,.sheet-rolltemplate-swn-attack .sheet-header .sheet-name,.sheet-rolltemplate-swn-skill .sheet-header .sheet-name,.sheet-rolltemplate-swn-save .sheet-header .sheet-name{margin-bottom:12px;font-size:13px;line-height:1;height:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:"Times New Roman",serif;font-weight:bold}.sheet-rolltemplate-swn-default .sheet-header .sheet-title,.sheet-rolltemplate-swn-attack .sheet-header .sheet-title,.sheet-rolltemplate-swn-skill .sheet-header .sheet-title,.sheet-rolltemplate-swn-save .sheet-header .sheet-title{font-size:14px;height:32px;line-height:16px;overflow:hidden;font-weight:bold;margin-bottom:24px}.sheet-rolltemplate-swn-default .sheet-content,.sheet-rolltemplate-swn-attack .sheet-content,.sheet-rolltemplate-swn-skill .sheet-content,.sheet-rolltemplate-swn-save .sheet-content{position:relative}.sheet-rolltemplate-swn-default .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-attack .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-skill .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-save .sheet-rolls .inlinerollresult{font-size:36px;font-weight:bold;line-height:29px;white-space:nowrap}.sheet-rolltemplate-swn-default .sheet-rolls,.sheet-rolltemplate-swn-attack .sheet-rolls,.sheet-rolltemplate-swn-skill .sheet-rolls,.sheet-rolltemplate-swn-save .sheet-rolls{height:35px;display:flex;align-items:start}.sheet-rolltemplate-swn-default .sheet-rollname,.sheet-rolltemplate-swn-attack .sheet-rollname,.sheet-rolltemplate-swn-skill .sheet-rollname,.sheet-rolltemplate-swn-save .sheet-rollname{font-size:12px;line-height:1}.sheet-rolltemplate-swn-default a,.sheet-rolltemplate-swn-attack a,.sheet-rolltemplate-swn-skill a,.sheet-rolltemplate-swn-save a{color:inherit}.sheet-rolltemplate-swn-default a:hover,.sheet-rolltemplate-swn-attack a:hover,.sheet-rolltemplate-swn-skill a:hover,.sheet-rolltemplate-swn-save a:hover{color:red}.sheet-rolltemplate-swn-default a[href^="~"],.sheet-rolltemplate-swn-attack a[href^="~"],.sheet-rolltemplate-swn-skill a[href^="~"],.sheet-rolltemplate-swn-save a[href^="~"]{padding:0;background:transparent;border:none;line-height:1.4}.sheet-rolltemplate-swn-default .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-black.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-default .sheet-name{color:#bababa}.sheet-rolltemplate-swn-default .sheet-rollname{margin-right:auto}.sheet-rolltemplate-swn-default .sheet-desc,.sheet-rolltemplate-swn-default .sheet-buttons{font-size:12px;line-height:1.4}.sheet-rolltemplate-swn-default .sheet-info{font-style:italic;font-size:10px;line-height:1;margin:-10px 0 5px}.sheet-rolltemplate-swn-default .sheet-info .inlinerollresult{font-size:1em;font-weight:normal}.sheet-rolltemplate-swn-attack .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-purple.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-attack .sheet-name{color:#b291ec}.sheet-rolltemplate-swn-attack .sheet-title .inlinerollresult{font-size:1em;pointer-events:none}.sheet-rolltemplate-swn-attack .sheet-damage{margin-left:auto}.sheet-rolltemplate-swn-attack .sheet-roll{margin-left:10px}.sheet-rolltemplate-swn-attack .sheet-rolls:not(:first-of-type){margin-top:5px}.sheet-rolltemplate-swn-attack .sheet-shock{font-style:italic;margin-top:4px;font-size:12px}.sheet-rolltemplate-swn-attack .sheet-info{font-style:italic;font-size:10px}.sheet-rolltemplate-swn-attack .sheet-info>span:first-child{margin-right:10px}.sheet-rolltemplate-swn-attack .sheet-info .inlinerollresult{font-weight:normal;font-size:1em;margin-left:2px}.sheet-rolltemplate-swn-attack .sheet-desc{font-size:12px;line-height:1.4}.sheet-rolltemplate-swn-skill .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-blue.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-skill .sheet-name{color:#80b0ff}.sheet-rolltemplate-swn-skill .sheet-title .sheet-italic{font-style:italic}.sheet-rolltemplate-swn-skill .sheet-title .inlinerollresult{font-size:1em;pointer-events:none}.sheet-rolltemplate-swn-skill .sheet-rollname{margin-right:auto}.sheet-rolltemplate-swn-save .sheet-container.sheet-success{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-green.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-save .sheet-container.sheet-success .sheet-name{color:#83db53}.sheet-rolltemplate-swn-save .sheet-container.sheet-failure{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-red.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-save .sheet-container.sheet-failure .sheet-name{color:#ff8080}.sheet-rolltemplate-swn-save .sheet-vs{margin:0 10px}.sheet-rolltemplate-swn-save .sheet-rollname{margin-right:auto} +.charsheet{background-color:#fff;padding:0 !important;font-size:12px;line-height:18px;min-width:800px;color:#000}div,span,label,textarea,button[type=roll],h1,h2,h3{box-sizing:border-box}label{margin:0;padding:0;color:inherit;font-size:1em;font-weight:normal;width:auto}p{font-size:1em;margin:0 0 5px;line-height:inherit}textarea{resize:vertical;padding:1px 4px;font-size:12px;margin:0;color:#000;display:block}input[type=text],input[type=number]{padding:1px 4px;font-size:12px;height:20px;color:#000}input[type=number][readonly=readonly],input[type=number].sheet-no-spinners{-moz-appearance:textfield}input[type=number][readonly=readonly]::-webkit-inner-spin-button,input[type=number][readonly=readonly]::-webkit-outer-spin-button,input[type=number].sheet-no-spinners::-webkit-inner-spin-button,input[type=number].sheet-no-spinners::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}select{margin:0;color:#000;font-size:12px;padding:1px 2px;line-height:18px;height:22px}button[type=roll].btn{margin:0}button[type=roll].sheet-unstyled-button{text-shadow:none;box-shadow:none;color:inherit;text-align:inherit;padding:0;border:none;border-radius:0;font-size:inherit;background:transparent}button[type=roll].sheet-unstyled-button::before{content:""}button[type=roll].sheet-invisible-button{position:absolute;z-index:1;opacity:0;left:0;width:100%;height:100%;top:0}button[type=roll].sheet-invisible-button:hover~div:not(.sheet-no-highlight){color:#2b3fea;border-color:#2b3fea}.sheet-hider[value="0"]+div,.sheet-hider[value="0"]+label,.sheet-hider[value="0"]+button,.sheet-hider[value="0"]+span{display:none}.sheet-hider[value=""]+button{display:none}.sheet-editmode[value="0"]~.sheet-content .sheet-empty-hider[value=""]+div{display:none}h1{font-family:"Contrail One",sans-serif;background-color:#9b9b9b;padding-left:5px;color:#fff;font-size:1.3em;line-height:22px;font-weight:normal}h1::after{content:"";position:absolute;right:0;background-color:#223297;background-position:center;background-size:100%;width:22px;height:22px}h2{font-family:"Contrail One",sans-serif;padding-left:5px;border:2px solid #000;background-color:#ccc;color:#000;font-size:1.1em;margin:5px 0;width:175px;line-height:18px}h3{font-size:1.2em;font-family:"Contrail One",sans-serif;color:#6b6b6b;line-height:1.5;font-weight:normal}.sheet-npc-input[value="0"]~.sheet-tabs .sheet-npc-tab,.sheet-npc-input[value="1"]~.sheet-tabs .sheet-pc-tab,.sheet-tab-input:not([value=character])~.sheet-character,.sheet-tab-input:not([value=ship])~.sheet-ship,.sheet-tab-input:not([value=drones])~.sheet-drones,.sheet-tab-input:not([value=npc])~.sheet-npc{display:none}.sheet-content{min-width:800px;display:grid;grid-gap:5px}.sheet-editmode[value="0"]~.sheet-content .sheet-edit,.sheet-editmode[value="0"]~.sheet-content .repcontrol,.sheet-editmode[value="1"]~.sheet-content .sheet-display{display:none}.sheet-header{margin-bottom:10px}.sheet-header input{opacity:0;position:absolute}.sheet-header .sheet-logo{width:480px}.sheet-header .sheet-npc-chooser{cursor:pointer;margin-left:auto;color:#fff}.sheet-header .sheet-npc-chooser span{padding:3px 6px}.sheet-header .sheet-npc-chooser input:not(:checked)~.sheet-pc-label,.sheet-header .sheet-npc-chooser input:checked~.sheet-npc-label{background:#11194b}.sheet-header .sheet-npc-chooser input:checked~.sheet-pc-label,.sheet-header .sheet-npc-chooser input:not(:checked)~.sheet-npc-label{background:#223297}.sheet-header .sheet-edit-button{margin:0 5px 0 20px}.sheet-header .sheet-settings-gear,.sheet-header .sheet-edit-button{cursor:pointer;font-size:16px;font-family:pictos}.sheet-header .sheet-settings-gear input:not(:checked)+span,.sheet-header .sheet-edit-button input:not(:checked)+span{color:#888}.sheet-header .sheet-settings-gear input:checked+span,.sheet-header .sheet-edit-button input:checked+span{color:#223297}.sheet-footer{font-size:10px;border-top:1px solid #ccc;margin-top:10px;color:#666}.sheet-footer button[type=roll].btn:hover{color:#223297}.sheet-tabs{margin-bottom:10px;width:100%;position:relative;z-index:2;border-top:1px solid #333;font-family:"Contrail One",sans-serif;flex-flow:row-reverse;justify-content:flex-end}.sheet-tabs input{opacity:0;position:absolute}.sheet-tabs input:checked+span{background:#11194b}.sheet-tabs input:not(:checked)+span{background:#223297}.sheet-tabs span{padding:1px 25px 0}.sheet-tabs label{cursor:pointer;line-height:17px;display:flex;margin:-1px -10px 0 0;color:#fff;clip-path:polygon(0 0, 100% 0, 100% 10px, calc(100% - 10px) 100%, 0 100%)}.sheet-flex{display:flex}.sheet-flex-start{display:flex;align-items:flex-start}.sheet-flex-center{display:flex;align-items:center}.sheet-flex-between{display:flex;align-items:center;justify-content:space-between}.sheet-flex-wrap{display:flex;flex-wrap:wrap;align-content:flex-start;align-items:flex-start}.sheet-grow{flex-grow:1}.sheet-halfgrid{display:grid;grid:auto/1fr 1fr;grid-gap:5px}.sheet-thirdsgrid{display:grid;grid:auto/repeat(3, 1fr);grid-column-gap:1%}.sheet-column>div:not(:first-child){margin-top:5px}.sheet-col-2{grid-column:2}.sheet-col-3{grid-column:3}.sheet-col-4{grid-column:4}.sheet-cols-2{grid-column:span 2}.sheet-cols-3{grid-column:span 3}.sheet-cols-4{grid-column:span 4}.sheet-rows-2{grid-row:span 2}.sheet-col55{width:55%}.sheet-col50{width:50%}.sheet-col40{width:40%}.sheet-colthird{width:calc(100%/3)}.sheet-col25{width:25%}.sheet-col20{width:20%}.sheet-colsixth{width:calc(100%/6)}.sheet-grey-box,.sheet-grey-box-bordered{padding:4px;background:#f0f0f0}.sheet-grey-box-bordered{border:1px solid #aaa}.sheet-core-box{padding:8px;background:#f0f0f0}.sheet-core-box-text{padding:8px 3px 3px 8px;background:#f0f0f0}input[type=text].sheet-inherit{border-radius:0;border:none;background:transparent;box-shadow:none;padding:0;color:inherit;font:inherit}.sheet-inline-input{position:relative;z-index:2;vertical-align:baseline;padding:0 3px}.sheet-text-input{color:#666;padding-right:5px;line-height:1}.sheet-text-input .sheet-label{font-size:.9em;text-align:inherit;line-height:14px;border-top:1px solid #aaa}.sheet-text-input .sheet-label input[type=checkbox]{height:14px;margin:0 3px 0 0}.sheet-text-input input[type=text],.sheet-text-input input[type=number],.sheet-text-input textarea{background:transparent;padding:1px 0;width:100%;border-radius:0;color:#223297;border:none;text-align:inherit;font-weight:inherit;box-shadow:none}.sheet-text-input input[type=text][readonly=readonly],.sheet-text-input input[type=number][readonly=readonly],.sheet-text-input textarea[readonly=readonly]{cursor:default}.sheet-text-input input[type=text],.sheet-text-input input[type=number]{font-size:1.1em}.sheet-text-input textarea{height:20px;min-height:20px}.sheet-text-input>input[type=checkbox]{height:14px;margin:3px 0}.sheet-text-input select{width:100%;height:20px;color:#223297;background:transparent;border:none}.sheet-text-input.sheet-important{font-weight:bold;font-size:1.1em}.sheet-text-input.sheet-center{text-align:center}.sheet-text-input.sheet-duo{display:grid;text-align:center;grid:auto/1fr 5px 1fr;align-items:center;align-content:start;grid-column-gap:4px}.sheet-text-input.sheet-duo>span{font-size:16px;color:#333}.sheet-text-input.sheet-duo .sheet-label{grid-column:1/4}.sheet-text-input button[type=roll].btn{width:100%}.sheet-text-input button[type=roll].btn:hover{color:red}.sheet-table-header{font-size:.9em;color:#666}.sheet-table-header .sheet-center{text-align:center}.sheet-table-header>div{padding-left:2px;border-bottom:1px solid #aaa}.sheet-blue-button{text-align:center;line-height:1.5}.sheet-blue-button button[type=roll].btn{background:#2b3fea;width:35px;height:35px;display:block;margin:0 auto;color:#fff}.sheet-blue-button button[type=roll].btn::before{content:""}.sheet-focusinfo{position:relative}.sheet-focusinfo .sheet-details{position:absolute;z-index:1;opacity:1;width:250px;top:22px;left:calc(50% - 125px);transition:all .3s;background:#fff;border:1px solid #777;border-radius:8px;padding:5px}.sheet-focusinfo input:not(:focus)~.sheet-details{visibility:hidden;opacity:0}.sheet-lower{text-transform:lowercase}.sheet-space-before::before,.sheet-space-after::after{content:" "}.sheet-colon-after::after{content:":"}.sheet-hide-empty[value=""]+span{display:none}.sheet-add-plus:not([value^="-"])+span::before{content:"+"}.sheet-api{font-size:.8em;background:#f0f0f0;border-radius:5px;padding:1px;line-height:1;vertical-align:super}.sheet-simple-repsection .repitem{margin-bottom:3px}.sheet-simple-repsection textarea{margin:3px 0 0;height:50px}.sheet-simple-repsection .sheet-display .sheet-name{font-weight:bold;font-size:1.1em;border-bottom:1px solid #777}.sheet-simple-repsection .sheet-display .sheet-details{line-height:1.4;text-align:justify;white-space:pre-wrap}.sheet-rep-grid{display:grid;align-items:start;grid-column-gap:3px;margin-bottom:3px}.sheet-rep-grid .sheet-text-input{padding:0}.sheet-skills-type[value=neither]~div .sheet-skills-either,.sheet-skills-type:not([value=first])~div .sheet-skills-first,.sheet-skills-type:not([value=revised])~div .sheet-skills-revised,.sheet-skills-type:not([value=neither])~div .sheet-skills-neither,.sheet-super-type:not([value=psionics]):not([value=both])~div .sheet-skills-psionics,.sheet-super-type:not([value=magic]):not([value=both])~div .sheet-skills-magic,.sheet-skills-query:not([value=hover])~div .sheet-skills-query-hover,.sheet-skills-query[value=query]~div .sheet-skills-query-display,.sheet-skills-query[value=query]~div .sheet-skills-query-ask{display:none}.sheet-skills-query-display input:not([value*=str])~.sheet-str,.sheet-skills-query-display input:not([value*=dex])~.sheet-dex,.sheet-skills-query-display input:not([value*=con])~.sheet-con,.sheet-skills-query-display input:not([value*=int])~.sheet-int,.sheet-skills-query-display input:not([value*=wis])~.sheet-wis,.sheet-skills-query-display input:not([value*=cha])~.sheet-cha{display:none}.sheet-skills-query[value=hover]~div .sheet-skill:hover .sheet-skills-query-display,.sheet-skill .sheet-display .sheet-query:focus+.sheet-skills-query-display{display:none}.repcontrol{display:flex;margin-top:4px}.repcontrol button{padding:3px 10px}.repcontrol_edit{order:1;margin-left:auto}.repcontrol button{background:#223297;color:#fff;box-shadow:none;text-shadow:none}.repcontrol_move,.repcontrol_del{padding:1px 10px}.itemcontrol{z-index:100}.sheet-section-toggle-header{position:relative}.sheet-section-toggle-header input{cursor:pointer;position:absolute;width:22px;height:100%;z-index:2;right:0;opacity:0}.sheet-section-toggle-header input+h1::after{background-image:url("https://svgshare.com/i/6Vz.svg")}.sheet-section-toggle-header input:not(:checked)+h1::after{transform:rotate(180deg)}.sheet-section-toggle-header+input+div{margin-top:5px}.sheet-settings label{font-weight:bold}.sheet-settings label span{margin-right:4px}.sheet-settings select{width:120px;display:inline-block}.sheet-settings select.sheet-wide{width:170px}.sheet-generate{margin-bottom:5px}.sheet-generate .sheet-generate-source{height:20px;width:170px}.sheet-generate .sheet-generate-info{margin:0 5px;min-height:20px;flex-grow:1;background:#f0f0f0;border-radius:4px;padding:1px 4px}.sheet-generate .sheet-generate-button{padding:1px 8px;border-radius:4px;cursor:pointer;background:#223297;color:#fff}.sheet-generate .sheet-generate-button input[type=checkbox]{position:absolute;visibility:hidden}.sheet-generate .sheet-generate-button:active{outline:5px auto;outline-offset:-2px}.sheet-core{display:grid;grid-column-gap:5px}.sheet-core.sheet-core-info{grid:auto/2.5fr 1fr .8fr}.sheet-core.sheet-core-info .sheet-info{flex-wrap:wrap;padding-right:3px}.sheet-core.sheet-core-info .sheet-info .sheet-important{width:calc(60% - 22px)}.sheet-core.sheet-core-info .sheet-info .sheet-level{width:44px}.sheet-core.sheet-core-info .sheet-info .sheet-level-rest{width:calc(40% - 22px)}.sheet-core.sheet-core-stats{grid:auto/minmax(310px, 1.2fr) minmax(260px, 1fr) minmax(220px, 0.8fr)}.sheet-core .sheet-main-content{margin-top:3px;display:grid;grid:auto/repeat(3, 1fr);grid-row-gap:3px}.sheet-core .sheet-main-content .sheet-left-margin{margin-left:15px}.sheet-saves .sheet-flex{margin-top:4px}.sheet-menus-content{display:grid;grid:auto/1fr 1fr}.sheet-menus button[type=roll].btn{color:#2b3fea;font-size:1.1em}.sheet-menus button[type=roll].btn:hover{color:red}.sheet-character .sheet-menus button[type=roll].btn{margin-right:auto}.sheet-attributes{font-size:1.1em;line-height:1.2}.sheet-attributes table{width:100%;height:100%}.sheet-attributes td:first-child,.sheet-attributes .sheet-mod{font-weight:bold}.sheet-attributes td:not(:first-child){text-align:center}.sheet-attributes input[type=number]{width:45px}.sheet-attributes td:nth-child(2n+3) input[type=number]{width:37px}.sheet-attributes .sheet-mod{font-size:1.1em}.sheet-editmode[value="0"]~.sheet-content .sheet-resources .repcontainer{grid:auto/1fr 1fr}.sheet-resources .sheet-credits{margin-left:auto}.sheet-resources .repcontainer{display:grid;grid-row-gap:3px;margin-top:3px}.sheet-resources input[type=text]{width:calc(100% - 8em);margin-right:auto}.sheet-resources .sheet-slash{margin:0 2px}.sheet-shells .sheet-shells-grid{grid:auto/40px 35px 35px 35px 45px 1fr}.sheet-transhuman-setting:not([value=transhuman])~.sheet-column .sheet-transhuman-enabled{display:none}.sheet-transhuman-setting[value=transhuman]~.sheet-core .sheet-transhuman-disabled{display:none}.sheet-weapons .sheet-edit{justify-content:space-between;padding-right:1px}.sheet-weapons .sheet-edit .sheet-name{width:138px}.sheet-weapons .sheet-edit .sheet-ab{width:45px}.sheet-weapons .sheet-edit .sheet-weapon-skill{width:80px}.sheet-weapons .sheet-edit .sheet-attribute{width:60px}.sheet-weapons .sheet-edit .sheet-burst{width:65px}.sheet-weapons .sheet-edit .sheet-damage{width:60px}.sheet-weapons .sheet-edit .sheet-shock-damage{width:55px}.sheet-weapons .sheet-edit .sheet-shock-ac{width:55px}.sheet-weapons .sheet-edit .sheet-skill-to-damage{width:30px}.sheet-weapons .sheet-edit .sheet-range{width:60px}.sheet-weapons .sheet-edit .sheet-ammo{width:80px}.sheet-weapons .sheet-edit .sheet-enc{width:40px}.sheet-weapons .sheet-text-input{padding-right:3px}.sheet-weapons .repitem{margin-bottom:3px}.sheet-weapons .sheet-display{font-size:1.1em;border:1px solid #888;text-indent:-10px;padding:2px 4px 2px 14px}.sheet-weapons .sheet-display .sheet-name{font-weight:bold}.sheet-weapons .repitem:nth-child(odd) .sheet-display{background:#d3d3d3}.sheet-skill-container,.sheet-skills-repeating .repcontainer{display:grid;grid:auto/1fr 1fr;grid-gap:4px 1%}.sheet-skill{position:relative}.sheet-skill .sheet-display,.sheet-skill .sheet-edit{border:1px solid #888;padding:2px 4px;height:100%}.sheet-skill .sheet-display{font-weight:bold;font-size:1.1em}.sheet-skill .sheet-display .sheet-dice[value="3d6d1"]+.sheet-name::after{content:"3d6"}.sheet-skill .sheet-display .sheet-dice[value="4d6d2"]+.sheet-name::after{content:"4d6"}.sheet-skill .sheet-display .sheet-name{margin-right:auto}.sheet-skill .sheet-display .sheet-name::after{vertical-align:super;margin-left:2px;font-weight:normal;font-size:.7em}.sheet-skill .sheet-display .sheet-query{width:55px;background:transparent;border-color:transparent;position:relative;height:18px;z-index:2;margin:0 2px;outline:none;color:inherit;flex-shrink:0;line-height:16px;padding:0}.sheet-skill:not(:hover) .sheet-display .sheet-query:not(:focus){display:none}.sheet-skill .sheet-skills-query-display{margin:0 3px;flex-shrink:0;font-weight:normal}.sheet-skill .sheet-edit input[type=text].sheet-name{width:100%;margin-bottom:2px}.sheet-skill .sheet-edit div.sheet-name{flex-grow:1;width:0;padding:1px 2px}.sheet-skill .sheet-edit .sheet-level{margin:0 2px;padding:1px 2px;width:40px}.sheet-skill .sheet-edit .sheet-query{width:50px}.sheet-skill .sheet-edit .sheet-dice{width:35px}.sheet-skill .sheet-edit.sheet-flex-wrap select,.sheet-skill .sheet-edit.sheet-flex-wrap input{flex-grow:1}.sheet-skill.sheet-skill-points .sheet-display{font-size:1.1em;font-weight:bold;width:100%}.sheet-skill.sheet-skill-points .sheet-name{background:#000;color:#fff;flex-grow:0;margin-right:auto;width:auto;padding:0 4px}.sheet-skill.sheet-skill-points .sheet-edit .sheet-level{margin:0}.sheet-skill-container .sheet-skill:nth-of-type(4n+1),.sheet-skill-container .sheet-skill:nth-of-type(4n+2){background:#d3d3d3}.repcontainer[data-groupname=repeating_skills] .repitem:nth-of-type(4n+1) .sheet-skill,.repcontainer[data-groupname=repeating_skills] .repitem:nth-of-type(4n+2) .sheet-skill{background:#d3d3d3}.sheet-skills-type:not([value=neither])~div .sheet-skills-repeating,.sheet-psionics .sheet-skills-repeating,.sheet-magic .sheet-skills-repeating{margin-top:4px}.sheet-skill-blurb{margin-top:3px;font-style:italic;text-align:center}.sheet-details-button{margin-left:auto;font-weight:bold;color:#999;letter-spacing:1px;cursor:pointer;text-transform:uppercase;font-size:10px}.sheet-details-button input{visibility:hidden;position:absolute}.sheet-details-button input:checked+span{color:#333}.sheet-input-details:not([value="1"])~div .sheet-details{display:none}.sheet-input-details:not([value="0"])~div .sheet-notdetails{display:none}.sheet-super-type:not([value=psionics])~div .sheet-super-psionics-only,.sheet-super-type:not([value=magic])~div .sheet-super-magic-only,.sheet-super-type:not([value=both])~div .sheet-super-both,.sheet-super-type[value=magic]~div .sheet-super-psionics,.sheet-super-type[value=psionics]~div .sheet-super-magic,.sheet-super-type[value=neither]~div .sheet-super{display:none}.sheet-tab-input:not([value=psionics])~.sheet-psionics,.sheet-tab-input:not([value=magic])~.sheet-magic{display:none}.sheet-psionics-effort{display:grid;grid:auto/auto 45px 45px 45px 45px 80px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-psionics-effort h3{line-height:1}.sheet-techniques{margin-top:5px}.sheet-techniques>.sheet-flex-center{margin-bottom:5px}.sheet-techniques .sheet-display .sheet-discipline{margin-left:auto}.sheet-techniques .sheet-edit .sheet-discipline{margin-left:5px;width:90px}.sheet-magic-effort{display:grid;grid:auto/auto 45px 45px 45px 45px 80px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-magic-effort h3{line-height:1}.sheet-spells{margin-top:5px}.sheet-spells>.sheet-flex-center{margin-bottom:5px}.sheet-spells>.sheet-flex{border-bottom:1px solid #666;margin-bottom:3px}.sheet-spells>.sheet-flex :not(:first-child){text-align:center}.sheet-spells .sheet-edit .sheet-name{width:220px;flex-grow:1}.sheet-spells .sheet-edit .sheet-prepared{margin:0 3px}.sheet-spells .sheet-edit .sheet-prepared,.sheet-spells .sheet-edit .sheet-damage{width:60px}.sheet-spells .sheet-display .sheet-name{padding-bottom:1px}.sheet-spells .sheet-display span.sheet-prepared{margin:0 4px 0 auto}.sheet-spells .sheet-display input[type=number].sheet-prepared{padding:0 4px;position:relative;z-index:2}.sheet-spell-slots{display:grid;grid:auto/auto 55px 55px 55px 55px 55px;align-items:center;grid-column-gap:4px;margin-top:5px}.sheet-spell-slots h3{line-height:1}.sheet-magic-type:not([value=adept])~div .sheet-adept,.sheet-magic-type:not([value=magister])~div .sheet-magister,.sheet-magic-type:not([value=arcanist])~div .sheet-arcanist,.sheet-magic-type[value=adept]~div .sheet-spellcaster{display:none}.sheet-ai-processing{display:grid;grid:auto/auto 45px 45px 45px 45px 80px;align-items:center;grid-column-gap:4px}.sheet-ai-processing h3{line-height:1}.sheet-ai-processing-nodes .sheet-nodes-grid{grid:auto/65px 65px 1fr}.sheet-ai-setting:not([value=ai])~.sheet-ai-enabled{display:none}.sheet-ai-setting[value=ai]~.sheet-column .sheet-ai-enabled{display:block}.sheet-ai-setting[value=ai]~.sheet-core .sheet-ai-disabled{display:none}.sheet-foci-loc[value=left]~.sheet-column .sheet-foci-right,.sheet-foci-loc[value=right]~.sheet-column .sheet-foci-left{display:none}.sheet-foci .sheet-details-button{position:relative;z-index:2}.sheet-foci .sheet-class-ability{position:relative}.sheet-foci .sheet-class-ability .sheet-edit .sheet-name{font-weight:bold;font-size:1.1em;border-bottom:1px solid #777}.sheet-foci .sheet-class-ability .sheet-name,.sheet-foci .sheet-class-ability .sheet-edit textarea,.sheet-foci .sheet-class-ability .sheet-display{margin-bottom:3px}.sheet-foci .sheet-edit .sheet-name{flex-grow:1}.sheet-foci .sheet-edit .sheet-level{margin-left:3px;width:60px}.sheet-foci .sheet-display .sheet-level{margin-left:auto}.sheet-armor .sheet-armor-grid{grid:auto/40px 1fr 40px 75px 35px 35px 70px}.sheet-armor .sheet-details div,.sheet-armor .sheet-details label{grid-column:2/8}.sheet-armor .sheet-details .sheet-display{line-height:1.3}.sheet-readied-stowed{display:grid;align-items:center;grid:auto/1fr 60px 60px 80px 80px;grid-column-gap:7px}.sheet-readied-stowed .sheet-input-gear-over[value="1"]+.sheet-text-input input:first-child{color:red}.sheet-cyberware h3{margin:5px 0}.sheet-cyberware .sheet-edit{flex-wrap:wrap}.sheet-cyberware .sheet-edit input[type=text]{width:0;flex-grow:1}.sheet-cyberware .sheet-edit>span{margin:0 3px}.sheet-equipment .repcontainer[data-groupname=repeating_weapons]+.repcontrol{display:none}.sheet-equipment .sheet-gear-grid{grid:auto/35px minmax(130px, 1fr) 2fr 35px 70px 40px}.sheet-equipment .sheet-other h3{margin-bottom:5px}.sheet-equipment .sheet-other-gear{height:100px}.sheet-misc .sheet-text-input input{padding-left:2px}.sheet-misc .repitem{margin-bottom:2px}.sheet-misc .sheet-xp{width:50px}.sheet-notes{margin-top:5px}.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-frigate,.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-cruiser,.sheet-ship-class[value=fighter]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship-class[value=frigate]~.sheet-section .sheet-generate-source .sheet-cruiser,.sheet-ship-class[value=frigate]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship-class[value=cruiser]~.sheet-section .sheet-generate-source .sheet-capital{display:none}.sheet-ship{margin-top:-20px;grid:auto/2fr 1fr 1fr}.sheet-ship .sheet-menus h3{line-height:1}.sheet-ship .sheet-menus button[type=roll].btn{line-height:1}.sheet-ship .sheet-type{padding:5px 8px}.sheet-ship .sheet-type h3{line-height:1}.sheet-ship .sheet-type select{height:20px;width:80px;background:transparent}.sheet-ship .sheet-info{flex-wrap:wrap}.sheet-ship .sheet-info .sheet-text-input{margin-bottom:3px}.sheet-ship .sheet-data{display:grid;grid:auto/repeat(3, 1fr)}.sheet-ship .sheet-data .sheet-text-input,.sheet-ship .sheet-data .sheet-text-input input[type=number]{text-align:center}.sheet-ship .sheet-stats{display:grid;grid:auto/1fr 1fr}.sheet-ship .sheet-input-color-negative[value^="-"]+.sheet-text-input input:first-child{color:red}.sheet-ship .repcontainer{display:grid;grid-row-gap:3px}.sheet-ship .repitem,.sheet-ship .repitem .sheet-edit,.sheet-ship .repitem .sheet-display{width:100%}.sheet-ship .sheet-grey-box-bordered{padding-right:0}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-name{width:calc(100% - 200px)}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-damage{width:65px}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-duo{width:80px}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-qualities{width:calc(100% - 195px)}.sheet-ship .sheet-ship-weapons .sheet-edit .sheet-range{width:70px}.sheet-ship .sheet-ship-weapons .sheet-display{text-indent:-10px;padding-left:14px}.sheet-ship .sheet-ship-defenses .sheet-edit .sheet-name,.sheet-ship .sheet-ship-defenses .sheet-edit .sheet-effect,.sheet-ship .sheet-ship-fitting .sheet-edit .sheet-name,.sheet-ship .sheet-ship-fitting .sheet-edit .sheet-effect{flex:1 1 auto;width:0}.sheet-ship .sheet-ship-defenses .sheet-display,.sheet-ship .sheet-ship-fitting .sheet-display{text-indent:-10px;padding:2px 4px 2px 14px}.sheet-ship .sheet-edit .sheet-broken{width:50px}.sheet-ship .sheet-edit .sheet-number{width:50px}.sheet-ship .sheet-display .sheet-name{font-weight:bold}.sheet-ship .sheet-display .sheet-broken{color:red;text-transform:uppercase}.sheet-ship .sheet-display .sheet-description{font-style:italic}.sheet-ship .sheet-input-type[value=SHIP]~div .sheet-vehicle-type:not(.sheet-vehicle-type-ship),.sheet-ship .sheet-input-type[value=MECH]~div .sheet-vehicle-type:not(.sheet-vehicle-type-mech),.sheet-ship .sheet-input-type[value=VEHICLE]~div .sheet-vehicle-type:not(.sheet-vehicle-type-custom){display:none}.sheet-editmode[value="0"]~.sheet-ship .sheet-ship-defenses,.sheet-editmode[value="0"]~.sheet-ship .sheet-ship-fitting{background:#f0f0f0;padding:3px 0}.sheet-npc{margin-top:-20px;grid:auto/3fr .8fr 2.2fr}.sheet-npc .sheet-input-individual-hide:not([value="1"])~.sheet-rolls .sheet-individual-hide{display:none}.sheet-npc .sheet-menus h3{line-height:1}.sheet-npc .sheet-menus button[type=roll].btn{line-height:1}.sheet-npc .sheet-info{display:grid;grid:auto/repeat(5, 1fr) 1.3fr}.sheet-npc .sheet-npc-hp{display:grid;grid:auto/40px 1fr}.sheet-npc .sheet-individual-hide input[type=checkbox]{margin-right:2px}.sheet-npc .sheet-npc-rep-header{margin:4px}.sheet-npc .sheet-rolls>:first-child{margin-bottom:5px}.sheet-npc .sheet-npc-checkbox{font-size:.9em}.sheet-npc .sheet-npc-checkbox span{vertical-align:middle;margin-right:4px}.sheet-npc .repcontainer{display:grid;grid-gap:3px}.sheet-npc .sheet-full-attacks{margin:0 6px 0 auto}.sheet-npc .sheet-grey-box-bordered{padding-right:1px}.sheet-npc .sheet-text-input{padding-right:3px}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-name{width:0;flex-grow:1}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-attacks,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-ab,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-range{width:50px}.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-burst,.sheet-npc .sheet-npc-attacks .sheet-edit .sheet-damage{width:65px}.sheet-npc .sheet-npc-attacks .sheet-display{font-size:1.1em;font-weight:bold;padding:4px 8px}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-range[value="0"]+span{display:none}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-attacks[value="1"]+span{display:none}.sheet-npc .sheet-npc-attacks .sheet-display .sheet-attacks{text-transform:lowercase}.sheet-npc .sheet-npc-abilities .sheet-npc-checkbox{margin-left:auto}.sheet-npc .sheet-npc-abilities .sheet-edit .sheet-name{width:100px}.sheet-npc .sheet-npc-abilities .sheet-edit .sheet-description{width:0;flex-grow:1}.sheet-npc .sheet-npc-abilities .sheet-display{text-indent:-10px;padding:4px 8px 4px 18px}.sheet-npc .sheet-npc-abilities .sheet-display .sheet-name{font-weight:bold}.sheet-npc .sheet-npc-abilities .sheet-display .sheet-description{font-style:italic}.sheet-drone{display:grid;grid:86px 1fr/1fr 1fr;grid-gap:5px;align-items:start;border-top:1px solid #555;padding:5px 0}.sheet-drone .sheet-stats{display:grid;align-content:start;grid:auto/repeat(7, 1fr)}.sheet-drone .sheet-fittings .sheet-name{font-weight:bold}.sheet-drone .sheet-fittings .sheet-name:not(:empty)::after{content:". "}.sheet-drone .sheet-fittings .sheet-desc{font-style:italic}.sheet-drone .sheet-fittings .sheet-display{margin-bottom:3px}.sheet-drone .sheet-fitting-grid{grid:auto/0.8fr 1.2fr}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="3"]~.sheet-fitting-5,.sheet-drone .sheet-num-fittings[value="4"]~.sheet-fitting-5{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-4,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-4,.sheet-drone .sheet-num-fittings[value="3"]~.sheet-fitting-4{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-3,.sheet-drone .sheet-num-fittings[value="2"]~.sheet-fitting-3{display:none}.sheet-drone .sheet-num-fittings[value="1"]~.sheet-fitting-2{display:none}.sheet-drone .sheet-weapon-grid{grid:auto/35px 1fr 50px 60px 35px 70px}.sheet-drone .sheet-weapon{position:relative}.sheet-drone .sheet-weapon .sheet-name{font-weight:bold}.sheet-drone .sheet-weapon .sheet-display{margin-right:4px}.sheet-drone .sheet-weapon .sheet-display div::after{content:","}.sheet-editmode[value="0"]~.sheet-content .sheet-drone .sheet-drone-weapons{display:flex;flex-wrap:wrap}.sheet-rolltemplate-swn-default,.sheet-rolltemplate-swn-attack,.sheet-rolltemplate-swn-skill,.sheet-rolltemplate-swn-save{margin-left:-41px;width:292px;color:#fff;font-family:"Arial",sans-serif}.sheet-rolltemplate-swn-default div,.sheet-rolltemplate-swn-attack div,.sheet-rolltemplate-swn-skill div,.sheet-rolltemplate-swn-save div{box-sizing:border-box}.withoutavatars .sheet-rolltemplate-swn-default,.withoutavatars .sheet-rolltemplate-swn-attack,.withoutavatars .sheet-rolltemplate-swn-skill,.withoutavatars .sheet-rolltemplate-swn-save{margin-left:-11px}.sheet-rolltemplate-swn-default .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-attack .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-skill .sheet-container .inlinerollresult,.sheet-rolltemplate-swn-save .sheet-container .inlinerollresult{padding:0;font-family:inherit;background:transparent;border-color:transparent;border:none}.sheet-rolltemplate-swn-default .sheet-container,.sheet-rolltemplate-swn-attack .sheet-container,.sheet-rolltemplate-swn-skill .sheet-container,.sheet-rolltemplate-swn-save .sheet-container{min-height:175px;position:relative;padding:18px 20px;clip-path:polygon(0 7px, 7px 0, 173px 0, 177px 3px, 281px 3px, 289px 11px, 289px 82px, 100% 87px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 22px 100%, 0 calc(100% - 22px))}.sheet-rolltemplate-swn-default .sheet-header,.sheet-rolltemplate-swn-attack .sheet-header,.sheet-rolltemplate-swn-skill .sheet-header,.sheet-rolltemplate-swn-save .sheet-header{width:198px}.sheet-rolltemplate-swn-default .sheet-header .sheet-name,.sheet-rolltemplate-swn-attack .sheet-header .sheet-name,.sheet-rolltemplate-swn-skill .sheet-header .sheet-name,.sheet-rolltemplate-swn-save .sheet-header .sheet-name{margin-bottom:12px;font-size:13px;line-height:1;height:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:"Times New Roman",serif;font-weight:bold}.sheet-rolltemplate-swn-default .sheet-header .sheet-title,.sheet-rolltemplate-swn-attack .sheet-header .sheet-title,.sheet-rolltemplate-swn-skill .sheet-header .sheet-title,.sheet-rolltemplate-swn-save .sheet-header .sheet-title{font-size:14px;height:32px;line-height:16px;overflow:hidden;font-weight:bold;margin-bottom:24px}.sheet-rolltemplate-swn-default .sheet-content,.sheet-rolltemplate-swn-attack .sheet-content,.sheet-rolltemplate-swn-skill .sheet-content,.sheet-rolltemplate-swn-save .sheet-content{position:relative}.sheet-rolltemplate-swn-default .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-attack .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-skill .sheet-rolls .inlinerollresult,.sheet-rolltemplate-swn-save .sheet-rolls .inlinerollresult{font-size:36px;font-weight:bold;line-height:29px;white-space:nowrap}.sheet-rolltemplate-swn-default .sheet-rolls,.sheet-rolltemplate-swn-attack .sheet-rolls,.sheet-rolltemplate-swn-skill .sheet-rolls,.sheet-rolltemplate-swn-save .sheet-rolls{height:35px;display:flex;align-items:start}.sheet-rolltemplate-swn-default .sheet-rollname,.sheet-rolltemplate-swn-attack .sheet-rollname,.sheet-rolltemplate-swn-skill .sheet-rollname,.sheet-rolltemplate-swn-save .sheet-rollname{font-size:12px;line-height:1}.sheet-rolltemplate-swn-default a,.sheet-rolltemplate-swn-attack a,.sheet-rolltemplate-swn-skill a,.sheet-rolltemplate-swn-save a{color:inherit}.sheet-rolltemplate-swn-default a:hover,.sheet-rolltemplate-swn-attack a:hover,.sheet-rolltemplate-swn-skill a:hover,.sheet-rolltemplate-swn-save a:hover{color:red}.sheet-rolltemplate-swn-default a[href^="~"],.sheet-rolltemplate-swn-attack a[href^="~"],.sheet-rolltemplate-swn-skill a[href^="~"],.sheet-rolltemplate-swn-save a[href^="~"]{padding:0;background:transparent;border:none;line-height:1.4}.sheet-rolltemplate-swn-default .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-black.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-default .sheet-name{color:#bababa}.sheet-rolltemplate-swn-default .sheet-rollname{margin-right:auto}.sheet-rolltemplate-swn-default .sheet-desc,.sheet-rolltemplate-swn-default .sheet-buttons{font-size:12px;line-height:1.4}.sheet-rolltemplate-swn-default .sheet-info{font-style:italic;font-size:10px;line-height:1;margin:-10px 0 5px}.sheet-rolltemplate-swn-default .sheet-info .inlinerollresult{font-size:1em;font-weight:normal}.sheet-rolltemplate-swn-attack .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-purple.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-attack .sheet-name{color:#b291ec}.sheet-rolltemplate-swn-attack .sheet-title .inlinerollresult{font-size:1em;pointer-events:none}.sheet-rolltemplate-swn-attack .sheet-damage{margin-left:auto}.sheet-rolltemplate-swn-attack .sheet-roll{margin-left:10px}.sheet-rolltemplate-swn-attack .sheet-rolls:not(:first-of-type){margin-top:5px}.sheet-rolltemplate-swn-attack .sheet-shock{font-style:italic;margin-top:4px;font-size:12px}.sheet-rolltemplate-swn-attack .sheet-info{font-style:italic;font-size:10px}.sheet-rolltemplate-swn-attack .sheet-info>span:first-child{margin-right:10px}.sheet-rolltemplate-swn-attack .sheet-info .inlinerollresult{font-weight:normal;font-size:1em;margin-left:2px}.sheet-rolltemplate-swn-attack .sheet-desc{font-size:12px;line-height:1.4}.sheet-rolltemplate-swn-skill .sheet-container{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-blue.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-skill .sheet-name{color:#80b0ff}.sheet-rolltemplate-swn-skill .sheet-title .sheet-italic{font-style:italic}.sheet-rolltemplate-swn-skill .sheet-title .inlinerollresult{font-size:1em;pointer-events:none}.sheet-rolltemplate-swn-skill .sheet-rollname{margin-right:auto}.sheet-rolltemplate-swn-save .sheet-container.sheet-success{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-green.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-save .sheet-container.sheet-success .sheet-name{color:#83db53}.sheet-rolltemplate-swn-save .sheet-container.sheet-failure{background:url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-top-red.png") top no-repeat,url("https://raw.githubusercontent.com/roll20/roll20-character-sheets/master/Stars_Without_Number_Revised/Assets/rt-bottom.png") bottom no-repeat,#0f0f0f}.sheet-rolltemplate-swn-save .sheet-container.sheet-failure .sheet-name{color:#ff8080}.sheet-rolltemplate-swn-save .sheet-vs{margin:0 10px}.sheet-rolltemplate-swn-save .sheet-rollname{margin-right:auto} diff --git a/Stars_Without_Number_Revised/Stars_Without_Number_Revised.html b/Stars_Without_Number_Revised/Stars_Without_Number_Revised.html index d14a07da7a..fe350a0be4 100644 --- a/Stars_Without_Number_Revised/Stars_Without_Number_Revised.html +++ b/Stars_Without_Number_Revised/Stars_Without_Number_Revised.html @@ -1,3 +1,8 @@ -

API

/
/
/
/
Mod

/
/

(, : ). :  / 
/

:
:
-

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

:

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

:
:
-

/
/
#

–

–

:

/
/
/
/

:(;; ;  ,  ,  ). :  / 
/

:( ,  ).

:( ,  ).

/

: ( )

/
( ). :  / 
/
( ). :  / 
/
( ). :  / 
/
+

API

Play as an AI, with mechanical Shells and advanced Processing and Routines. Rules for this are in the Deluxe Edition.

Run a Transhuman campaign with body-swapping. Rules for this are in the Deluxe edition.

/
/
/
/
Mod

/
/

(, : ). :  / 
/

:
:
-

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

:

+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +

:
:
-

Processing Nodes

AI Routines

/
/
#

–

–

:

/
/
/
/

:(;; ;  ,  ,  ). :  / 
/

:( ,  ).

:( ,  ).

/

: ( )

/
( ). :  / 
/
( ). :  / 
/
( ). :  / 
/
{{charname}}
{{title}}
{{#reaction}}
{{#rollLess() reaction 3}} {{/rollLess() reaction 3}}{{#rollBetween() reaction 3 5}} {{/rollBetween() reaction 3 5}}{{#rollBetween() reaction 6 8}} {{/rollBetween() reaction 6 8}}{{#rollBetween() reaction 9 11}} {{/rollBetween() reaction 9 11}}{{#rollGreater() reaction 11}} {{/rollGreater() reaction 11}}
{{reaction}}
{{/reaction}} {{#info}}
{{info}}
{{/info}} {{#rollGreater() damage 0}}
{{damage}}
{{/rollGreater() damage 0}} {{#allprops() charname title damage reaction desc info buttons}}
{{key}}
{{value}}
{{/allprops() charname title damage reaction desc info buttons}}
{{desc}}
{{buttons}}
{{charname}}
{{title}} {{#rollGreater() burst 0}}(){{/rollGreater() burst 0}} {{#rollLess() mod 0}}{{mod}}{{/rollLess() mod 0}}{{#rollGreater() mod 0}}+ {{mod}}{{/rollGreater() mod 0}}
{{attack}}
{{damage}}
{{#attack2}}
{{attack2}}
{{damage2}}
{{/attack2}}{{#attack3}}
{{attack3}}
{{damage3}}
{{/attack3}}{{#attack4}}
{{attack4}}
{{damage4}}
{{/attack4}} {{#attack5}}
{{attack5}}
{{damage5}}
{{/attack5}} {{#attack6}}
{{attack6}}
{{damage6}}
{{/attack6}} {{#attack7}}
{{attack7}}
{{damage7}}
{{/attack7}} {{#attack8}}
{{attack8}}
{{damage8}}
{{/attack8}} {{#shock}}
{{shock}}
{{/shock}}
{{#range}} {{range}}{{/range}}{{#ammo}} {{ammo}}{{/ammo}}
{{desc}}
{{charname}}
{{title}} {{attribute}} {{#rollLess() mod 0}}{{mod}}{{/rollLess() mod 0}}{{#rollGreater() mod 0}}+ {{mod}}{{/rollGreater() mod 0}}
{{dice}}
{{#save}} {{#^rollLess() dice save}}
{{charname}}
{{title}}
{{dice}}
{{save}}{{morale}}
{{/^rollLess() dice save}} {{#rollLess() dice save}}
{{charname}}
{{title}}
{{dice}}
{{save}}{{morale}}
{{/rollLess() dice save}} {{/save}} {{#morale}} {{#^rollGreater() dice morale}}
{{charname}}
{{title}}
{{dice}}
{{save}}{{morale}}
{{/^rollGreater() dice morale}} {{#rollGreater() dice morale}}
{{charname}}
{{title}}
{{dice}}
{{save}}{{morale}}
{{/rollGreater() dice morale}} {{/morale}}
- + \ No newline at end of file diff --git a/Stars_Without_Number_Revised/sheet.json b/Stars_Without_Number_Revised/sheet.json index a3502d7a3d..b13e4615b2 100644 --- a/Stars_Without_Number_Revised/sheet.json +++ b/Stars_Without_Number_Revised/sheet.json @@ -61,6 +61,15 @@ "description": "Run a space opera campaign with Heroic characters. Rules for this are in the Deluxe edition.", "descriptiontranslationkey": "HEROIC_CHARACTERS_DESC" }, + { + "attribute": "setting_transhuman_enable", + "displayname": "Transhuman Characters", + "displaytranslationkey": "TRANSHUMAN_CHARACTERS", + "type": "checkbox", + "value": "transhuman", + "description": "Run a Transhuman campaign with body-swapping. Rules for this are in the Deluxe edition.", + "descriptiontranslationkey": "TRANSHUMAN_CHARACTERS_DESC" + }, { "attribute": "setting_super_type", "displayname": "Subsystems", diff --git a/Stars_Without_Number_Revised/translation.json b/Stars_Without_Number_Revised/translation.json index 08ea6900f2..b1e0bada9e 100644 --- a/Stars_Without_Number_Revised/translation.json +++ b/Stars_Without_Number_Revised/translation.json @@ -26,8 +26,15 @@ "ADVANCED_NAV_COMPUTER_DESC": "Adds +2 for travelling familiar spike courses.", "ADVENTURER": "Adventurer", "ADVENTURER_CLASS_ABILITY": "To define your Adventurer’s abilities, pick two of the following three partial classes.\nPartial Expert: You gain a free level in a non-combat focus related to your background. Most concepts will take Specialist, though Diplomat, Starfarer, Healer, or some other focus might suit better. Gain an extra skill point every time you gain a character level which can be spent on any non-psychic, non-combat skill.\nPartial Psychic: You are a restricted psychic. Pick one psychic discipline as a bonus skill at level-0. You can improve this skill with foci or skill points gained from advancing a level, but you cannot learn or improve any other psychic skill. Your maximum Effort equals 1 plus this psychic skill’s level plus the best of your Wisdom or Constitution modifiers, down to a minimum of 1.\nPartial Warrior: You gain a free level in a combat focus related to your background. Gain +1 to your attack bonus at first and fifth levels. Gain 2 extra maximum hit points each level. Thus, at first level, you’d roll 1d6+2 for your maximum hit points. At second, you’d roll 2d6+4, and so forth.", + "AFFINITY?": "Affinity?", + "AFFINITY_ABILITIES": "Affinity Abilities", + "AI": "AI", + "AI_CHARACTER": "AI Character", + "AI_CHARACTER_DESC": "Play as an AI, with mechanical Shells and advanced Processing and Routines. Rules for this are in the Deluxe Edition.", "AI_COMPANION": "AI Companion", "AI_COMPANION_DESC": "True AI was never easily made by pretech scientists. AIs required individual care during their development, and a disturbing percentage of them had to be “braked” at sub-human levels in order to prevent runaway cogitation on imponderable philosophical questions and the ensuing metastatic insanity that followed. Some AIs managed to attain a human level of sentience before requiring brakes. The less brilliant of these AIs could be contained in hardware no larger than five or six centimeters on a side, usually with integral sensors and vocalization units. If an AI companion takes a liking to a PC, they can be useful allies; they all have effective Program and Fix skills at level 3 or greater, and may have additional capabilities. A distressing number of these AIs have their own purposes, however, that are not necessarily complementary to those of their “owners”.", + "AI_ROUTINE": "AI Routine", + "AI_ROUTINES": "AI Routines", "ALECTO": "Alecto", "ALERT": "Alert", "ALERT_DESC": "You are keenly aware of your surroundings and virtually impossible to take unaware. You have an instinctive alacrity of response that helps you act before less wary persons can think to move.\nLevel 1: Gain Notice as a bonus skill. You cannot be surprised, nor can others use the Execution Attack option on you. When you roll initiative, roll twice and take the best result.\nLevel 2: You always act first in a combat round unless someone else involved is also this Alert.", @@ -92,6 +99,7 @@ "BARBARIAN_HERO": "Barbarian Hero", "BARBARIAN_TRIBAL": "Barbarian Tribal", "BASE": "Base", + "BASIC_ABILITIES": "Basic Abilities", "BATTLESHIP": "Battleship", "BEAST": "Beast", "BEZOAR": "Bezoar", @@ -181,6 +189,7 @@ "COMBAT_SHOTGUN_DESC": "Combat shotguns are more complicated and temperamental versions of conventional shotguns. These weapons have substantially larger ammunition capacity and are capable of firing in burst mode. Combat shotguns can fire slug rounds just as normal shotguns.", "COMBAT_UNARMED": "Combat/Unarmed", "COMMITTED_EFFORT": "Committed Effort", + "COMMITTED_PROCESSING": "Committed Processing", "COMMUNICATIONS": "Communications", "COMM_SERVER": "Comm server", "COMM_SERVER_DESC": "A powerful base unit for providing communications without comsats. The server provides service between up to three dozen compads within 300 kilometers of the unit. Server usage can be locked to specific compads, and all transmissions are heavily encrypted. A comm server can function for several months on a type B cell.", @@ -193,6 +202,7 @@ "CONCERT_OF_MINDS_DESC": "As an On Turn action, the metapsion may Commit Effort and form a psychic gestalt with one or more willing psychics within three meters, including up to one other psychic per Metapsion skill level. This gestalt persists as long as the Effort remains committed, regardless of the subsequent distance between psychics. On their own turn, any member of the gestalt may use any power or technique known by any other member, using the other member’s skill levels as necessary and paying any Effort cost from their own pool. This gestalt shares only psychic power, not thoughts or senses. At the end of each round in which one or more members have used some other member’s powers or abilities on their turn of action, the metapsion must Commit Effort for the scene or the gestalt drops and cannot be re-established for the rest of the scene.", "CONNECT": "Connect", "CONNECTED": "Connected", + "CONNECTED?": "Connected?", "CONNECTED_DESC": "You’re remarkably gifted at making friends and forging ties with the people around you. Wherever you go, you always seem to know somebody useful to your ends.\nLevel 1: Gain Connect as a bonus skill. If you’ve spent at least a week in a not-entirely-hostile location, you’ll have built a web of contacts willing to do favors for you that are no more than mildly illegal. You can call on one favor per game day and the GM decides how far they’ll go for you.\nLevel 2: Once per game session, if it’s not entirely implausible, you meet someone you know who is willing to do modest favors for you. You can decide when and where you want to meet this person, but the GM decides who they are and what they can do for you.", "CONSTITUTION": "Constitution", "CONSTITUTION_DESC": "Hardiness, enduring injury, resisting toxins, going without food or sleep", @@ -501,6 +511,7 @@ "LIGHTNING_CHARGE_MANTLE": "Lightning Charge Mantle", "LINE_SHUNT": "Line shunt", "LINE_SHUNT_DESC": "These palm-sized discs are roughly one centimeter in thickness, with an adhesive side that will stick to virtually anything and a polychromatic phased compound on its shell which will shift color to blend with its surroundings. One or more line shunts must be applied at specific locations within a site to access the more well-defended varieties of computerized systems. Shunts employ phased energy manipulation tech, and so do not need to be directly in contact with a data line but only within a few centimeters. Line shunts are single-use, and keep functioning until removed.", + "LOCATION": "Location", "LOW_LIGHT_GOGGLES": "Low-light goggles", "LOW_LIGHT_GOGGLES_DESC": "These goggles provide a monochrome but serviceable view out to the wearer’s normal visual distance, provided that there is any illumination available at all. A type A power cell will operate these goggles for a week.", "LUCK": "Luck", @@ -669,6 +680,8 @@ "PRIMITIVE_BOW": "Primitive Bow", "PRIMITIVE_BOW_DESC": "Bows are uncommon weapons in the far future, though some lostworlder barbarians have nothing better. Some sophisticated \"conversion bows\" use special materials to convert the kinetic energy of the draw into a force field \"glazing\" around the arrow, improving penetration. Bows can be reloaded with a Move action, or faster if the Gunslinger focus is applied.", "PRIMITIVE_DRONE": "Primitive Drone", + "PROCESSING": "Processing", + "PROCESSING_NODES": "Processing Nodes", "PROFESSION": "Profession", "PROFICIENT": "Proficient", "PROFICIENT_APPORTATION": "Proficient Apportation", @@ -789,6 +802,8 @@ "SERIAL_KILLER": "Serial Killer", "SHEAR_RIFLE": "Shear Rifle", "SHEAR_RIFLE_DESC": "Shear rifles are the product of a relatively few functioning pretech manufactories. These weapons use miniaturized grav projectors to create dangerous repulsor fields inside a target, tearing the object apart along perfectly smooth planes. Shear rifles are completely silent in operation.", + "SHELL": "Shell", + "SHELLS": "Shells", "SHIELD": "Shield", "SHIELD_DESC": "Shields are an ancient yet extremely useful adjunct to body armor. Primitive shields are usually of wood or stretched hide, while more modern riot shields tend toward clear plastic. A force pavis is more effective, being a small pretech force disc projector that can absorb all manner of small arms fire. All shields require one free hand to use effectively. Shields grant a base AC to their user; 13 for primitive and riot shields, and 15 for a force pavis. If the bearer’s AC is already equal or better, the shield simply grants a +1 AC bonus. Primitive and riot shields are ignored by all weapons that ignore primitive armor. Aside from their AC benefit, a shield also renders the bearer immune to the first instance of melee Shock damage they take each round, assuming the shield is effective against the weapon being used.", "SHIP": "Ship", @@ -1026,6 +1041,8 @@ "TRADE_GOODS_DESC": "Glowbugs, ceramic fire lighters, antibiotics, ceraplast tools, and the other fruits of a TL4 civilization that might be valuable on more primitive worlds. A kilo of trade goods can usually be traded for at least a hundred credits worth of local products on more primitive worlds that are not well-served by interstellar merchants. The profit margin shrinks rapidly as interstellar trade becomes more common, and there is always the difficulty of finding local products that are actually worth a merchant’s journey.", "TRADE_METALS": "Trade metals", "TRADE_METALS_DESC": "The fruit of common TL4 asteroid mining, trade metals include gold, platinum, artificial gemstones, and other substances precious on many lost worlds and trivially valuable on spacefaring ones. A kilo worth of trade metals can be exchanged for as much as a thousand credits worth of local products on metal-poor worlds isolated from interstellar trade. Markets flood rapidly, however, and it usually doesn’t take more than a few merchant trips to persuade the locals to demand trade goods or credits.", + "TRANSHUMAN_CHARACTERS": "Transhuman Characters", + "TRANSHUMAN_CHARACTERS_DESC" : "Run a Transhuman campaign with body-swapping. Rules for this are in the Deluxe edition.", "TRANSLATOR_TORC": "Translator torc", "TRANSLATOR_TORC_DESC": "Each translator torc is keyed for two languages, and will automatically translate what it hears in one into speech in the other. The translations are eccentric in many cases, and there is always a several-second delay between each statement and its translation. Any attempts to exert social skills or Charisma through a translator torc suffer a -2 skill check penalty. The torc is powered by one type A power cell, which lasts for one week of regular use.", "TRANSMIT_THOUGHT": "Transmit Thought",