(() => { 'use strict'; // The name used by this script to send alerts to the GM in the chat. const CHAT_NAME = 'ItsATrap-PF-Simple'; // A mapping of saving throw short names to their attribute names. const SAVE_NAMES = { 'fort': 'fort', 'ref': 'ref', 'will': 'will' }; // Register the theme with ItsATrap. on('ready', () => { // Initialize state if(!state.TrapThemePFSimple) state.TrapThemePFSimple = { trapSpotterAttempts: {} }; /** * A theme for the Pathfinder (Simple) OGL character sheet by Stephen L.. */ class TrapThemePFSimple extends D20TrapTheme { /** * @inheritdoc */ get name() { return 'PF-Simple'; } /** * @inheritdoc */ getAC(character) { return CharSheetUtils.getSheetAttr(character, 'ac'); } /** * @inheritdoc */ getPassivePerception(character) { return CharSheetUtils.getSheetAttr(character, 'perception') .then(perception => { return perception + 10; }); } /** * @inheritdoc */ getSaveBonus(character, saveName) { return CharSheetUtils.getSheetAttr(character, SAVE_NAMES[saveName]); } /** * @inheritdoc */ getThemeProperties(trapToken) { let result = super.getThemeProperties(trapToken); let save = _.find(result, item => { return item.id === 'save'; }); save.options = [ 'none', 'fort', 'ref', 'will' ]; return result; } /** * @inheritdoc * Also supports the Trap Spotter ability. */ passiveSearch(trap, charToken) { super.passiveSearch(trap, charToken); let character = getObj('character', charToken.get('represents')); let effect = (new TrapEffect(trap, charToken)).json; // Perform automated behavior for Trap Spotter. if((effect.type === 'trap' || _.isUndefined(effect.type)) && effect.spotDC && character) { CharSheetUtils.getSheetRepeatingRow(character, 'classabilities', rowAttrs => { if(!rowAttrs.name) return false; let abilityName = rowAttrs.name.get('current'); return abilityName.toLowerCase().includes('trap spotter'); }) .then(trapSpotter => { let dist = ItsATrap.getSearchDistance(trap, charToken); if(trapSpotter && dist <= 10) this._trapSpotter(character, trap, effect); }) .catch(err => { sendChat('Trap theme: ' + this.name, '/w gm ' + err.message); log(err.stack); }); } } /** * Trap Spotter behavior. * @private */ _trapSpotter(character, trap, effect) { // Quit early if this character has already attempted to trap-spot // this trap. let trapId = trap.get('_id'); let charId = trap.get('_id'); if(!state.TrapThemePFSimple.trapSpotterAttempts[trapId]) state.TrapThemePFSimple.trapSpotterAttempts[trapId] = {}; if(state.TrapThemePFSimple.trapSpotterAttempts[trapId][charId]) return; else state.TrapThemePFSimple.trapSpotterAttempts[trapId][charId] = true; // Make a hidden Perception check to try to notice the trap. CharSheetUtils.getSheetAttr(character, 'perception') .then(perception => { if(_.isNumber(perception)) { return CharSheetUtils.rollAsync(`1d20 + ${perception}`); } else throw new Error('Trap Spotter: Could not get Perception value for Character ' + charToken.get('_id') + '.'); }) .then(searchResult => { // Inform the GM about the Trap Spotter attempt. sendChat('Trap theme: ' + this.name, `/w gm ${character.get('name')} attempted to notice trap "${trap.get('name')}" with Trap Spotter ability. Perception ${searchResult.total} vs DC ${effect.spotDC}`); // Resolve whether the trap was spotted or not. if(searchResult.total >= effect.spotDC) { let html = TrapTheme.htmlNoticeTrap(character, trap); ItsATrap.noticeTrap(trap, html.toString(TrapTheme.css)); } }) .catch(err => { sendChat('Trap theme: ' + this.name, '/w gm ' + err.message); log(err.stack); }); } } ItsATrap.registerTheme(new TrapThemePFSimple()); }); })();