diff --git a/Pathfinder Community/src/PFSheet.js b/Pathfinder Community/src/PFSheet.js new file mode 100644 index 0000000000..43034e7e74 --- /dev/null +++ b/Pathfinder Community/src/PFSheet.js @@ -0,0 +1,853 @@ +'use strict'; +import _ from 'underscore'; +import {PFLog, PFConsole} from './PFLog'; +import TAS from './TheAaronSheet.js'; +import * as SWUtils from './SWUtils'; +import PFConst from './PFConst'; +import * as PFUtils from './PFUtils'; +import * as PFCustom from './PFCustom'; +import * as PFMigrate from './PFMigrate'; +import * as PFDefense from './PFDefense'; +import * as PFSize from './PFSize'; +import * as PFUtilsAsync from './PFUtilsAsync'; +import * as PFInitiative from './PFInitiative'; +import * as PFSkills from './PFSkills'; +import * as PFEncumbrance from './PFEncumbrance'; +import * as PFInventory from './PFInventory'; +import * as PFAbilityScores from './PFAbilityScores'; +import * as PFBuffs from './PFBuffs'; +import * as PFSaves from './PFSaves'; +import * as PFHealth from './PFHealth'; +import * as PFChecks from './PFChecks'; +import * as PFAbility from './PFAbility'; +import * as PFNPC from './PFNPC'; +import * as PFAttackGrid from './PFAttackGrid'; +import * as PFAttackOptions from './PFAttackOptions'; +import * as PFAttacks from './PFAttacks'; +import * as PFFeatures from './PFFeatures'; +import * as PFSpells from './PFSpells'; +import * as PFSpellCasterClasses from './PFSpellCasterClasses'; +import * as PFPsionic from './PFPsionic'; +import * as PFMythic from './PFMythic'; +import * as PFClassRaceGrid from './PFClassRaceGrid'; +import * as PFConditions from './PFConditions'; +import * as PFHorror from './PFHorror'; +import * as PFOccult from './PFOccult'; +import * as PFMacros from './PFMacros'; + +function expandAll() { + getAttrs(['expandall'], function (v) { + var skilltab = '4', + setter = {}; + if (parseInt(v['expandall'], 10)) { + //set expandall to 0 + //set tabs to "all" + //set conditions and buffs to "show" + //set all others to default (which is "show") + SWUtils.setWrapper({ + expandall: '0', + pagetab: '99', + buffs_tab: '99', + abilities_tab: '99', + 'npc-abilities_tab': '99', + skills_tab: '99', + spellclass_tab: '99', + spells_tab: '99', + npc_spellclass_tab: '99', + equipment_tab: '99', + 'conditions-show': 1, + 'buffstop-show': 1, + 'command-buttons-show': 1, + 'NPC-command-buttons-show': 1, + 'character-details-show': 1, + 'ability-scores-show': 1, + 'class-info-show': 1, + class1_show: 1, + class2_show: 1, + class3_show: 1, + 'health-and-wounds-show': 1, + 'initiative-show': 1, + 'macro-text-show': 1, + 'notes-show': 1, + 'saves-show': 1, + 'all-saves-macro-show': 1, + 'save-notes-show': 1, + 'defense-values-show': 1, + 'armor-shield-show': 1, + 'sanity-show': 1, + 'defense-notes-show': 1, + 'attack-bonuses-show': 1, + atkm2_show: 1, + ranged_2_show: 1, + cmb_2_show: 1, + 'attack-notes-show': 1, + 'attack-options-show': 1, + 'two-weapon-show': 1, + 'attacks-show': 1, + 'skill-ranks-show': 1, + 'skill_options-show': 1, + 'skills-show': 1, + 'artistry-show': 1, + 'craft-show': 1, + 'knowledge-show': 1, + 'lore-show': 1, + 'perform-show': 1, + 'profession-show': 1, + 'misc-show': 1, + 'skill-notes-show': 1, + 'ability-command-buttons-show': 1, + 'NPC-ability-command-buttons-show': 1, + 'feats-show': 1, + 'mythic-info-show': 1, + 'psionic-info-show': 1, + 'burn-show': 1, + 'abilities-show': 1, + 'spellclasses-show': 1, + 'spellclass-0-show': 1, + 'spellclass-0-spellpoints-show': 1, + 'spellclass-0-spells-notes-show': 1, + 'spellclass-0-perday-show': 1, + 'spellclass-0-domains-show': 1, + domain02_show: 1, + domain03_show: 1, + 'spellclass-1-show': 1, + 'spellclass-1-spellpoints-show': 1, + 'spellclass-1-spells-notes-show': 1, + 'spellclass-1-perday-show': 1, + 'spellclass-1-domains-show': 1, + 'spellclass-2-show': 1, + 'spellclass-2-spellpoints-show': 1, + 'spellclass-2-spells-notes-show': 1, + 'spellclass-2-perday-show': 1, + 'spellclass-2-domains-show': 1, + 'spelloptions-show': 1, + 'spell-lists-show': 1, + 'currency-show': 1, + 'carried-weight-show': 1, + 'loads-show': 1, + 'worn-items-show': 1, + 'other-items-show': 1, + 'equipment-show': 1, + 'npc-compimport-show': 1, + 'npc-details-show': 1, + 'npc-defense-show': 1, + 'npc-offense-show': 1, + 'npc-speed-show': 1, + 'npc-repeating-weapons-show': 1, + 'npc-spells-show': 1, + 'npc-spell-like-abilities-show': 1, + 'npc-tactics-show': 1, + 'npc-statistics-show': 1, + 'npc-feats-show': 1, + 'npc-mythic-feats-show': 1, + 'npc-skills-show': 1, + 'npc-ecology-show': 1, + 'npc-special-abilities-show': 1, + 'custom-attr-sect-a-show': 1, + 'custom-attr-sect-c-show': 1, + 'custom-attr-sect-b-show': 1, + 'custom-attr-sect-d-show': 1, + 'custom-attr-sect-n-show': 1, + 'header-image-show': 1, + 'sheet-import-show': 1, + 'roll-template-info-show': 1, + 'macros-show': 1, + 'migrations-show': 1, + 'cleanup-show': 1, + 'san-show': 1, + 'buff-min-show': 0, + 'buff-expand-show': 0, + 'buff-column-show': 0, + 'weapon-min-show': 0, + 'weapon-expand-show': 0, + 'weapon-column-show': 0, + 'abilities-min-show': 0, + 'abilities-expand-show': 0, + 'abilities-column-show': 0, + 'class-ability-min-show': 0, + 'class-ability-expand-show': 0, + 'class-ability-column-show': 0, + 'feat-min-show': 0, + 'feat-expand-show': 0, + 'feat-column-show': 0, + 'mythic-feats-min-show': 0, + 'mythic-feats-expand-show': 0, + 'mythic-feats-column-show': 0, + 'racial-trait-min-show': 0, + 'racial-trait-expand-show': 0, + 'racial-trait-column-show': 0, + 'traits-min-show': 0, + 'traits-expand-show': 0, + 'traits-column-show': 0, + 'npc-spell-like-abilities-min-show': 0, + 'npc-spell-like-abilities-expand-show': 0, + 'npc-spell-like-abilities-column-show': 0, + 'mythic-min-show': 0, + 'mythic-expand-show': 0, + 'mythic-column-show': 0, + 'newspells-min-show': 0, + 'newspells-expand-show': 0, + 'newspells-column-show': 0, + 'item-min-show': 0, + 'item-expand-show': 0, + 'item-column-show': 0, + 'npcweapon-min-show': 0, + 'npcweapon-expand-show': 0, + 'npcweapon-column-show': 0, + 'npcnewspells-min-show': 0, + 'npcnewspells-expand-show': 0, + 'npcnewspells-column-show': 0, + 'npcfeat-min-show': 0, + 'npcfeat-expand-show': 0, + 'npcfeat-column-show': 0, + 'npcmythic-feats-min-show': 0, + 'npcmythic-feats-expand-show': 0, + 'npcmythic-feats-column-show': 0, + 'npc-abilities-min-show': 0, + 'npc-abilities-expand-show': 0, + 'npc-abilities-column-show': 0, + 'npc-special-abilities-min-show': 0, + 'npc-special-abilities-expand-show': 0, + 'npc-special-abilities-column-show': 0, + extra_fields_san_show: 1, + extra_fields_attacks_show: 1, + skill_onetimecolumns_show: 1, + extra_fields_saves_show: 1, + extra_fields_spells_show: 1, + extra_fields_caster_show: 1, + extra_fields_abilities_show: 1, + extra_fields_init_show: 1, + extra_fields_speeds_show: 1, + extra_fields_defense_show: 1, + extra_fields_conditions_show: 1, + }); + //now go through repeating sections and expand those to be sure users can see them. + _.each(PFConst.repeatingSections, function (section) { + var rsection = 'repeating_' + section; + getSectionIDs(rsection, function (ids) { + var setter = _.reduce( + ids, + function (memo, id) { + var prefix = rsection + '_' + id + '_'; + switch (section) { + case 'weapon': + memo[prefix + 'misc-show'] = 1; + memo[prefix + 'add-damage-show'] = 1; + memo[prefix + 'iterative-attacks-show'] = 1; + memo[prefix + 'advmacro-text-show'] = 1; + break; + case 'buff': + memo[prefix + 'options-show'] = 1; + memo[prefix + 'description-show'] = 1; + break; + case 'buff2': + memo[prefix + 'options-show'] = 1; + memo[prefix + 'description-show'] = 1; + break; + case 'spells': + memo[prefix + 'misc-show'] = 1; + memo[prefix + 'attack-show'] = 1; + memo[prefix + 'description-show'] = 1; + break; + case 'class-ability': + case 'feat': + case 'racial-trait': + case 'trait': + case 'mythic-ability': + case 'mythic-feat': + memo[prefix + 'description-show'] = 1; + break; + case 'item': + memo[prefix + 'description-show'] = 1; + memo[prefix + 'armor-attributes-show'] = 1; + memo[prefix + 'weapon-attributes-show'] = 1; + break; + case 'npc-spell-like-abilities': + memo[prefix + 'attack-show'] = 1; + break; + case 'ability': + memo[prefix + 'options-show'] = 1; + memo[prefix + 'description-show'] = 1; + memo[prefix + 'misc-show'] = 1; + memo[prefix + 'showextrafields'] = 1; + memo[prefix + 'range-show'] = 1; + memo[prefix + 'attack-show'] = 1; + break; + } + memo[prefix + 'row-show'] = 1; + memo[prefix + 'ids-show'] = 1; + if (section !== 'buff' && section !== 'buff2') { + memo[prefix + 'macro-text-show'] = 1; + } + return memo; + }, + {}, + ); + SWUtils.setWrapper(setter, { + silent: true, + }); + }); + }); + } + }); +} + +/** Sets any values if sheet created brand new. Makes sure all migrations up to date. + * makes sure NPC value set. + */ +function setupNewSheet(callback) { + var done = _.once(function () { + var setter = {is_newsheet: 0, is_v1: 1, use_buff_bonuses: 1, use_advanced_options: 0, modify_dmg_by_size: 1, PFSheet_Version: String(PFConst.version.toFixed(2))}; + setter[PFConst.announcementVersionAttr] = 1; + SWUtils.setWrapper(setter, PFConst.silentParams, function () { + if (typeof callback === 'function') { + callback(); + } + }); + }); + getAttrs(['is_npc', 'set_pfs'], function (v) { + var isNPC = parseInt(v.is_npc, 10) || 0, + isPFS = parseInt(v.set_pfs, 10) || 0; + PFMigrate.setAllMigrateFlags(function () { + PFSkills.migrate(); + PFCustom.migrate(); + if (isNPC) { + PFNPC.setToNPC(done); + } else if (isPFS) { + PFHealth.setToPFS(done); + } else { + done(); + } + }); + }); +} + +/** Only updates things between versions, normally things that are not part of migrate() + *@param {number} oldversion the current version attribute + *@param {function} callback when done if no errors + *@param {function} errorCallback call this if we get an error + */ +function migrate(oldversion, callback, errorCallback) { + var done = _.once(function () { + TAS.debug('leaving PFSheet.migrate'); + if (typeof callback === 'function') { + callback(); + } + }), + errorDone = _.once(function () { + TAS.warn('leaving PFSheet.migrate ERROR UPGRADE NOT FINISHED'); + if (typeof errorCallback === 'function') { + errorCallback(); + } else { + done(); + } + }), + doneOne; + try { + //don't need to check if oldversion > 0 since this is only called if it is. + //TAS.debug("At PFSheet.migrate from oldversion:"+oldversion); + if (oldversion < 1.0) { + doneOne = _.after(7, function () { + TAS.info('PFSheet.migrate we finished calling all the migrates'); + done(); + }); + PFMigrate.migrateConfigFlags( + TAS.callback(function () { + PFInventory.migrate(doneOne, oldversion); + PFSkills.migrate(doneOne, oldversion); + PFHealth.migrate(doneOne, oldversion); + PFAttacks.migrate(doneOne, oldversion); + PFAbility.migrate(doneOne, oldversion); + PFFeatures.migrate(doneOne, oldversion); + PFSpells.migrate(doneOne, oldversion); + }), + oldversion, + ); + } else if (oldversion < 1.17) { + if (oldversion < 1.02) { + PFAbility.migrate(null, oldversion); + PFFeatures.migrate(null, oldversion); + } + if (oldversion < 1.05) { + PFAttackOptions.resetOptions(); + } + if (oldversion < 1.07) { + PFInventory.migrate(null, oldversion); + } + if (oldversion < 1.1) { + PFMigrate.migrateAbilityListFlags(); + PFFeatures.migrate(null, oldversion); + } + if (oldversion < 1.12) { + PFAbility.migrate(null, oldversion); + } + if (oldversion < 1.17) { + PFInventory.migrate(function () { + PFInventory.resetCommandMacro(); + }); + } + } else { + if (oldversion < 1.18) { + //future updates here. any above will recalc whole sheet after callback + PFInitiative.recalculate(null, false, oldversion); + PFHealth.recalculate(null, false, oldversion); + PFMigrate.migrateSpellPointFlag(null, oldversion); + } + if (oldversion < 1.2) { + PFHealth.recalculate(); + } + if (oldversion < 1.4) { + PFMigrate.migrateWhisperDropdowns(); + PFInventory.resetCommandMacro(); + PFAttackGrid.resetCommandMacro(); + PFAbility.resetCommandMacro(); + PFFeatures.resetCommandMacro(); + PFAttacks.recalculate(); + PFClassRaceGrid.setHitPoints(); + } + if (oldversion < 1.43) { + PFSpells.recalculate(); + PFSkills.resetCommandMacro(); + } + if (oldversion < 1.5) { + PFSpells.resetSpellsTotals(null, null, null, true); + PFInventory.updateRepeatingItems(); + PFAttacks.migrateLinkedAttacks(null, oldversion); + } + if (oldversion < 1.53) { + PFSkills.migrate(null, oldversion); + PFSize.recalculate(function () { + PFEncumbrance.migrate(); + }); + } + if (oldversion < 1.54) { + PFBuffs.recalculate(); + } + if (oldversion < 1.55) { + PFAttacks.recalculate(); + PFSkills.migrate(); + } + if (oldversion < 1.56) { + PFAttacks.updateRepeatingWeaponDamages(); + } + if (oldversion < 1.57) { + PFDefense.updateDefenses(); + } + if (oldversion <= 1.65) { + PFCustom.migrate(function () { + PFBuffs.migrate(function () { + PFConditions.migrate(function () { + PFBuffs.recalculate(function () { + PFCustom.recalculate(function () { + PFSkills.migrate(function () { + PFSkills.recalculateSkills(); + }, oldversion); + PFAbility.setRuleTabs(); + PFInventory.updateLocations(); + PFAttackGrid.recalculate(function () { + PFAttacks.adjustAllDamageDiceAsync(); + PFAttacks.updateDualWieldAttacks(); + PFAttacks.recalculateRepeatingWeapons(); + }); + PFInventory.migrate(); + }); + }); + }, oldversion); + }, oldversion); + }, oldversion); + } + if (oldversion < 1.68) { + PFCustom.migrate(function () { + PFSkills.recalculate(null, null, oldversion); + PFDefense.recalculate(null, null, oldversion); + PFAttackGrid.recalculate(null, null, oldversion); + }); + } + if (oldversion < 1.69) { + PFSpellCasterClasses.recalculate(null, null, oldversion); + PFFeatures.recalculate(null, null, oldversion); + PFAttacks.recalculate(null, null, oldversion); + PFAbility.recalculate(null, null, oldversion); + PFInventory.recalculate(null, null, oldversion); + PFBuffs.recalculate(null, null, oldversion); + PFDefense.recalculate(null, null, oldversion); + } + if (oldversion < 1.693) { + PFCustom.fixProfessionDropdowns(PFSkills.recalculate); + } + if (oldversion < 1.697) { + PFAttacks.recalculate(null, null, oldversion); + } + if (oldversion < 1.72) { + PFSize.recalculate(oldversion, function () { + PFAttacks.adjustAllDamageDiceAsync(null, null); + }); + PFSpells.resetSpellsTotals(); + } + if (oldversion < 1.724) { + PFSkills.migrate(null, oldversion); + } + if (oldversion < 1.731) { + PFMacros.checkScrollDesc(); + } + if (oldversion < 1.732) { + PFMacros.checkBaseAttacks(); + } + if (oldversion < 1.787) { + PFAbility.updateIncludeLinkVersionCheck(); + } + if (oldversion < 1.822) { + PFSpells.setCasterTypeSpells(); + } + if (oldversion < 1.826) { + PFMacros.checkAbilityType2(); + } + } + } catch (err) { + TAS.error('PFSheet.migrate', err); + errorDone(); + } finally { + done(); + } +} +function recalculateParallelModules(callback, silently, oldversion) { + var done = _.once(function () { + TAS.info('leaving PFSheet.recalculateParallelModules'); + if (typeof callback === 'function') { + callback(); + } + }), + parallelRecalcFuncs = [ + PFSpellCasterClasses.recalculate, + PFSaves.recalculate, + PFFeatures.recalculate, + PFPsionic.recalculate, + PFSkills.recalculate, + PFAbility.recalculate, + PFInitiative.recalculate, + PFAttacks.recalculate, + PFHorror.recalculate, + PFOccult.recalculate, + ], + numberModules = _.size(parallelRecalcFuncs), + doneOneModuleInner = _.after(numberModules, done), + curr = 0, + currstarted = 0, + doneOneModule = function () { + curr++; + TAS.info('PFSheet.recalculateParallelModules, finished ' + curr + ' modules'); + doneOneModuleInner(); + }; + + //TAS.debug("at recalculateParallelModules! there are "+numberModules +" modules"); + try { + _.each(parallelRecalcFuncs, function (methodToCall) { + try { + currstarted++; + //TAS.info("starting " + currstarted + " parallel modules calling:"+methodToCall); + methodToCall(doneOneModule, silently, oldversion); + } catch (err) { + TAS.error('PFSheet.recalculateParallelModules error calling recalculate', err); + doneOneModule(); + } + }); + } catch (err2) { + TAS.error('PFSheet.recalculateParallelModules OUTER error!', err2); + done(); + } +} +function recalculateDefenseAndEncumbrance(callback, silently, oldversion) { + var done = _.once(function () { + //TAS.debug("leaving PFSheet.recalculateDefenseAndEncumbrance"); + if (typeof callback === 'function') { + callback(); + } + }), + callEncumbrance = function () { + PFEncumbrance.recalculate(done, silently, oldversion); + }, + doneBeforeEncumbrance = _.after(2, callEncumbrance); + try { + PFInventory.recalculate(doneBeforeEncumbrance, silently, oldversion); + PFDefense.recalculate(doneBeforeEncumbrance, silently, oldversion); + } catch (err) { + TAS.error('pfsheet.recalculateDefenseAndEncumbrance', err); + callEncumbrance(); + } +} +function recalculateCore(callback, silently, oldversion) { + var done = _.once(function () { + //TAS.debug("leaving PFSheet.recalculateCore"); + if (typeof callback === 'function') { + callback(); + } + }), + sizeOnce = _.once(function () { + PFSize.recalculate(done, silently, oldversion); + }), + healthOnce = _.once(function () { + PFHealth.recalculate(sizeOnce, silently, oldversion); + }), + npcOnce = _.once(function () { + PFNPC.recalculate(healthOnce, silently, oldversion); + }), + mythicOnce = _.once(function () { + PFMythic.recalculate(npcOnce, silently, oldversion); + }), + customOnce = _.once(function () { + PFCustom.recalculate(mythicOnce, silently, oldversion); + }), + conditioncheckOnce = _.once(function () { + PFChecks.applyConditions(customOnce, silently, oldversion); + }), + classOnce = _.once(function () { + PFClassRaceGrid.recalculate(conditioncheckOnce, silently, oldversion); + }), + abilityScoresOnce = _.once(function () { + PFAbilityScores.recalculate(classOnce, silently, oldversion); + }), + abilityAffectingConditionsOnce = _.once(function () { + PFConditions.recalculate(abilityScoresOnce, silently, oldversion); + }), + buffsOnce = _.once(function () { + PFBuffs.recalculate(abilityAffectingConditionsOnce, silently, oldversion); + }); + + PFMigrate.migrateConfigFlags(buffsOnce, oldversion); + + //TAS.debug("at recalculateCore!!!!"); +} + +/** recalculate - all pages in sheet! + *@param {number} oldversion the current version attribute + *@param {function} callback when done if no errors + *@param {function} errorCallback call this if we get an error + */ +export function recalculate(oldversion, callback, silently) { + var done = function () { + TAS.info('leaving PFSheet.recalculate'); + if (typeof callback === 'function') { + callback(); + } + }, + callParallel = TAS.callback(function callRecalculateParallelModules() { + recalculateParallelModules(done, silently, oldversion); + }), + callEncumbrance = TAS.callback(function callRecalculateDefenseAndEncumbrance() { + recalculateDefenseAndEncumbrance(callParallel, silently, oldversion); + }), + callRecalcCore = TAS.callback(function callRecalculateCore() { + recalculateCore(callEncumbrance, silently, oldversion); + }); + silently = true; + callRecalcCore(); +} +/* checkForUpdate looks at current version of page in PFSheet_Version and compares to code PFConst.version + * calls recalculateSheet if versions don't match or if recalculate button was pressed. + * */ +export function checkForUpdate(forceRecalc) { + var done = function () { + TAS.info('leaving PFSheet.checkForUpdate'); + SWUtils.setWrapper({recalc1: 0, migrate1: 0, is_newsheet: 0}, PFConst.silentParams); + }, + errorDone = _.once(function () { + TAS.warn('leaving PFSheet.checkForUpdate ERROR UPGRADE NOT FINISHED DO NOT RESET VERSION'); + SWUtils.setWrapper({recalc1: 0, migrate1: 0}, {silent: true}); + }); + getAttrs( + [ + 'PFSheet_Version', + 'migrate1', + 'recalc1', + 'is_newsheet', + 'is_v1', + 'hp', + 'hp_max', + 'npc-hd', + 'npc-hd-num', + 'race', + 'class-0-name', + 'npc-type', + 'level', + 'hide_announcements', + PFConst.announcementVersionAttr, + ], + function (v) { + var setter = {}, + setAny = 0, + migrateSheet = false, + newSheet = false, + recalc = false, + currVer = parseFloat(v.PFSheet_Version, 10) || 0, + setUpgradeFinished = function () { + SWUtils.setWrapper( + {recalc1: 0, migrate1: 0, is_newsheet: 0, character_sheet: 'Pathinder_Neceros v' + String(PFConst.version), PFSheet_Version: String(PFConst.version.toFixed(3))}, + PFConst.silentParams, + function () { + if (currVer < 1.17) { + recalculate(currVer, null, false); + } + }, + ); + }; + TAS.notice('Attributes at version: ' + currVer); + + if (forceRecalc) { + recalc = true; + } else { + if (parseInt(v['recalc1'], 10)) { + //HIT RECALC + recalc = true; + } + if (parseInt(v['migrate1'], 10)) { + migrateSheet = true; + } + if ( + parseInt(v['is_newsheet'], 10) || + (currVer === 0 && + (parseInt(v.is_v1, 10) || + !( + parseInt(v.hp, 10) || + parseInt(v.hp_max, 10) || + parseInt(v['npc-hd'], 10) || + parseInt(v['npc-hd-num'], 10) || + v.race || + v['class-0-name'] || + v['npc-type'] || + parseInt(v['level'], 10) + ))) + ) { + //NEW SHEET: + newSheet = true; + } + } + //force this on sheet open, not sure wtf is wrong + if (currVer !== PFConst.version) { + migrateSheet = true; + } + if (!newSheet && parseInt(v.hide_announcements, 10) && !parseInt(v[PFConst.announcementVersionAttr], 10)) { + setter[PFConst.announcementVersionAttr] = 1; + SWUtils.setWrapper(setter, PFConst.silentParams); + } + if (newSheet) { + setupNewSheet(done); + } else if (migrateSheet) { + TAS.notice('UPGRADE SHEET'); + migrate(currVer, setUpgradeFinished, errorDone); + } else if (recalc) { + currVer = -1; + recalculate(currVer, done, true); + } else { + done(); + } + }, + ); +} + +function registerEventHandlers() { + var eventToWatch = ''; + + on( + 'sheet:opened', + TAS.callback(function eventSheetOpened() { + //eventInfo has undefined values for this event. + checkForUpdate(); + }), + ); + on( + 'change:recalc1 change:migrate1', + TAS.callback(function eventRecalculateSheet(eventInfo) { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + checkForUpdate(); + } + }), + ); + on('change:expandall', function (eventInfo) { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + expandAll(); + } + }); + + on( + 'change:repeating_weapon:source-item', + TAS.callback(function eventUpdateAttackSourceItem(eventInfo) { + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + getAttrs([eventInfo.sourceAttribute], function (v) { + var weaponId = SWUtils.getRowId(eventInfo.sourceAttribute), + sourceId = v[eventInfo.sourceAttribute]; + //TAS.debug("PFSheet new item id: " + sourceId + " this row weapon id: "+weaponId, v); + if (sourceId) { + sourceId = 'repeating_item_' + sourceId + '_create-attack-entry'; + PFInventory.createAttackEntryFromRow(sourceId, null, false, weaponId); + } + }); + } + }), + ); + on( + 'change:repeating_weapon:source-ability', + TAS.callback(function eventUpdateAttackSourceAbility(eventInfo) { + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + getAttrs([eventInfo.sourceAttribute], function (v) { + var weaponId = SWUtils.getRowId(eventInfo.sourceAttribute), + sourceId = v[eventInfo.sourceAttribute]; + if (sourceId) { + PFAbility.createAttackEntryFromRow(sourceId, null, false, null, weaponId); + } + }); + } + }), + ); + on( + 'change:repeating_weapon:source-spell', + TAS.callback(function eventUpdateAttackSourceSpell(eventInfo) { + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + getAttrs([eventInfo.sourceAttribute], function (v) { + var weaponId = SWUtils.getRowId(eventInfo.sourceAttribute), + sourceId = v[eventInfo.sourceAttribute]; + if (sourceId) { + PFSpells.createAttackEntryFromRow(sourceId, null, false, null, weaponId); + } + }); + } + }), + ); + + //delete a list + on( + 'change:delete_rep_spells change:delete_rep_weapon change:delete_rep_item change:delete_rep_ability change:delete_rep_mythic-feat change:delete_rep_mythic-ability change:delete_rep_buff change:delete_rep_buff2 change:delete_rep_trait change:delete_rep_racial-trait change:delete_rep_feat change:delete_rep_class-ability change:delete_rep_npc-spell-like-abilities', + TAS.callback(function eventDeleteOldList(eventInfo) { + TAS.debug('caught ' + eventInfo.sourceAttribute + ' event: ' + eventInfo.sourceType); + if (eventInfo.sourceType === 'player' || eventInfo.sourceType === 'api') { + getAttrs([eventInfo.sourceAttribute], function (v) { + var section = ''; + if (parseInt(v[eventInfo.sourceAttribute], 10)) { + section = eventInfo.sourceAttribute.replace('delete_rep_', ''); + SWUtils.deleteRepeating(function () { + var setter; + setter = {}; + setter[eventInfo.sourceAttribute] = 0; + setter[eventInfo.sourceAttribute + '_btn'] = 0; + SWUtils.setWrapper(setter, {silent: true}); + if (/buff/i.test(eventInfo.sourceAttribute)) { + PFBuffs.clearBuffTotals(); + } + }, section); + } + }); + } + }), + ); + + on('change:hide_announcements', function () { + var setter = {}; + setter[PFConst.announcementVersionAttr] = 1; + setAttrs(setter, PFConst.silentParams); + }); +} +registerEventHandlers(); diff --git a/Pathfinder Community/src/PFSize.js b/Pathfinder Community/src/PFSize.js new file mode 100644 index 0000000000..c3f00ac8d9 --- /dev/null +++ b/Pathfinder Community/src/PFSize.js @@ -0,0 +1,407 @@ +'use strict'; +import _ from 'underscore'; +import {PFLog, PFConsole} from './PFLog'; +import TAS from './TheAaronSheet.js'; +import * as SWUtils from './SWUtils'; +import PFConst from './PFConst'; +import * as PFUtils from './PFUtils'; +import * as PFMigrate from './PFMigrate'; +import * as PFEncumbrance from './PFEncumbrance'; +import * as PFAttacks from './PFAttacks'; + +//these use strings on the left so javascript doesn't confuse the number with array indices +//converts from the attack mod value to easy 0 through 8 +export var sizeModToEasySizeMap={ + '0':4, //medium + '1':3, //small + '2':2, //tiny + '-8':8, //colossal + '4':1, //diminutive + '-4':7, //gargantuan + '-2':6, //huge + '-1':5, //large + '8':0 //fine +}, +reverseSizeMap={ + '0':8, //fine + '1':4, //diminutive + '2':2, //tiny + '3':1, //small + '4':0, //medium + '5':-1, //large + '6':-2, //huge + '7':-4, //gargantuan + '8':-8 //colossal +}, +skillSizeMap = { + '0':0, //medium + '1':2, //small + '2':4, //tiny + '8':8, //diminutive + '4':6, //fine + '-8':-8, //colossal + '-4':-6, //gargantuan + '-2':-4, //huge + '-1':-2 //large +}, +sizeNameMap = { + 'colossal':-8, + 'gargantuan':-4, + 'huge':-2, + 'large':-1, + 'medium':0, + 'small':1, + 'tiny':2, + 'diminutive':4, + 'fine':8 +}, +reverseSizeNameMap = { + '0':'medium', + '1':'small', + '2':'tiny', + '-8':'colossal', + '4':'diminutive', + '-4':'gargantuan', + '-2':'huge', + '-1':'large', + '8':'fine' +}; + +/** getSizeFromText - returns size mod based on size display name + * @param {string} sizeDisplay size in english (medium, large, gargantuan, tiny, etc) + * @returns {jsobj} map of {"size":size mod for AC,"skillSize": size mod for fly} + */ +export function getSizeFromText (sizeDisplay) { + var sizeMap = { + "size": 0, + "skillSize": 0 + }; + try { + if (sizeDisplay) { + sizeDisplay = SWUtils.trimBoth(sizeDisplay); + sizeDisplay = sizeDisplay.toLowerCase(); + sizeMap.size=sizeNameMap[sizeDisplay]; + sizeMap.skillSize = skillSizeMap[String(sizeMap.size)]; + } + } catch (err) { + TAS.error("get size from text:" + sizeDisplay, err); + sizeMap.size = 0; + sizeMap.skillSize = 0; + } finally { + return sizeMap; + } +} +/**returns number of levels size went up or down + * ex: Med to Lg is +1, Med to Sm is -1, Md to Tiny is -2, etc + * @param {int} currSize new size mod , usually value of @{size} + * @param {int} defaultSize default size mod, for sheet it is value of @{default_char_size} + * for weapon it is @{repeating_weapon_$X_default_size} + * @returns {int} difference in sizes (not difference in size mods) + */ +export function getSizeLevelChange (currSize,defaultSize) { + var newSize,oldSize,levelChange; + newSize=sizeModToEasySizeMap[String(currSize)]; + oldSize=sizeModToEasySizeMap[String(defaultSize)]; + levelChange = newSize-oldSize; + return levelChange; +} +/**updateDamageDice returns new dice for weapon/attack damage change due to size + *@param {Number} sizediff difference in LEVELS of size (Medium to Large is 1, Medium to Small is -1) + *@param {Number} defaultSize size modifier, necessary since different rules for small + *@param {Number} currDice num dice from 1 to n + *@param {Number} currDie num sides of die : valid only from 1 to 12 + *@returns {jsobj} {dice:n,die:n} + */ +export function updateDamageDice (sizediff,defaultSize,currDice,currDie){ + var diceSizes = { 1:["1d1"], 2:["1d2"], 3:["1d3"], + 4:["1d4"], + 5:["1d6"], + 6:["1d8","2d4"], + 7:["1d10"], + 8:["2d6","3d4","1d12"], + 9:["2d8","4d4"], 10:["3d6","5d4"], 11:["3d8","6d4","2d10"], + 12:["4d6","7d4","2d12"], 13:["4d8","8d4","9d4","5d6","3d10"], + 14:["6d6","5d8","10d4","11d4","9d4","3d12"], + 15:["6d8","7d6","12d4","13d4","4d10"], + 16:["8d6","7d8","14d4","15d4","4d12"], + 17:["8d8","16d4","9d6","10d6","11d6","5d10","17d4","18d4","19d4","5d12"], + 18:["12d6","20d4","9d8","7d10","6d12","21d4","22d4","23d4"], + 19:["12d8","24d4","13d6","14d6","15d6","8d10"], + 20:["16d6","13d8","10d10","8d12"] + }, + currSize=0, + dicestring="", + newDice=0,newDie=0,matches, + rowdiff=0, currow=0, newrow=0, newrowstring="", + reversedDiceSizes=_.reduce(diceSizes,function(memo,pairs,idx){ + _.each(pairs,function(pair){ memo[pair]=idx; }); + return memo; + },{}); + try { + //TAS.debug("PFSize.updateDamageDice defSize:"+defaultSize+", diff:"+sizediff+", dice:"+currDice+"d"+currDie); + currDice=parseInt(currDice,10); + currDie=parseInt(currDie,10); + if(!(isNaN(currDice)||isNaN(currDie))){ + dicestring=currDice+"d"+currDie; + currSize=sizeModToEasySizeMap[String(defaultSize)]; + //TAS.debug("currSize now : "+currSize); + if (currDice<=0 || currDie > 12 ) {return null;} + if (currDie===4 && currDice >24){ currDice=24;} + else if (currDie===6 && currDice > 16) {currDice=16;} + else if (currDie===8 && currDice > 13) {currDice=13;} + else if (currDie===10 && currDice > 10) {currDice=10;} + else if (currDie===12 && currDice > 8) {currDice=8;} + currow=parseInt(reversedDiceSizes[dicestring],10)||0; + if (!currow){return null;} + while (sizediff !== 0){ + if (sizediff > 0){ + if ((currDie<=6 && currDice===1)|| currSize <=3) { + rowdiff=1; + } else { + rowdiff=2; + } + } else if (sizediff<0) { + if ((currDie<=8 && currDice===1)||currSize<=4 ) { + rowdiff=-1; + } else { + rowdiff = -2; + } + } + newrow = currow + rowdiff; + newrow = Math.min(Math.max(newrow,1),20); + dicestring = diceSizes[newrow][0]; + //TAS.debug("PFSize "+currDice+"d"+currDie+" is currrow:"+currow+" going from size:"+currSize+" of diff:"+sizediff+", move "+rowdiff+" levels to "+ newrow+" dice is "+dicestring); + matches=dicestring.match(/(\d+)d(\d+)/); + currDice=parseInt(matches[1],10); + currDie=parseInt(matches[2],10); + currow =newrow; + if (sizediff >0 ) { + currSize++; + sizediff--; + if (currow>=20){ + TAS.warn("PFSize.updateDamageDice increased off top of grid to row "+currow); + break; + } + } else { + currSize--; + sizediff++; + if (currow<=1) { + TAS.warn("PFSize.updateDamageDice decreased under bottom of grid to row "+currow); + break; + } + } + //TAS.debug("updateDamageDice: currow is now"+currow+", diff still:"+sizediff); + } + } + } catch(err){ + TAS.error("updateDamageDice: ",err); + } finally { + return {"dice":currDice,"die":currDie}; + } +} +/** sets the size variables based on the string passed in + * + * @param {string} str the name of the size in english + * @param {Map} setter to pass to setAttrs + */ +export function setSizeFromString(str,setter){ + var tempstr='',sizeMap={}; + try { + sizeMap = getSizeFromText(str); + if (sizeMap && sizeMap.size !== 0) { + setter.size = sizeMap.size; + setter['default_char_size']=sizeMap.size; + setter.size_skill = sizeMap.skillSize; + setter["CMD-size"] = (sizeMap.size * -1); + setter.size_skill_double = (sizeMap.skillSize * 2); + } else { + setter['size']=0; + setter['default_char_size']=0; + setter.size_skill = 0; + setter["CMD-size"] = 0; + setter.size_skill_double =0; + if (!sizeMap){ + sizeMap = {'size':0,'skillSize':0}; + } + } + //tempstr=reverseSizeNameMap[String(sizeMap.size)]; + setter['size_display']=SWUtils.getTranslated(str.toLowerCase()); + } finally { + return sizeMap + } +} + +function setSizeDisplay (size,v,setter){ + var tempstr='',sizeDisplay=''; + setter=setter||{}; + tempstr=reverseSizeNameMap[String(size)]; + sizeDisplay=SWUtils.getTranslated(tempstr); + if (sizeDisplay && (sizeDisplay!== v.size_display || !v.size_display)){ + setter.size_display=sizeDisplay; + } + return setter; +} + +/** Overwrites any current change to size with change to size from buffs + * + * @param {*} levelChange + * @param {*} v + * @param {*} eventInfo + * @param {*} setter + */ +export function updateSize (levelChange,v,eventInfo,setter) { + var size = 0,newSize=0, defaultSize=0,deflevel=0,newlevel=0, skillSize = 0; + try { + setter=setter||{}; + defaultSize = parseInt(v.default_char_size,10)||0; + //if levelchange is 0, then we are resetting it to defaultSize + newSize=defaultSize; + if (levelChange!==0 ){ + deflevel = sizeModToEasySizeMap[String(defaultSize)]; + newlevel = deflevel+levelChange; + newSize = reverseSizeMap[String(newlevel)]; + } + + size = parseInt(v['size'], 10) || 0; + if (newSize!==size){ + setter['size']=newSize; + setter["CMD-size"] = (newSize * -1); + skillSize = skillSizeMap[String(newSize)]; + setter.size_skill = skillSize; + setter.size_skill_double = (2*skillSize); + } else { + //if the same, we may be resetting, so check each one: + if ((parseInt(v["CMD-size"],10)||0) !== (newSize *-1)){ + setter["CMD-size"] = (newSize * -1); + } + skillSize = skillSizeMap[String(newSize)]; + if ((parseInt(v.size_skill,10)||0) !== skillSize){ + setter.size_skill = skillSize; + } + if ((parseInt(v.size_skill_double,10)||0) !== (2*skillSize)){ + setter.size_skill_double = (2*skillSize); + } + } + setSizeDisplay(newSize,v,setter); + } catch (err) { + TAS.error("PFSize.updateSize", err); + } finally { + TAS.debug("PFSize.updateSize returning with ",setter); + return setter; + } +} +/** + * + * @param {function(Number)} callback call with the value of the change in sizes so if 0 we know there was no change + * @param {*} silently + * @param {*} eventInfo + */ +function updateSizeAsync (callback, silently,eventInfo) { + var done = function (change) { + if (typeof callback === "function") { + callback(change); + } + }; + getAttrs(["size", "size_skill","size_skill_double", "default_char_size", "CMD-size", "buff_size-total","size_display"], function (v) { + var params = {}, + setter = {}, + levelChange=0,currSize=0,defSize=0,ddLevelChange=0,forceResize=0; + try { + //if updating buff just overwrite the size dropdown + if (eventInfo && eventInfo.sourceAttribute==='buff_size-total'){ + levelChange=parseInt(v['buff_size-total'],10)||0; + updateSize(levelChange,v,eventInfo,setter); + } else if (!eventInfo || eventInfo.sourceAttribute==='size'){ + //user must remove the buff if they want to use the dropdown + levelChange=parseInt(v['buff_size-total'],10)||0; + if (levelChange===0){ + currSize=parseInt(v.size,10)||0; + defSize=parseInt(v.default_char_size,10)||0; + levelChange=getSizeLevelChange(currSize,defSize); + if (levelChange===0 ){ + if (eventInfo){ + if ((parseInt(eventInfo.previousValue,10)||0) !== (parseInt(eventInfo.newValue,10)||0)){ + forceResize=(parseInt(eventInfo.newValue,10)||0) - (parseInt(eventInfo.previousValue,10)||0); + } + } + } + } + updateSize(levelChange,v,eventInfo,setter); + } else if (eventInfo.sourceAttribute==='default_char_size'){ + //just pass the new level diff to callback + currSize=parseInt(v.size,10)||0; + defSize=parseInt(v.default_char_size,10)||0; + levelChange=getSizeLevelChange(currSize,defSize); + } else { + TAS.warn("Called updateSizeAsync with unexpected event:",eventInfo); + } + + } catch (err) { + TAS.error("PFSize.updateSizeAsync", err); + } finally { + if (_.size(setter) > 0) { + //TAS.debug("PFSize.updateSizeAsync, setting:",setter); + if (silently) { + params = PFConst.silentParams; + } + SWUtils.setWrapper(setter, params, function(){done(levelChange);}); + } else if (levelChange!==0 || forceResize !== 0){ + done(1); + } else { + done(0); + } + } + }); +} +function setNewSize(eventInfo){ + updateSizeAsync(function(changed){ + if (changed ){ + PFEncumbrance.updateLoadsAndLift(); + PFAttacks.adjustAllDamageDiceAsync(null,eventInfo); + } + },false,eventInfo); + // changing size back to medium would never update DamageDice + PFEncumbrance.updateLoadsAndLift(); + PFAttacks.adjustAllDamageDiceAsync(null,eventInfo); +} +function applyNewSizeToSheet(eventInfo){ + //TAS.debug("PFSize.applyNewSizeToSheet"); + PFEncumbrance.updateLoadsAndLift(); + PFAttacks.adjustAllDamageDiceAsync(null,eventInfo); +} +export function migrate (callback){ + if (typeof callback === "function") { + callback(); + } +} +export var recalculate = TAS.callback(function PFSizeRecalculate(callback, silently, oldversion) { + var done = _.once(function () { + TAS.info("leaving PFSize.recalculate"); + if (typeof callback === "function") { + callback(); + } + }); + updateSizeAsync(done, silently,null); +}); +function registerEventHandlers () { + //size + on("change:size change:default_char_size", TAS.callback(function eventUpdateSize(eventInfo) { + var prevv=0,newv=0; + TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType ); + TAS.debug(eventInfo); + if (eventInfo.sourceType === "player" ) { + setNewSize(eventInfo); + } else { + //if sheetworker then it may be a loop, so don't change size just make sure everything is using the new size. + //this will happen every time we call with buffs so it will be called twice + //applyNewSizeToSheet(eventInfo); + } + })); + on("change:size change:buff_size-total", TAS.callback(function eventUpdateSize(eventInfo) { + TAS.debug("caught " + eventInfo.sourceAttribute + " event: " + eventInfo.sourceType); + if (eventInfo.sourceType === "sheetworker" || eventInfo.sourceType === "api" ) { + setNewSize(eventInfo); + } + })); +} +registerEventHandlers(); diff --git a/Pathfinder Community/src/PFSkills.js b/Pathfinder Community/src/PFSkills.js new file mode 100644 index 0000000000..da279f79b7 --- /dev/null +++ b/Pathfinder Community/src/PFSkills.js @@ -0,0 +1,1130 @@ +'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();