Skip to content

Instantly share code, notes, and snippets.

@Cazra
Created February 16, 2015 15:44
Show Gist options
  • Save Cazra/cdc8784a009a26b53dda to your computer and use it in GitHub Desktop.
Save Cazra/cdc8784a009a26b53dda to your computer and use it in GitHub Desktop.
Roll20 API: MLP:RiM s4 skill roller
/**
* 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 a character controlled by the player.
* @param {String} playerId The player's ID.
* @param {String} [name] The character's name. If not provided,
* the player's first controlled token is returned.
* @return {Character}
*/
function getCharacter(playerId, name) {
var allCharacters = findObjs({
_type: "character"
});
if(name) {
// If a name was provided, get that character if it exists and
// is controlled by playerId.
return _.find(allCharacters, function(ch) {
return (ch.get("name").toLowerCase().trim().indexOf(name) !== -1 && controlledBy(ch, playerId));
});
}
else {
// If no name was provided, get the first character controlled by
// playerId.
return _.find(allCharacters, function(ch) {
return controlledBy(ch, playerId);
});
}
};
/**
* Checks if a character is controllable by a player.
* @param {Character} ch The character
* @param {String} playerId The ID of the player.
* @return {boolean} true iff the character is controllable by the player.
*/
function controlledBy(ch, playerId) {
return (ch.get("controlledby").indexOf(playerId) !== -1);
};
/**
* 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 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,
bonus: bonus
};
}
};
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;
};
function getSkillRoll(skill, advDis) {
advDis = advDis || 0;
var name = skill.name;
var attr = skill.attr;
var dice = "2d6";
var tier = "untrained";
var bonus = skill.bonus;
if(skill.trained) {
dice = "3d6d1";
tier = "trained";
}
if(skill.improved) {
dice = "4d6d2";
tier = "improved";
}
if(skill.greater) {
tier = "greater";
}
var roll = "/r [" + name + " " + tier +"]" + " {{" + dice + " + " + advDis + ", 12 + 1d0}kl1, 2 + 1d0}kh1";
if(skill.greater)
roll += " + 1[G]";
roll += " + " + attr.value + "[" + attr.name + "]";
roll += " + " + bonus;
return roll;
}
function getAdvantages(args) {
args = _.drop(args, 1);
return _.reduce(args, function(memo, arg) {
arg = arg.toLowerCase();
var value = 0;
if(arg.indexOf("a") !== -1) {
arg.replace("a", "").trim();
value = parseInt(arg) || 0;
}
return memo + value;
}, 0);
}
function getDrawbacks(args) {
args = _.drop(args, 1);
return _.reduce(args, function(memo, arg) {
arg = arg.toLowerCase();
var value = 0;
if(arg.indexOf("d") !== -1) {
arg.replace("d", "").trim();
value = Math.abs(parseInt(arg) || 0);
}
return memo + value;
}, 0);
}
on("chat:message", function(msg) {
if(msg.type == "api" && msg.content.indexOf(cmd) !== -1) {
var str = msg.content.replace(cmd, "");
var args;
var sign;
if(str.indexOf("+") !== -1) {
args = str.split("+");
sign = "";
}
else if(str.indexOf("-") !== -1) {
args = str.split("-");
sign = "-";
}
else {
args = [str];
}
args = _.map(args, function(arg) {
return arg.trim().toLowerCase();
});
var playerId = msg.playerid;
var charName = "";
var skillName = args[0];
var adv = sign + args[1];
var character;
if(skillName && skillName.indexOf(":") !== -1) {
var toks = _.map(skillName.split(":"), function(tok) {
return tok.trim();
});
charName = toks[0];
character = getCharacter(playerId, charName);
skillName = toks[1];
}
else {
character = getCharacter(playerId);
}
if(character) {
var skill = getSkill(character, skillName);
if(skill) {
var roll = getSkillRoll(skill, adv);
sendChat(msg.who, roll);
}
else
sendChat("ERROR", "/w " + msg.who + " Could not find skill: " + skillName);
}
else
sendChat("ERROR", "/w " + msg.who + " Could not find character: " + charName);
}
});
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment