Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
Create a free account

[Script] Skill Challenge


Edited 1663957070
Skill Challenge v 1.0 Welcome to Skill Challenge. This script helps in creating a Skill Challenge for your players without having to track each roll an Successes or Failures. Roll20's D&amp;D 5e Sheets were used in the development. Using Skill Challenge The script will prompt inputs from GM and Players. The GM can set the DC for the Challenge and how many Successes are needed to complete the Challenge. Failures will always be Successes divided by 2 rounded up.&nbsp; Basic Commands The script uses a standardized API command syntax. All Skill Challenge commands will begin with !sc . This will then be followed by a space, a double dash preceding a keyword. This looks like this: !sc --showChallenge Setting up Skill Challenge After installing the script and reloading the sandbox, Skill Challenge will create 7 new macros. For the GM are the following necessary: API-SkillChallenge-Start , API-SkillChallenge-Show , API-SkillChallenge-Stop As GM you should show them in your macro bar, for easy use. Using Skill Challenge Start by using the API-SkillChallenge-Start macro. This will give you 3 Options. Set DC - Here you can set the DC for the Challenge. Hide DC - Here you can toggle to hide the DC or show it. Set Success/Failures - Here you can set the Successes needed to complete the Skill Challenge After setting everything up, use the API-SkillChallenge-Show macro. Now Players can select a skill with the Select Skill button in the chat. After submitting a skill roll will be shown for the character. If the roll succeeded or failed will also be shown. The details of the roll will be whispered to the player. It will be shown how many successes and failures the party currently have, and a new prompt for the Select Skill will pop up in the chat. The Skill Challenge ends when the Successes reach the max amount or the Failures. As GM you can use the API-SkillChallenge-Stop macro to preemptively stop the Skill Challenge. Maybe the party got into a fight, who knows. I would love some feedback for my first script. I am pretty sure both, usability and the code, can be improved. Github link&nbsp; <a href="" rel="nofollow"></a> Code:&nbsp; //VARs Region const VERSION = 1.0; const macroNames = ["API-SkillCha-Hide-DC", "API-SkillCha-Select-Skill", "API-SkillCha-Set-DC", "API-SkillCha-Set-Successes", "API-SkillChallenge-Show","API-SkillChallenge-Start", "API-SkillChallenge-Stop"]; var inChallenge = false; const skills = ['Acrobatics', 'Animal Handling', 'Arcana', "Athletics", "Deception", "History", "Insight", "Intimidation", "Investigation", "Medicine", "Nature", "Perception", "Performance", "Persuasion", "Religion", "Sleight of Hand", "Stealth", "Survival"]; const skillsWithBlank = ['Animal Handling', "Sleight of Hand"]; var currentDc = 0; var currentPlayerSkill = ""; var maxSuccesses = 0; var maxFailures = 0; var currentSuccesses = 0; var currentFails = 0; var currentPlayer = ""; var currentCharacter = ""; var isGM = false; var dcHidden = false; //rolls var skillRoll = 0; //1d20 var skillBonus = 0; var profBonus = 0; var skillCheck = 0; //VARs Region END function resetVars(){ inChallenge = false; currentDc = 0; currentPlayerSkill = ""; maxSuccesses = 0; maxFailures = 0; currentSuccesses = 0; currentFails = 0; currentPlayer = ""; currentCharacter = ""; isGM = false; skillRoll = 0; skillBonus = 0; profBonus = 0; skillCheck = 0; dcHidden = false; } //check if Skill Challenge is beeing used the first time. on("ready", function(){ hasAllMacros(); }) function hasAllMacros(){ for(let i = 0; i &lt; macroNames.length;i++){ let macro = findObjs({ _type: "macro", name: macroNames[i]})[0]; if(!macro){ log("Skill Challenge --&gt; Macro: " + macroNames[i] + " NOT Found!"); log("Skill Challenge --&gt; Creating Macro: " + macroNames[i]); createMacros(macroNames[i]); } } log("Skill Challenge --&gt; All Macros Ready"); } function createMacros(macroname){ let players = findObjs({_type:"player"}).filter(p =&gt; playerIsGM(p.get('_id'))); let API_SkillCha_Hide_DC = { name: "API-SkillCha-Hide-DC", action: "!sc --toggleDC", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } let API_SkillCha_Select_Skill = { name: "API-SkillCha-Select-Skill", action: "!sc --skill ?{Select your skill|Acrobatics|Animal Handling|Arcana|Athletics|Deception|History|Insight|Intimidation|Investigation|Medicine|Nature|Perception|Performance|Persuasion|Religion|Sleight of Hand|Stealth|Survival}", istokenaction: false, visibleto: "all", _playerid: players[0].get("id") } let API_SkillCha_Set_DC = { name: "API-SkillCha-Set-DC", action: "!sc --dc ?{Set DC}", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } let API_SkillCha_Set_Successes = { name: "API-SkillCha-Set-Successes", action: "!sc --successes ?{Success Count}", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } let API_SkillChallenge_Show = { name: "API-SkillChallenge-Show", action: "!sc --showChallenge", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } let API_SkillChallenge_Start = { name: "API-SkillChallenge-Start", action: "!sc --startChallenge", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } let API_SkillChallenge_Stop = { name: "API-SkillChallenge-Stop", action: "!sc --stopChallenge", istokenaction: false, visibleto: "", _playerid: players[0].get("id") } switch(macroname){ case "API-SkillCha-Hide-DC": createObj("macro", API_SkillCha_Hide_DC); break; case "API-SkillCha-Select-Skill": createObj("macro", API_SkillCha_Select_Skill); break; case "API-SkillCha-Set-DC": createObj("macro", API_SkillCha_Set_DC); break; case "API-SkillCha-Set-Successes": createObj("macro", API_SkillCha_Set_Successes); break; case "API-SkillChallenge-Show": createObj("macro", API_SkillChallenge_Show); break; case "API-SkillChallenge-Start": createObj("macro", API_SkillChallenge_Start); break; case "API-SkillChallenge-Stop": createObj("macro", API_SkillChallenge_Stop); break; } } //main on("chat:message", function(msg){ //only continue when chatmessage is an !api message if(msg.type !== "api"){ return; } if(msg.content === "!"){ return; } log(msg); msgArgs = msg.content.split(" "); if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--startChallenge" &amp;&amp; inChallenge === false){ inChallenge = true; setDc(); setSuccesses(); } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--stopChallenge" &amp;&amp; inChallenge === true){ resetVars(); } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--showChallenge"){ if(currentDc !== 0 &amp;&amp; maxSuccesses !== 0){ sendChat("SkillChallenge", " &amp;{template:desc} {{desc=A new Skill Challenge has begun!}}"); showPlayers(); getSkill(); } } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--toggleDC"){ dcHidden = !dcHidden; if(dcHidden){ sendChat("SkillChallenge", "/w gm &amp;{template:desc} {{desc=DC will be hidden}}"); }else{ sendChat("SkillChallenge", "/w gm &amp;{template:desc} {{desc=DC will be shown}}"); } } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--dc"){ currentDc = msgArgs[2]; } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--successes"){ maxSuccesses = msgArgs[2]; maxFailures = Math.ceil(maxSuccesses/2); } if(msgArgs[0] === "!sc" &amp;&amp; msgArgs[1] === "--skill"){ currentPlayerSkill = skillHandling(msgArgs[2]); currentPlayer = getPlayer(msg.playerid); currentCharacter = getCharacter(currentPlayer.get("id")); } if(inChallenge){ if(currentDc !== 0 &amp;&amp; maxSuccesses !== 0){ if(currentPlayerSkill !== ""){ getSkillCheck(); presentSkillCheck(); if(hasSkillChallengeEnded() == true){ resetVars(); }else{ presentProgress(); } } } } }) function setDc(){ sendChat("SkillChallenge", "/w gm &amp;{template:desc} {{desc=[Set DC](!&amp;#13;#API-SkillCha-Set-DC)}}"); sendChat("SkillChallenge", "/w gm &amp;{template:desc} {{desc=[Hide DC](!&amp;#13;#API-SkillCha-Hide-DC)}}"); } function setSuccesses(){ sendChat("SkillChallenge", "/w gm &amp;{template:desc} {{desc=[Set Success/Failures](!&amp;#13;#API-SkillCha-Set-Successes)}}"); } function showPlayers(){ if(dcHidden){ sendChat("SkillChallenge", " &amp;{template:simple} {{r1=Secret}} {{r2= " + maxSuccesses + "}} {{charname=DC to Beat / Successes Needed}} {{always=1}}"); }else{ sendChat("SkillChallenge", " &amp;{template:simple} {{r1=" + currentDc + "}} {{r2= " + maxSuccesses + "}} {{charname=DC to Beat / Successes Needed}} {{always=1}}"); } } function getSkill(){ sendChat("SkillChallenge", " &amp;{template:desc} {{desc=[Select Skill](!&amp;#13;#API-SkillCha-Select-Skill)}}"); } function skillHandling(skill){ let playerSkill = skill; playerSkill = playerSkill.toLowerCase(); if(playerSkill == "animal"){ playerSkill = skillsWithBlank[0] } if(playerSkill == "sleight"){ playerSkill = skillsWithBlank[1] } log("playerSkill: " + playerSkill); for(let i = 0; i &lt; skills.length; i++){ if(playerSkill == skills[i].toLowerCase()){ return skills[i]; } } return ""; } function getPlayer(playerid){ let player = findObjs({ _type: "player", _id: playerid})[0]; return player; } function getCharacter(playerid){ let character = findObjs({ _type: "character", controlledby: playerid}); if(playerIsGM(playerid)){ character = findObjs({ _type: "character", name: "Lux"})[0]; } return character; } function getSkillCheck(){ skillRoll = randomInteger(20); skillBonus = getAttribute(currentCharacter.get("id"), currentPlayerSkill); skillCheck = Number(skillRoll) + Number(skillBonus) + Number(profBonus); log("skillRoll: " + skillRoll + " skillBonus: " + skillBonus + " profBonus: " + profBonus); if(checkifSuccessOrFailure() == "Success!" ){ currentSuccesses++; }else{ currentFails++; } } function getAttribute(charid, skill){ let attri = findObjs({ _type: "attribute", _characterid: charid, name: skillHandlingForSearch(skill)})[0]; let attriBonus = attri.get("current"); if(Number.isInteger(attriBonus)){ profBonus = 0; }else{ if(attriBonus.includes("+")){ profBonus = attriBonus.split("+")[1]; profBonus = randomInteger(profBonus.split("d")[1]); attriBonus = attriBonus.split("+")[0]; } } return attriBonus; } function skillHandlingForSearch(skill){ skill = skill.toLowerCase(); skill = skill.replace(" ", "_"); skill = skill + "_bonus"; return skill; } function presentSkillCheck(){ sendChat("SkillChallenge", " &amp;{template:simple} {{rname=" + currentCharacter.get("name")+ "'s Skill Check VS DC}} {{r1=" +skillCheck+"}} {{r2="+currentDc+"}}{{always=1}}{{charname="+checkifSuccessOrFailure()+"}}"); sendChat("SkillChallenge", "/w "+ currentPlayer.get("displayname") + " &amp;{template:desc}{{desc=Your SkillRoll: " + skillRoll + "/20 + SkillBonus: " + skillBonus + "+ ProfBonus: " + profBonus+"}}"); } function checkifSuccessOrFailure(){ if(skillCheck &gt;= currentDc){ return "Success!"; }else{ return "Fail!"; } } function hasSkillChallengeEnded(){ if(currentSuccesses == maxSuccesses){ sendChat("SkillChallenge", " &amp;{template:desc} {{desc=The Skill Challenge has Succeeded! You WON!}}"); return true; } if(currentFails == maxFailures){ sendChat("SkillChallenge", " &amp;{template:desc} {{desc=The Skill Challenge has Failed!}}"); return true; } return false; } function presentProgress(){ sendChat("SkillChallenge"," &amp;{template:desc}{{desc=You currently have: " + currentSuccesses + "/" + maxSuccesses + " Successes}}"); sendChat("SkillChallenge"," &amp;{template:desc}{{desc=You currently have: " + currentFails + "/" + maxFailures + " Failures }}") sendChat("SkillChallenge"," &amp;{template:desc}{{desc=[Select Skill](!&amp;#13;#API-SkillCha-Select-Skill)}}"); }
Forum Champion
Marketplace Creator
API Scripter
Hi Patrick! Looks very useful! There is a small typo in your documentation: The script uses a standardized API command syntax. All Skill Challenge commands will begin with !sk. This will then be followed by a space, a double dash preceding a keyword. This looks like this: !sc --showChallenge That first !sk should be !sc
Hi keithcurtis, oh yeah thanks, i fixed that now.
Sheet Author
Is this tailored for a specific sheet?&nbsp; I don't see that mentioned in the OP, and the skills mentioned could apply to several game systems.
Hi Kraynic, Thanks for pointing that out, Roll20's D&amp;D 5e Sheets were used in the development, so with them it should work properly. I have not tested it with other sheets, but they could work if the skills are identical.