diff --git a/RoleplayingIsMagic_4E/mlp_dice.js b/RoleplayingIsMagic_4E/mlp_dice.js new file mode 100644 index 0000000000..8b64cbe052 --- /dev/null +++ b/RoleplayingIsMagic_4E/mlp_dice.js @@ -0,0 +1,333 @@ +/** + * Provides a dice rolling command specialized for MLP: RiM season 4 edition. + * The syntax for the command is + * !r [{Character name}:]{skill name} [+/- Advantages/Drawbacks] + * + * e.g. + * !r Spectrum Square:Energy Weapons +4 -2 + * + * The character name and skill name are case-insensitive. You should only + * need to enter the character name if you control more than one character, + * otherwise it automatically use the first character you control (which for + * most players is just one). + * + * There are a couple other assumptions the script makes about a character's + * attributes in order for it to work: + * - All attributes related to skills have the word 'skill' in them somewhere. + * - Skill attributes are divided into 4 parts: + * repeating_skills{mind|body|heart}_{repeating skills index}_{SkillAttributeName} + * + * * The 1st part is just a literal used by roll20 for all repeating fieldset attributes + * on a character sheet. + * * The 2nd part is the name of the repeating field set. The script assumes that + * your character sheets have skillsmind, skillsbody, and skillsheart repeating + * fieldsets, which are identical accept for the primary attribute they're + * based off of. + * * The 3rd part is the index of the skill's group of attributes in the + * repeating fieldset. All attributes for one skill share this index. It too + * is automatically generated by Roll20's character sheet API. + * * The 4th part is the name of an attribute for the skill. This script assumes + * that each skill's repeating field set contains fields with these names: + * skillX, skillXT, skillXI, skillXG, skillXMisc, where X is M, B, or H for + * mind, body, and heart respectively. + * + * skillX is the text field actually containing the skill's name. + * + * skillXT is a checkbox field marking whether the skill is trained. I.E. + * The character took the Skill Training edge for that skill. + * The checkbox's value when checked is 1. + * + * skillXI is a checkbox for Improved Skill Training for the skill, again + * with a checked value of 1. + * + * skillXG is a checkbox for Greater Skill Training for the skill, again + * with a checked value of 1. + * + * skillXMisc is a number field for the total of any other modifiers for + * the skill, which aren't Advantages and Drawbacks. + */ +(function() { + + var cmd = "!r "; + + /** + * Gets the character the player is currently speaking as. + * @param {String} playerId The player's ID. + * @return {Character} + */ + function getCharacter(playerId) { + var player = findObjs({ + _type: 'player', + _id: playerId + })[0]; + + log(player); + + var speakingAs = player.get('speakingas') || player.get('_displayname'); + if(speakingAs.indexOf('player') === 0) + throw new Error('You are not currently speaking as a character.'); + else if(speakingAs.indexOf('character') === 0) { + var characterId = speakingAs.replace('character|', ''); + log(speakingAs, characterId); + return findObjs({ + _type: 'character', + _id: characterId + })[0]; + } + else { + var character = findObjs({ + _type: 'character', + name: speakingAs + })[0]; + if(character) + return character; + else + throw new Error('Bad speakingas value: ' + speakingAs); + } + }; + + /** + * Gets all the skill attributes for a character. + * @param {Character} character + * @return {Attribute[]} + */ + function getAllSkills(character) { + return _.filter(findObjs({ + _type: "attribute", + _characterid: character.id + }), function(attr) { + return (attr.get("name").indexOf("skill") !== -1); + }); + }; + + + + /** + * Gets information about a skill. + * @param {Character} character + * @param {String} name The name of the skill. + * @return {Object} + */ + function getSkill(character, name) { + var skills = getAllSkills(character); + var skill = _.find(skills, function(attr) { + var value = getSkillName(attr); + return (value.indexOf(name) !== -1); + }); + + if(skill) { + var name = getSkillName(skill); + var toks = splitSkill(skill); + var index = toks[0]; + var type = toks[1]; + + var trained = (parseInt(getSkillField(skills, type, index, "T")) == 1); + var improved = (parseInt(getSkillField(skills, type, index, "I")) == 1); + var greater = (parseInt(getSkillField(skills, type, index, "G")) == 1); + var bonus = parseInt(getSkillField(skills, type, index, "Misc")) || 0; + var notes = getSkillField(skills, type, index, 'Conds') || ''; + + var adv = parseInt(getSkillField(skills, type, index, 'Adv')) || 0; + var dis = parseInt(getSkillField(skills, type, index, 'Dis')) || 0; + var advDis = adv - dis; + + var attr = {}; + if(type === "skillM") + attr.name = "mind"; + if(type === "skillB") + attr.name = "body"; + if(type === "skillH") + attr.name = "heart"; + + attr.value = getAttrByName(character.id, attr.name); + + return { + name: name, + attr: attr, + trained: trained, + improved: improved, + greater: greater, + advDis: advDis, + bonus: bonus, + notes: notes + }; + } + }; + + /** + * [splitSkill description] + * @param {[type]} skill + * @return {[type]} + */ + function splitSkill(skill) { + var toks = skill.get("name").split("_"); + return [toks[2], toks[3]]; + }; + + /** + * Gets the value of the attribute for a skill's name. + */ + function getSkillName(skill) { + return skill.get("current").toLowerCase(); + }; + + /** + * Extracts a field value for a skill. + */ + function getSkillField(skills, type, index, field) { + var field = _.find(skills, function(skill) { + var toks = splitSkill(skill); + return (toks[0] === index && toks[1] === (type + field)); + }); + if(field) + return field.get("current"); + else + return undefined; + }; + + + /** + * Parses the Advantage/Disadvantage total from an expression of + * of +N advantages and -N disadvantages. E.g. "+2 -1 + 4" + * @return {Boolean} + */ + function parseAdvDis(expr) { + if(!expr) + return 0; + + expr = expr.replace(' ', ''); + var total = 0; + var regex = /([+]|-)(\d+)/g + + // Get the first match. + var match = regex.exec(expr); + while(match) { + if(match[1] === '+') + total += parseInt(match[2]); + else + total -= parseInt(match[2]); + + // Get the next match. + match = regex.exec(expr); + } + + return total; + } + + /** + * An object representing a skillcheck. + * @typedef {object} SkillCheck + * @property {string} skillName The name of the skill, or what its name starts with. + * @property {int} advDis The total Advantage/Disadvantage modifier. + * @property {string} note A string appended to the roll's notes. + */ + + /** + * An object representing a skill and its attributes. + * @typedef {object} Skill + * @property {string} name + * @property {SkillAttr} attr + * @property {boolean} trained + * @property {boolean} improved + * @property {boolean} greater + * @property {int} advDis + * @property {int} bonus + * @property {string} notes + */ + + /** + * The attribute used for a skillcheck. + * @typedef {object} SkillAttr + * @property {string} name + * @property {int} value + */ + + /** + * Rolls a skill check for a character using the skillcheck template. + * @param {Character} character + * @param {SkillCheck} skillCheck + */ + function rollSkillCheck(character, skillCheck) { + var charName = character.get('name'); + + var skill = getSkill(character, skillCheck.skillName); + + var notes = skill.notes; + if(skillCheck.note) { + if(skill.notes) + notes += '
' + notes += skillCheck.note; + } + var advDis = skill.advDis + skillCheck.advDis; + if(advDis >= 0) + advDis = '+' + advDis; + + var training = 'untrained'; + var dice = '2d6'; + if(skill.greater) { + training = 'greater'; + dice = '4d6d2'; + } + else if(skill.improved) { + training = 'improved'; + dice = '4d6d2'; + } + else if(skill.trained) { + training = 'trained'; + dice = '3d6d1'; + } + + var roll = '{{ ' + dice + ' ' + advDis + ', 12 + 1d0}kl1, 2 + 1d0}kh1 '; + if(skill.greater) { + roll += '+1 [G] '; + } + roll += ' +' + skill.attr.value + '[' + skill.attr.name + '] + ' + skill.bonus; + + var templateStr = '&{template:skillcheck} {{charName=' + charName + '}} '; + templateStr += '{{skillName=' + skillCheck.skillName + '}} {{result=[[' + roll + ']]}} '; + templateStr += '{{' + training + '=true}} '; + if(notes) { + templateStr += '{{notes=' + notes + '}}'; + } + + sendChat(character.get('name'), templateStr); + } + + on("chat:message", function(msg) { + try { + if(msg.type == "api" && msg.content.indexOf(cmd) !== -1) { + var playerId = msg.playerid; + var character = getCharacter(playerId); + var str = msg.content.replace(cmd, ""); + + // Process the roll command as a regular expression. + // + // group 1 is the skill name. + // group 2 is the advantage/disadvantage modifier. + // group 6 is a string appended to the notes for the roll. + var regex = /([^+\-\\"]+)(( *([+]|-) *\d+)*)? *("(.*?)")?/; + var match = regex.exec(str); + + var skillName = match[1].trim().toLowerCase(); + var advDis = parseAdvDis(match[2]); + var note = match[6]; + + if(match) { + var skillCheck = { + skillName: skillName, + advDis: advDis, + note: note + }; + rollSkillCheck(character, skillCheck); + } + else + throw new Error('Bad roll format. Expected format: {skill name} [+/- Advantage/Disadvantage modifier] ["any notes about the roll"]'); + } + } + catch(err) { + sendChat("ERROR", "/w " + msg.who + " Error processing roll: " + msg.content); + log('MLP Dice ERROR: ' + err.message); + } + + }); +})(); diff --git a/RoleplayingIsMagic_4E/mlp_rim4.css b/RoleplayingIsMagic_4E/mlp_rim4.css new file mode 100644 index 0000000000..f88a690adf --- /dev/null +++ b/RoleplayingIsMagic_4E/mlp_rim4.css @@ -0,0 +1,280 @@ +.charsheet { + +} + +.charsheet .sheet-everything { + background-image: url("https://sites.google.com/site/roleplayingismagichome/_/rsrc/1385907608270/config/WebsiteBackground_Rough.png.1385907608112.png"); + height: 90%; + position: absolute; + width: 95%; +} + +.charsheet .sheet-banner { + display: inline-block; + width: 100%; +} +.charsheet .sheet-banner img.sheet-mlprimLogo { + height: 0.75in; + position: absolute; + right: 0.5in; +} + +.charsheet .sheet-tabsContainer { + bottom: 0.5in; + left: 0.5in; + min-height: 5.5in; + min-width: 7in; + position: absolute; + right: 0.5in; + top: 1in; +} +.charsheet .sheet-tab { + float: left; + width: 16%; + height: 32px; +} +.charsheet .sheet-tab .sheet-tabInput { + position: absolute; + top: -9999px; +} +.charsheet .sheet-tab > label { + background: #82c; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + height: 22px; + margin: 0; + padding: 10px 0 0 0; + text-align: center; + vertical-align: middle; + width: 100%; +} +.charsheet .sheet-tab > label { + cursor: pointer; +} +.charsheet .sheet-tab > input:checked + label { + background: #a5f; +} +.charsheet .sheet-tab .sheet-tabPanel { + background: #404; + border: 4px solid #a5f; + border-radius: 10px; + color: #fff; + display: none; + height: 80%; + left: -32px; + overflow-y: auto; + padding: 20px; + position: absolute; + top: 32px; + width: 100%; +} + +.charsheet .sheet-tab input:checked + label + .sheet-tabPanel { + display: inline-block; +} + + +.charsheet button { + background: #a5f; + border-color: #82c; + color: #fff; + padding: 4px; +} +.charsheet input,select,textarea { + background: #000; + border: none; + color: #0ff; +} +.charsheet input[type=number]::-webkit-inner-spin-button, +.charsheet input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} +.charsheet textarea { + height: auto; + resize: none; + max-width: 90%; +} + +.charsheet .sheet-fraction { + display: inline; + font-size: 1.2em; + font-weight: bold; + padding-left: 2px; + padding-right: 2px; + text-align: center; +} + +.charsheet h2 { + border-bottom: 1px solid #a5f; + color: #fff; + line-height: normal; + margin: 0 0 5px 0; + width: 100%; +} + +.charsheet .sheet-hLayout > div { + display: inline; +} +.charsheet .sheet-vLayout > div { + display: block; +} + +.charsheet .sheet-field label { + color: white; + display: inline-block; + width: 80px; +} + +.charsheet .sheet-column { + display: inline-block; +} + +.charsheet #sheet-mainPanel input[type=text],select { + max-width: 150px; +} +.charsheet #sheet-mainPanel .sheet-attrBox { + margin: auto; + width: 150px; +} +.charsheet #sheet-mainPanel .sheet-attrBox label { + text-align: right; + width: 50%; +} + +.charsheet #sheet-skillsPanel .repitem { + margin-bottom: 5px; +} +.charsheet #sheet-edgesPanel .repitem { + margin-bottom: 5px; +} + +.charsheet .sheet-rankBox { + border: 1px solid #a5f; + display: inline-block; + position: relative; + height: 16px; + text-align: center; + width: 16px; +} +.charsheet .sheet-rankBox input{ + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; +} +.charsheet .sheet-rankBox input + div{ + background: #000; + color: #a5f; + height: 100%; + width: 100%; +} +.charsheet .sheet-rankBox input:checked + div{ + background: #a5f; + color: #000; +} + +.charsheet .sheet-expandBox { + display: inline-block; + left: 1em; + position: relative; +} + +.charsheet #sheet-magicPanel .sheet-spellBody{ + border-bottom: 1px solid #a5f; + margin-bottom: 16px; +} + +.charsheet .sheet-conditionalModifiers { + margin-left: 1em; + margin-top: 0.25em; + width: 90%; +} +.charsheet .sheet-conditionalModifiers input { + padding: 1px; + width: 50%; +} +.charsheet .sheet-conditionalModifiers * { + font-size: 0.8em; +} + +.charsheet ::-webkit-scrollbar { + width: 10px; +} + +.charsheet ::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.5); + border-radius: 5px; +} + +.charsheet ::-webkit-scrollbar-thumb { + background: #a5f; + border-radius: 5px; +} + +.charsheet .sheet-readOnlyField { + background: #000; + border-radius: 3px; + color: #08f; + cursor: not-allowed; + display: inline-block; + padding: 4px; + width: 3em; +} + + +/* roll template styles */ + +.sheet-rolltemplate-skillcheck table { + background: #404; + border: 2px solid #a5f; + border-radius: 1em; + color: #fff; + overflow: hidden; + width: 100%; +} + +.sheet-rolltemplate-skillcheck table thead th { + background: #a5f; + color: #000; + padding: 0.5em 1em; +} + +.sheet-rolltemplate-skillcheck table tbody { + text-align: center; +} + +.sheet-rolltemplate-skillcheck table tbody tr td{ + padding: 0.5em 0; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-rollNotes { + font-size: 0.8em; + font-style: italic; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-trainingLevel { + font-weight: bold; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-untrained { + color: #118844; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-trained { + color: #2f8; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-improved { + color: #2cf; +} + +.sheet-rolltemplate-skillcheck table tbody .sheet-greater { + color: #fc4; +} + +.sheet-rolltemplate-skillcheck table tbody tr td .inlinerollresult { + background: #000; +} diff --git a/RoleplayingIsMagic_4E/mlp_rim4.html b/RoleplayingIsMagic_4E/mlp_rim4.html new file mode 100644 index 0000000000..e69fe11fb2 --- /dev/null +++ b/RoleplayingIsMagic_4E/mlp_rim4.html @@ -0,0 +1,738 @@ +
+ +
+ +
+ + +
+

General

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
/
+ + Modifiers: +
+
+ +
/
+ + Modifiers: +
+
+ +
/
+ + Modifiers: +
+
+ +

Attributes

+ + + + + + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ +

Description

+ +
+
+ + + + + +
+ + +
+

MIND Skills

+
+
+ Name: + Skill Training:
+ +
T
+
+
+ +
I
+
+
+ +
G
+
+ Modifier: + +
+
+ Conditional Modifiers: + +
+
+ +

BODY Skills

+
+
+ Name: + Skill Training:
+ +
T
+
+
+ +
I
+
+
+ +
G
+
+ Modifier: + +
+
+ Conditional Modifiers: + +
+
+ +

HEART Skills

+
+
+ Name: + Skill Training:
+ +
T
+
+
+ +
I
+
+
+ +
G
+
+ Modifier: + +
+
+ Conditional Modifiers: + +
+
+
+
+ + + + + +
+ + +
+

+ This section is for all edges other than Skill Training. Those go over in the Skills section. +

+
+ Name: + Effect: + Edge rank: +
+ +
T
+
+
+ +
I
+
+
+ +
G
+
+
+
+
+ + + + + +
+ + +
+
+
+
+
+ +
P
+
+
+
+

+ + Lv. +

+
+ Target: + Range: + Duration:
+
+ Effects: +
+
+ + + + + + + + + + + + +
+
+ +
 
+
Animate +
+
+ +
 
+
Combine +
+
+ +
 
+
Deceive +
+
+ +
 
+
Diminish +
+
+ +
 
+
Forge +
+
+ +
 
+
Modify +
+
+ +
 
+
Reveal +
+
+ +
 
+
Separate +
+

+
+ Subjects: +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
 
+
Air +
+
+ +
 
+
Animal +
+
+ +
 
+
Body +
+
+ +
 
+
Construct +
+
+ +
 
+
Earth +
+
+ +
 
+
Energy +
+
+ +
 
+
Force +
+
+ +
 
+
Heat +
+
+ +
 
+
Light +
+
+ +
 
+
Magic +
+
+ +
 
+
Mind +
+
+ +
 
+
Plant +
+
+ +
 
+
Shadow +
+
+ +
 
+
Sound +
+
+ +
 
+
Space +
+
+ +
 
+
Time +
+
+ +
 
+
Water +
+
+ +
 
+
Weather +
+
+ + +
+
+
+
+
+
+ + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + {{#untrained}} + + {{/untrained}} + {{#trained}} + + {{/trained}} + {{#improved}} + + {{/improved}} + {{#greater}} + + {{/greater}} + + + + + {{#notes}} + + + + {{/notes}} + +
+ {{charName}} ⇒ {{skillName}} +
UntrainedTrainedImprovedGreater
Result: {{result}}
+
Notes:
+
{{notes}}
+
+
diff --git a/RoleplayingIsMagic_4E/readme.md b/RoleplayingIsMagic_4E/readme.md new file mode 100644 index 0000000000..14a0dbeb11 --- /dev/null +++ b/RoleplayingIsMagic_4E/readme.md @@ -0,0 +1,19 @@ +# Roleplaying is Magic: Season 4 edition character sheet + +This character sheet is for [Roleplaying is Magic: Season 4 edition](http://roleplayingismagic.com/), +a popular fan-created tabletop RPG system based upon the world of +My Little Pony: Friendship is Magic and developed by Roan Arts. + +## RiM Dice script + +The skill rolling buttons in this sheet make use of an API script for rolling +dice and skill checks specifically for the Roleplaying is Magic system. This +script is published with the sheet as the file ```mlp_dice.js```. If you use +this sheet, it is highly recommended that you also install this API script for +your campaign. + +For the skill rolling buttons to work, you must also be speaking as the +character you are rolling the skill for. + +The script can also be used as a +chat command. This is documented in the ```mlp_dice.js``` script.