Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions HiddenRolls/Help.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
HiddenRolls

HiddenRolls is a package of commands for hiding roll information in various
ways. Inline rolls can be modified to display only the total (hiding the
formula), only the roll values (hiding the formula and totals), or only the
formula (whispering the totals to the GM).

It is recommended that this script be used in conjunction with the CommandShell
module, which will improve output formatting and command discovery.


Commands:

!hideroll [options] command
!hiderolls [options] command
Replaces all inline rolls in command with their respective totals.

!hidetotal [options] command
!hidetotals [options] command
Replaces all inline rolls in command with new expressions containing
only the dice from the original expressions (i.e. no bonuses or
annotations). The full expression (with totals) is whispered to the GM.

!hideall [options] command
Forwards command to the GM. Whispers command to the sender, with all
inline rolls removed (and replaced with their respective formulas).

The above commands accept the following options:

-h, --help Displays a help message and exits.

-v, --verbose Generates additional output, depending on the command:
!hideroll(s): Whispers full results (including
rolls and formulas) to the GM.
!hidetotal(s): Whispers full results to the
sender in addition to the GM.
!hideall: Sends the stripped command to
global chat instead of
whispering it to the sender.


Notes:

In order to guarantee the integrity of roll results, the Roll20 API does not
provide a way to insert roll results into an inline roll. As a result,
HiddenRolls displays dice results as "{1d0+V1+V2+...}[XdY] ", where V1, V2, etc.
are roll values and XdY is the dice expression. For example, "[[1d20+7]]" would
be displayed as "[[{1d0+12}[1d20] +7]]". The "1d0" allows the expression to
function like a dice expression (otherwise the label would generate errors in
certain conditions), and the label allows the GM to verify the roll formula.

Roll20's chat system also automatically consumes template expressions before
forwarding messages to scripts. As a result, expressions like
"&{template:default}" will never be seen by scripts. To work around this,
HiddenRolls replaces all instances of "&" with "&". To use a roll template,
simply specify it as "&{template:...}".


Examples:

Below are some example commands, along with the chat commands they will
generate. Note that the whispers below are sent by the API, so they will not
appear in the sender's chat log unless they are the target of the whisper.

!hideroll The ogre swings its club ([[1d20+7]] to hit), doing [[2d6+5]] damage
The ogre swings its club ([[19]] to hit), doing [[13]] damage

!hiderolls -v The fireball explodes for [[5d6]] damage
The fireball explodes for [[17]] damage
/w gm The fireball explodes for [[{1d0+1+3+6+5+2}[5d6] ]] damage

!hidetotal The troll makes a toughness save: [[1d20+8]]
The troll makes a toughness save: [[{1d0+15}[1d20] +0[hidden] ]]
/w gm The troll makes a toughness save: [[{1d0+15}[1d20] +8]]

!hidetotals -v The enchanter's spell does [[2d8+2]] damage
The enchanter's spell does [[{1d0+7+3}[2d8] +0[hidden] ]] damage
/w gm The enchanter's spell does [[{1d0+7+3}[2d8] +2]] damage
/w Tim The enchanter's spell does [[{1d0+7+3}[2d8] +2]] damage

!hideall Slipping stealthily into a sneaky stance: [[1d20+12]]
/w gm Slipping stealthily into a sneaky stance: [[1d20+12]]
/w Snake Slipping stealthily into a sneaky stance: [1d20+12]
Note the single brackets: the expression is not an inline roll, and is
present only to display the formula being rolled.

!hideall -v Is it live or is it Magicex? [[1d20+8]] will save.
/w gm Is it live or is it Magicex? [[1d20+8]] will save.
Is it live or is it Magicex? [1d20+8] will save.
Note the single brackets: the expression is not an inline roll, and is
present only to display the formula being rolled.

!hideall &{template:default} {{name=Skill Check}} {{Hide=[[1d20+12]]}}
/w gm &{template:default} {{name=Skill Check}} {{Hide=[[1d20+12]]}}
/w Snake {template:default} {{name=Skill Check}} {{Hide=[1d20+12]}}
216 changes: 216 additions & 0 deletions HiddenRolls/hidden.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
var HiddenRolls = HiddenRolls || {
COMMANDS: ["!hideroll", "!hiderolls", "!hidetotal", "!hidetotals", "!hideall", "!help"],

write: function(s, who, style, from){
if (who){
who = "/w " + who.split(" ", 1)[0] + " ";
}
sendChat(from, who + s.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>"));
},

showHelp: function(who){
var helpMsg = "";
helpMsg += "Usage: !hideroll(s) [options] command\n";
helpMsg += " or: !hidetotal(s) [options] command\n";
helpMsg += " or: !hideall [options] command\n";
helpMsg += "!hideroll and !hiderolls hide roll results, only displaying the total.\n";
helpMsg += "!hidetotal and !hidetotals hide result totals, only displaying the roll values.\n";
helpMsg += "!hideall hides rolls and totals, only displaying the roll formula.\n";
HiddenRolls.write(helpMsg, who, "", "HR");
helpMsg = "Options:\n";
helpMsg += " -h, --help: display this help message\n";
helpMsg += " -v, --verbose: generate additional output as follows:\n";
helpMsg += " !hideroll(s): whisper full results to GM\n";
helpMsg += " !hidetotal(s): whisper full results to sender\n";
helpMsg += " !hideall: send expression to global chat\n";
HiddenRolls.write(helpMsg, who, "font-size: small; font-family: monospace", "HR");
},

generateExpression: function(who, rolls, diceOnly){
if (!rolls){ return null; }

var retval = "";
for (var i = 0; i < rolls.length; i++){
var r = rolls[i];
/////
//
//handle r.mods; for now, just acknowledge that we can't
if (r.mods){ return null; }
//
/////
switch(r.type){
case "C":
// comment; just append it onto the expression
retval += r.text;
break;
case "M":
// math; just append it onto the expression
if ((diceOnly) && (r.expr.length > 1)){
retval += "+0[hidden] ";
}
else{
retval += r.expr;
}
break;
case "L":
// label; add it to the expression as a label
if (diceOnly){ break; }
retval += "[" + r.text + "] ";
break;
case "R":
// roll; add result of each die to 1d0 so we can label the results like dice
retval += "{1d0";
for (var j = 0; j < r.results.length; j++){
retval += "+" + r.results[j].v;
if (r.results[j].d){
// dropped die; display as "Vd0", where V was the value
retval += "d0";
}
}
retval += "}";
retval += "[" + r.dice + "d" + r.sides + "] ";
break;
case "G":
var subExp = HiddenRolls.generateExpression(who, r.rolls[0], diceOnly);
if (!subExp){ return null; }
retval += "{" + subExp;
for (var j = 1; j < r.rolls.length; j++){
subExp = HiddenRolls.generateExpression(who, r.rolls[j], diceOnly);
if (!subExp){ return null; }
retval += "," + subExp;
}
retval += "}";
break;
default:
// unrecognized type; just return the expression unmodified
HiddenRolls.write("Unrecognized roll result type: " + r.type, who, "", "HiddenRolls");
return null;
}
}
return retval;
},

handleHideMessage: function(tokens, msg){
if (tokens.length < 2){
return HiddenRolls.showHelp(msg.who);
}

var verbose = false;
var cmd = msg.content.replace(/^\S+\s+/, "");
var inlineRolls = msg.inlinerolls || [];

if ((tokens[1] == "-h") || (tokens[1] == "--help")){
return HiddenRolls.showHelp(msg.who);
}

if ((tokens[1] == "-v") || (tokens[1] == "--verbose")){
verbose = true;
cmd = cmd.replace(/^\S+\s+/, "");
}

cmd = cmd.replace(/&amp;/g, "&");

switch(tokens[0]){
case "!hideroll":
case "!hiderolls":
// hide rolls; display only totals (e.g. "ogre attacks: [[1d20+7]] to hit" => "ogre attacks: [[18]] to hit")
var replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].results)){ return s; }
return "[[" + inlineRolls[i].results.total + "]]";
};
sendChat(msg.who, cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines));
if (verbose){
// verbose mode: whisper original formula to gm with roll results substituted in
replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].results)){ return s; }
var expr = HiddenRolls.generateExpression(msg.who, inlineRolls[i].results.rolls);
return (expr ? "[[" + expr + "]]" : inlineRolls[i].expression);
};
sendChat(msg.who, "/w gm " + cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines));
}
break;
case "!hidetotal":
case "!hidetotals":
// hide totals; display only dice rolls
var replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].results)){ return s; }
var expr = HiddenRolls.generateExpression(msg.who, inlineRolls[i].results.rolls, true);
return (expr ? "[[" + expr + "]]" : inlineRolls[i].expression);
};
sendChat(msg.who, cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines));
// whisper totals to gm
replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].results)){ return s; }
var expr = HiddenRolls.generateExpression(msg.who, inlineRolls[i].results.rolls);
return (expr ? "[[" + expr + "]]" : inlineRolls[i].expression);
};
var hiddenCmd = cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines);
sendChat(msg.who, "/w gm " + hiddenCmd);
if ((verbose) && (!playerIsGM(msg.playerid))){
// verbose mode: whisper totals to original sender too
sendChat(msg.who, "/w " + msg.who.split(" ", 1)[0] + " " + hiddenCmd);
}
break;
case "!hideall":
// whisper results to gm
var replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].expression)){ return s; }
return "[[" + inlineRolls[i].expression + "]]";
};
sendChat(msg.who, "/w gm " + cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines));
replaceInlines = function(s){
if (!inlineRolls){ return s; }
var i = parseInt(s.substring(3, s.length - 2));
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i].expression)){ return s; }
return "[" + inlineRolls[i].expression + "]";
};
var cmdExpr = cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines);
if (verbose){
// verbose mode: send expression to global chat
sendChat(msg.who, cmdExpr);
}
else if (!playerIsGM(msg.playerid)){
// normal mode: whisper expression to sender
sendChat(msg.who, "/w " + msg.who.split(" ", 1)[0] + " " + cmdExpr);
}
break;
default:
return HiddenRolls.showHelp(msg.who);
}
},

