Last active
January 4, 2017 03:36
-
-
Save SCPRedMage/7cef64189658c80ffbe60fa35e933a6c to your computer and use it in GitHub Desktop.
Roll20 Hero Lab Pathfinder import script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var HLImport = HLImport || (function() { | |
'use strict'; | |
var version = '0.8', | |
lastUpdate = 1483500397, | |
skillsAttr = ["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", "acrobatics-macro", "artistry-macro", "appraise-macro", "bluff-macro", "climb-macro", "craft-macro", "craft2-macro", "craft3-macro", "diplomacy-macro", "disable-Device-macro", "disguise-macro", "escape-Artist-macro", "fly-macro", "handle-Animal-macro", "heal-macro", "intimidate-macro", "linguistics-macro", "lore-macro", "knowledge-arcana-macro", "knowledge-dungeoneering-macro", "knowledge-engineering-macro", "knowledge-geography-macro", "knowledge-history-macro", "knowledge-local-macro", "knowledge-nature-macro", "knowledge-nobility-macro", "knowledge-planes-macro", "knowledge-religion-macro", "perception-macro", "perform-macro", "perform2-macro", "perform3-macro", "profession-macro", "profession2-macro", "profession3-macro", "ride-macro", "sense-Motive-macro", "sleight-of-Hand-macro", "spellcraft-macro", "stealth-macro", "survival-macro", "swim-macro", "use-magic-device-macro", "misc-skill-0-macro", "misc-skill-1-macro", "misc-skill-2-macro", "misc-skill-3-macro", "misc-skill-4-macro", "misc-skill-5-macro"], | |
regexIndexOf = function(str, regex, startpos) { | |
var indexOf = str.substring(startpos || 0).search(regex); | |
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf; | |
}, | |
generateUUID = (function() { | |
var a = 0, b = []; | |
return function() { | |
var c = (new Date()).getTime() + 0, d = c === a; | |
a = c; | |
for (var e = new Array(8), f = 7; 0 <= f; f--) { | |
e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); | |
c = Math.floor(c / 64); | |
} | |
c = e.join(""); | |
if (d) { | |
for (f = 11; 0 <= f && 63 === b[f]; f--) { | |
b[f] = 0; | |
} | |
b[f]++; | |
} else { | |
for (f = 0; 12 > f; f++) { | |
b[f] = Math.floor(64 * Math.random()); | |
} | |
} | |
for (f = 0; 12 > f; f++){ | |
c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); | |
} | |
return c; | |
}; | |
}()), | |
generateRowID = function () { | |
"use strict"; | |
return generateUUID().replace(/_/g, "Z"); | |
}, | |
buildList = function (objArray, propName) { return _.map(objArray, function (item) { return item[propName]; }).join(", "); }, | |
setAttr = function (character,attribute,value,max) | |
{ | |
if (_.isUndefined(value)) | |
{ | |
log (attribute+" value undefined!"); | |
return; | |
} | |
var lowerCaseNames = _.map(Object.keys(character.attrs, function(key) { return key.toLowerCase(); })); | |
if (_.contains(lowerCaseNames,attribute.toLowerCase())) | |
character.attrs[Object.keys(character.attrs)[_.indexOf(lowerCaseNames,attribute.toLowerCase())]].set( (max) ? "max":"current",value); | |
else | |
{ | |
var obj = { name: attribute, _characterid: character.character.get("_id") }; | |
obj[(max) ? "max":"current"] = value; | |
character.add(attribute, createObj("attribute", obj)); | |
} | |
}, | |
parseNum = function (num) | |
{ | |
if (_.isUndefined(num) || num === "") | |
return 0; | |
return parseInt(num); | |
}, | |
importSaves = function (character,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; | |
setAttr(character,"class-0-"+abbr,parseNum(save._base)); | |
setAttr(character,abbr+"-resist",parseNum(save._fromresist)); | |
setAttr(character,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; | |
} | |
setAttr(character,"Save-notes",saveNotes.trim()); | |
}, | |
importAbilityScores = function (character,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 | |
setAttr(character, abName+"-base", base); | |
// If the modifier is positive, assume it's an enhancement bonus; otherwise, assume it's a penalty | |
if (modifier > 0) | |
setAttr(character, abName+"-enhance", modifier); | |
else | |
setAttr(character, abName+"-penalty", modifier); | |
}); | |
}, | |
importClasses = function (character, 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; | |
setAttr(character,"class-"+i+"-name",classObj._name); | |
setAttr(character,"class-"+i+"-level",classObj._level); | |
i++; | |
} | |
return classList; | |
}, | |
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; | |
} | |
}, | |
importInit = function(character,initObj) | |
{ | |
setAttr(character,"init-misc",parseNum(initObj._total)-parseNum(initObj._attrtext)); | |
setAttr(character,"init-ability","@{"+initObj._attrname.substr(0,3).toUpperCase()+"-mod}"); | |
setAttr(character,"init_notes",initObj.situationalmodifiers._text); | |
}, | |
// Calculate number of bonus slots from spell level and ability modifier | |
bonusSpellSlots = function(abilMod,spellLevel) { return Math.max(0, Math.floor((abilMod + 4 - spellLevel) / 4)); }, | |
// Find an existing repeatable item with the same name, or generate new row ID | |
getOrMakeRowID = function(character,repeatPrefix,name,newList) | |
{ | |
if (_.isUndefined(newList)) | |
newList = []; | |
// Get list of all of the character's attributes, filtered to just the name attributes for rows matching the repeatPrefix | |
var attrNames = _.filter(Object.keys(character.attrs), function(obj) { if (obj.indexOf(repeatPrefix) !== -1 && obj.indexOf("_name") !== -1) return true; return false;}); | |
var attrMatch = _.find(attrNames, function(currentAttrName) | |
{ | |
var attrName = character.attrs[currentAttrName].get("current").toString(); | |
// 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 = currentAttrName.substring(repeatPrefix.length,(currentAttrName.indexOf("_name"))); | |
if (!_.isUndefined(ID) && !_.contains(newList,ID)) | |
return true; | |
} | |
return false; | |
}); | |
if (!_.isUndefined(attrMatch)) | |
return attrMatch.substring(repeatPrefix.length,(attrMatch.indexOf("_name"))); | |
return generateRowID(); | |
}, | |
getOrMakeClassRowID = function(character,repeatPrefix,name,newList) | |
{ | |
// Get list of all of the character's attributes | |
// Get list of all of the character's attributes, filtered to just the name attributes for rows matching the repeatPrefix | |
var attrNames = _.filter(Object.keys(character.attrs), function(obj) { if (obj.indexOf(repeatPrefix) !== -1 && obj.indexOf("_name") !== -1) return true; return false;}); | |
var attrMatch = _.find(attrNames, function(currentAttrName) | |
{ | |
var attrName = character.attrs[currentAttrName].get("current").toString(); | |
// 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 = currentAttrName.substring(repeatPrefix.length,(currentAttrName.indexOf("_name"))); | |
if (!_.isUndefined(ID) && !_.contains(newList,ID)) | |
return true; | |
} | |
return false; | |
}); | |
if (!_.isUndefined(attrMatch)) | |
return attrMatch.substring(repeatPrefix.length,(attrMatch.indexOf("_name"))); | |
return generateRowID(); | |
}, | |
// Find an existing repeatable item with the same name and spellclass, or generate new row ID | |
getOrMakeSpellRowID = function(character,repeatPrefix,name,spellclass) | |
{ | |
// Get list of all of the character's attributes, filtered to just the name attributes for rows matching the repeatPrefix | |
var attrNames = _.filter(Object.keys(character.attrs), function(obj) { if (obj.indexOf(repeatPrefix) !== -1 && obj.indexOf("_name") !== -1) return true; return false;}); | |
var ID, objSpellclass; | |
var attrMatch = _.find(attrNames, function(currentAttrName) | |
{ | |
var attrName = character.attrs[currentAttrName].get("current").toString(); | |
if (attrName === name) | |
{ | |
ID = currentAttrName.substring(repeatPrefix.length,(currentAttrName.indexOf("_name"))); | |
if (_.contains(attrNames,repeatPrefix+ID+"_spellclass")) | |
objSpellclass = parseInt(character.attrs[repeatPrefix+ID+"_spellclass"].get("current")); | |
else | |
objSpellclass = "@{spellclass-0-name}"; | |
log ("Spellclass: " + objSpellclass + " vs " + spellclass + ": " + (objSpellclass == spellclass)); | |
if (objSpellclass == spellclass) | |
return true; | |
} | |
return false; | |
}); | |
if (!_.isUndefined(attrMatch)) | |
return attrMatch.substring(repeatPrefix.length,(attrMatch.indexOf("_name"))); | |
return generateRowID(); | |
}, | |
importFeats = function(character,feats) | |
{ | |
var skipList = [], newList = []; | |
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(character,"repeating_feat_",feat._name,newList); | |
if (taken > 1) | |
setAttr(character,"repeating_feat_"+row+"_name",feat._name + " x" + taken); | |
else | |
setAttr(character,"repeating_feat_"+row+"_name",feat._name); | |
setAttr(character,"repeating_feat_"+row+"_description",feat.description); | |
skipList.push(feat._name); | |
newList.push(row); | |
}); | |
}, | |
importTraits = function(character,traits) | |
{ | |
traits.forEach(function(trait) | |
{ | |
var row = getOrMakeRowID(character,"repeating_trait_",trait._name); | |
setAttr(character,"repeating_trait_"+row+"_name",trait._name); | |
setAttr(character,"repeating_trait_"+row+"_description",trait.description); | |
}); | |
}, | |
clearSkills = function(character) | |
{ | |
// Get list of all of the character's attributes | |
var attrObjs = Object.keys(character.attrs); | |
attrObjs.forEach(function(attr) { | |
if (_.contains(skillsAttr,attr.toLowerCase())) | |
character.remove(attr); | |
}); | |
}, | |
safeAttrValue = function(attr) | |
{ | |
if (attr == "undefined") | |
return ""; | |
else | |
return attr; | |
}, | |
importSkills = function(character,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 | |
clearSkills(character); | |
// 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++) | |
{ | |
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) | |
setAttr(character,"craft-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
craft++; | |
} | |
else if (craft <= 3) | |
{ | |
skillAttrPrefix = "craft" + craft; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,"craft"+craft+"-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
craft++; | |
} | |
else | |
{ | |
if (misc <= 5) | |
{ | |
skillAttrPrefix = "misc-skill-" + misc; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
else | |
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) | |
setAttr(character,"perform-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
perform++; | |
} | |
else if (perform <= 3) | |
{ | |
skillAttrPrefix = "perform" + perform; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,"perform"+perform+"-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
perform++; | |
} | |
else | |
{ | |
if (misc <= 5) | |
{ | |
skillAttrPrefix = "misc-skill-" + misc; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
else | |
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) | |
setAttr(character,"profession-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
profession++; | |
} | |
else if (profession <= 3) | |
{ | |
skillAttrPrefix = "profession" + profession; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,"profession"+profession+"-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
profession++; | |
} | |
else | |
{ | |
if (misc <= 5) | |
{ | |
skillAttrPrefix = "misc-skill-" + misc; | |
if (skill._name.match(/\(([^\)]+)\)/) !== null) | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
else | |
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; | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
} | |
else if (skill._name.indexOf("Artistry") !== -1) | |
{ | |
if (artistry === 1) | |
{ | |
skillAttrPrefix = "artistry"; | |
setAttr(character,"artistry-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
artistry++; | |
} | |
else if (artistry <= 3) | |
{ | |
skillAttrPrefix = "artistry" + artistry; | |
setAttr(character,"artistry"+artistry+"-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
artistry++; | |
} | |
else | |
{ | |
if (misc <= 5) | |
{ | |
skillAttrPrefix = "misc-skill-" + misc; | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
else | |
log("Ran out of misc skills for " + skill._name + "!"); | |
} | |
} | |
else if (skill._name.indexOf("Lore") !== -1) | |
{ | |
if (lore === 1) | |
{ | |
skillAttrPrefix = "lore"; | |
setAttr(character,"lore-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
lore++; | |
} | |
else if (lore <= 3) | |
{ | |
skillAttrPrefix = "lore" + lore; | |
setAttr(character,"lore"+lore+"-name",skill._name.match(/\(([^\)]+)\)/)[0].replace("(","").replace(")","")); | |
lore++; | |
} | |
else | |
{ | |
if (misc <= 5) | |
{ | |
skillAttrPrefix = "misc-skill-" + misc; | |
setAttr(character,skillAttrPrefix+"-name",skill._name); | |
misc++; | |
} | |
else | |
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"); | |
setAttr(character,skillAttrPrefix+"-ranks",parseNum(skill._ranks)); | |
setAttr(character,skillAttrPrefix+"-ability","@{"+skill._attrname+"-mod}"); | |
if (skill._classskill === "yes") setAttr(character,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); | |
setAttr(character,skillAttrPrefix+"-misc",skillMisc); | |
if (skill._trainedonly === "yes") setAttr(character,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++; | |
} | |
setAttr(character,skillAttrPrefix+"-macro",macro); | |
} | |
} | |
}, | |
// Import ACP and Max Dex; these aren't included under items, but the final values are listed in penalties | |
importPenalties = function (character,penalties) | |
{ | |
var ACP = 0; | |
var i = 0; | |
while (i < penalties.length) | |
{ | |
if (penalties[i]._name === "Armor Check Penalty") | |
{ | |
ACP = parseNum(penalties[i]._value); | |
setAttr(character,"armor3-acp",ACP); | |
} | |
else if (penalties[i]._name === "Max Dex Bonus") | |
setAttr(character,"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; | |
}, | |
// Make sure "stuff" is an array | |
arrayify = function(stuff) | |
{ | |
if (_.isUndefined(stuff)) | |
return []; | |
if (Array.isArray(stuff)) | |
return stuff; | |
return new Array(stuff); | |
}, | |
// Import spellclasses; presence in spellclasses node means it's a spellcaster, but some of the data is in the classes node | |
importSpellClasses = function (character,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); | |
setAttr(character,"spellclass-"+spellClassIndex,classIndex); | |
setAttr(character,"spellclass-"+spellClassIndex+"-level-misc", casterlevel - parseNum(classes[spellClassName]._level)); | |
if (!_.isUndefined(classes[spellClassName].arcanespellfailure)) | |
setAttr(character,"armor3-spell-fail",parseNum(classes[spellClassName].arcanespellfailure._value)); | |
// Make a guess at which ability modifier is used for this class... unless it's already been set | |
currentAbMod = getAttrByName(character.character.get("_id"),"Concentration-"+spellClassIndex+"-ability"); | |
if (!_.isUndefined(classes[spellClassName]._basespelldc)) | |
abMod = parseNum(classes[spellClassName]._basespelldc) - 10; | |
if (!currentAbMod && !_.isUndefined(classes[spellClassName]._basespelldc)) | |
{ | |
// Start at the fourth ability score (Intelligence), so we skip the physical abilities | |
for (j = 3; j < abScores.length; j++) | |
{ | |
log (abMod+" vs "+parseNum(abScores[j].attrbonus._modified)); | |
if (parseNum(abScores[j].attrbonus._modified) === abMod) | |
{ | |
log("Guess found, setting to " + abScores[j]._name); | |
setAttr(character,"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; | |
setAttr(character,"Concentration-"+spellClassIndex+"-misc",concmod); | |
} | |
// Calculate misc mods to spell penetration | |
if (!_.isUndefined(classes[spellClassName].overcomespellresistance)) | |
{ | |
spellpenmod = parseNum(classes[spellClassName].overcomespellresistance) - casterlevel; | |
setAttr(character,"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); | |
setAttr(character,"spellclass-"+spellClassIndex+"-level-"+spelllevel+"-class",spellslots); | |
} | |
} | |
} | |
spellClassesList[spellClassName] = classes[Object.keys(classes)[classIndex]]; | |
spellClassIndex++; | |
} | |
} | |
return spellClassesList; | |
}, | |
importSpells = function(character,spells,spellclasses) | |
{ | |
var i, rowID, repeatPrefix, 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 = "@{spellclass-"+_.indexOf(spellClassesKeys,spells[i]._class)+"-name}"; | |
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",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; | |
}, | |
importFeatures = function(character,specials,archetypes,resources) | |
{ | |
var specNameList = _.map(specials,function(special) { return special._name;}); | |
var skipList = [], newList = []; | |
_.each(specials, function(special) { | |
var name = special._name; | |
var repeatPrefix,id,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(", ")+")"); | |
} | |
// Import if it has a "specsource", assume it's a class feature | |
if (special.specsource) | |
repeatPrefix = "repeating_class-ability_"; | |
else | |
repeatPrefix = "repeating_racial-trait_"; | |
log(name + ": " + repeatPrefix); | |
classSource = getClassSource(arrayify(special.specsource),archetypes); | |
id = getOrMakeClassRowID(character,repeatPrefix,name,newList); | |
newList.push(id); | |
repeatPrefix = repeatPrefix + id; | |
setAttr(character, repeatPrefix+"_name",name); | |
setAttr(character, repeatPrefix+"_description", special.description); | |
if (classSource !== -1) | |
setAttr(character, repeatPrefix+"_class-number","@{class-"+classSource+"-name}"); | |
if (_.contains(Object.keys(resources),special._name)) | |
setAttr(character,repeatPrefix+"_max-calculation",resources[special._name]._max); | |
}); | |
}, | |
importItems = function(character,items,resources) | |
{ | |
var newList = [], itemsList = []; | |
_.each(items,function(item) { | |
var name = item._name; | |
itemsList.push(name); | |
var repeatPrefix = "repeating_item_"; | |
var row = getOrMakeRowID(character,repeatPrefix,name,newList); | |
repeatPrefix = repeatPrefix + row; | |
newList.push(row); | |
setAttr(character,repeatPrefix+"_name",name); | |
setAttr(character,repeatPrefix+"_weight",item.weight._value); | |
setAttr(character,repeatPrefix+"_value",(parseFloat(item.cost._value) / parseInt(item._quantity) )); | |
setAttr(character,repeatPrefix+"_description",item.description); | |
if (_.contains(Object.keys(resources),name) && item._quantity === "1" && resources[name]._max !== "1") | |
{ | |
setAttr(character,repeatPrefix+"_qty",resources[name]._left); | |
setAttr(character,repeatPrefix+"_qty",resources[name]._max,true); | |
} | |
else | |
setAttr(character,repeatPrefix+"_qty",item._quantity); | |
if (!_.isUndefined(item.itempower)) | |
_.each(arrayify(item.itempower), function(itemPower) { itemsList.push(itemPower._name); }); | |
}); | |
return itemsList; | |
}, | |
importCharacter = function (characterObj,token,passedCharacter) | |
{ | |
sendChat("HL-Import","Importing **"+characterObj._name+"**..."); | |
var R20character; | |
if (passedCharacter !== null) | |
{ | |
R20character = passedCharacter; | |
passedCharacter.set("name",characterObj._name); | |
} | |
else | |
{ | |
R20character = findObjs({ _type: "character", name: characterObj._name }); | |
if (R20character.length === 0) | |
R20character = createObj("character", { name: characterObj._name }); | |
else | |
R20character = R20character[0]; | |
} | |
R20character = { character: R20character, attrs: _.object(_.map(findObjs({ _type: "attribute", _characterid: R20character.get("_id") }), function(obj) { return [obj.get("name"), obj]; })), add: function(attrName, attrObj) { this.attrs[attrName] = attrObj; }, remove: function(attr) { this.attrs[attr].remove(); delete this.attrs[attr]; } }; | |
importAbilityScores(R20character,characterObj.attributes.attribute); | |
importSaves(R20character,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(R20character,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(R20character,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(R20character,characterObj.spellsknown.spell,spellClasses); | |
} | |
if (!_.isUndefined(characterObj.spellsmemorized.spell)) | |
{ | |
characterObj.spellsmemorized.spell = arrayify(characterObj.spellsmemorized.spell); | |
importSpells(R20character,characterObj.spellsmemorized.spell,spellClasses); | |
} | |
if (!_.isUndefined(characterObj.spellbook.spell)) | |
{ | |
characterObj.spellbook.spell = arrayify(characterObj.spellbook.spell); | |
importSpells(R20character,characterObj.spellbook.spell,spellClasses); | |
} | |
} | |
// Need to keep track of what archetypes the character has, since class feature source could be an archetype | |
archetypes = buildArchetypeArray(classes); | |
} | |
var resources = _.object(_.map(characterObj.trackedresources.trackedresource, function (resource) { return [resource._name,resource];})); | |
var items = arrayify(characterObj.magicitems.item || {}).concat(arrayify(characterObj.gear.item || {})); | |
var itemsList = importItems(R20character,_.reject(items,function(item) { return _.isUndefined(item._name); }),resources); | |
var specials = arrayify(characterObj.attack.special).concat(arrayify(characterObj.defenses.special),arrayify(characterObj.otherspecials.special),arrayify(characterObj.spelllike.special)); | |
importFeatures(R20character, _.reject(specials, function(special) { return (_.contains(itemsList,special._name)); }), archetypes, resources); | |
setAttr(R20character,"experience",parseFloat(characterObj.xp._total)); | |
setAttr(R20character,"class-0-bab",parseNum(characterObj.attack._baseattack)); | |
if (!_.isUndefined(characterObj.feats.feat)) | |
{ | |
characterObj.feats.feat = arrayify(characterObj.feats.feat); | |
importFeats(R20character,characterObj.feats.feat); | |
} | |
if (!_.isUndefined(characterObj.traits.trait)) | |
{ | |
characterObj.traits.trait = arrayify(characterObj.traits.trait); | |
importTraits(R20character,characterObj.traits.trait); | |
} | |
// Set max hp; remove hp from Con mod 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); | |
setAttr(R20character,"class-0-hp",(parseNum(characterObj.health._hitpoints) - (level * parseNum(characterObj.attributes.attribute[2].attrbonus._modified)))); | |
importInit(R20character,characterObj.initiative); | |
var racialHD = level - parseNum(characterObj.classes._level); | |
if (racialHD > 0) | |
setAttr(R20character,"class-5-level",racialHD); | |
var size = getSizeMod(characterObj.size._name); | |
setAttr(R20character,"size",size); | |
characterObj.penalties.penalty = arrayify(characterObj.penalties.penalty); | |
var ACP = importPenalties(R20character,characterObj.penalties.penalty); | |
characterObj.skills.skill = arrayify(characterObj.skills.skill); | |
importSkills(R20character,characterObj.skills.skill,size,ACP); | |
if (!_.isUndefined(characterObj.senses.special)) | |
{ | |
characterObj.senses.special = arrayify(characterObj.senses.special); | |
setAttr(R20character,"vision", buildList(characterObj.senses.special, "_shortname")); | |
} | |
if (!_.isUndefined(characterObj.damagereduction.special)) | |
{ | |
characterObj.damagereduction.special = arrayify(characterObj.damagereduction.special); | |
setAttr(R20character,"DR", buildList(characterObj.damagereduction.special, "_shortname")); | |
} | |
if (!_.isUndefined(characterObj.resistances.special)) | |
{ | |
characterObj.resistances.special = arrayify(characterObj.resistances.special); | |
setAttr(R20character,"resistances", buildList(characterObj.resistances.special, "_shortname")); | |
} | |
if (!_.isUndefined(characterObj.immunities.special)) | |
{ | |
characterObj.immunities.special = arrayify(characterObj.immunities.special); | |
setAttr(R20character,"immunities", buildList(characterObj.immunities.special, "_shortname")); | |
} | |
if (!_.isUndefined(characterObj.weaknesses.special)) | |
{ | |
characterObj.weaknesses.special = arrayify(characterObj.weaknesses.special); | |
setAttr(R20character,"weaknesses", buildList(characterObj.weaknesses.special, "_shortname")); | |
} | |
if (!_.isUndefined(characterObj.languages.language)) | |
{ | |
characterObj.languages.language = arrayify(characterObj.languages.language); | |
setAttr(R20character,"languages", buildList(characterObj.languages.language, "_name")); | |
} | |
setAttr(R20character,"player-name",characterObj._playername); | |
setAttr(R20character,"deity",characterObj.deity._name); | |
setAttr(R20character,"race",characterObj.race._racetext.substr(0,1).toUpperCase()+characterObj.race._racetext.substr(1,1000)); | |
setAttr(R20character,"alignment",characterObj.alignment._name); | |
setAttr(R20character,"gender",characterObj.personal._gender); | |
setAttr(R20character,"age",characterObj.personal._age); | |
setAttr(R20character,"height",characterObj.personal.charheight._text); | |
setAttr(R20character,"weight",characterObj.personal.charweight._text); | |
setAttr(R20character,"hair",characterObj.personal._hair); | |
setAttr(R20character,"eyes",characterObj.personal._eyes); | |
setAttr(R20character,"skin",characterObj.personal._skin); | |
setAttr(R20character,"npc-cr",characterObj.challengerating._text.replace("CR ","")); | |
setAttr(R20character,"npc-xp",characterObj.xpaward._value); | |
if (!_.isUndefined(characterObj.favoredclasses.favoredclass)) | |
{ | |
characterObj.favoredclasses.favoredclass = arrayify(characterObj.favoredclasses.favoredclass); | |
setAttr(R20character,"class-favored", buildList(characterObj.favoredclasses.favoredclass, "_name")); | |
} | |
if (token !== null) | |
token.set("represents",R20character.character.get("_id")); | |
sendChat("HL-Import", "**"+characterObj._name + "** import complete!"); | |
if (!_.isUndefined(characterObj.minions.character)) | |
{ | |
characterObj.minions.character = arrayify(characterObj.minions.character); | |
characterObj.minions.character.forEach(function(charObj) { importCharacter(charObj,null); }); | |
} | |
}, | |
checkInstall = function() { | |
log('-=> HL-Import v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); | |
}, | |
HandleInput = function(msg) { | |
"use strict"; | |
var msg, | |
parameters, | |
selected, | |
tok, | |
character, | |
characters, | |
JSONstring; | |
if (msg.type === "api" && msg.content.indexOf("!hl-import") !== -1 ) | |
{ | |
if (msg.content.indexOf("--char") !== -1) | |
{ | |
parameters = msg.content.split(" "); | |
if ((_.indexOf(parameters, "--char")+1) < parameters.length) | |
{ | |
character = getObj("character", parameters[_.indexOf(parameters, "--char")+1]); | |
if (character !== null) | |
{ | |
character.get("bio", function(bio) { | |
JSONstring = unescape(bio).replace(/<br>/g,"\n"); | |
characters = new Array(JSON.parse(JSONstring).document.public.character); | |
characters.forEach(function(charObj,index) { | |
if (index === 0) | |
importCharacter(charObj,null,character); | |
else | |
importCharacter(charObj,null,null); | |
}); | |
}); | |
} | |
} | |
} | |
else | |
{ | |
selected = msg.selected; | |
_.each(selected, function(obj) { | |
tok = getObj("graphic", obj._id); | |
JSONstring = unescape(tok.get("gmnotes")).replace(/<br>/g,"\n"); | |
characters = new Array(JSON.parse(JSONstring).document.public.character); | |
characters.forEach(function(charObj,index) { | |
if (index === 0) | |
importCharacter(charObj,tok,null); | |
else | |
importCharacter(charObj,null,null); | |
}); | |
}); | |
} | |
} | |
}, | |
RegisterEventHandlers = function() { | |
on('chat:message', HandleInput); | |
}; | |
return { | |
CheckInstall: checkInstall, | |
RegisterEventHandlers: RegisterEventHandlers | |
}; | |
}()); | |
on("ready",function(){ | |
'use strict'; | |
HLImport.CheckInstall(); | |
HLImport.RegisterEventHandlers(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment