Skip to content

Instantly share code, notes, and snippets.

@SCPRedMage
Created January 31, 2017 03:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SCPRedMage/9ac2bf758226b2002412da44bebe3ae9 to your computer and use it in GitHub Desktop.
Save SCPRedMage/9ac2bf758226b2002412da44bebe3ae9 to your computer and use it in GitHub Desktop.
HL-import module for Roll20 Pathfinder character sheet
var HLImport = HLImport || (function() {
'use strict';
var parseNum = function(num)
{
if (_.isUndefined(num) || num === "")
return 0;
return (parseInt(num) || 0);
},
buildList = function(objArray, propName) { return _.map(objArray, function (item) { return item[propName]; }).join(", "); },
getSizeMod = function(size)
{
switch(size.toLowerCase())
{
case "colossal":
return -8;
break;
case "gargantuan":
return -4;
break
case "huge":
return -2;
break;
case "large":
return -1;
break;
case "small":
return 1;
break;
case "tiny":
return 2;
break;
case "diminutive":
return 4;
break;
case "fine":
return 8;
break;
default:
return 0;
}
},
// Make sure "stuff" is an array
arrayify = function(stuff)
{
if (_.isUndefined(stuff))
return [];
if (Array.isArray(stuff))
return stuff;
return new Array(stuff);
},
importInit = function(attrs,initObj)
{
attrs["init-misc"] = parseNum(initObj._total)-parseNum(initObj._attrtext);
attrs["init-ability"] = "@{"+initObj._attrname.substr(0,3).toUpperCase()+"-mod}";
attrs["init_notes"] = initObj.situationalmodifiers._text;
},
importAbilityScores = function(attrs,attributes)
{
attributes.forEach(function(abScore) {
var abName = abScore._name.substr(0,3).toUpperCase();
var base = parseNum(abScore.attrvalue._base);
var 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;
// If the modifier is positive, assume it's an enhancement bonus; otherwise, assume it's a penalty
if (modifier > 0)
attrs[abName+"-enhance"] = modifier;
else
attrs[abName+"-penalty"] = modifier;
});
},
importSaves = function(attrs,saves)
{
// Since the XML doesn't break this down by class, add it all to class 0
var i = 0;
var saveNotes = saves.allsaves.situationalmodifiers._text;
for (i = 0; i < saves.save.length; i++)
{
var save = saves.save[i];
var abbr = save._abbr;
attrs["class-0-"+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);
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
getOrMakeRowID = function(featIDList,name)
{
var attrNames = Object.values(featIDList);
var rows = Object.keys(featIDList);
var attrMatch = _.find(attrNames, function(currentAttrName)
{
var 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)
{
var 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
getOrMakeItemRowID = function(featIDList,name)
{
var attrNames = Object.values(featIDList);
var rows = Object.keys(featIDList);
var compareName = name.replace(/\(.*\)/,"").replace(/\+\d+/,"").toLowerCase().replace("masterwork","").trim();
var attrMatch = _.find(attrNames, function(currentAttrName)
{
var 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)
{
var 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
getOrMakeSpellRowID = function(featIDList,name,spellclass)
{
var attrMatch = _.find(featIDList, function(currentFeat)
{
if (currentFeat.name === name && currentFeat.spellclass === spellclass)
return true;
return false;
});
if (!_.isUndefined(attrMatch))
return featIDList[_.indexOf(featIDList,attrMatch)];
return generateRowID();
},
getOrMakeClassRowID = function(featIDList,name)
{
var attrNames = Object.values(featIDList);
var rows = Object.keys(featIDList);
var attrMatch = _.find(attrNames, function(currentAttrName)
{
var 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
name = name.replace(/ x[0-9]+$/,"").replace(/\B\+\d*/g,"").replace(/\(([^\)]+)\)/g,"").replace(/\b\d+d\d+\+*-*\d*\b/g,"").trim();
attrName = attrName.replace(/ x[0-9]+$/,"").replace(/\B\+\d*/g,"").replace(/\(([^\)]+)\)/g,"").replace(/\b\d+d\d+\+*-*\d*\b/g,"").trim();
if (attrName === name)
{
var ID = rows[_.indexOf(attrNames,currentAttrName)];
if (!_.isUndefined(ID))
return true;
}
return false;
});
if (!_.isUndefined(attrMatch))
return rows[_.indexOf(attrNames,attrMatch)];
return generateRowID();
},
importFeats = function(attrs,feats,featIDList,resources)
{
var repeatPrefix = "repeating_npc-spell-like-abilities";
var skipList = [];
var 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
var taken = _.filter(featNames,function(featName) { return featName === feat._name; } ).length;
var 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+"_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
nameIsShield = function(name)
{
if (name.toLowerCase().indexOf("shield") !== -1 || name.toLowerCase().indexOf("klar") !== -1)
return true;
return false;
},
importItems = function(items,resources,armorPenalties,armor,weapons)
{
var repeatPrefix = "repeating_item";
getSectionIDs(repeatPrefix, function(idarray) {
var 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
var shieldACP = parseNum(names["shield3-acp"]);
var 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"];
var itemIDList = _.object(_.map(names,function(name,attr) {
return [attr.substring(repeatPrefix.length+1,(attr.indexOf("_name"))),name];
}));
var itemsList = [];
var attrs = {};
var armorNames = _.map(armor, function(obj) { return obj._name; });
var weaponNames = _.map(weapons, function(obj) { return obj._name; });
// List of words that indicate an item is masterwork
var masterworkWords = ["mithral","adamantine","angelskin","darkleaf","darkwood","dragonhide","eel","fire-forged","frost-forged","greenwood","paueliel"]
_.each(items,function(item)
{
var 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+"_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
var 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)
{
var 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))
{
var weaponObj = weapons[_.indexOf(weaponNames,weaponCompareName)];
attrs[repeatPrefix+"_item-enhance"] = 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))
{
var weaponDice = weaponObj._damage.match(/\d+d\d+/);
if (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))
{
var 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
var 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))
{
var 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-enhance"] = 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-enhance"] = 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);
});
});
},
importTraits = function(attrs,traits,traitIDList,resources)
{
var repeatPrefix = "repeating_npc-spell-like-abilities";
traits.forEach(function(trait)
{
var 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+"_rule_category"] = "traits";
if (_.contains(Object.keys(resources),trait._name))
attrs[repeatPrefix+"_"+row+"_max-calculation"] = resources[trait._name]._max;
});
},
importSLAs = function(attrs,SLAs,SLAsIDList,resources)
{
var repeatPrefix = "repeating_npc-spell-like-abilities";
SLAs.forEach(function(SLA)
{
var 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+"_rule_category"] = "spell-like-abilities";
attrs[repeatPrefix+"_"+row+"_ability_type"] = "Sp";
if (_.contains(Object.keys(resources),SLA._name))
attrs[repeatPrefix+"_"+row+"_max-calculation"] = resources[SLA._name]._max;
});
},
importFeatures = function(attrs,featureList,specials,archetypes,resources)
{
var specNameList = _.map(specials,function(special) { return special._name;});
var skipList = [];
_.each(specials, function(special)
{
var name = special._name;
var repeatPrefix = "repeating_npc-spell-like-abilities",row,classSource = -1;
var cleanName = name.replace(/ x[0-9]+$/,"").replace(/\(([^\)]+)\)/g,"").trim();
if (_.contains(skipList,cleanName))
return;
var multiList = _.filter(specNameList, function(spec) { return (spec.replace(/\(([^\)]+)\)/g,"").trim() === cleanName); });
if (multiList.length > 1)
{
skipList.push(cleanName);
var parenList = _.map(multiList, function(item) { return item.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")",""); });
name = name.replace(/\(([^\)]+)\)/,"("+parenList.join(", ")+")");
}
row = getOrMakeClassRowID(featureList,name);
repeatPrefix = "repeating_npc-spell-like-abilities_" + row;
if (!_.isUndefined(featureList[row]))
delete featureList[row];
else // If we created a new row for this, set rule category
{
// Import if it has a "specsource", assume it's a class feature
if (special.specsource)
attrs[repeatPrefix+"_rule_category"] = "class-features";
else
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"] = special.specsource;
}
if (_.contains(Object.keys(resources),special._name))
attrs[repeatPrefix+"_max-calculation"] = resources[special._name]._max;
});
},
importClasses = function(attrs, classes)
{
var classList = new Object();
var i = 0;
var classObj;
while (i < classes.length)
{
classObj = classes[i];
// We can only handle 5 classes
if (i >= 5)
return;
classList[classObj._name.replace(/\(([^\)]+)\)/g,"").replace("(","").replace(")","").trim()] = classObj;
attrs["class-"+i+"-name"] = classObj._name;
attrs["class-"+i+"-level"] = classObj._level;
i++;
}
return classList;
},
// Import spellclasses; presence in spellclasses node means it's a spellcaster, but some of the data is in the classes node
importSpellClasses = function(attrs, spellclasses,classes,abScores)
{
var spellClassesList = new Object();
var i, j, abMod = 0, currentAbMod, spellslots, spelllevel, casterlevel, concmod, spellpenmod;
var spellClassIndex = 0;
for (i = 0; i < spellclasses.length; i++)
{
var 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();
var 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);
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;
if (!_.isUndefined(classes[spellClassName]._basespelldc))
{
// 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)
{
var attr = {}
attr["Concentration-"+spellClassIndex+"-ability"] = "@{"+abScores[j]._name.substr(0,3).toUpperCase()+"-mod}";
setAttrs(attr);
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;
}
// 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))
{
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);
if (spelllevel > 0)
spellslots = spellslots - bonusSpellSlots(abMod,spelllevel);
attrs["spellclass-"+spellClassIndex+"-level-"+spelllevel+"-class"] = spellslots;
}
}
}
spellClassesList[spellClassName] = classes[Object.keys(classes)[classIndex]];
spellClassIndex++;
}
}
return spellClassesList;
},
importSpells = function(spells,spellclasses)
{
console.log("Import spells");
var repeatPrefix = "repeating_spells";
getSectionIDs(repeatPrefix, function(idarray) {
var spellNameAttrs = _.union(_.map(idarray,function(id) { return repeatPrefix+"_"+id+"_name"; }),_.map(idarray,function(id) { return repeatPrefix+"_"+id+"_spellclass_number"; }));
getAttrs(spellNameAttrs, function(spellAttrs) {
var spellObjList = {};
var spellKeys = Object.keys(spellAttrs);
_.each(spellKeys,function(spellKey) {
var rowID;
if (spellKey.indexOf("_name") !== -1)
{
rowID = spellKey.substring(repeatPrefix.length+1,(spellKey.indexOf("_name")));
if (_.isUndefined(spellObjList[rowID]))
spellObjList[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] = {};
spellObjList[rowID].spellclass = spellAttrs[spellKey];
}
});
var spellClassesKeys = Object.keys(spellclasses);
var attrs = {};
_.each(spells, function(spell) {
var rowID, spellClass, spellName, school, 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_";
spellClass = _.indexOf(spellClassesKeys,spell._class);
spellName = spell._name.replace(/\(x\d+\)/,"").trim();
rowID = getOrMakeSpellRowID(spellObjList,spellName,spellClass);
// Update prefix with ID
repeatPrefix = repeatPrefix + rowID;
attrs[repeatPrefix+"_name"] = spellName;
attrs[repeatPrefix+"_spell_level"] = level;
attrs[repeatPrefix+"_spellclass_number"] = spellClass;
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+"_range"] = spell._range;
attrs[repeatPrefix+"_duration"] = spell._duration;
attrs[repeatPrefix+"_save"] = spell._save.replace(/DC \d+/,"").trim();
attrs[repeatPrefix+"_cast-time"] = spell._casttime;
attrs[repeatPrefix+"_sr"] = spell._resist.replace("harmless","Harmless");
attrs[repeatPrefix+"_DC_misc"] = parseNum(spell._dc) - parseNum(spellclasses[(spell._class !== "") ? spell._class:Object.keys(spellclasses)[0]]._basespelldc) - level;
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);
});
});
/*var i, rowID, spellClass, spellName, school, level;
var spellClassesKeys = Object.keys(spellclasses);
for (i = 0; i < spells.length; i++)
{
// Search for a repeating spell with the same level, name, and spellclass; if not found, make new row
level = parseNum(spells[i]._level);
repeatPrefix = "repeating_spells_";
spellClass = _.indexOf(spellClassesKeys,spells[i]._class);
spellName = spells[i]._name.replace(/\(x\d+\)/,"").trim();
rowID = getOrMakeSpellRowID(character,repeatPrefix,spellName,spellClass);
// Update prefix with ID
repeatPrefix = repeatPrefix + rowID;
setAttr(character,repeatPrefix+"_name",spellName);
setAttr(character,repeatPrefix+"_spell_level",level);
setAttr(character,repeatPrefix+"_spellclass_number",spellClass);
setAttr(character,repeatPrefix+"_components",spells[i]._componenttext.replace("Divine Focus", "DF").replace("Focus","F").replace("Material","M").replace("Verbal","V").replace("Somatic","S").replace(" or ","/"));
setAttr(character,repeatPrefix+"_range",spells[i]._range);
setAttr(character,repeatPrefix+"_duration",spells[i]._duration);
setAttr(character,repeatPrefix+"_save",spells[i]._save.replace(/DC \d+/,"").trim());
setAttr(character,repeatPrefix+"_cast-time",spells[i]._casttime);
setAttr(character,repeatPrefix+"_sr",spells[i]._resist.replace("harmless","Harmless"));
setAttr(character,repeatPrefix+"_DC_misc",parseNum(spells[i]._dc) - parseNum(spellclasses[(spells[i]._class !== "") ? spells[i]._class:Object.keys(spellclasses)[0]]._basespelldc) - level);
if (spells[i]._area !== "")
setAttr(character,repeatPrefix+"_targets",spells[i]._area);
else if (spells[i]._effect !== "")
setAttr(character,repeatPrefix+"_targets",spells[i]._effect);
else
setAttr(character,repeatPrefix+"_targets",spells[i]._target);
school = spells[i]._schooltext;
if (spells[i]._subschooltext !== "")
school = school + " (" + spells[i]._subschooltext + ")";
if (spells[i]._descriptortext !== "")
school = school + " [" + spells[i]._descriptortext + "]";
setAttr(character,repeatPrefix+"_school",school);
setAttr(character,repeatPrefix+"_description",spells[i].description);
}*/
},
calcHitDice = function(hitdice)
{
var dice = hitdice.match(/\d+d\d/g);
var numDice = 0;
var i = 0;
while (i < dice.length)
{
numDice += parseInt(dice[i].split("d")[0]);
i++;
}
return numDice;
},
buildArchetypeArray = function(classes)
{
var archetypes = new Object();
_.each(classes, function (classObj, className) {
if (classObj._name.indexOf("(") === -1)
{
archetypes[className] = [];
return;
}
var archeString = classObj._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
var 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
getClassSource = function(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
var classes = Object.keys(archetypes);
// Check if source is a class, first
var 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
var className = _.find(classes, function(item) { return (_.intersection(archetypes[item],sources).length); });
if (className)
return classes.indexOf(className);
return -1;
},
bonusSpellSlots = function(abilMod,spellLevel) { return Math.max(0, Math.floor((abilMod + 4 - spellLevel) / 4)); },
importSkills = function(attrs,skills,size,ACP)
{
// Ripped from the PF character sheet JS
var 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=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
var craft = 1;
var perform = 1;
var profession = 1;
var artistry = 1;
var lore = 1;
var misc = 0;
var i = 0;
var skill;
var skillMisc;
var skillAttrPrefix;
for (i = 0; i < skills.length; i++)
{
/*if (_.isUndefined(skill._name))
{
continue;
}*/
skill = skills[i];
console.log(skill._name);
// 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 <= 3)
{
skillAttrPrefix = "craft" + craft;
if (skill._name.match(/\(([^\)]+)\)/) !== null)
attrs["craft"+craft+"-name"] = skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
craft++;
}
else
{
if (misc <= 5)
{
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 <= 3)
{
skillAttrPrefix = "perform" + perform;
if (skill._name.match(/\(([^\)]+)\)/) !== null)
attrs["perform"+perform+"-name"] = skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
perform++;
}
else
{
if (misc <= 5)
{
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 <= 3)
{
skillAttrPrefix = "profession" + profession;
if (skill._name.match(/\(([^\)]+)\)/) !== null)
attrs["profession"+profession+"-name"] = skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
profession++;
}
else
{
if (misc <= 5)
{
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 <= 3)
{
skillAttrPrefix = "artistry" + artistry;
attrs["artistry"+artistry+"-name"] = skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
artistry++;
}
else
{
if (misc <= 5)
{
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 <= 3)
{
skillAttrPrefix = "lore" + lore;
attrs["lore"+lore+"-name"] = skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","");
lore++;
}
else
{
if (misc <= 5)
{
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+"-ranks"] = parseNum(skill._ranks);
attrs[skillAttrPrefix+"-ability"] = "@{"+skill._attrname+"-mod}";
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))
{
var macro = "@{PC-whisper} &{template:pf_generic} {{color=@{rolltemplate_color}}} {{header_image=@{header_image-pf_generic-skill}}} @{toggle_rounded_flag} {{character_name=@{character_name}}} {{character_id=@{character_id}}} {{subtitle}} {{name="+skill._name+"}} {{Check=[[ @{skill-query} + [[ @{"+skillAttrPrefix+"} ]] ]]}}";
skill.situationalmodifiers.situationalmodifier = arrayify(skill.situationalmodifiers.situationalmodifier);
var j = 0;
while (j < skill.situationalmodifiers.situationalmodifier.length)
{
macro = macro + " {{" + skill.situationalmodifiers.situationalmodifier[j]._source + "=" + skill.situationalmodifiers.situationalmodifier[j]._text+"}}"
j++;
}
attrs[skillAttrPrefix+"-macro"] = macro;
}
}
},
// Import ACP and Max Dex; these aren't included under items, but the final values are listed in penalties
importPenalties = function(attrs,penalties)
{
var ACP = 0;
var 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;
},
importAC = function(attrs,acObj)
{
attrs["AC-natural"] = parseNum(acObj._fromnatural);
attrs["AC-deflect"] = parseNum(acObj._fromdeflect);
attrs["AC-dodge"] = parseNum(acObj._fromdodge);
// Are we replacing Dex to AC with something else?
if (acObj._fromdexterity === "")
{
if (acObj._fromcharisma !== "")
{
attrs["AC-ability"] = "( ((@{CHA-mod} + [[ @{max-dex-source} ]]) - abs(@{CHA-mod} - [[ @{max-dex-source} ]])) / 2 )";
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} + [[ @{max-dex-source} ]]) - abs(@{WIS-mod} - [[ @{max-dex-source} ]])) / 2 )";
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-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);
}
},
importCharacter = function(characterObj)
{
var attrs = {};
importAbilityScores(attrs,characterObj.attributes.attribute);
importSaves(attrs,characterObj.saves);
var classes, spellClasses, 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);
// If any of the character's classes is a spellcaster, it'll be listed here, too
if (!_.isUndefined(characterObj.spellclasses.spellclass))
{
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!
if (!_.isUndefined(characterObj.spellsknown.spell))
{
characterObj.spellsknown.spell = arrayify(characterObj.spellsknown.spell);
importSpells(characterObj.spellsknown.spell,spellClasses);
}
if (!_.isUndefined(characterObj.spellbook.spell))
{
characterObj.spellbook.spell = arrayify(characterObj.spellbook.spell);
importSpells(characterObj.spellbook.spell,spellClasses);
}
if (!_.isUndefined(characterObj.spellsmemorized.spell))
{
characterObj.spellsmemorized.spell = arrayify(characterObj.spellsmemorized.spell);
importSpells(characterObj.spellsmemorized.spell,spellClasses);
}
}
// Need to keep track of what archetypes the character has, since class feature source could be an archetype
archetypes = buildArchetypeArray(classes);
}
importAC(attrs,characterObj.armorclass);
characterObj.penalties.penalty = arrayify(characterObj.penalties.penalty);
var 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
var armorPenalties = {};
armorPenalties.ACP = parseNum(attrs["armor3-acp"]);
armorPenalties.maxDex = parseNum(attrs["armor3-max-dex"]);
armorPenalties.spellfail = parseNum(attrs["armor3-spell-fail"]);
// 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"];
var armor = _.reject(arrayify(characterObj.defenses.armor || {}),function(item) { return _.isUndefined(item._name); });
var 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
var resources = _.object(_.map(characterObj.trackedresources.trackedresource, function (resource) { return [resource._name,resource];}));
// Make an array of items, both magic and mundane
var 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
var itemNames = _.map(items, function(obj) { return obj._name; });
var specials = _.reject(arrayify(characterObj.attack.special).concat(arrayify(characterObj.defenses.special),arrayify(characterObj.otherspecials.special),arrayify(characterObj.spelllike.special)), function(obj) { return _.contains(itemNames, obj._name); });
importItems(items,resources,armorPenalties,armor,weapons);
getSectionIDs("repeating_npc-spell-like-abilities", function(idarray) {
var abilityNameAttrs = _.union(_.map(idarray,function(id) { return "repeating_npc-spell-like-abilities_"+id+"_name"; }),_.map(idarray,function(id) { return "repeating_npc-spell-like-abilities_"+id+"_rule_category"; }));
getAttrs(abilityNameAttrs, function(abilityAttrs) {
var abilityObjList = {};
var abilityKeys = Object.keys(abilityAttrs);
var asyncAttrs = {};
_.each(abilityKeys,function(abilityKey) {
var rowID;
if (abilityKey.indexOf("_name") !== -1)
{
rowID = abilityKey.substring("repeating_npc-spell-like-abilities_".length,(abilityKey.indexOf("_name")));
if (_.isUndefined(abilityObjList[rowID]))
abilityObjList[rowID] = {};
abilityObjList[rowID].name = abilityAttrs[abilityKey];
}
if (abilityKey.indexOf("_rule_category") !== -1)
{
rowID = abilityKey.substring("repeating_npc-spell-like-abilities_".length,(abilityKey.indexOf("_rule_category")));
if (_.isUndefined(abilityObjList[rowID]))
abilityObjList[rowID] = {};
abilityObjList[rowID].rulecategory= abilityAttrs[abilityKey];
}
});
_.each(Object.keys(abilityObjList),function(abilityKey) { abilityObjList[abilityKey].id = abilityKey; });
if (!_.isUndefined(characterObj.feats.feat))
{
var featsArray = _.filter(abilityObjList,_.matcher({rulecategory:"feats"}));
var featsList = {};
_.each(featsArray, function(obj){ featsList[obj.id] = obj.name; });
characterObj.feats.feat = arrayify(characterObj.feats.feat);
importFeats(asyncAttrs, characterObj.feats.feat, featsList, resources);
}
if (!_.isUndefined(characterObj.traits.trait))
{
var traitsArray = _.filter(abilityObjList,_.matcher({rulecategory:"traits"}));
var traitsList = {};
_.each(traitsArray, function(obj){ traitsList[obj.id] = obj.name; });
characterObj.traits.trait = arrayify(characterObj.traits.trait);
importTraits(asyncAttrs, characterObj.traits.trait, traitsList, resources);
}
if (!_.isUndefined(characterObj.spelllike.special))
{
var SLAsArray = _.filter(abilityObjList,_.matcher({rulecategory:"spell-like-abilities"}));
var SLAsList = {};
_.each(SLAsArray, function(obj){ SLAsList[obj.id] = obj.name; });
characterObj.spelllike.special = arrayify(characterObj.spelllike.special);
importSLAs(asyncAttrs, characterObj.spelllike.special, SLAsList, resources);
}
var featuresArray = _.filter(abilityObjList, function (obj) { if (obj.rulecategory === "traits" || obj.rulecategory === "feats") return false; return true; });
var featuresList = {};
_.each(featuresArray, function(obj){ featuresList[obj.id] = obj.name; });
importFeatures(asyncAttrs, featuresList, specials, archetypes, resources);
setAttrs(asyncAttrs);
});
});
attrs["experience"] = parseFloat(characterObj.xp._total);
attrs["class-0-bab"] = 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
var level = calcHitDice(characterObj.health._hitdice);
attrs["class-0-hp"] = (parseNum(characterObj.health._hitpoints) - (level * parseNum(characterObj.attributes.attribute[2].attrbonus._modified)));
importInit(attrs,characterObj.initiative);
var racialHD = level - parseNum(characterObj.classes._level);
if (racialHD > 0)
attrs["npc-hd-num"] = racialHD;
var size = getSizeMod(characterObj.size._name);
attrs["size"] = size;
attrs["default_char_size"] = size;
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["character_name"] = characterObj._name;
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;
//attrs["npc-cr"] = characterObj.challengerating._text.replace("CR ","");
//attrs["npc-xp"] = characterObj.xpaward._value;
if (!_.isUndefined(characterObj.favoredclasses.favoredclass))
{
characterObj.favoredclasses.favoredclass = arrayify(characterObj.favoredclasses.favoredclass);
attrs["class-favored"] = buildList(characterObj.favoredclasses.favoredclass, "_name");
}
setAttrs(attrs);
},
registerEventHandlers = function() {
on("change:herolab_import", function(eventInfo) {
getAttrs(["herolab_import"], function(values) {
var 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();
console.log(PFLog.l + ' HLImport module loaded ' + PFLog.r, PFLog.bg);
PFLog.modulecount++;
return {
importCharacter: importCharacter
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment