(function() { // A cache mapping character names to the row number of their Perception skill. // Ideally, the row IDs would be cached instead, since that is impervious to // changing the order of the rows. // In order to use row IDs, the 5E Shaped character sheet would need hidden // attributes in each row to store their IDs. var perceptionRowNumCache = {}; /** * Gets the passive perception for a character. It is assumed that * Perception is the 12th skill in the character's skill list. * @param {Character} character */ function getPassivePerception(character) { var charName = character.get('name'); var mind = parseInt(findObjs({ _type: 'attribute', _characterid: character.get('_id'), name: 'mind' })[0].get('current')); var skill = RiM4Dice.getSkill(character, 'perception'); var result = 4 + mind; if(skill) { if(skill.trained) result += 3; if(skill.improved) result += 2; if(skill.greater) result += 1; result += (skill.misc || 0) + (skill.advDis || 0); } return result; } /** * Produces HTML for a padded table row. * @param {string} innerHTML * @param {string} style * @return {string} */ function htmlPaddedRow(innerHTML, style) { return '' + innerHTML + ''; } /** * Produces HTML for a faked inline roll result. * @param {roll} result * @param {string} tooltip * @return {string} */ function htmlRollResult(result, tooltip) { var style = 'background-color: #FEF68E; cursor: help; font-size: 1.1em; font-weight: bold; padding: 0 3px;'; return '' + result.total + ''; } /** * Displays the message to notice a trap. * @param {Graphic} trap */ function noticeTrap(character, trap) { var noticeHtml = "IT'S A TRAP!!!
" + character.get('name') + ' notices a trap:
' + trap.get('name') ItsATrap.noticeTrap(trap, noticeHtml); } /** * Asynchronously rolls a dice roll expression and returns the result's total in * a callback. The result is undefined if an invalid expression is given. * @param {string} expr * @return {int} */ function rollAsync(expr, callback) { try { sendChat('ItsATrap-DnD5', '/w gm [[' + expr + ']]', function(msg) { try { var results = msg[0].inlinerolls[0].results; callback(results, expr); } catch(err) { callback(undefined, expr); } }); } catch(err) { callback(undefined, expr); } } /** * Sends an HTML-stylized message about an activated trap. * @param {object} effect */ function sendHtmlTrapMessage(effect) { var tableStyle = [ 'background-color: #fff;', 'border: solid 1px #000;', 'border-collapse: separate;', 'border-radius: 10px;', 'overflow: hidden;', 'width: 100%;' ].join(' '); var headerStyle = [ 'background-color: #000;', 'color: #fff;', 'font-weight: bold;' ].join(' '); var messageStyle = [ 'background-color: #ccc;', 'font-style: italic;' ].join(' '); // Start message var msg = ''; msg += ""; msg += ''; // Add the flavor message. msg += htmlPaddedRow(effect.message, messageStyle); // Add message for who triggered it. if(effect.character) { var targetMsg = 'Target: ' + effect.character.get('name'); msg += htmlPaddedRow(targetMsg); } // Add the skill check message. if(effect.character && effect.skill) { var rollHtml = htmlRollResult(effect.skill.roll, effect.skill.rollExpr); var skillMsg = '' + effect.skill.name.toUpperCase() + ' check: ' + rollHtml + ' vs Dif ' + effect.skill.dif; msg += htmlPaddedRow(skillMsg); var skill = RiM4Dice.getSkill(effect.character, effect.skill.name); if(skill && skill.notes) { var skillNotesMsg = '' + skill.notes + ''; msg += htmlPaddedRow(skillNotesMsg); } // Add the hit/miss message. if(effect.trapHit) { var resultHtml = 'HIT! '; if(effect.damage) resultHtml += 'Damage: [[' + effect.damage + ']]'; else resultHtml += effect.character.get('name') + ' falls prey to the trap\'s effects!'; msg += htmlPaddedRow(resultHtml); } else { var resultHtml = 'MISS! '; if(effect.damage && effect.missHalf) resultHtml += 'Half damage: [[floor((' + effect.damage + ')/2)]].'; msg += htmlPaddedRow(resultHtml); } } // End message. msg += '
IT'S A TRAP!!!
'; // Send the HTML message to the chat. sendChat('Admiral Ackbar', msg); // Remind the GM about the trap's effects. if(effect.notes) sendChat('Admiral Ackbar', '/w gm Trap Effects:
' + effect.notes); } /** * A theme for the 5th Edition OGL character sheet. * @implements ItsATrap#TrapTheme */ var theme = { name: 'MLP-RIM-4', /** * Display the raw message and play the effect's sound. * @inheritdoc */ activateEffect: function(effect) { var charToken = getObj('graphic', effect.victimId); var character = getObj('character', charToken.get('represents')); effect.character = character; // Automate trap attack/save mechanics. if(character && effect.skill) { var attr = findObjs({ _type: 'attribute', _characterid: character.get('_id'), name: effect.skill.attr })[0].get('current'); RiM4Dice.rollSkillCheck(character, effect.skill.name, function(skillRoll, expr) { if(skillRoll) { effect.skill.roll = skillRoll; effect.skill.rollExpr = expr; effect.trapHit = skillRoll.total < effect.skill.dif; sendHtmlTrapMessage(effect); } else { // default to primary attribute. rollAsync('2d6 + ' + attr + ' [' + effect.skill.attr + ']', function(attrRoll, expr) { effect.skill.roll = attrRoll; effect.skill.rollExpr = expr; effect.trapHit = attrRoll.total < effect.skill.dif; sendHtmlTrapMessage(effect); }); } }); } else sendHtmlTrapMessage(effect); // If the effect has a sound, try to play it. ItsATrap.playEffectSound(effect); // If the effect has an api command, execute it. ItsATrap.executeTrapCommand(effect); }, /** * Display a message if the character is within 5 units of the trap. * @inheritdoc */ passiveSearch: function(trap, charToken) { var effect = ItsATrap.getTrapEffect(charToken, trap); var character = getObj('character', charToken.get('represents')); // Only do passive search for traps that have a spotDC. if(effect.spotDif && character) { var passPerception = getPassivePerception(character); if(passPerception === undefined) { var mind = findObjs({ _type: 'attribute', _characterid: character.get('_id'), name: 'mind' })[0].get('current'); if(mind && mind + 4 >= effect.spotDif) noticeTrap(character, trap); } else if(passPerception >= effect.spotDif) noticeTrap(character, trap); } } }; // Register the theme with ItsATrap. var register = setInterval(function() { if(ItsATrap) { clearInterval(register); ItsATrap.registerTheme(theme); } }, 1000); })();