Hi ! I have upgraded the script developed by aRotondi (https://app.roll20.net/forum/post/398930/script-anima-beyond-fantasy-open-rolls-slash-fumbles-script/?pageforid=398930#post-398930). Fumble : Add fumble modifiers (1 : -15, 2 : 0, 3 : +15) ('complex' and 'mastery' supported). For initiative (1 : -125, 2 : -100, 3 : -75). Display if fumble is critical (ie. > 80) Calculate the total ability - the fumble level Delete limit to 0 (I think that ability - the fumble level can be less than 0) Add 'close' flag for resistances and initiative -> block open roll on('chat:message',function(msg) { //example roll: !openroll 90 mastery inhumanity // will roll 1d100+90 including open roll values, limited to values below zen, with a reduced chance to fumble from mastery // Adding 'initiative' as a flag will dictate the result with appropriate flags to the initiative tracker. if(!(msg.selected && msg.selected.length > 0)) return; // Make sure there's a selected object var token = getObj('graphic', msg.selected[0]._id); if(token.get('subtype') != 'token') return; // Don't try to perform this command on a drawing or card //code supplied by Brian for determining if a player is speaking in character or not. var name = msg.who; var character = findObjs({ _type: 'character', name: name })[0]; var who; if(character) // Player is speaking as a character who = 'character|'+character.id; else // Player is speaking as a player who = 'player|'+msg.playerid; //parse the message type if(msg.type != 'api') return; var parts = msg.content.toLowerCase().split(' '); log( 'parts: '+parts); var command = parts.shift().substring(1); //remove the ! if(command == 'openroll') { //Variable Declarations var openFloor = 90; // The minimum result needed to roll again. set to the default described in the core rules. var fumbleCeil = 3; // Rolls below or equal to this number are considered fumbles. set to core rules default. var open; // Flag for the player having rolled a number above openFloor. var close = false; // Flag to set dice to closed. var dice; // Numberical value of latest dice rolled (1d100). Good for debugging/transparency. var mod = 0; // Variable to keep track of mod throughout number calculations. var rollCount=1; // Number of times dice have been rolled. Good for debugging and fumble validation. var fumbleLevel; // Number used when fumble is achieved. Fumbles depend on context, so little automated use. var total = 0; // The final result of the roll. var inhumanity = false; // Limit rolls below 320 unless this is true. var zen = false; // Limit rolls below 440 unless this is true. var rollTracking=''; // Track rolls in string form for logs / output. var initiative = false; // Flag to set the result after flag calculation to the initiative table. var reset = false; // Flag to reset the initiative window in case pages were changed. var capped = false; // Flag to display to the player when their roll is limited. //loop through the remaining parts for flags and other mods _.each(parts,function(curPart) { if(!isNaN(Number(curPart))) //add numbers to mod mod+= Number(curPart); else { if(curPart == 'inhumanity' || curPart == 'inhuman') inhumanity = true; if(curPart == 'zen') zen = true; if(curPart == 'reset') reset = true; if(curPart == 'initiative') { initiative = true; close = true; } if(curPart== 'close') close = true; if(curPart == 'complex') { if(!initiative) // complex doesn't apply to initiative rolls. fumbleCeil+=2; } if(curPart == 'mastery') { if(!initiative) //mastery doesn't apply to initiative rolls. fumbleCeil--; } } }); total += mod; log('Openroll Floor: '+openFloor); log('Fumble Ceiling: '+fumbleCeil); //Roll the actual dice already! do { open=false; // Reset open roll flag. dice = randomInteger(100); // Roll 1d100. log('Dice roll ' +rollCount + ': '+dice); if(dice <= fumbleCeil && rollCount==1) { // if dice result was < fumbleCeil, a fumble has occured. // NOTE: fumbles cannot happen after an open roll occurs, hence the check on rollCount. fumbleMod = 0; fumbleDice = 0; if (initiative) { fumbleModArray = [-125, -100, -75]; fumbleLevel = fumbleModArray[dice-1]; } else { fumbleModArray = [-15, 0, 15, 15, 15]; fumbleModArrayMastery = [0, 15]; if (fumbleCeil == 2) fumbleMod = fumbleModArrayMastery[dice-1]; else fumbleMod = fumbleModArray[dice-1]; fumbleDice=randomInteger(100); fumbleLevel= fumbleMod - fumbleDice; log('Fumble Level: '+fumbleLevel); sendChat( who, '<a style="color:Red"><b>Fumble! Level: '+fumbleLevel+'</b></a>'); if (fumbleLevel <= -80) sendChat( who, '<a style="color:Red"><b>Critical Fumble!</b></a>'); } total += fumbleLevel; rollTracking += '('+fumbleLevel+')+'; } else { //check if dice result was > openFloor, if so, will roll again. if(dice >= openFloor && !close) { if(openFloor <100) openFloor++; // Increment openFloor, can never exceed 100. sendChat( who,rollCount+' Open Roll! <a style="color:Green"> <b>('+dice+')</b></a>'); // Bragging / transparency. rollTracking += '<a style="color:Green"><b>('+dice+')</b></a>+'; // Add dice roll to string for output later. open=true; rollCount++; } else rollTracking += '('+dice+')+'; total += dice; //record the total so far. } } while(open); //roll again if openroll occured. //take off the last + in the rollTracking string so the output doesn't look stupid. rollTracking = rollTracking.substring(0,(rollTracking.length)-1); //apply any and all limitations. if(inhumanity && !zen)//only one can apply, as zen overwrites inhumanity. { if(total>439) { capped = true; // the player was limited. dice = total; //reuse variable, save original (uncapped) roll. total = 439; //zen starts at 440. } }else if(!zen) { if(total > 319) { capped = true; // the player was limited. dice = total; // reuse variable, save original (uncapped) roll. total = 319; // standard roll, limit below inhumanity (320). } } //no else case for if(zen), as all rolls above 440 are allowed if the roll has zen. if(capped) { sendChat(who,'/direct <p style="color:GoldenRod"><b>CAPPED! ('+dice+')</b></p>'); } sendChat(who, '/direct Roll Result: ['+rollTracking+']+('+mod+') = ('+total+')'); //Separate logic for applying the above roll to the current token's Initiative. if(initiative) { var turnorder; //turn object that keeps all initiative information. if(Campaign().get("turnorder") == "" || reset) turnorder = []; //check for reset flag or empty init else turnorder = JSON.parse(Campaign().get("turnorder")); //reject any tokens from turnorder that match this ID . turnorder = _.reject(turnorder,function(curChar) { if(curChar.id == msg.selected[0]._id) //same character return true; }); //push new token onto the turn order. turnorder.push ({ id: msg.selected[0]._id, pr: total, }); //If the initiative window is not displayed, open it. Campaign().initiativepage=true; //Add a new custom entry to the end of the turn order. Campaign().set("turnorder", JSON.stringify(turnorder)); } } });