Added a campaign-setting-neutral version of my Roleplaying is Magic 4E character sheet.

This commit is contained in:
Cazra
2016-03-12 10:49:24 -05:00
parent 77032cbbdc
commit 5640f2fc20
4 changed files with 1370 additions and 0 deletions
+333
View File
@@ -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 += '<br>'
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);
}
});
})();
+280
View File
@@ -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;
}
+738
View File
@@ -0,0 +1,738 @@
<div class="everything">
<div class="banner"><div class="space"></div><img class="mlprimLogo" src="https://sites.google.com/site/roleplayingismagichome/_/rsrc/1404003237260/0-0/RiM-Logo-Redesign.png" /></div>
<div class="tabsContainer">
<div class="tab">
<input id="sheet-mainRadio" type="radio" class="tabInput" name="tab" checked />
<label for="sheet-mainRadio">Main</label>
<div id="mainPanel" class="tabPanel">
<h2>General</h2>
<div class='column vLayout' style="width: 50%;">
<div class="field">
<label>Name:</label>
<input type="text" name="attr_name">
</div>
<div class="field">
<label>Race:</label>
<input type="text" name="attr_race">
</div>
<div class="field">
<label>Guiding Element:</label>
<select name="attr_guiding_elem">
<option value="def">Select</option>
<option value="generosity">Generosity</option>
<option value="honesty">Honesty</option>
<option value="kindness">Kindness</option>
<option value="laughter">Laughter</option>
<option value="loyalty">Loyalty</option>
<option value="magic">Magic</option>
</select>
</div>
<div class="field">
<label>Fatal Flaw:</label>
<input type="text" name="attr_flaw">
</div>
</div>
<div class="column vLayout">
<div class="field">
<label>XP Spent:</label>
<span class="readOnlyField" name="attr_xpSpent"></span>
</div>
<div class="field">
<label>Fortitude:</label>
<input type="number" name="attr_fortitude"><div class="fraction">/</div>
<span class="readOnlyField" name="attr_fortitude_max"></span>
Modifiers: <input type="number" name="attr_fortitudeOtherMods" value="0" />
</div>
<div class="field">
<label>Willpower:</label>
<input type="number" name="attr_willpower"><div class="fraction">/</div>
<span class="readOnlyField" name="attr_willpower_max"></span>
Modifiers: <input type="number" name="attr_willpowerOtherMods" value="0" />
</div>
<div class="field">
<label>Valor:</label>
<input type="number" name="attr_valor"><div class="fraction">/</div>
<span class="readOnlyField" name="attr_valor_max"></span>
Modifiers: <input type="number" name="attr_valorOtherMods" value="0" />
</div>
</div>
<h2>Attributes</h2>
<table width="100%">
<tr>
<td>
<div class="attrBox field">
<label>MIND</label>
<input type="number" name="attr_mind">
</div>
</td>
<td>
<div class="attrBox field">
<label>BODY</label>
<input type="number" name="attr_body">
</div>
</td>
<td>
<div class="attrBox field">
<label>HEART</label>
<input type="number" name="attr_heart">
</div>
</td>
</tr>
</table>
<h2>Description</h2>
<textarea rows="3" name="attr_description"></textarea>
</div>
</div>
<div class="tab">
<input id="skillsRadio" type="radio" class="tabInput" name="tab" />
<label for="skillsRadio">Skills</label>
<div id="skillsPanel" class="tabPanel scrollingY">
<h2>MIND Skills</h2>
<fieldset name="skillsMind" class="repeating_skillsMind">
<div>
Name: <input type="text" name="attr_skillM">
Skill Training: <div class="rankBox trained" title="Trained">
<input type="checkbox" name="attr_skillMT" value="1">
<div>T</div>
</div>
<div class="rankBox improved" title="Improved">
<input type="checkbox" name="attr_skillMI" value="1">
<div>I</div>
</div>
<div class="rankBox greater" title="Greater">
<input type="checkbox" name="attr_skillMG" value="1">
<div>G</div>
</div>
Modifier: <input class="skillMod" type="number" name="attr_skillMMisc" value="0" title="Misc. Modifier"/>
<button type="roll" value="!r @{skillM}"></button>
</div>
<div class="conditionalModifiers">
<span>Conditional Modifiers:</span>
<input type="text" name="attr_skillMconditionals">
</div>
</fieldset>
<h2>BODY Skills</h2>
<fieldset name="skillsBody" class="repeating_skillsBody">
<div>
Name: <input type="text" name="attr_skillB">
Skill Training: <div class="rankBox trained" title="Trained">
<input type="checkbox" name="attr_skillBT" value="1">
<div>T</div>
</div>
<div class="rankBox improved" title="Improved">
<input type="checkbox" name="attr_skillBI value="1"">
<div>I</div>
</div>
<div class="rankBox greater" title="Greater">
<input type="checkbox" name="attr_skillBG" value="1">
<div>G</div>
</div>
Modifier: <input class="skillMod" type="number" name="attr_skillBMisc" value="0" title="Misc. Modifier"/>
<button type="roll" value="!r @{skillB}"></button>
</div>
<div class="conditionalModifiers">
<span>Conditional Modifiers:</span>
<input type="text" name="attr_skillBconditionals">
</div>
</fieldset>
<h2>HEART Skills</h2>
<fieldset name="skillsHeart" class="repeating_skillsHeart">
<div>
Name: <input type="text" name="attr_skillH">
Skill Training: <div class="rankBox trained" title="Trained">
<input type="checkbox" name="attr_skillHT" value="1">
<div>T</div>
</div>
<div class="rankBox improved" title="Improved">
<input type="checkbox" name="attr_skillHI" value="1">
<div>I</div>
</div>
<div class="rankBox greater" title="Greater">
<input type="checkbox" name="attr_skillHG" value="1">
<div>G</div>
</div>
Modifier: <input class="skillMod" type="number" name="attr_skillHMisc" value="0" title="Misc. Modifier"/>
<button type="roll" value="!r @{skillH}"></button>
</div>
<div class="conditionalModifiers">
<span>Conditional Modifiers:</span>
<input type="text" name="attr_skillHconditionals">
</div>
</fieldset>
</div>
</div>
<div class="tab">
<input id="edgesRadio" type="radio" class="tabInput" name="tab" />
<label for="edgesRadio">Edges</label>
<div id="edgesPanel" class="tabPanel">
<p>
This section is for all edges other than Skill Training. Those go over in the Skills section.
</p>
<fieldset name="edges" class="repeating_edges">
Name: <input type="text" name="attr_edgeName" style="width: 100px;"/>
Effect: <input type="text" name="attr_edgeEffect" />
Edge rank:
<div class="rankBox trained" title="Trained">
<input type="checkbox" name="attr_edgeT" value="1">
<div>T</div>
</div>
<div class="rankBox trained" title="Improved">
<input type="checkbox" name="attr_edgeI" value="1">
<div>I</div>
</div>
<div class="rankBox trained" title="Greater">
<input type="checkbox" name="attr_edgeG" value="1">
<div>G</div>
</div>
</fieldset>
</div>
</div>
<div class="tab">
<input id="magicRadio" type="radio" class="tabInput" name="tab" />
<label for="magicRadio">Magic</label>
<div id="magicPanel" class="tabPanel">
<fieldset name="spells" class="repeating_spells">
<div class="panel">
<div style="display: inline-block;">
<div class="rankBox" title="Prepared?">
<input type="checkbox" name="attr_spellPrepared"/>
<div>P</div>
</div>
</div>
<div style="display: inline-block; vertical-align: text-top; width: 90%;">
<h2>
<input type="text" name="attr_spellName" placeholder="Spell name"/>
Lv. <input type="number" name="attr_spellLevel" disabled="true"
value="(@{spellTarget} + @{spellRange} + @{spellDuration} + @{spellEffectsTotal} + @{spellSubjectsTotal})"/>
</h2>
<div class="spellBody">
Target: <select name="attr_spellTarget" style="width: 100px;">
<option value=1>Individual</option>
<option value=2>Group</option>
<option value=4>Area</option>
<option value=8>Mass</option>
</select>
Range: <select name="attr_spellRange" style="width: 100px;">
<option value=1>Contact</option>
<option value=2>Seen</option>
<option value=4>Known</option>
<option value=8>Unknown</option>
</select>
Duration: <select name="attr_spellDuration" style="width: 100px;">
<option value=1>Immediate</option>
<option value=2>Sustained</option>
<option value=4>Temporary</option>
<option value=8>Persistent</option>
</select> <br/>
<div style="display: inline-block;">
Effects:
</div>
<div style="display: inline-block; margin-bottom: 10px; vertical-align: text-top;">
<input type="hidden" name="attr_spellEffectsTotal" disabled="true"
value="(@{spellAnimate} + @{spellCombine} + @{spellDeceive} + @{spellDiminish} +
@{spellForge} + @{spellModify} + @{spellReveal} + @{spellSeparate})"/>
<table class="layoutTable">
<tr>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellAnimate" value="1"/>
<div>&nbsp;</div>
</div> Animate
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellCombine" value="1"/>
<div>&nbsp;</div>
</div> Combine
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellDeceive" value="1"/>
<div>&nbsp;</div>
</div> Deceive
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellDiminish" value="1"/>
<div>&nbsp;</div>
</div> Diminish
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellForge" value="1"/>
<div>&nbsp;</div>
</div> Forge
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellModify" value="1"/>
<div>&nbsp;</div>
</div> Modify
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellReveal" value="1"/>
<div>&nbsp;</div>
</div> Reveal
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellSeparate" value="1"/>
<div>&nbsp;</div>
</div> Separate
</td>
</tr>
</table>
</div> <br/>
<div style="display: inline-block;">
Subjects:
</div>
<div style="display: inline-block; margin-bottom: 10px; vertical-align: text-top;">
<input type="hidden" name="attr_spellSubjectsTotal" disabled="true"
value="(@{spellAir} + @{spellAnimal} + @{spellBody} + @{spellConstruct} + @{spellEarth} +
@{spellEnergy} + @{spellForce} + @{spellHeat} + @{spellLight} + @{spellMagic} +
@{spellMind} + @{spellPlant} + @{spellShadow} + @{spellSound} + @{spellSpace} +
@{spellTime} + @{spellWater} + @{spellWeather})"/>
<table class="layoutTable">
<tr>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellAir" value="1"/>
<div>&nbsp;</div>
</div> Air
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellAnimal" value="1"/>
<div>&nbsp;</div>
</div> Animal
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellBody" value="1"/>
<div>&nbsp;</div>
</div> Body
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellConstruct" value="1"/>
<div>&nbsp;</div>
</div> Construct
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellEarth" value="1"/>
<div>&nbsp;</div>
</div> Earth
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellEnergy" value="1"/>
<div>&nbsp;</div>
</div> Energy
</td>
</tr>
<tr>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellForce" value="1"/>
<div>&nbsp;</div>
</div> Force
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellHeat" value="1"/>
<div>&nbsp;</div>
</div> Heat
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellLight" value="1"/>
<div>&nbsp;</div>
</div> Light
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellMagic" value="1"/>
<div>&nbsp;</div>
</div> Magic
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellMind" value="1"/>
<div>&nbsp;</div>
</div> Mind
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellPlant" value="1"/>
<div>&nbsp;</div>
</div> Plant
</td>
</tr>
<tr>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellShadow" value="1"/>
<div>&nbsp;</div>
</div> Shadow
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellSound" value="1"/>
<div>&nbsp;</div>
</div> Sound
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellSpace" value="1"/>
<div>&nbsp;</div>
</div> Space
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellTime" value="1"/>
<div>&nbsp;</div>
</div> Time
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellWater" value="1"/>
<div>&nbsp;</div>
</div> Water
</td>
<td>
<div class="rankBox">
<input type="checkbox" name="attr_spellWeather" value="1"/>
<div>&nbsp;</div>
</div> Weather
</td>
</tr>
</table>
</div> <!-- End effects and subjects -->
<textarea name="attr_spellDescription" rows="3" placeholder="Spell description"></textarea>
</div> <!-- End spellBody -->
</div>
</div> <!-- End panel -->
</fieldset>
</div>
</div>
<div class="tab">
<input id="lootRadio" type="radio" class="tabInput" name="tab" />
<label for="lootRadio">Loot</label>
<div id="lootPanel" class="tabPanel">
<div class="field money">
<label>Bits</label>
<input type="number" name="attr_money">
</div>
<div class="field">
<label>Equipped</label>
<textarea rows="5" name="attr_equipped"></textarea>
</div>
<div class="field">
<label>Inventory</label>
<textarea rows="9" name="attr_inventory"></textarea>
</div>
</div>
</div>
<div class="tab">
<input id="notesRadio" type="radio" class="tabInput" name="tab" />
<label for="notesRadio">Notes</label>
<div id="notesPanel" class="tabPanel">
<textarea rows="20" name="attr_notes"></textarea>
</div>
</div>
</div>
</div>
<span name='attr_debug' style="display:none;"></span>
<script type="text/worker">
var Asem = function(initCount, cb) {
this.count = initCount;
this.cb = cb;
this.hasFired = false;
};
_.extend(Asem.prototype, {
signal: function() {
this.count--;
if(this.count <= 0 && !this.hasFired) {
this.hasFired = true;
this.cb();
}
},
wait: function() {
this.count++;
}
});
/**
* Gets the specified attributes and parses them as an integer or 0.
*/
function parseAttrs(attrs, cb) {
getAttrs(attrs, function(values) {
_.each(attrs, function(attr) {
values[attr] = parseInt(values[attr]) || 0;
});
cb(values);
});
}
function updateFortMax() {
parseAttrs(['body', 'heart', 'edgeVitality', 'edgeVitalityI', 'edgeVitalityG', 'fortitudeOtherMods', 'fortitudeMaxMods'], function(values) {
var fortMax = 10*values.body +
5*(values.heart + values.edgeVitality + values.edgeVitalityI + values.edgeVitalityG) +
values.fortitudeOtherMods;
setAttrs({
fortitude_max: fortMax
});
});
}
function updateWillMax() {
parseAttrs(['mind', 'heart', 'edgeDetermined', 'edgeDeterminedI', 'edgeDeterminedG', 'willpowerOtherMods'], function(values) {
var willMax = 10*values.mind +
5*(values.heart + values.edgeDetermined + values.edgeDeterminedI + values.edgeDeterminedG) +
values.willpowerOtherMods;
setAttrs({
willpower_max: willMax
});
});
}
function updateValorMax() {
parseAttrs(['heart', 'edgeNobleSoul', 'edgeNobleSoulI', 'edgeNobleSoulG', 'valorOtherMods'], function(values) {
var valorMax = values.heart + values.edgeNobleSoul + values.edgeNobleSoulI + values.edgeNobleSoulG +
values.valorOtherMods;
setAttrs({
valor_max: valorMax
});
});
}
function updateHealthGraphic() {
parseAttrs(['fortitude', 'fortitude_max', 'willpower', 'willpower_max'], function(values) {
if(values.fortitude_max === 0 || values.willpower_max === 0) {
setAttrs({
statusImage: 'healthy'
});
}
else {
var alpha = Math.min(values.fortitude/values.fortitude_max, values.willpower/values.willpower_max);
if(alpha > 0.5) {
setAttrs({
statusImage: 'healthy'
});
}
else if(alpha > 0.25) {
setAttrs({
statusImage: 'bloodied'
});
}
else if(alpha > 0) {
setAttrs({
statusImage: 'critical'
});
}
else {
setAttrs({
statusImage: 'sidelined'
});
}
}
});
}
function updateXpSpent() {
var xpSpent = 0;
var asem = new Asem(1, function() {
setAttrs({
xpSpent: xpSpent
});
});
// Mind skill training
getSectionIDs('repeating_skillsMind', function(ids) {
_.each(ids, function(id) {
var baseAttr = 'repeating_skillsMind_' + id +'_';
parseAttrs([baseAttr + 'skillMT', baseAttr + 'skillMI', baseAttr + 'skillMG'], function(values) {
xpSpent += 5*values[baseAttr + 'skillMT'] + 10*values[baseAttr + 'skillMI'] + 15*values[baseAttr + 'skillMG'];
asem.signal();
});
asem.wait();
})
asem.signal();
});
asem.wait();
// Body skill training
getSectionIDs('repeating_skillsBody', function(ids) {
_.each(ids, function(id) {
var baseAttr = 'repeating_skillsBody_' + id +'_';
parseAttrs([baseAttr + 'skillBT', baseAttr + 'skillBI', baseAttr + 'skillBG'], function(values) {
xpSpent += 5*values[baseAttr + 'skillBT'] + 10*values[baseAttr + 'skillBI'] + 15*values[baseAttr + 'skillBG'];
asem.signal();
});
asem.wait();
})
asem.signal();
});
asem.wait();
// Heart skill training
getSectionIDs('repeating_skillsHeart', function(ids) {
_.each(ids, function(id) {
var baseAttr = 'repeating_skillsHeart_' + id +'_';
parseAttrs([baseAttr + 'skillHT', baseAttr + 'skillHI', baseAttr + 'skillHG'], function(values) {
xpSpent += 5*values[baseAttr + 'skillHT'] + 10*values[baseAttr + 'skillHI'] + 15*values[baseAttr + 'skillHG'];
asem.signal();
});
asem.wait();
})
asem.signal();
});
asem.wait();
// Built-in Edges
parseAttrs(['edgeDetermined', 'edgeDeterminedI', 'edgeDeterminedG',
'edgeVitality', 'edgeVitalityI', 'edgeVitalityG',
'edgeNobleSoul', 'edgeNobleSoulI', 'edgeNobleSoulG'], function(values) {
xpSpent += 5*values.edgeDetermined + 10*values.edgeDeterminedI + 15*values.edgeDeterminedG;
xpSpent += 5*values.edgeVitality + 10*values.edgeVitalityI + 15*values.edgeVitalityG;
xpSpent += 5*values.edgeNobleSoul + 10*values.edgeNobleSoulI + 15*values.edgeNobleSoulG;
asem.signal();
});
asem.wait();
// Custom edges
getSectionIDs('repeating_edges', function(ids) {
_.each(ids, function(id) {
var baseAttr = 'repeating_edges_' + id + '_';
parseAttrs([baseAttr + 'edgeT', baseAttr + 'edgeI', baseAttr + 'edgeG'], function(values) {
setAttrs({
debug: JSON.stringify(values)
});
xpSpent += 5*values[baseAttr + 'edgeT'] + 10*values[baseAttr + 'edgeI'] + 15*values[baseAttr + 'edgeG'];
asem.signal();
});
asem.wait();
});
asem.signal();
});
asem.wait();
asem.signal();
}
on('change:body change:heart change:edgeVitality change:edgeVitalityI change:edgeVitalityG change:fortitudeOtherMods', function() {
updateFortMax();
});
on('change:mind change:heart change:edgeDetermined change:edgeDeterminedI change:edgeDeterminedG change:willpowerOtherMods', function() {
updateWillMax();
});
on('change:heart change:edgeNobleSoul change:edgeNobleSoulI change:edgeNobleSoulG change:valorOtherMods', function() {
updateValorMax();
});
on('change:repeating_skillsMind:skillMT change:repeating_skillsMind:skillMI change:repeating_skillsMind:skillMG ' +
'change:repeating_skillsBody:skillBT change:repeating_skillsBody:skillBI change:repeating_skillsBody:skillBG ' +
'change:repeating_skillsHeart:skillHT change:repeating_skillsHeart:skillHI change:repeating_skillsHeart:skillHG ' +
'change:edgeDetermined change:edgeDeterminedI change:edgeDeterminedG ' +
'change:edgeVitality change:edgeVitalityI change:edgeVitalityG ' +
'change:edgeNobleSoul change:edgeNobleSoulI change:edgeNobleSoulG ' +
'change:repeating_edges:edgeT change:repeating_edges:edgeI change:repeating_edges:edgeG', function() {
updateXpSpent();
});
on('sheet:opened', function() {
updateFortMax();
updateWillMax();
updateValorMax();
updateXpSpent();
});
</script>
<rolltemplate class="sheet-rolltemplate-skillcheck">
<table>
<thead>
<tr>
<th>
{{charName}} &#x21D2; {{skillName}}
</th>
</tr>
</thead>
<tbody>
<tr class="trainingLevel">
{{#untrained}}
<td class="untrained">Untrained</td>
{{/untrained}}
{{#trained}}
<td class="trained">Trained</td>
{{/trained}}
{{#improved}}
<td class="improved">Improved</td>
{{/improved}}
{{#greater}}
<td class="greater">Greater</td>
{{/greater}}
</tr>
<tr>
<td>Result: {{result}}</td>
</tr>
{{#notes}}
<tr class="rollNotes">
<td>
<div>Notes:</div>
<div>{{notes}}</div>
</td>
</tr>
{{/notes}}
</tbody>
</table>
</rolltemplate>
+19
View File
@@ -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.