Skip to content

Instantly share code, notes, and snippets.

@kjaegers

kjaegers/scriptcards.js Secret

Last active May 6, 2021
Embed
What would you like to do?
Public development version of ScriptCards
// Github: https://gist.github.com/kjaegers/515dff0f04c006d7192e0fec534d96bf
// By: Kurt Jaegers
// Contact: https://app.roll20.net/users/2365448/kurt-j
var API_Meta = API_Meta||{};
API_Meta.ScriptCards={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
{try{throw new Error('');}catch(e){API_Meta.ScriptCards.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}}
var scriptCardsStashedScripts = {};
const ScriptCards = (() => { // eslint-disable-line no-unused-vars
/*
ScriptCards is a run-time script interpreter for use in Roll20. Scripts for ScriptCards are entered into the chat window, either directly, through cut/paste,
or executed from macros. A ScriptCard script consists of one or more lines, each delimited by a double dash (--) starting the line, followed by a statement type
identifier.
After the identifier, is a line tag, followed by a vertical bar (|) character, followed by the line content. The scripting language supports end-inclusion of
function libraries with the +++libname+++ directive, which will be pre-parsed and removed from the script. Any number of libraries can be specified by separating
library names (case sensitive) with semicolons (;).
Please see the ScriptCards Wiki Entry on Roll20 at https://wiki.roll20.net/Script:ScriptCards for details.
*/
/* Inline Update Notes
*/
const APINAME = "ScriptCards";
const APIVERSION = "1.2.2b";
const APIAUTHOR = "Kurt Jaegers";
// These are the parameters that all cards will start with. This table is copied to the cardParameters table inside the processing loop and that table is updated
// with settings from --# lines in the script.
const defaultParameters = {
reentrant: "0",
tableborder: "2px solid #000000;",
tablebgcolor: "#EEEEEE",
tableborderradius: "6px;",
tableshadow: "5px 3px 3px 0px #aaa;",
title: "ScriptCards",
titlecardbackground: "#1C6EA4",
backgroundimage: "url(https://s3.amazonaws.com/files.d20.io/images/213988167/5OWd9-vnDUGdtYI0cjeGxA/original.jpg?16178201495)",
titlecardbottomborder: "2px solid #444444;",
titlefontface: "Contrail One",
titlefontsize: "1.2em",
titlefontlineheight: "1.2em",
lineheight: "normal",
titlefontcolor: "#FFFFFF",
subtitlefontsize: "13px",
subtitlefontface: "Tahoma",
subtitlefontcolor: "#FFFFFF",
subtitleseperator: " &" + "#x2666; ",
tooltip: "Sent by ScriptCards",
bodyfontsize: "14px;",
bodyfontface: "Helvetica",
oddrowbackground: "#D0E4F5",
evenrowbackground: "#eeeeee",
oddrowfontcolor: "#000000",
evenrowfontcolor: "#000000",
whisper: "",
emotetext: "",
sourcetoken: "",
targettoken: "",
activepage: "",
emotebackground: "#f5f5ba",
emotefont: "font-family: Georgia, serif; font-weight: bold; ",
emotestate: "visible",
rollfontface: "helvetica",
leftsub: "",
rightsub: "",
sourcecharacter: "",
targetcharacter: "",
activepageobject: undefined,
debug: "0",
hidecard: "0",
hidetitlecard: "0",
dontcheckbuttonsforapi: "0",
roundup: "0",
buttonbackground: "#1C6EA4",
buttontextcolor: "#FFFFFF",
buttonbordercolor: "#999999",
buttonfontsize: "x-small",
buttonfontface: "Tahoma",
dicefontcolor: "#1C6EA4",
dicefontsize: "3.0em",
usehollowdice: "0",
allowplaintextinrolls: "0",
whisper: "",
showfromfornonwhispers: "0",
allowinlinerollsinoutput: "0",
nominmaxhighlight: "0",
disablestringexpansion: "0",
disablerollvariableexpansion: "0",
disableparameterexpansion: "0",
disablerollprocessing: "0",
disableattributereplacement: "0",
disableinlineformatting: "0",
styleTableTag: " border-collapse:separate; border: solid black 2px; border-radius: 6px; -moz-border-radius: 6px; ",
styleNormal:" text-align: center; font-size: 100%; display: inline-block; font-weight: bold; height: 1em; min-width: 1.75em; margin-top: -1px; margin-bottom: 1px; padding: 0px 2px; border: 1px solid; border-radius: 3px; background-color: #FFFEA2; border-color: #87850A; color: #000000;",
styleFumble: " text-align: center; font-size: 100%; display: inline-block; font-weight: bold; height: 1em; min-width: 1.75em; margin-top: -1px; margin-bottom: 1px; padding: 0px 2px; border: 1px solid; border-radius: 3px; background-color: #FFAAAA; border-color: #660000; color: #660000;",
styleCrit: " text-align: center; font-size: 100%; display: inline-block; font-weight: bold; height: 1em; min-width: 1.75em; margin-top: -1px; margin-bottom: 1px; padding: 0px 2px; border: 1px solid; border-radius: 3px; background-color: #88CC88; border-color: #004400; color: #004400;",
styleBoth: " text-align: center; font-size: 100%; display: inline-block; font-weight: bold; height: 1em; min-width: 1.75em; margin-top: -1px; margin-bottom: 1px; padding: 0px 2px; border: 1px solid; border-radius: 3px; background-color: #8FA4D4; border-color: #061539; color: #061539;",
};
var htmlTemplate = `<div style="display: table; border: !{tableborder}; background-color: !{tablebgcolor}; width: 100%; text-align: left; border-radius: !{tableborderradius}; border-collapse: separate; box-shadow: !{tableshadow};"><div style="display: table-header-group; background: !{titlecardbackground}; border-bottom: !{titlecardbottomborder}"><div style="display: table-row;"><div style="display: table-cell; padding: 2px 2px; text-align: center;"><span style="font-family: !{titlefontface}; font-style:normal; font-size: !{titlefontsize}; line-height: !{titlefontlineheight}; font-weight: bold; color: !{titlefontcolor}; text-shadow: -1px 1px 0 #000, 1px 1px 0 #000, 1px -1px 0 #000, -1px -1px 0 #000;">=X=TITLE=X=</span><br /><span style="font-family: !{subtitlefontface}; font-variant: normal; font-size: !{subtitlefontsize}; font-style:normal; font-weight: bold; color: !{subtitlefontcolor}; ">=X=SUBTITLE=X=</span></div></div></div><div style="display: table-row-group;">`;
var htmlTemplateHiddenTitle = `<div style="display: table; border: !{tableborder}; background-color: !{tablebgcolor}; width: 100%; text-align: left; border-radius: !{tableborderradius}; border-collapse: separate; box-shadow: !{tableshadow};"><div style="display: table-row-group;">`;
var htmlRowTemplate = `<div style="display: table-row; =X=ROWBG=X=;"><div style="display: table-cell; padding: 0px 0px; font-family: !{bodyfontface}; font-style: normal; font-weight:normal; font-size: !{bodyfontsize}; "><span style="line-height: !{lineheight}; color: =X=FONTCOLOR=X=;">=X=ROWDATA=X=</span></div></div>`;
var htmlTemplateEnd = `</div></div><br />`;
var buttonStyle = 'background-color:!{buttonbackground}; color: #!{buttontextcolor}; text-align: center; vertical-align:middle; border-radius: 5px; border-color:!{buttonbordercolor}; font-family: !{buttonfontface}; font-size:!{buttonfontsize};';
var stringVariables = {};
var rollVariables = {};
var arrayVariables = {};
var arrayIndexes = {};
var arrays = [];
var tokenMarkerURLs = [];
var rollComponents = [
'Base','Total','Ones','Aces','Odds','Evens','Odds','RollText','Text','Style', 'tableEntryText', 'tableEntryImgURL', 'tableEntryValue'
];
var tokenAttributes = "token_name:token_id:statusmarkers:bar1_value:bar1_max:bar2_value:bar2_max:bar3_value:bar3_max:top:left:width:height:rotation:layer:aura1_radius:aura1_color:aura2_radius:aura2_color:aura1_square:aura2_square:tint_color:light_radius:light_dimradius:light_angle:light_losangle:light_multiplier:light_otherplayers:light_hassight:flipv:fliph:controlledby:_cardid:_pageid:imgsrc:bar1_link:bar2_link:bar3_link:represents:layer:isdrawing:name:gmnotes:showname:showplayers_name:showplayers_bar1:showplayers_bar2:showplayers_bar3:showplayers_aura1:showplayers_aura2:playersedit_name:playersedit_bar1:playersedit_bar2:playersedit_bar3:playersedit_aura1:playersedit_aura2:lastmove:adv_fow_view_distance:has_bright_light_vision:has_night_vision:night_vision_distance:emits_bright_light:bright_light_distance:emits_low_light:low_light_distance:has_limit_field_of_vision: limit_field_of_vision_center: limit_field_of_vision_total: has_limit_field_of_night_vision: limit_field_of_night_vision_center: limit_field_of_night_vision_total: has_directional_bright_light: directional_bright_light_center: directional_bright_light_total: has_directional_dim_light: directional_dim_light_center: directional_dim_light_total";
var repeatingSection = undefined;
var repeatingSectionIDs = undefined;
var repeatingIndex = undefined;
var repeatingCharID = undefined;
var repeatingCharAttrs = undefined;
var repeatingSectionName = undefined;
var ScriptCardsLibrary = {};
const diceLetters = "JABCDEFGHIJKLMNOPQRSTUVWYZ";
var jsonObject = undefined;
// Used for storing parameters passed to a subroutine with --> or --?|> lines
var callParamList = {};
on('ready', function () {
if (!state[APINAME]) { state[APINAME] = {module: APINAME, schemaVersion: APIVERSION, config: {}, persistentVariables: {} }; }
if (state[APINAME].storedVariables == undefined) { state[APINAME].storedVariables = {}; }
if (state[APINAME].storedSettings == undefined) { state[APINAME].storedSettings = {}; }
if (state[APINAME].storedStrings == undefined) { state[APINAME].storedStrings = {}; }
if (state[APINAME].storedSnippets == undefined) { state[APINAME].storedSnippets = {}; }
const tokenMarkers = JSON.parse(Campaign().get("token_markers"));
for (var x=0;x<tokenMarkers.length;x++) {
tokenMarkerURLs[tokenMarkers[x].name] = tokenMarkers[x].url;
}
loadLibraryHandounts();
API_Meta.ScriptCards.version = APIVERSION;
log(`-=> ${APINAME} - ${APIVERSION} by ${APIAUTHOR} Ready <=- Meta Offset : ${API_Meta.ScriptCards.offset}`);
on("change:handout", function () {
loadLibraryHandounts();
});
on('chat:message', function (msg) {
if (msg.type === "api") {
var apiCmdText = msg.content.toLowerCase();
var processThisAPI = false;
var isResume = false;
var isReentrant = false;
var resumeArgs;
var cardContent;
if (apiCmdText.startsWith("!sc-liststoredsettings")) {
var metaCard = "!scriptcard {{ ";
metaCard += "--#title|Stored Settings Report ";
if (apiCmdText.split(" ").length == 1) {
metaCard += "--#leftsub|Settings List "
var stored = state[APINAME].storedSettings;
for (const key in stored) {
metaCard += `--+${key}|[button]Show::!sc-liststoredsettings ${key}[/button] [button]Delete::!sc-deletestoredsettings ${key}[/button]`;
}
} else {
var settingName = msg.content.substring(msg.content.indexOf(" ")).trim();
if (settingName) {
metaCard += `--#leftsub|Setting List --#rightsub|${settingName} `;
var stored = state[APINAME].storedSettings[settingName];
for (const key in stored) {
if (stored[key] !== defaultParameters[key]) {
metaCard += `--+${key}|${stored[key]}`;
}
}
}
}
metaCard += " }};"
sendChat("API", metaCard);
}
if (apiCmdText.startsWith("!sc-deletestoredsettings ")) {
var settingName = msg.content.substring(msg.content.indexOf(" ")).trim();
if (state[APINAME].storedSettings[settingName]) {
delete state[APINAME].storedSettings[settingName];
metaCard = `!scriptcard {{ --#title|Remove Stored Setting --#leftsub|${settingName} --+|The setting group ${settingName} has been deleted. }} `;
sendChat("API", metaCard);
}
}
if (apiCmdText.startsWith("!sc-resume ")) {
var resumeString = msg.content.substring(11);
resumeArgs = resumeString.split("-|-");
if (scriptCardsStashedScripts[resumeArgs[0]]) {
isResume = true;
processThisAPI = true;
}
}
if (apiCmdText.startsWith("!sc-reentrant ")) {
var resumeString = msg.content.substring(14);
resumeArgs = resumeString.split("-|-");
if (scriptCardsStashedScripts[resumeArgs[0]]) {
isResume = true;
isReentrant = true;
processThisAPI = true;
}
}
if (apiCmdText.startsWith ("!scriptcards ")) { processThisAPI = true; }
if (apiCmdText.startsWith ("!scriptcard ")) { processThisAPI = true; }
if (apiCmdText.startsWith ("!script ")) { processThisAPI = true; }
if (apiCmdText.startsWith ("!scriptcards{{")) { processThisAPI = true; }
if (apiCmdText.startsWith ("!scriptcard{{")) { processThisAPI = true; }
if (apiCmdText.startsWith ("!script{{")) { processThisAPI = true; }
if (processThisAPI) {
var cardParameters = {};
Object.assign(cardParameters,defaultParameters);
// Store labels and their corresponding line numbers for branching
var lineLabels = {};
var labelChecking = {};
// The returnStack stores the line number to return to after a gosub, while the parameter stack stores parameter lists for nexted gosubs
var returnStack = [];
var parameterStack = [];
var tableLineCounter = 0;
// Builds up a list of lines that will appear on the output display
var outputLines = [];
var gmonlyLines = [];
var lineCounter = 1;
// Clear out any pre-existing roll variables
rollVariables = {};
stringVariables = {};
arrayVariables = {};
arrayIndexes = {};
if (msg.playerid) {
var sendingPlayer = getObj("player", msg.playerid);
if (sendingPlayer) {
stringVariables["SendingPlayerID"] = msg.playerid;
stringVariables["SendingPlayerName"] = sendingPlayer.get("_displayname");
stringVariables["SendingPlayerColor"] = sendingPlayer.get("color");
stringVariables["SendingPlayerSpeakingAs"] = sendingPlayer.get("speakingas");
stringVariables["SendingPlayerIsGM"] = playerIsGM(msg.playerid) ? "1" : "0";
}
}
if (isResume) {
var stashIndex = resumeArgs[0];
if (scriptCardsStashedScripts[stashIndex].scriptContent) { cardLines = JSON.parse(scriptCardsStashedScripts[stashIndex].scriptContent); }
if (scriptCardsStashedScripts[stashIndex].cardParameters) { cardParameters = JSON.parse(scriptCardsStashedScripts[stashIndex].cardParameters); }
if (scriptCardsStashedScripts[stashIndex].stringVariables) { stringVariables = JSON.parse(scriptCardsStashedScripts[stashIndex].stringVariables); }
if (scriptCardsStashedScripts[stashIndex].rollVariables) { rollVariables = JSON.parse(scriptCardsStashedScripts[stashIndex].rollVariables); }
if (scriptCardsStashedScripts[stashIndex].arrayVariables) { arrayVariables = JSON.parse(scriptCardsStashedScripts[stashIndex].arrayVariables); }
if (scriptCardsStashedScripts[stashIndex].arrayIndexes) { arrayIndexes = JSON.parse(scriptCardsStashedScripts[stashIndex].arrayIndexes); }
if (scriptCardsStashedScripts[stashIndex].returnStack) { returnStack = JSON.parse(scriptCardsStashedScripts[stashIndex].returnStack); }
if (scriptCardsStashedScripts[stashIndex].parameterStack) { parameterStack = JSON.parse(scriptCardsStashedScripts[stashIndex].parameterStack); }
if (scriptCardsStashedScripts[stashIndex].outputLines) { outputLines = JSON.parse(scriptCardsStashedScripts[stashIndex].outputLines); }
if (scriptCardsStashedScripts[stashIndex].gmonlyLines) { gmonlyLines = JSON.parse(scriptCardsStashedScripts[stashIndex].gmonlyLines); }
if (scriptCardsStashedScripts[stashIndex].repeatingSectionIDs) { repeatingSectionIDs = JSON.parse(scriptCardsStashedScripts[stashIndex].repeatingSectionIDs); }
if (scriptCardsStashedScripts[stashIndex].repeatingSection) { repeatingSection = JSON.parse(scriptCardsStashedScripts[stashIndex].repeatingSection); }
if (scriptCardsStashedScripts[stashIndex].repeatingCharAttrs) { repeatingCharAttrs = JSON.parse(scriptCardsStashedScripts[stashIndex].repeatingCharAttrs); }
repeatingCharID = scriptCardsStashedScripts[stashIndex].repeatingCharID;
repeatingSectionName = scriptCardsStashedScripts[stashIndex].repeatingSectionName;
repeatingIndex = scriptCardsStashedScripts[stashIndex].repeatingIndex;
lineCounter = scriptCardsStashedScripts[stashIndex].programCounter;
if(cardParameters.sourcetoken) {
var charLookup = getObj("graphic", cardParameters.sourcetoken);
if (charLookup !== undefined && charLookup.get("represents") !== "") {
cardParameters.sourcecharacter = getObj("character", charLookup.get("represents"));
}
}
if(cardParameters.targettoken) {
var charLookup = getObj("graphic", cardParameters.targettoken);
if (charLookup !== undefined && charLookup.get("represents") !== "") {
cardParameters.targetcharacter = getObj("character", charLookup.get("represents"));
}
}
if (!isReentrant) {
for (var x=1; x<resumeArgs.length; x++) {
var thisInfo = resumeArgs[x].split(";");
stringVariables[thisInfo[0].trim()] = thisInfo[1].trim();
}
}
if (!isReentrant && scriptCardsStashedScripts[resumeArgs[0]]) { delete scriptCardsStashedScripts[resumeArgs[0]]; }
} else {
// Strip out all newlines in the input text
cardContent = msg.content.replace(/(\r\n|\n|\r)/gm, "");
cardContent = cardContent.replace(/(<br ?\/?>)*/g,"");
cardContent = cardContent.replace(/\}\}/g," }}");
cardContent = cardContent.trim();
if (cardContent.charAt(cardContent.length-1) !== "}") {
if (cardContent.charAt(cardContent.length-2) !== "}") {
cardContent += "}";
}
cardContent += "}";
}
var libraries = cardContent.match(/\+\+\+.+?\+\+\+/g);
if (libraries) {
cardContent = insertLibraryContent(cardContent, libraries[0].replace(/\+\+\+/g,""));
cardContent = cardContent.replace(/\+\+\+.+?\+\+\+/g,"")
}
// Split the card into an array of tag-based (--) lines
var cardLines = parseCardContent(cardContent);
}
// pre-parse line labels and store line numbers for branching
for (var x=0; x<cardLines.length; x++) {
var thisTag = getLineTag(cardLines[x],x,false)
var isRedef = false;
if (thisTag.charAt(0)==":") {
if (lineLabels[thisTag.substring(1)]) {
log(`ScriptCards Warning: redefined label ${thisTag.substring(1)}`);
isRedef = true;
}
if (labelChecking[thisTag.substring(1).toLowerCase()] && !isRedef) {
log(`ScriptCards Warning: Similar labels ${labelChecking[thisTag.substring(1).toLowerCase()]} and ${thisTag.substring(1)}`);
}
lineLabels[thisTag.substring(1)] = x;
labelChecking[thisTag.substring(1).toLowerCase()] = thisTag.substring(1);
}
}
if (isReentrant) {
outputLines = [];
gmonlyLines = [];
var entryLabel = resumeArgs[1].split(";")[0];
stringVariables["reentryval"] = resumeArgs[1].split(";")[1];
//log(`entryLabel: ${entryLabel}, reentryval: ${stringVariables["reentryval"]}`);
if (lineLabels[entryLabel]) {
lineCounter = lineLabels[entryLabel]
} else {
log(`ScriptCards Error: Label ${resumeArgs[1]} is not defined for reentrant script`)
};
}
// log(lineLabels);
// Process card lines starting with the first line (cardLines[0] will contain an empty string due to the split)
while (lineCounter < cardLines.length) {
var thisTag = getLineTag(cardLines[lineCounter],x,true);
thisTag = replaceCharacterAttributes(inlineReplaceRollVariables(substituteCallVars(thisTag,cardParameters)), cardParameters);
var thisContent = getLineContent(cardLines[lineCounter]);
if (thisTag.charAt(0) !== "+" && thisTag.charAt(0) !== "&" && thisTag.charAt(0) !== "*") {
thisContent = replaceCharacterAttributes(inlineReplaceRollVariables(substituteCallVars(thisContent,cardParameters)), cardParameters);
} else {
thisContent = replaceCharacterAttributes(substituteCallVars(thisContent,cardParameters), cardParameters);
}
if (cardParameters.debug == 1) {
log(`Line Counter: ${lineCounter}, Tag:${thisTag}, Content:${thisContent}`);
}
// Handle Stashing and asking for info
if (thisTag.charAt(0).toLowerCase() == "i") {
var myGuid = uuidv4();
var stashType = thisTag.substring(1);
var stashList = thisContent.split("||");
var buildLine = "";
var varList = "";
for (var x=0; x<stashList.length; x++) {
var theseParams = stashList[x].split(";");
if (theseParams[0].toLowerCase() == "t") {
if (buildLine !== "") { buildLine += "-|-"; varList += ";"; }
buildLine += theseParams[1] + ";&#64;{target|" + theseParams[2] + "|token_id}";
varList += theseParams[1];
}
if (theseParams[0].toLowerCase() == "q") {
if (buildLine !== "") { buildLine += "-|-"; varList += ";"; }
buildLine += theseParams[1] + ";?{" + theseParams[2] + "}";
varList += theseParams[1];
}
}
var flavorText = stashType.split(";")[0];
var buttonLabel = stashType.split(";")[1];
stashAScript(myGuid, cardLines, cardParameters, stringVariables, rollVariables, returnStack, parameterStack, lineCounter + 1, outputLines, varList, "X", arrayVariables, arrayIndexes, gmonlyLines);
lineCounter = cardLines.length + 100;
cardParameters.hidecard = "1";
sendChat(msg.who, `/w ${msg.who} ${flavorText}` + makeButton(buttonLabel, `!sc-resume ${myGuid}-|-${buildLine}`, cardParameters));
}
// Handle setting of card parameters (lines beginning with --#)
if (thisTag.charAt(0) === "#") {
var paramName = thisTag.substring(1).toLowerCase();
if (cardParameters[paramName] !== undefined) {
cardParameters[paramName] = thisContent;
if (cardParameters.debug == "1") { log(`Setting parameter ${paramName} to value ${thisContent}`)}
} else {
if (cardParameters.debug == "1") { log(`Unable to set parameter ${paramName} to value ${thisContent}`)}
}
switch (paramName) {
case "sourcetoken":
var charLookup = getObj("graphic", thisContent.trim());
if (charLookup !== undefined && charLookup.get("represents") !== "") {
cardParameters.sourcecharacter = getObj("character", charLookup.get("represents"));
}
break;
case "targettoken":
var charLookup = getObj("graphic", thisContent.trim());
if (charLookup !== undefined && charLookup.get("represents") !== "") {
cardParameters.targetcharacter = getObj("character", charLookup.get("represents"));
}
break;
case "activepage":
if (thisContent.trim().toLowerCase() === "playerpage") {
cardParameters.activepageobject = getObj("page", Campaign().get("playerpageid"));
} else {
var pageLookup = getObj("page", thisContent.trim());
if (pageLookup !== undefined) {
cardParameters.activepageobject = pageLookup;
}
}
}
}
// Handle setting string values
if (thisTag.charAt(0) === "&") {
var variableName = thisTag.substring(1);
if (thisContent.charAt(0) == "+") {
stringVariables[variableName] = (stringVariables[variableName] || "") + replaceRollVariables(thisContent.substring(1), cardParameters);
} else {
stringVariables[variableName] = replaceRollVariables(thisContent,cardParameters);
}
}
// Handle "Case" statements
if (thisTag.charAt(0).toLowerCase() === "c") {
var testvalue = thisTag.substring(1);
var cases = thisContent.split("|");
if (cases) {
for (var x=0; x<cases.length; x++) {
var testcase = cases[x].split(":")[0];
if (testvalue.toLowerCase() == testcase.toLowerCase()) {
var jumpDest = cases[x].split(":")[1];
var resultType = "goto";
var varName = undefined;
var varValue = undefined;
if (jumpDest) {
switch (jumpDest.charAt(0)) {
case ">" : resultType = "gosub"; break;
case "=" :
case "&" :
jumpDest.charAt(0) == "=" ? resultType = "rollset" : resultType = "stringset";
jumpDest = jumpDest.substring(1);
varName = jumpDest.split(";")[0];
varValue = jumpDest.split(";")[1];
break;
}
switch (resultType) {
case "goto":
if (lineLabels[jumpDest]) {
lineCounter = lineLabels[jumpDest] ;
} else {
log(`ScriptCards Error: Label ${jumpDest} is not defined on line ${lineCounter}`);
}
break;
case "gosub":
jumpDest = jumpDest.substring(1);
parameterStack.push(callParamList);
var paramList = CSVtoArray(jumpDest.trim());
callParamList = {};
var paramCount = 0;
if (paramList) {
paramList.forEach(function(item) {
callParamList[paramCount] = item.toString().trim();
paramCount++;
});
}
returnStack.push(lineCounter);
jumpDest = jumpDest.split(";")[0];
if (lineLabels[jumpDest]) {
lineCounter = lineLabels[jumpDest] ;
} else {
log(`ScriptCards Error: Label ${jumpDest} is not defined on line ${lineCounter}`);
}
break;
case "rollset":
rollVariables[varName] = parseDiceRoll(replaceStringVariables(varValue, cardParameters), cardParameters);
break;
case "stringset":
if (varName && varValue) {
if (resultType == "stringset" && varValue.charAt(0) == "+") {
varValue = (stringVariables[varName] || "") + varValue.substring(1);
}
stringVariables[varName] = replaceRollVariables(varValue,cardParameters);
} else {
log(`ScriptCards Error: Variable name or value not specified in conditional on line ${lineCounter}`);
}
break;
}
x = cases.length + 1;
}
}
}
}
}
// Handle setting RollVariables to function call results
if (thisTag.charAt(0) === "~") {
var variableName = thisTag.substring(1);
var params = thisContent.split(";");
switch (params[0].toLowerCase()) {
case "turnorder":
var turnorder;
if (params.length == 2) {
if (params[1].toLowerCase() == "clear") {
Campaign().set("turnorder", "");
}
}
if (params.length == 3) {
if (params[1].toLowerCase() == "removetoken") {
if (Campaign().get("turnorder") == "") {
turnorder = [];
} else {
turnorder = JSON.parse(Campaign().get("turnorder"));
}
for (var x=turnorder.length-1; x>=0; x--) {
if (turnorder[x].id == params[2]) {
turnorder.splice(x,1);
}
}
}
Campaign().set("turnorder", JSON.stringify(turnorder));
}
if (params.length == 4) {
if (params[1].toLowerCase() == "addtoken") {
if (Campaign().get("turnorder") == "") {
turnorder = [];
} else {
turnorder = JSON.parse(Campaign().get("turnorder"));
}
turnorder.push({
id: params[2],
pr: params[3],
custom: "",
});
Campaign().set("turnorder", JSON.stringify(turnorder));
}
if (params[1].toLowerCase() == "replacetoken") {
if (Campaign().get("turnorder") == "") {
turnorder = [];
} else {
turnorder = JSON.parse(Campaign().get("turnorder"));
}
var wasfound = false;
for (var x=turnorder.length-1; x>=0; x--) {
if (turnorder[x].id.trim() == params[2].trim()) {
turnorder[x].pr = params[3];
wasfound=true;
}
}
if (!wasfound) {
turnorder.push({
id: params[2],
pr: params[3],
custom: "",
});
}
Campaign().set("turnorder", JSON.stringify(turnorder));
}
if (params[1].toLowerCase() == "addcustom") {
if (Campaign().get("turnorder") == "") {
turnorder = [];
} else {
turnorder = JSON.parse(Campaign().get("turnorder"));
}
turnorder.push({
id: "-1",
pr: params[3],
custom: params[2],
});
Campaign().set("turnorder", JSON.stringify(turnorder));
}
}
break;
// Chebyshev Unit distance between two tokens (params[1] and params[2]) (4E/5E)
case "chebyshevdistance":
case "distance":
var result = 0;
if (params.length >= 3) {
var token1 = getObj("graphic", params[1]);
var token2 = getObj("graphic", params[2]);
if (token1 && token2) {
// Calculate the Chebyshev Distance between the grid points
var x1 = token1.get("left") / 70;
var x2 = token2.get("left") / 70;
var y1 = token1.get("top") / 70;
var y2 = token2.get("top") / 70;
result = Math.floor(Math.max(Math.abs(x1 - x2), Math.abs(y1-y2)));
}
}
rollVariables[variableName] = parseDiceRoll(result.toString(), cardParameters);
break;
case "euclideandistance":
var result = 0;
if (params.length >= 3) {
var token1 = getObj("graphic", params[1]);
var token2 = getObj("graphic", params[2]);
if (token1 && token2) {
// Calculate the euclidean unit distance between two tokens (params[1] and params[2])
var x1 = token1.get("left") / 70;
var x2 = token2.get("left") / 70;
var y1 = token1.get("top") / 70;
var y2 = token2.get("top") / 70;
result = Math.floor(Math.sqrt(Math.pow((x1-x2),2)+Math.pow((y1-y2),2)));
}
}
rollVariables[variableName] = parseDiceRoll(result.toString(), cardParameters);
break;
case "manhattandistance":
case "taxicabdistance":
var result = 0;
if (params.length >= 3) {
var token1 = getObj("graphic", params[1]);
var token2 = getObj("graphic", params[2]);
if (token1 && token2) {
// Calculate the manhattan unit distance between two tokens (params[1] and params[2])
var x1 = token1.get("left") / 70;
var x2 = token2.get("left") / 70;
var y1 = token1.get("top") / 70;
var y2 = token2.get("top") / 70;
result = Math.abs(x2-x1) + Math.abs(y2-y1);
}
}
rollVariables[variableName] = parseDiceRoll(result.toString(), cardParameters);
break;
case "getselected":
if (msg.selected) {
for (var x=0; x < msg.selected.length; x++) {
var obj = getObj(msg.selected[x]._type, msg.selected[x]._id);
stringVariables[variableName + (x+1).toString()] = obj.get("id");
}
stringVariables[variableName + "Count"] = msg.selected.length.toString();
rollVariables[variableName + "Count"] = parseDiceRoll(msg.selected.length.toString(), cardParameters);
} else {
stringVariables[variableName + "Count"] = "0";
rollVariables[variableName + "Count"] = parseDiceRoll("0", cardParameters);
}
break;
case "stateitem":
if (params.length == 3) {
switch (params[1].toLowerCase()) {
case "write":
if (params[2].toLowerCase() == "rollvariable") {
if (rollVariables[variableName]) {
state[APINAME].storedRollVariable = Object.assign(rollVariables[variableName]);
}
}
if (params[2].toLowerCase() == "stringvariable") {
if (stringVariables[variableName]) {
state[APINAME].storedStringVariable = Object.assign(stringVariables[variableName]);
}
}
if (params[2].toLowerCase() == "array") {
if (arrayVariables[variableName]) {
state[APINAME].storedArrayVariable = Object.assign(arrayVariables[variableName]);
state[APINAME].storedArrayIndex = Object.assign(arrayIndexes[variableName]);
}
}
break;
case "read":
if (params[2].toLowerCase() == "rollvariable") {
if (state[APINAME].storedRollVariable) { rollVariables[variableName] = Object.assign(state[APINAME].storedRollVariable); }
}
if (params[2].toLowerCase() == "stringvariable") {
if (state[APINAME].storedStringVariable) { stringVariables[variableName] = Object.assign(state[APINAME].storedStringVariable); }
}
if (params[2].toLowerCase() == "array") {
if (state[APINAME].storedArrayVariable) {
arrayVariables[variableName] = Object.assign(state[APINAME].storedArrayVariable);
arrayIndexes[variableName] = Object.assign(state[APINAME].storedArrayIndex);
}
}
break;
}
}
break;
case "math": //min,max,clamp,round,floor,ceil
case "round":
case "range":
// call is --var|range;min;val1;val2
if (params[1].toLowerCase() == "min" && params.length == 4) {
var val1 = parseDiceRoll(params[2], cardParameters);
var val2 = parseDiceRoll(params[3], cardParameters);
if (val1.Total <= val2.Total) {
rollVariables[variableName] = val1;
} else {
rollVariables[variableName] = val2;
}
}
// call is --var|range;max;val1;val2
if (params[1].toLowerCase() == "max" && params.length == 4) {
var val1 = parseDiceRoll(params[2], cardParameters);
var val2 = parseDiceRoll(params[3], cardParameters);
if (val1.Total >= val2.Total) {
rollVariables[variableName] = val1;
} else {
rollVariables[variableName] = val2;
}
}
// call is --var|range;clamp;val;lowerbound;upperbound
if (params[1].toLowerCase() == "clamp" && params.length == 5) {
var val = parseDiceRoll(params[2], cardParameters);
var lower = parseDiceRoll(params[3], cardParameters);
var upper = parseDiceRoll(params[4], cardParameters);
if (val.Total >= lower.Total && val.Total <= upper.Total) { rollVariables[variableName] = val; }
if (val.Total < lower.Total) { rollVariables[variableName] = lower; }
if (val.Total > upper.Total) { rollVariables[variableName] = upper; }
}
if (params.length == 3) {
if (params[1].toLowerCase() == "down" || params[1].toLowerCase() == "floor") {
rollVariables[variableName] = parseDiceRoll(Math.floor(Number(params[2])).toString(), cardParameters);
}
if (params[1].toLowerCase() == "up" || params[1].toLowerCase() == "ceil") {
rollVariables[variableName] = parseDiceRoll(Math.ceil(Number(params[2])).toString(), cardParameters);
}
if (params[1].toLowerCase() == "closest" || params[1].toLowerCase() == "round") {
rollVariables[variableName] = parseDiceRoll(Math.round(Number(params[2])).toString(), cardParameters);
}
}
break;
case "attribute":
if (params.length > 4) {
if (params[1].toLowerCase() == "set") {
var theCharacter = getObj("character", params[2]);
if (theCharacter) {
var oldAttrs = findObjs({ _type:"attribute", _characterid: params[2], name: params[3].trim()});
if (oldAttrs.length > 0) {
oldAttrs.forEach(function(element) { element.remove(); });
}
if (params[4] !== "") {
createObj("attribute", { _characterid: params[2], name: params[3].trim(), current: params[4].trim() });
}
}
}
}
case "stringfuncs": // strlength, substring, replace, split, before, after
case "strings":
case "string":
if (params.length == 3) {
switch (params[1].toLowerCase()) {
//stringfuncs;strlength;string
case "strlength":
case "length":
rollVariables[variableName] = parseDiceRoll((params[2].length.toString()), cardParameters);
break;
}
}
if (params.length == 4) {
switch (params[1].toLowerCase()) {
//stringfuncs;split;delimeter;string
case "split":
var splits = params[3].split(params[2]);
rollVariables[variableName+"Count"] = parseDiceRoll(splits.length.toString(), cardParameters);
for (var x=0; x<splits.length; x++) {
stringVariables[variableName+(x+1).toString()] = splits[x];
}
break;
//stringfuncs;before;delimiter;string
case "before":
if (params[3].indexOf(params[2]) < 0) {
stringVariables[variableName] = params[3];
} else {
stringVariables[variableName] = params[3].substring(0,params[3].indexOf(params[2]));
}
break;
//stringfuncs;after;delimeter;string
case "after":
if (params[3].indexOf(params[2]) < 0) {
stringVariables[variableName] = params[3];
} else {
stringVariables[variableName] = params[3].substring(params[3].indexOf(params[2]));
}
break;
//stringfuncs;left;count;string
case "left":
if (params[3].length < Number(params[2])) {
stringVariables[variableName] = params[3];
} else {
stringVariables[variableName] = params[3].substring(0,Number(params[2]));
}
break;
//stringfuncs;right;count;string
case "right":
if (params[3].length < Number(params[2])) {
stringVariables[variableName] = params[3];
} else {
stringVariables[variableName] = params[3].substring(params[3].length-Number(params[2]));
}
break;
}
}
if (params.length == 5) {
switch (params[1].toLowerCase()) {
//stringfuncs0;substring1;start2;length3;string4
case "substring":
stringVariables[variableName] = params[4].substring(Number(params[2]) - 1, Number(params[3]) + Number(params[2])-1);
break;
case "replace":
stringVariables[variableName] = params[4].replace(params[2], params[3]);
break;
}
}
break;
case "array":
if (params.length > 2) {
if (params[1].toLowerCase() == "define") {
arrayVariables[params[2]] = [];
for (var x=3; x<params.length; x++) {
arrayVariables[params[2]].push(params[x]);
}
arrayIndexes[params[2]] = 0;
}
if (params[1].toLowerCase() == "stringify") {
if (arrayVariables[params[2]]) {
stringVariables[variableName] = arrayVariables[params[2]].join(";");
} else {
stringVariables[variableName] = "";
}
}
if (params[1].toLowerCase() == "pagetokens") {
arrayVariables[params[2]] = [];
var templateToken = getObj("graphic", params[3]);
if (templateToken) {
var t = findObjs({_type:"graphic", _pageid: templateToken.get("_pageid") });
if (t) {
for (var x=0; x<t.length; x++) {
arrayVariables[params[2]].push(t[x].id);
}
}
arrayIndexes[params[2]] = 0;
if (variableName) { stringVariables[variableName] = arrayVariables[params[2]].length; }
} else {
arrayVariables[params[2]] = [];
if (variableName) { stringVariables[variableName] = "0"; }
}
}
if (params[1].toLowerCase() == "selectedtokens") {
if (msg.selected) {
arrayVariables[params[2]] = [];
for (var x=0; x < msg.selected.length; x++) {
var obj = getObj(msg.selected[x]._type, msg.selected[x]._id);
arrayVariables[params[2]].push(obj.get("id"));
}
arrayIndexes[params[2]] = 0;
if (variableName) { stringVariables[variableName] = arrayVariables[params[2]].length; }
} else {
arrayVariables[params[2]] = [];
if (variableName) { stringVariables[variableName] = "0"; }
}
}
if (params[1].toLowerCase() == "statusmarkers") {
arrayVariables[params[2]] = [];
var theToken = getObj("graphic", params[3]);
if (theToken) {
var markers = theToken.get("statusmarkers").split(",");
for (var x=0; x<markers.length; x++) {
arrayVariables[params[2]].push(markers[x]);
}
arrayIndexes[params[2]] = 0;
}
}
if (params[1].toLowerCase() == "add") {
if (!arrayVariables[params[2]]) { arrayVariables[params[2]] = []; arrayIndexes[params[2]] = 0; }
for (var x=3; x<params.length; x++) {
arrayVariables[params[2]].push(params[x]);
}
}
if (params[1].toLowerCase() == "remove") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
for (var x=3; x<params.length; x++) {
for (var i=arrayVariables[params[2]].length-1; i>=0; i--) {
if (arrayVariables[params[2]][i] == params[x]) {
arrayVariables[params[2]].splice(i,1);
}
}
}
}
if (arrayVariables[params[2]] && arrayVariables[params[2]].length == 0) {
delete arrayVariables[params[2]];
delete arrayIndexes[params[2]];
} else {
arrayIndexes[params[2]] = 0;
}
}
if (params[1].toLowerCase() == "removeat") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
if (Number(params[3] < arrayVariables[params[2]].length))
{
arrayVariables[params[2]].splice(Number(params[3]),1);
}
}
if (arrayVariables[params[2]].length == 0) {
delete arrayVariables[params[2]];
delete arrayIndexes[params[2]];
} else {
arrayIndexes[params[2]] = 0;
}
}
if (params[1].toLowerCase() == "setindex") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
if (arrayVariables[params[2]].length > Number(params[3])) {
arrayIndexes[params[2]] = Number(params[3]);
}
}
}
if (params[1].toLowerCase() == "getindex") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
stringVariables[variableName] = arrayIndexes[params[2]];
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "indexof") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
var wasFound = arrayVariables[params[2]].indexOf(params[3]);
if (wasFound >= 0) {
stringVariables[variableName] = wasFound.toString();
} else {
stringVariables[variableName] = "ArrayError";
}
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getlength" || params[1].toLowerCase() == "getcount") {
if (arrayVariables[params[2]]) {
stringVariables[variableName] = arrayVariables[params[2]].length;
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getcurrent") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
stringVariables[variableName] = arrayVariables[params[2]][arrayIndexes[params[2]]];
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getfirst") {
if (arrayVariables[params[2]] && arrayVariables[params[2]].length > 0) {
arrayIndexes[params[2]] = 0;
stringVariables[variableName] = arrayVariables[params[2]][arrayIndexes[params[2]]];
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getlast") {
if (arrayVariables[params[2]]) {
arrayIndexes[params[2]] = arrayVariables[params[2]].length-1;
stringVariables[variableName] = arrayVariables[params[2]][arrayIndexes[params[2]]];
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getnext") {
if (arrayVariables[params[2]]) {
if (arrayIndexes[params[2]] < arrayVariables[params[2]].length-1) {
arrayIndexes[params[2]]++;
stringVariables[variableName] = arrayVariables[params[2]][arrayIndexes[params[2]]];
} else {
stringVariables[variableName] = "ArrayError";
}
} else {
stringVariables[variableName] = "ArrayError";
}
}
if (params[1].toLowerCase() == "getprevious") {
if (arrayVariables[params[2]]) {
if (arrayIndexes[params[2]] > 0) {
arrayIndexes[params[2]]--;
stringVariables[variableName] = arrayVariables[params[2]][arrayIndexes[params[2]]];
} else {
stringVariables[variableName] = "ArrayError";
}
} else {
stringVariables[variableName] = "ArrayError";
}
}
}
if (params.length == 5) {
if (params[1].toLowerCase() == "replace") {
if (arrayVariables[params[2]]) {
for (var i=0; i < arrayVariables[params[2]].length; i++) {
if (arrayVariables[params[2]][i] == params[3]) {
arrayVariables[params[2]][i] = params[4];
}
}
}
arrayIndexes[params[2]] = 0;
}
if (params[1].toLowerCase() == "fromstring") {
arrayVariables[params[2]] = [];
var splitString = params[4].split(params[3]);
for (var x=0; x<splitString.length; x++) {
arrayVariables[params[2]].push(splitString[x]);
}
arrayIndexes[params[2]] = 0;
}
}
break;
}
}
// Handle API Call Lines
if (thisTag.charAt(0) === "@") {
var apicmd = thisTag.substring(1);
var spacer = " ";
//var params = thisContent.replace(/(?:\s+|\b)_/g, " --");
var params = thisContent.replace(/(^|\ +)_/g, " --");
var apiMessage = `!${apicmd}${spacer}${params}`.trim();
sendChat(msg.who, apiMessage);
}
// Handle repeating attribute statements
if (thisTag.charAt(0).toLowerCase() === "r") {
var command = thisTag.substring(1).toLowerCase();
var param = thisContent.split(";");
switch (command.toLowerCase()) {
// Find parameters are character id, value name (ie, Greatsword), section name (attack), and field to search (atkname)
case "find":
repeatingSection = getSectionAttrs(param[0], param[1], param[2], param[3]);
fillCharAttrs(findObjs({_type: 'attribute', _characterid: param[0]}));
repeatingCharID = param[0];
repeatingSectionName = param[2];
if (repeatingSection && repeatingSection[0]) {
repeatingSectionIDs = [];
repeatingSectionIDs.push(repeatingSection[0].split("|")[1]);
repeatingIndex = 0;
} else {
repeatingSectionIDs = [];
repeatingSectionIDs[0] = "NoRepeatingAttributeLoaded";
repeatingIndex = 0;
}
if (repeatingSection) { parseRepeatingSection() };
break;
case "first":
repeatingSectionIDs = getRepeatingSectionIDs(param[0], param[1]);
if (repeatingSectionIDs) {
repeatingIndex = 0;
repeatingCharID = param[0];
repeatingSectionName = param[1];
fillCharAttrs(findObjs({_type: 'attribute', _characterid: repeatingCharID}));
repeatingSection = getSectionAttrsByID(repeatingCharID, repeatingSectionName, repeatingSectionIDs[repeatingIndex]);
parseRepeatingSection();
repeatingIndex=0;
} else {
repeatingSection = undefined;
}
break;
case "next":
if (repeatingSectionIDs) {
if (repeatingSectionIDs[repeatingIndex + 1]) {
repeatingIndex++;
repeatingSection = getSectionAttrsByID(repeatingCharID, repeatingSectionName, repeatingSectionIDs[repeatingIndex]);
parseRepeatingSection();
} else {
repeatingSection = undefined;
repeatingSectionIDs = undefined;
}
} else {
repeatingSection = undefined;
repeatingSectionIDs = undefined;
}
break;
case "dump":
if (repeatingSection) {
for(var x=0; x<repeatingSection.length; x++) {
log(repeatingSection[x]);
}
}
}
}
// Handle setting roll ID variables
if (thisTag.charAt(0) === "=") {
var rollIDName = thisTag.substring(1);
rollVariables[rollIDName] = parseDiceRoll(replaceStringVariables(thisContent, cardParameters), cardParameters);
}
// Handle direct output lines
if (thisTag.charAt(0) === "+") {
var rowData = buildRowOutput(thisTag.substring(1), replaceRollVariables(thisContent,cardParameters));
tableLineCounter += 1;
if (tableLineCounter % 2 == 0) {
while(rowData.indexOf("=X=FONTCOLOR=X=") > 0) { rowData = rowData.replace("=X=FONTCOLOR=X=", cardParameters.evenrowfontcolor); }
while(rowData.indexOf("=X=ROWBG=X=") > 0) { rowData = rowData.replace("=X=ROWBG=X=", ` background: ${cardParameters.evenrowbackground}; `); }
} else {
while(rowData.indexOf("=X=FONTCOLOR=X=") > 0) { rowData = rowData.replace("=X=FONTCOLOR=X=", cardParameters.oddrowfontcolor); }
while(rowData.indexOf("=X=ROWBG=X=") > 0) { rowData = rowData.replace("=X=ROWBG=X=", ` background: ${cardParameters.oddrowbackground}; `); }
}
rowData = processInlineFormatting(rowData, cardParameters);
outputLines.push(rowData);
}
if (thisTag.charAt(0) === "*") {
var rowData = buildRowOutput(thisTag.substring(1), replaceRollVariables(thisContent,cardParameters));
tableLineCounter += 1;
if (tableLineCounter % 2 == 0) {
while(rowData.indexOf("=X=FONTCOLOR=X=") > 0) { rowData = rowData.replace("=X=FONTCOLOR=X=", cardParameters.evenrowfontcolor); }
while(rowData.indexOf("=X=ROWBG=X=") > 0) { rowData = rowData.replace("=X=ROWBG=X=", ` background: ${cardParameters.evenrowbackground}; `); }
} else {
while(rowData.indexOf("=X=FONTCOLOR=X=") > 0) { rowData = rowData.replace("=X=FONTCOLOR=X=", cardParameters.oddrowfontcolor); }
while(rowData.indexOf("=X=ROWBG=X=") > 0) { rowData = rowData.replace("=X=ROWBG=X=", ` background: ${cardParameters.oddrowbackground}; `); }
}
rowData = processInlineFormatting(rowData, cardParameters);
gmonlyLines.push(rowData);
}
// Handle Conditional Lines
if (thisTag.charAt(0) === "?") {
var isTrue = processFullConditional(thisTag.substring(1));
var trueDest = thisContent.trim();
var falseDest = undefined;
var varName = undefined;
var varValue = undefined;
var resultType = "goto";
if (trueDest.indexOf("|") >= 0) {
falseDest = trueDest.split("|")[1].trim();
trueDest = trueDest.split("|")[0].trim();
}
if (cardParameters.debug == 1) { log(`Condition ${thisTag.substring(1)} evaluation result: ${isTrue}`); }
var jumpDest = isTrue ? trueDest : falseDest;
if (jumpDest) {
switch (jumpDest.charAt(0)) {
case ">" : resultType = "gosub"; break;
case "=" :
case "&" :
jumpDest.charAt(0) == "=" ? resultType = "rollset" : resultType = "stringset";
jumpDest = jumpDest.substring(1);
varName = jumpDest.split(";")[0];
varValue = jumpDest.split(";")[1];
break;
}
switch (resultType) {
case "goto":
if (lineLabels[jumpDest]) {
lineCounter = lineLabels[jumpDest] ;
} else {
log(`ScriptCards Error: Label ${jumpDest} is not defined on line ${lineCounter}`);
}
break;
case "gosub":
jumpDest = jumpDest.substring(1);
parameterStack.push(callParamList);
var paramList = CSVtoArray(jumpDest.trim());
callParamList = {};
var paramCount = 0;
if (paramList) {
paramList.forEach(function(item) {
callParamList[paramCount] = item.toString().trim();
paramCount++;
});
}
returnStack.push(lineCounter);
jumpDest = jumpDest.split(";")[0];
if (lineLabels[jumpDest]) {
lineCounter = lineLabels[jumpDest] ;
} else {
log(`ScriptCards Error: Label ${jumpDest} is not defined on line ${lineCounter}`);
}
break;
case "rollset":
rollVariables[varName] = parseDiceRoll(replaceStringVariables(varValue, cardParameters), cardParameters);
break;
case "stringset":
if (varName && varValue) {
if (resultType == "stringset" && varValue.charAt(0) == "+") {
varValue = (stringVariables[varName] || "") + varValue.substring(1);
}
stringVariables[varName] = replaceRollVariables(varValue,cardParameters);
} else {
log(`ScriptCards Error: Variable name or value not specified in conditional on line ${lineCounter}`);
}
break;
}
}
}
// Handle X lines (exit)
if (thisTag.charAt(0).toLowerCase() == "x") {
if (cardParameters["reentrant"] !== 0) {
stashAScript(cardParameters["reentrant"], cardLines, cardParameters, stringVariables, rollVariables, returnStack, parameterStack, lineCounter + 1, outputLines, varList, "X", arrayVariables, arrayIndexes, gmonlyLines);
}
lineCounter = cardLines.length+1;
}
// Handle E lines (echo)
if (thisTag.charAt(0).toLowerCase() == "e") {
var sendAs = thisTag.substring(1);
sendChat(sendAs, thisContent);
}
// Handle [ lines (Arrays)
if (thisTag.charAt(0) == "[") {
var arrayName = thisTag.substring(1);
var addItems = false;
if (thisContent.charAt(0) == "+") { addItems = true; thisContent = thisContent.substring(1); }
if (!addItems || !arrays[arrayName]) { arrays[arrayName] = []; }
var items = thisContent.split("|");
for (var x=0; x<items.length; x++) { arrays[arrayName].push(items[x]); }
}
// Handle V lines (visual effects)
if (thisTag.charAt(0).toLowerCase() == "v") {
if (thisTag.length > 1) {
var effectType = thisTag.substring(1).toLowerCase();
switch (effectType) {
case "token":
var params = thisContent.split(" ");
if (params.length >= 2) {
var s = getObj("graphic", params[0]);
if (s) {
var x = s.get("left");
var y = s.get("top");
var pid = s.get("_pageid");
var effectInfo = findObjs({
_type : "custfx",
name : params[1].trim()
});
if (!_.isEmpty(effectInfo)) {
spawnFxWithDefinition(x, y, effectInfo[0].get('definition'), pid);
} else {
var t = params[1].trim();
if (t !== "" && t !== "none") {
spawnFx(x,y,t,pid);
}
}
}
}
break;
case "betweentokens":
var params = thisContent.split(" ");
if (params.length >= 3) {
var s = getObj("graphic", params[0]);
var p = getObj("graphic", params[1]);
if (s && p) {
var x1 = s.get("left");
var y1 = s.get("top");
var x2 = p.get("left");
var y2 = p.get("top");
var pid = s.get("_pageid");
var effectInfo = findObjs({
_type : "custfx",
name : params[2].trim()
});
if (!_.isEmpty(effectInfo)) {
var angleDeg = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
if (angleDeg < 0) {
angleDeg += 360;
}
var definition = effectInfo[0].get('definition');
definition.angle = angleDeg;
spawnFxWithDefinition(x1, y1, definition, pid);
} else {
var t = params[2].trim();
if (t !== "" && t !== "none" ) {
spawnFxBetweenPoints({x:x1, y:y1}, {x:x2, y:y2}, t, pid);
}
}
}
}
break;
}
}
}
// Handle S (Stash) statements
if (thisTag.charAt(0).toLowerCase() == "s") {
switch (thisTag.substring(1).toLowerCase()) {
case "rollvariables":
if (thisContent.trim().length > 0) {
state[APINAME].storedVariables[thisContent.trim()] = JSON.parse(JSON.stringify(rollVariables));
}
break;
case "stringvariables":
if (thisContent.trim().length > 0) {
state[APINAME].storedStrings[thisContent.trim()] = JSON.parse(JSON.stringify(stringVariables));
}
break;
case "settings":
if (thisContent.trim().length > 0) {
state[APINAME].storedSettings[thisContent.trim()] = {};
for (var key in cardParameters) {
if (cardParameters[key] !== defaultParameters[key]) {
state[APINAME].storedSettings[thisContent.trim()][key] = cardParameters[key];
}
}
}
break;
}
}
// Handle L (Load) statements
if (thisTag.charAt(0).toLowerCase() == "l") {
switch (thisTag.substring(1).toLowerCase()) {
case "rollvariables":
if (thisContent.trim().length > 0 && state[APINAME].storedVariables[thisContent.trim()] !== undefined) {
newVariables = state[APINAME].storedVariables[thisContent.trim()];
for (var key in newVariables) {
rollVariables[key] = JSON.parse(JSON.stringify(newVariables[key]));
}
}
break;
case "stringvariables":
if (thisContent.trim().length > 0 && state[APINAME].storedStrings[thisContent.trim()] !== undefined) {
newVariables = state[APINAME].storedStrings[thisContent.trim()];
for (var key in newVariables) {
stringVariables[key] = JSON.parse(JSON.stringify(newVariables[key]));
}
}
break;
case "settings":
if (thisContent.trim().length > 0) {
if (thisContent.trim().length > 0 && state[APINAME].storedSettings[thisContent.trim()] !== undefined) {
newSettings = state[APINAME].storedSettings[thisContent.trim()];
for (var key in newSettings) {
cardParameters[key] = newSettings[key];
}
}
}
break;
}
}
// Handle branch lines
if (thisTag.charAt(0) === "^") {
var jumpTo = thisTag.substring(1);
if (lineLabels[jumpTo]) { lineCounter = lineLabels[jumpTo] } else { log(`ScriptCards Error: Label ${jumpTo} is not defined on line ${lineCounter}`)};
}
// Handle gosub lines
if (thisTag.charAt(0) === ">") {
parameterStack.push(callParamList);
var paramList = CSVtoArray(thisContent.trim());
callParamList = {};
var paramCount = 1;
if (paramList) {
paramList.forEach(function(item) {
callParamList[paramCount] = item.toString().trim();
paramCount++;
});
}
var jumpTo = thisTag.substring(1);
if (lineLabels[jumpTo]) {
returnStack.push(lineCounter);
lineCounter = lineLabels[jumpTo];
} else { log(`ScriptCards Error: Label ${jumpTo} is not defined on line ${lineCounter}`)};
}
// Handle return from gosub
if (thisTag.charAt(0) === "<") {
if (returnStack.length > 0) {
callParamList = parameterStack.pop();
lineCounter = returnStack.pop();
}
}
lineCounter++;
}
var subtitle = "";
if ((cardParameters.leftsub !== "") && (cardParameters.rightsub !== "")) {
subtitle = cardParameters.leftsub + cardParameters.subtitleseperator + cardParameters.rightsub;
}
if ((cardParameters.leftsub !== "") && (cardParameters.rightsub == "")) {
subtitle = cardParameters.leftsub;
}
if ((cardParameters.leftsub == "") && (cardParameters.rightsub !== "")) {
subtitle = cardParameters.rightsub;
}
var cardOutput;
var gmoutput;
if (gmonlyLines.length > 0) {
gmoutput = htmlTemplateHiddenTitle.replace("=X=TITLE=X=", cardParameters.title).replace("=X=SUBTITLE=X=", subtitle);
}
if (cardParameters.hidetitlecard == "0") {
cardOutput = htmlTemplate.replace("=X=TITLE=X=", cardParameters.title).replace("=X=SUBTITLE=X=", subtitle);
} else {
cardOutput = htmlTemplateHiddenTitle.replace("=X=TITLE=X=", cardParameters.title).replace("=X=SUBTITLE=X=", subtitle);
}
for (var x=0; x<outputLines.length; x++) {
//cardOutput += processInlineFormatting(outputLines[x], cardParameters);
cardOutput += outputLines[x];
}
for (var x=0; x<gmonlyLines.length; x++) {
//gmoutput += processInlineFormatting(gmonlyLines[x], cardParameters);
gmoutput += gmonlyLines[x];
}
cardOutput += htmlTemplateEnd;
cardOutput = replaceStyleInformation(cardOutput, cardParameters);
if (gmonlyLines.length > 0) {
gmoutput += htmlTemplateEnd;
gmoutput = replaceStyleInformation(gmoutput, cardParameters);
}
var emote = "";
var emoteLeft = "";
var emoteRight = "";
if (cardParameters.emotestate == "visible") {
if (cardParameters.sourcetoken !== "") {
var thisToken = getObj("graphic", cardParameters.sourcetoken.trim());
if (thisToken !== undefined && thisToken.get("imgsrc") !== "") {
emoteLeft = `<img src=${thisToken.get("imgsrc")} style='height: 50px; min-width: 50px; float: left;'></img>`;
}
}
if (cardParameters.targettoken !== "") {
var thisToken = getObj("graphic", cardParameters.targettoken.trim());
if (thisToken !== undefined && thisToken.get("imgsrc") !== "") {
emoteRight = `<img src=${thisToken.get("imgsrc")} style='height: 50px; min-width: 50px; float: left;'></img>`;
}
}
if (cardParameters.emotetext !== "" || emoteLeft !== "" || emoteRight !== "") {
if (emoteLeft == "") { emoteLeft = "&nbsp;"}
if (emoteRight == "") { emoteRight = "&nbsp;"}
emote = "<div style='display: table; margin: -5px 0px 3px -7px; font-weight: normal; font-style: normal; background: " + cardParameters.emotebackground + "'>" + emoteLeft + "<div style='display: table-cell; width: 100%; " + cardParameters.emotefont + " vertical-align: middle; text-align: center; padding: 0px 2px;'>" + cardParameters.emotetext + "</div><div style='display: table-cell; margin: -5px 0px 3px -7px; font-weight: normal; font-style: normal;'>" + emoteRight + "</div></div>"
emote = inlineReplaceRollVariables(emote, cardParameters);
}
}
var from = cardParameters.showfromfornonwhispers !== "0" ? msg.who : "";
cardOutput = removeInlineRolls(cardOutput, cardParameters);
emote = removeInlineRolls(emote, cardParameters);
if (cardParameters.hidecard == "0") {
if (emote !== "") {
if (cardParameters.whisper == "" || cardParameters.whisper == "0") {
sendChat(from, "/desc " + emote + " " + cardOutput );
} else {
var whispers = cardParameters.whisper.split(",");
for (var w in whispers) {
//var WhisperTarget = (whispers[w].trim() == 'self' ? msg.playerid : whispers[w].trim());
var WhisperTarget = whispers[w].trim();
if (WhisperTarget == "self") {
WhisperTarget = getObj("player", msg.playerid).get("displayname");
}
sendChat(msg.who, `/w "${WhisperTarget}" ${cardOutput}`);
//sendChat(msg.who, "/w \"" + WhisperTarget + "\" " + cardOutput );
}
}
} else {
if (cardParameters.whisper == "" || cardParameters.whisper == "0") {
sendChat(from, "/desc " + cardOutput );
} else {
var whispers = cardParameters.whisper.split(",");
for (var w in whispers) {
//var WhisperTarget = (whispers[w].trim() == 'self' ? msg.playerid : whispers[w].trim());
var WhisperTarget = whispers[w].trim();
if (WhisperTarget == "self") {
WhisperTarget = getObj("player", msg.playerid).get("displayname");
}
sendChat(msg.who, `/w "${WhisperTarget}" ${cardOutput}`);
//sendChat(msg.who, "/w " + WhisperTarget + " " + cardOutput );
}
}
}
}
if (gmonlyLines.length > 0) {
sendChat("API", "/w gm " + gmoutput);
}
}
}
});
});
// Breaks the passed text into a series of lines and returns them as an object
function parseCardContent(content) {
// Strip off the !pc and the opening {{ and closing }}
var work = content.substr(content.indexOf("{{") + 2) ;
work = work.substr(0,work.lastIndexOf("}}") -1);
work=work.trim();
work = " " + work;
// Split into an array on the -- divider
if (work !== undefined) {
return work.split("--");
//return work.split(/\s+--/);
} else {
return [];
}
}
// Given "content", replace occurances of roll variable references with their values. Used in --+ lines
function replaceRollVariables(content,cardParameters) {
if (cardParameters.disablerollvariableexpansion !== "0") { return content; }
for (var key in rollVariables) {
var baseString = `[$${key}`;
var suffix = `]`;
for (var i=0; i<rollComponents.length; i++)
{
while (content.indexOf(baseString+"."+rollComponents[i]+suffix) >= 0) { content = content.replace(baseString+"."+rollComponents[i]+suffix, rollVariables[key][rollComponents[i]]); }
}
if (cardParameters.nominmaxhighlight == "0") {
while (content.indexOf(baseString+suffix) >= 0) { content = content.replace(baseString+suffix, buildTooltip(rollVariables[key].Total, "Roll: " + rollVariables[key].RollText + "<br /><br />Result: " + rollVariables[key].Text, rollVariables[key].Style)); }
} else {
while (content.indexOf(baseString+suffix) >= 0) { content = content.replace(baseString+suffix, buildTooltip(rollVariables[key].Total, "Roll: " + rollVariables[key].RollText + "<br /><br />Result: " + rollVariables[key].Text, defaultParameters.styleNormal)); }
}
}
content = replaceStringVariables(content, cardParameters);
return content;
}
function replaceStringVariables(content, cardParameters)
{
if (cardParameters && cardParameters.disablestringexpansion !== "0") { return content; }
if (content) {
for (var key in stringVariables) {
var baseString = `[&${key}`;
var suffix = `]`;
var replacement = ""
if (stringVariables[key]) { replacement = stringVariables[key]; }
while (content.indexOf(baseString+suffix) >= 0) { content = content.replace(baseString+suffix, replacement); }
}
content = content.replace(/\[&.*?\]/g, "");
}
var matches = content.match(/\(.+?\|.+?\)/g);
if (matches) {
matches.forEach(function (item) {
var indexValue = Number(item.split("|")[0].substring(1))-1;
var arrayName = item.split("|")[1];
arrayName = arrayName.slice(0,-1);
var arrayValue = "";
if (arrays[arrayName]) {
arrayValue = arrays[arrayName][indexValue];
}
while(content.indexOf(item) >= 0) { content = content.replace(item, arrayValue); }
});
}
return content;
}
// Given "content", replace occurances of roll variable references with their values. Used in all other line types
function inlineReplaceRollVariables(content, cardParameters) {
if (cardParameters && cardParameters.disablerollvariableexpansion !== "0") { return content; }
for (var key in rollVariables) {
var baseString = `[$${key}`;
var suffix = `]`;
for (var i=0; i<rollComponents.length; i++)
{
while (content.indexOf(baseString+"."+rollComponents[i]+suffix) >= 0) { content = content.replace(baseString+"."+rollComponents[i]+suffix, rollVariables[key][rollComponents[i]]); }
}
while (content.indexOf(baseString+suffix) >= 0) {content = content.replace(baseString+suffix, rollVariables[key].Total); }
}
content = replaceStringVariables(content, cardParameters);
return content;
}
function getLineTag(line,linenum,logerror) {
if (line.indexOf("|") >= 0) {
return line.split("|")[0];
} else {
if (line.trim() !== "" && logerror) {
log(`ScriptCards Error: Line ${linenum} is missing a | character. (${line})`);
}
return "/Error - No Line Tag Specified";
}
}
function getLineContent(line) {
if (line.indexOf("|") >= 0) {
return line.substring(line.indexOf("|")+1).trim();
} else {
return "/Error - No Line Content Specified";
}
}
// Take a "Roll Text" string (ie, "1d20 + 5 [Str] + 3 [Prof]") and execute the rolls.
function parseDiceRoll(rollText, cardParameters) {
if (cardParameters.disablerollprocessing !== "0") { return content; }
rollText = inlineReplaceRollVariables(rollText, cardParameters);
rollText = cleanUpRollSpacing(rollText);
rollText = rollText.trim();
var rollComponents = rollText.split(" ");
var rollResult = {
Total: 0,
Base: 0,
Ones: 0,
Aces: 0,
Odds: 0,
Evens: 0,
RollText: rollText,
Text: "",
Style: "",
tableEntryText: "",
tableEntryImgURL: "",
tableEntryValue: ""
}
var hadOne = false;
var hadAce = false;
rollResult.Style = defaultParameters.styleNormal;
var currentOperator = "+";
for (var x=0; x<rollComponents.length; x++) {
var text = rollComponents[x];
var componentHandled = false;
// A die specifier in XdX format
if (text.match(/^\d+d\d+$/)) {
componentHandled = true;
var count=text.split("d")[0];
var sides=text.split("d")[1];
rollResult.Text += `${count}d${sides} (`;
for (c=0; c<count; c++) {
var thisRoll = randomInteger(sides);
switch (currentOperator) {
case "+": rollResult.Total += thisRoll; rollResult.Base += thisRoll; break;
case "-": rollResult.Total -= thisRoll; rollResult.Base -= thisRoll; break;
case "*": rollResult.Total *= thisRoll; rollResult.Base *= thisRoll; break;
case "/": rollResult.Total /= thisRoll; rollResult.Base /= thisRoll; break;
case "%": rollResult.Total %= thisRoll; rollResult.Base %= thisRoll; break;
}
if (thisRoll == 1) { rollResult.Ones++; hadOne = true; }
if (thisRoll == sides) { rollResult.Aces++; hadAce = true; }
if (thisRoll % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
rollResult.Text += thisRoll;
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// A die specifier in XdXkhX or XdXklX format
if (text.match(/^\d+d\d+k[lh]\d+$/)) {
componentHandled = true;
var count = Number(text.split("d")[0]);
var sides = Number(text.split("d")[1].split("k")[0]);
var keepType = text.split("k")[1].substring(0,1);
var keepCount = Number(text.split("k")[1].substring(1));
var rollSet = [];
if (keepCount > sides) { keepCount = sides; }
rollResult.Text += `${count}d${sides}k${keepType}${keepCount} (`;
for (c=0; c<count; c++) {
var thisRoll = randomInteger(sides);
rollSet.push(thisRoll);
}
if (keepType === "l") {
rollSet.sort(function(a, b){return a-b});
} else {
rollSet.sort(function(a, b){return b-a});
}
for (c=0; c<count; c++) {
if (c < keepCount) {
switch (currentOperator) {
case "+": rollResult.Total += rollSet[c]; rollResult.Base += rollSet[c]; break;
case "-": rollResult.Total -= rollSet[c]; rollResult.Base -= rollSet[c]; break;
case "*": rollResult.Total *= rollSet[c]; rollResult.Base *= rollSet[c]; break;
case "/": rollResult.Total /= rollSet[c]; rollResult.Base /= rollSet[c]; break;
case "%": rollResult.Total %= rollSet[c]; rollResult.base %= rollSet[c]; break;
}
if (rollSet[c] == 1) { rollResult.Ones++; hadOne = true; }
if (rollSet[c] == sides) { rollResult.Aces++; hadAce = true; }
if (rollSet[c] % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
}
rollResult.Text += rollSet[c];
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// A die specifier in XdXkhXr<>X or XdXklXr<>X format
if (text.match(/^\d+d\d+k[lh]\d+r[\<\>]\d+$/)) {
componentHandled = true;
var count = Number(text.split("d")[0]);
var sides = Number(text.split("d")[1].split("k")[0]);
var keepType = text.split("k")[1].substring(0,1);
var keepCount = Number(text.split("k")[1].substring(1).split("r")[0]);
var rerolltype = text.split("r")[1].substring(0,1);
var rerolltexttype = "L";
if (rerolltype == ">") {rerolltexttype = "G"}
var rerollThreshold = Number(text.split("r")[1].substring(1));
var rollSet = [];
if (rerollThreshold == sides && rerolltype == "<") { rerollThreshold = sides - 1 }
if (rerollThreshold == 1 && rerolltype == ">" ) { rerollThreshold = 2 }
if (keepCount > sides) { keepCount = sides; }
rollResult.Text += `${count}d${sides}k${keepType}${keepCount}r${rerolltexttype}${rerollThreshold} (`;
for (c=0; c<count; c++) {
var thisRoll = randomInteger(sides);
if (rerolltype == "<") {
while (thisRoll <= rerollThreshold) {
thisRoll = randomInteger(sides);
}
}
if (rerolltype == ">") {
while (thisRoll >= rerollThreshold) {
thisRoll = randomInteger(sides);
}
}
rollSet.push(thisRoll);
}
if (keepType === "l") {
rollSet.sort(function(a, b){return a-b});
} else {
rollSet.sort(function(a, b){return b-a});
}
for (c=0; c<count; c++) {
if (c < keepCount) {
switch (currentOperator) {
case "+": rollResult.Total += rollSet[c]; rollResult.Base += rollSet[c]; break;
case "-": rollResult.Total -= rollSet[c]; rollResult.Base -= rollSet[c]; break;
case "*": rollResult.Total *= rollSet[c]; rollResult.Base *= rollSet[c]; break;
case "/": rollResult.Total /= rollSet[c]; rollResult.Base /= rollSet[c]; break;
case "%": rollResult.Total %= rollSet[c]; rollResult.base %= rollSet[c]; break;
}
if (rollSet[c] == 1) { rollResult.Ones++; hadOne = true; }
if (rollSet[c] == sides) { rollResult.Aces++; hadAce = true; }
if (rollSet[c] % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
}
rollResult.Text += rollSet[c];
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// A die specifier in XdXro<X or XdXro>X format (reroll above/below number)
if (text.match(/^\d+d\d+ro[\<\>]\d+$/)) {
componentHandled = true;
var count = Number(text.split("d")[0]);
var sides = Number(text.split("d")[1].split("ro")[0]);
var rerolltype = text.split("ro")[1].substring(0,1);
var rerolltexttype = "L";
if (rerolltype == ">") {rerolltexttype = "G"}
var rerollThreshold = Number(text.split("ro")[1].substring(1));
var rollSet = [];
var rollTextSet = [];
if (rerollThreshold == sides && rerolltype == "<") { rerollThreshold = sides - 1 }
if (rerollThreshold == 1 && rerolltype == ">" ) { rerollThreshold = 2 }
rollResult.Text += `${count}d${sides}ro${rerolltexttype}${rerollThreshold} (`;
for (c=0; c<count; c++) {
var thisRoll = 0;
var thisRollText = "";
thisRoll = randomInteger(sides);
thisRollText = thisRoll.toString();
if (rerolltexttype == "G" && thisRoll >= rerollThreshold) {
thisRoll = randomInteger(sides);
thisRollText += "(" + thisRoll.toString() + ")";
}
if (rerolltexttype == "L" && thisRoll <= rerollThreshold) {
thisRoll = randomInteger(sides);
thisRollText += "(" + thisRoll.toString() + ")";
}
rollSet.push(thisRoll);
rollTextSet.push(thisRollText);
}
for (c=0; c<count; c++) {
switch (currentOperator) {
case "+": rollResult.Total += rollSet[c]; rollResult.Base += rollSet[c]; break;
case "-": rollResult.Total -= rollSet[c]; rollResult.Base -= rollSet[c]; break;
case "*": rollResult.Total *= rollSet[c]; rollResult.Base *= rollSet[c]; break;
case "/": rollResult.Total /= rollSet[c]; rollResult.Base /= rollSet[c]; break;
case "%": rollResult.Total %= rollSet[c]; rollResult.base %= rollSet[c]; break;
}
if (rollSet[c] == 1) { rollResult.Ones++; hadOne = true; }
if (rollSet[c] == sides) { rollResult.Aces++; hadAce = true; }
if (rollSet[c] % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
rollResult.Text += rollTextSet[c];
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// A die specifier in XdXr<X or XdXr>X format (reroll above/below number)
if (text.match(/^\d+d\d+r[\<\>]\d+$/)) {
componentHandled = true;
var count = Number(text.split("d")[0]);
var sides = Number(text.split("d")[1].split("r")[0]);
var rerolltype = text.split("r")[1].substring(0,1);
var rerolltexttype = "L";
if (rerolltype == ">") {rerolltexttype = "G"}
var rerollThreshold = Number(text.split("r")[1].substring(1));
var rollSet = [];
if (rerollThreshold == sides && rerolltype == "<") { rerollThreshold = sides - 1 }
if (rerollThreshold == 1 && rerolltype == ">" ) { rerollThreshold = 2 }
rollResult.Text += `${count}d${sides}ro${rerolltexttype}${rerollThreshold} (`;
var thisRoll=-1;
for (c=0; c<count; c++) {
if (rerolltype == "<") {
do {
thisRoll = randomInteger(sides);
} while (thisRoll <= rerollThreshold);
} else {
do {
thisRoll = randomInteger(sides);
} while (thisRoll >= rerollThreshold);
}
rollSet.push(thisRoll);
}
for (c=0; c<count; c++) {
switch (currentOperator) {
case "+": rollResult.Total += rollSet[c]; rollResult.Base += rollSet[c]; break;
case "-": rollResult.Total -= rollSet[c]; rollResult.Base -= rollSet[c]; break;
case "*": rollResult.Total *= rollSet[c]; rollResult.Base *= rollSet[c]; break;
case "/": rollResult.Total /= rollSet[c]; rollResult.Base /= rollSet[c]; break;
case "%": rollResult.Total %= rollSet[c]; rollResult.base %= rollSet[c]; break;
}
if (rollSet[c] == 1) { rollResult.Ones++; hadOne = true; }
if (rollSet[c] == sides) { rollResult.Aces++; hadAce = true; }
if (rollSet[c] % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
rollResult.Text += rollSet[c];
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// A die specifier in XdX! or XdX!>X format (exploding dice)
if (text.match(/^\d+d\d+!([\<\>]\d+)?$/)) {
componentHandled = true;
var count = Number(text.split("d")[0]);
var sides = Number(text.split("d")[1].split("!")[0]);
var rerollnumber = sides;
var comptype = "G";
if (text.split("d")[1].split("!")[1] !== "") {
comptype = text.split("d")[1].split("!")[1].substring(0,1);
if (comptype == "<") {
comptype = "L";
} else {
comptype = "G";
}
rerollnumber = Number(text.split("d")[1].split("!")[1].substring(1));
}
if (comptype == "G" && rerollnumber == 1) { rerollnumber += 1 }
if (comptype == "L" && rerollnumber == sides) { rerollnumber -= 1 }
var rollSet = [];
var rollTextSet = [];
rollResult.Text += `${count}d${sides}!${comptype}${rerollnumber} (`;
for (c=0; c<count; c++) {
var thisRoll = 0;
var subroll = 0
var thisRollText = ""
subroll = randomInteger(sides);
if (comptype == "G") {
thisRoll = subroll;
thisRollText += subroll.toString();
while (subroll >= rerollnumber) {
subroll = randomInteger(sides);
thisRoll += subroll;
thisRollText += "!" + subroll.toString();
}
}
if (comptype == "L") {
thisRoll = subroll;
thisRollText += subroll.toString();
while (subroll <= rerollnumber) {
subroll = randomInteger(sides);
thisRoll += subroll;
thisRollText += "!" + subroll.toString();
}
}
rollSet.push(thisRoll);
rollTextSet.push(thisRollText);
}
for (c=0; c<count; c++) {
switch (currentOperator) {
case "+": rollResult.Total += rollSet[c]; rollResult.Base += rollSet[c]; break;
case "-": rollResult.Total -= rollSet[c]; rollResult.Base -= rollSet[c]; break;
case "*": rollResult.Total *= rollSet[c]; rollResult.Base *= rollSet[c]; break;
case "/": rollResult.Total /= rollSet[c]; rollResult.Base /= rollSet[c]; break;
case "%": rollResult.Total %= rollSet[c]; rollResult.base %= rollSet[c]; break;
}
if (rollSet[c] == 1) { rollResult.Ones++; hadOne = true; }
if (rollSet[c] == sides) { rollResult.Aces++; hadAce = true; }
if (rollSet[c] % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
rollResult.Text += rollTextSet[c];
if (c<count-1) { rollResult.Text += " ," }
}
rollResult.Text += ") ";
}
// A die specifier in XdX>X or XdX<X format
if (text.match(/^(\d+)d(\d+)([\>\<])(\d+)$/)) {
componentHandled = true;
var parts = text.match(/^(\d+)d(\d+)([\>\<])(\d+)$/);
var count=parts[1];
var sides=parts[2];
var op = parts[3];
var success = parts[4];
rollResult.Text += `${count}d${sides}${op==">"?"G":"L"}${success} (`;
for (c=0; c<count; c++) {
var thisRoll = randomInteger(sides);
var countIt = false;
if (op == ">" && thisRoll > success) { countIt = true }
if (op == "<" && thisRoll < success) { countIt = true }
if (countIt) {
switch (currentOperator) {
case "+": rollResult.Total += 1; rollResult.Base += 1; break;
case "-": rollResult.Total -= 1; rollResult.Base -= 1; break;
}
}
if (thisRoll == 1) { rollResult.Ones++; hadOne = true; }
if (thisRoll == sides) { rollResult.Aces++; hadAce = true; }
if (thisRoll % 2 == 0) { rollResult.Evens++; } else { rollResult.Odds++; }
rollResult.Text += thisRoll;
if (c<count-1) { rollResult.Text += "," }
}
rollResult.Text += ") ";
}
// An operator
if (text.match(/^[\+\-\*\/\\\%]$/)) {
componentHandled = true;
currentOperator = text;
//rollResult.Text += `${currentOperator} `;
rollResult.Text += currentOperator == "*" ? "x " : currentOperator + " ";
}
// Just a number
if (text.match(/^[+-]?(\d*\.)?\d*$/)) {
componentHandled = true;
rollResult.Text += `${text} `;
if (!isNaN(text)) {
switch (currentOperator) {
case "+": rollResult.Total += Number(text); break;
case "-": rollResult.Total -= Number(text); break;
case "*": rollResult.Total *= Number(text); break;
case "/": rollResult.Total /= Number(text); break;
case "%": rollResult.Total %= Number(text); break;
case "\\": rollResult.Total = cardParameters.roundup == "0" ? Math.floor(rollResult.Total / Number(text)) : Math.ceil(rollResult.Total / Number(text)); break;
}
}
}
// A card variable
if (text.match(/^\[\$.+\]$/)) {
componentHandled = true;
var thisKey = text.substring(2,text.length-1);
var thisValue = Number(inlineReplaceRollVariables(thisKey, cardParameters), cardParameters);
if (rollVariables[thisKey]) {
rollResult.Text += `(${rollVariables[thisKey].Text}) `;
} else {
rollResult.Text += `${thisValue} `;
}
switch (currentOperator) {
case "+": rollResult.Total += thisValue; break;
case "-": rollResult.Total -= thisValue; break;
case "*": rollResult.Total *= thisValue; break;
case "/": rollResult.Total /= thisValue; break;
case "%": rollResult.Total %= thisValue; break;
case "\\": rollResult.Total = cardParameters.roundup == "0" ? Math.floor(rollResult.Total / thisValue) : Math.ceil(rollResult.Total / thisValue); break;
}
}
// Flavor Text
if (text.match(/^\[.+\]$/)) {
componentHandled = true;
if ((text.charAt(1) !== "$") && (text.charAt(1) !== "=")) {
rollResult.Text += ` ${text} `;
}
}
// Plain Text
if (text.match(/\b[A-Za-z]+\b$/) && cardParameters.allowplaintextinrolls !== 0) {
componentHandled = true;
rollResult.Text += ` ${text} `;
}
// A Rollable Table Result
if (text.match(/\[[Tt]\#.+?\]/g)) {
componentHandled = true;
var rollTableName = text.substring(3,text.length-1);
var tableResult = rollOnRollableTable(rollTableName);
if (tableResult) {
rollResult.tableEntryText = tableResult[0];
rollResult.tableEntryImgURL = tableResult[1];
rollResult.tableEntryValue = isNaN(rollResult.tableEntryText) ? 0 : parseInt(rollResult.tableEntryText);
}
}
if (!componentHandled) {
componentHandled = true;
rollResult.Text += `${text} `;
}
}
if (hadOne && hadAce) { rollResult.Style = defaultParameters.styleBoth; }
if (hadOne && !hadAce) { rollResult.Style = defaultParameters.styleFumble; }
if (!hadOne && hadAce) { rollResult.Style = defaultParameters.styleCrit; }
rollResult.Text = rollResult.Text.replace(/\+ \+/g," + ");
rollResult.Text = rollResult.Text.replace(/\- \-/g," - ");
return rollResult;
}
function cleanUpRollSpacing(input) {
input = input.replace(/\+/g, " + ");
//input = input.replace(/\-/g, " - ");
input = input.replace(/(?<!\[)\-\b(?![\w\s]*[\]])/g," - ");
input = input.replace(/\*/g, " * ");
input = input.replace(/\//g, " / ");
input = input.replace(/\\/g, " \\ ")
input = input.replace(/\%/g, " % ");
input = input.replace(/\[/g, " [");
input = input.replace(/\]/g, "] ");
input = input.replace(/\s+/g, " ");
return input;
}
function buildRowOutput(tag, content) {
return htmlRowTemplate.replace("=X=ROWDATA=X=", `<strong>${tag}</strong> ${content}`);
}
function buildTooltip(text,tip,style){
var tooltipStyle = ` font-family: ${defaultParameters.titlefont}; font-size: ${defaultParameters.titlefontsize}; font-weight: normal; font-style: normal; ${style} `;
return `<span style='${tooltipStyle}' class='showtip tipsy' title='${tip}'>${text}</span>`;
};
function processFullConditional(conditional, cardParameters) {
// Remove multiple spaces
var trimmed = conditional.replace(/\s+/g, ' ').trim