Files
roll20-character-sheets/Stars_Without_Number_Revised/Source/ts/calculations.ts
T
2021-03-01 00:03:43 +01:00

391 lines
18 KiB
TypeScript

/* 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: {[key: string]: any} = {
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)) || 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"], 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))) || 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) , 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 calculateStrain = () => {
//Add up all the strain
getAttrs(["strain_permanent", "strain_extra", "strain", "strain_max", "strain_above_max"], v => {
const strain = (parseInt(v.strain_permanent) || 0) + (parseInt(v.strain_extra) || 0)
const strain_above_max = strain > parseInt(v.strain_max) ? 1 : 0
mySetAttrs({
strain: strain,
strain_above_max: strain_above_max
}, v)
})
}
const calculateAttr = (attr: string) => {
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: string) => {
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) || 0, parseInt(v.dexterity_mod) || 0);
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: {[key: string]: string} = {};
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 => {
const level = parseInt(v.level)
if (v.setting_xp_scheme === "xp") {
if (level < 10) {
setAttrs({
xp_next: xp[level]
});
} else {
setAttrs({
xp_next: 93 + ((level - 9) * 24)
});
}
} else if (v.setting_xp_scheme === "money") {
setAttrs({
xp_next: 2500 * (2 * level)
});
}
});
};
const calculateGearReadiedStowed = () => {
const doCalc = (gearIDs: string[], weaponIDs: string[], armorIDs: string[]) => {
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: {[key: string]: string} = {};
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").toString().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.toString();
setting[`${prefix}_weapon_damage_display`] = `${damage || 0}\xa0${translate("DAMAGE").toString().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: string) => {
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: {[k: string]: string}, 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
});
});
});
};