mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-08-31 04:02:29 +00:00
5680 lines
210 KiB
JavaScript
5680 lines
210 KiB
JavaScript
/* global setAttrs:false, getAttrs:false, on:false, getSectionIDs:false, generateRowID:false, getTranslationByKey:false */
|
|
'use strict';
|
|
|
|
const currentVersion = '4.3.1';
|
|
const SKILLS = {
|
|
ACROBATICS: 'dexterity',
|
|
ANIMALHANDLING: 'wisdom',
|
|
ARCANA: 'intelligence',
|
|
ATHLETICS: 'strength',
|
|
DECEPTION: 'charisma',
|
|
HISTORY: 'intelligence',
|
|
INSIGHT: 'wisdom',
|
|
INTIMIDATION: 'charisma',
|
|
INVESTIGATION: 'intelligence',
|
|
MEDICINE: 'wisdom',
|
|
NATURE: 'intelligence',
|
|
PERCEPTION: 'wisdom',
|
|
PERFORMANCE: 'charisma',
|
|
PERSUASION: 'charisma',
|
|
RELIGION: 'intelligence',
|
|
SLEIGHTOFHAND: 'dexterity',
|
|
STEALTH: 'dexterity',
|
|
SURVIVAL: 'wisdom',
|
|
};
|
|
const CLASSES = ['barbarian', 'bard', 'cleric', 'druid', 'fighter', 'monk', 'paladin', 'ranger', 'rogue', 'sorcerer', 'warlock', 'wizard'];
|
|
const ABILITIES = ['strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma'];
|
|
|
|
const toggleVars = {
|
|
roll: '{{vs_ac=1}} @{roll_info} {{roll1=[[@{shaped_d20}cs>@{crit_range} + @{attack_formula}]]}} @{roll_setting}cs>@{crit_range} + @{attack_formula}]]}} {{targetAC=@{attacks_vs_target_ac}}} {{targetName=@{attacks_vs_target_name}}}',
|
|
saving_throw: '{{saving_throw_condition=@{saving_throw_condition}}} {{saving_throw_dc=@{saving_throw_dc}}} {{saving_throw_vs_ability=@{saving_throw_vs_ability}}} {{saving_throw_failure=@{saving_throw_failure}}} {{saving_throw_success=@{saving_throw_success}}} {{targetName=@{attacks_vs_target_name}}}',
|
|
damage: '{{damage=[[@{damage_formula}]]}} {{damage_type=@{damage_type}}} {{crit_damage=[[0d0 + @{damage_crit}[crit damage] @{damage_crit_formula}]]}}',
|
|
second_damage: '{{second_damage=[[@{second_damage_formula}]]}} {{second_damage_type=@{second_damage_type}}} {{second_crit_damage=[[0d0 + @{second_damage_crit}[crit damage] @{second_damage_crit_formula}]]}}',
|
|
heal: '{{heal=[[@{heal_formula}]]}}',
|
|
heal_query: '?{Heal Bonus Amount|}',
|
|
extras: '{{emote=@{emote}}} {{freetext=@{freetext}}} @{freeform}',
|
|
higher_level: '{{cast_as_level=@{higher_level_query}}}',
|
|
};
|
|
|
|
const capitalize = (string) => {
|
|
return string.replace(/\w\S*/g, (txt) => {
|
|
return txt.charAt(0).toUpperCase() + txt.substr(1);
|
|
});
|
|
};
|
|
const firstThree = (string) => {
|
|
return string.substring(0, 3);
|
|
};
|
|
const round = (value, places) => {
|
|
return +(`${Math.round(`${value}e+${places}`)}e-${places}`);
|
|
};
|
|
const calculatePercentDifference = (oldValue, newValue) => {
|
|
return Math.abs(((oldValue - newValue) / oldValue) * 100);
|
|
};
|
|
const isUndefined = (value) => {
|
|
if (typeof value === 'undefined' || value === '') {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
const getIntValue = (value, defaultValue) => {
|
|
if (!defaultValue) {
|
|
defaultValue = 0;
|
|
}
|
|
if (value === 0 || value === '0') {
|
|
return 0;
|
|
}
|
|
return parseInt(value, 10) || defaultValue;
|
|
};
|
|
const getFloatValue = (value, defaultValue) => {
|
|
if (!defaultValue) {
|
|
defaultValue = 0;
|
|
}
|
|
return parseFloat(value) || defaultValue;
|
|
};
|
|
const exists = (value) => {
|
|
if (isUndefined(value) || value === '' || value === '0' || value === 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
const getAbilityModName = (varName) => {
|
|
if (!varName) {
|
|
return 'strength_mod';
|
|
} else if (typeof varName === 'string') {
|
|
varName = varName.replace(/\W/g, '');
|
|
}
|
|
return varName;
|
|
};
|
|
const getAbilityName = (varName) => {
|
|
if (!varName) {
|
|
return 'strength';
|
|
}
|
|
return getAbilityModName(varName).replace('_mod', '');
|
|
};
|
|
const getAbilityValue = (v, varName, defaultAbility) => {
|
|
if (typeof varName === 'undefined') {
|
|
if (defaultAbility) {
|
|
return getIntValue(v[`${defaultAbility}_mod`]);
|
|
}
|
|
} else if (exists(varName)) {
|
|
varName = getAbilityModName(varName);
|
|
return getIntValue(v[`${varName}_mod`], 0);
|
|
}
|
|
return 0;
|
|
};
|
|
const getAbilityShortName = (varName, capital) => {
|
|
if (!varName) {
|
|
return 'Str';
|
|
}
|
|
varName = varName.replace(/\W/g, '');
|
|
if (capital) {
|
|
varName = capitalize(varName);
|
|
}
|
|
return firstThree(varName);
|
|
};
|
|
const getRepeatingInfo = (leadingString, eventInfo) => {
|
|
const re = new RegExp(`${leadingString}_(-[a-zA-Z0-9\-]*)_(.*)`);
|
|
const match = eventInfo.sourceAttribute.match(re);
|
|
|
|
let result;
|
|
if (match && match[1] && match[2]) {
|
|
result = {
|
|
rowId: match[1],
|
|
field: match[2],
|
|
};
|
|
}
|
|
return result;
|
|
};
|
|
const isEmpty = (obj) => {
|
|
for (const prop in obj) {
|
|
if (obj.hasOwnProperty(prop)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
const setFinalAttrs = (v, finalSetAttrs, callback) => {
|
|
if (!isEmpty(finalSetAttrs)) {
|
|
for (const key in finalSetAttrs) {
|
|
if (finalSetAttrs.hasOwnProperty(key)) {
|
|
if (v[key] === finalSetAttrs[key]) {
|
|
delete finalSetAttrs[key];
|
|
}
|
|
}
|
|
}
|
|
if (!isEmpty(finalSetAttrs)) {
|
|
console.info('finalSetAttrs', finalSetAttrs);
|
|
}
|
|
if (!isEmpty(finalSetAttrs)) {
|
|
if (callback) {
|
|
setAttrs(finalSetAttrs, {}, callback);
|
|
} else {
|
|
setAttrs(finalSetAttrs);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const fromVOrFinalSetAttrs = (v, finalSetAttrs, value) => {
|
|
if (exists(finalSetAttrs[value])) {
|
|
return finalSetAttrs[value];
|
|
}
|
|
return v[value];
|
|
};
|
|
const parseAttackComponent = (v, repeatingString, finalSetAttrs, options) => {
|
|
let parsed = v[`${repeatingString}parsed`];
|
|
|
|
if (isUndefined(parsed)) {
|
|
parsed = finalSetAttrs[`${repeatingString}parsed`];
|
|
}
|
|
|
|
if (isUndefined(parsed) || parsed.indexOf(options.parseName) === -1) {
|
|
let aTriggerFieldExists = false;
|
|
|
|
for (const triggerField of options.triggerFields) {
|
|
if (!isUndefined(v[repeatingString + triggerField])) {
|
|
aTriggerFieldExists = true;
|
|
}
|
|
}
|
|
if (aTriggerFieldExists && isUndefined(v[repeatingString + options.toggleField])) {
|
|
finalSetAttrs[repeatingString + options.toggleField] = options.toggleFieldSetTo;
|
|
|
|
if (isUndefined(finalSetAttrs[`${repeatingString}parsed`])) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ` ${options.parseName}`;
|
|
}
|
|
if (options.attackAbility && isUndefined(v[`${repeatingString}attack_ability`])) {
|
|
finalSetAttrs[`${repeatingString}attack_ability`] = v.default_ability;
|
|
}
|
|
|
|
if (options.addCastingModifier) {
|
|
if (!isUndefined(v[`${repeatingString}damage`]) && isUndefined(v[`${repeatingString}damage_ability`])) {
|
|
finalSetAttrs[`${repeatingString}damage_ability`] = v.default_ability;
|
|
}
|
|
if (!isUndefined(v[`${repeatingString}heal`]) && isUndefined(v[`${repeatingString}heal_ability`])) {
|
|
finalSetAttrs[`${repeatingString}heal_ability`] = v.default_ability;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const hasUpperCase = (string) => {
|
|
return (/[A-Z]/.test(string));
|
|
};
|
|
const ordinalSpellLevel = (level) => {
|
|
let spellLevel = '';
|
|
if (level === 0) {
|
|
spellLevel = 'Cantrip';
|
|
} else {
|
|
switch (level % 10) {
|
|
case 1:
|
|
spellLevel = `${level}st-level`;
|
|
break;
|
|
case 2:
|
|
spellLevel = `${level}nd-level`;
|
|
break;
|
|
case 3:
|
|
spellLevel = `${level}rd-level`;
|
|
break;
|
|
default:
|
|
spellLevel = `${level}th-level`;
|
|
break;
|
|
}
|
|
}
|
|
return spellLevel;
|
|
};
|
|
const versionCompare = (v1, v2, options) => {
|
|
const lexicographical = options && options.lexicographical;
|
|
const zeroExtend = options && options.zeroExtend;
|
|
let v1parts = v1.split('.');
|
|
let v2parts = v2.split('.');
|
|
|
|
const isValidPart = (x) => {
|
|
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
|
|
};
|
|
|
|
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
|
|
return NaN;
|
|
}
|
|
|
|
if (zeroExtend) {
|
|
while (v1parts.length < v2parts.length) {
|
|
v1parts.push('0');
|
|
}
|
|
while (v2parts.length < v1parts.length) {
|
|
v2parts.push('0');
|
|
}
|
|
}
|
|
|
|
if (!lexicographical) {
|
|
v1parts = v1parts.map(Number);
|
|
v2parts = v2parts.map(Number);
|
|
}
|
|
|
|
for (let i = 0; i < v1parts.length; ++i) {
|
|
if (v2parts.length === i) {
|
|
return 1;
|
|
}
|
|
|
|
if (v1parts[i] === v2parts[i]) {
|
|
continue;
|
|
} else if (v1parts[i] > v2parts[i]) {
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (v1parts.length !== v2parts.length) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
const getAbilityMod = (score) => {
|
|
return Math.floor((getIntValue(score) - 10) / 2);
|
|
};
|
|
const addArithmeticOperator = (string, number) => {
|
|
let value = number;
|
|
if (string) {
|
|
if (number < 0) {
|
|
value = ` - ${Math.abs(number)}`;
|
|
} else {
|
|
value = ` + ${number}`;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
const numberWithCommas = (x) => {
|
|
const parts = x.toString().split('.');
|
|
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
return parts.join('.');
|
|
};
|
|
const findClosest = (array, goal) => {
|
|
return array.reduce((prev, curr) => {
|
|
return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
|
|
});
|
|
};
|
|
const getCorrectAbilityBasedOnBonus = (finalSetAttrs, repeatingString, fieldName, bonus, spellMods, meleeMods, spellAttack, rangedAttack, dexMod) => {
|
|
let closest;
|
|
if (bonus) {
|
|
if (spellAttack) {
|
|
closest = findClosest(spellMods, bonus);
|
|
bonus -= closest;
|
|
if (closest === spellMods[0]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'intelligence';
|
|
} else if (closest === spellMods[1]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'wisdom';
|
|
} else if (closest === spellMods[2]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'charisma';
|
|
}
|
|
} else {
|
|
if (rangedAttack) {
|
|
finalSetAttrs[`${repeatingString}attack_ability`] = 'dexterity';
|
|
bonus -= dexMod;
|
|
} else {
|
|
closest = findClosest(meleeMods, bonus);
|
|
bonus -= closest;
|
|
if (closest === meleeMods[0]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'strength';
|
|
} else if (closest === meleeMods[1]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'dexterity';
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
finalSetAttrs[repeatingString + fieldName] = 0;
|
|
}
|
|
return bonus;
|
|
};
|
|
const getAnyCorrectAbilityBasedOnBonus = (finalSetAttrs, repeatingString, fieldName, bonus, abilityMods) => {
|
|
const closest = findClosest(abilityMods, bonus);
|
|
if (bonus) {
|
|
bonus -= closest;
|
|
|
|
if (closest === abilityMods[0]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'strength';
|
|
} else if (closest === abilityMods[1]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'dexterity';
|
|
} else if (closest === abilityMods[2]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'constitution';
|
|
} else if (closest === abilityMods[3]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'intelligence';
|
|
} else if (closest === abilityMods[4]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'wisdom';
|
|
} else if (closest === abilityMods[5]) {
|
|
finalSetAttrs[repeatingString + fieldName] = 'charisma';
|
|
}
|
|
} else {
|
|
finalSetAttrs[repeatingString + fieldName] = 0;
|
|
}
|
|
return bonus;
|
|
};
|
|
|
|
const lowercaseDamageTypes = (string) => {
|
|
if (!string) {
|
|
string = '';
|
|
}
|
|
return string
|
|
.replace('Acid', 'acid')
|
|
.replace('Cold', 'cold')
|
|
.replace('Fire', 'fire')
|
|
.replace('Force', 'force')
|
|
.replace('Lightning', 'lightning')
|
|
.replace('Necrotic', 'necrotic')
|
|
.replace('Poison', 'poison')
|
|
.replace('Psychic', 'psychic')
|
|
.replace('Radiant', 'radiant')
|
|
.replace('Thunder', 'thunder')
|
|
.replace('Bludgeoning', 'bludgeoning')
|
|
.replace('Piercing', 'piercing')
|
|
.replace('Slashing', 'slashing')
|
|
.replace('And', 'and')
|
|
.replace('From', 'from')
|
|
.replace('Nonmagical', 'nonmagical')
|
|
.replace('Weapons', 'weapons')
|
|
.replace('That', 'that')
|
|
.replace('Aren\'t', 'aren\'t')
|
|
.replace('Silvered', 'silvered')
|
|
.replace('Adamantine', 'adamantine');
|
|
};
|
|
const showSign = (value) => {
|
|
if (value >= 0) {
|
|
value = `+${value}`;
|
|
}
|
|
return value;
|
|
};
|
|
|
|
const camelize = (str) => {
|
|
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
|
|
if (+match === 0) return '';
|
|
return index === 0 ? match.toLowerCase() : match.toUpperCase();
|
|
});
|
|
};
|
|
|
|
const calculateGold = () => {
|
|
const collectionArray = ['cp', 'copper_per_gold', 'sp', 'silver_per_gold', 'ep', 'electrum_per_gold', 'gp', 'pp', 'platinum_per_gold'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const copperPieces = getFloatValue(v.cp);
|
|
const silverPieces = getFloatValue(v.sp);
|
|
const electrumPieces = getFloatValue(v.ep);
|
|
const goldPieces = getFloatValue(v.gp);
|
|
const platinumPieces = getFloatValue(v.pp);
|
|
const copperPerGold = getFloatValue(v.copper_per_gold, 100);
|
|
const silverPerGold = getFloatValue(v.silver_per_gold, 10);
|
|
const electrumPerGold = getFloatValue(v.electrum_per_gold, 2);
|
|
const platinumPerGold = getFloatValue(v.platinum_per_gold, 10);
|
|
|
|
finalSetAttrs.total_gp = ((copperPieces / copperPerGold) + (silverPieces / silverPerGold) + (electrumPieces / electrumPerGold) + goldPieces + (platinumPieces * platinumPerGold)).toFixed(2);
|
|
finalSetAttrs.weight_coinage = (copperPieces + silverPieces + electrumPieces + goldPieces + platinumPieces) / 50;
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:cp change:copper_per_gold change:sp change:silver_per_gold change:ep change:electrum_per_gold change:gp change:pp change:platinum_per_gold', () => {
|
|
calculateGold();
|
|
});
|
|
|
|
const updateAbilityModifier = (ability) => {
|
|
const collectionArray = [ability, `${ability}_bonus`, `${ability}_mod`, `${ability}_mod_with_sign`, `${ability}_check_mod`, `${ability}_check_mod_formula`, `${ability}_check_bonus`, 'global_ability_bonus', 'strength_mod', 'dexterity_mod', 'jack_of_all_trades_toggle', 'jack_of_all_trades', 'remarkable_athlete_toggle', 'remarkable_athlete', 'global_check_bonus'];
|
|
const finalSetAttrs = {};
|
|
|
|
if (ability === 'strength' || ability === 'dexterity') {
|
|
collectionArray.push('finesse_mod');
|
|
}
|
|
if (ability === 'strength') {
|
|
collectionArray.push('weight_multiplier');
|
|
collectionArray.push('carrying_capacity');
|
|
collectionArray.push('max_push_drag_lift');
|
|
collectionArray.push('encumbered');
|
|
collectionArray.push('heavily_encumbered');
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const abilityScore = getIntValue(v[ability]);
|
|
const abilityBonus = getIntValue(v[`${ability}_bonus`]);
|
|
const globalAbilityBonus = v.global_ability_bonus;
|
|
let abilityScoreCalc = abilityScore + abilityBonus;
|
|
|
|
if (!isNaN(globalAbilityBonus)) {
|
|
abilityScoreCalc += getIntValue(globalAbilityBonus);
|
|
}
|
|
|
|
const abilityCheckBonus = getIntValue(v[`${ability}_check_bonus`]);
|
|
const abilityMod = getAbilityMod(abilityScoreCalc);
|
|
|
|
let abilityCheck = abilityMod;
|
|
let abilityCheckFormula = '';
|
|
|
|
if (abilityMod !== 0) {
|
|
abilityCheckFormula = `${abilityMod}[${firstThree(ability)} mod with bonus]`;
|
|
}
|
|
if ((ability === 'strength' || ability === 'dexterity' || ability === 'constitution') && v.remarkable_athlete_toggle === '@{remarkable_athlete}') {
|
|
const remarkableAthlete = getIntValue(v.remarkable_athlete);
|
|
abilityCheck += remarkableAthlete;
|
|
abilityCheckFormula += `${addArithmeticOperator(abilityCheckFormula, remarkableAthlete)}[remarkable athlete]`;
|
|
} else if (v.jack_of_all_trades_toggle === '@{jack_of_all_trades}') {
|
|
const jackOfAllTrades = getIntValue(v.jack_of_all_trades);
|
|
abilityCheck += jackOfAllTrades;
|
|
abilityCheckFormula += `${addArithmeticOperator(abilityCheckFormula, jackOfAllTrades)}[jack of all trades]`;
|
|
}
|
|
if (abilityCheckBonus) {
|
|
abilityCheck += abilityCheckBonus;
|
|
abilityCheckFormula += `${addArithmeticOperator(abilityCheckFormula, abilityCheckBonus)}[${ability} check bonus]`;
|
|
}
|
|
if (v.global_check_bonus) {
|
|
if (!isNaN(v.global_check_bonus)) {
|
|
abilityCheck += getIntValue(v.global_check_bonus);
|
|
}
|
|
if (abilityCheckFormula) {
|
|
abilityCheckFormula += ' + ';
|
|
}
|
|
abilityCheckFormula += '(@{global_check_bonus})[global check bonus]';
|
|
}
|
|
|
|
finalSetAttrs[`${ability}_calculated`] = abilityScoreCalc;
|
|
finalSetAttrs[`${ability}_mod`] = abilityMod;
|
|
finalSetAttrs[`${ability}_mod_with_sign`] = showSign(abilityMod);
|
|
finalSetAttrs[`${ability}_check_mod`] = abilityCheck;
|
|
finalSetAttrs[`${ability}_check_mod_with_sign`] = showSign(abilityCheck);
|
|
finalSetAttrs[`${ability}_check_mod_formula`] = abilityCheckFormula;
|
|
|
|
if (ability === 'strength') {
|
|
finalSetAttrs.finesse_mod = Math.max(abilityMod, getIntValue(v.dexterity_mod));
|
|
const weightMultiplier = getFloatValue(v.weight_multiplier, 1);
|
|
finalSetAttrs.carrying_capacity = abilityScore * 15 * weightMultiplier;
|
|
finalSetAttrs.max_push_drag_lift = abilityScore * 30 * weightMultiplier;
|
|
finalSetAttrs.encumbered = abilityScore * 5 * weightMultiplier;
|
|
finalSetAttrs.heavily_encumbered = abilityScore * 10 * weightMultiplier;
|
|
} else if (ability === 'dexterity') {
|
|
finalSetAttrs.finesse_mod = Math.max(abilityMod, getIntValue(v.strength_mod));
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
const updateAbilityModifiers = () => {
|
|
updateAbilityModifier('strength');
|
|
updateAbilityModifier('dexterity');
|
|
updateAbilityModifier('constitution');
|
|
updateAbilityModifier('intelligence');
|
|
updateAbilityModifier('wisdom');
|
|
updateAbilityModifier('charisma');
|
|
};
|
|
|
|
on('change:jack_of_all_trades_toggle change:jack_of_all_trades change:global_ability_bonus change:global_check_bonus', () => {
|
|
updateAbilityModifiers();
|
|
});
|
|
on('change:strength change:strength_bonus change:strength_check_mod change:strength_check_bonus change:remarkable_athlete_toggle change:remarkable_athlete change:weight_multiplier', () => {
|
|
updateAbilityModifier('strength');
|
|
});
|
|
on('change:dexterity change:dexterity_bonus change:dexterity_check_mod change:dexterity_check_bonus change:remarkable_athlete_toggle change:remarkable_athlete', () => {
|
|
updateAbilityModifier('dexterity');
|
|
});
|
|
on('change:constitution change:constitution_bonus change:constitution_check_mod change:constitution_check_bonus change:remarkable_athlete_toggle change:remarkable_athlete', () => {
|
|
updateAbilityModifier('constitution');
|
|
});
|
|
on('change:intelligence change:intelligence_bonus change:intelligence_check_mod change:intelligence_check_bonus', () => {
|
|
updateAbilityModifier('intelligence');
|
|
});
|
|
on('change:wisdom change:wisdom_bonus change:wisdom_check_mod change:wisdom_check_bonus', () => {
|
|
updateAbilityModifier('wisdom');
|
|
});
|
|
on('change:charisma change:charisma_bonus change:charisma_check_mod change:charisma_check_bonus', () => {
|
|
updateAbilityModifier('charisma');
|
|
});
|
|
|
|
const setTrait = (obj) => {
|
|
const repeatingItem = 'repeating_trait';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
let itemId;
|
|
|
|
if (!obj.name) {
|
|
obj.name = obj.storageName;
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}storage_name`);
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}uses`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}heal_toggle`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
if (repeatingItem === 'repeating_trait') {
|
|
collectionArray.push(`${repeatingString}display_text`);
|
|
}
|
|
for (const prop in obj) {
|
|
if (obj.hasOwnProperty(prop)) {
|
|
collectionArray.push(`${repeatingString}${prop}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
let repeatingString;
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (v[`${repeatingString}storage_name`] === obj.storageName) {
|
|
itemId = id;
|
|
}
|
|
}
|
|
if (!itemId) {
|
|
itemId = generateRowID();
|
|
}
|
|
|
|
repeatingString = `${repeatingItem}_${itemId}_`;
|
|
if (!obj.clear && isUndefined(v[`${repeatingString}storage_name`])) {
|
|
finalSetAttrs[`${repeatingString}storage_name`] = obj.storageName;
|
|
}
|
|
if (!obj.clear && v[`${repeatingString}name`] !== obj.name) {
|
|
finalSetAttrs[`${repeatingString}name`] = obj.name;
|
|
}
|
|
delete obj.storageName;
|
|
|
|
if (obj.clear) {
|
|
delete obj.clear;
|
|
for (const prop in obj) {
|
|
if (obj.hasOwnProperty(prop) && !isUndefined(v[`${repeatingString}${prop}`])) {
|
|
finalSetAttrs[`${repeatingString}${prop}`] = obj[prop];
|
|
}
|
|
}
|
|
} else {
|
|
for (const prop in obj) {
|
|
if (obj.hasOwnProperty(prop)) {
|
|
if ((prop === 'name' || prop === 'freetext') && (isUndefined(v[`${repeatingString}${prop}`]) || calculatePercentDifference(v[`${repeatingString}${prop}`].length, obj[prop].length) < 10)) {
|
|
finalSetAttrs[`${repeatingString}${prop}`] = obj[prop];
|
|
} else if (v[`${repeatingString}${prop}`] !== obj[prop]) {
|
|
finalSetAttrs[`${repeatingString}${prop}`] = obj[prop];
|
|
}
|
|
}
|
|
}
|
|
if (obj.saving_throw_ability || obj.saving_throw_bonus || obj.saving_throw_vs_ability) {
|
|
finalSetAttrs[`${repeatingString}saving_throw_toggle`] = toggleVars.saving_throw;
|
|
}
|
|
if (obj.damage || obj.damage_ability || obj.damage_bonus) {
|
|
finalSetAttrs[`${repeatingString}damage_toggle`] = toggleVars.damage;
|
|
}
|
|
if (obj.heal || obj.heal_ability || obj.heal_bonus || obj.heal_query_toggle) {
|
|
finalSetAttrs[`${repeatingString}heal_toggle`] = toggleVars.heal;
|
|
}
|
|
if (obj.freetext && isUndefined(v[`${repeatingString}extras_toggle`])) {
|
|
finalSetAttrs[`${repeatingString}extras_toggle`] = toggleVars.extras;
|
|
}
|
|
if (obj.uses_max && !obj.uses && isUndefined(v[`${repeatingString}uses`])) {
|
|
finalSetAttrs[`${repeatingString}uses`] = obj.uses_max;
|
|
}
|
|
if (obj.freetext && repeatingItem === 'repeating_trait' && (isUndefined(v[`${repeatingString}display_text`]) ||
|
|
calculatePercentDifference(v[`${repeatingString}display_text`].length, obj.freetext.length) < 10)) {
|
|
finalSetAttrs[`${repeatingString}display_text`] = obj.freetext;
|
|
}
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
return itemId;
|
|
});
|
|
});
|
|
};
|
|
|
|
const setClassFeatures = () => {
|
|
const finalSetAttrs = {};
|
|
const collectionArray = ['ac_unarmored_ability', 'jack_of_all_trades_toggle', 'careful_spell_toggle', 'distant_spell_toggle', 'empowered_spell_toggle', 'extended_spell_toggle', 'heightened_spell_toggle', 'quickened_spell_toggle', 'subtle_spell_toggle', 'twinned_spell_toggle'];
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
for (const className of CLASSES) {
|
|
collectionArray.push(`${className}_level`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.fighter_level >= 5) {
|
|
let extraAttackTimes;
|
|
if (v.fighter_level >= 20) {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_FOUR');
|
|
} else if (v.fighter_level >= 11) {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_THREE');
|
|
} else {
|
|
extraAttackTimes = getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TWICE');
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TEXT').replace('NUMBER_OF_TIMES', extraAttackTimes),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK'),
|
|
storageName: 'Extra Attack',
|
|
});
|
|
} else if (v.barbarian_level >= 5 || v.monk_level >= 5 || v.paladin_level >= 5 || v.ranger_level >= 5) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TEXT').replace('NUMBER_OF_TIMES', getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK_TWICE')),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXTRA_ATTACK'),
|
|
storageName: 'Extra Attack',
|
|
});
|
|
}
|
|
|
|
if (v.barbarian_level) {
|
|
let rageUses;
|
|
if (v.barbarian_level >= 20) {
|
|
rageUses = 999999;
|
|
} else if (v.barbarian_level >= 17) {
|
|
rageUses = 6;
|
|
} else if (v.barbarian_level >= 12) {
|
|
rageUses = 5;
|
|
} else if (v.barbarian_level >= 6) {
|
|
rageUses = 4;
|
|
} else if (v.barbarian_level >= 3) {
|
|
rageUses = 3;
|
|
} else {
|
|
rageUses = 2;
|
|
}
|
|
|
|
let rageDamage;
|
|
if (v.barbarian_level >= 16) {
|
|
rageDamage = 4;
|
|
} else if (v.barbarian_level >= 9) {
|
|
rageDamage = 3;
|
|
} else {
|
|
rageDamage = 2;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RAGE_TEXT').replace('RAGE_DAMAGE', rageDamage),
|
|
name: getTranslationByKey('CLASS_FEATURE_RAGE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Rage',
|
|
uses_max: rageUses,
|
|
});
|
|
|
|
if (isUndefined(v.ac_unarmored_ability)) {
|
|
finalSetAttrs.ac_unarmored_ability = 'constitution';
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE_BARBARIAN_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE'),
|
|
storageName: 'Unarmored Defense Barbarian',
|
|
});
|
|
|
|
if (v.barbarian_level >= 2) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RECKLESS_ATTACK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RECKLESS_ATTACK'),
|
|
storageName: 'Reckless Attack',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DANGER_SENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DANGER_SENSE'),
|
|
storageName: 'Danger Sense',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 5) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FAST_MOVEMENT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FAST_MOVEMENT'),
|
|
storageName: 'Fast Movement',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 7) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FERAL_INSTINCT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FERAL_INSTINCT'),
|
|
storageName: 'Feral Instinct',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 9) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BRUTAL_CRITICAL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BRUTAL_CRITICAL'),
|
|
storageName: 'Brutal Critical',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 11) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RELENTLESS_RAGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RELENTLESS_RAGE'),
|
|
storageName: 'Relentless Rage',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 15) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PERSISTENT_RAGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PERSISTENT_RAGE'),
|
|
storageName: 'Persistent Rage',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 18) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_MIGHT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_MIGHT'),
|
|
storageName: 'Indomitable Might',
|
|
});
|
|
}
|
|
if (v.barbarian_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PRIMAL_CHAMPION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PRIMAL_CHAMPION'),
|
|
storageName: 'Primal Champion',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.bard_level) {
|
|
let die;
|
|
if (v.bard_level >= 15) {
|
|
die = 'd12';
|
|
} else if (v.bard_level >= 10) {
|
|
die = 'd10';
|
|
} else if (v.bard_level >= 5) {
|
|
die = 'd8';
|
|
} else {
|
|
die = 'd6';
|
|
}
|
|
let recharge = 'Long Rest';
|
|
if (v.bard_level >= 5) {
|
|
recharge = 'Short Rest';
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BARDIC_INSPIRATION_TEXT').replace('d6', die),
|
|
name: getTranslationByKey('CLASS_FEATURE_BARDIC_INSPIRATION'),
|
|
recharge,
|
|
storageName: 'Bardic Inspiration',
|
|
uses_max: Math.max(getIntValue(v.charisma_mod), 1),
|
|
});
|
|
|
|
if (v.bard_level >= 2) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_JACK_OF_ALL_TRADES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_JACK_OF_ALL_TRADES'),
|
|
storageName: 'Jack of All Trades',
|
|
});
|
|
finalSetAttrs.jack_of_all_trades_toggle = '@{jack_of_all_trades}';
|
|
|
|
let heal;
|
|
if (v.bard_level >= 17) {
|
|
heal = 'd12';
|
|
} else if (v.bard_level >= 13) {
|
|
heal = 'd10';
|
|
} else if (v.bard_level >= 9) {
|
|
heal = 'd8';
|
|
} else {
|
|
heal = 'd6';
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SONG_OF_REST_TEXT'),
|
|
heal,
|
|
name: getTranslationByKey('CLASS_FEATURE_SONG_OF_REST'),
|
|
storageName: 'Song of Rest',
|
|
});
|
|
}
|
|
if (v.bard_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXPERTISE_BARD_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXPERTISE'),
|
|
storageName: 'Expertise Bard',
|
|
});
|
|
}
|
|
if (v.bard_level >= 6) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_COUNTERCHARM_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_COUNTERCHARM'),
|
|
storageName: 'Countercharm',
|
|
});
|
|
}
|
|
if (v.bard_level >= 10) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MAGICAL_SECRETS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MAGICAL_SECRETS'),
|
|
storageName: 'Magical Secrets',
|
|
});
|
|
}
|
|
if (v.bard_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SUPERIOR_INSPIRATION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SUPERIOR_INSPIRATION'),
|
|
storageName: 'Superior Inspiration',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.cleric_level) {
|
|
if (v.cleric_level >= 2) {
|
|
let channelDivinityUses;
|
|
if (v.cleric_level >= 18) {
|
|
channelDivinityUses = 3;
|
|
} else if (v.cleric_level >= 6) {
|
|
channelDivinityUses = 2;
|
|
} else {
|
|
channelDivinityUses = 1;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_CLERIC_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Channel Divinity Cleric',
|
|
uses_max: channelDivinityUses,
|
|
});
|
|
|
|
let turnUndeadText = getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD_TEXT');
|
|
if (v.cleric_level >= 5) {
|
|
let turnUndeadDestroyCR;
|
|
if (v.cleric_level >= 17) {
|
|
turnUndeadDestroyCR = '4';
|
|
} else if (v.cleric_level >= 14) {
|
|
turnUndeadDestroyCR = '3';
|
|
} else if (v.cleric_level >= 11) {
|
|
turnUndeadDestroyCR = '2';
|
|
} else if (v.cleric_level >= 8) {
|
|
turnUndeadDestroyCR = '1';
|
|
} else {
|
|
turnUndeadDestroyCR = '1/2';
|
|
}
|
|
turnUndeadText += `\n${getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD_TEXT_PART_2').replace('CHALLENGE_RATING', turnUndeadDestroyCR)}`;
|
|
}
|
|
setTrait({
|
|
freetext: turnUndeadText,
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_TURN_UNDEAD'),
|
|
saving_throw_ability: 'wisdom',
|
|
saving_throw_vs_ability: 'WISDOM',
|
|
storageName: 'Turn Undead',
|
|
});
|
|
if (v.cleric_level >= 10) {
|
|
setTrait({
|
|
freeform: '{{text_top=[[d100]] > [[@{cleric_level}]]}}',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_INTERVENTION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_INTERVENTION'),
|
|
storageName: 'Divine Intervention',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (v.druid_level) {
|
|
if (v.druid_level >= 2) {
|
|
const wildShapeHours = Math.floor(v.druid_level / 2);
|
|
let wildShapeUses;
|
|
let wildShapeCR;
|
|
let wildShapeLimitations;
|
|
|
|
if (v.druid_level >= 4) {
|
|
wildShapeCR = '1/2';
|
|
wildShapeLimitations = getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_NO_FLYING');
|
|
} else if (v.druid_level >= 8) {
|
|
wildShapeCR = 1;
|
|
wildShapeLimitations = '';
|
|
} else {
|
|
wildShapeCR = '1/4';
|
|
wildShapeLimitations = getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_NO_FLYING_OR_SWIMMING');
|
|
}
|
|
if (v.druid_level >= 20) {
|
|
wildShapeUses = 999999;
|
|
} else {
|
|
wildShapeUses = 2;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_WILD_SHAPE_TEXT').replace('WILD_SHAPE_HOURS', wildShapeHours).replace('CHALLENGE_RATING', wildShapeCR).replace('LIMITATIONS', ` ${wildShapeLimitations}`),
|
|
name: getTranslationByKey('CLASS_FEATURE_WILD_SHAPE'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Wild Shape',
|
|
uses_max: wildShapeUses,
|
|
});
|
|
}
|
|
if (v.druid_level >= 18) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY_DRUID_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY'),
|
|
storageName: 'Timeless Body',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BEAST_SPELLS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BEAST_SPELLS'),
|
|
storageName: 'Beast Spells',
|
|
});
|
|
}
|
|
if (v.druid_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ARCHDRUID_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ARCHDRUID'),
|
|
storageName: 'Archdruid',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.fighter_level) {
|
|
setTrait({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_FIGHTER_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SECOND_WIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SECOND_WIND'),
|
|
heal: 'd10 + @{fighter_level}',
|
|
recharge: 'Short Rest',
|
|
storageName: 'Second Wind',
|
|
uses_max: 1,
|
|
});
|
|
|
|
if (v.fighter_level >= 2) {
|
|
let actionSurgeUses;
|
|
if (v.fighter_level >= 17) {
|
|
actionSurgeUses = 2;
|
|
} else {
|
|
actionSurgeUses = 1;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ACTION_SURGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ACTION_SURGE'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Action Surge',
|
|
uses_max: actionSurgeUses,
|
|
});
|
|
}
|
|
if (v.fighter_level >= 9) {
|
|
let indomitableUses;
|
|
if (v.fighter_level >= 17) {
|
|
indomitableUses = 3;
|
|
} else if (v.fighter_level >= 13) {
|
|
indomitableUses = 2;
|
|
} else {
|
|
indomitableUses = 1;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_INDOMITABLE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_INDOMITABLE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Indomitable',
|
|
uses_max: indomitableUses,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.monk_level) {
|
|
if (isUndefined(v.ac_unarmored_ability)) {
|
|
finalSetAttrs.ac_unarmored_ability = 'wisdom';
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE_MONK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_DEFENSE'),
|
|
storageName: 'Unarmored Defense Monk',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MARTIAL_ARTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MARTIAL_ARTS'),
|
|
storageName: 'Martial Arts',
|
|
});
|
|
if (v.monk_level >= 2) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_KI_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_KI'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Ki',
|
|
uses_max: v.monk_level,
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FLURRY_OF_BLOWS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FLURRY_OF_BLOWS'),
|
|
storageName: 'Flurry of Blows',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PATIENT_DEFENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PATIENT_DEFENSE'),
|
|
storageName: 'Patient Defense',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STEP_OF_THE_WIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STEP_OF_THE_WIND'),
|
|
storageName: 'Step of the Wind',
|
|
});
|
|
|
|
let unarmoredMovementFeet;
|
|
if (v.monk_level >= 18) {
|
|
unarmoredMovementFeet = 30;
|
|
} else if (v.monk_level >= 14) {
|
|
unarmoredMovementFeet = 25;
|
|
} else if (v.monk_level >= 10) {
|
|
unarmoredMovementFeet = 20;
|
|
} else if (v.monk_level >= 6) {
|
|
unarmoredMovementFeet = 15;
|
|
} else {
|
|
unarmoredMovementFeet = 10;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNARMORED_MOVEMENT_TEXT').replace('UNARMORED_MOVEMENT_FEET', unarmoredMovementFeet),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNARMORED_MOVEMENT'),
|
|
storageName: 'Unarmored Movement',
|
|
});
|
|
}
|
|
if (v.monk_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DEFLECT_MISSILES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DEFLECT_MISSILES'),
|
|
storageName: 'Deflect Missiles',
|
|
});
|
|
}
|
|
if (v.monk_level >= 4) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SLOW_FALL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SLOW_FALL'),
|
|
storageName: 'Slow Fall',
|
|
});
|
|
}
|
|
if (v.monk_level >= 5) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE'),
|
|
saving_throw_ability: 'wisdom',
|
|
saving_throw_failure: getTranslationByKey('CLASS_FEATURE_STUNNING_STRIKE_SAVING_THROW_FAILURE'),
|
|
saving_throw_vs_ability: 'CONSTITUTION',
|
|
storageName: 'Stunning Strike',
|
|
});
|
|
}
|
|
if (v.monk_level >= 6) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_KI_EMPOWERED_STRIKES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_KI_EMPOWERED_STRIKES'),
|
|
storageName: 'Ki-Empowered Strikes',
|
|
});
|
|
}
|
|
if (v.monk_level >= 7) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EVASION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EVASION'),
|
|
storageName: 'Evasion',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STILLNESS_OF_MIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STILLNESS_OF_MIND'),
|
|
storageName: 'Stillness of Mind',
|
|
});
|
|
}
|
|
if (v.monk_level >= 10) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PURITY_OF_BODY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PURITY_OF_BODY'),
|
|
storageName: 'Purity of Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 13) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TONGUE_OF_THE_SUN_AND_MOON_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TONGUE_OF_THE_SUN_AND_MOON'),
|
|
storageName: 'Tongue of the Sun and Moon',
|
|
});
|
|
}
|
|
if (v.monk_level >= 14) {
|
|
finalSetAttrs.death_saving_throw_prof = '@{pb}';
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIAMOND_SOUL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIAMOND_SOUL'),
|
|
storageName: 'Diamond Soul',
|
|
});
|
|
}
|
|
if (v.monk_level >= 15) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY_MONK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_TIMELESS_BODY'),
|
|
storageName: 'Timeless Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 15) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EMPTY_BODY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EMPTY_BODY'),
|
|
storageName: 'Empty Body',
|
|
});
|
|
}
|
|
if (v.monk_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PERFECT_SELF_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PERFECT_SELF'),
|
|
storageName: 'Perfect Self',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.paladin_level) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_SENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_SENSE'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Divine Sense',
|
|
uses_max: Math.max(1 + v.charisma_mod, 1),
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_LAY_ON_HANDS_TEXT'),
|
|
heal_query_toggle: toggleVars.heal_query,
|
|
name: getTranslationByKey('CLASS_FEATURE_LAY_ON_HANDS'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Lay on Hands',
|
|
uses_max: 5 * v.paladin_level,
|
|
});
|
|
|
|
if (v.paladin_level >= 2) {
|
|
setTrait({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_PALADIN_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
setTrait({
|
|
damage: '(?{Spell Level|1|2|3|4+, 4} + 1)d8',
|
|
damage_type: getTranslationByKey('RADIANT'),
|
|
freeform: '{{subheader=(as ?{Spell Level|1|2|3|4+})}}',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_SMITE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_SMITE'),
|
|
second_damage: 'd8',
|
|
second_damage_type: getTranslationByKey('VS_FIEND_OR_UNDEAD'),
|
|
storageName: 'Divine Smite',
|
|
});
|
|
}
|
|
if (v.paladin_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_DIVINE_HEALTH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_DIVINE_HEALTH'),
|
|
storageName: 'Divine Health',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY_PALADIN_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CHANNEL_DIVINITY'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Channel Divinity Paladin',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
if (v.paladin_level >= 6) {
|
|
let auraRange;
|
|
if (v.paladin_level >= 18) {
|
|
auraRange = 30;
|
|
} else {
|
|
auraRange = 10;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_AURA_OF_PROTECTION_TEXT').replace('AURA_RANGE', auraRange),
|
|
name: getTranslationByKey('CLASS_FEATURE_AURA_OF_PROTECTION'),
|
|
storageName: 'Aura of Protection',
|
|
});
|
|
|
|
if (v.paladin_level >= 10) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_AURA_OF_COURAGE_TEXT').replace('AURA_RANGE', auraRange),
|
|
name: getTranslationByKey('CLASS_FEATURE_AURA_OF_COURAGE'),
|
|
storageName: 'Aura of Courage',
|
|
});
|
|
}
|
|
}
|
|
if (v.paladin_level >= 11) {
|
|
setTrait({
|
|
damage: '1d8',
|
|
damage_ability: '',
|
|
damage_type: 'radiant',
|
|
freetext: getTranslationByKey('CLASS_FEATURE_IMPROVED_DIVINE_SMITE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_IMPROVED_DIVINE_SMITE'),
|
|
storageName: 'Improved Divine Smite',
|
|
});
|
|
}
|
|
if (v.paladin_level >= 14) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CLEANSING_TOUCH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CLEANSING_TOUCH'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Cleaning Touch',
|
|
uses_max: Math.max(getIntValue(v.charisma_mod), 1),
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.ranger_level) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FAVORED_ENEMY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FAVORED_ENEMY'),
|
|
storageName: 'Favored Enemy',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_NATURAL_EXPLORER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_NATURAL_EXPLORER'),
|
|
storageName: 'Natural Explorer',
|
|
});
|
|
if (v.ranger_level >= 2) {
|
|
setTrait({
|
|
freetext: `${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_TEXT')} ${getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE_RANGER_OPTIONS')}`,
|
|
name: getTranslationByKey('CLASS_FEATURE_FIGHTING_STYLE'),
|
|
storageName: 'Fighting Style',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PRIMEVAL_AWARENESS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PRIMEVAL_AWARENESS'),
|
|
storageName: 'Primeval Awareness',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 8) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_LANDS_STRIDE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_LANDS_STRIDE'),
|
|
storageName: 'Land\'s Stride',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 10) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_HIDE_IN_PLAIN_SIGHT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_HIDE_IN_PLAIN_SIGHT'),
|
|
storageName: 'Hide in Plain Sight',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 14) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_VANISH_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_VANISH'),
|
|
storageName: 'Vanish',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 18) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FERAL_SENSES_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FERAL_SENSES'),
|
|
storageName: 'Feral Senses',
|
|
});
|
|
}
|
|
if (v.ranger_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FOE_SLAYER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FOE_SLAYER'),
|
|
storageName: 'Foe Slayer',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.rogue_level) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EXPERTISE_ROGUE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EXPERTISE'),
|
|
storageName: 'Expertise Rogue',
|
|
});
|
|
setTrait({
|
|
damage: `${Math.ceil(getIntValue(v.rogue_level) / 2)}d6`,
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SNEAK_ATTACK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SNEAK_ATTACK'),
|
|
storageName: 'Sneak Attack',
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_THIEVES_CANT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_THIEVES_CANT'),
|
|
storageName: 'Thieves\' Cant',
|
|
});
|
|
if (v.rogue_level >= 2) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_CUNNING_ACTION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_CUNNING_ACTION'),
|
|
storageName: 'Cunning Action',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 5) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_UNCANNY_DODGE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_UNCANNY_DODGE'),
|
|
storageName: 'Uncanny Dodge',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 7) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_EVASION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_EVASION'),
|
|
storageName: 'Evasion',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 11) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_RELIABLE_TALENT_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_RELIABLE_TALENT'),
|
|
storageName: 'Reliable Talent',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 14) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_BLINDSENSE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_BLINDSENSE'),
|
|
storageName: 'Blindsense',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 15) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SLIPPERY_MIND_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SLIPPERY_MIND'),
|
|
storageName: 'Slippery Mind',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 18) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELUSIVE_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELUSIVE'),
|
|
storageName: 'Elusive',
|
|
});
|
|
}
|
|
if (v.rogue_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_STROKE_OF_LUCK_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_STROKE_OF_LUCK'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Stroke of Luck',
|
|
uses_max: 1,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.sorcerer_level) {
|
|
if (v.sorcerer_level >= 2) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SORCERY_POINTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SORCERY_POINTS'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Sorcery Points',
|
|
uses_max: v.sorcerer_level,
|
|
});
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_FLEXIBLE_CASTING_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_FLEXIBLE_CASTING'),
|
|
storageName: 'Flexible Casting',
|
|
});
|
|
if (v.sorcerer_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC'),
|
|
storageName: 'Metamagic',
|
|
});
|
|
}
|
|
if (v.sorcerer_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SORCEROUS_RESTORATION_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SORCEROUS_RESTORATION'),
|
|
storageName: 'Sorcerous Restoration',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (v.careful_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_CAREFUL_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_CAREFUL_SPELL'),
|
|
storageName: 'Careful Spell',
|
|
});
|
|
}
|
|
if (v.distant_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_DISTANT_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_DISTANT_SPELL'),
|
|
storageName: 'Distant Spell',
|
|
});
|
|
}
|
|
if (v.empowered_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EMPOWERED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EMPOWERED_SPELL'),
|
|
storageName: 'Empowered Spell',
|
|
});
|
|
}
|
|
if (v.extended_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EXTENDED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_EXTENDED_SPELL'),
|
|
storageName: 'Extended Spell',
|
|
});
|
|
}
|
|
if (v.heightened_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_HEIGHTENED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_HEIGHTENED_SPELL'),
|
|
storageName: 'Heightened Spell',
|
|
});
|
|
}
|
|
if (v.quickened_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_QUICKENED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_QUICKENED_SPELL'),
|
|
storageName: 'Quickened Spell',
|
|
});
|
|
}
|
|
if (v.subtle_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_SUBTLE_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_SUBTLE_SPELL'),
|
|
storageName: 'Subtle Spell',
|
|
});
|
|
}
|
|
if (v.twinned_spell_toggle === '1') {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TWINNED_SPELL_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_METAMAGIC_TWINNED_SPELL'),
|
|
storageName: 'Twinned Spell',
|
|
});
|
|
}
|
|
|
|
if (v.warlock_level) {
|
|
let warlockSpellSlots;
|
|
if (v.warlock_level >= 17) {
|
|
warlockSpellSlots = 4;
|
|
} else if (v.warlock_level >= 11) {
|
|
warlockSpellSlots = 3;
|
|
} else if (v.warlock_level >= 2) {
|
|
warlockSpellSlots = 2;
|
|
} else {
|
|
warlockSpellSlots = 1;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_WARLOCK_SPELL_SLOTS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_WARLOCK_SPELL_SLOTS'),
|
|
recharge: 'Short Rest',
|
|
storageName: 'Warlock Spell Slots',
|
|
uses_max: warlockSpellSlots,
|
|
});
|
|
|
|
if (v.warlock_level >= 2) {
|
|
let eldritchInvocations;
|
|
if (v.warlock_level >= 17) {
|
|
eldritchInvocations = 8;
|
|
} else if (v.warlock_level >= 15) {
|
|
eldritchInvocations = 7;
|
|
} else if (v.warlock_level >= 12) {
|
|
eldritchInvocations = 6;
|
|
} else if (v.warlock_level >= 9) {
|
|
eldritchInvocations = 5;
|
|
} else if (v.warlock_level >= 7) {
|
|
eldritchInvocations = 4;
|
|
} else if (v.warlock_level >= 5) {
|
|
eldritchInvocations = 3;
|
|
} else {
|
|
eldritchInvocations = 2;
|
|
}
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELDRITCH_INVOCATIONS_TEXT').replace('NUMBER_OF_INVOCATIONS_KNOWN', eldritchInvocations),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELDRITCH_INVOCATIONS'),
|
|
storageName: 'Eldritch Invocations',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 3) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_PACT_BOON_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_PACT_BOON'),
|
|
storageName: 'Pact Boon',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 11) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_MYSTIC_ARCANUM_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_MYSTIC_ARCANUM'),
|
|
storageName: 'Mystic Arcanum',
|
|
});
|
|
}
|
|
if (v.warlock_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ELDRITCH_MASTER_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ELDRITCH_MASTER'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Eldritch Master',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (v.wizard_level) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_ARCANE_RECOVERY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_ARCANE_RECOVERY'),
|
|
recharge: 'Long Rest',
|
|
storageName: 'Arcane Recovery',
|
|
uses_max: 1,
|
|
});
|
|
if (v.wizard_level >= 18) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SPELL_MASTERY_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SPELL_MASTERY'),
|
|
storageName: 'Spell Mastery',
|
|
});
|
|
}
|
|
if (v.wizard_level >= 20) {
|
|
setTrait({
|
|
freetext: getTranslationByKey('CLASS_FEATURE_SIGNATURE_SPELLS_TEXT'),
|
|
name: getTranslationByKey('CLASS_FEATURE_SIGNATURE_SPELLS'),
|
|
storageName: 'Signature Spells',
|
|
});
|
|
}
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const updateSpellSlots = () => {
|
|
const collectionArray = ['caster_level', 'caster_type'];
|
|
const finalSetAttrs = {};
|
|
|
|
const spellSlots = {
|
|
1: 0,
|
|
2: 0,
|
|
3: 0,
|
|
4: 0,
|
|
5: 0,
|
|
6: 0,
|
|
7: 0,
|
|
8: 0,
|
|
9: 0,
|
|
};
|
|
|
|
for (const level in spellSlots) {
|
|
if (spellSlots.hasOwnProperty(level)) {
|
|
const repeatingString = `spell_slots_l${level}`;
|
|
collectionArray.push(`${repeatingString}_calc`);
|
|
collectionArray.push(`${repeatingString}_bonus`);
|
|
collectionArray.push(`${repeatingString}_max`);
|
|
collectionArray.push(`${repeatingString}_toggle`);
|
|
}
|
|
}
|
|
getAttrs(collectionArray, (v) => {
|
|
const casterLevel = getIntValue(v.caster_level);
|
|
const casterType = v.caster_type;
|
|
|
|
if (casterType === 'full') {
|
|
if (casterLevel >= 3) {
|
|
spellSlots[1] = 4;
|
|
} else if (casterLevel === 2) {
|
|
spellSlots[1] = 3;
|
|
} else if (casterLevel === 1) {
|
|
spellSlots[1] = 2;
|
|
}
|
|
if (casterLevel >= 4) {
|
|
spellSlots[2] = 3;
|
|
} else if (casterLevel === 3) {
|
|
spellSlots[2] = 2;
|
|
}
|
|
if (casterLevel >= 6) {
|
|
spellSlots[3] = 3;
|
|
} else if (casterLevel === 5) {
|
|
spellSlots[3] = 2;
|
|
}
|
|
if (casterLevel >= 9) {
|
|
spellSlots[4] = 3;
|
|
} else if (casterLevel === 8) {
|
|
spellSlots[4] = 2;
|
|
} else if (casterLevel === 7) {
|
|
spellSlots[4] = 1;
|
|
}
|
|
if (casterLevel >= 18) {
|
|
spellSlots[5] = 3;
|
|
} else if (casterLevel >= 10) {
|
|
spellSlots[5] = 2;
|
|
} else if (casterLevel === 9) {
|
|
spellSlots[5] = 1;
|
|
}
|
|
if (casterLevel >= 19) {
|
|
spellSlots[6] = 2;
|
|
} else if (casterLevel >= 11) {
|
|
spellSlots[6] = 1;
|
|
}
|
|
if (casterLevel >= 20) {
|
|
spellSlots[7] = 2;
|
|
} else if (casterLevel >= 13) {
|
|
spellSlots[7] = 1;
|
|
}
|
|
if (casterLevel >= 15) {
|
|
spellSlots[8] = 1;
|
|
}
|
|
if (casterLevel >= 17) {
|
|
spellSlots[9] = 1;
|
|
}
|
|
}
|
|
|
|
if (casterType === 'half') {
|
|
if (casterLevel >= 5) {
|
|
spellSlots[1] = 4;
|
|
} else if (casterLevel >= 3) {
|
|
spellSlots[1] = 3;
|
|
} else if (casterLevel === 2) {
|
|
spellSlots[1] = 2;
|
|
}
|
|
if (casterLevel >= 7) {
|
|
spellSlots[2] = 3;
|
|
} else if (casterLevel >= 5) {
|
|
spellSlots[2] = 2;
|
|
}
|
|
if (casterLevel >= 11) {
|
|
spellSlots[3] = 3;
|
|
} else if (casterLevel >= 9) {
|
|
spellSlots[3] = 2;
|
|
}
|
|
if (casterLevel >= 17) {
|
|
spellSlots[4] = 3;
|
|
} else if (casterLevel >= 15) {
|
|
spellSlots[4] = 2;
|
|
} else if (casterLevel >= 13) {
|
|
spellSlots[4] = 1;
|
|
}
|
|
if (casterLevel >= 19) {
|
|
spellSlots[5] = 2;
|
|
} else if (casterLevel >= 17) {
|
|
spellSlots[5] = 1;
|
|
}
|
|
}
|
|
|
|
if (casterType === 'third') {
|
|
if (casterLevel >= 7) {
|
|
spellSlots[1] = 4;
|
|
} else if (casterLevel >= 4) {
|
|
spellSlots[1] = 3;
|
|
} else if (casterLevel === 3) {
|
|
spellSlots[1] = 2;
|
|
}
|
|
if (casterLevel >= 10) {
|
|
spellSlots[2] = 3;
|
|
} else if (casterLevel >= 7) {
|
|
spellSlots[2] = 2;
|
|
}
|
|
if (casterLevel >= 16) {
|
|
spellSlots[3] = 3;
|
|
} else if (casterLevel >= 13) {
|
|
spellSlots[3] = 2;
|
|
}
|
|
if (casterLevel >= 19) {
|
|
spellSlots[4] = 1;
|
|
}
|
|
}
|
|
|
|
for (const level in spellSlots) {
|
|
if (spellSlots.hasOwnProperty(level)) {
|
|
const repeatingString = `spell_slots_l${level}`;
|
|
finalSetAttrs[repeatingString] = 0;
|
|
if (spellSlots[level] !== 0 || exists(v[`${repeatingString}_calc`])) {
|
|
finalSetAttrs[`spell_slots_l${level}_calc`] = spellSlots[level];
|
|
}
|
|
|
|
const slots = v[`${repeatingString}`];
|
|
const slotBonus = getIntValue(v[`${repeatingString}_bonus`]);
|
|
const spellSlotMax = spellSlots[level] + slotBonus;
|
|
|
|
if (spellSlotMax > 0) {
|
|
finalSetAttrs[`${repeatingString}_max`] = spellSlotMax;
|
|
if (isUndefined(slots)) {
|
|
finalSetAttrs[repeatingString] = spellSlotMax;
|
|
}
|
|
finalSetAttrs[`${repeatingString}_toggle`] = 'on';
|
|
} else {
|
|
if (exists(v[`${repeatingString}_max`])) {
|
|
finalSetAttrs[`${repeatingString}_max`] = 0;
|
|
}
|
|
if (exists(v[`${repeatingString}_toggle`])) {
|
|
finalSetAttrs[`${repeatingString}_toggle`] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const updateHD = (v, finalSetAttrs, hd) => {
|
|
for (const key in hd) {
|
|
if (hd.hasOwnProperty(key)) {
|
|
if (hd[key] && hd[key] !== 0) {
|
|
finalSetAttrs[`hd_${key}_max`] = hd[key];
|
|
finalSetAttrs[`hd_${key}_query`] = '?{HD';
|
|
for (let x = 1; x <= hd[key]; x++) {
|
|
finalSetAttrs[`hd_${key}_query`] += `|${x}`;
|
|
}
|
|
finalSetAttrs[`hd_${key}_query`] += '}';
|
|
finalSetAttrs[`hd_${key}_toggle`] = 1;
|
|
} else {
|
|
if (!isUndefined(v[`hd_${key}_max`])) {
|
|
finalSetAttrs[`hd_${key}_max`] = 0;
|
|
}
|
|
if (!isUndefined(v[`hd_${key}_query`])) {
|
|
finalSetAttrs[`hd_${key}_query`] = '';
|
|
}
|
|
if (exists(v[`hd_${key}_toggle`])) {
|
|
finalSetAttrs[`hd_${key}_toggle`] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const updateLevels = (repeatingInfo) => {
|
|
const repeatingItem = 'repeating_class';
|
|
const collectionArray = ['is_npc', 'caster_level', 'caster_type', 'class_and_level', 'level', 'xp_next_level'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const className of CLASSES) {
|
|
collectionArray.push(`${className}_level`);
|
|
collectionArray.push(`has_${className}_levels`);
|
|
}
|
|
|
|
const defaultClassDetails = {
|
|
barbarian: {
|
|
hd: 'd12',
|
|
},
|
|
bard: {
|
|
hd: 'd8',
|
|
spellcasting: 'full',
|
|
},
|
|
cleric: {
|
|
hd: 'd8',
|
|
spellcasting: 'full',
|
|
},
|
|
druid: {
|
|
hd: 'd8',
|
|
spellcasting: 'full',
|
|
},
|
|
fighter: {
|
|
hd: 'd10',
|
|
},
|
|
monk: {
|
|
hd: 'd8',
|
|
},
|
|
paladin: {
|
|
hd: 'd10',
|
|
spellcasting: 'half',
|
|
},
|
|
ranger: {
|
|
hd: 'd10',
|
|
spellcasting: 'half',
|
|
},
|
|
rogue: {
|
|
hd: 'd8',
|
|
},
|
|
sorcerer: {
|
|
hd: 'd6',
|
|
spellcasting: 'full',
|
|
},
|
|
warlock: {
|
|
hd: 'd8',
|
|
spellcasting: 'warlock',
|
|
},
|
|
wizard: {
|
|
hd: 'd6',
|
|
spellcasting: 'full',
|
|
},
|
|
};
|
|
const hd = {
|
|
d20: 0,
|
|
d12: 0,
|
|
d10: 0,
|
|
d8: 0,
|
|
d6: 0,
|
|
d4: 0,
|
|
d2: 0,
|
|
d0: 0,
|
|
};
|
|
const spellcasting = {
|
|
full: 0,
|
|
half: 0,
|
|
third: 0,
|
|
warlock: 0,
|
|
};
|
|
let totalLevel = 0;
|
|
const classLevels = {};
|
|
let classesWithSpellcasting = 0;
|
|
const xpTable = [0, 300, 900, 2700, 6500, 14000, 23000, 34000, 48000, 64000, 85000, 100000, 120000, 140000, 165000, 195000, 225000, 265000, 305000, 355000, 385000, 405000, 435000, 465000, 495000, 525000, 555000, 585000, 605000, 635000, 665000];
|
|
|
|
for (const key in hd) {
|
|
if (hd.hasOwnProperty(key)) {
|
|
collectionArray.push(`hd_${key}_max`);
|
|
collectionArray.push(`hd_${key}_query`);
|
|
collectionArray.push(`hd_${key}_toggle`);
|
|
}
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}level`);
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}custom_name`);
|
|
collectionArray.push(`${repeatingString}hd`);
|
|
collectionArray.push(`${repeatingString}spellcasting`);
|
|
collectionArray.push(`${repeatingString}custom_class_toggle`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const className of CLASSES) {
|
|
finalSetAttrs[`${className}_level`] = 0;
|
|
}
|
|
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
let className = v[`${repeatingString}name`];
|
|
let classLevel = v[`${repeatingString}level`];
|
|
|
|
if (isUndefined(className) && isUndefined(classLevel)) {
|
|
continue;
|
|
}
|
|
|
|
if (isUndefined(className)) {
|
|
className = 'barbarian';
|
|
}
|
|
if (className === 'custom') {
|
|
finalSetAttrs[`${repeatingString}custom_class_toggle`] = 'on';
|
|
const customName = v[`${repeatingString}custom_name`];
|
|
if (exists(customName)) {
|
|
className = customName;
|
|
} else {
|
|
className = 'custom';
|
|
}
|
|
} else if (v[`${repeatingString}custom_class_toggle`]) {
|
|
finalSetAttrs[`${repeatingString}custom_class_toggle`] = 0;
|
|
}
|
|
|
|
if (isUndefined(classLevel)) {
|
|
classLevel = 1;
|
|
finalSetAttrs[`${repeatingString}level`] = classLevel;
|
|
finalSetAttrs[`${className}_level`] = classLevel;
|
|
} else {
|
|
classLevel = getIntValue(classLevel);
|
|
totalLevel += classLevel;
|
|
if (classLevels[className]) {
|
|
classLevels[className] += classLevel;
|
|
} else {
|
|
classLevels[className] = classLevel;
|
|
}
|
|
}
|
|
|
|
let classHd = v[`${repeatingString}hd`];
|
|
if (isUndefined(classHd) || repeatingInfo.field === 'name') {
|
|
if (defaultClassDetails.hasOwnProperty(className)) {
|
|
classHd = defaultClassDetails[className].hd;
|
|
} else {
|
|
classHd = 'd12';
|
|
}
|
|
finalSetAttrs[`${repeatingString}hd`] = classHd;
|
|
}
|
|
if (classHd && classLevel) {
|
|
hd[classHd] += classLevel;
|
|
}
|
|
|
|
let classSpellcasting = v[`${repeatingString}spellcasting`];
|
|
if (isUndefined(classSpellcasting)) {
|
|
if (defaultClassDetails.hasOwnProperty(className)) {
|
|
classSpellcasting = defaultClassDetails[className].spellcasting;
|
|
if (classSpellcasting) {
|
|
finalSetAttrs[`${repeatingString}spellcasting`] = classSpellcasting;
|
|
}
|
|
} else {
|
|
finalSetAttrs[`${repeatingString}spellcasting`] = 'none';
|
|
}
|
|
} else if (classSpellcasting === 'warlock') {
|
|
spellcasting[classSpellcasting] += classLevel;
|
|
} else {
|
|
classesWithSpellcasting += 1;
|
|
spellcasting[classSpellcasting] += classLevel;
|
|
}
|
|
}
|
|
|
|
finalSetAttrs.number_of_classes = 0;
|
|
finalSetAttrs.class_and_level = '';
|
|
for (const prop in classLevels) {
|
|
if (classLevels.hasOwnProperty(prop)) {
|
|
finalSetAttrs.number_of_classes = finalSetAttrs.number_of_classes + 1;
|
|
if (classLevels.hasOwnProperty(prop)) {
|
|
finalSetAttrs[`${prop}_level`] = classLevels[prop];
|
|
if (finalSetAttrs.class_and_level !== '') {
|
|
finalSetAttrs.class_and_level += ', ';
|
|
}
|
|
finalSetAttrs.class_and_level += `${capitalize(prop)} ${classLevels[prop]}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
finalSetAttrs.level = totalLevel;
|
|
|
|
let xpForNextLevel = 0;
|
|
if (!totalLevel) {
|
|
totalLevel = 0;
|
|
}
|
|
if (totalLevel > 30) {
|
|
xpForNextLevel = xpTable[30];
|
|
} else {
|
|
xpForNextLevel = xpTable[totalLevel];
|
|
}
|
|
finalSetAttrs.xp_next_level = xpForNextLevel;
|
|
|
|
for (const className of CLASSES) {
|
|
if (finalSetAttrs[`${className}_level`] > 0) {
|
|
finalSetAttrs[`has_${className}_levels`] = 1;
|
|
} else if (!isUndefined(v[`has_${className}_levels`])) {
|
|
finalSetAttrs[`has_${className}_levels`] = 0;
|
|
}
|
|
}
|
|
|
|
updateHD(v, finalSetAttrs, hd);
|
|
|
|
let casterLevel = 0;
|
|
if (!v.is_npc || v.is_npc === '0' || v.is_npc === 0) {
|
|
if (classesWithSpellcasting > 1) {
|
|
casterLevel += spellcasting.full;
|
|
casterLevel += Math.floor(spellcasting.half / 2);
|
|
casterLevel += Math.floor(spellcasting.third / 3);
|
|
} else {
|
|
casterLevel = spellcasting.full + spellcasting.half + spellcasting.third;
|
|
}
|
|
finalSetAttrs.caster_level = casterLevel;
|
|
}
|
|
|
|
if (classesWithSpellcasting > 1 || spellcasting.full) {
|
|
finalSetAttrs.caster_type = 'full';
|
|
} else if (spellcasting.half) {
|
|
finalSetAttrs.caster_type = 'half';
|
|
} else if (spellcasting.third) {
|
|
finalSetAttrs.caster_type = 'third';
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
setClassFeatures();
|
|
updateSpellSlots();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
on('change:repeating_class', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_class', eventInfo);
|
|
if (repeatingInfo) {
|
|
updateLevels(repeatingInfo);
|
|
}
|
|
});
|
|
on('remove:repeating_class', () => {
|
|
updateLevels();
|
|
});
|
|
|
|
const watchForClassLevelChanges = () => {
|
|
const classFeatureWatch = [];
|
|
for (const ability of ABILITIES) {
|
|
classFeatureWatch.push(`change:${ability}_mod`);
|
|
}
|
|
classFeatureWatch.push('change:careful_spell_toggle');
|
|
classFeatureWatch.push('change:distant_spell_toggle');
|
|
classFeatureWatch.push('change:empowered_spell_toggle');
|
|
classFeatureWatch.push('change:extended_spell_toggle');
|
|
classFeatureWatch.push('change:heightened_spell_toggle');
|
|
classFeatureWatch.push('change:quickened_spell_toggle');
|
|
classFeatureWatch.push('change:subtle_spell_toggle');
|
|
classFeatureWatch.push('change:twinned_spell_toggle');
|
|
|
|
on(classFeatureWatch.join(' '), () => {
|
|
setClassFeatures();
|
|
});
|
|
};
|
|
watchForClassLevelChanges();
|
|
on('change:spell_slots_l1_bonus change:spell_slots_l2_bonus change:spell_slots_l3_bonus change:spell_slots_l4_bonus change:spell_slots_l5_bonus change:spell_slots_l6_bonus change:spell_slots_l7_bonus change:spell_slots_l8_bonus change:spell_slots_l9_bonus', () => {
|
|
updateSpellSlots();
|
|
});
|
|
|
|
const getPB = (level, challenge) => {
|
|
let pb = 2;
|
|
|
|
level = getIntValue(level);
|
|
if (challenge === '1/8' || challenge === '1/4' || challenge === '1/2') {
|
|
challenge = 1;
|
|
} else {
|
|
getIntValue(challenge);
|
|
}
|
|
const levelOrChallenge = Math.max(level, challenge);
|
|
|
|
if (exists(levelOrChallenge)) {
|
|
pb += Math.floor(Math.abs((levelOrChallenge - 1) / 4));
|
|
}
|
|
return pb;
|
|
};
|
|
const updatePb = () => {
|
|
const collectionArray = ['level', 'challenge', 'pb', 'exp', 'h_PB'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const pb = getPB(v.level, v.challenge);
|
|
|
|
finalSetAttrs.pb = pb;
|
|
finalSetAttrs.exp = pb * 2;
|
|
finalSetAttrs.h_PB = pb / 2;
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
on('change:level', () => {
|
|
updatePb();
|
|
});
|
|
|
|
const sumRepeating = (options, sumItems) => {
|
|
const repeatingItem = `repeating_${options.collection}`;
|
|
let collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(repeatingString + options.toggle);
|
|
if (options.qty) {
|
|
collectionArray.push(repeatingString + options.qty);
|
|
}
|
|
|
|
for (const sumItem of sumItems) {
|
|
finalSetAttrs[sumItem.totalField] = 0;
|
|
if (sumItem.totalFieldSecondary) {
|
|
finalSetAttrs[sumItem.totalFieldSecondary] = 0;
|
|
}
|
|
collectionArray.push(repeatingString + sumItem.fieldToAdd);
|
|
if (sumItem.bonus) {
|
|
collectionArray.push(repeatingString + sumItem.bonus);
|
|
}
|
|
if (sumItem.armorType) {
|
|
collectionArray.push(repeatingString + sumItem.armorType);
|
|
}
|
|
if (sumItem.addOnAfterQty) {
|
|
collectionArray.push(repeatingString + sumItem.addOnAfterQty);
|
|
}
|
|
}
|
|
}
|
|
if (options.getExtraFields) {
|
|
collectionArray = collectionArray.concat(options.getExtraFields);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
let dexMod = 0;
|
|
if (options.collection === 'armor') {
|
|
dexMod = getIntValue(v.dexterity_mod);
|
|
}
|
|
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
const qty = getIntValue(v[repeatingString + options.qty], 1);
|
|
|
|
for (const sumItem of sumItems) {
|
|
let fieldToAdd = getFloatValue(v[repeatingString + sumItem.fieldToAdd]);
|
|
if (sumItem.bonus) {
|
|
fieldToAdd += getFloatValue(v[repeatingString + sumItem.bonus]);
|
|
}
|
|
if (sumItem.armorType) {
|
|
if (!v[repeatingString + sumItem.armorType] || v[repeatingString + sumItem.armorType] === 'Light Armor') {
|
|
fieldToAdd += dexMod;
|
|
} else if (v[repeatingString + sumItem.armorType] === 'Medium Armor') {
|
|
const mediumArmorDexMod = getIntValue(v.medium_armor_max_dex, 2);
|
|
fieldToAdd += Math.min(mediumArmorDexMod, dexMod);
|
|
}
|
|
}
|
|
|
|
let itemTotal = round((qty * fieldToAdd), 2);
|
|
|
|
if (sumItem.addOnAfterQty) {
|
|
itemTotal += getFloatValue(v[repeatingString + sumItem.addOnAfterQty]);
|
|
}
|
|
|
|
itemTotal = round(itemTotal, 2);
|
|
|
|
if (sumItem.itemTotal) {
|
|
finalSetAttrs[repeatingString + sumItem.itemTotal] = itemTotal;
|
|
}
|
|
|
|
const toggle = v[repeatingString + options.toggle];
|
|
if (toggle !== 0 && toggle !== '0') {
|
|
let addToPrimary = true;
|
|
let addToSecondary = false;
|
|
|
|
if (sumItem.armorType) {
|
|
if (v[repeatingString + sumItem.armorType] === 'Shield' || v[repeatingString + sumItem.armorType] === 'AC') {
|
|
addToSecondary = true;
|
|
} else if (v[repeatingString + sumItem.armorType] === 'Unarmored') {
|
|
addToPrimary = false;
|
|
addToSecondary = true;
|
|
}
|
|
}
|
|
|
|
if (addToPrimary) {
|
|
finalSetAttrs[sumItem.totalField] += itemTotal;
|
|
}
|
|
if (addToSecondary) {
|
|
finalSetAttrs[sumItem.totalFieldSecondary] += itemTotal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const sumItem of sumItems) {
|
|
if (sumItem.totalField && !exists(finalSetAttrs[sumItem.totalField])) {
|
|
finalSetAttrs[sumItem.totalField] = 0;
|
|
}
|
|
if (sumItem.totalFieldSecondary && !exists(finalSetAttrs[sumItem.totalFieldSecondary])) {
|
|
finalSetAttrs[sumItem.totalFieldSecondary] = 0;
|
|
}
|
|
}
|
|
|
|
if (options.collection === 'armor' && !getIntValue(v.is_npc)) {
|
|
finalSetAttrs.ac_unarmored_calc += 10 + dexMod + getAbilityValue(v, v.ac_unarmored_ability);
|
|
|
|
finalSetAttrs.AC = Math.max(finalSetAttrs.ac_armored_calc, finalSetAttrs.ac_unarmored_calc);
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const updateArmor = (rowId) => {
|
|
const repeatingItem = 'repeating_armor';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
let stealthPenalty;
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}parsed`);
|
|
collectionArray.push(`${repeatingString}modifiers`);
|
|
collectionArray.push(`${repeatingString}stealth`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (isUndefined(v[`${repeatingString}parsed`]) || v[`${repeatingString}parsed`].indexOf('acBonus') === -1) {
|
|
const armorModifiers = v[`${repeatingString}modifiers`];
|
|
if (exists(armorModifiers)) {
|
|
finalSetAttrs[`${repeatingString}ac_bonus`] = armorModifiers.replace(/^\D+/g, '');
|
|
}
|
|
if (isUndefined(finalSetAttrs[`${repeatingString}parsed`])) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ' acBonus';
|
|
}
|
|
|
|
if (v[`${repeatingString}stealth`] === 'Disadvantage') {
|
|
stealthPenalty = true;
|
|
}
|
|
}
|
|
if (stealthPenalty) {
|
|
setAdvantageOnStealth('dis');
|
|
} else {
|
|
setAdvantageOnStealth('normal');
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
|
|
const options = {
|
|
collection: 'armor',
|
|
getExtraFields: ['medium_armor_max_dex', 'dexterity_mod', 'ac_unarmored_ability', 'is_npc'],
|
|
toggle: 'worn',
|
|
};
|
|
for (const ability of ABILITIES) {
|
|
options.getExtraFields.push(`${ability}_mod`);
|
|
}
|
|
const sumItems = [
|
|
{
|
|
fieldToAdd: 'weight',
|
|
totalField: 'weight_armor',
|
|
},
|
|
{
|
|
fieldToAdd: 'ac_base',
|
|
bonus: 'ac_bonus',
|
|
armorType: 'type',
|
|
itemTotal: 'ac_total',
|
|
totalField: 'ac_armored_calc',
|
|
totalFieldSecondary: 'ac_unarmored_calc',
|
|
},
|
|
];
|
|
sumRepeating(options, sumItems);
|
|
};
|
|
on('change:repeating_armor', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_armor', eventInfo);
|
|
if (repeatingInfo && repeatingInfo.field !== 'ac_total') {
|
|
updateArmor(repeatingInfo.rowId);
|
|
}
|
|
});
|
|
on('change:dexterity_mod change:medium_armor_max_dex change:ac_unarmored_ability remove:repeating_armor', () => {
|
|
updateArmor();
|
|
});
|
|
|
|
const updateEquipment = (rowId) => {
|
|
const repeatingItem = 'repeating_equipment';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}content`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (isUndefined(v[`${repeatingString}parsed`]) || v[`${repeatingString}parsed`].indexOf('content') === -1) {
|
|
let content = v[`${repeatingString}content`];
|
|
if (exists(content)) {
|
|
content = content.replace(/\s(\d+d\d+\s(?:\+|\-)\s\d+)\s/g, ' [[$1]] ')
|
|
.replace(/\s(\d+d\d+)\s/g, ' [[$1]] ')
|
|
.replace(/\s(\d+)\s/g, ' [[$1]] ');
|
|
|
|
finalSetAttrs[`${repeatingString}content`] = content;
|
|
|
|
if (isUndefined(finalSetAttrs[`${repeatingString}parsed`])) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ' content';
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
on('change:repeating_equipment', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_equipment', eventInfo);
|
|
updateEquipment(repeatingInfo.rowId);
|
|
});
|
|
const weighEquipment = () => {
|
|
const options = {
|
|
collection: 'equipment',
|
|
toggle: 'carried',
|
|
qty: 'qty',
|
|
};
|
|
const sumItems = [
|
|
{
|
|
fieldToAdd: 'weight',
|
|
itemTotal: 'weight_total',
|
|
totalField: 'weight_equipment',
|
|
},
|
|
];
|
|
sumRepeating(options, sumItems);
|
|
};
|
|
on('change:repeating_equipment:carried change:repeating_equipment:qty change:repeating_equipment:weight remove:repeating_equipment', () => {
|
|
weighEquipment();
|
|
});
|
|
|
|
const weighAmmo = () => {
|
|
const options = {
|
|
collection: 'ammo',
|
|
qty: 'qty',
|
|
};
|
|
const sumItems = [
|
|
{
|
|
fieldToAdd: 'weight',
|
|
totalField: 'weight_ammo',
|
|
},
|
|
];
|
|
sumRepeating(options, sumItems);
|
|
};
|
|
on('change:repeating_ammo:weight change:repeating_ammo:qty', () => {
|
|
weighAmmo();
|
|
});
|
|
|
|
const updateJackOfAllTrades = () => {
|
|
const collectionArray = ['pb', 'jack_of_all_trades'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.jack_of_all_trades = Math.floor(getIntValue(v.pb) / 2);
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:jack_of_all_trades_toggle', () => {
|
|
updateJackOfAllTrades();
|
|
});
|
|
|
|
const updateRemarkableAthlete = () => {
|
|
const collectionArray = ['pb', 'remarkable_athlete'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.remarkable_athlete = Math.ceil(getIntValue(v.pb) / 2);
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:remarkable_athlete_toggle', () => {
|
|
updateRemarkableAthlete();
|
|
});
|
|
|
|
const updateInitiative = () => {
|
|
const collectionArray = ['initiative', 'initiative_formula', 'dexterity_mod', 'dexterity_check_bonus', 'initiative_bonus', 'jack_of_all_trades_toggle', 'jack_of_all_trades', 'remarkable_athlete_toggle', 'remarkable_athlete', 'global_check_bonus'];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs.initiative = 0;
|
|
getAttrs(collectionArray, (v) => {
|
|
const dexMod = getIntValue(v.dexterity_mod);
|
|
if (exists(dexMod)) {
|
|
finalSetAttrs.initiative += dexMod;
|
|
}
|
|
finalSetAttrs.initiative_formula = `${dexMod}[dex]`;
|
|
|
|
const dexCheckBonus = getIntValue(v.dexterity_check_bonus);
|
|
if (exists(dexCheckBonus)) {
|
|
finalSetAttrs.initiative += dexCheckBonus;
|
|
finalSetAttrs.initiative_formula += `${addArithmeticOperator(finalSetAttrs.initiative_formula, dexCheckBonus)}[dex check bonus]`;
|
|
}
|
|
|
|
const initiativeBonus = v.initiative_bonus;
|
|
if (exists(initiativeBonus)) {
|
|
if (!isNaN(initiativeBonus)) {
|
|
finalSetAttrs.initiative += getIntValue(initiativeBonus);
|
|
}
|
|
finalSetAttrs.initiative_formula += `${addArithmeticOperator(finalSetAttrs.initiative_formula, initiativeBonus)}[initiative bonus]`;
|
|
}
|
|
|
|
if (v.remarkable_athlete_toggle === '@{remarkable_athlete}') {
|
|
const remarkableAthlete = getIntValue(v.remarkable_athlete);
|
|
if (exists(remarkableAthlete)) {
|
|
finalSetAttrs.initiative += remarkableAthlete;
|
|
finalSetAttrs.initiative_formula += `${addArithmeticOperator(finalSetAttrs.initiative_formula, remarkableAthlete)}[remarkable athlete]`;
|
|
}
|
|
} else if (v.jack_of_all_trades_toggle === '@{jack_of_all_trades}') {
|
|
const jackOfAllTrades = getIntValue(v.jack_of_all_trades);
|
|
if (exists(jackOfAllTrades)) {
|
|
finalSetAttrs.initiative += jackOfAllTrades;
|
|
finalSetAttrs.initiative_formula += `${addArithmeticOperator(finalSetAttrs.initiative_formula, jackOfAllTrades)}[jack of all trades]`;
|
|
}
|
|
}
|
|
|
|
const globalCheckBonus = v.global_check_bonus;
|
|
if (exists(globalCheckBonus)) {
|
|
if (finalSetAttrs.initiative_formula) {
|
|
finalSetAttrs.initiative_formula += ' + ';
|
|
}
|
|
finalSetAttrs.initiative_formula += '(@{global_check_bonus})[global check bonus]';
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:dexterity_mod change:dexterity_check_bonus change:initiative_bonus change:jack_of_all_trades_toggle change:jack_of_all_trades change:remarkable_athlete_toggle change:remarkable_athlete change:global_check_bonus', () => {
|
|
updateInitiative();
|
|
});
|
|
|
|
const updateWeight = () => {
|
|
const collectionArray = ['weight_attacks', 'weight_ammo', 'weight_armor', 'weight_equipment', 'weight_coinage'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.weight_total = round((getFloatValue(v.weight_attacks) + getFloatValue(v.weight_ammo) + getFloatValue(v.weight_armor) + getFloatValue(v.weight_equipment) + getFloatValue(v.weight_coinage)), 2);
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:weight_attacks change:weight_ammo change:weight_armor change:weight_equipment change:weight_coinage', () => {
|
|
updateWeight();
|
|
});
|
|
const updateAttackToggle = (v, finalSetAttrs, repeatingString, options) => {
|
|
const attackParse = {
|
|
attackAbility: options.attackAbility,
|
|
parseName: 'attack',
|
|
toggleField: 'roll_toggle',
|
|
toggleFieldSetTo: toggleVars.roll,
|
|
triggerFields: ['type', 'attack_bonus'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, attackParse);
|
|
|
|
let attackFormula = '';
|
|
const attackToggle = v[`${repeatingString}roll_toggle`];
|
|
|
|
let toHit = 0;
|
|
if (!attackToggle || attackToggle === toggleVars.roll) {
|
|
const proficiency = v[`${repeatingString}proficiency`];
|
|
if (!proficiency || proficiency === 'on') {
|
|
const pb = getIntValue(v.pb);
|
|
toHit += pb;
|
|
attackFormula += `${pb}[proficient]`;
|
|
}
|
|
|
|
let attackAbility = v[`${repeatingString}attack_ability`];
|
|
if (isUndefined(attackAbility) && v[`${repeatingString}type`] === 'Ranged Weapon') {
|
|
attackAbility = 'dexterity';
|
|
finalSetAttrs[`${repeatingString}attack_ability`] = attackAbility;
|
|
} else if (finalSetAttrs[`${repeatingString}attack_ability`]) {
|
|
attackAbility = finalSetAttrs[`${repeatingString}attack_ability`];
|
|
}
|
|
attackAbility = getAbilityValue(v, attackAbility, options.defaultAbility);
|
|
if (exists(attackAbility)) {
|
|
toHit += attackAbility;
|
|
attackFormula += `${addArithmeticOperator(attackFormula, attackAbility)}[${getAbilityShortName(v[`${repeatingString}attack_ability`])}]`;
|
|
}
|
|
|
|
const attackBonus = getIntValue(v[`${repeatingString}attack_bonus`]);
|
|
if (exists(attackBonus)) {
|
|
toHit += attackBonus;
|
|
attackFormula += `${addArithmeticOperator(attackFormula, attackBonus)}[bonus]`;
|
|
}
|
|
|
|
if (exists(options.globalAttackBonus)) {
|
|
if (!isNaN(options.globalAttackBonus)) {
|
|
toHit += getIntValue(options.globalAttackBonus);
|
|
}
|
|
attackFormula += `${addArithmeticOperator(attackFormula, options.globalAttackBonus)}[${options.globalAttackBonusLabel}]`;
|
|
}
|
|
|
|
if (v[`${repeatingString}type`] === 'Melee Weapon') {
|
|
if (exists(options.globalMeleeAttackBonus)) {
|
|
if (!isNaN(options.globalMeleeAttackBonus)) {
|
|
toHit += getIntValue(options.globalMeleeAttackBonus);
|
|
}
|
|
attackFormula += `${addArithmeticOperator(attackFormula, options.globalMeleeAttackBonus)}[global melee attack bonus]`;
|
|
}
|
|
} else if (v[`${repeatingString}type`] === 'Ranged Weapon') {
|
|
if (exists(options.globalRangedAttackBonus)) {
|
|
if (!isNaN(options.globalRangedAttackBonus)) {
|
|
toHit += getIntValue(options.globalRangedAttackBonus);
|
|
}
|
|
attackFormula += `${addArithmeticOperator(attackFormula, options.globalRangedAttackBonus)}[global ranged attack bonus]`;
|
|
}
|
|
}
|
|
finalSetAttrs[`${repeatingString}attack_formula`] = attackFormula;
|
|
}
|
|
if (options.type === 'attack') {
|
|
finalSetAttrs[`${repeatingString}to_hit`] = toHit;
|
|
}
|
|
};
|
|
|
|
const updateSavingThrowToggle = (v, finalSetAttrs, repeatingString, options) => {
|
|
const savingThrowParse = {
|
|
parseName: 'savingThrow',
|
|
toggleField: 'saving_throw_toggle',
|
|
toggleFieldSetTo: toggleVars.saving_throw,
|
|
triggerFields: ['saving_throw_ability', 'saving_throw_bonus', 'saving_throw_vs_ability'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, savingThrowParse);
|
|
|
|
const savingThrowToggle = v[`${repeatingString}saving_throw_toggle`];
|
|
if (savingThrowToggle === toggleVars.saving_throw) {
|
|
let savingThrowDC = 8 + getIntValue(v.pb);
|
|
let savingThrowAbility = v[`${repeatingString}saving_throw_ability`];
|
|
if (!savingThrowAbility && savingThrowAbility !== '0') {
|
|
savingThrowAbility = v.default_ability;
|
|
finalSetAttrs[`${repeatingString}saving_throw_ability`] = v.default_ability;
|
|
}
|
|
|
|
savingThrowDC += getAbilityValue(v, savingThrowAbility, 'strength');
|
|
if (options && options.bonusDC) {
|
|
savingThrowDC += getIntValue(options.bonusDC);
|
|
}
|
|
savingThrowDC += getIntValue(v[`${repeatingString}saving_throw_bonus`]);
|
|
finalSetAttrs[`${repeatingString}saving_throw_dc`] = savingThrowDC;
|
|
}
|
|
};
|
|
|
|
const updateDamageToggle = (v, finalSetAttrs, repeatingString, options) => {
|
|
const damageParse = {
|
|
addCastingModifier: exists(v[`${repeatingString}add_casting_modifier`]),
|
|
parseName: 'damage',
|
|
toggleField: 'damage_toggle',
|
|
toggleFieldSetTo: toggleVars.damage,
|
|
triggerFields: ['damage', 'damage_ability', 'damage_bonus', 'damage_type'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, damageParse);
|
|
|
|
let damageString = '';
|
|
let damageFormula = '';
|
|
const damageToggle = v[`${repeatingString}damage_toggle`];
|
|
let damageAbility;
|
|
let damageType;
|
|
|
|
if (!damageToggle || damageToggle === toggleVars.damage) {
|
|
let damageAddition = 0;
|
|
|
|
const damage = v[`${repeatingString}damage`];
|
|
if (exists(damage)) {
|
|
damageString += damage;
|
|
damageFormula += `${damage}[damage]`;
|
|
}
|
|
|
|
if (!options.defaultDamageAbility) {
|
|
options.defaultDamageAbility = 0;
|
|
}
|
|
|
|
damageAbility = v[`${repeatingString}damage_ability`];
|
|
if (isUndefined(damageAbility) && v[`${repeatingString}type`] === 'Melee Weapon') {
|
|
damageAbility = 'strength';
|
|
finalSetAttrs[`${repeatingString}damage_ability`] = damageAbility;
|
|
}
|
|
if (isUndefined(damageAbility) && v[`${repeatingString}type`] === 'Ranged Weapon') {
|
|
damageAbility = 'dexterity';
|
|
finalSetAttrs[`${repeatingString}damage_ability`] = damageAbility;
|
|
}
|
|
if (exists(damageAbility) || options.defaultDamageAbility) {
|
|
const damageAbilityValue = getAbilityValue(v, damageAbility, options.defaultDamageAbility);
|
|
if (exists(damageAbilityValue)) {
|
|
damageAddition += damageAbilityValue;
|
|
damageFormula += `${addArithmeticOperator(damageFormula, damageAbilityValue)}[${getAbilityShortName(v[`${repeatingString}damage_ability`])}]`;
|
|
}
|
|
}
|
|
|
|
const damageBonus = getIntValue(v[`${repeatingString}damage_bonus`]);
|
|
if (exists(damageBonus)) {
|
|
damageAddition += damageBonus;
|
|
damageFormula += `${addArithmeticOperator(damageFormula, damageBonus)}[bonus]`;
|
|
}
|
|
|
|
if (exists(options.globalDamageBonus)) {
|
|
if (!isNaN(options.globalDamageBonus)) {
|
|
damageAddition += getIntValue(options.globalDamageBonus);
|
|
} else {
|
|
damageString += `${addArithmeticOperator(damageString, options.globalDamageBonus)}`;
|
|
}
|
|
damageFormula += `${addArithmeticOperator(damageFormula, options.globalDamageBonus)}[global damage bonus]`;
|
|
}
|
|
|
|
if (options && exists(options.globalMeleeDamageBonus) && (v[`${repeatingString}type`] === 'Melee Weapon')) {
|
|
if (!isNaN(options.globalMeleeDamageBonus)) {
|
|
damageAddition += getIntValue(options.globalMeleeDamageBonus);
|
|
} else {
|
|
damageString += `${addArithmeticOperator(damageString, options.globalMeleeDamageBonus)}`;
|
|
}
|
|
damageFormula += `${addArithmeticOperator(damageFormula, options.globalMeleeDamageBonus)}[global melee damage bonus]`;
|
|
} else if (options && exists(options.globalRangedDamageBonus) && v[`${repeatingString}type`] === 'Ranged Weapon') {
|
|
if (!isNaN(options.globalRangedDamageBonus)) {
|
|
damageAddition += getIntValue(options.globalRangedDamageBonus);
|
|
} else {
|
|
damageString += `${addArithmeticOperator(damageString, options.globalRangedDamageBonus)}`;
|
|
}
|
|
damageFormula += `${addArithmeticOperator(damageFormula, options.globalRangedDamageBonus)}[global ranged damage bonus]`;
|
|
}
|
|
|
|
if (exists(damageAddition)) {
|
|
damageString += addArithmeticOperator(damageString, damageAddition);
|
|
}
|
|
|
|
damageType = v[`${repeatingString}damage_type`];
|
|
if (exists(damageType)) {
|
|
if (hasUpperCase(damageType)) {
|
|
damageType = damageType.toLowerCase();
|
|
finalSetAttrs[`${repeatingString}damage_type`] = damageType;
|
|
}
|
|
damageString += ` ${damageType}`;
|
|
}
|
|
}
|
|
if (!exists(damageFormula)) {
|
|
damageFormula = 0;
|
|
}
|
|
finalSetAttrs[`${repeatingString}damage_formula`] = damageFormula;
|
|
|
|
const secondDamageParse = {
|
|
parseName: 'secondDamage',
|
|
toggleField: 'second_damage_toggle',
|
|
toggleFieldSetTo: toggleVars.second_damage,
|
|
triggerFields: ['second_damage', 'second_damage_ability', 'second_damage_bonus', 'second_damage_type'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, secondDamageParse);
|
|
|
|
let secondDamageFormula = '';
|
|
|
|
const secondDamageToggle = fromVOrFinalSetAttrs(v, finalSetAttrs, `${repeatingString}second_damage_toggle`);
|
|
if (secondDamageToggle === toggleVars.second_damage) {
|
|
let secondDamageAddition = 0;
|
|
const secondDamage = v[`${repeatingString}second_damage`];
|
|
if (exists(secondDamage)) {
|
|
damageString += ` + ${secondDamage}`;
|
|
secondDamageFormula += `${secondDamage}[second damage]`;
|
|
}
|
|
|
|
let secondDamageAbility = v[`${repeatingString}second_damage_ability`];
|
|
if (exists(secondDamageAbility)) {
|
|
secondDamageAbility = getAbilityValue(v, secondDamageAbility);
|
|
if (exists(secondDamageAbility)) {
|
|
secondDamageAddition += secondDamageAbility;
|
|
secondDamageFormula += `${addArithmeticOperator(secondDamageFormula, secondDamageAbility)}[${getAbilityShortName(v[`${repeatingString}second_damage_ability`])}]`;
|
|
}
|
|
}
|
|
|
|
const secondDamageBonus = getIntValue(v[`${repeatingString}second_damage_bonus`]);
|
|
if (exists(secondDamageBonus)) {
|
|
secondDamageAddition += secondDamageBonus;
|
|
secondDamageFormula += `${addArithmeticOperator(secondDamageFormula, secondDamageBonus)}[bonus]`;
|
|
}
|
|
|
|
if (exists(secondDamageAddition)) {
|
|
damageString += addArithmeticOperator(damageString, secondDamageAddition);
|
|
}
|
|
|
|
let secondDamageType = v[`${repeatingString}second_damage_type`];
|
|
if (exists(secondDamageType)) {
|
|
if (hasUpperCase(secondDamageType)) {
|
|
secondDamageType = secondDamageType.toLowerCase();
|
|
finalSetAttrs[`${repeatingString}second_damage_type`] = secondDamageType;
|
|
}
|
|
damageString += ` ${secondDamageType}`;
|
|
}
|
|
|
|
if (isUndefined(v[`${repeatingString}parsed`]) || v[`${repeatingString}parsed`].indexOf('damageProperties') === -1) {
|
|
const damageProperties = v[`${repeatingString}properties`];
|
|
if (exists(damageProperties)) {
|
|
if (damageProperties.indexOf('Versatile') !== -1) {
|
|
if (!exists(damageAbility)) {
|
|
damageAbility = 'strength';
|
|
}
|
|
finalSetAttrs[`${repeatingString}second_damage_ability`] = damageAbility;
|
|
finalSetAttrs[`${repeatingString}second_damage_type`] = damageType;
|
|
}
|
|
if (!finalSetAttrs[`${repeatingString}parsed`]) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ' damageProperties';
|
|
}
|
|
}
|
|
}
|
|
if (!exists(secondDamageFormula)) {
|
|
secondDamageFormula = 0;
|
|
}
|
|
finalSetAttrs[`${repeatingString}second_damage_formula`] = secondDamageFormula;
|
|
if (options.type === 'attack') {
|
|
finalSetAttrs[`${repeatingString}damage_string`] = damageString;
|
|
}
|
|
};
|
|
|
|
const updateHealToggle = (v, finalSetAttrs, repeatingString) => {
|
|
const healParse = {
|
|
addCastingModifier: exists(v[`${repeatingString}add_casting_modifier`]),
|
|
parseName: 'heal',
|
|
toggleField: 'heal_toggle',
|
|
toggleFieldSetTo: toggleVars.heal,
|
|
triggerFields: ['heal', 'heal_query_toggle'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, healParse);
|
|
let healFormula = '';
|
|
const healToggle = v[`${repeatingString}heal_toggle`];
|
|
if (healToggle === toggleVars.heal) {
|
|
if (!isUndefined(v[`${repeatingString}heal`])) {
|
|
healFormula = '@{heal}[heal]';
|
|
}
|
|
if (!isUndefined(v[`${repeatingString}heal_ability`])) {
|
|
const healAbility = getAbilityValue(v, v[`${repeatingString}heal_ability`]);
|
|
healFormula += `${addArithmeticOperator(healFormula, healAbility)}[${getAbilityShortName(v[`${repeatingString}heal_ability`])}]`;
|
|
}
|
|
if (!isUndefined(v[`${repeatingString}heal_bonus`])) {
|
|
const healBonus = getIntValue(v[`${repeatingString}heal_bonus`]);
|
|
healFormula += `${addArithmeticOperator(healFormula, healBonus)}[bonus]`;
|
|
}
|
|
|
|
if (!isUndefined(v.global_spell_heal_bonus)) {
|
|
if (healFormula) {
|
|
healFormula += ' + ';
|
|
}
|
|
healFormula += '@{global_spell_heal_bonus}[global spell heal bonus]';
|
|
}
|
|
if (v[`${repeatingString}heal_query_toggle`] === toggleVars.heal_query) {
|
|
if (healFormula) {
|
|
healFormula += ' + ';
|
|
}
|
|
healFormula += '@{heal_query_toggle}[query amount]';
|
|
}
|
|
|
|
finalSetAttrs[`${repeatingString}heal_formula`] = healFormula;
|
|
}
|
|
};
|
|
|
|
const updateHigherLevelToggle = (v, finalSetAttrs, repeatingString) => {
|
|
const higherLevelParse = {
|
|
addCastingModifier: exists(v[`${repeatingString}add_casting_modifier`]),
|
|
parseName: 'higherLevel',
|
|
toggleField: 'higher_level_toggle',
|
|
toggleFieldSetTo: toggleVars.higher_level,
|
|
triggerFields: ['higher_level_dice', 'higher_level_die', 'second_higher_level_dice', 'second_higher_level_die', 'higher_level_heal'],
|
|
};
|
|
parseAttackComponent(v, repeatingString, finalSetAttrs, higherLevelParse);
|
|
|
|
const higherLevelToggle = v[`${repeatingString}higher_level_toggle`];
|
|
if (exists(higherLevelToggle) && higherLevelToggle === toggleVars.higher_level) {
|
|
const spellLevel = getIntValue(v[`${repeatingString}spell_level`]);
|
|
finalSetAttrs[`${repeatingString}higher_level_query`] = `@{higher_level_query_${spellLevel}}`;
|
|
|
|
const damageToggle = v[`${repeatingString}damage_toggle`];
|
|
if (damageToggle && damageToggle === toggleVars.damage) {
|
|
const higherLevelDamage = '((@{higher_level_query} - @{spell_level}) * @{higher_level_dice})@{higher_level_die}[higher lvl]';
|
|
finalSetAttrs[`${repeatingString}damage_formula`] += addArithmeticOperator(finalSetAttrs[`${repeatingString}damage_formula`], higherLevelDamage);
|
|
finalSetAttrs[`${repeatingString}damage_crit_formula`] = higherLevelDamage;
|
|
}
|
|
|
|
const secondDamageToggle = v[`${repeatingString}second_damage_toggle`];
|
|
if (secondDamageToggle && secondDamageToggle === toggleVars.second_damage) {
|
|
const higherLevelSecondDamage = '((@{higher_level_query} - @{spell_level}) * @{second_higher_level_dice})@{second_higher_level_die}[higher lvl]';
|
|
finalSetAttrs[`${repeatingString}second_damage_formula`] += addArithmeticOperator(finalSetAttrs[`${repeatingString}second_damage_formula`], higherLevelSecondDamage);
|
|
finalSetAttrs[`${repeatingString}second_damage_crit_formula`] = higherLevelSecondDamage;
|
|
}
|
|
|
|
const healToggle = v[`${repeatingString}heal_toggle`];
|
|
if (healToggle && healToggle === toggleVars.heal) {
|
|
if (finalSetAttrs[`${repeatingString}heal_formula`]) {
|
|
finalSetAttrs[`${repeatingString}heal_formula`] += ' + ';
|
|
}
|
|
finalSetAttrs[`${repeatingString}heal_formula`] += '((@{higher_level_query} - @{spell_level}) * @{higher_level_dice})@{higher_level_die}[higher lvl] + (@{higher_level_heal} * (@{higher_level_query} - @{spell_level}))[higher lvl flat amount]';
|
|
}
|
|
}
|
|
};
|
|
|
|
const updateCritDamage = () => {
|
|
const repeatingItems = ['repeating_attack', 'repeating_spell', 'repeating_trait', 'repeating_action', 'repeating_reaction', 'repeating_legendaryaction', 'repeating_lairaction', 'repeating_regionaleffect'];
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const repeatingItem of repeatingItems) {
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}damage`);
|
|
collectionArray.push(`${repeatingString}damage_crit`);
|
|
collectionArray.push(`${repeatingString}second_damage`);
|
|
collectionArray.push(`${repeatingString}second_damage_crit`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (!v[`${repeatingString}damage_crit`] && v[`${repeatingString}damage`]) {
|
|
finalSetAttrs[`${repeatingString}damage_crit`] = v[`${repeatingString}damage`];
|
|
} else if (v[`${repeatingString}damage`] && v[`${repeatingString}damage_crit`]) {
|
|
finalSetAttrs[`${repeatingString}damage_crit`] = `${v[`${repeatingString}damage`]} + ${v[`${repeatingString}damage_crit`]}`;
|
|
}
|
|
if (!v[`${repeatingString}second_damage_crit`] && v[`${repeatingString}second_damage`]) {
|
|
finalSetAttrs[`${repeatingString}second_damage_crit`] = v[`${repeatingString}second_damage`];
|
|
} else if (v[`${repeatingString}second_damage`] && v[`${repeatingString}second_damage_crit`]) {
|
|
finalSetAttrs[`${repeatingString}second_damage_crit`] = `${v[`${repeatingString}second_damage`]} + ${v[`${repeatingString}second_damage_crit`]}`;
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
const setCritDamage = (v, finalSetAttrs, repeatingString) => {
|
|
if (!v[`${repeatingString}damage_crit`] && v[`${repeatingString}damage`]) {
|
|
finalSetAttrs[`${repeatingString}damage_crit`] = v[`${repeatingString}damage`];
|
|
}
|
|
if (!v[`${repeatingString}second_damage_crit`] && v[`${repeatingString}second_damage`]) {
|
|
finalSetAttrs[`${repeatingString}second_damage_crit`] = v[`${repeatingString}second_damage`];
|
|
}
|
|
};
|
|
|
|
const findAmmo = (name, callback) => {
|
|
const repeatingItem = 'repeating_ammo';
|
|
const collectionArray = [];
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}qty`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
if (v[`${repeatingString}name`] === name) {
|
|
callback(`@{${repeatingString}qty}`);
|
|
}
|
|
}
|
|
console.warn(`cannot find ammo field by the name ${name}`);
|
|
});
|
|
});
|
|
};
|
|
|
|
const updateActionChatMacro = (type) => {
|
|
const repeatingItem = `repeating_${type}`;
|
|
const collectionArray = [`${type}s_macro_var`, `${type}s_chat_var`, `${type}s_exist`];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs[`${type}s_macro_var`] = '';
|
|
finalSetAttrs[`${type}s_chat_var`] = '';
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
let actionName = v[`${repeatingString}name`];
|
|
const freeText = v[`${repeatingString}freetext`];
|
|
|
|
if (id !== ids[0]) {
|
|
finalSetAttrs[`${type}s_macro_var`] += ', ';
|
|
}
|
|
let actionType = 'action';
|
|
let title;
|
|
if (type === 'trait') {
|
|
title = 'Traits';
|
|
actionType = 'trait';
|
|
} else if (type === 'action') {
|
|
title = 'Actions';
|
|
} else if (type === 'reaction') {
|
|
title = 'Reactions';
|
|
} else if (type === 'legendaryaction') {
|
|
title = 'Legendary Actions';
|
|
} else if (type === 'lairaction') {
|
|
title = 'Lair Actions';
|
|
actionName = freeText;
|
|
} else if (type === 'regionaleffect') {
|
|
title = 'Regional Effects';
|
|
actionName = freeText;
|
|
}
|
|
|
|
if (actionName && actionName.length > 50) {
|
|
actionName = `${actionName.substring(0, 50)}...`;
|
|
}
|
|
|
|
finalSetAttrs[`${type}s_macro_var`] += `[${actionName}](~repeating_${type}_${id}_${actionType})`;
|
|
finalSetAttrs[`${type}s_chat_var`] += `{{${title}=${finalSetAttrs[`${type}s_macro_var`]}}}`;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const updateAction = (type, rowId) => {
|
|
const repeatingItem = `repeating_${type}`;
|
|
const collectionArray = ['pb', 'strength_mod', 'finesse_mod', 'global_attack_bonus', 'global_melee_attack_bonus', 'global_ranged_attack_bonus', 'global_damage_bonus', 'global_melee_damage_bonus', 'global_ranged_damage_bonus', 'default_ability'];
|
|
const finalSetAttrs = {};
|
|
|
|
const rechargeRegex = /\s*?\((?:Recharge\s*?(\d+\-\d+|\d+)|Recharges\safter\sa\s(.*))\)/gi;
|
|
const rechargeDayRegex = /\s*?\((\d+\/Day)\)/gi;
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}type`);
|
|
collectionArray.push(`${repeatingString}uses`);
|
|
collectionArray.push(`${repeatingString}uses_max`);
|
|
collectionArray.push(`${repeatingString}has_uses`);
|
|
collectionArray.push(`${repeatingString}roll_toggle`);
|
|
collectionArray.push(`${repeatingString}to_hit`);
|
|
collectionArray.push(`${repeatingString}attack_formula`);
|
|
collectionArray.push(`${repeatingString}proficiency`);
|
|
collectionArray.push(`${repeatingString}attack_ability`);
|
|
collectionArray.push(`${repeatingString}attack_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}saving_throw_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_dc`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}damage_formula`);
|
|
collectionArray.push(`${repeatingString}damage`);
|
|
collectionArray.push(`${repeatingString}damage_ability`);
|
|
collectionArray.push(`${repeatingString}damage_bonus`);
|
|
collectionArray.push(`${repeatingString}damage_type`);
|
|
collectionArray.push(`${repeatingString}damage_crit`);
|
|
collectionArray.push(`${repeatingString}second_damage_toggle`);
|
|
collectionArray.push(`${repeatingString}second_damage_formula`);
|
|
collectionArray.push(`${repeatingString}second_damage`);
|
|
collectionArray.push(`${repeatingString}second_damage_ability`);
|
|
collectionArray.push(`${repeatingString}second_damage_bonus`);
|
|
collectionArray.push(`${repeatingString}second_damage_type`);
|
|
collectionArray.push(`${repeatingString}second_damage_crit`);
|
|
collectionArray.push(`${repeatingString}damage_string`);
|
|
collectionArray.push(`${repeatingString}heal_toggle`);
|
|
collectionArray.push(`${repeatingString}heal`);
|
|
collectionArray.push(`${repeatingString}heal_ability`);
|
|
collectionArray.push(`${repeatingString}heal_bonus`);
|
|
collectionArray.push(`${repeatingString}heal_query_toggle`);
|
|
collectionArray.push(`${repeatingString}parsed`);
|
|
collectionArray.push(`${repeatingString}recharge`);
|
|
collectionArray.push(`${repeatingString}recharge_display`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
collectionArray.push(`${repeatingString}emote`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}freeform`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
const actionName = v[`${repeatingString}name`];
|
|
if (!isUndefined(actionName)) {
|
|
const rechargeResult = rechargeRegex.exec(actionName);
|
|
if (rechargeResult) {
|
|
finalSetAttrs[`${repeatingString}recharge`] = rechargeResult[1] || rechargeResult[2];
|
|
finalSetAttrs[`${repeatingString}name`] = actionName.replace(rechargeRegex, '');
|
|
}
|
|
const rechargeDayResult = rechargeDayRegex.exec(actionName);
|
|
if (rechargeDayResult) {
|
|
finalSetAttrs[`${repeatingString}recharge`] = rechargeDayResult[1] || rechargeDayResult[2];
|
|
finalSetAttrs[`${repeatingString}name`] = actionName.replace(rechargeDayRegex, '');
|
|
}
|
|
}
|
|
|
|
if (v[`${repeatingString}uses`] || v[`${repeatingString}uses_max`]) {
|
|
finalSetAttrs[`${repeatingString}has_uses`] = 1;
|
|
} else if (!isUndefined(v[`${repeatingString}has_uses`])) {
|
|
finalSetAttrs[`${repeatingString}has_uses`] = 0;
|
|
}
|
|
|
|
const recharge = fromVOrFinalSetAttrs(v, finalSetAttrs, `${repeatingString}recharge`);
|
|
if (exists(recharge)) {
|
|
if (isNaN(recharge)) {
|
|
finalSetAttrs[`${repeatingString}recharge_display`] = ` (${recharge})`;
|
|
} else {
|
|
finalSetAttrs[`${repeatingString}recharge_display`] = ` (Recharge ${recharge})`;
|
|
}
|
|
} else if (exists(v[`${repeatingString}recharge_display`])) {
|
|
finalSetAttrs[`${repeatingString}recharge_display`] = '';
|
|
}
|
|
|
|
let attackOptions = {};
|
|
if (type !== 'trait') {
|
|
attackOptions = {
|
|
defaultAbility: 'strength',
|
|
globalAttackBonus: v.global_attack_bonus,
|
|
globalAttackBonusLabel: 'global attack bonus',
|
|
globalMeleeAttackBonus: v.global_melee_attack_bonus,
|
|
globalRangedAttackBonus: v.global_ranged_attack_bonus,
|
|
type: 'attack',
|
|
};
|
|
}
|
|
updateAttackToggle(v, finalSetAttrs, repeatingString, attackOptions);
|
|
|
|
updateSavingThrowToggle(v, finalSetAttrs, repeatingString);
|
|
|
|
let damageOptions = {};
|
|
if (type !== 'trait') {
|
|
damageOptions = {
|
|
defaultDamageAbility: 'strength',
|
|
globalDamageBonus: v.global_damage_bonus,
|
|
globalMeleeDamageBonus: v.global_melee_damage_bonus,
|
|
globalRangedDamageBonus: v.global_ranged_damage_bonus,
|
|
type: 'attack',
|
|
};
|
|
}
|
|
updateDamageToggle(v, finalSetAttrs, repeatingString, damageOptions);
|
|
if (v.damage_type) {
|
|
finalSetAttrs.damage_type = lowercaseDamageTypes(v.damage_type);
|
|
}
|
|
if (v.second_damage_type) {
|
|
finalSetAttrs.second_damage_type = lowercaseDamageTypes(v.second_damage_type);
|
|
}
|
|
setCritDamage(v, finalSetAttrs, repeatingString);
|
|
|
|
updateHealToggle(v, finalSetAttrs, repeatingString);
|
|
|
|
if (isUndefined(v[`${repeatingString}extras_toggle`]) && (v[`${repeatingString}emote`] || v[`${repeatingString}freetext`] || v[`${repeatingString}freeform`])) {
|
|
finalSetAttrs[`${repeatingString}extras_toggle`] = toggleVars.extras;
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
const updateActions = () => {
|
|
updateAction('trait');
|
|
updateAction('action');
|
|
updateAction('reaction');
|
|
updateAction('legendaryaction');
|
|
updateAction('lairaction');
|
|
updateAction('regionaleffect');
|
|
};
|
|
|
|
const updateActionIfTriggered = (type, eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo(`repeating_${type}`, eventInfo);
|
|
if (repeatingInfo && repeatingInfo.field !== 'name' && repeatingInfo.field !== 'freetext' && repeatingInfo.field !== 'to_hit' && repeatingInfo.field !== 'attack_formula' && repeatingInfo.field !== 'damage_formula' && repeatingInfo.field !== 'damage_crit' && repeatingInfo.field !== 'second_damage_formula' && repeatingInfo.field !== 'second_damage_crit' && repeatingInfo.field !== 'damage_string' && repeatingInfo.field !== 'saving_throw_dc' && repeatingInfo.field !== 'parsed' && repeatingInfo.field !== 'recharge_display') {
|
|
updateAction(type, repeatingInfo.rowId);
|
|
}
|
|
};
|
|
on('change:repeating_trait', (eventInfo) => {
|
|
updateActionIfTriggered('trait', eventInfo);
|
|
});
|
|
on('change:repeating_action', (eventInfo) => {
|
|
updateActionIfTriggered('action', eventInfo);
|
|
});
|
|
on('change:repeating_reaction', (eventInfo) => {
|
|
updateActionIfTriggered('reaction', eventInfo);
|
|
});
|
|
on('change:repeating_legendaryaction', (eventInfo) => {
|
|
updateActionIfTriggered('legendaryaction', eventInfo);
|
|
});
|
|
on('change:repeating_lairaction', (eventInfo) => {
|
|
updateActionIfTriggered('lairaction', eventInfo);
|
|
});
|
|
on('change:repeating_regionaleffect', (eventInfo) => {
|
|
updateActionIfTriggered('regionaleffect', eventInfo);
|
|
});
|
|
|
|
const updateAttack = (rowId) => {
|
|
const repeatingItem = 'repeating_attack';
|
|
const collectionArray = ['pb', 'strength_mod', 'finesse_mod', 'global_attack_bonus', 'global_melee_attack_bonus', 'global_ranged_attack_bonus', 'global_damage_bonus', 'global_melee_damage_bonus', 'global_ranged_damage_bonus', 'default_ability', 'ammo_auto_use'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}type`);
|
|
collectionArray.push(`${repeatingString}roll_toggle`);
|
|
collectionArray.push(`${repeatingString}to_hit`);
|
|
collectionArray.push(`${repeatingString}attack_formula`);
|
|
collectionArray.push(`${repeatingString}proficiency`);
|
|
collectionArray.push(`${repeatingString}attack_ability`);
|
|
collectionArray.push(`${repeatingString}attack_bonus`);
|
|
collectionArray.push(`${repeatingString}ammo_toggle_var`);
|
|
collectionArray.push(`${repeatingString}ammo_field_name`);
|
|
collectionArray.push(`${repeatingString}ammo_used`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}saving_throw_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_dc`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}damage_formula`);
|
|
collectionArray.push(`${repeatingString}damage`);
|
|
collectionArray.push(`${repeatingString}damage_ability`);
|
|
collectionArray.push(`${repeatingString}damage_bonus`);
|
|
collectionArray.push(`${repeatingString}damage_type`);
|
|
collectionArray.push(`${repeatingString}damage_crit`);
|
|
collectionArray.push(`${repeatingString}second_damage_toggle`);
|
|
collectionArray.push(`${repeatingString}second_damage_formula`);
|
|
collectionArray.push(`${repeatingString}second_damage`);
|
|
collectionArray.push(`${repeatingString}second_damage_ability`);
|
|
collectionArray.push(`${repeatingString}second_damage_bonus`);
|
|
collectionArray.push(`${repeatingString}second_damage_type`);
|
|
collectionArray.push(`${repeatingString}second_damage_crit`);
|
|
collectionArray.push(`${repeatingString}damage_string`);
|
|
collectionArray.push(`${repeatingString}modifiers`);
|
|
collectionArray.push(`${repeatingString}properties`);
|
|
collectionArray.push(`${repeatingString}weight`);
|
|
collectionArray.push(`${repeatingString}parsed`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
const attackName = v[`${repeatingString}name`];
|
|
if (isUndefined(attackName)) {
|
|
return;
|
|
}
|
|
|
|
if (isUndefined(v[`${repeatingString}parsed`]) || v[`${repeatingString}parsed`].indexOf('modifiers') === -1) {
|
|
const attackModifiers = v[`${repeatingString}modifiers`];
|
|
if (exists(attackModifiers)) {
|
|
const attackBonus = attackModifiers.replace(/.*(?:Melee|Ranged) Attacks \+(\d+).*/gi, '$1');
|
|
const damageBonus = attackModifiers.replace(/.*(?:Melee|Ranged) Damage \+(\d+).*/gi, '$1');
|
|
|
|
finalSetAttrs[`${repeatingString}attack_bonus`] = attackBonus;
|
|
finalSetAttrs[`${repeatingString}damage_bonus`] = damageBonus;
|
|
if (!finalSetAttrs[`${repeatingString}parsed`]) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ' modifiers';
|
|
}
|
|
}
|
|
if (isUndefined(v[`${repeatingString}parsed`]) || v[`${repeatingString}parsed`].indexOf('attackProperties') === -1) {
|
|
const attackProperties = v[`${repeatingString}properties`];
|
|
if (exists(attackProperties)) {
|
|
if (attackProperties.indexOf('Reach') !== -1) {
|
|
finalSetAttrs[`${repeatingString}reach`] = '10 ft';
|
|
} else if (v[`${repeatingString}type`] === 'Melee Weapon') {
|
|
finalSetAttrs[`${repeatingString}reach`] = '5 ft';
|
|
}
|
|
if (attackProperties.indexOf('Finesse') !== -1) {
|
|
finalSetAttrs[`${repeatingString}attack_ability`] = 'finesse';
|
|
finalSetAttrs[`${repeatingString}damage_ability`] = 'finesse';
|
|
}
|
|
if (!finalSetAttrs[`${repeatingString}parsed`]) {
|
|
finalSetAttrs[`${repeatingString}parsed`] = '';
|
|
}
|
|
finalSetAttrs[`${repeatingString}parsed`] += ' attackProperties';
|
|
}
|
|
}
|
|
|
|
const attackOptions = {
|
|
defaultAbility: 'strength',
|
|
globalAttackBonus: v.global_attack_bonus,
|
|
globalAttackBonusLabel: 'global attack bonus',
|
|
globalMeleeAttackBonus: v.global_melee_attack_bonus,
|
|
globalRangedAttackBonus: v.global_ranged_attack_bonus,
|
|
type: 'attack',
|
|
};
|
|
updateAttackToggle(v, finalSetAttrs, repeatingString, attackOptions);
|
|
|
|
const ammoName = v[`${repeatingString}ammo_field_name`];
|
|
const ammoUsed = getIntValue(v[`${repeatingString}ammo_used`], 1);
|
|
if (!isUndefined(ammoName)) {
|
|
let ammoAutoUse;
|
|
if (v.ammo_auto_use === '1') {
|
|
ammoAutoUse = 1;
|
|
} else {
|
|
ammoAutoUse = 0;
|
|
}
|
|
|
|
findAmmo(ammoName, (ammoQtyName) => {
|
|
const setAmmo = {};
|
|
setAmmo[`${repeatingString}ammo_toggle_var`] = `{{ammo=[[${ammoQtyName}-${ammoAutoUse * ammoUsed}]]}} {{ammo_name=${ammoName}}}`;
|
|
setFinalAttrs(v, setAmmo);
|
|
});
|
|
}
|
|
|
|
updateSavingThrowToggle(v, finalSetAttrs, repeatingString);
|
|
|
|
const damageOptions = {
|
|
defaultDamageAbility: 'strength',
|
|
globalDamageBonus: v.global_damage_bonus,
|
|
globalMeleeDamageBonus: v.global_melee_damage_bonus,
|
|
globalRangedDamageBonus: v.global_ranged_damage_bonus,
|
|
type: 'attack',
|
|
};
|
|
updateDamageToggle(v, finalSetAttrs, repeatingString, damageOptions);
|
|
if (v.damage_type) {
|
|
finalSetAttrs.damage_type = lowercaseDamageTypes(v.damage_type);
|
|
}
|
|
if (v.second_damage_type) {
|
|
finalSetAttrs.second_damage_type = lowercaseDamageTypes(v.second_damage_type);
|
|
}
|
|
setCritDamage(v, finalSetAttrs, repeatingString);
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_attack', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_attack', eventInfo);
|
|
if (repeatingInfo && repeatingInfo.field !== 'toggle_details' && repeatingInfo.field !== 'to_hit' && repeatingInfo.field !== 'attack_formula' && repeatingInfo.field !== 'damage_formula' && repeatingInfo.field !== 'damage_crit' && repeatingInfo.field !== 'second_damage_formula' && repeatingInfo.field !== 'second_damage_crit' && repeatingInfo.field !== 'damage_string' && repeatingInfo.field !== 'saving_throw_dc' && repeatingInfo.field !== 'qty' && repeatingInfo.field !== 'weight' && repeatingInfo.field !== 'parsed') {
|
|
updateAttack(repeatingInfo.row);
|
|
}
|
|
});
|
|
const weighAttacks = () => {
|
|
const options = {
|
|
collection: 'attack',
|
|
toggle: 'carried',
|
|
qty: 'qty',
|
|
};
|
|
const sumItems = [
|
|
{
|
|
fieldToAdd: 'weight',
|
|
itemTotal: 'weight_total',
|
|
totalField: 'weight_attacks',
|
|
},
|
|
];
|
|
sumRepeating(options, sumItems);
|
|
};
|
|
|
|
const updateAttackChatMacro = () => {
|
|
const repeatingItem = 'repeating_attack';
|
|
const collectionArray = ['attacks_macro_var'];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs.attacks_macro_var = '';
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
let actionName = v[`${repeatingString}name`];
|
|
if (actionName && actionName.length > 50) {
|
|
actionName = `${actionName.substring(0, 50)}...`;
|
|
}
|
|
|
|
if (id !== ids[0]) {
|
|
finalSetAttrs.attacks_macro_var += ', ';
|
|
}
|
|
|
|
finalSetAttrs.attacks_macro_var += `[${actionName}](~repeating_attack_${id}_attack)`;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_attack remove:repeating_attack', () => {
|
|
updateAttackChatMacro();
|
|
});
|
|
on('change:repeating_attack:carried change:repeating_attack:qty change:repeating_attack:weight remove:repeating_attack', () => {
|
|
weighAttacks();
|
|
});
|
|
on('change:global_attack_bonus change:global_melee_attack_bonus change:global_ranged_attack_bonus change:global_damage_bonus change:global_melee_damage_bonus change:global_ranged_damage_bonus change:ammo_auto_use', () => {
|
|
updateAttack();
|
|
updateAction('action');
|
|
updateAction('reaction');
|
|
updateAction('legendaryaction');
|
|
updateAction('lairaction');
|
|
});
|
|
|
|
const updateSpell = (rowId) => {
|
|
const repeatingItem = 'repeating_spell';
|
|
const collectionArray = ['is_npc', 'pb', 'finesse_mod', 'global_spell_attack_bonus', 'global_spell_damage_bonus', 'global_spell_dc_bonus', 'global_spell_heal_bonus', 'default_ability', 'caster_level'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}type`);
|
|
collectionArray.push(`${repeatingString}roll_toggle`);
|
|
collectionArray.push(`${repeatingString}to_hit`);
|
|
collectionArray.push(`${repeatingString}attack_formula`);
|
|
collectionArray.push(`${repeatingString}proficiency`);
|
|
collectionArray.push(`${repeatingString}attack_ability`);
|
|
collectionArray.push(`${repeatingString}attack_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}saving_throw_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_vs_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_dc`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}damage_formula`);
|
|
collectionArray.push(`${repeatingString}damage`);
|
|
collectionArray.push(`${repeatingString}damage_ability`);
|
|
collectionArray.push(`${repeatingString}damage_bonus`);
|
|
collectionArray.push(`${repeatingString}damage_type`);
|
|
collectionArray.push(`${repeatingString}damage_crit`);
|
|
collectionArray.push(`${repeatingString}second_damage_toggle`);
|
|
collectionArray.push(`${repeatingString}second_damage_formula`);
|
|
collectionArray.push(`${repeatingString}second_damage`);
|
|
collectionArray.push(`${repeatingString}second_damage_ability`);
|
|
collectionArray.push(`${repeatingString}second_damage_bonus`);
|
|
collectionArray.push(`${repeatingString}second_damage_type`);
|
|
collectionArray.push(`${repeatingString}second_damage_crit`);
|
|
collectionArray.push(`${repeatingString}damage_string`);
|
|
collectionArray.push(`${repeatingString}parsed`);
|
|
collectionArray.push(`${repeatingString}spell_level`);
|
|
collectionArray.push(`${repeatingString}casting_time`);
|
|
collectionArray.push(`${repeatingString}components`);
|
|
collectionArray.push(`${repeatingString}heal_toggle`);
|
|
collectionArray.push(`${repeatingString}heal`);
|
|
collectionArray.push(`${repeatingString}heal_ability`);
|
|
collectionArray.push(`${repeatingString}heal_bonus`);
|
|
collectionArray.push(`${repeatingString}heal_query_toggle`);
|
|
collectionArray.push(`${repeatingString}add_casting_modifier`);
|
|
collectionArray.push(`${repeatingString}higher_level_toggle`);
|
|
collectionArray.push(`${repeatingString}higher_level_dice`);
|
|
collectionArray.push(`${repeatingString}higher_level_die`);
|
|
collectionArray.push(`${repeatingString}second_higher_level_dice`);
|
|
collectionArray.push(`${repeatingString}second_higher_level_die`);
|
|
collectionArray.push(`${repeatingString}higher_level_heal`);
|
|
collectionArray.push(`${repeatingString}components_verbal`);
|
|
collectionArray.push(`${repeatingString}components_somatic`);
|
|
collectionArray.push(`${repeatingString}components_material`);
|
|
collectionArray.push(`${repeatingString}duration`);
|
|
collectionArray.push(`${repeatingString}concentration`);
|
|
collectionArray.push(`${repeatingString}concentration_text`);
|
|
collectionArray.push(`${repeatingString}ritual`);
|
|
collectionArray.push(`${repeatingString}ritual_output`);
|
|
collectionArray.push(`${repeatingString}ritual_show`);
|
|
collectionArray.push(`${repeatingString}materials`);
|
|
collectionArray.push(`${repeatingString}materials_show`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
collectionArray.push(`${repeatingString}emote`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}freeform`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
const spellLevel = getIntValue(v[`${repeatingString}spell_level`]);
|
|
if (spellLevel === 0) {
|
|
finalSetAttrs[`${repeatingString}spell_level`] = spellLevel;
|
|
finalSetAttrs[`${repeatingString}is_prepared`] = 'on';
|
|
}
|
|
finalSetAttrs[`${repeatingString}friendly_level`] = ordinalSpellLevel(spellLevel);
|
|
|
|
const concentration = v[`${repeatingString}concentration`];
|
|
if (concentration === 'Yes') {
|
|
finalSetAttrs[`${repeatingString}concentration_show`] = 1;
|
|
finalSetAttrs[`${repeatingString}concentration_text`] = 'Concentration, ';
|
|
} else if (!isUndefined(v[`${repeatingString}concentration_text`])) {
|
|
finalSetAttrs[`${repeatingString}concentration_text`] = '';
|
|
finalSetAttrs[`${repeatingString}concentration_show`] = 0;
|
|
}
|
|
if (v.duration) {
|
|
finalSetAttrs.duration = v.duration.toLowerCase();
|
|
}
|
|
const ritual = v[`${repeatingString}ritual`];
|
|
if (ritual === 'Yes') {
|
|
finalSetAttrs[`${repeatingString}ritual_show`] = 1;
|
|
finalSetAttrs[`${repeatingString}ritual_output`] = '?{Cast as|Ritual,{{ritual=1}}|Spell,}';
|
|
} else {
|
|
if (!isUndefined(v[`${repeatingString}ritual_show`])) {
|
|
finalSetAttrs[`${repeatingString}ritual_show`] = 0;
|
|
}
|
|
if (!isUndefined(v[`${repeatingString}ritual_output`])) {
|
|
finalSetAttrs[`${repeatingString}ritual_output`] = '';
|
|
}
|
|
}
|
|
const materials = v[`${repeatingString}materials`];
|
|
if (!isUndefined(materials) && materials !== '') {
|
|
finalSetAttrs[`${repeatingString}materials_show`] = 1;
|
|
} else if (!isUndefined(v[`${repeatingString}materials_show`])) {
|
|
finalSetAttrs[`${repeatingString}materials_show`] = 0;
|
|
}
|
|
|
|
const spellComponents = v[`${repeatingString}components`];
|
|
finalSetAttrs[`${repeatingString}components_verbal`] = 0;
|
|
finalSetAttrs[`${repeatingString}components_somatic`] = 0;
|
|
finalSetAttrs[`${repeatingString}components_material`] = 0;
|
|
if (exists(spellComponents)) {
|
|
if (spellComponents.indexOf('V') !== -1) {
|
|
finalSetAttrs[`${repeatingString}components_verbal`] = 1;
|
|
}
|
|
if (spellComponents.indexOf('S') !== -1) {
|
|
finalSetAttrs[`${repeatingString}components_somatic`] = 1;
|
|
}
|
|
if (spellComponents.indexOf('M') !== -1) {
|
|
finalSetAttrs[`${repeatingString}components_material`] = 1;
|
|
}
|
|
}
|
|
|
|
const attackOptions = {
|
|
attackAbility: true,
|
|
globalAttackBonus: v.global_spell_attack_bonus,
|
|
type: 'spell',
|
|
};
|
|
updateAttackToggle(v, finalSetAttrs, repeatingString, attackOptions);
|
|
|
|
const savingThrowOptions = {
|
|
bonusDC: v.global_spell_dc_bonus,
|
|
};
|
|
updateSavingThrowToggle(v, finalSetAttrs, repeatingString, savingThrowOptions);
|
|
|
|
const damageOptions = {
|
|
globalDamageBonus: v.global_spell_damage_bonus,
|
|
type: 'spell',
|
|
};
|
|
updateDamageToggle(v, finalSetAttrs, repeatingString, damageOptions);
|
|
if (v.damage_type) {
|
|
finalSetAttrs.damage_type = lowercaseDamageTypes(v.damage_type);
|
|
}
|
|
if (v.second_damage_type) {
|
|
finalSetAttrs.second_damage_type = lowercaseDamageTypes(v.second_damage_type);
|
|
}
|
|
setCritDamage(v, finalSetAttrs, repeatingString);
|
|
|
|
if (getIntValue(v.is_npc) === 1 && v.caster_level && v[`${repeatingString}damage`] && v[`${repeatingString}damage`].indexOf('@{level}') !== -1) {
|
|
finalSetAttrs[`${repeatingString}damage`] = v[`${repeatingString}damage`].replace('@{level}', '@{caster_level}');
|
|
}
|
|
|
|
updateHealToggle(v, finalSetAttrs, repeatingString);
|
|
|
|
updateHigherLevelToggle(v, finalSetAttrs, repeatingString);
|
|
|
|
if (isUndefined(v[`${repeatingString}extras_toggle`]) && (v[`${repeatingString}emote`] || v[`${repeatingString}freetext`] || v[`${repeatingString}freeform`])) {
|
|
finalSetAttrs[`${repeatingString}extras_toggle`] = toggleVars.extras;
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_spell', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_spell', eventInfo);
|
|
if (repeatingInfo && repeatingInfo.field !== 'toggle_details' && repeatingInfo.field !== 'to_hit' && repeatingInfo.field !== 'attack_formula' && repeatingInfo.field !== 'damage_formula' && repeatingInfo.field !== 'damage_crit' && repeatingInfo.field !== 'second_damage_formula' && repeatingInfo.field !== 'second_damage_crit' && repeatingInfo.field !== 'damage_string' && repeatingInfo.field !== 'saving_throw_dc' && repeatingInfo.field !== 'heal_formula' && repeatingInfo.field !== 'higher_level_query' && repeatingInfo.field !== 'parsed') {
|
|
console.info('spell repeatingInfo.field', repeatingInfo.field);
|
|
updateSpell(repeatingInfo.rowId);
|
|
}
|
|
});
|
|
on('change:global_spell_attack_bonus change:global_spell_damage_bonus change:global_spell_dc_bonus change:global_spell_heal_bonus', () => {
|
|
updateSpell();
|
|
});
|
|
|
|
const updateSpellChatMacro = () => {
|
|
const repeatingItem = 'repeating_spell';
|
|
const collectionArray = ['spells_show_unprepared'];
|
|
const finalSetAttrs = {};
|
|
const spells = {
|
|
0: [],
|
|
1: [],
|
|
2: [],
|
|
3: [],
|
|
4: [],
|
|
5: [],
|
|
6: [],
|
|
7: [],
|
|
8: [],
|
|
9: [],
|
|
};
|
|
|
|
for (let i = 0; i <= 9; i++) {
|
|
collectionArray.push(`spells_level_${0}_macro_var`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}spell_level`);
|
|
collectionArray.push(`${repeatingString}is_prepared`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
const showUnprepared = v.spells_show_unprepared === 'on' || isUndefined(v.spells_show_unprepared);
|
|
const spellName = v[`${repeatingString}name`];
|
|
const spellLevel = getIntValue(v[`${repeatingString}spell_level`], 0);
|
|
const spellPrepared = v[`${repeatingString}is_prepared`] === 'on';
|
|
|
|
if (spellName && spellPrepared) {
|
|
spells[spellLevel].push(`[${spellName}](~repeating_spell_${id}_spell)`);
|
|
} else if (spellName && showUnprepared) {
|
|
spells[spellLevel].push(`<span class="sheet-unprepared">[${spellName}](~repeating_spell_${id}_spell)</span>`);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i <= 9; i++) {
|
|
if (spells[i].length > 0) {
|
|
finalSetAttrs[`spells_level_${i}_macro_var`] = spells[i].join(', ');
|
|
} else {
|
|
finalSetAttrs[`spells_level_${i}_macro_var`] = '';
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_spell', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_spell', eventInfo);
|
|
if (repeatingInfo && (repeatingInfo.field === 'name' || repeatingInfo.field === 'spell_level' || repeatingInfo.field === 'is_prepared')) {
|
|
updateSpellChatMacro();
|
|
}
|
|
});
|
|
on('change:spells_show_unprepared', () => {
|
|
updateSpellChatMacro();
|
|
});
|
|
on('remove:repeating_spell', () => {
|
|
updateSpellChatMacro();
|
|
});
|
|
|
|
const updateSpellChatMacroShow = () => {
|
|
const collectionArray = ['spells_show_spell_level_if_all_slots_are_used'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (let i = 0; i <= 9; i++) {
|
|
collectionArray.push(`spell_slots_l${i}`);
|
|
collectionArray.push(`spells_level_${i}_macro_var`);
|
|
collectionArray.push(`spells_level_${i}_show`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const showLevelIfAllSlotsAreUsed = v.spells_show_spell_level_if_all_slots_are_used === 'on' || isUndefined(v.spells_show_spell_level_if_all_slots_are_used);
|
|
|
|
for (let i = 0; i <= 9; i++) {
|
|
if ((!showLevelIfAllSlotsAreUsed && !getIntValue(v[`spell_slots_l${i}`])) || !v[`spells_level_${i}_macro_var`]) {
|
|
finalSetAttrs[`spells_level_${i}_show`] = '';
|
|
} else {
|
|
finalSetAttrs[`spells_level_${i}_show`] = true;
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
const watchForSpellChanges = () => {
|
|
const spellsWatch = ['change:spells_show_spell_level_if_all_slots_are_used'];
|
|
|
|
for (let i = 0; i <= 9; i++) {
|
|
spellsWatch.push(`change:spells_level_${i}_macro_var`);
|
|
spellsWatch.push(`change:spell_slots_l${i}`);
|
|
}
|
|
|
|
on(spellsWatch.join(' '), () => {
|
|
updateSpellChatMacroShow();
|
|
});
|
|
};
|
|
watchForSpellChanges();
|
|
|
|
const generateHigherLevelQueries = () => {
|
|
const collectionArray = ['warlock_level', 'number_of_classes'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (let i = 1; i <= 8; i++) {
|
|
collectionArray.push(`higher_level_query_${i}`);
|
|
}
|
|
for (let i = 1; i <= 9; i++) {
|
|
collectionArray.push(`spell_slots_l${i}`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (let i = 1; i <= 8; i++) {
|
|
let levelQuery = '';
|
|
|
|
if (i < 6 && v.number_of_classes === 1 && v.warlock_level > 0 && Math.ceil(getIntValue(v.warlock_level) / 2) >= i) {
|
|
let spellLevel = 1;
|
|
if (v.warlock_level >= 9) {
|
|
spellLevel = 5;
|
|
} else if (v.warlock_level >= 7) {
|
|
spellLevel = 4;
|
|
} else if (v.warlock_level >= 5) {
|
|
spellLevel = 3;
|
|
} else if (v.warlock_level >= 3) {
|
|
spellLevel = 2;
|
|
}
|
|
levelQuery += `|${spellLevel}`;
|
|
} else {
|
|
for (let j = i; j <= 9; j++) {
|
|
if (getIntValue(v[`spell_slots_l${j}`])) {
|
|
levelQuery += `|${j}`;
|
|
}
|
|
}
|
|
}
|
|
if (levelQuery !== '') {
|
|
finalSetAttrs[`higher_level_query_${i}`] = `?{Spell Level${levelQuery}}`;
|
|
} else {
|
|
finalSetAttrs[`higher_level_query_${i}`] = i;
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
on('change:warlock_level change:spell_slots_l1 change:spell_slots_l2 change:spell_slots_l3 change:spell_slots_l4 change:spell_slots_l5 change:spell_slots_l6 change:spell_slots_l7 change:spell_slots_l8 change:spell_slots_l9', () => {
|
|
generateHigherLevelQueries();
|
|
});
|
|
|
|
function updateD20Mod() {
|
|
const collectionArray = ['halfling_luck'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.halfling_luck === 'on') {
|
|
finalSetAttrs.d20_mod = 'ro<1';
|
|
} else {
|
|
finalSetAttrs.d20_mod = '';
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
}
|
|
|
|
on('change:halfling_luck', () => {
|
|
updateD20Mod();
|
|
});
|
|
|
|
const updateAbilityChecksMacro = () => {
|
|
const repeatingItem = 'repeating_skill';
|
|
const collectionArray = ['ability_checks_query_var', 'ability_checks_macro_var', 'ability_checks_show_totals'];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs.ability_checks_query_var = '?{Ability Check';
|
|
finalSetAttrs.ability_checks_macro_var = '';
|
|
finalSetAttrs.skills_macro_var = '';
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_check_mod`);
|
|
collectionArray.push(`${ability}_check_mod_with_sign`);
|
|
}
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}ability`);
|
|
collectionArray.push(`${repeatingString}total_with_sign`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const ability of ABILITIES) {
|
|
if (!v[`${ability}_check_mod_with_sign`]) {
|
|
console.warn('rerun until the fields are set');
|
|
updateAbilityModifiers();
|
|
updateAbilityChecksMacro();
|
|
}
|
|
|
|
finalSetAttrs.ability_checks_query_var += `|${capitalize(ability)},{{title=${capitalize(ability)}}} {{roll1=[[@{shaped_d20} + ${v[`${ability}_check_mod`]}]]}} @{roll_setting} + ${v[`${ability}_check_mod`]}]]}}`;
|
|
if (v.ability_checks_show_totals === 'on') {
|
|
finalSetAttrs.ability_checks_macro_var += `[${capitalize(ability)} ${v[`${ability}_check_mod_with_sign`]}](~shaped_${ability}_check)`;
|
|
} else {
|
|
finalSetAttrs.ability_checks_macro_var += `[${capitalize(ability)}](~shaped_${ability}_check)`;
|
|
}
|
|
finalSetAttrs.ability_checks_macro_var += ', ';
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
finalSetAttrs.ability_checks_query_var += `|${v[`${repeatingString}name`]}, {{title=${v[`${repeatingString}name`]} (${capitalize(getAbilityShortName(v[`${repeatingString}ability`]))})}} {{roll1=[[@{shaped_d20} + @{${repeatingString}formula}]]}} @{roll_setting} + @{${repeatingString}formula}]]}}`;
|
|
if (id !== ids[0]) {
|
|
finalSetAttrs.ability_checks_macro_var += ', ';
|
|
finalSetAttrs.skills_macro_var += ', ';
|
|
}
|
|
let skillButton;
|
|
if (v.ability_checks_show_totals === 'on') {
|
|
skillButton = `[${v[`${repeatingString}name`]} ${v[`${repeatingString}total_with_sign`]}](~repeating_skill_${id}_skill)`;
|
|
} else {
|
|
skillButton = `[${v[`${repeatingString}name`]}](~repeating_skill_${id}_skill)`;
|
|
}
|
|
finalSetAttrs.ability_checks_macro_var += skillButton;
|
|
finalSetAttrs.skills_macro_var += skillButton;
|
|
}
|
|
finalSetAttrs.ability_checks_query_var += '}';
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const updateShapedD20 = () => {
|
|
const collectionArray = ['roll_setting', 'roll_info', 'shaped_d20'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.roll_info = '';
|
|
finalSetAttrs.shaped_d20 = 'd20';
|
|
|
|
if (v.roll_setting === 'adv {{ignore=[[0') {
|
|
finalSetAttrs.roll_info = '{{advantage=1}}';
|
|
finalSetAttrs.shaped_d20 = '2d20@{d20_mod}kh1';
|
|
} else if (v.roll_setting === 'dis {{ignore=[[0') {
|
|
finalSetAttrs.roll_info = '{{disadvantage=1}}';
|
|
finalSetAttrs.shaped_d20 = '2d20@{d20_mod}kl1';
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:roll_setting', () => {
|
|
updateShapedD20();
|
|
});
|
|
|
|
const updateSkill = (rowId) => {
|
|
const repeatingItem = 'repeating_skill';
|
|
const collectionArray = ['jack_of_all_trades_toggle', 'jack_of_all_trades', 'remarkable_athlete_toggle', 'remarkable_athlete', 'pb', 'exp', 'global_check_bonus'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
collectionArray.push(`${ability}_check_bonus`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}skill_d20`);
|
|
collectionArray.push(`${repeatingString}skill_info`);
|
|
collectionArray.push(`${repeatingString}proficiency`);
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}storage_name`);
|
|
collectionArray.push(`${repeatingString}ability`);
|
|
collectionArray.push(`${repeatingString}bonus`);
|
|
collectionArray.push(`${repeatingString}ability_key`);
|
|
collectionArray.push(`${repeatingString}formula`);
|
|
collectionArray.push(`${repeatingString}total`);
|
|
collectionArray.push(`${repeatingString}total_with_sign`);
|
|
collectionArray.push(`${repeatingString}passive_bonus`);
|
|
collectionArray.push(`${repeatingString}passive_total`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
const skillName = v[`${repeatingString}name`];
|
|
if (isUndefined(skillName)) {
|
|
return;
|
|
}
|
|
|
|
const ability = getAbilityModName(v[`${repeatingString}ability`]);
|
|
const abilityName = getAbilityName(ability);
|
|
finalSetAttrs[`${repeatingString}ability_key`] = getAbilityShortName(v[`${repeatingString}ability`]).toUpperCase();
|
|
|
|
let total = 0;
|
|
let totalFormula = '';
|
|
const proficiency = v[`${repeatingString}proficiency`];
|
|
if (!proficiency || proficiency === 'unproficient') {
|
|
if ((abilityName === 'strength' || abilityName === 'dexterity' || abilityName === 'constitution') && v.remarkable_athlete_toggle === '@{remarkable_athlete}') {
|
|
const remarkableAthlete = getIntValue(v.remarkable_athlete);
|
|
total += remarkableAthlete;
|
|
totalFormula += `${remarkableAthlete}[remarkable athlete]`;
|
|
} else if (v.jack_of_all_trades_toggle === '@{jack_of_all_trades}') {
|
|
const jackOfAllTrades = getIntValue(v.jack_of_all_trades);
|
|
total += jackOfAllTrades;
|
|
totalFormula += `${jackOfAllTrades}[jack of all trades]`;
|
|
}
|
|
} else if (proficiency === 'proficient') {
|
|
const pb = getIntValue(v.pb);
|
|
total += pb;
|
|
totalFormula += `${pb}[proficient]`;
|
|
} else if (proficiency === 'expertise') {
|
|
const exp = getIntValue(v.exp);
|
|
total += exp;
|
|
totalFormula += `${exp}[expertise]`;
|
|
}
|
|
|
|
const abilityValue = getAbilityValue(v, ability, 'strength');
|
|
if (exists(abilityValue)) {
|
|
total += abilityValue;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, abilityValue)}[${getAbilityShortName(ability)}]`;
|
|
}
|
|
|
|
const skillBonus = getIntValue(v[`${repeatingString}bonus`]);
|
|
if (exists(skillBonus)) {
|
|
total += skillBonus;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, skillBonus)}[bonus]`;
|
|
}
|
|
|
|
if (exists(abilityName)) {
|
|
let checkBonus = v[`${abilityName}_check_bonus`];
|
|
if (exists(checkBonus)) {
|
|
checkBonus = getIntValue(checkBonus);
|
|
total += checkBonus;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, checkBonus)}[${getAbilityShortName(ability)} check bonus]`;
|
|
}
|
|
}
|
|
|
|
const globalCheckBonus = v.global_check_bonus;
|
|
if (exists(globalCheckBonus)) {
|
|
if (!isNaN(globalCheckBonus)) {
|
|
total += getIntValue(globalCheckBonus);
|
|
}
|
|
if (totalFormula) {
|
|
totalFormula += ' + ';
|
|
}
|
|
totalFormula += '(@{global_check_bonus})[global check bonus]';
|
|
}
|
|
const passiveBonus = getIntValue(v[`${repeatingString}passive_bonus`]);
|
|
let advantageOrDisadvantage = 0;
|
|
|
|
finalSetAttrs[`${repeatingString}skill_info`] = '';
|
|
|
|
if (!isUndefined(v[`${repeatingString}skill_d20`])) {
|
|
if (v[`${repeatingString}skill_d20`].indexOf('kh1') !== -1) {
|
|
finalSetAttrs[`${repeatingString}skill_info`] = '{{advantage=1}}';
|
|
advantageOrDisadvantage = 5;
|
|
} else if (v[`${repeatingString}skill_d20`].indexOf('kl1') !== -1) {
|
|
finalSetAttrs[`${repeatingString}skill_info`] = '{{disadvantage=1}}';
|
|
advantageOrDisadvantage = -5;
|
|
}
|
|
}
|
|
|
|
const passiveTotal = 10 + total + passiveBonus + advantageOrDisadvantage;
|
|
|
|
finalSetAttrs[`${repeatingString}total`] = total;
|
|
finalSetAttrs[`${repeatingString}passive`] = passiveTotal;
|
|
finalSetAttrs[`${repeatingString}total_with_sign`] = showSign(total);
|
|
finalSetAttrs[`${repeatingString}formula`] = totalFormula;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateAbilityChecksMacro();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
on('change:repeating_skill', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_skill', eventInfo);
|
|
if (repeatingInfo && repeatingInfo.field !== 'ability_key' && repeatingInfo.field !== 'total' && repeatingInfo.field !== 'total_with_sign' && repeatingInfo.field !== 'passive_total' && repeatingInfo.field !== 'passive_total_with_sign' && repeatingInfo.field !== 'formula') {
|
|
updateSkill(repeatingInfo.rowId);
|
|
}
|
|
});
|
|
on('remove:repeating_skill change:ability_checks_show_totals', () => {
|
|
updateAbilityChecksMacro();
|
|
});
|
|
on('change:jack_of_all_trades_toggle change:jack_of_all_trades change:remarkable_athlete_toggle change:remarkable_athlete change:global_check_bonus change:strength_check_bonus change:dexterity_check_bonus change:constitution_check_bonus change:intelligence_check_bonus change:wisdom_check_bonus change:charisma_check_bonus', () => {
|
|
updateSkill();
|
|
});
|
|
|
|
const updateSkillsFromSRD = () => {
|
|
const repeatingItem = 'repeating_skill';
|
|
const collectionArray = ['skills_srd', 'level', 'challenge', 'strength_mod', 'dexterity_mod', 'constitution_mod', 'intelligence_mod', 'wisdom_mod', 'charisma_mod'];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}ability`);
|
|
}
|
|
getAttrs(collectionArray, (v) => {
|
|
const skillsFromSRD = v.skills_srd;
|
|
const skillsObj = {};
|
|
const pb = getPB(v.level, v.challenge);
|
|
const expertise = pb * 2;
|
|
let skillName;
|
|
let repeatingString;
|
|
|
|
if (!isUndefined(skillsFromSRD)) {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
skillName = v[`${repeatingString}name`];
|
|
skillsObj[skillName] = id;
|
|
}
|
|
|
|
const re = /(\w+)\s?((?:\+|\-)\d+)/gi;
|
|
let match;
|
|
while ((match = re.exec(skillsFromSRD)) !== null) {
|
|
if (match && match[1] && match[2]) {
|
|
skillName = match[1];
|
|
if (skillsObj[skillName]) {
|
|
const skillId = skillsObj[skillName];
|
|
repeatingString = `${repeatingItem}_${skillId}_`;
|
|
|
|
const skillAbility = v[`${repeatingString}ability`];
|
|
const abilityValue = getAbilityValue(v, skillAbility);
|
|
const skillBonus = getIntValue(match[2]) - abilityValue;
|
|
|
|
if (skillBonus >= expertise) {
|
|
finalSetAttrs[`${repeatingString}proficiency`] = 'expertise';
|
|
if (skillBonus > expertise) {
|
|
finalSetAttrs[`${repeatingString}bonus`] = skillBonus - expertise;
|
|
}
|
|
} else if (skillBonus >= pb) {
|
|
finalSetAttrs[`${repeatingString}proficiency`] = 'proficient';
|
|
if (skillBonus > pb) {
|
|
finalSetAttrs[`${repeatingString}bonus`] = skillBonus - pb;
|
|
}
|
|
}
|
|
} else {
|
|
console.warn(`${skillName} does not exist in the list of skills`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:skills_srd', () => {
|
|
updateSkillsFromSRD();
|
|
});
|
|
|
|
const updateSavingThrow = (ability) => {
|
|
const collectionArray = ['pb', `${ability}_mod`, `${ability}_save_prof`, `${ability}_save_bonus`, 'global_saving_throw_bonus', 'saving_throws_half_proficiency'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const abilityMod = getIntValue(v[`${ability}_mod`]);
|
|
let total = abilityMod;
|
|
let totalFormula = `${abilityMod}[${getAbilityShortName(ability)}]`;
|
|
|
|
const pb = getIntValue(v.pb);
|
|
if (v[`${ability}_save_prof`] === '@{PB}') {
|
|
total += pb;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, pb)}[proficient]`;
|
|
} else if (v.saving_throws_half_proficiency === 'on') {
|
|
const halfPB = Math.floor(pb / 2);
|
|
total += halfPB;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, halfPB)}[half proficiency]`;
|
|
}
|
|
|
|
const abilitySavingThrowBonus = getIntValue(v[`${ability}_save_bonus`]);
|
|
if (abilitySavingThrowBonus) {
|
|
total += abilitySavingThrowBonus;
|
|
totalFormula += `${addArithmeticOperator(totalFormula, abilitySavingThrowBonus)}[${getAbilityShortName(ability)}saving throw bonus]`;
|
|
}
|
|
|
|
const globalSavingThrowBonus = v.global_saving_throw_bonus;
|
|
if (!isUndefined(globalSavingThrowBonus)) {
|
|
if (!isNaN(globalSavingThrowBonus)) {
|
|
total += getIntValue(globalSavingThrowBonus);
|
|
}
|
|
totalFormula += `${addArithmeticOperator(totalFormula, globalSavingThrowBonus)}[global saving throw bonus]`;
|
|
}
|
|
|
|
finalSetAttrs[`${ability}_saving_throw_mod`] = totalFormula;
|
|
finalSetAttrs[`${ability}_saving_throw_mod_with_sign`] = showSign(total);
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
const updateSavingThrows = () => {
|
|
updateSavingThrow('strength');
|
|
updateSavingThrow('dexterity');
|
|
updateSavingThrow('constitution');
|
|
updateSavingThrow('intelligence');
|
|
updateSavingThrow('wisdom');
|
|
updateSavingThrow('charisma');
|
|
};
|
|
on('change:pb change:global_saving_throw_bonus change:saving_throws_half_proficiency', () => {
|
|
updateSavingThrows();
|
|
});
|
|
on('change:strength_mod change:strength_save_prof change:strength_save_bonus', () => {
|
|
updateSavingThrow('strength');
|
|
});
|
|
on('change:dexterity_mod change:dexterity_save_prof change:dexterity_save_bonus', () => {
|
|
updateSavingThrow('dexterity');
|
|
});
|
|
on('change:constitution_mod change:constitution_save_prof change:constitution_save_bonus', () => {
|
|
updateSavingThrow('constitution');
|
|
});
|
|
on('change:intelligence_mod change:intelligence_save_prof change:intelligence_save_bonus', () => {
|
|
updateSavingThrow('intelligence');
|
|
});
|
|
on('change:wisdom_mod change:wisdom_save_prof change:wisdom_save_bonus', () => {
|
|
updateSavingThrow('wisdom');
|
|
});
|
|
on('change:charisma_mod change:charisma_save_prof change:charisma_save_bonus', () => {
|
|
updateSavingThrow('charisma');
|
|
});
|
|
|
|
const updateSavingThrowsFromSRD = () => {
|
|
const collectionArray = ['saving_throws_srd'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const savingThrowsFromSRD = v.saving_throws_srd;
|
|
const pbVar = '@{PB}';
|
|
|
|
if (savingThrowsFromSRD.indexOf('Str') !== -1) {
|
|
finalSetAttrs.strength_save_prof = pbVar;
|
|
}
|
|
if (savingThrowsFromSRD.indexOf('Dex') !== -1) {
|
|
finalSetAttrs.dexterity_save_prof = pbVar;
|
|
}
|
|
if (savingThrowsFromSRD.indexOf('Con') !== -1) {
|
|
finalSetAttrs.constitution_save_prof = pbVar;
|
|
}
|
|
if (savingThrowsFromSRD.indexOf('Int') !== -1) {
|
|
finalSetAttrs.intelligence_save_prof = pbVar;
|
|
}
|
|
if (savingThrowsFromSRD.indexOf('Wis') !== -1) {
|
|
finalSetAttrs.wisdom_save_prof = pbVar;
|
|
}
|
|
if (savingThrowsFromSRD.indexOf('Cha') !== -1) {
|
|
finalSetAttrs.charisma_save_prof = pbVar;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:saving_throws_srd', () => {
|
|
updateSavingThrowsFromSRD();
|
|
});
|
|
|
|
const updateSpellsFromSRD = () => {
|
|
const collectionArray = ['spells_srd'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const spells = v.spells_srd.split(', ');
|
|
|
|
for (const spell of spells) {
|
|
const repeatingString = `repeating_spell_${generateRowID()}_`;
|
|
finalSetAttrs[`${repeatingString}name`] = spell;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:spells_srd', () => {
|
|
updateSpellsFromSRD();
|
|
});
|
|
|
|
const updateAttachers = () => {
|
|
const repeatingItem = 'repeating_attacher';
|
|
const collectionArray = ['attacher_initiative', 'attacher_death_saving_throw', 'attacher_hit_dice', 'attacher_attack', 'attacher_spell', 'attacher_skill', 'attacher_crit'];
|
|
const finalSetAttrs = {};
|
|
const itemsToPush = ['initiative', 'death_saving_throw', 'hit_dice', 'attack', 'spell', 'skill'];
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`attacher_${ability}_check`);
|
|
collectionArray.push(`attacher_${ability}_saving_throw`);
|
|
itemsToPush.push(`${ability}_check`);
|
|
itemsToPush.push(`${ability}_saving_throw`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}freeform`);
|
|
collectionArray.push(`${repeatingString}crit_attacher`);
|
|
|
|
for (const itemToPush of itemsToPush) {
|
|
collectionArray.push(`${repeatingString}${itemToPush}_attacher`);
|
|
finalSetAttrs[`attacher_${itemToPush}`] = '';
|
|
}
|
|
}
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
for (const itemToPush of itemsToPush) {
|
|
const attacher = v[`${repeatingString}${itemToPush}_attacher`];
|
|
if (exists(attacher) && attacher === 'on') {
|
|
const attacherName = v[`${repeatingString}name`] || '';
|
|
|
|
const freeText = v[`${repeatingString}freetext`];
|
|
if (exists(attacherName) && exists(freeText)) {
|
|
const critAttacher = v[`${repeatingString}crit_attacher`];
|
|
if (critAttacher === 'on') {
|
|
finalSetAttrs[`attacher_${itemToPush}`] += `{{crit_name=${attacherName}}} `;
|
|
finalSetAttrs[`attacher_${itemToPush}`] += `{{crit_text=${freeText}}} `;
|
|
} else {
|
|
finalSetAttrs[`attacher_${itemToPush}`] += `{{${attacherName}=${freeText}}} `;
|
|
}
|
|
}
|
|
const freeForm = v[`${repeatingString}freeform`];
|
|
if (exists(freeForm)) {
|
|
finalSetAttrs[`attacher_${itemToPush}`] += `${freeForm} `;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
on('change:repeating_attacher remove:repeating_attacher', () => {
|
|
updateAttachers();
|
|
});
|
|
|
|
const updateNPCChallenge = () => {
|
|
const collectionArray = ['challenge', 'xp'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const challenge = v.challenge;
|
|
const xpPerChallenge = {
|
|
0: 0,
|
|
'1/8': 25,
|
|
'1/4': 50,
|
|
'1/2': 100,
|
|
1: 200,
|
|
2: 450,
|
|
3: 700,
|
|
4: 1100,
|
|
5: 1800,
|
|
6: 2300,
|
|
7: 2900,
|
|
8: 3900,
|
|
9: 5000,
|
|
10: 5900,
|
|
11: 7200,
|
|
12: 8400,
|
|
13: 10000,
|
|
14: 11500,
|
|
15: 13000,
|
|
16: 15000,
|
|
17: 18000,
|
|
18: 20000,
|
|
19: 22000,
|
|
20: 25000,
|
|
21: 33000,
|
|
22: 41000,
|
|
23: 50000,
|
|
24: 62000,
|
|
25: 75000,
|
|
26: 90000,
|
|
27: 105000,
|
|
28: 120000,
|
|
29: 135000,
|
|
30: 155000,
|
|
};
|
|
|
|
finalSetAttrs.xp = xpPerChallenge[challenge];
|
|
finalSetAttrs.xp_readable = numberWithCommas(finalSetAttrs.xp);
|
|
|
|
finalSetAttrs.level = challenge;
|
|
if (finalSetAttrs.level < 1) {
|
|
finalSetAttrs.level = 1;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
on('change:challenge', () => {
|
|
updateNPCChallenge();
|
|
});
|
|
|
|
const updateNPCHPFromSRD = () => {
|
|
const collectionArray = ['hp_srd', 'constitution', 'constitution_mod', 'constitution_bonus', 'global_ability_bonus'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (exists(v.hp_srd)) {
|
|
const match = v.hp_srd.match(/\((\d+)d(\d+)(?:\s?(\+|\-)\s?(\d+))?\)/i);
|
|
if (!match || !match[1] || !match[2]) {
|
|
console.warn('Character doesn\'t have valid HP/HD format');
|
|
} else {
|
|
const hdNum = getIntValue(match[1]);
|
|
|
|
let conMod = getIntValue(v.constitution_mod);
|
|
if (!conMod) {
|
|
const conScore = getIntValue(v.constitution);
|
|
const conBonus = getIntValue(v.constitution_bonus);
|
|
const globalAbilityBonus = getIntValue(v.global_ability_bonus);
|
|
|
|
conMod = getAbilityMod((conScore + conBonus + globalAbilityBonus));
|
|
}
|
|
|
|
finalSetAttrs.hit_dice = hdNum;
|
|
finalSetAttrs.hit_die = `d${getIntValue(match[2])}`;
|
|
|
|
const hpExpectedBonus = hdNum * conMod;
|
|
const hpBonusSign = match[3];
|
|
const hpBonus = getIntValue(match[4]);
|
|
if (hpBonus !== hpExpectedBonus) {
|
|
if (hpBonusSign === '-') {
|
|
finalSetAttrs.hp_extra = hpBonus + hpExpectedBonus;
|
|
} else {
|
|
finalSetAttrs.hp_extra = hpBonus - hpExpectedBonus;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:hp_srd', () => {
|
|
updateNPCHPFromSRD();
|
|
});
|
|
|
|
const updateNPCHP = () => {
|
|
const collectionArray = ['HP', 'HP_max', 'hp_formula', 'hit_dice', 'hit_die', 'hp_extra', 'constitution_mod'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const hdNum = getIntValue(v.hit_dice);
|
|
const hdSize = getIntValue(v.hit_die.replace('d', ''));
|
|
const hdAverage = (hdSize / 2) + 0.5;
|
|
let hpFormula = `${hdNum}d${hdSize}`;
|
|
const conMod = getIntValue(v.constitution_mod);
|
|
let totalHP = Math.floor(hdNum * hdAverage);
|
|
let amount;
|
|
|
|
if (conMod !== 0) {
|
|
const bonusHP = hdNum * conMod;
|
|
totalHP += bonusHP;
|
|
hpFormula += addArithmeticOperator(hpFormula, bonusHP);
|
|
}
|
|
|
|
if (exists(v.hp_extra)) {
|
|
const regex = (/(?:(\+|\-)\s?)?(\d+)(?:d(\d+))?/gi);
|
|
let splitFormula;
|
|
|
|
while ((splitFormula = regex.exec(v.hp_extra)) !== null) {
|
|
if (!splitFormula || !splitFormula[2]) {
|
|
console.warn('Character doesn\'t have valid hp formula');
|
|
} else {
|
|
amount = 0;
|
|
|
|
if (!splitFormula[3]) {
|
|
amount = getIntValue(splitFormula[2]);
|
|
} else {
|
|
const extraHdNum = getIntValue(splitFormula[2]);
|
|
const extraHdSize = getIntValue(splitFormula[3]);
|
|
const extraHdAverage = (extraHdSize / 2) + 0.5;
|
|
amount = Math.floor(extraHdNum * extraHdAverage);
|
|
}
|
|
|
|
if (!splitFormula[1] || splitFormula[1] === '+') {
|
|
totalHP += amount;
|
|
hpFormula += `${addArithmeticOperator(hpFormula, amount)}`;
|
|
} else if (splitFormula[1] === '-') {
|
|
totalHP -= amount;
|
|
hpFormula += `${addArithmeticOperator(hpFormula, amount)}`;
|
|
}
|
|
}
|
|
v.hp_extra.toString().replace(splitFormula[0], '');
|
|
}
|
|
}
|
|
|
|
if (totalHP) {
|
|
finalSetAttrs.HP = totalHP;
|
|
finalSetAttrs.HP_max = totalHP;
|
|
finalSetAttrs.hp_formula = hpFormula;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:hit_dice change:hit_die change:hp_extra change:constitution_mod', () => {
|
|
updateNPCHP();
|
|
});
|
|
const updateNPCHD = () => {
|
|
const collectionArray = ['hit_dice', 'hit_die'];
|
|
const finalSetAttrs = {};
|
|
const hd = {
|
|
d20: 0,
|
|
d12: 0,
|
|
d10: 0,
|
|
d8: 0,
|
|
d6: 0,
|
|
d4: 0,
|
|
d2: 0,
|
|
d0: 0,
|
|
};
|
|
for (const key in hd) {
|
|
if (hd.hasOwnProperty(key)) {
|
|
collectionArray.push(`hd_${key}_max`);
|
|
collectionArray.push(`hd_${key}_query`);
|
|
collectionArray.push(`hd_${key}_toggle`);
|
|
}
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const hdNum = getIntValue(v.hit_dice);
|
|
const hdSize = v.hit_die;
|
|
|
|
if (hdNum && hdSize) {
|
|
hd[hdSize] = hdNum;
|
|
|
|
updateHD(v, finalSetAttrs, hd);
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:hit_dice change:hit_die', () => {
|
|
updateNPCHD();
|
|
});
|
|
|
|
const updateNPCAC = () => {
|
|
const collectionArray = ['ac_srd', 'ac', 'ac_note', 'dexterity_mod'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (exists(v.ac_srd)) {
|
|
const match = v.ac_srd.match(/(\d+)\s?(.*)/);
|
|
if (match && match[1]) {
|
|
finalSetAttrs.AC = match[1];
|
|
}
|
|
if (match && match[2]) {
|
|
finalSetAttrs.ac_note = match[2].replace(/\(|\)/g, '');
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
on('change:ac_srd', () => {
|
|
updateNPCAC();
|
|
});
|
|
|
|
const setDefaultAbility = (v, finalSetAttrs) => {
|
|
const abilityScores = [getIntValue(v.intelligence), getIntValue(v.wisdom), getIntValue(v.charisma)];
|
|
const highestAbilityScore = Math.max.apply(Math, abilityScores);
|
|
let highestAbilityName = 'intelligence';
|
|
|
|
if (highestAbilityScore === abilityScores[1]) {
|
|
highestAbilityName = 'wisdom';
|
|
} else if (highestAbilityScore === abilityScores[2]) {
|
|
highestAbilityName = 'charisma';
|
|
}
|
|
|
|
finalSetAttrs.default_ability = `@{${highestAbilityName}_mod}`;
|
|
};
|
|
|
|
const parseSRDContentSection = (content, finalSetAttrs, title, name) => {
|
|
const re = /@(.*)@:\s([^@]+)/gi;
|
|
let match;
|
|
let section;
|
|
|
|
if (content.indexOf(title) !== -1) {
|
|
const contentSplit = content.split(`${title}\n`);
|
|
section = contentSplit[1];
|
|
content = contentSplit[0];
|
|
}
|
|
if (exists(section)) {
|
|
if (name === 'legendaryaction') {
|
|
const legendaryActionsMatch = section.match(/Can take (\d+) Legendary Actions/i);
|
|
if (legendaryActionsMatch && legendaryActionsMatch[1]) {
|
|
finalSetAttrs.legendary_action_amount = legendaryActionsMatch[1];
|
|
}
|
|
}
|
|
|
|
while ((match = re.exec(section.replace(/\*\*/g, '@'))) !== null) {
|
|
if (match && match[1] && match[2]) {
|
|
const repeatingString = `repeating_${name}_${generateRowID()}_`;
|
|
finalSetAttrs[`${repeatingString}name`] = match[1];
|
|
const text = match[2].trim();
|
|
if (name === 'trait') {
|
|
finalSetAttrs[`${repeatingString}display_text`] = text;
|
|
}
|
|
finalSetAttrs[`${repeatingString}freetext`] = text;
|
|
} else {
|
|
console.warn(`Character doesn\'t have a valid ${name} format`);
|
|
}
|
|
}
|
|
}
|
|
return content;
|
|
};
|
|
|
|
const updateNPCContent = () => {
|
|
const collectionArray = ['content_srd'];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(ability);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
let content = v.content_srd;
|
|
let regionalEffects;
|
|
let lairActions;
|
|
let repeatingString;
|
|
|
|
setDefaultAbility(v, finalSetAttrs);
|
|
|
|
if (exists(content)) {
|
|
if (content.indexOf('Regional Effects') !== -1) {
|
|
const regionalEffectsSplit = content.split(/Regional Effects\n/);
|
|
regionalEffects = regionalEffectsSplit[1];
|
|
content = regionalEffectsSplit[0];
|
|
}
|
|
if (exists(regionalEffects)) {
|
|
const regionalEffectsList = regionalEffects.split(/\*\*/);
|
|
regionalEffectsList.slice(1, -1).forEach((regionalEffect) => {
|
|
repeatingString = `repeating_regionaleffect_${generateRowID()}_`;
|
|
finalSetAttrs[`${repeatingString}freetext`] = regionalEffect.trim();
|
|
});
|
|
finalSetAttrs.regional_effects_fade = regionalEffectsList.slice(-1)[0];
|
|
}
|
|
if (content.indexOf('Lair Actions') !== -1) {
|
|
const lairActionsSplit = content.split(/Lair Actions\n/);
|
|
lairActions = lairActionsSplit[1];
|
|
content = lairActionsSplit[0];
|
|
}
|
|
if (exists(lairActions)) {
|
|
lairActions.split(/\*\*/).slice(1).forEach((lairAction) => {
|
|
repeatingString = `repeating_lairaction_${generateRowID()}_`;
|
|
finalSetAttrs[`${repeatingString}freetext`] = lairAction.trim();
|
|
});
|
|
}
|
|
|
|
content = parseSRDContentSection(content, finalSetAttrs, 'Legendary Actions', 'legendaryaction');
|
|
content = parseSRDContentSection(content, finalSetAttrs, 'Reactions', 'reaction');
|
|
content = parseSRDContentSection(content, finalSetAttrs, 'Actions', 'action');
|
|
parseSRDContentSection(content, finalSetAttrs, 'Traits', 'trait');
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:content_srd', () => {
|
|
updateNPCContent();
|
|
});
|
|
|
|
const displayTextForTraits = () => {
|
|
const repeatingItem = 'repeating_trait';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}display_text`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (isUndefined(v.display_text)) {
|
|
finalSetAttrs[`${repeatingString}display_text`] = v[`${repeatingString}freetext`];
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const parseDamage = (finalSetAttrs, repeatingString, freetext, regex, name, spellMods, meleeMods, spellAttack, rangedAttack, dexMod) => {
|
|
const damageParseRegex = /(\d+d?\d+)(?:\s?(?:\+|\-)\s?(\d+))?/gi;
|
|
const damage = regex.exec(freetext);
|
|
let damageBonus;
|
|
if (damage) {
|
|
/*
|
|
1 is damage without dice. Example '1'
|
|
2 is damage with dice. Example '2d6+4'
|
|
3 is damage type. Example 'slashing' or 'lightning or thunder'
|
|
*/
|
|
if (damage[1]) {
|
|
damageBonus = damage[1];
|
|
}
|
|
if (damage[2]) {
|
|
const damageParsed = damageParseRegex.exec(damage[2]);
|
|
if (damageParsed) {
|
|
if (damageParsed[1]) {
|
|
finalSetAttrs[repeatingString + name] = damageParsed[1];
|
|
}
|
|
if (damageParsed[2]) {
|
|
damageBonus = damageParsed[2];
|
|
}
|
|
}
|
|
}
|
|
damageBonus = getCorrectAbilityBasedOnBonus(finalSetAttrs, repeatingString, `${name}_ability`, damageBonus, spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
if (damage[3]) {
|
|
finalSetAttrs[`${repeatingString}${name}_type`] = damage[3];
|
|
}
|
|
if (damageBonus) {
|
|
finalSetAttrs[`${repeatingString}${name}_bonus`] = damageBonus;
|
|
}
|
|
freetext = freetext.replace(regex, '');
|
|
finalSetAttrs[`${repeatingString}${name}_toggle`] = toggleVars[name];
|
|
}
|
|
return freetext;
|
|
};
|
|
|
|
const parseAction = (type, rowId) => {
|
|
const repeatingItem = `repeating_${type}`;
|
|
const collectionArray = ['level', 'challenge', 'global_attack_bonus', 'global_melee_attack_bonus', 'global_ranged_attack_bonus', 'global_damage_bonus', 'global_melee_damage_bonus', 'global_ranged_damage_bonus', 'default_ability'];
|
|
const finalSetAttrs = {};
|
|
|
|
const damageSyntax = /(?:(\d+)|.*?\(([\dd\s\+\-]*)\).*?)\s*?/;
|
|
const damageType = /((?:[a-zA-Z]+|[a-zA-Z]+\s(?:or|and)\s[a-zA-Z]+)(?:\s*?\([a-zA-Z\s]+\))?)\s*?damage\s?(\([a-zA-Z'\s]+\))?/;
|
|
const altDamageSyntax = /(?:,\s*?or\s*?)/;
|
|
const plus = /\s*?plus\s*?/;
|
|
const savingThrowRe = /(?:DC)\s*?(\d+)\s*?([a-zA-Z]*)\s*?(?:saving throw)/;
|
|
const saveSuccess = /or\s(.*)?\son a successful one./;
|
|
const damageRegex = new RegExp(damageSyntax.source + damageType.source, 'i');
|
|
const damagePlusRegex = new RegExp(plus.source + damageSyntax.source + damageType.source, 'i');
|
|
const altDamageRegex = new RegExp(altDamageSyntax.source + damageSyntax.source + damageType.source, 'i');
|
|
const savingThrowRegex = new RegExp(savingThrowRe.source, 'i');
|
|
const saveSuccessRegex = new RegExp(saveSuccess.source, 'i');
|
|
|
|
const typeRegex = /(melee|ranged|melee or ranged)\s*(spell|weapon)\s*attack/gi;
|
|
const toHitRegex = /:\s?\+\s?(\d+)\s*(?:to hit)/gi;
|
|
const reachRegex = /(?:reach)\s?(\d+)\s?(?:ft)/gi;
|
|
const rangeRegex = /(?:range)\s?(\d+)\/(\d+)\s?(ft)/gi;
|
|
const spellcastingRegex = /(\d+)\w+\slevel\s\((\d+)\s?slot(?:s)?\)/gi;
|
|
const spellcastingLevelRegex = /(\d+)(?:st|dn|rd|th)-level spellcaster/i;
|
|
const spellcastingAbilityRegex = /spellcasting ability is (\w+)/i;
|
|
|
|
for (const ability of ABILITIES) {
|
|
collectionArray.push(`${ability}_mod`);
|
|
}
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
if (rowId) {
|
|
ids = [];
|
|
ids.push(rowId);
|
|
}
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const pb = getPB(v.level, v.challenge);
|
|
const strMod = getIntValue(v.strength_mod);
|
|
const dexMod = getIntValue(v.dexterity_mod);
|
|
const conMod = getIntValue(v.constitution_mod);
|
|
const intMod = getIntValue(v.intelligence_mod);
|
|
const wisMod = getIntValue(v.wisdom_mod);
|
|
const chaMod = getIntValue(v.charisma_mod);
|
|
const meleeMods = [strMod, dexMod];
|
|
const spellMods = [intMod, wisMod, chaMod];
|
|
const abilityMods = [strMod, dexMod, conMod, intMod, wisMod, chaMod];
|
|
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
let rangedAttack = false;
|
|
let spellAttack = false;
|
|
|
|
const name = v[`${repeatingString}name`];
|
|
let freetext = v[`${repeatingString}freetext`];
|
|
|
|
if (name && name.indexOf('Spellcasting') !== -1) {
|
|
const spellcastingSearch = spellcastingAbilityRegex.exec(freetext);
|
|
if (spellcastingSearch && spellcastingSearch[1]) {
|
|
const spellcastingAbility = spellcastingSearch[1].toLowerCase();
|
|
finalSetAttrs.default_ability = `@{${spellcastingAbility}_mod}`;
|
|
}
|
|
const spellcastingLevelSearch = spellcastingLevelRegex.exec(freetext);
|
|
if (spellcastingLevelSearch && spellcastingLevelSearch[1]) {
|
|
finalSetAttrs.caster_level = spellcastingLevelSearch[1];
|
|
}
|
|
|
|
let match;
|
|
while ((match = spellcastingRegex.exec(freetext)) !== null) {
|
|
if (match && match[1] && match[2]) {
|
|
finalSetAttrs[`spell_slots_l${match[1]}_bonus`] = match[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
const actionType = typeRegex.exec(freetext);
|
|
if (actionType) {
|
|
if (actionType[1]) {
|
|
actionType[1] = actionType[1].toLowerCase();
|
|
if (actionType[1] === 'melee') {
|
|
finalSetAttrs[`${repeatingString}type`] = 'Melee Weapon';
|
|
} else if (actionType[1] === 'ranged') {
|
|
finalSetAttrs[`${repeatingString}type`] = 'Ranged Weapon';
|
|
rangedAttack = true;
|
|
} else {
|
|
finalSetAttrs[`${repeatingString}type`] = 'Other';
|
|
}
|
|
}
|
|
if (actionType[2]) {
|
|
actionType[2] = actionType[2].toLowerCase();
|
|
if (actionType[2] === 'spell') {
|
|
spellAttack = true;
|
|
}
|
|
}
|
|
freetext = freetext.replace(typeRegex, '');
|
|
}
|
|
|
|
const reach = reachRegex.exec(freetext);
|
|
if (reach && reach[1]) {
|
|
finalSetAttrs[`${repeatingString}reach`] = `${reach[1]}ft.`;
|
|
freetext = freetext.replace(reachRegex, '');
|
|
}
|
|
const range = rangeRegex.exec(freetext);
|
|
if (range && range[1] && range[2]) {
|
|
finalSetAttrs[`${repeatingString}range`] = `${range[1]}ft.`;
|
|
freetext = freetext.replace(rangeRegex, '');
|
|
}
|
|
|
|
const toHit = toHitRegex.exec(freetext);
|
|
if (toHit && toHit[1]) {
|
|
let hitBonus = toHit[1];
|
|
hitBonus -= pb;
|
|
|
|
hitBonus = getCorrectAbilityBasedOnBonus(finalSetAttrs, repeatingString, 'attack_ability', hitBonus, spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
|
|
if (hitBonus) {
|
|
finalSetAttrs[`${repeatingString}attack_bonus`] = hitBonus;
|
|
}
|
|
freetext = freetext.replace(toHitRegex, '');
|
|
finalSetAttrs[`${repeatingString}roll_toggle`] = toggleVars.roll;
|
|
} else {
|
|
finalSetAttrs[`${repeatingString}roll_toggle`] = '0';
|
|
}
|
|
|
|
const savingThrow = savingThrowRegex.exec(freetext);
|
|
if (savingThrow) {
|
|
if (savingThrow[1]) {
|
|
let savingThrowTotal = savingThrow[1];
|
|
savingThrowTotal -= 8;
|
|
savingThrowTotal -= pb;
|
|
|
|
savingThrowTotal = getAnyCorrectAbilityBasedOnBonus(finalSetAttrs, repeatingString, 'saving_throw_ability', savingThrowTotal, abilityMods);
|
|
|
|
if (savingThrowTotal) {
|
|
finalSetAttrs[`${repeatingString}saving_throw_bonus`] = savingThrowTotal;
|
|
}
|
|
freetext = freetext.replace(savingThrowRegex, '');
|
|
finalSetAttrs[`${repeatingString}saving_throw_toggle`] = toggleVars.saving_throw;
|
|
}
|
|
if (savingThrow[2]) {
|
|
finalSetAttrs[`${repeatingString}saving_throw_vs_ability`] = savingThrow[2].toUpperCase();
|
|
}
|
|
const halfDamage = saveSuccessRegex.exec(freetext);
|
|
if (halfDamage && halfDamage[1]) {
|
|
finalSetAttrs[`${repeatingString}saving_throw_success`] = halfDamage[1];
|
|
freetext = freetext.replace(saveSuccessRegex, '');
|
|
}
|
|
} else {
|
|
finalSetAttrs[`${repeatingString}saving_throw_toggle`] = '0';
|
|
}
|
|
|
|
freetext = parseDamage(finalSetAttrs, repeatingString, freetext, damageRegex, 'damage', spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
freetext = parseDamage(finalSetAttrs, repeatingString, freetext, altDamageRegex, 'second_damage', spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
freetext = parseDamage(finalSetAttrs, repeatingString, freetext, damagePlusRegex, 'second_damage', spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
if (!exists(finalSetAttrs[`${repeatingString}second_damage`])) {
|
|
freetext = parseDamage(finalSetAttrs, repeatingString, freetext, damageRegex, 'second_damage', spellMods, meleeMods, spellAttack, rangedAttack, dexMod);
|
|
}
|
|
|
|
finalSetAttrs[`${repeatingString}extras_toggle`] = toggleVars.extras;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateAction(type, rowId);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_trait:freetext', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_trait', eventInfo);
|
|
parseAction('trait', repeatingInfo.rowId);
|
|
});
|
|
on('change:repeating_action:freetext', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_action', eventInfo);
|
|
parseAction('action', repeatingInfo.rowId);
|
|
});
|
|
on('change:repeating_reaction:freetext', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_reaction', eventInfo);
|
|
parseAction('reaction', repeatingInfo.rowId);
|
|
});
|
|
on('change:repeating_legendaryaction:freetext', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_legendaryaction', eventInfo);
|
|
parseAction('legendaryaction', repeatingInfo.rowId);
|
|
});
|
|
on('change:repeating_lairaction:freetext', (eventInfo) => {
|
|
const repeatingInfo = getRepeatingInfo('repeating_lairaction', eventInfo);
|
|
parseAction('lairaction', repeatingInfo.rowId);
|
|
});
|
|
|
|
const countAction = (type) => {
|
|
const repeatingItem = `repeating_${type}`;
|
|
const collectionArray = [`${type}s_exist`];
|
|
const finalSetAttrs = {};
|
|
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
getAttrs(collectionArray, (v) => {
|
|
if (ids.length > 0) {
|
|
finalSetAttrs[`${type}s_exist`] = 1;
|
|
} else if (exists(v[`${type}s_exist`])) {
|
|
finalSetAttrs[`${type}s_exist`] = 0;
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:repeating_trait remove:repeating_trait', () => {
|
|
countAction('trait');
|
|
updateActionChatMacro('trait');
|
|
});
|
|
on('change:repeating_action remove:repeating_action', () => {
|
|
countAction('action');
|
|
updateActionChatMacro('action');
|
|
});
|
|
on('change:repeating_reaction remove:repeating_reaction', () => {
|
|
countAction('reaction');
|
|
updateActionChatMacro('reaction');
|
|
});
|
|
on('change:repeating_legendaryaction remove:repeating_legendaryaction', () => {
|
|
countAction('legendaryaction');
|
|
updateActionChatMacro('legendaryaction');
|
|
});
|
|
on('change:repeating_lairaction remove:repeating_lairaction', () => {
|
|
countAction('lairaction');
|
|
updateActionChatMacro('lairaction');
|
|
});
|
|
on('change:repeating_regionaleffect remove:repeating_regionaleffect', () => {
|
|
countAction('regionaleffect');
|
|
updateActionChatMacro('regionaleffect');
|
|
});
|
|
|
|
const switchToNPC = () => {
|
|
const collectionArray = ['is_npc', 'size'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const isNPC = getIntValue(v.is_npc) === 1;
|
|
|
|
if (isNPC && isUndefined(v.size)) {
|
|
finalSetAttrs.size = 'Large';
|
|
}
|
|
|
|
if (isNPC) {
|
|
finalSetAttrs.hit_dice_output_option = '/w GM';
|
|
} else {
|
|
finalSetAttrs.hit_dice_output_option = '';
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:is_npc', () => {
|
|
switchToNPC();
|
|
});
|
|
|
|
const updateSize = () => {
|
|
const collectionArray = ['size'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const creatureSize = v.size || 'Large';
|
|
const sizeToHdSize = {
|
|
Tiny: 4,
|
|
Small: 6,
|
|
Medium: 8,
|
|
Large: 10,
|
|
Huge: 12,
|
|
Gargantuan: 20,
|
|
};
|
|
finalSetAttrs.hit_die = `d${sizeToHdSize[creatureSize]}`;
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:size', () => {
|
|
updateSize();
|
|
});
|
|
|
|
const updateType = () => {
|
|
const collectionArray = ['type'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.type) {
|
|
finalSetAttrs.type = v.type.toLowerCase();
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:type', () => {
|
|
updateType();
|
|
});
|
|
|
|
const updateAlignment = () => {
|
|
const collectionArray = ['alignment', 'is_npc'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.alignment && v.is_npc === '1') {
|
|
finalSetAttrs.alignment = v.alignment.toLowerCase();
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:alignment', () => {
|
|
updateAlignment();
|
|
});
|
|
|
|
const updateSenses = () => {
|
|
const collectionArray = ['senses'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.senses) {
|
|
finalSetAttrs.senses_exist = 1;
|
|
finalSetAttrs.senses = v.senses.toLowerCase();
|
|
} else {
|
|
finalSetAttrs.senses_exist = 0;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:senses', () => {
|
|
updateSenses();
|
|
});
|
|
|
|
const updateLanguages = () => {
|
|
const collectionArray = ['languages', 'languages_exist', 'languages_chat_var'];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs.languages_exist = 0;
|
|
finalSetAttrs.languages_chat_var = '';
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.languages) {
|
|
finalSetAttrs.languages_exist = 1;
|
|
finalSetAttrs.languages_chat_var = '{{Languages=@{languages}}}';
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:languages', () => {
|
|
updateLanguages();
|
|
});
|
|
|
|
const updateSpeed = () => {
|
|
const collectionArray = ['npc_speed'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.npc_speed = v.npc_speed.toLowerCase();
|
|
const match = finalSetAttrs.npc_speed.match(/^\s*(\d+)\s*ft/);
|
|
if (match && match[1]) {
|
|
finalSetAttrs.speed = match[1];
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:npc_speed', () => {
|
|
updateSpeed();
|
|
});
|
|
|
|
const updateACNote = () => {
|
|
const collectionArray = ['ac_note'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
finalSetAttrs.ac_note = v.ac_note.toLowerCase();
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
on('change:ac_note', () => {
|
|
updateACNote();
|
|
});
|
|
|
|
const updateDamageResistancesVar = () => {
|
|
const collectionArray = ['damage_resistances_var', 'damage_vulnerabilities_exist', 'damage_resistances_exist', 'damage_immunities_exist', 'condition_immunities_exist'];
|
|
const finalSetAttrs = {};
|
|
|
|
finalSetAttrs.damage_resistances_var = '';
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.damage_vulnerabilities_exist) {
|
|
finalSetAttrs.damage_resistances_var += '{{Damage Vulnerabilities=@{damage_vulnerabilities}}}';
|
|
}
|
|
if (v.damage_resistances_exist) {
|
|
if (finalSetAttrs.damage_resistances_var !== '') {
|
|
finalSetAttrs.damage_resistances_var += ' ';
|
|
}
|
|
finalSetAttrs.damage_resistances_var += '{{Damage Resistances=@{damage_resistances}}}';
|
|
}
|
|
if (v.damage_immunities_exist) {
|
|
if (finalSetAttrs.damage_resistances_var !== '') {
|
|
finalSetAttrs.damage_resistances_var += ' ';
|
|
}
|
|
finalSetAttrs.damage_resistances_var += '{{Damage Immunities=@{damage_immunities}}}';
|
|
}
|
|
if (v.condition_immunities_exist) {
|
|
if (finalSetAttrs.damage_resistances_var !== '') {
|
|
finalSetAttrs.damage_resistances_var += ' ';
|
|
}
|
|
finalSetAttrs.damage_resistances_var += '{{Condition Immunities=@{condition_immunities}}}';
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const updateDamageVulnerabilities = () => {
|
|
const collectionArray = ['damage_vulnerabilities'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.damage_vulnerabilities) {
|
|
finalSetAttrs.damage_vulnerabilities_exist = 1;
|
|
finalSetAttrs.damage_vulnerabilities = lowercaseDamageTypes(v.damage_vulnerabilities);
|
|
} else {
|
|
finalSetAttrs.damage_vulnerabilities_exist = 0;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateDamageResistancesVar();
|
|
});
|
|
});
|
|
};
|
|
on('change:damage_vulnerabilities', () => {
|
|
updateDamageVulnerabilities();
|
|
});
|
|
const updateDamageResistances = () => {
|
|
const collectionArray = ['damage_resistances'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.damage_resistances) {
|
|
finalSetAttrs.damage_resistances_exist = 1;
|
|
finalSetAttrs.damage_resistances = lowercaseDamageTypes(v.damage_resistances);
|
|
} else {
|
|
finalSetAttrs.damage_resistances_exist = 0;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateDamageResistancesVar();
|
|
});
|
|
});
|
|
};
|
|
on('change:damage_resistances', () => {
|
|
updateDamageResistances();
|
|
});
|
|
const updateDamageImmunities = () => {
|
|
const collectionArray = ['damage_immunities'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.damage_immunities) {
|
|
finalSetAttrs.damage_immunities_exist = 1;
|
|
finalSetAttrs.damage_immunities = lowercaseDamageTypes(v.damage_immunities);
|
|
} else {
|
|
finalSetAttrs.damage_immunities_exist = 0;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateDamageResistancesVar();
|
|
});
|
|
});
|
|
};
|
|
on('change:damage_immunities', () => {
|
|
updateDamageImmunities();
|
|
});
|
|
const updateConditionImmunities = () => {
|
|
const collectionArray = ['condition_immunities'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.condition_immunities) {
|
|
finalSetAttrs.condition_immunities_exist = 1;
|
|
finalSetAttrs.condition_immunities = lowercaseDamageTypes(v.condition_immunities);
|
|
} else {
|
|
finalSetAttrs.condition_immunities_exist = 0;
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs, () => {
|
|
updateDamageResistancesVar();
|
|
});
|
|
});
|
|
};
|
|
on('change:condition_immunities', () => {
|
|
updateConditionImmunities();
|
|
});
|
|
|
|
const resourcesToTraits = () => {
|
|
const repeatingItem = 'repeating_resource';
|
|
const newRepeatingItem = 'repeating_trait';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
let repeatingString;
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}uses`);
|
|
collectionArray.push(`${repeatingString}uses_max`);
|
|
collectionArray.push(`${repeatingString}toggle_details`);
|
|
collectionArray.push(`${repeatingString}recharge`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}freeform`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
const newRepeatingString = `${newRepeatingItem}_${generateRowID()}_`;
|
|
|
|
finalSetAttrs[`${newRepeatingString}name`] = v[`${repeatingString}name`];
|
|
finalSetAttrs[`${newRepeatingString}uses`] = v[`${repeatingString}uses`];
|
|
finalSetAttrs[`${newRepeatingString}uses_max`] = v[`${repeatingString}uses_max`];
|
|
finalSetAttrs[`${newRepeatingString}toggle_details`] = v[`${repeatingString}toggle_details`];
|
|
finalSetAttrs[`${newRepeatingString}recharge`] = v[`${repeatingString}recharge`];
|
|
finalSetAttrs[`${newRepeatingString}extras_toggle`] = v[`${repeatingString}extras_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}freetext`] = v[`${repeatingString}freetext`];
|
|
finalSetAttrs[`${newRepeatingString}freeform`] = v[`${repeatingString}freeform`];
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const classFeaturesToTraits = () => {
|
|
const repeatingItem = 'repeating_classfeature';
|
|
const newRepeatingItem = 'repeating_trait';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
let repeatingString;
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}uses`);
|
|
collectionArray.push(`${repeatingString}uses_max`);
|
|
collectionArray.push(`${repeatingString}recharge`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}saving_throw_condition`);
|
|
collectionArray.push(`${repeatingString}saving_throw_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_bonus`);
|
|
collectionArray.push(`${repeatingString}saving_throw_vs_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_failure`);
|
|
collectionArray.push(`${repeatingString}saving_throw_success`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}damage`);
|
|
collectionArray.push(`${repeatingString}damage_ability`);
|
|
collectionArray.push(`${repeatingString}damage_bonus`);
|
|
collectionArray.push(`${repeatingString}damage_type`);
|
|
collectionArray.push(`${repeatingString}second_damage_toggle`);
|
|
collectionArray.push(`${repeatingString}second_damage`);
|
|
collectionArray.push(`${repeatingString}second_damage_ability`);
|
|
collectionArray.push(`${repeatingString}second_damage_bonus`);
|
|
collectionArray.push(`${repeatingString}second_damage_type`);
|
|
collectionArray.push(`${repeatingString}heal`);
|
|
collectionArray.push(`${repeatingString}heal_ability`);
|
|
collectionArray.push(`${repeatingString}heal_bonus`);
|
|
collectionArray.push(`${repeatingString}heal_query_toggle`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
collectionArray.push(`${repeatingString}emote`);
|
|
collectionArray.push(`${repeatingString}freetext`);
|
|
collectionArray.push(`${repeatingString}freeform`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
const newRepeatingString = `${newRepeatingItem}_${generateRowID()}_`;
|
|
finalSetAttrs[`${newRepeatingString}name`] = v[`${repeatingString}name`];
|
|
finalSetAttrs[`${newRepeatingString}uses`] = v[`${repeatingString}uses`];
|
|
finalSetAttrs[`${newRepeatingString}uses_max`] = v[`${repeatingString}uses_max`];
|
|
finalSetAttrs[`${newRepeatingString}recharge`] = v[`${repeatingString}recharge`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_toggle`] = v[`${repeatingString}saving_throw_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_condition`] = v[`${repeatingString}saving_throw_condition`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_ability`] = v[`${repeatingString}saving_throw_ability`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_bonus`] = v[`${repeatingString}saving_throw_bonus`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_vs_ability`] = v[`${repeatingString}saving_throw_vs_ability`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_failure`] = v[`${repeatingString}saving_throw_failure`];
|
|
finalSetAttrs[`${newRepeatingString}saving_throw_success`] = v[`${repeatingString}saving_throw_success`];
|
|
finalSetAttrs[`${newRepeatingString}damage_toggle`] = v[`${repeatingString}damage_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}damage`] = v[`${repeatingString}damage`];
|
|
finalSetAttrs[`${newRepeatingString}damage_ability`] = v[`${repeatingString}damage_ability`];
|
|
finalSetAttrs[`${newRepeatingString}damage_bonus`] = v[`${repeatingString}damage_bonus`];
|
|
finalSetAttrs[`${newRepeatingString}damage_type`] = v[`${repeatingString}damage_type`];
|
|
finalSetAttrs[`${newRepeatingString}second_damage_toggle`] = v[`${repeatingString}second_damage_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}second_damage`] = v[`${repeatingString}second_damage`];
|
|
finalSetAttrs[`${newRepeatingString}second_damage_ability`] = v[`${repeatingString}second_damage_ability`];
|
|
finalSetAttrs[`${newRepeatingString}second_damage_bonus`] = v[`${repeatingString}second_damage_bonus`];
|
|
finalSetAttrs[`${newRepeatingString}second_damage_type`] = v[`${repeatingString}second_damage_type`];
|
|
finalSetAttrs[`${newRepeatingString}heal`] = v[`${repeatingString}heal`];
|
|
finalSetAttrs[`${newRepeatingString}heal_ability`] = v[`${repeatingString}heal_ability`];
|
|
finalSetAttrs[`${newRepeatingString}heal_bonus`] = v[`${repeatingString}heal_bonus`];
|
|
finalSetAttrs[`${newRepeatingString}heal_query_toggle`] = v[`${repeatingString}heal_query_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}extras_toggle`] = v[`${repeatingString}extras_toggle`];
|
|
finalSetAttrs[`${newRepeatingString}emote`] = v[`${repeatingString}emote`];
|
|
finalSetAttrs[`${newRepeatingString}freetext`] = v[`${repeatingString}freetext`];
|
|
finalSetAttrs[`${newRepeatingString}display_text`] = v[`${repeatingString}freetext`];
|
|
finalSetAttrs[`${newRepeatingString}freeform`] = v[`${repeatingString}freeform`];
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const getSkillIdByStorageName = (v, repeatingItem, ids, prop) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
if (v[`${repeatingString}storage_name`] === prop || v[`${repeatingString}storage_name`] === camelize(prop)) {
|
|
return id;
|
|
}
|
|
}
|
|
};
|
|
|
|
const setAdvantageOnStealth = (mode) => {
|
|
const repeatingItem = 'repeating_skill';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
let repeatingString;
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}storage_name`);
|
|
collectionArray.push(`${repeatingString}skill_d20`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const prop in SKILLS) {
|
|
if (SKILLS.hasOwnProperty(prop)) {
|
|
const skillId = getSkillIdByStorageName(v, repeatingItem, ids, prop);
|
|
repeatingString = `${repeatingItem}_${skillId}_`;
|
|
|
|
if (v[`${repeatingString}storage_name`] === 'STEALTH') {
|
|
if (mode === 'dis') {
|
|
finalSetAttrs[`${repeatingString}skill_d20`] = '2d20@{d20_mod}kl1';
|
|
} else if (mode === 'normal') {
|
|
finalSetAttrs[`${repeatingString}skill_d20`] = '@{shaped_d20}';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const generateSkills = () => {
|
|
const repeatingItem = 'repeating_skill';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
let repeatingString;
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}storage_name`);
|
|
collectionArray.push(`${repeatingString}name`);
|
|
collectionArray.push(`${repeatingString}ability`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const prop in SKILLS) {
|
|
if (SKILLS.hasOwnProperty(prop)) {
|
|
let skillId = getSkillIdByStorageName(v, repeatingItem, ids, prop);
|
|
|
|
if (!skillId) {
|
|
skillId = generateRowID();
|
|
}
|
|
repeatingString = `${repeatingItem}_${skillId}_`;
|
|
|
|
finalSetAttrs[`${repeatingString}storage_name`] = prop;
|
|
finalSetAttrs[`${repeatingString}name`] = getTranslationByKey(prop);
|
|
finalSetAttrs[`${repeatingString}ability`] = SKILLS[prop];
|
|
updateSkill(skillId);
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
on('change:generate_skills', () => {
|
|
generateSkills();
|
|
});
|
|
|
|
on('change:pb', () => {
|
|
updateSkill();
|
|
updateAttack();
|
|
updateSpell();
|
|
updateJackOfAllTrades();
|
|
updateRemarkableAthlete();
|
|
updateActions();
|
|
});
|
|
on('change:strength_mod change:dexterity_mod change:constitution_mod change:intelligence_mod change:wisdom_mod change:charisma_mod', () => {
|
|
updateSkill();
|
|
updateAttack();
|
|
updateSpell();
|
|
updateActions();
|
|
});
|
|
|
|
const extasToExtrasFix = (repeatingItem) => {
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}extas_toggle`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
if (!isUndefined(v[`${repeatingString}extas_toggle`])) {
|
|
finalSetAttrs[`${repeatingString}extras_toggle`] = v[`${repeatingString}extas_toggle`];
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const armorPlusDexRemoval = () => {
|
|
const repeatingItem = 'repeating_armor';
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}type`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
const armorType = v[`${repeatingString}type`];
|
|
if (armorType === 'Armor + Dex') {
|
|
finalSetAttrs[`${repeatingString}type`] = 'Light Armor';
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
};
|
|
|
|
const fixRollTwo = () => {
|
|
const collectionArray = ['roll_setting'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (v.roll_setting === '@{attr_roll_2}') {
|
|
finalSetAttrs.roll_setting = '{{roll2=[[d20@{d20_mod}';
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const atSyntaxToAbilityName = (v, finalSetAttrs, repeatingString, field) => {
|
|
if (v[`${repeatingString}${field}`]) {
|
|
finalSetAttrs[`${repeatingString}${field}`] = getAbilityName(v[`${repeatingString}${field}`]);
|
|
}
|
|
};
|
|
|
|
const updateActionComponents = () => {
|
|
const repeatingItems = ['repeating_attack', 'repeating_spell', 'repeating_trait', 'repeating_action', 'repeating_reaction', 'repeating_legendaryaction', 'repeating_lairaction', 'repeating_regionaleffect'];
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const repeatingItem of repeatingItems) {
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}attack_ability`);
|
|
collectionArray.push(`${repeatingString}damage_ability`);
|
|
collectionArray.push(`${repeatingString}second_damage_ability`);
|
|
collectionArray.push(`${repeatingString}heal_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_ability`);
|
|
collectionArray.push(`${repeatingString}saving_throw_vs_ability`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'attack_ability');
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'damage_ability');
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'second_damage_ability');
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'heal_ability');
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'saving_throw_ability');
|
|
|
|
if (v[`${repeatingString}saving_throw_vs_ability`]) {
|
|
finalSetAttrs[`${repeatingString}saving_throw_vs_ability`] = v[`${repeatingString}saving_throw_vs_ability`].toUpperCase();
|
|
}
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
const changeOldToggleToNew = (v, finalSetAttrs, field, oldValue, newValue) => {
|
|
if (v[field] === `@{${oldValue}}`) {
|
|
finalSetAttrs[field] = newValue;
|
|
}
|
|
};
|
|
const removeToggleVar = () => {
|
|
const collectionArray = ['careful_spell_toggle', 'distant_spell_toggle', 'empowered_spell_toggle', 'extended_spell_toggle', 'heightened_spell_toggle', 'quickened_spell_toggle', 'subtle_spell_toggle', 'twinned_spell_toggle', 'initiative_tie_breaker', 'ammo_auto_use', 'hide_attack', 'hide_damage', 'hide_saving_throw_dc', 'hide_spell_content', 'hide_action_freetext', 'hide_saving_throw_failure', 'hide_saving_throw_success', 'hide_recharge', 'initiative_output_option', 'output_option', 'death_save_output_option', 'hit_dice_output_option', 'roll_setting', 'show_character_name', 'attacks_vs_target_ac', 'attacks_vs_target_name', 'initiative_roll', 'initiative_to_tracker'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
changeOldToggleToNew(v, finalSetAttrs, 'careful_spell_toggle', 'careful_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'distant_spell_toggle', 'distant_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'empowered_spell_toggle', 'empowered_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'extended_spell_toggle', 'extended_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'heightened_spell_toggle', 'heightened_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'quickened_spell_toggle', 'quickened_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'subtle_spell_toggle', 'subtle_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'twinned_spell_toggle', 'twinned_spell_toggle_var', '1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_tie_breaker', 'initiative_tie_breaker_var', '[[@{initiative} / 100]][tie breaker]');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'ammo_auto_use', 'ammo_auto_use_var', '1');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_attack', 'hide_attack_var', '{{hide_attack=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_damage', 'hide_damage_var', '{{hide_damage=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_saving_throw_dc', 'hide_saving_throw_dc_var', '{{hide_saving_throw_dc=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_spell_content', 'hide_spell_content_var', '{{hide_spell_content=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_action_freetext', 'hide_action_freetext_var', '{{hide_freetext=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_saving_throw_failure', 'hide_saving_throw_failure_var', '{{hide_saving_throw_failure=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_saving_throw_success', 'hide_saving_throw_success_var', '{{hide_saving_throw_success=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hide_recharge', 'hide_recharge_var', '{{hide_recharge=1}}');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_output_option', 'output_to_all', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_output_option', 'output_to_gm', '/w GM');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'output_option', 'output_to_all', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'output_option', 'output_to_gm', '/w GM');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'death_save_output_option', 'output_to_all', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'death_save_output_option', 'output_to_gm', '/w GM');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hit_dice_output_option', 'output_to_all', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'hit_dice_output_option', 'output_to_gm', '/w GM');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'roll_setting', 'roll_advantage', 'adv {{ignore=[[0');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'roll_setting', 'roll_disadvantage', 'dis {{ignore=[[0');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'roll_setting', 'roll_1', '{{ignore=[[0');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'roll_setting', 'roll_2', '{{roll2=[[d20@{d20_mod}');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'show_character_name', 'show_character_name_no', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'show_character_name', 'show_character_name_yes', '{{show_character_name=1}}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'attacks_vs_target_ac', 'attacks_vs_target_ac_no', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'attacks_vs_target_ac', 'attacks_vs_target_ac_yes', '[[@{target|AC}]]');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'attacks_vs_target_name', 'attacks_vs_target_name_no', '');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'attacks_vs_target_name', 'attacks_vs_target_name_yes', '@{target|token_name}');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_roll', 'normal_initiative', '@{shaped_d20}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_roll', 'advantage_on_initiative', '2d20@{d20_mod}kh1');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_roll', 'disadvantage_on_initiative', '2d20@{d20_mod}kl1');
|
|
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_to_tracker', 'initiative_to_tracker_yes', '@{selected|initiative_formula} &{tracker}');
|
|
changeOldToggleToNew(v, finalSetAttrs, 'initiative_to_tracker', 'initiative_to_tracker_no', '@{initiative_formula}');
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const changeOldRepeatingToggleToNew = (v, finalSetAttrs, repeatingString, field, oldValue, newValue) => {
|
|
if (v[`${repeatingString}${field}`] === `@{${oldValue}}`) {
|
|
finalSetAttrs[`${repeatingString}${field}`] = newValue;
|
|
}
|
|
};
|
|
const updateActionComponentsToRemoveExtraFields = () => {
|
|
const repeatingItems = ['repeating_attack', 'repeating_spell', 'repeating_trait', 'repeating_action', 'repeating_reaction', 'repeating_legendaryaction', 'repeating_lairaction', 'repeating_regionaleffect'];
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const repeatingItem of repeatingItems) {
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}roll_toggle`);
|
|
collectionArray.push(`${repeatingString}content_toggle`);
|
|
collectionArray.push(`${repeatingString}saving_throw_toggle`);
|
|
collectionArray.push(`${repeatingString}damage_toggle`);
|
|
collectionArray.push(`${repeatingString}second_damage_toggle`);
|
|
collectionArray.push(`${repeatingString}extras_toggle`);
|
|
collectionArray.push(`${repeatingString}heal_toggle`);
|
|
collectionArray.push(`${repeatingString}heal_query_toggle`);
|
|
collectionArray.push(`${repeatingString}higher_level_toggle`);
|
|
collectionArray.push(`${repeatingString}special_effects_toggle`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'roll_toggle', 'roll_toggle_var', '{{vs_ac=1}} @{roll_info} {{roll1=[[@{shaped_d20}cs>@{crit_range} + @{attack_formula}]]}} @{roll_setting}cs>@{crit_range} + @{attack_formula}]]}} {{targetAC=@{attacks_vs_target_ac}}} {{targetName=@{attacks_vs_target_name}}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'content_toggle', 'content_toggle_var', '{{content=@{content}}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'saving_throw_toggle', 'saving_throw_toggle_var', '{{saving_throw_condition=@{saving_throw_condition}}} {{saving_throw_dc=@{saving_throw_dc}}} {{saving_throw_vs_ability=@{saving_throw_vs_ability}}} {{saving_throw_failure=@{saving_throw_failure}}} {{saving_throw_success=@{saving_throw_success}}} {{targetName=@{attacks_vs_target_name}}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'damage_toggle', 'damage_toggle_var', '{{damage=[[@{damage_formula}]]}} {{damage_type=@{damage_type}}} {{crit_damage=[[0d0 + @{damage_crit}[crit damage] @{damage_crit_formula}]]}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'second_damage_toggle', 'second_damage_toggle_var', '{{second_damage=[[@{second_damage_formula}]]}} {{second_damage_type=@{second_damage_type}}} {{second_crit_damage=[[0d0 + @{second_damage_crit}[crit damage] @{second_damage_crit_formula}]]}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'extras_toggle', 'extras_var', '{{emote=@{emote}}} {{freetext=@{freetext}}} @{freeform}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'heal_toggle', 'heal_toggle_var', '{{heal=[[@{heal_formula}]]}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'heal_query_toggle', 'heal_query', '?{Heal Bonus Amount|}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'higher_level_toggle', 'higher_level_toggle_var', '{{cast_as_level=@{higher_level_query}}}');
|
|
changeOldRepeatingToggleToNew(v, finalSetAttrs, repeatingString, 'special_effects_toggle', 'special_effects_var', '{{fx=@{type}-@{color} @{points_of_origin}}}');
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
const updateSkillAbility = () => {
|
|
const repeatingItems = ['repeating_skill'];
|
|
const collectionArray = [];
|
|
const finalSetAttrs = {};
|
|
|
|
for (const repeatingItem of repeatingItems) {
|
|
getSectionIDs(repeatingItem, (ids) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
collectionArray.push(`${repeatingString}ability`);
|
|
}
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
for (const id of ids) {
|
|
const repeatingString = `${repeatingItem}_${id}_`;
|
|
|
|
atSyntaxToAbilityName(v, finalSetAttrs, repeatingString, 'ability');
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
const updateArmorAbility = () => {
|
|
const collectionArray = ['ac_unarmored_ability'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (!isUndefined(v.ac_unarmored_ability)) {
|
|
finalSetAttrs.ac_unarmored_ability = getAbilityName(v.ac_unarmored_ability);
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const updateDefaultAbility = () => {
|
|
const collectionArray = ['default_ability'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
if (!isUndefined(v.default_ability)) {
|
|
finalSetAttrs.default_ability = getAbilityName(v.default_ability);
|
|
}
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
const checkVersionFormat = (version, finalSetAttrs) => {
|
|
const versionRegex = /\d+\.\d+\.\d+/gi;
|
|
const versionIsProperFormat = versionRegex.exec(version);
|
|
|
|
if (version && !versionIsProperFormat) {
|
|
finalSetAttrs.version = version = currentVersion;
|
|
}
|
|
return version;
|
|
};
|
|
|
|
const sheetOpened = () => {
|
|
const collectionArray = ['version', 'strength', 'dexterity', 'constitution', 'intelligence', 'wisdom', 'charisma', 'import_data', 'roll_setting'];
|
|
const finalSetAttrs = {};
|
|
|
|
getAttrs(collectionArray, (v) => {
|
|
const version = checkVersionFormat(v.version, finalSetAttrs);
|
|
|
|
if (!version) {
|
|
if (!v.import_data) {
|
|
finalSetAttrs.edit_mode = 'on';
|
|
}
|
|
if (isUndefined(v.roll_setting)) { // API Script import sets this when making characters
|
|
finalSetAttrs.roll_setting = '{{ignore=[[0';
|
|
}
|
|
const setAbilities = {};
|
|
if (isUndefined(v.strength)) {
|
|
setAbilities.strength = 10;
|
|
}
|
|
if (isUndefined(v.dexterity)) {
|
|
setAbilities.dexterity = 10;
|
|
}
|
|
if (isUndefined(v.constitution)) {
|
|
setAbilities.constitution = 10;
|
|
}
|
|
if (isUndefined(v.intelligence)) {
|
|
setAbilities.intelligence = 10;
|
|
}
|
|
if (isUndefined(v.wisdom)) {
|
|
setAbilities.wisdom = 10;
|
|
}
|
|
if (isUndefined(v.charisma)) {
|
|
setAbilities.charisma = 10;
|
|
}
|
|
setFinalAttrs(v, setAbilities, () => {
|
|
updatePb();
|
|
generateSkills();
|
|
updateSavingThrows();
|
|
updateLevels();
|
|
updateAbilityChecksMacro();
|
|
updateInitiative();
|
|
updateArmor();
|
|
updateSpellChatMacroShow();
|
|
});
|
|
} else {
|
|
if (versionCompare(version, '2.1.0') < 0) {
|
|
updateNPCChallenge();
|
|
updateDamageVulnerabilities();
|
|
updateDamageResistances();
|
|
updateDamageImmunities();
|
|
updateConditionImmunities();
|
|
updateLanguages();
|
|
updateSenses();
|
|
}
|
|
if (versionCompare(version, '2.1.3') < 0) {
|
|
updateType();
|
|
updateAlignment();
|
|
}
|
|
if (versionCompare(version, '2.1.5') < 0) {
|
|
updateNPCAC();
|
|
}
|
|
if (versionCompare(version, '2.1.10') < 0) {
|
|
updateSavingThrows();
|
|
}
|
|
if (versionCompare(version, '2.1.13') < 0) {
|
|
weighEquipment();
|
|
}
|
|
if (versionCompare(version, '2.1.15') < 0) {
|
|
displayTextForTraits();
|
|
}
|
|
if (versionCompare(version, '2.2.1') < 0) {
|
|
updateAbilityModifiers();
|
|
}
|
|
if (versionCompare(version, '2.2.2') < 0) {
|
|
resourcesToTraits();
|
|
}
|
|
if (versionCompare(version, '2.2.4') < 0) {
|
|
updateInitiative();
|
|
}
|
|
if (versionCompare(version, '2.2.5') < 0) {
|
|
weighAmmo();
|
|
}
|
|
if (versionCompare(version, '2.2.6') < 0) {
|
|
updateLevels();
|
|
extasToExtrasFix('repeating_attack');
|
|
extasToExtrasFix('repeating_action');
|
|
extasToExtrasFix('repeating_spell');
|
|
}
|
|
if (versionCompare(version, '2.3.3') < 0) {
|
|
updateAttachers();
|
|
}
|
|
if (versionCompare(version, '2.4.2') < 0) {
|
|
updateAbilityModifiers();
|
|
updateActionChatMacro('trait');
|
|
updateActionChatMacro('action');
|
|
updateActionChatMacro('reaction');
|
|
updateActionChatMacro('legendaryaction');
|
|
}
|
|
if (versionCompare(version, '2.4.3') < 0) {
|
|
setClassFeatures();
|
|
}
|
|
if (versionCompare(version, '2.4.7') < 0) {
|
|
classFeaturesToTraits();
|
|
updateAction('trait');
|
|
}
|
|
if (versionCompare(version, '2.4.8') < 0) {
|
|
fixRollTwo();
|
|
}
|
|
if (versionCompare(version, '2.4.12') < 0) {
|
|
armorPlusDexRemoval();
|
|
updateArmor();
|
|
}
|
|
if (versionCompare(version, '2.6.3') < 0) {
|
|
updateSpell();
|
|
}
|
|
if (versionCompare(version, '3.1.0') < 0) {
|
|
updateAttackChatMacro();
|
|
}
|
|
if (versionCompare(version, '3.1.1') < 0) {
|
|
updateActionChatMacro('lairaction');
|
|
updateActionChatMacro('regionaleffect');
|
|
updateAbilityChecksMacro();
|
|
}
|
|
if (versionCompare(version, '3.2.1') < 0) {
|
|
updateCritDamage();
|
|
}
|
|
if (versionCompare(version, '3.2.3') < 0) {
|
|
updateDamageResistancesVar();
|
|
}
|
|
if (versionCompare(version, '3.5.0') < 0) {
|
|
generateHigherLevelQueries();
|
|
updateSpellChatMacro();
|
|
}
|
|
if (versionCompare(version, '3.5.1') < 0) {
|
|
updateSpellSlots();
|
|
}
|
|
if (versionCompare(version, '3.6.1') < 0) {
|
|
updateNPCHD();
|
|
switchToNPC();
|
|
}
|
|
if (versionCompare(version, '4.0.3') < 0) {
|
|
updateSpellChatMacroShow();
|
|
}
|
|
if (versionCompare(version, '4.1.4') < 0) {
|
|
updateDefaultAbility();
|
|
updateArmorAbility();
|
|
updateActionComponents();
|
|
updateSkillAbility();
|
|
}
|
|
if (versionCompare(version, '4.1.5') < 0) {
|
|
updateSkill();
|
|
}
|
|
if (versionCompare(version, '4.2.0') < 0) {
|
|
updateD20Mod();
|
|
updateShapedD20();
|
|
}
|
|
if (versionCompare(version, '4.2.1') < 0) {
|
|
updateActionComponentsToRemoveExtraFields();
|
|
updateAbilityChecksMacro();
|
|
removeToggleVar();
|
|
}
|
|
if (versionCompare(version, '4.2.2') < 0) {
|
|
updateActions();
|
|
}
|
|
if (versionCompare(version, '4.2.3') < 0) {
|
|
updateArmor();
|
|
}
|
|
if (versionCompare(version, '4.2.4') < 0) {
|
|
updateAttack();
|
|
}
|
|
}
|
|
|
|
if (isUndefined(version) || !version || version !== currentVersion) {
|
|
finalSetAttrs.version = currentVersion;
|
|
}
|
|
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
});
|
|
};
|
|
|
|
on('sheet:opened', () => {
|
|
sheetOpened();
|
|
});
|
|
|
|
|
|
const importData = () => {
|
|
getAttrs(['import_data', 'version'], v => {
|
|
if (v.import_data) {
|
|
const finalSetAttrs = {};
|
|
const importObject = JSON.parse(v.import_data);
|
|
|
|
if (importObject.npc) {
|
|
if (!v.version) {
|
|
sheetOpened();
|
|
}
|
|
for (const prop in importObject.npc) {
|
|
if (importObject.npc.hasOwnProperty(prop)) {
|
|
finalSetAttrs[prop] = importObject.npc[prop];
|
|
}
|
|
}
|
|
}
|
|
if (importObject.spells) {
|
|
importObject.spells.forEach(spell => {
|
|
const repeatingString = `repeating_spell_${generateRowID()}_`;
|
|
for (const prop in spell) {
|
|
if (spell.hasOwnProperty(prop)) {
|
|
finalSetAttrs[`${repeatingString}${prop}`] = spell[prop];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
finalSetAttrs.import_data = '';
|
|
finalSetAttrs.import_data_present = 'off';
|
|
setFinalAttrs(v, finalSetAttrs);
|
|
}
|
|
});
|
|
};
|
|
|
|
const deleteImportData = () => {
|
|
getAttrs(['import_data', 'version'], v => {
|
|
const importObject = JSON.parse(v.import_data);
|
|
if (importObject.npc && !v.version) {
|
|
sheetOpened(); // NPC import will have wiped all the existing attributes
|
|
}
|
|
setFinalAttrs({}, {
|
|
import_data: '',
|
|
import_data_present: 'off',
|
|
});
|
|
});
|
|
};
|
|
on('change:accept_import', importData);
|
|
on('change:reject_import', deleteImportData);
|