'use strict'; import _ from 'underscore'; import TAS from './TheAaronSheet.js'; import {PFLog, PFConsole} from './PFLog'; import PFConst from './PFConst'; import * as SWUtils from './SWUtils'; import * as PFUtils from './PFUtils'; import * as PFMacros from './PFMacros'; import * as PFMigrate from './PFMigrate'; import * as PFUtilsAsync from './PFUtilsAsync'; import * as PFAbilityScores from './PFAbilityScores'; export var regularCoreSkills = ["Appraise", "Acrobatics", "Bluff", "Climb", "Diplomacy", "Disable-Device", "Disguise", "Escape-Artist", "Fly", "Handle-Animal", "Heal", "Intimidate", "Linguistics", "Perception", "Ride", "Sense-Motive", "Sleight-of-Hand", "Spellcraft", "Stealth", "Survival", "Swim", "Use-Magic-Device"], regularBackgroundSkills = ["Appraise", "Handle-Animal", "Linguistics", "Sleight-of-Hand"], regularAdventureSkills = ["Acrobatics", "Bluff", "Climb", "Diplomacy", "Disable-Device", "Disguise", "Escape-Artist", "Fly", "Heal", "Intimidate", "Perception", "Ride", "Sense-Motive", "Spellcraft", "Stealth", "Survival", "Swim", "Use-Magic-Device"], regularBackgroundSkillsPlusKnow = regularBackgroundSkills.concat(["Knowledge-Engineering", "Knowledge-Geography", "Knowledge-History", "Knowledge-Nobility"]).sort(), regularAdventurePlusKnow = regularAdventureSkills.concat(["Knowledge-Arcana", "Knowledge-Dungeoneering", "Knowledge-Local", "Knowledge-Nature", "Knowledge-Planes", "Knowledge-Religion"]).sort(), //number that is appended to 10 versions of skills with subskills. skillAppendNums = ["", "2", "3", "4", "5", "6", "7", "8", "9", "10"], //same but for misc-skill miscSkillAppendNums = ["-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9"], coreSkillsWithFillInNames = ["Craft", "Misc-Skill", "Perform", "Profession"], backgroundOnlySkillsWithFillinNames = ["Artistry", "Lore"], skillsWithFillInNames = coreSkillsWithFillInNames.concat(backgroundOnlySkillsWithFillinNames).concat(["CS-Misc-Skill"]).sort(), backgroundOnlySkills = SWUtils.cartesianAppend(backgroundOnlySkillsWithFillinNames, skillAppendNums), knowledgeSubSkills = ["Arcana", "Dungeoneering", "Engineering", "Geography", "History", "Local", "Nature", "Nobility", "Planes", "Religion"], coreSkillsWithSubSkills = coreSkillsWithFillInNames.concat(["Knowledge"]).sort(), skillsWithSubSkills = skillsWithFillInNames.concat(["Knowledge"]).sort(), knowledgeSkillAppends = _.map(knowledgeSubSkills, function (subskill) { return "-" + subskill; }), //for each skill array of the possible skills {"Craft":["Craft","Craft2"...],"Perform":["Perform","Perform2"...] } subskillArrays = _.reduce(skillsWithSubSkills, function (memo, skill) { var appenders = (skill === "Misc-Skill") ? miscSkillAppendNums : (skill === "CS-Misc-Skill") ? miscSkillAppendNums : (skill === "Knowledge") ? knowledgeSkillAppends : skillAppendNums; memo[skill] = SWUtils.cartesianAppend([skill], skillAppendNums); return memo; }, {}), backgroundCoreSkills = regularBackgroundSkillsPlusKnow.concat(subskillArrays["Craft"]).concat(subskillArrays["Perform"]).concat(subskillArrays["Profession"]).concat(["Misc-Skill-5", "Misc-Skill-6", "Misc-Skill-7", "Misc-Skill-8", "Misc-Skill-9"]).sort(), adventureSkills = regularAdventurePlusKnow.concat(["Misc-Skill-0", "Misc-Skill-1", "Misc-Skill-2", "Misc-Skill-3", "Misc-Skill-4"]).sort(), checkRTArray = ["-ReqTrain", "-ranks"], baseGenMacro = "&{template:pf_generic} @{toggle_accessible_flag} @{toggle_rounded_flag} {{font=@{apply_specfont_chat}@{use_specfont}}} {{scroll_desc=@{scroll-desc}}} {{color=@{rolltemplate_color}}} {{header_image=@{header_image-pf_generic-skill}}} {{character_name=@{character_name}}} {{character_id=@{character_id}}} {{subtitle}} ", skillHeaderMacro = "{{name=^{REPLACELOWER} ^{skills} }} ", npcSkillHeaderMacro = "{{name=^{npc} ^{REPLACELOWER} ^{skills} }} ", // 1 is the normal size modifier in size_skill, 2 is size_skill_double sizeSkills = { "Fly": 1, "Stealth": 2, "CS-Stealth": 2 }, //these are for building the macros knowledgeSubSkillsTranslateKeys = _.map(knowledgeSubSkills, function (key) { return key.toLowerCase(); }), skillsWithSpaces = ["disable device", "escape artist", "sense motive", "handle animal", "use magic device", "sleight of hand"], knowledgeSkills = _.map(knowledgeSubSkills, function (subskill) { return "Knowledge-" + subskill; }), backgroundSkills = backgroundCoreSkills.concat(backgroundOnlySkills).sort(), allCoreSkills = adventureSkills.concat(backgroundCoreSkills).sort(), consolidatedSkills = ["CS-Acrobatics", "CS-Athletics", "CS-Finesse", "CS-Influence", "CS-Nature", "CS-Perception", "CS-Performance", "CS-Religion", "CS-Society", "CS-Spellcraft", "CS-Stealth", "CS-Survival"], consolidatedMiscSkills = ["CS-Misc-Skill-0", "CS-Misc-Skill-1", "CS-Misc-Skill-2", "CS-Misc-Skill-3", "CS-Misc-Skill-4","CS-Misc-Skill-5", "CS-Misc-Skill-6", "CS-Misc-Skill-7", "CS-Misc-Skill-8", "CS-Misc-Skill-9"], allNonFillInSkills = regularCoreSkills.concat(knowledgeSkills).concat(consolidatedSkills).sort(), nonMiscFillInSkillsInstances = SWUtils.cartesianAppend(["Craft", "Perform", "Profession", "Artistry", "Lore"], skillAppendNums), miscFillInSkillsInstances =SWUtils.cartesianAppend(["Misc-Skill"], miscSkillAppendNums).concat(consolidatedMiscSkills), allFillInSkillInstances = nonMiscFillInSkillsInstances.concat(miscFillInSkillsInstances).sort(), allTheSkills = allNonFillInSkills.concat(allFillInSkillInstances).sort(), coreSkillAbilityDefaults = { "Acrobatics": "dex", "Appraise": "int", "Bluff": "cha", "Climb": "str", "Craft": "int", "Diplomacy": "cha", "Disable-Device": "dex", "Disguise": "cha", "Escape-Artist": "dex", "Fly": "dex", "Handle-Animal": "cha", "Heal": "wis", "Intimidate": "cha", "Knowledge-Arcana": "int", "Knowledge-Dungeoneering": "int", "Knowledge-Engineering": "int", "Knowledge-Geography": "int", "Knowledge-History": "int", "Knowledge-Local": "int", "Knowledge-Nature": "int", "Knowledge-Nobility": "int", "Knowledge": "int", "Knowledge-Planes": "int", "Knowledge-Religion": "int", "Linguistics": "int", "Perception": "wis", "Perform": "cha", "Profession": "wis", "Ride": "dex", "Sense-Motive": "wis", "Sleight-of-Hand": "dex", "Spellcraft": "int", "Stealth": "dex", "Survival": "wis", "Swim": "str", "Use-Magic-Device": "cha" }, consolidatedSkillAbilityDefaults = { "CS-Acrobatics": "dex", "CS-Athletics": "str", "CS-Finesse": "dex", "CS-Influence": "cha", "CS-Nature": "int", "CS-Perception": "wis", "CS-Performance": "cha", "CS-Religion": "int", "CS-Society": "int", "CS-Spellcraft": "int", "CS-Stealth": "dex", "CS-Survival": "wis" }, globalSkillModAttrs = ['enforce_requires_training', 'size_skill', 'size_skill_double', 'acp', 'Phys-skills-cond', 'Perception-cond', 'STR-mod','DEX-mod','CON-mod','INT-mod','WIS-mod','CHA-mod', 'buff_STR_skills-total','buff_DEX_skills-total','buff_CON_skills-total', 'buff_INT_skills-total','buff_WIS_skills-total','buff_CHA_skills-total', 'checks-cond','buff_Check-total','buff_check_skills-total'], skillNameAppends = ['', '-cs', '-ranks', '-ability', '-racial', '-trait', '-feat', '-item', '-misc-mod', '-ReqTrain', '-ut'], //ability based skill buffs events located in PFBuffs events = { skillGlobalPhysEventAuto: "change:phys-skills-cond change:acp", skillEventsAuto: "change:REPLACE-misc-mod", skillEventsPlayer: "change:REPLACE-ability change:REPLACE-ranks change:REPLACE-racial change:REPLACE-trait change:REPLACE-feat change:REPLACE-item change:REPLACE-ReqTrain" }; /**appendToSubSkills - util to append the string to all 10 names of one type of skill (perform, craft, knowledge, etc) * adds the numbers from 0-9 or 1-10 or knowledge, then appends the string , to generate all 10 versions. * @param {string} skilllist The name of the skill in, member of skillsWithSubSkills * @param {string} appendToEnd The string to append. * @returns {Array[string]} array of skill names */ function appendToSubSkills (skilllist, appendToEnd) { return _.reduce(skilllist, function (memo, skill) { var appendnums = (skill === "Misc-Skill") ? miscSkillAppendNums : (skill === "CS-Misc-Skill") ? miscSkillAppendNums : (skill === "Knowledge") ? knowledgeSkillAppends : skillAppendNums, appendArray = SWUtils.cartesianAppend([skill], appendnums, appendToEnd); return memo.concat(appendArray); }, []); } /* updateMaxSkills Calculates and sets maximum skill ranks. Minimum 1 per level. * divides by 2 if using consolidated skills * @param {event} eventInfo - from event * @callback {function} - callback when done */ function updateMaxSkills (eventInfo, callback) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }), fields=["total-skill", "total-fcskill", "INT-mod", "level", "Max-Skill-Ranks-mod", "Max-Skill-Ranks", "unchained_skills-show", "BG-Skill-Use","npc-skill","npc-hd-num", "class-0-skill","class-1-skill","class-2-skill","class-3-skill","class-4-skill","class-5-skill", "class-0-level","class-1-level","class-2-level","class-3-level","class-4-level","class-5-level" ]; getAttrs(fields, function (v) { var intMod = parseInt(v["INT-mod"], 10) || 0, classSkills = parseInt(v["total-skill"], 10) || 0, level = parseInt(v.level, 10) || 0, fcSkills = parseInt(v["total-fcskill"], 10) || 0, extra = parseInt(v["Max-Skill-Ranks-mod"], 10) || 0, currSkills = parseInt(v["Max-Skill-Ranks"], 10) || 0, totIntMod = 0, minSkills=0, i=0, thislvl=0, classPlusInt = 0, thisSkill=0, totAllSkills = 0, isConsolidated=0, setter = {}; try { if(parseInt(v["unchained_skills-show"],10)===1){ if((parseInt(v["BG-Skill-Use"],10)||0) === 0){ isConsolidated =1; } } for(i=0;i<6;i++){ thislvl=parseInt(v['class-'+i+'-level'],10)||0; if (thislvl>0){ thisSkill=( (parseInt(v['class-'+i+'-skill'],10)||0) * thislvl ) + (intMod * thislvl); if (thisSkill < thislvl){ thisSkill=thislvl; } classPlusInt += thisSkill; } } thislvl = parseInt(v['npc-hd-num'],10)||0; thisSkill = parseInt(v['npc-skill'],10)||0; if (thislvl && thisSkill){ thisSkill = thislvl * thisSkill + intMod * thislvl; if (thisSkill < thislvl){ thisSkill=thislvl; } classPlusInt += thisSkill; } if (isConsolidated){ classPlusInt = Math.floor(classPlusInt / 2); } totAllSkills = classPlusInt + extra; if (totAllSkills < level){ totAllSkills = level; } totAllSkills += fcSkills; if (currSkills !== totAllSkills) { setter["Max-Skill-Ranks"] = totAllSkills; } } catch (err) { TAS.error("PFSkills.updateMaxSkills", err); } finally { if (_.size(setter) > 0) { SWUtils.setWrapper(setter, { silent: true }, done); } else { done(); } } }); } /** verifyHasSkill - Checks to see if skill is in list of valid skills for this character (consolidated, background, core). * @param {string} skill = the skill name * @param {function} callback = a function that takes a a boolean as a first parameter. * called with true if skill is part of valid list, or false if not. */ export function verifyHasSkill (skill, callback) { var first3 = '', first4 = '', core = false, bg = false, cs = false, isSub = false, fields = ["BG-Skill-Use", "unchained_skills-show"]; try { if (skill && typeof callback === "function") { first4 = skill.slice(0, 4).toLowerCase(); first3 = first4.slice(0, 3); if (first3 === 'cs-') { cs = true; } else if (first4 === 'arti' || first4 === 'lore') { bg = true; } else { core = true; } if (_.contains(allFillInSkillInstances, skill)) { isSub = true; fields = fields.concat([skill + "-name", skill + "-ranks"]); } getAttrs(fields, function (v) { var retval = false, usesBG=parseInt(v["BG-Skill-Use"],10)||0, usesUnchained=parseInt(v["unchained_skills-show"],10)||0; if (!isSub || v[skill + "-name"] || (parseInt(v[skill+"-ranks"],10)||0)>0) { if (core) { if (!usesUnchained || usesBG) { retval = true; } } else if (bg) { if (usesUnchained && usesBG) { retval = true; } } else { if (usesUnchained && !usesBG) { retval = true; } } } callback(retval); }); } } catch (err) { TAS.error("PFSkills.verifyHasSkill", err); callback(false); } } /** synchronously calculates new value for skill and returns values for setAttrs * @param {string} skill name of skill to update * @param {Map} v attributes and values from sheet * @param {Map} setter optional, map to send to setAttrs so far * @returns {Map} setter with additional values, or new map, for setAttrs */ function setSkillVal (skill, v, setter){ var skillSize = 0, watchrt = parseInt(v["enforce_requires_training"], 10) || 0, csNm = skill + "-cs", ranksNm = skill + "-ranks", abNm = skill + "-ability", racialNm = skill + "-racial", traitNm = skill + "-trait", featNm = skill + "-feat", itemNm = skill + "-item", miscNm = skill + "-misc-mod", utNm = skill + "-ut", rtNm = skill + "-ReqTrain", adj, skillTot = 0, setAny = 0, cond = 0, cs = parseInt(v[csNm], 10) || 0, currSkill = parseInt(v[skill], 10), //no default ranks = parseInt(v[ranksNm], 10) || 0, rt = parseInt(v[rtNm], 10) || 0, allCond = 0, buffs= 0, abilityModName = '', abilityName='', physCond = 0, perCond = 0, globalBuffCond=0; try { setter = setter || {}; abilityModName = v[abNm]; //PFUtils.findAbilityInString(v[abNm]); if (rt && ranks === 0) { if (v[utNm] !== "{{untrained=1}}") { setter[utNm] = "{{untrained=1}}"; } } else if (v[utNm] !== "{{untrained=}}") { setter[utNm] = "{{untrained=}}"; //cannot set to "" because then it chooses the default which is "{{untrained=1}}" } if (ranks && cs) { skillTot += 3; } if (abilityModName === "DEX-mod" || abilityModName === "STR-mod") { adj = parseInt(v["acp"], 10) || 0; skillTot += adj; physCond = parseInt(v["Phys-skills-cond"], 10) || 0; } if (abilityModName){ abilityName = abilityModName.slice(0,3).toUpperCase(); buffs += parseInt(v['buff_'+abilityName+'_skills-total'],10)||0; } if(skill==='Stealth'||skill==='CS-Stealth'){ skillSize=2; }else if (skill==='Fly'){ skillSize=1; } else { skillSize=0; } //skillSize = sizeSkills[skill]; if (skillSize) { if (skillSize === 1) { adj = parseInt(v["size_skill"], 10) || 0; skillTot += adj; } else if (skillSize === 2) { adj = parseInt(v["size_skill_double"], 10) || 0; //if(skill==='Stealth'){TAS.notice("adjusting stealth by "+adj);} skillTot += adj; } } if (skill === "Perception" || skill === "CS-Perception") { perCond = parseInt(v["Perception-cond"], 10) || 0; } globalBuffCond=(parseInt(v['checks-cond'],10)||0)+(parseInt(v['buff_Check-total'],10)||0)+ (parseInt(v['buff_check_skills-total'],10)||0); cond = allCond + physCond + perCond+globalBuffCond; skillTot += ranks + cond + buffs+ (parseInt(v[abilityModName], 10) || 0) + (parseInt(v[racialNm], 10) || 0) + (parseInt(v[traitNm], 10) || 0) + (parseInt(v[featNm], 10) || 0) + (parseInt(v[itemNm], 10) || 0) + (parseInt(v[miscNm], 10) || 0); if (currSkill !== skillTot) { setter[skill] = skillTot; } } catch (erskill){ TAS.error("PFSkills.setNewSkillVal",erskill); } finally { return setter; } } /** updates one skill row reading all values on row * @param {string} skill to update, must have same capitalization as on HTML * @param {function} callback = callback after done with params newvalue, oldvalue. * @param {boolean} silently = whether to update silently or not. ignored, always silent. */ export function updateSkillAsync (skill, callback, silently) { var done = function (newVal, oldVal) { if (typeof callback === "function") { callback(newVal, oldVal); } }, fields = skillNameAppends.map(function(append){return skill+append;}); fields = fields.concat(globalSkillModAttrs); getAttrs(fields,function (v) { var setter = {}, params = {}, currSkill=0; try { currSkill = parseInt(v[skill], 10); setter = setSkillVal(skill,v,setter); } catch (err) { TAS.error(err); } finally { if (_.size(setter) > 0) { SWUtils.setWrapper(setter, PFConst.silentParams, function () { done(setter[skill] , currSkill); }); } else { done(currSkill, currSkill); } } }); } /** Sets new skill value by applying diff * @param {string} skill skill to update * @param {Number} diff difference to apply * @param {Map} v current values from getAttrs * @param {Map} setter current map for setAttrs if provided this is used * @returns {Map} setter or map of skill to new number for setAttrs */ function setSkillValByDiff(skill,diff,v,setter){ var tot=0; setter= setter||{}; tot=parseInt(v[skill],10)||0; tot+=diff; setter[skill]=tot; return setter; } /** when user checks class skill, +3 or -3 depending on if ranks > 0, if ranks 0 no change * @param {string} skill - from allTheSkills */ function updateSkillByClassChkAsync(skill){ getAttrs([skill,skill+'-ranks',skill+'-cs'],function(v){ var tot=0,cs=0,ranks=0,newtot=0,setter={}; tot=parseInt(v[skill],10)||0; cs=parseInt(v[skill+'-cs'],10)||0; ranks=parseInt(v[skill+'-ranks'],10)||0; if(ranks){ if(cs){ newtot=tot+3; } else { newtot = tot-3; } setter[skill]=newtot; SWUtils.setWrapper(setter); } }); } function recalculateSkillArrayMiscFields (skills, callback){ var doneOneMisc = _.after(_.size(skills),callback); _.each(skills, function (skill) { SWUtils.evaluateAndSetNumber(skill + "-misc", skill + "-misc-mod", 0, function () { doneOneMisc(); }, true); }); } /** Recalcs the array of skills but does NOT recalc the misc macro fields * * @param {*} skills * @param {*} callback * @param {*} silently */ function recalcSkillTotals (skills,callback,silently){ var done = _.once(function () { if (typeof callback === "function") { callback(); } }), fields, subfields; fields = globalSkillModAttrs; _.each(skills, function (skill) { var subfields=skillNameAppends.map(function(append){return skill+append;}); fields = fields.concat(subfields); }); getAttrs(fields,function(v){ var setter={}; setter=_.reduce(skills,function(m,skill){ try {setSkillVal(skill,v,m);} catch (e){} finally { return m; } },{}); if(_.size(setter)){ SWUtils.setWrapper(setter,PFConst.silentParams,done); } else { done(); } }); } /** recalcSkillArray recalculates skill misc macro fields then totals. * @param {Array} skills array of skills to update. * @param {function} callback when done * @param {boolean} silently whether to call SWUtils.setWrapper of skill total with silent or not. */ function recalcSkillArray (skills, callback, silently) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }), skillCount = _.size(skills), skillsHandled = 0, doneMisc = function () { recalcSkillTotals(skills,done,silently); }; recalculateSkillArrayMiscFields(skills,doneMisc) } export function recalculateSkills (callback, silently, onlySkills) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }); getAttrs(["unchained_skills-show", "BG-Skill-Use"], function (v) { try { if (parseInt(v["unchained_skills-show"],10)) { if (parseInt(v["BG-Skill-Use"],10)) { if(onlySkills){ recalcSkillTotals(backgroundOnlySkills, null, silently); recalcSkillTotals(allCoreSkills, done, silently); }else { //TAS.debug("PFSkills.recalculate: has background skills"); recalcSkillArray(backgroundOnlySkills, null, silently); //return after long one recalcSkillArray(allCoreSkills, done, silently); } } else { if (onlySkills){ recalcSkillTotals(consolidatedSkills, done, silently); recalcSkillTotals(consolidatedMiscSkills, null, silently); }else { //TAS.debug("PFSkills.recalculate: has consolidatedSkills skills"); recalcSkillArray(consolidatedSkills, done, silently); recalcSkillArray(consolidatedMiscSkills, null, silently); } } } else { if (onlySkills){ recalcSkillTotals(allCoreSkills, done, silently); }else { //TAS.debug("PFSkills.recalculate:has core skills skills"); recalcSkillArray(allCoreSkills, done, silently); } } } catch (err) { TAS.error("PFSKills.recalculate", err); done(); } }); } export function updateAllSkillsDiff (newmod,oldmod){ TAS.notice("PFSkills updateallskills diff updating by " +newmod+", from "+ oldmod); getAttrs(allTheSkills,function(v){ var diff = newmod - oldmod,setter={}; setter= _.mapObject(v,function(val,key){ return (parseInt(val,10)||0)+diff; }); TAS.debug("SPSkills.updateAllSkillsDiff setting",setter); SWUtils.setWrapper(setter,PFConst.silentParams); }); } export function recalculateAbilityBasedSkills (abilityBuff,eventInfo,callback,silently){ var done=function(){ if (typeof callback === "function"){ callback();} }, updatedAttr ,tempstr='',matches,fields; if(abilityBuff) { tempstr = abilityBuff; } else if(eventInfo){ tempstr = eventInfo.sourceAttribute; if(tempstr.indexOf('repeating_')>=0){ tempstr = SWUtils.getAttributeName(tempstr); } } if(tempstr){ if (tempstr==='physical'||tempstr==='acp'){ updatedAttr = /STR\-mod|DEX\-mod/; } else { matches=tempstr.match(/str|dex|con|int|wis|cha/i); if(matches){ //TAS.debug("recalculateAbilityBasedSkills the match is: "+matches[0],matches); updatedAttr= new RegExp(matches[0].toUpperCase()+'\-mod'); } } //TAS.debug("recalculateAbilityBasedSkills updatedAttr is now "+updatedAttr); } if(!updatedAttr){ done(); return; } fields = allTheSkills.map(function(skill){ return skill+'-ability'; }); //TAS.debug("recalculateAbilityBasedSkills getting all skill abilities"); getAttrs(fields,function(v){ var skillArray=[]; //TAS.debug("recalculateAbilityBasedSkills skill abilities are ",fields,v); skillArray = _.reduce(v,function(m,val,field){ if(updatedAttr.test(val)){ //TAS.debug("recalculateAbilityBasedSkills field "+field+" matches and skill is "+ field.slice(0,-8)); m.push(field.slice(0,-8)); } return m; },[]); TAS.notice("PFSkills updateAbilityBasedSkills getting array:",skillArray); if(_.size(skillArray)){ recalcSkillTotals(skillArray,done,silently); } else { done(); } }); } /** updates the macros for only the 7 subskill rolltemplates * @param {boolean} background -if background skills turned on * @param {boolean} rt - if Enforce Requires Training checked * @param {event} eventInfo ? * @param {jsobject_map} currMacros map of parent skill button name to command macro. (knowledge, Perform, etc) * @param {boolean} isNPC - if sheet is NPC * @param {boolean} showBonus - if skill total should be displayed on button. */ function updateSubSkillMacroBook (background, rt, eventInfo, currMacros, isNPC, showBonus) { var headerString = isNPC ? npcSkillHeaderMacro : skillHeaderMacro, skillPrefix = isNPC ? "NPC-" : "", assembleSubSkillButtonArray = function (skill, shouldEnforce, v) { var appendnums = (skill === "Misc-Skill") ? miscSkillAppendNums : (skill === "CS-Misc-Skill") ? miscSkillAppendNums : (skill === "Knowledge") ? knowledgeSkillAppends : skillAppendNums, subskills = SWUtils.cartesianAppend([skill], appendnums), firstPass = []; if (skill === "Knowledge") { firstPass = subskills; return firstPass; //knowledge rollable even if untrained } firstPass = _.filter(subskills, function (subskill) { if (v[subskill + "-name"]) { return true; } return false; }); if (!shouldEnforce) { return firstPass; } return _.filter(firstPass, function (skill) { if ((parseInt(v[skill + "-ReqTrain"], 10) || 0) === 0 || (parseInt(v[skill + "-ranks"], 10) || 0) > 0) { return true; } return false; }); }, getKnowledgeButtonMacro = function (showBonus) { var bonusStr = showBonus ? "+ @{REPLACE}" : "", knowledgebutton = "[^{REPLACENAME}" + bonusStr + "](~@{character_id}|" + skillPrefix + "REPLACE-check) "; return headerString.replace('REPLACELOWER', 'knowledge') + "{{ " + _.reduce(knowledgeSubSkillsTranslateKeys, function (memo, subskill, idx) { memo += knowledgebutton.replace(/REPLACENAME/g, subskill).replace(/REPLACE/g, knowledgeSkills[idx]); return memo; }, "") + " }}"; }, getSubSkillButtonMacro = function (skill, skillArray, showBonus,v) { var skillTransKey = skill.toLowerCase(), bonusStr = showBonus ? "+ @{REPLACE}" : "", baseMacro = headerString.replace('REPLACELOWER', skillTransKey), singleRowButton = "[REPLACENAME" + bonusStr + "](~@{character_id}|" + skillPrefix + "REPLACE-check) ", tempstr = ""; if (skill === "Knowledge") { return getKnowledgeButtonMacro(); } tempstr = _.reduce(skillArray, function (memo, subskill, idx) { var buttonName = v[subskill+"-name"]; if (buttonName){ buttonName = SWUtils.escapeForChatLinkButton(buttonName); buttonName = SWUtils.escapeForRollTemplate(buttonName); } else { buttonName = "@{"+subskill+"-name}"; } memo += singleRowButton.replace(/REPLACENAME/g, buttonName).replace(/REPLACE/g, subskill); return memo; }, ""); if (!tempstr) { tempstr = "description = ^{no-skills-available}"; } return baseMacro + "{{ " + tempstr + " }}"; }, subskillParents = background ? skillsWithFillInNames : coreSkillsWithFillInNames, allsubskillFields = appendToSubSkills(subskillParents, ["-name"]); if (rt) { allsubskillFields = allsubskillFields.concat( appendToSubSkills(subskillParents, checkRTArray) ); allsubskillFields = allsubskillFields.sort(); //allsubskillFields.concat(appendToSubSkills(subskillParents, checkRTArray)).sort(); } //TAS.debug("updateSubSkillMacroBook: allsubskillFields are:", allsubskillFields); getAttrs(allsubskillFields, function (v) { var setter = {}, tempKMac = ""; //TAS.debug("updateSubSkillMacroBook: event and data are:", eventInfo, v); _.each(subskillParents, function (skill) { var canshowarray = assembleSubSkillButtonArray(skill, rt, v), tempMacro = getSubSkillButtonMacro(skill, canshowarray, showBonus,v); tempMacro = baseGenMacro + tempMacro; if (currMacros[skillPrefix + skill.toLowerCase() + "_skills-macro"] !== tempMacro) { setter[skillPrefix + skill.toLowerCase() + "_skills-macro"] = tempMacro; } }); if (currMacros[skillPrefix + "knowledge_skills-macro"]) { tempKMac = baseGenMacro + getKnowledgeButtonMacro(showBonus); if (currMacros[skillPrefix + "knowledge_skills-macro"] !== tempKMac) { setter[skillPrefix + "knowledge_skills-macro"] = tempKMac; } } if (_.size(setter) > 0) { SWUtils.setWrapper(setter, PFConst.silentParams); } }); } function assembleSkillButtonArray (skills, shouldEnforce, sv) { if (!shouldEnforce) { return skills; } return _.filter(skills, function (skill) { if (/^Knowled|^Linguis|^Sleight/i.test(skill.slice(0, 7)) || (parseInt(sv[skill + "-ReqTrain"],10)||0) !== 1 || (parseInt(sv[skill + "-ranks"], 10) || 0) > 0) { return true; } return false; }); } function getSkillButtonMacro (name, skillArray, showBonus, isNPC) { var skillTransKey = name.toLowerCase(), skillPrefix = isNPC ? "NPC-" : "", bonusStr = showBonus ? " + @{REPLACE}" : "", baseMacro = "{{name= ^{" + skillTransKey + "} }} ", npcBaseMacro = "{{name= ^{npc} ^{" + skillTransKey + "} }} ", rowbutton = "[^{REPLACELOWER}" + bonusStr + "](~@{character_id}|" + skillPrefix + "REPLACE-check) ", subskillbutton = "[^{REPLACELOWER}](~@{character_id}|" + skillPrefix + "REPLACELOWERMAC_skills_buttons_macro) ", baseToSend = isNPC?npcBaseMacro:baseMacro, tempstr=""; try { tempstr = _.reduce(skillArray, function (memo, skill, idx) { var thistranskey = skill.toLowerCase(), thisbutton = (_.contains(skillsWithSubSkills, skill)) ? subskillbutton : rowbutton; thisbutton = thisbutton.replace(/REPLACELOWERMAC/g, thistranskey); thisbutton = thisbutton.replace(/REPLACELOWER/g, thistranskey); thisbutton = thisbutton.replace(/REPLACE/g, skill); memo += thisbutton + " "; return memo; }, ""); if (!tempstr) { tempstr = "^{no-skills-available} "; } } finally { return baseToSend + "{{ " + tempstr + "}}"; } } function resetOneCommandMacro (callback, eventInfo, isNPC,showBonus,unchained,background,consolidated,rt){ var done = _.once(function () { if (typeof callback === "function") { callback(); } }), skillPrefix = isNPC ? "NPC-" : ""; getAttrs([skillPrefix+"skills-macro", skillPrefix+"background_skills-macro", skillPrefix+"adventure_skills-macro", skillPrefix+"artistry_skills-macro", skillPrefix+"lore_skills-macro", skillPrefix+"craft_skills-macro", skillPrefix+"knowledge_skills-macro", skillPrefix+"perform_skills-macro", skillPrefix+"profession_skills-macro", skillPrefix+"misc-skill_skills-macro", skillPrefix+"CS-misc-skill_skills-macro"], function (v) { var i = 0, setter = {}, tempSkillArray = [], tempMacro = "", allskillstitle = "skills", coreArray; if (!consolidated) { updateSubSkillMacroBook(background, rt, eventInfo, v, isNPC, showBonus); getAttrs(SWUtils.cartesianAppend(regularCoreSkills, checkRTArray), function (v) { var canshowarray = [], tempRTMacro = "", temparray = []; try { if (background) { canshowarray = assembleSkillButtonArray(regularBackgroundSkills, rt, v) || []; temparray = temparray.concat(canshowarray); canshowarray = canshowarray.concat(skillsWithSubSkills).sort(); tempRTMacro = baseGenMacro + getSkillButtonMacro("background-skills", canshowarray, showBonus, isNPC); if (v[skillPrefix + "background_skills-macro"] !== tempRTMacro) { setter[skillPrefix + "background_skills-macro"] = tempRTMacro; } canshowarray = assembleSkillButtonArray(regularAdventureSkills, rt, v) || []; temparray = temparray.concat(canshowarray); canshowarray = canshowarray.concat(["Knowledge","Misc-Skill"]).sort(); tempRTMacro = baseGenMacro + getSkillButtonMacro("adventure-skills", canshowarray, showBonus, isNPC); if (v[skillPrefix + "adventure_skills-macro"] !== tempRTMacro) { setter[skillPrefix + "adventure_skills-macro"] = tempRTMacro; } temparray = temparray.concat(skillsWithSubSkills).sort(); } else { canshowarray = assembleSkillButtonArray(regularCoreSkills, rt, v) || []; temparray = temparray.concat(canshowarray).concat(coreSkillsWithSubSkills).sort(); } tempRTMacro = baseGenMacro + getSkillButtonMacro("skills", temparray, showBonus, isNPC); if (v[skillPrefix + "skills-macro"] !== tempRTMacro) { setter[skillPrefix + "skills-macro"] = tempRTMacro; } } catch (errRT){ TAS.error("PFSkills.resetOneCommandMacro errRT",errRT); } finally { if (_.size(setter) > 0) { SWUtils.setWrapper(setter, PFConst.silentParams, done); } else { done(); } } }); } else { //consolidated tempSkillArray = consolidatedSkills;//.concat(consolidatedMiscSkills); getAttrs(SWUtils.cartesianAppend(tempSkillArray, ["-ReqTrain", "-ranks"]), function (sv) { var canshowarray, setter = {}, tempMacro ; canshowarray = assembleSkillButtonArray(tempSkillArray, rt, sv); canshowarray.push("CS-Misc-Skill"); tempMacro = getSkillButtonMacro("consolidated-skills", canshowarray, showBonus, isNPC); setter[skillPrefix + "consolidated_skills-macro"] = baseGenMacro + tempMacro; SWUtils.setWrapper(setter,PFConst.silentParams, done); }); } }); } export function resetCommandMacro (eventInfo, callback) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }); getAttrs(["BG-Skill-Use", "unchained_skills-show", "enforce_requires_training", "is_npc", "include_skill_totals"],function(vout){ var isNPC = parseInt(vout["is_npc"], 10) || 0, skillPrefix = isNPC ? "NPC-" : "", showBonus = parseInt(vout.include_skill_totals, 10) || 0, unchained = parseInt(vout["unchained_skills-show"], 10) || 0, background = unchained && (parseInt(vout["BG-Skill-Use"], 10) || 0), consolidated = unchained && (!background), rt = parseInt(vout["enforce_requires_training"], 10) || 0; resetOneCommandMacro(done,eventInfo,isNPC,showBonus,unchained,background,consolidated,rt); if (isNPC){ resetOneCommandMacro(done,eventInfo,false,showBonus,unchained,background,consolidated,rt); } }); } /** migrate skills * @param {function} callback callback when done * @param {number} oldversion old version , -1 if hit recalc */ export function migrate (callback, oldversion) { var done = _.once(function () { // TAS.debug("leaving PFSkills.migrate"); if (typeof callback === "function") { callback(); } }), doneOne = _.after(4,done), /** migrateOldClassSkillValue - converts class skill checkboxes from old autocalc string to number "" or 3. * @param {function} callback when done * @param {number} oldversion currversionnumber */ migrateOldClassSkillValue = function (callback, oldversion) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }), migrateClassSkill = function (skill) { var csNm = skill + "-cs"; getAttrs([csNm], function (v) { var setter = {}; if (isNaN(parseInt(v[csNm], 10))) { if (! (!v[csNm] || v[csNm] == "0") ) { //TAS.debug({"function":"migrateClassSkill","raw":v[csNm],"cs":cs}); setter[csNm] = 3; SWUtils.setWrapper(setter, PFConst.silentParams); } } }); }, migrateClassSkillArray = function (skills) { skills.forEach(function (skill) { migrateClassSkill(skill); }); }, determineArray = function () { migrateClassSkillArray(allTheSkills); //not bothering to code correctly to wait since this is almost a year old. SWUtils.setWrapper({classSkillsMigrated: 1}, PFConst.silentParams,done); }; getAttrs(["classSkillsMigrated"], function (vm) { if (!(parseInt(vm.classSkillsMigrated, 10) || 0)) { determineArray(); } done(); }); }, /** setAdvancedMacroCheckbox - part of migrate .66 to 1.00 sets checkbox to unhide advanced * skillmacro (investigator) if character sheet already using it.) * @param {function} callback whendone */ setAdvancedMacroCheckbox = function (callback) { var done = _.once(function () { if (typeof callback === "function") { callback(); } }); getAttrs(["adv_macro_show", "skill-invest-query"], function (v) { var showAdv = parseInt(v.adv_macro_show, 10) || 0; if (v["skill-invest-query"] && !showAdv) { SWUtils.setWrapper({adv_macro_show: 1}, PFConst.silentParams, done); } }); }, migrateMacros2 = function(callback){ getAttrs(['migrated_skill_speedup3'],function(vout){ var fields; if(vout.migrated_skill_speedup3*1){ if (typeof callback === "function") { callback(); } //return; } fields = allTheSkills.map(function(skill){return skill+'-macro';}); getAttrs(fields,function(v){ var setter={}; try{ setter=_.reduce(v,function(m,macro,attr){ try { m[attr]=macro.replace(/ \+ \[\[ @{checks\-cond} \+ @{buff_check_skills-total} \+ @{buff_Check-total} \]\] /,""); // TAS.debug("removed cond and buffs macro from skill macro-text"); }catch (ierr){ TAS.error("PFBuffs.migrate add buff checks for "+attr,ierr); } finally { return m; } },{}); } catch (err){ TAS.error("PFSkills.migrate skill speedup",err); } finally { setter.migrated_skill_speedup3=1; setAttrs(setter,PFConst.silentParams,callback); } }); }); }, migrateTake10Dropdown = function(callback,oldversion){ var done = function(){ if (typeof callback === "function"){ callback(); } }; getAttrs(['migrated_take10_dropdown','skill-query','investigator_dice','skill-invest-query'],function(v){ var setter={}; //TAS.notice("########################","PFSkills.migrate",v,"################3"); //TAS.debug("V 13 just to make sure the damn thing is working"); if((parseInt(v.migrated_take10_dropdown,10)||0)===0){ if(v['skill-query']==='?{Roll or Take 10/20?|1d20,1d20+@{skill-invest-query}|10,10+@{skill-invest-query}|20,20+@{skill-invest-query}}'){ setter['skill-query']='?{Roll or Take 10/20?|1d20,1d20|10,10|20,20}+@{skill-invest-query}'; } else if (v['skill-query']==='@{skill-invest-query}'){ setter['skill-query']='@{skill-invest-query}+@{custom_dice}'; } else if (!v['skill-query'] || v['skill-query']==="0"){ setter['skill-query']='1d20+@{skill-invest-query}'; } else if (v['skill-query']!=='@{skill-invest-query}+@{custom_dice}' && v['skill-query']!=='?{Roll or Take 10/20?|1d20,1d20|10,10|20,20}+@{skill-invest-query}' && v['skill-query']!=='1d20+@{skill-invest-query}'){ setter['skill-query']='1d20+@{skill-invest-query}'; } if (v.investigator_dice==="0" || !v.investigator_dice){ setter.investigator_dice="[[ 1d6 ]] [custom bonus]"; } if (!v['skill-invest-query']){ setter['skill-invest-query']="0"; } else if ( v['skill-invest-query'] !== '@{investigator_dice}' && v['skill-invest-query'] !== "0" ){ setter['skill-invest-query']="0"; } setter.migrated_take10_dropdown=1; //TAS.debug("#####################","PFBuffs.migrate setting",setter); SWUtils.setWrapper(setter,PFConst.silentParams,done); } else { done(); } }); }; //TAS.debug("at PFSkills.migrate"); migrateTake10Dropdown(doneOne); migrateOldClassSkillValue(doneOne); migrateMacros2(doneOne); PFMigrate.migrateMaxSkills(doneOne); } /* recalculate - updates ALL skills - calls PFUtilsAsync.setDropdownValue for ability then updateSkill */ export var recalculate = TAS.callback(function PFSkillsRecalculate(callback, silently, oldversion) { var done = _.once(function () { TAS.info("leaving PFSkills.recalculate"); resetCommandMacro(); if (typeof callback === "function") { callback(); } }); //TAS.debug("PFSkills.recalculate"); migrate(function () { //TAS.debug"PFSkills.recalculate back from PFSkills.migrate"); updateMaxSkills(); recalculateSkills(done, silently); }, oldversion); }); function registerEventHandlers () { on("change:str-mod change:dex-mod change:con-mod change:int-mod change:wis-mod change:cha-mod",TAS.callback(function eventAbilityScoreToSkill(eventInfo){ TAS.notice("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { recalculateAbilityBasedSkills(null,eventInfo); } })); //SKILLS************************************************************************ on("change:total-skill change:total-fcskill change:int-mod change:level change:max-skill-ranks-mod change:unchained_skills-show change:BG-Skill-Use", TAS.callback(function eventUpdateMaxSkills(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { updateMaxSkills(eventInfo); } })); on(events.skillGlobalPhysEventAuto, TAS.callback(function eventGlobalConditionAffectingSkill(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event for " + eventInfo.sourceType); if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { recalculateAbilityBasedSkills('physical',eventInfo); } })); //each skill has a dropdown handler and a skill update handler //concat them all up, only happens once so no big deal _.each(allTheSkills, function (skill) { on((events.skillEventsAuto.replace(/REPLACE/g, skill)), TAS.callback(function eventSkillsAuto(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event for " + skill + ", " + eventInfo.sourceType); if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { verifyHasSkill(skill, function (hasSkill) { if (hasSkill) { updateSkillAsync(skill, eventInfo); } }); } })); on((events.skillEventsPlayer.replace(/REPLACE/g, skill)), TAS.callback(function eventSkillsPlayer(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event for " + skill + ", " + eventInfo.sourceType); if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { updateSkillAsync(skill); } })); on("change:" + skill + "-misc", TAS.callback(function eventSkillMiscFieldUpdate(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { //tests that -misc field can evaluate to a number. SWUtils.evaluateAndSetNumber(skill + '-misc', skill + '-misc-mod', 0); } TAS.debug("calling evalute for "+skill); SWUtils.evaluateAndAddToTotAsync(null, null, skill + '-misc', skill + '-misc-mod', skill); })); on("change:" + skill + "-cs", TAS.callback(function eventClassSkillCheckbox(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { updateSkillByClassChkAsync(skill); } })); //these always displayed if rt or not if (skill.slice(0, 9) !== "Knowledge" && skill !== "Linguistics" && skill !== "Sleight-of-Hand") { on("change:" + skill + "-ReqTrain change:" + skill + "-ranks", TAS.callback(function eventSkillRequiresTrainingRanks(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event for " + skill + ", " + eventInfo.sourceType); if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { getAttrs(["enforce_requires_training"], function (v) { if (parseInt(v.enforce_requires_training,10)) { resetCommandMacro(eventInfo); } }); } })); } //end of skill loop }); //skills affected by size _.each(sizeSkills, function (mult, skill) { if (mult === 1) { on("change:size_skill", TAS.callback(function eventUpdateSizeSkill(eventInfo) { if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); updateSkillAsync(skill); } })); } else if (mult === 2) { on("change:size_skill_double", TAS.callback(function eventUpdateSizeSkillDouble(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api") { updateSkillAsync(skill); } })); } }); on("change:enforce_requires_training", TAS.callback(function eventRequiresTraining(eventInfo) { if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); resetCommandMacro(eventInfo); } })); _.each(SWUtils.cartesianAppend(allFillInSkillInstances, ["-name"]), function (skill) { on("change:" + skill, TAS.callback(function eventSkillsWithFillInNames(eventInfo) { if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); var rt = skill.slice(0, -4) + "ReqTrain", r = skill.slice(0, -4) + "ranks"; //if we changed name on a skill that isn't choose-able don't bother. getAttrs(["enforce_requires_training", rt, r, "unchained_skills-show", "BG-Skill-Use", "artistry_skills-macro", "lore_skills-macro", "craft_skills-macro", "knowledge_skills-macro", "perform_skills-macro", "profession_skills-macro", "misc-skill_skills-macro", "is_npc", "include_skill_totals", "NPC-craft_skills-macro", "NPC-knowledge_skills-macro", "NPC-perform_skills-macro", "NPC-profession_skills-macro", "NPC-misc-skill_skills-macro"], function (v) { var isrt = parseInt(v.enforce_requires_training, 10), bg = 0, isNPC = parseInt(v.is_npc, 10) || 0, showBonus = parseInt(v.include_skill_totals, 10) || 0; if (!(isrt && parseInt(v[rt], 10) && isNaN(parseInt(v[r], 10)))) { bg = isNPC ? 0 : ((parseInt(v["unchained_skills-show"], 10) || 0) && (parseInt(v["BG-Skill-Use"], 10) || 0)); //TAS.debug"calling updatesubskillmacro: bg:" + bg + ",isrt:" + isrt); updateSubSkillMacroBook(bg, isrt, eventInfo, v, isNPC, showBonus); } }); } })); }); //reset based on config changes on("change:unchained_skills-show change:BG-Skill-Use change:include_skill_totals", TAS.callback(function eventResetUnchainedSkills(eventInfo) { TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); if (eventInfo.sourceType === "player" || eventInfo.sourceType === "api") { recalculate(eventInfo, function(){resetCommandMacro(eventInfo);}); } })); } registerEventHandlers();