Script:Conditions and Status Tracker
From Roll20 Wiki
Version: 1.8
Last Modified: 2014-01-15
Code: {{{2}}}
Dependencies: None
Conflicts: None
Tracks arbitrary statuses associated with tokens. Statuses may have a turn-limited duration, or may be permanent. Only the GM may modify what statuses exist on a given token, but depending on settings the status may be broadcast to all players.
The API commands !StatusAdd
, !StatusDel
, !StatusAll
, and !StatusClearAll
are used to manipulate what statuses are present.
Syntax
!StatusAdd <status> <duration> <description> [GM only flag] [// marker]
!StatusDel <status>
!StatusAll
!StatusClearAllFormally:
S
→ !StatusAddstatus
duration
description
GMFlag
marker
S
→ !StatusDelstatus
S
→ !StatusAll
S
→ !StatusClearAll
status
→ string
duration
→ integer
description
→ string
GMFlag
→ ε
GMFlag
→ GM
marker
→ ε
marker
→ // string
Parameter | Values |
---|---|
status | Name of the status to add. No spaces are allowed. |
duration | Number of turns the status lasts. Use -1 for permanent duration.
|
description | Description of the status. |
GM only flag | Optional. GM is the only accepted value. If this flag is set, only the GM will be notified about this status.
|
marker | Optional. Defines the statusmarker to set on the token representing this status. |
Installation
There are several configuration variables near the top of the script. You may alter them to customize the script functionality:
- StatusTracker.statusIndicator – Then default marker to use if
marker
is not supplied. - StatusTracker.Chat – Set to
true
to indicate the status in the chat window, orfalse
otherwise. - StatusTracker.ChatDescriptions – Set to
true
to display the status descriptions, orfalse
otherwise. - StatusTracker.ChatOnlyGM – If set to
true
, all chat messages will be sent to the GM regardless of theGM
flag. Set tofalse
to control statuses individually. - StatusTracker.currentTurn – Not customizable.
- StatusTracker.bloodiedTintColor – Set to a hexadecimal color value in order to tint the token to the set color when
bar1
is below half of its maximum. Set to"transparent"
to disable this behavior. - StatusTracker.bloodiedMarker – Set to a statusmarker name in order to mark the token when
bar1
is below half of its maximum. Set to"none"
to disable this behavior. - StatusTracker.deadMarker – Set to a statusmarker name which indicates that the token is dead (
bar1
is 0). - StatusTracker.statusIncidatorBG – Background style color for status chat messages.
- StatusTracker.statusIndicatorFontColor – Foreground style color for status chat messages.
Code
// Gist: https://gist.github.com/DarokinB/5776368 /* *********** STATUS TRACKER ********************************************* * The purpose of this script is to track character and token status * as well as durations of the status, with a reminder given on each turn. * It allows the GM to manimpulate the controls, while players can only test * their characters current status, if the GM allows it. It will use basic * API commands. * Please Note: This script will lock up one of the indicators on a token * to show a character is under a specific effect. You can change which * indicator to your preference. * * Commands: * !StatusAdd <status name> <duration, -1 for perm> <description> <GM Only Flag> // <marker name> * !StatusDel <status name> * !StatusAll * !StatusClearAll * * For ease of use, add the following to a macro and assign it to your macro bar: * !StatusAdd ?{Status name} ?{Status Duration, -1 for perm} ?{Status description} ?{Type GM for GM only} // ?{Status Indicator [default is purple]} * NOTE: If you end the command with GM, only the GM will be able to see the * reminder. * * NOTE: Names cannot contain spaces, but it searches for containing so for * 'Hank the Dwarf' you can put !Status Hank Bless 10 * *****************************************************************************/ //Storage location state.activeStatus = state.activeStatus || new Array(); var StatusTracker = StatusTracker || {}; //Personal Settings StatusTracker.statusIndicator = "purple"; //default marker if DM does not designate one StatusTracker.Chat = true; //Uses chat window to give indicators StatusTracker.ChatDescriptions = true; //displays descriptions StatusTracker.ChatOnlyGM = false; //All chat only goes to the gm StatusTracker.currentTurn = ""; //The current tokens turn StatusTracker.bloodiedTintColor = "#FF0000"; //If you want a creature's token to be tinted when below 1/2 hp (bar1_value), 'transparent' for none. StatusTracker.bloodiedMarker = "none"; //If you want a creature's token to get marked when below 1/2 hp (bar1_value), 'none' for none. StatusTracker.deadMarker = "dead"; //Marker to show if the token is dead (0 bar1_value) StatusTracker.statusIncidatorBG = 'blue'; StatusTracker.statusIndicatorFontColor = '#ffffff'; //Adding a status to the tracker StatusTracker.AddStatus = function(CharID, statusName, statusDescript, Duration, GMOnly, Marker) { if (CharID == "") return; //Don't add empty statuses state.activeStatus.push({ 'CharID': CharID, 'statusName': statusName, 'statusDescript': statusDescript, 'Duration': Duration, 'GMOnly': GMOnly, 'Marker': Marker, }); StatusTracker.addMarker(CharID, Marker); } StatusTracker.addMarker = function(CharID, Marker) { //Find the token with the ID: non-linked tokens var currChar = findObjs({ _type: "graphic", _id: CharID, _pageid: Campaign().get("playerpageid"), }); _.each(currChar, function(obj) { obj.set("statusmarkers", StatusTracker.addMarkerToString(obj.get("statusmarkers"), Marker)); }); } //Removes a marker of the listed type if it is present StatusTracker.delMarker = function(CharID, Marker) { //Find the token with the ID: non-linked tokens var currChar = findObjs({ _type: "graphic", _id: CharID, _pageid: Campaign().get("playerpageid"), }); _.each(currChar, function(obj){ var returnString = StatusTracker.delMarkerFromString(obj.get("statusmarkers"), Marker); obj.set("statusmarkers", returnString); }); } StatusTracker.addMarkerToString = function(Original, AddMarker){ var splitOriginal = Original.split(","); var newString = new Array(); var maxLoops = splitOriginal.length; var loop = 0; var Added = false; while (loop < maxLoops && Added == false) { if (splitOriginal[loop].indexOf(AddMarker) !== -1){ //already has more than one if(splitOriginal[loop].indexOf("@") !== -1) { var num = Number(splitOriginal[loop].substr(splitOriginal[loop].length - 1)); num += 1; splitOriginal[loop] = splitOriginal[loop].substr(0,splitOriginal[loop].length - 1) + num; Added = true; } //already has it but not at multiple, first check to make sure not added //above, then add 1 more. Forced addition of @ if(Added == false) { splitOriginal[loop] = splitOriginal[loop] + "@2"; Added = true; } } loop += 1; } if (Added == false ) { return Original + "," + AddMarker; } else { return splitOriginal.join(); } } StatusTracker.delMarkerFromString = function(Original, delMarker) { var splitOriginal = Original.split(","); var maxLoops = splitOriginal.length; var loop = 0; var Deleted = false; while (loop < maxLoops && Deleted == false) { if (splitOriginal[loop].indexOf(delMarker) !== -1) { //already has more than one if(splitOriginal[loop].indexOf("@") !== -1) { var num = Number(splitOriginal[loop].substr(splitOriginal[loop].length - 1)); //Lowers the count down num -= 1; //tests for solitary remaining if (num == 1 ) { splitOriginal[loop] = splitOriginal[loop].substr(0,splitOriginal[loop].length - 2); } else { splitOriginal[loop] = splitOriginal[loop].substr(0,splitOriginal[loop].length - 1) + num; } Deleted = true; } //Matches but there are not multiple, removes instance if (Deleted == false) { splitOriginal.splice(loop,1); Deleted = true; } } loop += 1; } return splitOriginal.join(); } StatusTracker.getTokenName = function(CharID) { if (CharID == "") return ""; //if CharID is a token, turn on the status indicator for the token var Chars = findObjs({ _type: "graphic", _id: CharID, }); if (Chars.length > 0 ) { var name = Chars[0].get("name"); } return name; } StatusTracker.DelStatus = function(CharID, statusName){ var loop = 0; var maxLoops = state.activeStatus.length; while (loop < maxLoops) { if (state.activeStatus[loop].statusName == statusName && state.activeStatus[loop].CharID == CharID) { //Message message = state.activeStatus[loop].statusName + " ends."; StatusTracker.sendMessage(message, state.activeStatus.GMOnly, state.activeStatus[loop].statusDescript); //Remove the relevant marker StatusTracker.delMarker(state.activeStatus[loop].CharID, state.activeStatus[loop].Marker); state.activeStatus.splice(loop,1); loop = loop - 1; maxLoops = maxLoops - 1; } loop = loop + 1; } } StatusTracker.sendMessage = function(message, GMOnly, description) { if (StatusTracker.Chat == false) return; //if not a direct message, assign chat limiters if (message.indexOf("/direct") > 0 ) { if (GMOnly == true || StatusTracker.ChatOnlyGM == true) { message = "/w gm " + message; } else { message = "/desc " + message; } } if (StatusTracker.ChatDescriptions == true && description != "") { message = message + " (" + description + ")"; } sendChat("", message); } StatusTracker.newTurn = function(CharID) { //loops through all durations and effects ones on the current character/token var loop = 0; var maxLoops = state.activeStatus.length; var count = 0; if (maxLoops == 0) return; //If the current token does not have any statues, exit while (loop < maxLoops) { if (state.activeStatus[loop].CharID == CharID) {count += 1} loop = loop + 1 } if (count == 0) return; //reset loop loop = 0; //Extract the current tokens name var charName = StatusTracker.getTokenName(CharID) if (StatusTracker.Chat) { StatusTracker.sendMessage("/direct <div align='center'> <p style='color:" + StatusTracker.statusIndicatorFontColor + "; background-color:" + StatusTracker.statusIncidatorBG + "'> **** " + charName + " Status Effects ****</p></div>", StatusTracker.ChatOnlyGM, "") } while (loop < maxLoops) { //Decrement Duration var Duration = Number(state.activeStatus[loop].Duration || 1); // A -1 Duration is permanent, the current token/character's statuses are // increments for the next round. if (Duration > 0 && state.activeStatus[loop].CharID == CharID) { state.activeStatus[loop].Duration = Duration - 1; } //Still active, announced if (Duration > 1 && state.activeStatus[loop].CharID == CharID) { StatusTracker.sendMessage(StatusTracker.getTokenName(state.activeStatus[loop].CharID) + " " + state.activeStatus[loop].statusName + " for " + (Duration - 1) + " more rounds.", state.activeStatus[loop].GMOnly, state.activeStatus[loop].statusDescript); } //Permanent announced if (Duration == -1 && state.activeStatus[loop].CharID == CharID) { StatusTracker.sendMessage(StatusTracker.getTokenName(state.activeStatus[loop].CharID) + " " + state.activeStatus[loop].statusName + " still.", state.activeStatus[loop].GMOnly, state.activeStatus[loop].statusDescript); } //Ending effects if (Duration <= 1 && Duration !== -1 && state.activeStatus[loop].CharID == CharID) { StatusTracker.sendMessage( StatusTracker.getTokenName(state.activeStatus[loop].CharID) + " " + state.activeStatus[loop].statusName + " ends.", state.activeStatus[loop].GMOnly, state.activeStatus[loop].statusDescript); StatusTracker.DelStatus(state.activeStatus[loop].CharID, state.activeStatus[loop].statusName); //drop loop by 1 now that there is 1 less object in the array. loop = 0; maxLoops = state.activeStatus.length; } loop = loop + 1; } if (StatusTracker.Chat) { StatusTracker.sendMessage("/direct <div align='center'> <p style='color:" + StatusTracker.statusIndicatorFontColor + "; background-color:" + StatusTracker.statusIncidatorBG + "'> **** End " + charName + " Status Effects ****</p></div>", StatusTracker.ChatOnlyGM, "") } return; } StatusTracker.getCurrentToken = function() { var turn_order = JSON.parse(Campaign().get('turnorder')); if (!turn_order.length) { return ""; } var turn = turn_order.shift(); return getObj('graphic', turn.id) || ""; }; on("change:campaign:turnorder", function() { var status_current_token = StatusTracker.getCurrentToken(); //Handler for non-token items in initiative if (status_current_token == "") return; //If turn order was changed but it is still the same persons turn, exit if (status_current_token.id == StatusTracker.currentTurn) return; StatusTracker.currentTurn = status_current_token.id; StatusTracker.newTurn(status_current_token.id); }); on("chat:message", function(msg) { var cmd = "!StatusAll"; if (msg.type == "api" && msg.content.indexOf(cmd) !== -1 && msg.who.indexOf("(GM)") !== -1) { var loop = 0; var maxLoops = state.activeStatus.length; StatusTracker.sendMessage("/direct <div align='center'> <p style='color:" + StatusTracker.statusIndicatorFontColor + "; background-color:" + StatusTracker.statusIncidatorBG + "'> **** All Status Effects ****</p></div>", StatusTracker.ChatOnlyGM, "") while (loop < maxLoops) { sendChat("", "/w gm " + StatusTracker.getTokenName(state.activeStatus[loop].CharID) + " " + state.activeStatus[loop].statusName + " for " + state.activeStatus[loop].Duration + " rounds. [" + state.activeStatus[loop].Marker + " marker].") loop = loop + 1; } StatusTracker.sendMessage("/direct <div align='center'> <p style='color:" + StatusTracker.statusIndicatorFontColor + "; background-color:" + StatusTracker.statusIncidatorBG + "'> **** End All Status Effects ****</p></div>", StatusTracker.ChatOnlyGM, "") } }); on("chat:message", function(msg) { var cmd = "!StatusClearAll"; if (msg.type == "api" && msg.content.indexOf(cmd) !== -1 && msg.who.indexOf("(GM)") !== -1) { state.activeStatus = new Array(); } }); on("chat:message", function(msg) { var cmd = "!StatusAdd "; if (msg.type == "api" && msg.content.indexOf(cmd) !== -1 && msg.who.indexOf("(GM)") !== -1) { var cleanedMsg = msg.content.replace(cmd, ""); //Pulls any marker first var marker = cleanedMsg.split("// ")[1] || StatusTracker.statusIndicator; cleanedMsg = cleanedMsg.split("//")[0]; //Pulls the effect name var statusName = cleanedMsg.split(" ")[0]; cleanedMsg = cleanedMsg.substr(statusName.length + 1) //Removes the target from the array //Pulls the duration var Duration = cleanedMsg.split(" ")[0]; cleanedMsg = cleanedMsg.substr(Duration.length + 1) //Removes the target from the array var GMOnly = false; if (cleanedMsg.substr(cleanedMsg.length - 2) == "GM") { GMOnly = true; cleanedMsg = cleanedMsg.substr(0, cleanedMsg.length - 2); } //The remainder is the description var statusDescription = cleanedMsg; //Adds the status to each of the selected tokens _.each(msg.selected, function (obj){ //Runs through each selected token and adds the status if (obj._type == "graphic") { //only if the selected is a token StatusTracker.AddStatus(obj._id, statusName, statusDescription, Duration, GMOnly, marker); sendChat("","/desc " + statusName + " added.") } }) } }); on("chat:message", function(msg) { var cmd = "!StatusDel "; if (msg.type == "api" && msg.content.indexOf(cmd) !== -1 && msg.who.indexOf("(GM)" !== -1)) { var CharID = ""; var cleanedMsg = msg.content.replace(cmd, ""); //Pulls the effect name var statusName = cleanedMsg.split(" ")[0]; cleanedMsg = cleanedMsg.substr(statusName.length + 1) //Removes the target from the array _.each(msg.selected, function (obj){ //Deletes the statud from each selected ID if (obj._type == "graphic") { //only if the selected is a token StatusTracker.DelStatus(obj._id, statusName); } }) } }); on("change:graphic:bar1_value", function(obj, prev) { if(obj.get("bar1_max") === "") return; if(obj.get("bar1_value") <= 0) { obj.set({ status_dead: true, tint_color: "transparent" }); } else if(obj.get("bar1_value") <= obj.get("bar1_max") / 2) { obj.set({ tint_color: "#FF0000", status_dead: false }); } else{ obj.set({ tint_color: "transparent", status_dead: false }); } });