handleChatMessage: function(msg){
if ((msg.type != "api") || (HiddenRolls.COMMANDS.indexOf(msg.content.split(" ", 1)[0]) < 0)){ return; }

return HiddenRolls.handleHideMessage(msg.content.split(" "), msg);
},

registerHiddenRolls: function(){
if ((typeof(Shell) != "undefined") && (Shell) && (Shell.registerCommand)){
Shell.registerCommand("!hideroll", "!hideroll [--verbose] command", "Display only roll totals in command", HiddenRolls.handleHideMessage);
Shell.registerCommand("!hiderolls", "!hiderolls [--verbose] command", "Display only roll totals in command", HiddenRolls.handleHideMessage);
Shell.registerCommand("!hidetotal", "!hidetotal [--verbose] command", "Display only raw rolls in command", HiddenRolls.handleHideMessage);
Shell.registerCommand("!hidetotals", "!hidetotals [--verbose] command", "Display only raw rolls in command", HiddenRolls.handleHideMessage);
Shell.registerCommand("!hideall", "!hideall [--verbose] command", "Only show results of command to GM", HiddenRolls.handleHideMessage);
Shell.permissionCommand(["!shell-permission", "add", "!hideall"], {'who': "HR"});
if (Shell.write){
HiddenRolls.write = Shell.write;
}
}
else{
on("chat:message", HiddenRolls.handleChatMessage);
}
}
};

on("ready", function(){ HiddenRolls.registerHiddenRolls(); });
10 changes: 10 additions & 0 deletions HiddenRolls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "HiddenRolls",
"version": "0.2",
"description": "Various types of roll hiding (unmodified roll only, final result only, only show GM)",
"authors": "manveti",
"roll20userid": "503018",
"dependencies": {},
"modifies": {},
"conflicts": []
}
4 changes: 4 additions & 0 deletions cron/Help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,7 @@ Examples:
This will remove job 42 after 10 rounds. Note that the quotes are required
around commands which contain tokens which are valid !cron arguments (e.g.
"-R"). Without the quotes, this will immediately remove job 42.

!cron -a 1 &amp;{template:default} {{name=Test}} {{foo=bar}}
Note that the "&" must be "&amp;", otherwise the "&{template:default}" will
be consumed by the chat system before the message is passed to the script.
2 changes: 1 addition & 1 deletion cron/cron.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ var cron = cron || {
if ((i < 0) || (i >= inlineRolls.length) || (!inlineRolls[i]) || (!inlineRolls[i]['expression'])){ return s; }
return "[[" + inlineRolls[i]['expression'] + "]]";
}
return cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines);
return cmd.replace(/\$\[\[\d+\]\]/g, replaceInlines).replace(/&amp;/g, "&");
},

handleCronMessage: function(tokens, msg){
Expand Down
2 changes: 1 addition & 1 deletion cron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cron",
"version": "0.1",
"version": "0.2",
"description": "Schedule (possibly recurring) commands to run at some point in the future.",
"authors": "manveti",
"roll20userid": "503018",
Expand Down