mirror of
https://github.com/Nighthawk42/roll20-character-sheets.git
synced 2026-09-08 16:12:33 +00:00
2072 lines
76 KiB
JavaScript
2072 lines
76 KiB
JavaScript
'use strict';
|
|
import _ from 'underscore';
|
|
import TAS from './TheAaronSheet.js';
|
|
import {PFLog, PFConsole} from './PFLog';
|
|
import * as PFSheet from './PFSheet';
|
|
import * as PFHealth from './PFHealth';
|
|
import * as PFSpellOptions from './PFSpellOptions';
|
|
import * as PFSpells from './PFSpells';
|
|
|
|
export function parseNum(num) {
|
|
if (_.isUndefined(num) || num === '') return 0;
|
|
return parseInt(num) || 0;
|
|
}
|
|
|
|
export function bonusSpellSlots(abilMod, spellLevel) {
|
|
return Math.max(0, Math.floor((abilMod + 4 - spellLevel) / 4));
|
|
}
|
|
|
|
export function buildList(objArray, propName) {
|
|
return _.map(objArray, function (item) {
|
|
return item[propName];
|
|
}).join(', ');
|
|
}
|
|
|
|
export function getSizeMod(size) {
|
|
switch (size.toLowerCase()) {
|
|
case 'colossal':
|
|
return -8;
|
|
case 'gargantuan':
|
|
return -4;
|
|
case 'huge':
|
|
return -2;
|
|
case 'large':
|
|
return -1;
|
|
case 'small':
|
|
return 1;
|
|
case 'tiny':
|
|
return 2;
|
|
case 'diminutive':
|
|
return 4;
|
|
case 'fine':
|
|
return 8;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Make sure "stuff" is an array
|
|
export function arrayify(stuff) {
|
|
if (_.isUndefined(stuff)) return [];
|
|
if (Array.isArray(stuff)) return stuff;
|
|
return new Array(stuff);
|
|
}
|
|
|
|
export function importInit(attrs, initObj) {
|
|
attrs.init = parseNum(initObj._total);
|
|
attrs['init-misc'] = parseNum(initObj._total) - parseNum(initObj._attrtext);
|
|
attrs['init-ability'] = `${initObj._attrname.substr(0, 3).toUpperCase()}-mod`;
|
|
attrs['init-ability-mod'] = parseNum(initObj._attrtext);
|
|
attrs.init_notes = initObj.situationalmodifiers._text;
|
|
}
|
|
|
|
export function importAbilityScores(attrs, attributes) {
|
|
attributes.forEach(function (abScore) {
|
|
const abName = abScore._name.substr(0, 3).toUpperCase();
|
|
const base = parseNum(abScore.attrvalue._base);
|
|
const modifier = parseNum(abScore.attrvalue._modified) - base; // Modifier is the total difference between what HL is reporting as the character's base ability score and the final modified ability score
|
|
attrs[`${abName}-base`] = base;
|
|
attrs[abName] = parseNum(abScore.attrvalue._modified);
|
|
attrs[`${abName}-mod`] = parseNum(abScore.attrbonus._modified);
|
|
// If the modifier is positive, assume it's an enhancement bonus; otherwise, assume it's a penalty
|
|
if (modifier > 0) {
|
|
attrs[`${abName}-enhance`] = modifier;
|
|
attrs[abName] = parseNum(abScore.attrvalue._modified);
|
|
} else {
|
|
attrs[`${abName}-penalty`] = modifier;
|
|
attrs[abName] = base;
|
|
}
|
|
});
|
|
}
|
|
|
|
export function importSaves(attrs, saves) {
|
|
// Since the XML doesn't break this down by class, add it all to class 0
|
|
let i = 0;
|
|
let saveNotes = saves.allsaves.situationalmodifiers._text;
|
|
for (i = 0; i < saves.save.length; i++) {
|
|
const save = saves.save[i];
|
|
const abbr = save._abbr;
|
|
|
|
attrs[abbr] = parseNum(save._save);
|
|
attrs[`class-0-${abbr}`] = attrs[`total-${abbr}`] = parseNum(save._base);
|
|
attrs[`${abbr}-resist`] = parseNum(save._fromresist);
|
|
attrs[`${abbr}-misc`] = parseNum(save._save) - parseNum(save._base) - parseNum(save._fromresist) - parseNum(save._fromattr);
|
|
attrs[`${abbr}-ability-mod`] = parseNum(save._fromattr);
|
|
|
|
if (save.situationalmodifiers._text !== '' && saveNotes.indexOf(save.situationalmodifiers._text) === -1)
|
|
saveNotes = `${saveNotes}\n**${abbr}:** ${save.situationalmodifiers._text}`;
|
|
}
|
|
attrs['Save-notes'] = saveNotes.trim();
|
|
}
|
|
|
|
// Find an existing repeatable item with the same name, or generate new row ID
|
|
export function getOrMakeRowID(featIDList, name) {
|
|
const attrNames = Object.values(featIDList);
|
|
const rows = Object.keys(featIDList);
|
|
|
|
const attrMatch = _.find(attrNames, function (currentAttrName) {
|
|
let attrName = currentAttrName;
|
|
// Eliminate anything in parentheses, dice expressions, and "x#" (we use that to indicate we've taken a feat more than once) before comparing names
|
|
attrName = attrName.replace(/ x[0-9]+$/, '').trim();
|
|
|
|
if (attrName === name) {
|
|
const ID = rows[_.indexOf(attrNames, currentAttrName)];
|
|
if (!_.isUndefined(ID)) return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!_.isUndefined(attrMatch)) return rows[_.indexOf(attrNames, attrMatch)];
|
|
return generateRowID();
|
|
}
|
|
|
|
// Find an existing repeatable item with the same name, or generate new row ID; extra processing for items
|
|
export function getOrMakeItemRowID(featIDList, name) {
|
|
const attrNames = Object.values(featIDList);
|
|
const rows = Object.keys(featIDList);
|
|
|
|
const compareName = name
|
|
.replace(/\(.*\)/, '')
|
|
.replace(/\+\d+/, '')
|
|
.toLowerCase()
|
|
.replace('masterwork', '')
|
|
.trim();
|
|
const attrMatch = _.find(attrNames, function (currentAttrName) {
|
|
let attrName = currentAttrName;
|
|
// Eliminate anything in parentheses, dice expressions, and "x#" (we use that to indicate we've taken a feat more than once) before comparing names
|
|
attrName = attrName
|
|
.replace(/\(.*\)/, '')
|
|
.replace(/\+\d+/, '')
|
|
.toLowerCase()
|
|
.replace('masterwork', '')
|
|
.trim();
|
|
|
|
if (attrName === compareName) {
|
|
const ID = rows[_.indexOf(attrNames, currentAttrName)];
|
|
if (!_.isUndefined(ID)) return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (!_.isUndefined(attrMatch)) return rows[_.indexOf(attrNames, attrMatch)];
|
|
return generateRowID();
|
|
}
|
|
|
|
// Find an existing repeatable item with the same name and spellclass, or generate new row ID
|
|
export function getOrMakeSpellRowID(featIDList, name, spellclass) {
|
|
const attrMatch = _.find(featIDList, function (currentFeat) {
|
|
if (currentFeat.name === name && currentFeat.spellclass === spellclass) return true;
|
|
return false;
|
|
});
|
|
if (!_.isUndefined(attrMatch)) return attrMatch.rowID;
|
|
return generateRowID();
|
|
}
|
|
|
|
export function getOrMakeClassRowID(featIDList, name) {
|
|
const attrObjs = Object.values(featIDList);
|
|
const rows = Object.keys(featIDList);
|
|
|
|
const attrMatch = _.find(attrObjs, function (currentAttrObj) {
|
|
let attrName = currentAttrObj.name;
|
|
// Eliminate anything in parentheses, dice expressions, and "x#" (we use that to indicate we've taken a feat more than once) before comparing names
|
|
name = name
|
|
.replace(/\(.+\)/g, '')
|
|
.replace(/\d+d\d+(\+\d*)*/g, '')
|
|
.replace(/\+\d+/g, '')
|
|
.trim();
|
|
|
|
attrName = attrName
|
|
.replace(/\(.+\)/g, '')
|
|
.replace(/\d+d\d+(\+\d*)*/g, '')
|
|
.replace(/\+\d+/g, '')
|
|
.trim();
|
|
|
|
if (attrName === name) return true;
|
|
return false;
|
|
});
|
|
if (!_.isUndefined(attrMatch)) return attrMatch.rowID;
|
|
return generateRowID();
|
|
}
|
|
|
|
export function importFeats(attrs, feats, featIDList, resources) {
|
|
const repeatPrefix = 'repeating_ability';
|
|
const skipList = [];
|
|
const featNames = _.map(feats, function (feat) {
|
|
return feat._name;
|
|
});
|
|
_.each(feats, function (feat) {
|
|
// Early exit if we already dealt with another copy of this feat
|
|
if (_.contains(skipList, feat._name)) return;
|
|
|
|
// Count the number of times the feat is listed, so we can indicate that in the feat name
|
|
const taken = _.filter(featNames, function (featName) {
|
|
return featName === feat._name;
|
|
}).length;
|
|
|
|
const row = getOrMakeRowID(featIDList, feat._name);
|
|
if (!_.isUndefined(featIDList[row])) delete featIDList[row];
|
|
|
|
if (taken > 1) attrs[`${repeatPrefix}_${row}_name`] = `${feat._name} x${taken}`;
|
|
else attrs[`${repeatPrefix}_${row}_name`] = feat._name;
|
|
attrs[`${repeatPrefix}_${row}_description`] = feat.description;
|
|
attrs[`${repeatPrefix}_${row}_tabcat`] = attrs[`${repeatPrefix}_${row}_rule_category`] = 'feats';
|
|
skipList.push(feat._name);
|
|
if (_.contains(Object.keys(resources), feat._name)) attrs[`${repeatPrefix}_${row}_max-calculation`] = resources[feat._name]._max;
|
|
});
|
|
}
|
|
|
|
// Hero Lab stores armor and shields identically, so so assume anything with "shield" or "klar" in the name is a shield
|
|
export function nameIsShield(name) {
|
|
if (name.toLowerCase().indexOf('shield') !== -1 || name.toLowerCase().indexOf('buckler') !== -1 || name.toLowerCase().indexOf('klar') !== -1) return true;
|
|
return false;
|
|
}
|
|
|
|
export function importItems(items, resources, armorPenalties, armor, weapons) {
|
|
let repeatPrefix = 'repeating_item';
|
|
getSectionIDs(repeatPrefix, function (idarray) {
|
|
const itemNameAttrs = _.union(
|
|
_.map(idarray, function (id) {
|
|
return `${repeatPrefix}_${id}_name`;
|
|
}),
|
|
['shield3-acp', 'shield3-spell-fail'],
|
|
);
|
|
getAttrs(itemNameAttrs, function (names) {
|
|
// Pull out the shield attributes before we build the ID list
|
|
const shieldACP = parseNum(names['shield3-acp']);
|
|
const shieldASF = parseNum(names['shield3-spell-fail']);
|
|
if (!_.isUndefined(names['shield3-acp'])) delete names['shield3-acp'];
|
|
if (!_.isUndefined(names['shield3-spell-fail'])) delete names['shield3-spell-fail'];
|
|
|
|
const itemIDList = _.object(
|
|
_.map(names, function (name, attr) {
|
|
return [attr.substring(repeatPrefix.length + 1, attr.indexOf('_name')), name];
|
|
}),
|
|
);
|
|
const itemsList = [];
|
|
const attrs = {};
|
|
const armorNames = _.map(armor, function (obj) {
|
|
return obj._name;
|
|
});
|
|
const weaponNames = _.map(weapons, function (obj) {
|
|
return obj._name;
|
|
});
|
|
|
|
// List of words that indicate an item is masterwork
|
|
const masterworkWords = ['mithral', 'adamantine', 'angelskin', 'darkleaf', 'darkwood', 'dragonhide', 'eel', 'fire-forged', 'frost-forged', 'greenwood', 'paueliel'];
|
|
_.each(items, function (item) {
|
|
const row = getOrMakeItemRowID(itemIDList, item._name);
|
|
if (!_.isUndefined(itemIDList[row])) delete itemIDList[row];
|
|
itemsList.push(item._name);
|
|
|
|
repeatPrefix = `repeating_item_${row}`;
|
|
attrs[`${repeatPrefix}_name`] = item._name;
|
|
attrs[`${repeatPrefix}_item-weight`] = item.weight._value;
|
|
attrs[`${repeatPrefix}_value`] = parseFloat(item.cost._value) / parseInt(item._quantity);
|
|
attrs[`${repeatPrefix}_description`] = item.description;
|
|
|
|
if (_.contains(Object.keys(resources), item._name) && item._quantity === '1' && resources[item._name]._max !== '1') {
|
|
attrs[`${repeatPrefix}_qty`] = resources[item._name]._left;
|
|
attrs[`${repeatPrefix}_qty_max`] = resources[item._name]._max;
|
|
} else attrs[`${repeatPrefix}_qty`] = item._quantity;
|
|
|
|
if (!_.isUndefined(item.itempower))
|
|
_.each(arrayify(item.itempower), function (itemPower) {
|
|
itemsList.push(itemPower._name);
|
|
});
|
|
|
|
// check if this is a weapon
|
|
let weaponCompareName = item._name;
|
|
// If this is a shield (but not a klar), the attack name will be "Heavy/light shield bash"
|
|
if (item._name.toLowerCase().indexOf('shield') !== -1) {
|
|
let attackName;
|
|
if (item._name.toLowerCase().indexOf('heavy' !== -1)) attackName = 'heavy shield bash';
|
|
else attackName = 'light shield bash';
|
|
weaponCompareName =
|
|
_.find(weaponNames, function (name) {
|
|
if (name.toLowerCase().indexOf(attackName) !== -1) return true;
|
|
return false;
|
|
}) || item._name;
|
|
}
|
|
if (_.contains(weaponNames, weaponCompareName)) {
|
|
const weaponObj = weapons[_.indexOf(weaponNames, weaponCompareName)];
|
|
if (weaponObj._damage !== 'As Spell') {
|
|
attrs[`${repeatPrefix}_item-wpenhance`] = parseNum(weaponObj._name.match(/\+\d+/));
|
|
|
|
if (!_.isUndefined(weaponObj._typetext)) attrs[`${repeatPrefix}_item-dmg-type`] = weaponObj._typetext;
|
|
|
|
// Check to see if item name includes any words that indicate this is a masterwork item
|
|
if (weaponCompareName.toLowerCase().indexOf('masterwork') !== -1 || _.intersection(masterworkWords, item._name.toLowerCase().split(' ')).length > 0)
|
|
attrs[`${repeatPrefix}_item-masterwork`] = 1;
|
|
|
|
if (!_.isUndefined(weaponObj._damage)) {
|
|
const weaponDice = weaponObj._damage.match(/\d+d\d+/);
|
|
if (!_.isNull(weaponDice) && weaponDice.length > 0) {
|
|
attrs[`${repeatPrefix}_item-damage-dice-num`] = parseNum(weaponDice[0].split('d')[0]);
|
|
attrs[`${repeatPrefix}_item-damage-die`] = parseNum(weaponDice[0].split('d')[1]);
|
|
}
|
|
}
|
|
|
|
if (!_.isUndefined(weaponObj._crit)) {
|
|
const critArray = weaponObj._crit.split('/');
|
|
if (critArray.length > 1) attrs[`${repeatPrefix}_item-crit-target`] = parseNum(critArray[0].match(/\d+/)[0]);
|
|
else attrs[`${repeatPrefix}_item-crit-target`] = 20;
|
|
attrs[`${repeatPrefix}_item-crit-multiplier`] = parseNum(critArray[critArray.length - 1].replace(/\D/g, ''));
|
|
}
|
|
|
|
if (!_.isUndefined(weaponObj.rangedattack) && !_.isUndefined(weaponObj.rangedattack._rangeincvalue))
|
|
attrs[`${repeatPrefix}_item-range`] = parseNum(weaponObj.rangedattack._rangeincvalue);
|
|
}
|
|
}
|
|
|
|
// check if this is armor
|
|
// If this is a klar, the armor name will be different
|
|
let armorCompareName = item._name;
|
|
if (item._name.toLowerCase().indexOf('klar') !== -1) {
|
|
armorCompareName =
|
|
_.find(armorNames, function (name) {
|
|
if (name.toLowerCase().indexOf('klar') !== -1) return true;
|
|
return false;
|
|
}) || item._name;
|
|
}
|
|
if (_.contains(armorNames, armorCompareName)) {
|
|
const armorObj = armor[_.indexOf(armorNames, armorCompareName)];
|
|
|
|
// Item is a shield
|
|
if (nameIsShield(item._name)) {
|
|
var enhancement = parseNum(armorCompareName.match(/\+\d+/));
|
|
var ACbonus = parseNum(armorObj._ac) - enhancement;
|
|
attrs[`${repeatPrefix}_item-acbonus`] = ACbonus;
|
|
attrs[`${repeatPrefix}_item-acenhance`] = enhancement;
|
|
if (!_.isUndefined(armorObj._equipped) && armorObj._equipped === 'yes') {
|
|
attrs[`${repeatPrefix}_item-acp`] = shieldACP;
|
|
attrs[`${repeatPrefix}_item-spell-fail`] = shieldASF;
|
|
attrs.shield3 = item._name;
|
|
attrs['shield3-acbonus'] = ACbonus;
|
|
attrs['shield3-enhance'] = enhancement;
|
|
}
|
|
} else {
|
|
var enhancement = parseNum(item._name.match(/\+\d+/));
|
|
var ACbonus = parseNum(armorObj._ac) - enhancement;
|
|
attrs[`${repeatPrefix}_item-acbonus`] = ACbonus;
|
|
attrs[`${repeatPrefix}_item-acenhance`] = enhancement;
|
|
if (!_.isUndefined(armorObj._equipped) && armorObj._equipped === 'yes') {
|
|
attrs['armor3-acp'] = attrs[`${repeatPrefix}_item-acp`] = armorPenalties.ACP - shieldACP;
|
|
attrs['armor3-spell-fail'] = attrs[`${repeatPrefix}_item-spell-fail`] = armorPenalties.spellfail - shieldASF;
|
|
if (armorPenalties.maxDex === 99) attrs['armor3-max-dex'] = attrs[`${repeatPrefix}_item-max-dex`] = '';
|
|
else attrs['armor3-max-dex'] = attrs[`${repeatPrefix}_item-max-dex`] = armorPenalties.maxDex;
|
|
attrs.armor3 = item._name;
|
|
attrs['armor3-acbonus'] = ACbonus;
|
|
attrs['armor3-enhance'] = enhancement;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
setAttrs(attrs, {silent: true});
|
|
});
|
|
});
|
|
}
|
|
|
|
export function importTraits(attrs, traits, traitIDList, resources) {
|
|
const repeatPrefix = 'repeating_ability';
|
|
traits.forEach(function (trait) {
|
|
const row = getOrMakeRowID(traitIDList, trait._name);
|
|
if (!_.isUndefined(traitIDList[row])) delete traitIDList[row];
|
|
attrs[`${repeatPrefix}_${row}_name`] = trait._name;
|
|
attrs[`${repeatPrefix}_${row}_description`] = trait.description;
|
|
attrs[`${repeatPrefix}_${row}_tabcat`] = attrs[`${repeatPrefix}_${row}_rule_category`] = 'traits';
|
|
if (_.contains(Object.keys(resources), trait._name)) attrs[`${repeatPrefix}_${row}_max-calculation`] = resources[trait._name]._max;
|
|
});
|
|
}
|
|
|
|
export function importSLAs(attrs, SLAs, SLAsIDList, resources) {
|
|
const repeatPrefix = 'repeating_ability';
|
|
SLAs.forEach(function (SLA) {
|
|
const row = getOrMakeRowID(SLAsIDList, SLA._name);
|
|
if (!_.isUndefined(SLAsIDList[row])) delete SLAsIDList[row];
|
|
attrs[`${repeatPrefix}_${row}_name`] = SLA._name;
|
|
attrs[`${repeatPrefix}_${row}_description`] = SLA.description;
|
|
attrs[`${repeatPrefix}_${row}_tabcat`] = attrs[`${repeatPrefix}_${row}_rule_category`] = 'spell-like-abilities';
|
|
attrs[`${repeatPrefix}_${row}_tabcat2`] = attrs[`${repeatPrefix}_${row}_ability_type`] = 'Sp';
|
|
if (_.contains(Object.keys(resources), SLA._name)) attrs[`${repeatPrefix}_${row}_max-calculation`] = resources[SLA._name]._max;
|
|
});
|
|
}
|
|
|
|
export function importFeatures(attrs, featureList, specials, archetypes, resources) {
|
|
const specNameList = _.map(specials, function (special) {
|
|
return special._name;
|
|
});
|
|
const skipList = [];
|
|
_.each(specials, function (special) {
|
|
let name = special._name;
|
|
let repeatPrefix = 'repeating_ability';
|
|
let row;
|
|
let classSource = -1;
|
|
const cleanName = name
|
|
.replace(/ x[0-9]+$/, '')
|
|
.replace(/\(([^\)]*)\)/g, '')
|
|
.trim();
|
|
if (_.contains(skipList, cleanName)) return;
|
|
const multiList = _.filter(specNameList, function (spec) {
|
|
return spec.replace(/\(([^\)]*)\)/g, '').trim() === cleanName;
|
|
});
|
|
if (multiList.length > 1) {
|
|
skipList.push(cleanName);
|
|
const parenList = _.map(multiList, function (item) {
|
|
return item
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
});
|
|
name = name.replace(/\(([^\)]*)\)/, `(${_.uniq(parenList).join(', ')})`);
|
|
}
|
|
row = getOrMakeClassRowID(featureList, name);
|
|
repeatPrefix = `repeating_ability_${row}`;
|
|
if (!_.isUndefined(featureList[row])) delete featureList[row];
|
|
// If we created a new row for this, set rule category
|
|
else {
|
|
// Import if it has a "specsource", assume it's a class feature
|
|
if (special.specsource) attrs[`${repeatPrefix}_tabcat`] = attrs[`${repeatPrefix}_rule_category`] = 'class-features';
|
|
else attrs[`${repeatPrefix}_tabcat`] = attrs[`${repeatPrefix}_rule_category`] = 'racial-traits';
|
|
}
|
|
classSource = getClassSource(arrayify(special.specsource), archetypes);
|
|
attrs[`${repeatPrefix}_name`] = name;
|
|
attrs[`${repeatPrefix}_description`] = special.description;
|
|
|
|
if (classSource !== -1) {
|
|
attrs[`${repeatPrefix}_CL-basis`] = `@{class-${classSource}-level}`;
|
|
attrs[`${repeatPrefix}_class-name`] = Object.keys(archetypes)[classSource];
|
|
}
|
|
|
|
if (_.contains(Object.keys(resources), special._name)) attrs[`${repeatPrefix}_max-calculation`] = resources[special._name]._max;
|
|
|
|
if (!_.isUndefined(special._type)) attrs[`${repeatPrefix}_tabcat2`] = attrs[`${repeatPrefix}_ability_type`] = special._type.substr(0, 2);
|
|
});
|
|
}
|
|
|
|
export function importClasses(attrs, classes, hitdice) {
|
|
const classList = new Object();
|
|
let diceList = _.without(
|
|
_.map(
|
|
hitdice
|
|
.split(';')
|
|
[hitdice.split(';').length - 1].trim()
|
|
.split(/[\+\-]/),
|
|
function (hitdie) {
|
|
const splitDie = hitdie.split('d');
|
|
if (splitDie.length < 2) return null;
|
|
return {dice: splitDie[0], die: splitDie[1]};
|
|
},
|
|
),
|
|
null,
|
|
);
|
|
|
|
let i = 0;
|
|
let classObj;
|
|
while (i < classes.length) {
|
|
classObj = classes[i];
|
|
|
|
// We can only handle 5 classes
|
|
if (i >= 5) return;
|
|
|
|
if (i > 0) attrs[`class${i}_show`] = 1;
|
|
|
|
classList[
|
|
classObj._name
|
|
.replace(/\(([^\)]*)\)/g, '')
|
|
.replace('(', '')
|
|
.replace(')', '')
|
|
.trim()
|
|
] = classObj;
|
|
attrs[`class-${i}-name`] = classObj._name;
|
|
attrs[`class-${i}-level`] = classObj._level;
|
|
|
|
const hitDie = _.find(diceList, function (dieObj) {
|
|
return dieObj.dice === classObj._level;
|
|
});
|
|
if (!_.isUndefined(hitDie)) {
|
|
attrs[`class-${i}-hd`] = hitDie.die;
|
|
diceList = _.without(diceList, hitDie);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
// Assign any remaining hit dice to racial
|
|
if (diceList.length > 0) attrs['npc-hd'] = diceList[0].die;
|
|
|
|
return classList;
|
|
}
|
|
|
|
// Import spellclasses; presence in spellclasses node means it's a spellcaster, but some of the data is in the classes node
|
|
export function importSpellClasses(attrs, spellclasses, classes, abScores) {
|
|
const spellClassesList = new Object();
|
|
|
|
let i;
|
|
let j;
|
|
let abMod = 0;
|
|
let currentAbMod;
|
|
let spellslots;
|
|
let spelllevel;
|
|
let casterlevel;
|
|
let concmod;
|
|
let spellpenmod;
|
|
let spellClassIndex = 0;
|
|
for (i = 0; i < spellclasses.length; i++) {
|
|
const spellClass = spellclasses[i];
|
|
// Only 3 spellclasses on character sheet, so if they somehow have more...
|
|
if (spellClassIndex >= 3) return spellClassesList;
|
|
|
|
var spellClassName = spellClass._name
|
|
.replace(/\(([^\)]*)\)/g, '')
|
|
.replace('(', '')
|
|
.replace(')', '')
|
|
.trim();
|
|
const classIndex = _.indexOf(
|
|
Object.keys(classes),
|
|
_.find(Object.keys(classes), function (className) {
|
|
if (className.toLowerCase().indexOf(spellClassName.toLowerCase()) !== -1) return true;
|
|
return false;
|
|
}),
|
|
);
|
|
|
|
if (classIndex !== -1) {
|
|
casterlevel = parseNum(classes[spellClassName]._casterlevel);
|
|
attrs[`spellclass-${spellClassIndex}`] = classIndex;
|
|
attrs[`spellclass-${spellClassIndex}-level-misc`] = casterlevel - parseNum(classes[spellClassName]._level);
|
|
attrs[`spellclass-${spellClassIndex}-level`] = parseNum(classes[spellClassName]._level);
|
|
attrs[`spellclass-${spellClassIndex}-level-total`] = casterlevel;
|
|
attrs[`spellclass-${spellClassIndex}-name`] = classes[spellClassName]._name.replace(/\(.*\)/g, '').trim();
|
|
attrs[`spellclass-${spellClassIndex}-exists`] = 1;
|
|
|
|
attrs[`spellclass-${spellClassIndex}-close`] = 25 + 5 * Math.floor(casterlevel / 2);
|
|
attrs[`spellclass-${spellClassIndex}-medium`] = 100 + 10 * casterlevel;
|
|
attrs[`spellclass-${spellClassIndex}-long`] = 400 + 40 * casterlevel;
|
|
|
|
if (!_.isUndefined(classes[spellClassName].arcanespellfailure)) attrs['armor3-spell-fail'] = parseNum(classes[spellClassName].arcanespellfailure._value);
|
|
|
|
// Make a guess at which ability modifier is used for this class
|
|
if (!_.isUndefined(classes[spellClassName]._basespelldc)) {
|
|
abMod = parseNum(classes[spellClassName]._basespelldc) - 10;
|
|
|
|
// Start at the fourth ability score (Intelligence), so we skip the physical abilities
|
|
for (j = 3; j < abScores.length; j++) {
|
|
if (parseNum(abScores[j].attrbonus._modified) === abMod) {
|
|
attrs[`Concentration-${spellClassIndex}-ability`] = `${abScores[j]._name.substr(0, 3).toUpperCase()}-mod`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (abMod !== 0) {
|
|
// Calculate misc mods to concentration
|
|
if (!_.isUndefined(classes[spellClassName]._concentrationcheck)) {
|
|
concmod = parseNum(classes[spellClassName]._concentrationcheck) - casterlevel - abMod;
|
|
attrs[`Concentration-${spellClassIndex}-misc`] = concmod;
|
|
attrs[`Concentration-${spellClassIndex}`] = parseNum(classes[spellClassName]._concentrationcheck);
|
|
attrs[`Concentration-${spellClassIndex}-mod`] = abMod;
|
|
}
|
|
|
|
// Calculate misc mods to spell penetration
|
|
if (!_.isUndefined(classes[spellClassName].overcomespellresistance)) {
|
|
spellpenmod = parseNum(classes[spellClassName].overcomespellresistance) - casterlevel;
|
|
attrs[`spellclass-${spellClassIndex}-SP_misc`] = spellpenmod;
|
|
}
|
|
|
|
// Populate spells / day; Hero Lab includes bonus slots, so remove those
|
|
if (!_.isUndefined(spellclasses[i].spelllevel)) {
|
|
// Initialize spells/day
|
|
for (j = 0; j < 10; j++) attrs[`spellclass-${spellClassIndex}-level-${j}-spells-per-day_max`] = 0;
|
|
spellclasses[i].spelllevel = arrayify(spellclasses[i].spelllevel);
|
|
for (j = 0; j < spellclasses[i].spelllevel.length; j++) {
|
|
spellslots = parseNum(spellclasses[i].spelllevel[j]._maxcasts);
|
|
spelllevel = parseNum(spellclasses[i].spelllevel[j]._level);
|
|
attrs[`spellclass-${spellClassIndex}-level-${spelllevel}-spells-per-day_max`] = spellslots;
|
|
if (spelllevel > 0) spellslots -= bonusSpellSlots(abMod, spelllevel);
|
|
if (spellslots < 0) spellslots = '';
|
|
attrs[`spellclass-${spellClassIndex}-level-${spelllevel}-class`] = spellslots;
|
|
}
|
|
}
|
|
// Set bonus spell slot table entries
|
|
attrs[`spellclass-${spellClassIndex}-level-1-bonus`] = bonusSpellSlots(abMod, 1);
|
|
attrs[`spellclass-${spellClassIndex}-level-2-bonus`] = bonusSpellSlots(abMod, 2);
|
|
attrs[`spellclass-${spellClassIndex}-level-3-bonus`] = bonusSpellSlots(abMod, 3);
|
|
attrs[`spellclass-${spellClassIndex}-level-4-bonus`] = bonusSpellSlots(abMod, 4);
|
|
attrs[`spellclass-${spellClassIndex}-level-5-bonus`] = bonusSpellSlots(abMod, 5);
|
|
attrs[`spellclass-${spellClassIndex}-level-6-bonus`] = bonusSpellSlots(abMod, 6);
|
|
attrs[`spellclass-${spellClassIndex}-level-7-bonus`] = bonusSpellSlots(abMod, 7);
|
|
attrs[`spellclass-${spellClassIndex}-level-8-bonus`] = bonusSpellSlots(abMod, 8);
|
|
attrs[`spellclass-${spellClassIndex}-level-9-bonus`] = bonusSpellSlots(abMod, 9);
|
|
}
|
|
spellClassesList[spellClassName] = classes[Object.keys(classes)[classIndex]];
|
|
spellClassIndex++;
|
|
}
|
|
}
|
|
|
|
if (spellClassIndex > 1) attrs.spellclasses_multiclassed = 1;
|
|
|
|
return spellClassesList;
|
|
}
|
|
|
|
export function importSpells(spells, spellclasses) {
|
|
const wizNames = ['Abjurer', 'Conjurer', 'Diviner', 'Enchanter', 'Evoker', 'Illusionist', 'Necromancer', 'Transmuter'];
|
|
let repeatPrefix = 'repeating_spells';
|
|
getSectionIDs(repeatPrefix, function (idarray) {
|
|
const spellNameAttrs = _.union(
|
|
_.map(idarray, function (id) {
|
|
return `${repeatPrefix}_${id}_name`;
|
|
}),
|
|
_.map(idarray, function (id) {
|
|
return `${repeatPrefix}_${id}_spellclass_number`;
|
|
}),
|
|
);
|
|
getAttrs(spellNameAttrs, function (spellAttrs) {
|
|
const spellObjList = {};
|
|
const spellKeys = Object.keys(spellAttrs);
|
|
_.each(spellKeys, function (spellKey) {
|
|
let rowID;
|
|
if (spellKey.indexOf('_name') !== -1) {
|
|
rowID = spellKey.substring(repeatPrefix.length + 1, spellKey.indexOf('_name'));
|
|
if (_.isUndefined(spellObjList[rowID])) spellObjList[rowID] = {rowID};
|
|
spellObjList[rowID].name = spellAttrs[spellKey];
|
|
}
|
|
if (spellKey.indexOf('_spellclass_number') !== -1) {
|
|
rowID = spellKey.substring(repeatPrefix.length + 1, spellKey.indexOf('_spellclass_number'));
|
|
if (_.isUndefined(spellObjList[rowID])) spellObjList[rowID] = {rowID};
|
|
spellObjList[rowID].spellclass = spellAttrs[spellKey];
|
|
}
|
|
});
|
|
|
|
const spellClassesKeys = Object.keys(spellclasses);
|
|
const attrs = {};
|
|
_.each(spells, function (spell) {
|
|
let rowID;
|
|
let spellClass;
|
|
let spellClassName;
|
|
let spellName;
|
|
let school;
|
|
let level;
|
|
|
|
// Search for a repeating spell with the same name and spellclass; if not found, make new row
|
|
level = parseNum(spell._level);
|
|
repeatPrefix = 'repeating_spells_';
|
|
spellName = spell._name.replace(/\(x\d+\)/, '').trim();
|
|
|
|
// If the spell doesn't specify a class, assume its our first spellcaster
|
|
if (spell._class === '') {
|
|
spellClass = 0;
|
|
spellClassName = spellClassesKeys[0];
|
|
} else {
|
|
spellClassName = spell._class;
|
|
spellClass = _.indexOf(spellClassesKeys, spellClassName);
|
|
|
|
if (spellClass === -1) {
|
|
spellClassName = _.intersection(spellClassesKeys, wizNames)[0];
|
|
spellClass = _.indexOf(spellClassesKeys, spellClassName);
|
|
}
|
|
}
|
|
|
|
rowID = getOrMakeSpellRowID(spellObjList, spellName, spellClass);
|
|
if (_.isUndefined(rowID)) {
|
|
console.log('Undefined spell row ID!');
|
|
console.log(spell);
|
|
}
|
|
// Update prefix with ID
|
|
repeatPrefix += rowID;
|
|
|
|
attrs[`${repeatPrefix}_name`] = spellName;
|
|
attrs[`${repeatPrefix}_spell_level_r`] = attrs[`${repeatPrefix}_spell_level`] = level;
|
|
attrs[`${repeatPrefix}_spellclass_number`] = spellClass;
|
|
attrs[`${repeatPrefix}_spellclass`] = spellClassName;
|
|
attrs[`${repeatPrefix}_components`] = spell._componenttext
|
|
.replace('Divine Focus', 'DF')
|
|
.replace('Focus', 'F')
|
|
.replace('Material', 'M')
|
|
.replace('Verbal', 'V')
|
|
.replace('Somatic', 'S')
|
|
.replace(' or ', '/');
|
|
attrs[`${repeatPrefix}_duration`] = spell._duration;
|
|
attrs[`${repeatPrefix}_save`] = spell._save.replace(/DC \d+/, '').trim();
|
|
attrs[`${repeatPrefix}_savedc`] = parseNum(spell._dc);
|
|
attrs[`${repeatPrefix}_cast-time`] = spell._casttime;
|
|
attrs[`${repeatPrefix}_DC_misc`] = parseNum(spell._dc) - parseNum(spellclasses[spellClassName !== '' ? spellClassName : Object.keys(spellclasses)[0]]._basespelldc) - level;
|
|
attrs[`${repeatPrefix}_CL_misc`] =
|
|
parseNum(spell._casterlevel) - parseNum(spellclasses[spellClassName !== '' ? spellClassName : Object.keys(spellclasses)[0]]._casterlevel);
|
|
attrs[`${repeatPrefix}_casterlevel`] = spell._casterlevel;
|
|
|
|
if (spell._resist.toLowerCase().indexOf('yes') !== -1) {
|
|
if (spell._resist.toLowerCase().indexOf('harmless') !== -1) attrs[`${repeatPrefix}_sr`] = 'Yes (Harmless)';
|
|
else if (spell._resist.toLowerCase().indexOf('object') !== -1) attrs[`${repeatPrefix}_sr`] = 'Yes (Object)';
|
|
else attrs[`${repeatPrefix}_sr`] = 'Yes';
|
|
} else if (spell._resist.toLowerCase().indexOf('no') !== -1) attrs[`${repeatPrefix}_sr`] = 'No';
|
|
else attrs[`${repeatPrefix}_sr`] = '';
|
|
|
|
switch (spell._range.toLowerCase()) {
|
|
case 'close (25 + 5 ft./2 levels)':
|
|
attrs[`${repeatPrefix}_range_pick`] = 'close';
|
|
break;
|
|
case 'medium (100 + 10 ft./level)':
|
|
attrs[`${repeatPrefix}_range_pick`] = 'medium';
|
|
break;
|
|
case 'long (400 + 40 ft./level)':
|
|
attrs[`${repeatPrefix}_range_pick`] = 'long';
|
|
break;
|
|
case 'touch':
|
|
case 'personal':
|
|
attrs[`${repeatPrefix}_range_pick`] = spell._range.toLowerCase();
|
|
break;
|
|
default:
|
|
attrs[`${repeatPrefix}_range_pick`] = 'number';
|
|
attrs[`${repeatPrefix}_range`] = spell._range;
|
|
}
|
|
|
|
if (spell._area !== '') attrs[`${repeatPrefix}_targets`] = spell._area;
|
|
else if (spell._effect !== '') attrs[`${repeatPrefix}_targets`] = spell._effect;
|
|
else attrs[`${repeatPrefix}_targets`] = spell._target;
|
|
|
|
school = spell._schooltext;
|
|
if (spell._subschooltext !== '') school = `${school} (${spell._subschooltext})`;
|
|
if (spell._descriptortext !== '') school = `${school} [${spell._descriptortext}]`;
|
|
attrs[`${repeatPrefix}_school`] = school;
|
|
|
|
attrs[`${repeatPrefix}_description`] = spell.description;
|
|
});
|
|
setAttrs(attrs, {silent: true}, function () {
|
|
PFSpellOptions.resetOptions();
|
|
PFSpells.resetCommandMacro();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
export function calcHitDice(hitdice) {
|
|
const dice = hitdice.match(/\d+d\d/g);
|
|
let numDice = 0;
|
|
let i = 0;
|
|
while (i < dice.length) {
|
|
numDice += parseInt(dice[i].split('d')[0]);
|
|
i++;
|
|
}
|
|
return numDice;
|
|
}
|
|
|
|
// Builds an object collection of archetypes, with the appropriate classes as the keys, in the order they're entered in the character sheet; use this to determine class specials come from
|
|
export function buildArchetypeArray(classes) {
|
|
const archetypes = new Object();
|
|
|
|
_.each(classes, function (classObj, className) {
|
|
if (classObj._name.indexOf('(') === -1) {
|
|
archetypes[className] = [];
|
|
return;
|
|
}
|
|
const archeString = classObj._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
let archeList = archeString.split(',');
|
|
archeList = _.map(archeList, function (arche) {
|
|
return arche.trim();
|
|
});
|
|
archetypes[className] = archeList;
|
|
});
|
|
return archetypes;
|
|
}
|
|
|
|
// Returns the array number of the class that grants a feature; returns -1 if we can't find the class
|
|
export function getClassSource(sources, archetypes) {
|
|
// If there's no listed source, it isn't from a class
|
|
if (!sources.length) return -1;
|
|
|
|
// Grab an array of class names from the archetypes object
|
|
const classes = Object.keys(archetypes);
|
|
|
|
// Check if source is a class, first
|
|
const intersect = _.intersection(sources, classes);
|
|
if (intersect.length) return classes.indexOf(intersect[0]);
|
|
|
|
// If not a class, check for an archetype as a source, and return the associated class
|
|
const className = _.find(classes, function (item) {
|
|
return _.intersection(archetypes[item], sources).length;
|
|
});
|
|
if (className) return classes.indexOf(className);
|
|
|
|
return -1;
|
|
}
|
|
|
|
export function importSkills(attrs, skills, size, ACP) {
|
|
// Ripped from the PF character sheet JS
|
|
let skillSize;
|
|
switch (Math.abs(size)) {
|
|
case 0:
|
|
skillSize = 0;
|
|
break;
|
|
case 1:
|
|
skillSize = 2;
|
|
break;
|
|
case 2:
|
|
skillSize = 4;
|
|
break;
|
|
case 4:
|
|
skillSize = 6;
|
|
break;
|
|
case 8:
|
|
skillSize = 8;
|
|
break;
|
|
case 16:
|
|
skillSize = 10;
|
|
break;
|
|
default:
|
|
skillSize = 0;
|
|
}
|
|
if (size < 0) {
|
|
skillSize *= -1;
|
|
}
|
|
|
|
// Clear out all existing skills data
|
|
_.extend(attrs, {
|
|
'acrobatics-ability': '',
|
|
'acrobatics-cs': '',
|
|
'acrobatics-ranks': '',
|
|
'acrobatics-class': '',
|
|
'acrobatics-ability-mod': '',
|
|
'acrobatics-racial': '',
|
|
'acrobatics-feat': '',
|
|
'acrobatics-item': '',
|
|
'acrobatics-size': '',
|
|
'acrobatics-acp': '',
|
|
'acrobatics-misc': '',
|
|
'acrobatics-reqtrain': '',
|
|
'artistry-ability': '',
|
|
'artistry-cs': '',
|
|
'artistry-ranks': '',
|
|
'artistry-class': '',
|
|
'artistry-ability-mod': '',
|
|
'artistry-racial': '',
|
|
'artistry-feat': '',
|
|
'artistry-item': '',
|
|
'artistry-size': '',
|
|
'artistry-acp': '',
|
|
'artistry-misc': '',
|
|
'artistry-reqtrain': '',
|
|
'artistry2-ability': '',
|
|
'artistry2-cs': '',
|
|
'artistry2-ranks': '',
|
|
'artistry2-class': '',
|
|
'artistry2-ability-mod': '',
|
|
'artistry2-racial': '',
|
|
'artistry2-feat': '',
|
|
'artistry2-item': '',
|
|
'artistry2-size': '',
|
|
'artistry2-acp': '',
|
|
'artistry2-misc': '',
|
|
'artistry2-reqtrain': '',
|
|
'artistry3-ability': '',
|
|
'artistry3-cs': '',
|
|
'artistry3-ranks': '',
|
|
'artistry3-class': '',
|
|
'artistry3-ability-mod': '',
|
|
'artistry3-racial': '',
|
|
'artistry3-feat': '',
|
|
'artistry3-item': '',
|
|
'artistry3-size': '',
|
|
'artistry3-acp': '',
|
|
'artistry3-misc': '',
|
|
'artistry3-reqtrain': '',
|
|
'appraise-ability': '',
|
|
'appraise-cs': '',
|
|
'appraise-ranks': '',
|
|
'appraise-class': '',
|
|
'appraise-ability-mod': '',
|
|
'appraise-racial': '',
|
|
'appraise-feat': '',
|
|
'appraise-item': '',
|
|
'appraise-size': '',
|
|
'appraise-acp': '',
|
|
'appraise-misc': '',
|
|
'appraise-reqtrain': '',
|
|
'bluff-ability': '',
|
|
'bluff-cs': '',
|
|
'bluff-ranks': '',
|
|
'bluff-class': '',
|
|
'bluff-ability-mod': '',
|
|
'bluff-racial': '',
|
|
'bluff-feat': '',
|
|
'bluff-item': '',
|
|
'bluff-size': '',
|
|
'bluff-acp': '',
|
|
'bluff-misc': '',
|
|
'bluff-reqtrain': '',
|
|
'climb-ability': '',
|
|
'climb-cs': '',
|
|
'climb-ranks': '',
|
|
'climb-class': '',
|
|
'climb-ability-mod': '',
|
|
'climb-racial': '',
|
|
'climb-feat': '',
|
|
'climb-item': '',
|
|
'climb-size': '',
|
|
'climb-acp': '',
|
|
'climb-misc': '',
|
|
'climb-reqtrain': '',
|
|
'craft-ability': '',
|
|
'craft-cs': '',
|
|
'craft-ranks': '',
|
|
'craft-class': '',
|
|
'craft-ability-mod': '',
|
|
'craft-racial': '',
|
|
'craft-feat': '',
|
|
'craft-item': '',
|
|
'craft-size': '',
|
|
'craft-acp': '',
|
|
'craft-misc': '',
|
|
'craft-reqtrain': '',
|
|
'craft2-ability': '',
|
|
'craft2-cs': '',
|
|
'craft2-ranks': '',
|
|
'craft2-class': '',
|
|
'craft2-ability-mod': '',
|
|
'craft2-racial': '',
|
|
'craft2-feat': '',
|
|
'craft2-item': '',
|
|
'craft2-size': '',
|
|
'craft2-acp': '',
|
|
'craft2-misc': '',
|
|
'craft2-reqtrain': '',
|
|
'craft3-ability': '',
|
|
'craft3-cs': '',
|
|
'craft3-ranks': '',
|
|
'craft3-class': '',
|
|
'craft3-ability-mod': '',
|
|
'craft3-racial': '',
|
|
'craft3-feat': '',
|
|
'craft3-item': '',
|
|
'craft3-size': '',
|
|
'craft3-acp': '',
|
|
'craft3-misc': '',
|
|
'craft3-reqtrain': '',
|
|
'diplomacy-ability': '',
|
|
'diplomacy-cs': '',
|
|
'diplomacy-ranks': '',
|
|
'diplomacy-class': '',
|
|
'diplomacy-ability-mod': '',
|
|
'diplomacy-racial': '',
|
|
'diplomacy-feat': '',
|
|
'diplomacy-item': '',
|
|
'diplomacy-size': '',
|
|
'diplomacy-acp': '',
|
|
'diplomacy-misc': '',
|
|
'diplomacy-reqtrain': '',
|
|
'disable-device-ability': '',
|
|
'disable-device-cs': '',
|
|
'disable-device-ranks': '',
|
|
'disable-device-class': '',
|
|
'disable-device-ability-mod': '',
|
|
'disable-device-racial': '',
|
|
'disable-device-feat': '',
|
|
'disable-device-item': '',
|
|
'disable-device-size': '',
|
|
'disable-device-acp': '',
|
|
'disable-device-misc': '',
|
|
'disable-device-reqtrain': '',
|
|
'disguise-ability': '',
|
|
'disguise-cs': '',
|
|
'disguise-ranks': '',
|
|
'disguise-class': '',
|
|
'disguise-ability-mod': '',
|
|
'disguise-racial': '',
|
|
'disguise-feat': '',
|
|
'disguise-item': '',
|
|
'disguise-size': '',
|
|
'disguise-acp': '',
|
|
'disguise-misc': '',
|
|
'disguise-reqtrain': '',
|
|
'escape-artist-ability': '',
|
|
'escape-artist-cs': '',
|
|
'escape-artist-ranks': '',
|
|
'escape-artist-class': '',
|
|
'escape-artist-ability-mod': '',
|
|
'escape-artist-racial': '',
|
|
'escape-artist-feat': '',
|
|
'escape-artist-item': '',
|
|
'escape-artist-size': '',
|
|
'escape-artist-acp': '',
|
|
'escape-artist-misc': '',
|
|
'escape-artist-reqtrain': '',
|
|
'fly-ability': '',
|
|
'fly-cs': '',
|
|
'fly-ranks': '',
|
|
'fly-class': '',
|
|
'fly-ability-mod': '',
|
|
'fly-racial': '',
|
|
'fly-feat': '',
|
|
'fly-item': '',
|
|
'fly-size': '',
|
|
'fly-acp': '',
|
|
'fly-misc': '',
|
|
'fly-reqtrain': '',
|
|
'handle-animal-ability': '',
|
|
'handle-animal-cs': '',
|
|
'handle-animal-ranks': '',
|
|
'handle-animal-class': '',
|
|
'handle-animal-ability-mod': '',
|
|
'handle-animal-racial': '',
|
|
'handle-animal-feat': '',
|
|
'handle-animal-item': '',
|
|
'handle-animal-size': '',
|
|
'handle-animal-acp': '',
|
|
'handle-animal-misc': '',
|
|
'handle-animal-reqtrain': '',
|
|
'heal-ability': '',
|
|
'heal-cs': '',
|
|
'heal-ranks': '',
|
|
'heal-class': '',
|
|
'heal-ability-mod': '',
|
|
'heal-racial': '',
|
|
'heal-feat': '',
|
|
'heal-item': '',
|
|
'heal-size': '',
|
|
'heal-acp': '',
|
|
'heal-misc': '',
|
|
'heal-reqtrain': '',
|
|
'intimidate-ability': '',
|
|
'intimidate-cs': '',
|
|
'intimidate-ranks': '',
|
|
'intimidate-class': '',
|
|
'intimidate-ability-mod': '',
|
|
'intimidate-racial': '',
|
|
'intimidate-feat': '',
|
|
'intimidate-item': '',
|
|
'intimidate-size': '',
|
|
'intimidate-acp': '',
|
|
'intimidate-misc': '',
|
|
'intimidate-reqtrain': '',
|
|
'linguistics-ability': '',
|
|
'linguistics-cs': '',
|
|
'linguistics-ranks': '',
|
|
'linguistics-class': '',
|
|
'linguistics-ability-mod': '',
|
|
'linguistics-racial': '',
|
|
'linguistics-feat': '',
|
|
'linguistics-item': '',
|
|
'linguistics-size': '',
|
|
'linguistics-acp': '',
|
|
'linguistics-misc': '',
|
|
'linguistics-reqtrain': '',
|
|
'lore-ability': '',
|
|
'lore-cs': '',
|
|
'lore-ranks': '',
|
|
'lore-class': '',
|
|
'lore-ability-mod': '',
|
|
'lore-racial': '',
|
|
'lore-feat': '',
|
|
'lore-item': '',
|
|
'lore-size': '',
|
|
'lore-acp': '',
|
|
'lore-misc': '',
|
|
'lore-reqtrain': '',
|
|
'lore2-ability': '',
|
|
'lore2-cs': '',
|
|
'lore2-ranks': '',
|
|
'lore2-class': '',
|
|
'lore2-ability-mod': '',
|
|
'lore2-racial': '',
|
|
'lore2-feat': '',
|
|
'lore2-item': '',
|
|
'lore2-size': '',
|
|
'lore2-acp': '',
|
|
'lore2-misc': '',
|
|
'lore2-reqtrain': '',
|
|
'lore3-ability': '',
|
|
'lore3-cs': '',
|
|
'lore3-ranks': '',
|
|
'lore3-class': '',
|
|
'lore3-ability-mod': '',
|
|
'lore3-racial': '',
|
|
'lore3-feat': '',
|
|
'lore3-item': '',
|
|
'lore3-size': '',
|
|
'lore3-acp': '',
|
|
'lore3-misc': '',
|
|
'lore3-reqtrain': '',
|
|
'knowledge-arcana-ability': '',
|
|
'knowledge-arcana-cs': '',
|
|
'knowledge-arcana-ranks': '',
|
|
'knowledge-arcana-class': '',
|
|
'knowledge-arcana-ability-mod': '',
|
|
'knowledge-arcana-racial': '',
|
|
'knowledge-arcana-feat': '',
|
|
'knowledge-arcana-item': '',
|
|
'knowledge-arcana-size': '',
|
|
'knowledge-arcana-acp': '',
|
|
'knowledge-arcana-misc': '',
|
|
'knowledge-arcana-reqtrain': '',
|
|
'knowledge-dungeoneering-ability': '',
|
|
'knowledge-dungeoneering-cs': '',
|
|
'knowledge-dungeoneering-ranks': '',
|
|
'knowledge-dungeoneering-class': '',
|
|
'knowledge-dungeoneering-ability-mod': '',
|
|
'knowledge-dungeoneering-racial': '',
|
|
'knowledge-dungeoneering-feat': '',
|
|
'knowledge-dungeoneering-item': '',
|
|
'knowledge-dungeoneering-size': '',
|
|
'knowledge-dungeoneering-acp': '',
|
|
'knowledge-dungeoneering-misc': '',
|
|
'knowledge-dungeoneering-reqtrain': '',
|
|
'knowledge-engineering-ability': '',
|
|
'knowledge-engineering-cs': '',
|
|
'knowledge-engineering-ranks': '',
|
|
'knowledge-engineering-class': '',
|
|
'knowledge-engineering-ability-mod': '',
|
|
'knowledge-engineering-racial': '',
|
|
'knowledge-engineering-feat': '',
|
|
'knowledge-engineering-item': '',
|
|
'knowledge-engineering-size': '',
|
|
'knowledge-engineering-acp': '',
|
|
'knowledge-engineering-misc': '',
|
|
'knowledge-engineering-reqtrain': '',
|
|
'knowledge-geography-ability': '',
|
|
'knowledge-geography-cs': '',
|
|
'knowledge-geography-ranks': '',
|
|
'knowledge-geography-class': '',
|
|
'knowledge-geography-ability-mod': '',
|
|
'knowledge-geography-racial': '',
|
|
'knowledge-geography-feat': '',
|
|
'knowledge-geography-item': '',
|
|
'knowledge-geography-size': '',
|
|
'knowledge-geography-acp': '',
|
|
'knowledge-geography-misc': '',
|
|
'knowledge-geography-reqtrain': '',
|
|
'knowledge-history-ability': '',
|
|
'knowledge-history-cs': '',
|
|
'knowledge-history-ranks': '',
|
|
'knowledge-history-class': '',
|
|
'knowledge-history-ability-mod': '',
|
|
'knowledge-history-racial': '',
|
|
'knowledge-history-feat': '',
|
|
'knowledge-history-item': '',
|
|
'knowledge-history-size': '',
|
|
'knowledge-history-acp': '',
|
|
'knowledge-history-misc': '',
|
|
'knowledge-history-reqtrain': '',
|
|
'knowledge-local-ability': '',
|
|
'knowledge-local-cs': '',
|
|
'knowledge-local-ranks': '',
|
|
'knowledge-local-class': '',
|
|
'knowledge-local-ability-mod': '',
|
|
'knowledge-local-racial': '',
|
|
'knowledge-local-feat': '',
|
|
'knowledge-local-item': '',
|
|
'knowledge-local-size': '',
|
|
'knowledge-local-acp': '',
|
|
'knowledge-local-misc': '',
|
|
'knowledge-local-reqtrain': '',
|
|
'knowledge-nature-ability': '',
|
|
'knowledge-nature-cs': '',
|
|
'knowledge-nature-ranks': '',
|
|
'knowledge-nature-class': '',
|
|
'knowledge-nature-ability-mod': '',
|
|
'knowledge-nature-racial': '',
|
|
'knowledge-nature-feat': '',
|
|
'knowledge-nature-item': '',
|
|
'knowledge-nature-size': '',
|
|
'knowledge-nature-acp': '',
|
|
'knowledge-nature-misc': '',
|
|
'knowledge-nature-reqtrain': '',
|
|
'knowledge-nobility-ability': '',
|
|
'knowledge-nobility-cs': '',
|
|
'knowledge-nobility-ranks': '',
|
|
'knowledge-nobility-class': '',
|
|
'knowledge-nobility-ability-mod': '',
|
|
'knowledge-nobility-racial': '',
|
|
'knowledge-nobility-feat': '',
|
|
'knowledge-nobility-item': '',
|
|
'knowledge-nobility-size': '',
|
|
'knowledge-nobility-acp': '',
|
|
'knowledge-nobility-misc': '',
|
|
'knowledge-nobility-reqtrain': '',
|
|
'knowledge-planes-ability': '',
|
|
'knowledge-planes-cs': '',
|
|
'knowledge-planes-ranks': '',
|
|
'knowledge-planes-class': '',
|
|
'knowledge-planes-ability-mod': '',
|
|
'knowledge-planes-racial': '',
|
|
'knowledge-planes-feat': '',
|
|
'knowledge-planes-item': '',
|
|
'knowledge-planes-size': '',
|
|
'knowledge-planes-acp': '',
|
|
'knowledge-planes-misc': '',
|
|
'knowledge-planes-reqtrain': '',
|
|
'knowledge-religion-ability': '',
|
|
'knowledge-religion-cs': '',
|
|
'knowledge-religion-ranks': '',
|
|
'knowledge-religion-class': '',
|
|
'knowledge-religion-ability-mod': '',
|
|
'knowledge-religion-racial': '',
|
|
'knowledge-religion-feat': '',
|
|
'knowledge-religion-item': '',
|
|
'knowledge-religion-size': '',
|
|
'knowledge-religion-acp': '',
|
|
'knowledge-religion-misc': '',
|
|
'knowledge-religion-reqtrain': '',
|
|
'perception-ability': '',
|
|
'perception-cs': '',
|
|
'perception-ranks': '',
|
|
'perception-class': '',
|
|
'perception-ability-mod': '',
|
|
'perception-racial': '',
|
|
'perception-feat': '',
|
|
'perception-item': '',
|
|
'perception-size': '',
|
|
'perception-acp': '',
|
|
'perception-misc': '',
|
|
'perception-reqtrain': '',
|
|
'perform-ability': '',
|
|
'perform-cs': '',
|
|
'perform-ranks': '',
|
|
'perform-class': '',
|
|
'perform-ability-mod': '',
|
|
'perform-racial': '',
|
|
'perform-feat': '',
|
|
'perform-item': '',
|
|
'perform-size': '',
|
|
'perform-acp': '',
|
|
'perform-misc': '',
|
|
'perform-reqtrain': '',
|
|
'perform2-ability': '',
|
|
'perform2-cs': '',
|
|
'perform2-ranks': '',
|
|
'perform2-class': '',
|
|
'perform2-ability-mod': '',
|
|
'perform2-racial': '',
|
|
'perform2-feat': '',
|
|
'perform2-item': '',
|
|
'perform2-size': '',
|
|
'perform2-acp': '',
|
|
'perform2-misc': '',
|
|
'perform2-reqtrain': '',
|
|
'perform3-ability': '',
|
|
'perform3-cs': '',
|
|
'perform3-ranks': '',
|
|
'perform3-class': '',
|
|
'perform3-ability-mod': '',
|
|
'perform3-racial': '',
|
|
'perform3-feat': '',
|
|
'perform3-item': '',
|
|
'perform3-size': '',
|
|
'perform3-acp': '',
|
|
'perform3-misc': '',
|
|
'perform3-reqtrain': '',
|
|
'profession-ability': '',
|
|
'profession-cs': '',
|
|
'profession-ranks': '',
|
|
'profession-class': '',
|
|
'profession-ability-mod': '',
|
|
'profession-racial': '',
|
|
'profession-feat': '',
|
|
'profession-item': '',
|
|
'profession-size': '',
|
|
'profession-acp': '',
|
|
'profession-misc': '',
|
|
'profession-reqtrain': '',
|
|
'profession2-ability': '',
|
|
'profession2-cs': '',
|
|
'profession2-ranks': '',
|
|
'profession2-class': '',
|
|
'profession2-ability-mod': '',
|
|
'profession2-racial': '',
|
|
'profession2-feat': '',
|
|
'profession2-item': '',
|
|
'profession2-size': '',
|
|
'profession2-acp': '',
|
|
'profession2-misc': '',
|
|
'profession2-reqtrain': '',
|
|
'profession3-ability': '',
|
|
'profession3-cs': '',
|
|
'profession3-ranks': '',
|
|
'profession3-class': '',
|
|
'profession3-ability-mod': '',
|
|
'profession3-racial': '',
|
|
'profession3-feat': '',
|
|
'profession3-item': '',
|
|
'profession3-size': '',
|
|
'profession3-acp': '',
|
|
'profession3-misc': '',
|
|
'profession3-reqtrain': '',
|
|
'ride-ability': '',
|
|
'ride-cs': '',
|
|
'ride-ranks': '',
|
|
'ride-class': '',
|
|
'ride-ability-mod': '',
|
|
'ride-racial': '',
|
|
'ride-feat': '',
|
|
'ride-item': '',
|
|
'ride-size': '',
|
|
'ride-acp': '',
|
|
'ride-misc': '',
|
|
'ride-reqtrain': '',
|
|
'sense-motive-ability': '',
|
|
'sense-motive-cs': '',
|
|
'sense-motive-ranks': '',
|
|
'sense-motive-class': '',
|
|
'sense-motive-ability-mod': '',
|
|
'sense-motive-racial': '',
|
|
'sense-motive-feat': '',
|
|
'sense-motive-item': '',
|
|
'sense-motive-size': '',
|
|
'sense-motive-acp': '',
|
|
'sense-motive-misc': '',
|
|
'sense-motive-reqtrain': '',
|
|
'sleight-of-hand-ability': '',
|
|
'sleight-of-hand-cs': '',
|
|
'sleight-of-hand-ranks': '',
|
|
'sleight-of-hand-class': '',
|
|
'sleight-of-hand-ability-mod': '',
|
|
'sleight-of-hand-racial': '',
|
|
'sleight-of-hand-feat': '',
|
|
'sleight-of-hand-item': '',
|
|
'sleight-of-hand-size': '',
|
|
'sleight-of-hand-acp': '',
|
|
'sleight-of-hand-misc': '',
|
|
'sleight-of-hand-reqtrain': '',
|
|
'spellcraft-ability': '',
|
|
'spellcraft-cs': '',
|
|
'spellcraft-ranks': '',
|
|
'spellcraft-class': '',
|
|
'spellcraft-ability-mod': '',
|
|
'spellcraft-racial': '',
|
|
'spellcraft-feat': '',
|
|
'spellcraft-item': '',
|
|
'spellcraft-size': '',
|
|
'spellcraft-acp': '',
|
|
'spellcraft-misc': '',
|
|
'spellcraft-reqtrain': '',
|
|
'stealth-ability': '',
|
|
' stealth-cs': '',
|
|
'stealth-ranks': '',
|
|
'stealth-class': '',
|
|
'stealth-ability-mod': '',
|
|
'stealth-racial': '',
|
|
'stealth-feat': '',
|
|
'stealth-item': '',
|
|
'stealth-size': '',
|
|
'stealth-acp': '',
|
|
'stealth-misc': '',
|
|
'stealth-reqtrain': '',
|
|
'survival-ability': '',
|
|
'survival-cs': '',
|
|
'survival-ranks': '',
|
|
'survival-class': '',
|
|
'survival-ability-mod': '',
|
|
'survival-racial': '',
|
|
'survival-feat': '',
|
|
'survival-item': '',
|
|
'survival-size': '',
|
|
'survival-acp': '',
|
|
'survival-misc': '',
|
|
'survival-reqtrain': '',
|
|
'swim-ability': '',
|
|
'swim-cs': '',
|
|
'swim-ranks': '',
|
|
'swim-class': '',
|
|
'swim-ability-mod': '',
|
|
'swim-racial': '',
|
|
'swim-feat': '',
|
|
'swim-item': '',
|
|
'swim-size': '',
|
|
'swim-acp': '',
|
|
'swim-misc': '',
|
|
'swim-reqtrain': '',
|
|
'use-magic-device-ability': '',
|
|
'use-magic-device-cs': '',
|
|
'use-magic-device-ranks': '',
|
|
'use-magic-device-class': '',
|
|
'use-magic-device-ability-mod': '',
|
|
'use-magic-device-racial': '',
|
|
'use-magic-device-feat': '',
|
|
'use-magic-device-item': '',
|
|
'use-magic-device-size': '',
|
|
'use-magic-device-acp': '',
|
|
'use-magic-device-misc': '',
|
|
'use-magic-device-reqtrain': '',
|
|
'misc-skill-0-ability': '',
|
|
'misc-skill-0-cs': '',
|
|
'misc-skill-0-ranks': '',
|
|
'misc-skill-0-class': '',
|
|
'misc-skill-0-ability-mod': '',
|
|
'misc-skill-0-racial': '',
|
|
'misc-skill-0-feat': '',
|
|
'misc-skill-0-item': '',
|
|
'misc-skill-0-size': '',
|
|
'misc-skill-0-acp': '',
|
|
'misc-skill-0-misc': '',
|
|
'misc-skill-0-reqtrain': '',
|
|
'misc-skill-1-ability': '',
|
|
'misc-skill-1-cs': '',
|
|
'misc-skill-1-ranks': '',
|
|
'misc-skill-1-class': '',
|
|
'misc-skill-1-ability-mod': '',
|
|
'misc-skill-1-racial': '',
|
|
'misc-skill-1-feat': '',
|
|
'misc-skill-1-item': '',
|
|
'misc-skill-1-size': '',
|
|
'misc-skill-1-acp': '',
|
|
'misc-skill-1-misc': '',
|
|
'misc-skill-1-reqtrain': '',
|
|
'misc-skill-2-ability': '',
|
|
'misc-skill-2-cs': '',
|
|
'misc-skill-2-ranks': '',
|
|
'misc-skill-2-class': '',
|
|
'misc-skill-2-ability-mod': '',
|
|
'misc-skill-2-racial': '',
|
|
'misc-skill-2-feat': '',
|
|
'misc-skill-2-item': '',
|
|
'misc-skill-2-size': '',
|
|
'misc-skill-2-acp': '',
|
|
'misc-skill-2-misc': '',
|
|
'misc-skill-2-reqtrain': '',
|
|
'misc-skill-3-ability': '',
|
|
'misc-skill-3-cs': '',
|
|
'misc-skill-3-ranks': '',
|
|
'misc-skill-3-class': '',
|
|
'misc-skill-3-ability-mod': '',
|
|
'misc-skill-3-racial': '',
|
|
'misc-skill-3-feat': '',
|
|
'misc-skill-3-item': '',
|
|
'misc-skill-3-size': '',
|
|
'misc-skill-3-acp': '',
|
|
'misc-skill-3-misc': '',
|
|
'misc-skill-3-reqtrain': '',
|
|
'misc-skill-4-ability': '',
|
|
'misc-skill-4-cs': '',
|
|
'misc-skill-4-ranks': '',
|
|
'misc-skill-4-class': '',
|
|
'misc-skill-4-ability-mod': '',
|
|
'misc-skill-4-racial': '',
|
|
'misc-skill-4-feat': '',
|
|
'misc-skill-4-item': '',
|
|
'misc-skill-4-size': '',
|
|
'misc-skill-4-acp': '',
|
|
'misc-skill-4-misc': '',
|
|
'misc-skill-4-reqtrain': '',
|
|
'misc-skill-5-ability': '',
|
|
'misc-skill-5-cs': '',
|
|
'misc-skill-5-ranks': '',
|
|
'misc-skill-5-class': '',
|
|
'misc-skill-5-ability-mod': '',
|
|
'misc-skill-5-racial': '',
|
|
'misc-skill-5-feat': '',
|
|
'misc-skill-5-item': '',
|
|
'misc-skill-5-size': '',
|
|
'misc-skill-5-acp': '',
|
|
'misc-skill-5-misc': '',
|
|
'misc-skill-5-reqtrain': '',
|
|
'craft-name': '',
|
|
'craft2-name': '',
|
|
'craft3-name': '',
|
|
'lore-name': '',
|
|
'perform-name': '',
|
|
'perform2-name': '',
|
|
'perform3-name': '',
|
|
'profession-name': '',
|
|
'profession2-name': '',
|
|
'profession3-name': '',
|
|
'misc-skill-0-name': '',
|
|
'misc-skill-1-name': '',
|
|
'misc-skill-2-name': '',
|
|
'misc-skill-3-name': '',
|
|
'misc-skill-4-name': '',
|
|
'misc-skill-5-name': '',
|
|
});
|
|
|
|
// Keep track of which of these skills we're on
|
|
let craft = 1;
|
|
let perform = 1;
|
|
let profession = 1;
|
|
let artistry = 1;
|
|
let lore = 1;
|
|
let misc = 0;
|
|
|
|
let i = 0;
|
|
let skill;
|
|
let skillMisc;
|
|
let skillAttrPrefix;
|
|
for (i = 0; i < skills.length; i++) {
|
|
/* if (_.isUndefined(skill._name))
|
|
{
|
|
continue;
|
|
} */
|
|
skill = skills[i];
|
|
|
|
// Figure out where we're putting this skill on the character sheet
|
|
if (skill._name.indexOf('Craft') !== -1) {
|
|
if (craft === 1) {
|
|
skillAttrPrefix = 'craft';
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs['craft-name'] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
craft++;
|
|
} else if (craft <= 10) {
|
|
skillAttrPrefix = `craft${craft}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs[`craft${craft}-name`] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
craft++;
|
|
} else if (misc <= 10) {
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null) attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
} else console.log(`Ran out of misc skills for ${skill._name}!`);
|
|
} else if (skill._name.indexOf('Perform') !== -1) {
|
|
if (perform === 1) {
|
|
skillAttrPrefix = 'perform';
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs['perform-name'] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
perform++;
|
|
} else if (perform <= 10) {
|
|
skillAttrPrefix = `perform${perform}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs[`perform${perform}-name`] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
perform++;
|
|
} else if (misc <= 10) {
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null) attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
} else console.log(`Ran out of misc skills for ${skill._name}!`);
|
|
} else if (skill._name.indexOf('Profession') !== -1) {
|
|
if (profession === 1) {
|
|
skillAttrPrefix = 'profession';
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs['profession-name'] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
profession++;
|
|
} else if (profession <= 10) {
|
|
skillAttrPrefix = `profession${profession}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null)
|
|
attrs[`profession${profession}-name`] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
profession++;
|
|
} else if (misc <= 10) {
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
if (skill._name.match(/\(([^\)]*)\)/) !== null) attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
} else console.log(`Ran out of misc skills for ${skill._name}!`);
|
|
} else if (skill._name.indexOf('Knowledge') !== -1) {
|
|
switch (skill._name.match(/\(([^\)]*)\)/g)[0]) {
|
|
case '(arcana)':
|
|
case '(dungeoneering)':
|
|
case '(engineering)':
|
|
case '(geography)':
|
|
case '(history)':
|
|
case '(local)':
|
|
case '(nature)':
|
|
case '(nobility)':
|
|
case '(planes)':
|
|
case '(religion)':
|
|
skillAttrPrefix = skill._name.toLowerCase().replace(/\s/g, '-').replace('(', '').replace(')', '');
|
|
break;
|
|
default:
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
}
|
|
} else if (skill._name.indexOf('Artistry') !== -1) {
|
|
if (artistry === 1) {
|
|
skillAttrPrefix = 'artistry';
|
|
attrs['artistry-name'] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
artistry++;
|
|
} else if (artistry <= 10) {
|
|
skillAttrPrefix = `artistry${artistry}`;
|
|
attrs[`artistry${artistry}-name`] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
artistry++;
|
|
} else if (misc <= 10) {
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
} else console.log(`Ran out of misc skills for ${skill._name}!`);
|
|
} else if (skill._name.indexOf('Lore') !== -1) {
|
|
if (lore === 1) {
|
|
skillAttrPrefix = 'lore';
|
|
attrs['lore-name'] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
lore++;
|
|
} else if (lore <= 10) {
|
|
skillAttrPrefix = `lore${lore}`;
|
|
attrs[`lore${lore}-name`] = skill._name
|
|
.match(/\(([^\)]*)\)/)[0]
|
|
.replace('(', '')
|
|
.replace(')', '');
|
|
lore++;
|
|
} else if (misc <= 10) {
|
|
skillAttrPrefix = `misc-skill-${misc}`;
|
|
attrs[`${skillAttrPrefix}-name`] = skill._name;
|
|
misc++;
|
|
} else console.log(`Ran out of misc skills for ${skill._name}!`);
|
|
} else
|
|
skillAttrPrefix = skill._name
|
|
.toLowerCase()
|
|
.replace(/\s/g, '-')
|
|
.replace('(', '')
|
|
.replace(')', '')
|
|
.replace('-hand', '-Hand')
|
|
.replace('e-device', 'e-Device')
|
|
.replace('-artist', '-Artist')
|
|
.replace('-animal', '-Animal');
|
|
|
|
attrs[skillAttrPrefix] = parseNum(skill._value);
|
|
attrs[`${skillAttrPrefix}-ranks`] = parseNum(skill._ranks);
|
|
attrs[`${skillAttrPrefix}-ability`] = `${skill._attrname}-mod`;
|
|
attrs[`${skillAttrPrefix}-ability-mod`] = parseNum(skill._attrbonus);
|
|
|
|
if (skill._classskill === 'yes') attrs[`${skillAttrPrefix}-cs`] = 3;
|
|
|
|
skillMisc = parseNum(skill._value) - parseNum(skill._ranks) - parseNum(skill._attrbonus);
|
|
if (parseNum(skill._ranks) !== 0 && skill._classskill === 'yes') skillMisc -= 3;
|
|
if (skill._armorcheck === 'yes') skillMisc -= ACP;
|
|
if (skill._name === 'Fly') skillMisc -= skillSize;
|
|
if (skill._name === 'Stealth') skillMisc -= 2 * skillSize;
|
|
attrs[`${skillAttrPrefix}-misc`] = skillMisc;
|
|
|
|
if (skill._trainedonly === 'yes') attrs[`${skillAttrPrefix}-ReqTrain`] = 1;
|
|
|
|
// Add situation modifiers to the macro
|
|
if (!_.isUndefined(skill.situationalmodifiers.situationalmodifier)) {
|
|
let notes = '';
|
|
skill.situationalmodifiers.situationalmodifier = arrayify(skill.situationalmodifiers.situationalmodifier);
|
|
let j = 0;
|
|
while (j < skill.situationalmodifiers.situationalmodifier.length) {
|
|
notes = `${notes}}} {{${skill.situationalmodifiers.situationalmodifier[j]._source}=${skill.situationalmodifiers.situationalmodifier[j]._text}`;
|
|
j++;
|
|
}
|
|
attrs[`${skillAttrPrefix}-note`] = notes;
|
|
}
|
|
}
|
|
const numCPP = Math.max(craft, perform, profession, artistry, lore);
|
|
if (numCPP > 6) attrs.custom_skill_num_show = 10;
|
|
else if (numCPP > 3) attrs.custom_skill_num_show = 6;
|
|
else if (numCPP > 2) attrs.custom_skill_num_show = 3;
|
|
else if (numCPP > 2) attrs.custom_skill_num_show = 2;
|
|
else attrs.custom_skill_num_show = 1;
|
|
|
|
if (misc > 6) attrs.misc_skill_num_show = 10;
|
|
else if (misc > 3) attrs.misc_skill_num_show = 6;
|
|
else if (misc > 2) attrs.misc_skill_num_show = 3;
|
|
else if (misc > 2) attrs.misc_skill_num_show = 2;
|
|
else attrs.misc_skill_num_show = 1;
|
|
}
|
|
|
|
// Import ACP and Max Dex; these aren't included under items, but the final values are listed in penalties
|
|
export function importPenalties(attrs, penalties) {
|
|
let ACP = 0;
|
|
let i = 0;
|
|
while (i < penalties.length) {
|
|
if (penalties[i]._name === 'Armor Check Penalty') {
|
|
ACP = parseNum(penalties[i]._value);
|
|
attrs['armor3-acp'] = ACP;
|
|
} else if (penalties[i]._name === 'Max Dex Bonus') attrs['armor3-max-dex'] = Math.min(99, parseNum(penalties[i]._value)); // Hero Lab uses 1000 for Max Dex when player doesn't have one; cap it at 99 to match sheet default
|
|
i++;
|
|
}
|
|
return ACP;
|
|
}
|
|
|
|
export function importAC(attrs, acObj, armorPenalties) {
|
|
attrs.AC = parseNum(acObj._ac);
|
|
attrs.Touch = parseNum(acObj._touch);
|
|
attrs['Flat-Footed'] = parseNum(acObj._flatfooted);
|
|
|
|
attrs['AC-natural'] = parseNum(acObj._fromnatural);
|
|
attrs['AC-deflect'] = parseNum(acObj._fromdeflect);
|
|
attrs['AC-dodge'] = parseNum(acObj._fromdodge);
|
|
attrs['AC-armor'] = parseNum(acObj._fromarmor);
|
|
attrs['AC-shield'] = parseNum(acObj._fromshield);
|
|
|
|
// Are we replacing Dex to AC with something else?
|
|
if (acObj._fromdexterity === '') {
|
|
if (acObj._fromcharisma !== '') {
|
|
attrs['AC-ability'] = 'CHA-mod';
|
|
attrs['AC-ability-mod'] = attrs['AC-ability-display'] = parseNum(acObj._fromcharisma);
|
|
attrs['AC-misc'] =
|
|
parseNum(acObj._ac) -
|
|
10 -
|
|
parseNum(acObj._fromarmor) -
|
|
parseNum(acObj._fromshield) -
|
|
parseNum(acObj._fromcharisma) -
|
|
parseNum(acObj._fromsize) -
|
|
parseNum(acObj._fromnatural) -
|
|
parseNum(acObj._fromdeflect) -
|
|
parseNum(acObj._fromdodge);
|
|
} else if (acObj._fromwisdom !== '') {
|
|
attrs['AC-ability'] = 'WIS-mod';
|
|
attrs['AC-ability-mod'] = attrs['AC-ability-display'] = parseNum(acObj._fromwisdom);
|
|
attrs['AC-misc'] =
|
|
parseNum(acObj._ac) -
|
|
10 -
|
|
parseNum(acObj._fromarmor) -
|
|
parseNum(acObj._fromshield) -
|
|
parseNum(acObj._fromwisdom) -
|
|
parseNum(acObj._fromsize) -
|
|
parseNum(acObj._fromnatural) -
|
|
parseNum(acObj._fromdeflect) -
|
|
parseNum(acObj._fromdodge);
|
|
}
|
|
} else {
|
|
attrs['AC-ability'] = 'DEX-mod';
|
|
attrs['AC-ability-mod'] = attrs['AC-ability-display'] = parseNum(acObj._fromdexterity);
|
|
attrs['AC-misc'] =
|
|
parseNum(acObj._ac) -
|
|
10 -
|
|
parseNum(acObj._fromarmor) -
|
|
parseNum(acObj._fromshield) -
|
|
parseNum(acObj._fromdexterity) -
|
|
parseNum(acObj._fromsize) -
|
|
parseNum(acObj._fromnatural) -
|
|
parseNum(acObj._fromdeflect) -
|
|
parseNum(acObj._fromdodge);
|
|
}
|
|
}
|
|
|
|
export function importCharacter(characterObj) {
|
|
const attrs = {};
|
|
|
|
importAbilityScores(attrs, characterObj.attributes.attribute);
|
|
importSaves(attrs, characterObj.saves);
|
|
let classes;
|
|
let spellClasses;
|
|
let archetypes = {};
|
|
// Class objects won't exist for creatures w/o class levels, such as animals
|
|
if (!_.isUndefined(characterObj.classes.class)) {
|
|
// Class will be an array if multiclassed, but a single object if single-classed; make it an array, just to be safe
|
|
characterObj.classes.class = arrayify(characterObj.classes.class);
|
|
|
|
classes = importClasses(attrs, characterObj.classes.class, characterObj.health._hitdice);
|
|
|
|
// If any of the character's classes is a spellcaster, it'll be listed here, too
|
|
if (!_.isUndefined(characterObj.spellclasses.spellclass)) {
|
|
attrs.use_spells = 1;
|
|
characterObj.spellclasses.spellclass = arrayify(characterObj.spellclasses.spellclass);
|
|
spellClasses = importSpellClasses(attrs, characterObj.spellclasses.spellclass, classes, characterObj.attributes.attribute);
|
|
|
|
// Well, it's a spellcaster, so let's import those spells, too!
|
|
let spellsArray = arrayify(characterObj.spellsknown.spell).concat(arrayify(characterObj.spellbook.spell)).concat(arrayify(characterObj.spellsmemorized.spell));
|
|
const spellNames = [];
|
|
spellsArray = _.reject(spellsArray, function (spell) {
|
|
if (_.contains(spellNames, spell._name)) return true;
|
|
spellNames.concat(spell._name);
|
|
return false;
|
|
});
|
|
importSpells(spellsArray, spellClasses);
|
|
}
|
|
|
|
// Need to keep track of what archetypes the character has, since class feature source could be an archetype
|
|
archetypes = buildArchetypeArray(classes);
|
|
}
|
|
|
|
characterObj.penalties.penalty = arrayify(characterObj.penalties.penalty);
|
|
const ACP = importPenalties(attrs, characterObj.penalties.penalty);
|
|
|
|
// Build an object we can pass to the item importing, so we can attach this to the inventory item
|
|
const armorPenalties = {};
|
|
armorPenalties.ACP = parseNum(attrs['armor3-acp']);
|
|
armorPenalties.maxDex = parseNum(attrs['armor3-max-dex']);
|
|
armorPenalties.spellfail = parseNum(attrs['armor3-spell-fail']);
|
|
|
|
importAC(attrs, characterObj.armorclass, armorPenalties);
|
|
|
|
// We might change these values if we're using a shield, so don't set them outside of item import
|
|
if (!_.isUndefined(attrs['armor3-acp'])) delete attrs['armor3-acp'];
|
|
if (!_.isUndefined(attrs['armor3-spell-fail'])) delete attrs['armor3-spell-fail'];
|
|
|
|
const armor = _.reject(arrayify(characterObj.defenses.armor || {}), function (item) {
|
|
return _.isUndefined(item._name);
|
|
});
|
|
const weapons = _.reject(arrayify(characterObj.melee.weapon || {}).concat(arrayify(characterObj.ranged.weapon || {})), function (item) {
|
|
return _.isUndefined(item._name);
|
|
});
|
|
|
|
// "Tracked Resources" is a list of uses, either a quantity of items, charges, or uses per day
|
|
const resources = _.object(
|
|
_.map(characterObj.trackedresources.trackedresource, function (resource) {
|
|
return [resource._name, resource];
|
|
}),
|
|
);
|
|
|
|
// Make an array of items, both magic and mundane
|
|
const items = _.reject(arrayify(characterObj.magicitems.item || {}).concat(arrayify(characterObj.gear.item || {})), function (item) {
|
|
return _.isUndefined(item._name);
|
|
});
|
|
|
|
// "Specials" could include items, so we need to filter them out
|
|
const itemNames = _.map(items, function (obj) {
|
|
return obj._name;
|
|
});
|
|
const specials = _.reject(
|
|
arrayify(characterObj.attack.special).concat(
|
|
arrayify(characterObj.defenses.special),
|
|
arrayify(characterObj.otherspecials.special),
|
|
arrayify(characterObj.movement.special),
|
|
arrayify(characterObj.defensive.special),
|
|
),
|
|
function (obj) {
|
|
return _.contains(itemNames, obj._name);
|
|
},
|
|
);
|
|
|
|
importItems(items, resources, armorPenalties, armor, weapons);
|
|
|
|
getSectionIDs('repeating_ability', function (idarray) {
|
|
const abilityNameAttrs = _.union(
|
|
_.map(idarray, function (id) {
|
|
return `repeating_ability_${id}_name`;
|
|
}),
|
|
_.map(idarray, function (id) {
|
|
return `repeating_ability_${id}_rule_category`;
|
|
}),
|
|
);
|
|
getAttrs(abilityNameAttrs, function (abilityAttrs) {
|
|
const abilityObjList = {};
|
|
const abilityKeys = Object.keys(abilityAttrs);
|
|
const asyncAttrs = {};
|
|
_.each(abilityKeys, function (abilityKey) {
|
|
let rowID;
|
|
if (abilityKey.indexOf('_name') !== -1) {
|
|
rowID = abilityKey.substring('repeating_ability_'.length, abilityKey.indexOf('_name'));
|
|
if (_.isUndefined(abilityObjList[rowID])) abilityObjList[rowID] = {rowID};
|
|
abilityObjList[rowID].name = abilityAttrs[abilityKey];
|
|
}
|
|
if (abilityKey.indexOf('_rule_category') !== -1) {
|
|
rowID = abilityKey.substring('repeating_ability_'.length, abilityKey.indexOf('_rule_category'));
|
|
if (_.isUndefined(abilityObjList[rowID])) abilityObjList[rowID] = {rowID};
|
|
abilityObjList[rowID].rulecategory = abilityAttrs[abilityKey];
|
|
}
|
|
});
|
|
|
|
if (!_.isUndefined(characterObj.feats.feat)) {
|
|
const featsArray = _.filter(abilityObjList, _.matcher({rulecategory: 'feats'}));
|
|
const featsList = {};
|
|
_.each(featsArray, function (obj) {
|
|
featsList[obj.rowID] = obj.name;
|
|
});
|
|
characterObj.feats.feat = arrayify(characterObj.feats.feat);
|
|
importFeats(asyncAttrs, characterObj.feats.feat, featsList, resources);
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.traits.trait)) {
|
|
const traitsArray = _.filter(abilityObjList, _.matcher({rulecategory: 'traits'}));
|
|
const traitsList = {};
|
|
_.each(traitsArray, function (obj) {
|
|
traitsList[obj.rowID] = obj.name;
|
|
});
|
|
characterObj.traits.trait = arrayify(characterObj.traits.trait);
|
|
importTraits(asyncAttrs, characterObj.traits.trait, traitsList, resources);
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.spelllike.special)) {
|
|
const SLAsArray = _.filter(abilityObjList, _.matcher({rulecategory: 'spell-like-abilities'}));
|
|
const SLAsList = {};
|
|
_.each(SLAsArray, function (obj) {
|
|
SLAsList[obj.rowID] = obj.name;
|
|
});
|
|
characterObj.spelllike.special = arrayify(characterObj.spelllike.special);
|
|
importSLAs(asyncAttrs, characterObj.spelllike.special, SLAsList, resources);
|
|
}
|
|
|
|
const featuresArray = _.filter(abilityObjList, function (obj) {
|
|
if (obj.rulecategory === 'traits' || obj.rulecategory === 'feats') return false;
|
|
return true;
|
|
});
|
|
const featuresList = {};
|
|
_.each(featuresArray, function (obj) {
|
|
featuresList[obj.rowID] = obj;
|
|
});
|
|
importFeatures(asyncAttrs, featuresList, specials, archetypes, resources);
|
|
|
|
setAttrs(asyncAttrs, {silent: true});
|
|
});
|
|
});
|
|
|
|
attrs.experience = parseFloat(characterObj.xp._total);
|
|
|
|
attrs['class-0-bab'] =
|
|
attrs.bab =
|
|
attrs['melee_bab-mod'] =
|
|
attrs['melee2_bab-mod'] =
|
|
attrs['ranged_bab-mod'] =
|
|
attrs['ranged2_bab-mod'] =
|
|
attrs['melee_bab-mod'] =
|
|
attrs['cmb_bab-mod'] =
|
|
attrs['cmb2_bab-mod'] =
|
|
parseNum(characterObj.attack._baseattack);
|
|
|
|
// Set max hp; remove Con mod from hp first, since the sheet will add that in
|
|
// Since the XML doesn't break this down by class, add it all to class 0
|
|
attrs['non-lethal-damage_max'] = attrs.HP_max = parseNum(characterObj.health._hitpoints);
|
|
const level = calcHitDice(characterObj.health._hitdice);
|
|
attrs.level = level;
|
|
attrs['HP-ability-mod'] = parseNum(characterObj.attributes.attribute[2].attrbonus._modified);
|
|
attrs.hp_ability_bonus = level * parseNum(characterObj.attributes.attribute[2].attrbonus._modified);
|
|
attrs['total-hp'] = attrs['class-0-hp'] = parseNum(characterObj.health._hitpoints) - level * parseNum(characterObj.attributes.attribute[2].attrbonus._modified);
|
|
importInit(attrs, characterObj.initiative);
|
|
const racialHD = level - parseNum(characterObj.classes._level);
|
|
if (racialHD > 0) {
|
|
attrs.add_race = 1;
|
|
attrs['npc-hd-num'] = racialHD;
|
|
}
|
|
|
|
const size = getSizeMod(characterObj.size._name);
|
|
attrs.size = size;
|
|
attrs.default_char_size = size;
|
|
attrs['CMD-size'] = size * -1;
|
|
|
|
characterObj.skills.skill = arrayify(characterObj.skills.skill);
|
|
importSkills(attrs, characterObj.skills.skill, size, ACP);
|
|
|
|
if (!_.isUndefined(characterObj.senses.special)) {
|
|
characterObj.senses.special = arrayify(characterObj.senses.special);
|
|
attrs.vision = buildList(characterObj.senses.special, '_shortname');
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.damagereduction.special)) {
|
|
characterObj.damagereduction.special = arrayify(characterObj.damagereduction.special);
|
|
attrs.DR = buildList(characterObj.damagereduction.special, '_shortname');
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.resistances.special)) {
|
|
characterObj.resistances.special = arrayify(characterObj.resistances.special);
|
|
attrs.resistances = buildList(characterObj.resistances.special, '_shortname');
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.immunities.special)) {
|
|
characterObj.immunities.special = arrayify(characterObj.immunities.special);
|
|
attrs.immunities = buildList(characterObj.immunities.special, '_shortname');
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.weaknesses.special)) {
|
|
characterObj.weaknesses.special = arrayify(characterObj.weaknesses.special);
|
|
attrs.weaknesses = buildList(characterObj.weaknesses.special, '_shortname');
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.languages.language)) {
|
|
characterObj.languages.language = arrayify(characterObj.languages.language);
|
|
attrs.languages = buildList(characterObj.languages.language, '_name');
|
|
}
|
|
|
|
attrs['npc-type'] = arrayify(characterObj.types.type)[0]._name;
|
|
if (!_.isUndefined(characterObj.subtypes.subtype)) attrs['npc-type'] = `${attrs['npc-type']} (${buildList(arrayify(characterObj.subtypes.subtype), '_name')})`;
|
|
|
|
attrs.character_name = characterObj._name;
|
|
// NPC Only
|
|
if (characterObj._role !== 'pc') {
|
|
attrs.is_npc = 1;
|
|
attrs.tab = 8;
|
|
if (!_.isUndefined(characterObj.npc.ecology.npcinfo)) {
|
|
attrs.environment = arrayify(characterObj.npc.ecology.npcinfo)[0].text || '';
|
|
attrs.organization = arrayify(characterObj.npc.ecology.npcinfo)[1].text || '';
|
|
attrs.other_items_treasure = arrayify(characterObj.npc.ecology.npcinfo)[2].text || '';
|
|
}
|
|
if (!_.isUndefined(characterObj.npc.tactics)) {
|
|
attrs['npc-during-combat'] = characterObj.npc.tactics || '';
|
|
}
|
|
}
|
|
attrs['player-name'] = characterObj._playername;
|
|
attrs.deity = characterObj.deity._name;
|
|
attrs.race = characterObj.race._racetext.substr(0, 1).toUpperCase() + characterObj.race._racetext.substr(1, 1000);
|
|
attrs.alignment = characterObj.alignment._name;
|
|
attrs.gender = characterObj.personal._gender;
|
|
attrs.age = characterObj.personal._age;
|
|
attrs.height = characterObj.personal.charheight._text;
|
|
attrs.weight = characterObj.personal.charweight._text;
|
|
attrs.hair = characterObj.personal._hair;
|
|
attrs.eyes = characterObj.personal._eyes;
|
|
attrs.skin = characterObj.personal._skin;
|
|
// PC/NPC
|
|
attrs.character_description = characterObj._role !== 'pc' ? characterObj.npc.description : characterObj.personal.description;
|
|
|
|
attrs['npc-cr'] = characterObj.challengerating._text.replace('CR ', '');
|
|
attrs['npc-xp'] = characterObj.xpaward._value;
|
|
|
|
attrs['speed-base'] = attrs['speed-modified'] = parseNum(characterObj.movement.speed._value);
|
|
|
|
attrs.cmb = parseNum(characterObj.maneuvers._total);
|
|
attrs.cmd = parseNum(characterObj.maneuvers._cmd);
|
|
attrs['ff-cmd'] = parseNum(characterObj.maneuvers._cmdflatfooted);
|
|
attrs['cmd-str'] = attrs['STR-mod']; // CMB/CMD doesn't have any breakdown in XML, so have to assume still using STR
|
|
attrs['cmd-misc'] = attrs.cmd - 10 - attrs['AC-ability-mod'] - attrs['cmd-str'] - attrs['CMD-size'] - attrs['AC-dodge'] - attrs['AC-deflect'] - attrs.bab;
|
|
|
|
attrs['attk-melee'] = parseNum(characterObj.attack._meleeattack.split('/')[0]);
|
|
attrs['attk-ranged'] = attrs['attk-melee2'] = parseNum(characterObj.attack._rangedattack.split('/')[0]); // Assuming Melee 2 is using Dex, setting to ranged bonus because lazy
|
|
|
|
if (!_.isUndefined(characterObj.favoredclasses.favoredclass)) {
|
|
characterObj.favoredclasses.favoredclass = arrayify(characterObj.favoredclasses.favoredclass);
|
|
attrs['class-favored'] = buildList(characterObj.favoredclasses.favoredclass, '_name');
|
|
}
|
|
|
|
attrs['other-PP'] = characterObj.money._pp;
|
|
attrs['other-GP'] = characterObj.money._gp;
|
|
attrs['other-SP'] = characterObj.money._sp;
|
|
attrs['other-CP'] = characterObj.money._cp;
|
|
|
|
attrs['load-str-bonus'] = parseNum(characterObj.encumbrance._encumstr) - attrs.STR;
|
|
attrs['load-light'] = characterObj.encumbrance._light;
|
|
attrs['load-medium'] = characterObj.encumbrance._medium;
|
|
attrs['load-heavy'] = attrs['lift-above-head'] = characterObj.encumbrance._heavy;
|
|
attrs['load-max'] = attrs['lift-off-ground'] = parseNum(characterObj.encumbrance._heavy) * 2;
|
|
attrs['lift-drag-and-push'] = parseNum(characterObj.encumbrance._heavy) * 5;
|
|
|
|
if (!_.isUndefined(characterObj.pathfindersociety)) {
|
|
attrs.set_pfs = 1;
|
|
PFHealth.setToPFS(null);
|
|
}
|
|
|
|
if (!_.isUndefined(characterObj.factions.faction)) {
|
|
const faction = _.find(arrayify(characterObj.factions.faction), function (fac) {
|
|
return _.isUndefined(fac._retired);
|
|
});
|
|
if (!_.isUndefined(faction)) {
|
|
attrs.prestige = faction._cpa;
|
|
attrs.fame = faction._tpa;
|
|
attrs.faction_notes = faction._name;
|
|
}
|
|
}
|
|
|
|
setAttrs(attrs, {silent: true});
|
|
}
|
|
export function registerEventHandlers() {
|
|
on('change:herolab_import', function (eventInfo) {
|
|
TAS.debug(`caught ${eventInfo.sourceAttribute} event${eventInfo.sourceType}`);
|
|
if (eventInfo.sourceType !== 'player') return;
|
|
getAttrs(['herolab_import'], function (values) {
|
|
let xmlObj;
|
|
if (_.isUndefined(values.herolab_import)) return;
|
|
try {
|
|
xmlObj = JSON.parse(values.herolab_import);
|
|
if (_.isArray(xmlObj.document.public.character)) importCharacter(xmlObj.document.public.character[0]);
|
|
else importCharacter(xmlObj.document.public.character);
|
|
setAttrs({herolab_import: ''}, {silent: true});
|
|
} catch (err) {
|
|
console.log(err);
|
|
setAttrs({herolab_import: err.message}, {silent: true});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
registerEventHandlers();
|
|
//PFConsole.log(' HLImport module loaded ');
|
|
//PFLog.modulecount++;
|