Skip to content

Instantly share code, notes, and snippets.

@kjaegers

kjaegers/scriptcards.js Secret

Last active Aug 1, 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 - So I can keep track of what to put in the release notes :)
- Added "angle" function
*/
const APINAME = "ScriptCards";
const APIVERSION = "1.3.9c";
const APIAUTHOR = "Kurt Jaegers";
const debugMode = false;
const parameterAliases = {
"tablebackgroundcolor": "tablebgcolor",
"titlecardbackgroundcolor": "titlecardbackground",
"nominmaxhilight": "nominmaxhighlight",
"norollhilight": "norollhilight",
"buttonbackgroundcolor": "buttonbackground",
}
// 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",
titlecardgradient: "0",
titlecardbackgroundimage: "",
titlecardbottomborder: "2px solid #444444;",
titlefontface: "Contrail One",
titlefontsize: "1.2em",
titlefontlineheight: "1.2em",
titlefontshadow: "-1px 1px 0 #000, 1px 1px 0 #000, 1px -1px 0 #000, -1px -1px 0 #000;",
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",
bodybackgroundimage: "",
oddrowbackgroundimage: "",
evenrowbackgroundimage: "",
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",
buttonbackgroundimage: "",
buttontextcolor: "White",
buttonbordercolor: "#999999",
buttonfontsize: "x-small",
buttonfontface: "Tahoma",
/*
damagebuttonbackgroundcolor: "#FF4444",
damagebuttonbackgroundimage: "",
healbuttonbackgroundcolor: "#22DD22",
healbuttonbackgroundimage: "",
*/
dicefontcolor: "#1C6EA4",
dicefontsize: "3.0em",
usehollowdice: "0",
allowplaintextinrolls: "0",
whisper: "",
showfromfornonwhispers: "0",
allowinlinerollsinoutput: "0",
nominmaxhighlight: "0",
norollhighlight: "0",
disablestringexpansion: "0",
disablerollvariableexpansion: "0",
disableparameterexpansion: "0",
disablerollprocessing: "0",
disableattributereplacement: "0",
disableinlineformatting: "0",
executionlimit:"40000",
deferralcharacter:"^",
locale:"en-US", //apparently not supported by Roll20's Javascript implementation...
timezone:"America/New_York",
hpbar: "3",
styleTableTag: " border-collapse:separate; border: solid black 2px; border-radius: 6px; -moz-border-radius: 6px; ",
stylenone: " 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; ",
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;",
};
// HTML Templates for the various pieces of the output card. Replaced sections are marked with
// !{...} syntax, and will have values substituted in them when the output line is built.
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-color: !{titlecardbackground}; background-image: !{titlecardbackgroundimage}; 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: !{titlefontshadow}">=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; background-image:!{bodybackgroundimage};">`;
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; background-image:!{bodybackgroundimage};">`;
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}; background-image:!{buttonbackgroundimage}; color: !{buttontextcolor}; text-align: center; vertical-align:middle; border-radius: 5px; border-color:!{buttonbordercolor}; font-family: !{buttonfontface}; font-size:!{buttonfontsize};';
var gradientStyle = "linear-gradient(rgba(255, 255, 255, .3), rgba(255, 255, 255, 0))";
// Objects to hold various variables and things we could need while running a script.
var stringVariables = {};
var rollVariables = {};
var arrayVariables = {};
var arrayIndexes = {};
var tokenMarkerURLs = [];
// The rollComponents list determines what suffixes are available when reporting out the value
// of rollVariables (i.e., AttackRoll.Base)
var rollComponents = [
'Base','Total','Ones','Aces','Odds','Evens','Odds','RollText','Text','Style', 'tableEntryText', 'tableEntryImgURL', 'tableEntryValue', 'Raw'
];
// tokenAttributes lists all of the attribute names that are valid for looking up attributes
// on token objects (as compared to character objects)
var tokenAttributes = "token_name: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:bar_location:compact_bar:light_sensitivity_multiplier:night_vision_effect";
//We use several variables to track repeating section (--R) commands
var repeatingSection = undefined;
var repeatingSectionIDs = undefined;
var repeatingIndex = undefined;
var repeatingCharID = undefined;
var repeatingCharAttrs = undefined;
var repeatingSectionName = undefined;
// Storage for any Library handouts found in the game
var ScriptCardsLibrary = {};
// The Dice Fonts in Roll20 use these letters to represent the characters that display
// the dice value (J=0, A=1, B=2, etc) To get the appropriate letter to display, we can
// just the substring numeric position in this string to find the matching letter.
const diceLetters = "JABCDEFGHIJKLMNOPQRSTUVWYZ";
// Planned JSON support. Not currently implemented/documented.
var jsonObject = undefined;
// Used for storing parameters passed to a subroutine with --> or --?|> lines
var callParamList = {};
on('ready', function () {
// if ScriptCards has never been run in this game, create state information to store
// configuration and values between sessions/sandbox instances.
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 = {}; }
// Retrieve the list of token/status markers from the Campaign and create an associative
// array that links the marker name to the URL of the marker image for use in the
// [sm]...[/sm] inline formatting syntax. This allows us to fully support custom token
// marker sets.
const tokenMarkers = JSON.parse(Campaign().get("token_markers"));
for (var x=0;x<tokenMarkers.length;x++) {
tokenMarkerURLs[tokenMarkers[x].name] = tokenMarkers[x].url;
}
// Cache any library handouts
loadLibraryHandounts();
API_Meta.ScriptCards.version = APIVERSION;
// Log that the script is "ready". We also include the meta offset which can be used
// to track sandbox crash errors by subtracting the offset from the line number that the
// sandbox reports to contain the error.
log(`-=> ${APINAME} - ${APIVERSION} by ${APIAUTHOR} Ready <=- Meta Offset : ${API_Meta.ScriptCards.offset}`);
// When a handout changes, recache the library handouts
on("change:handout", function () {
loadLibraryHandounts();
});
// Main processing area... looing for api commands to handle.
// While the main ScriptCards command is !scriptcards (or !script, or !scriptcard) we also
// respond to several other commands, including:
// !sc-liststoredsettings - Provides a list of stored settings groups (via --s)
// !sc-deletestoredsettings - Delete a stored settings group
// !sc-resume - resume a card paused with --i
// !sc-reentrant - resume execution of a card at a particular label
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;
// !sc-liststoredsettings creates a new scriptcard and sends it to
// chat. With no parameters, it reports a list of all of the stored settings
// groups. If a stored settings group name is passed, it will list all of the
// customized settings for that group.
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);
if (state[APINAME].storedSettings["Default"] !== undefined) {
newSettings = state[APINAME].storedSettings["Default"];
for (var key in newSettings) {
cardParameters[key] = newSettings[key];
}
}
msg.content = processInlinerolls(msg);
// 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 = {};
loopControl = {};
loopStack = [];
scriptData = [];
saveScriptData = [];
lastBlockAction = "";
executionCounter = 0;
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"));
} else {
cardParameters.sourcecharacter = undefined;
}
}
if(cardParameters.targettoken) {
var charLookup = getObj("graphic", cardParameters.targettoken);
if (charLookup !== undefined && charLookup.get("represents") !== "") {
cardParameters.targetcharacter = getObj("character", charLookup.get("represents"));
} else {
cardParameters.targetcharacter = undefined;
}
}
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 and for data lines to store in the data structure
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 (thisTag.toLowerCase() === "d!") {
var thisData = CSVtoArray(getLineContent(cardLines[x]));
while (thisData.length > 0) {
var dataElement = thisData.shift();
scriptData.push(dataElement);
saveScriptData.push(dataElement);
}
}
}
if (isReentrant) {
outputLines = [];
gmonlyLines = [];
var entryLabel = resumeArgs[1].split(";")[0];
stringVariables["reentryval"] = resumeArgs[1].split(";")[1];
if (lineLabels[entryLabel]) {
lineCounter = lineLabels[entryLabel]
} else {
log(`ScriptCards Error: Label ${resumeArgs[1]} is not defined for reentrant script`)
};
}
// 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 = replaceVariableContent(thisTag, cardParameters, false);
var thisContent = getLineContent(cardLines[lineCounter]);
thisContent = replaceVariableContent(thisContent, cardParameters, (thisTag.charAt(0) == "+" || thisTag.charAt(0) == "*" || thisTag.charAt(0) == "&"));
//thisContent = replaceCharacterAttributes(thisContent, 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 looping statements
if (thisTag.charAt(0) === "%") {
var loopCounter = thisTag.substring(1);
if (loopCounter && loopCounter !== "!") {
if (loopControl[loopCounter]) { log(`ScriptCards: Warning - loop counter ${loopCounter} reused inside itself on line ${lineCounter}.`); }
var params = thisContent.split(";");
if (params.length === 2) { params.push("1"); } // Add a "1" as the assumed step value if only two parameters
if (params.length === 3) {
if (isNumeric(params[0]) && isNumeric(params[1]) && isNumeric(params[2]) && parseInt(params[2]) != 0) {
loopControl[loopCounter] = { initial:parseInt(params[0]), current:parseInt(params[0]), end:parseInt(params[1]), step:parseInt(params[2]), nextIndex: lineCounter }
stringVariables[loopCounter] = params[0];
loopStack.push(loopCounter);
if (cardParameters.debug == 1) { log(`ScriptCards: Info - Beginning of loop ${loopCounter}`) }
} else {
if (parseInt(params[2] == 0)) {
log(`ScriptCards: Error - cannot use loop step of 0 at line ${lineCounter}`)
} else {
log(`ScriptCards: Error - loop initialization contains non-numeric values on line ${lineCounter}`)
}
}
}
} else {
if (loopStack.length >= 1) {
var currentLoop = loopStack[loopStack.length-1];
if (loopControl[currentLoop]) {
loopControl[currentLoop].current += loopControl[currentLoop].step;
stringVariables[currentLoop] = loopControl[currentLoop].current.toString();
if ((loopControl[currentLoop].step > 0 && loopControl[currentLoop].current > loopControl[currentLoop].end) ||
(loopControl[currentLoop].step < 0 && loopControl[currentLoop].current < loopControl[currentLoop].end) ||
loopCounter == "!") {
loopStack.pop();
delete loopControl[currentLoop];
if (cardParameters.debug == 1) { log(`ScriptCards: Info - End of loop ${currentLoop}`)}
if (loopCounter == "!") {
var line = lineCounter;
for (line = lineCounter + 1; line<cardLines.length; line++) {
if (getLineTag(cardLines[line],line,"").trim() == "%") {
lineCounter = line;
break;
}
}
if (lineCounter > cardLines.length) {
log(`ScriptCards: Warning - no end block marker found for loop block started ${loopCounter}`);
lineCounter = cardLines.length + 1;
}
}
} else {
lineCounter = loopControl[currentLoop].nextIndex;
}
}
} else {
log(`ScriptCards: Error - Loop end statement without and active loop on line ${lineCounter}`);
}
}
}
// Handle setting of card parameters (lines beginning with --#)
if (thisTag.charAt(0) === "#") {
var paramName = thisTag.substring(1).toLowerCase();
paramName = parameterAliases[paramName] || paramName;
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;
}
}
break;
case "titlecardgradient":
if (thisContent.trim() !== "0") {
cardParameters["titlecardbackgroundimage"] = gradientStyle;
} else {
cardParameters["titlecardbackgroundimage"] = "";
}
break;
case "buttontextcolor":
if (thisContent.trim().match(/^[0-9a-fA-F]{6}$/)) {
//cardParameters["buttontextcolor"] = `#${thisContent.trim()}`;
}
break;
case "bodybackgroundimage":
if (thisContent.trim() !== "") {
cardParameters.oddrowbackground = "#00000000";
cardParameters.evenrowbackground = "#00000000";
}
break;
case "evenrowbackgroundimage":
if (thisContent.trim() !== "") {
cardParameters.evenrowbackground = "#00000000";
}
break;
case "oddrowbackgroundimage":
if (thisContent.trim() !== "") {
cardParameters.oddrowbackground = "#00000000";
}
break;
/*
case "damagebuttonbackgroundimage":
if (thisContent.trim() !== "") {
cardParameters.damagebuttonbackgroundcolor = "#00000000";
}
break;
case "healbuttonbackgroundimage":
if (thisContent.trim() !== "") {
cardParameters.healbuttonbackgroundcolor = "#00000000";
}
break;
*/
}
}
// Handle setting string values
if (thisTag.charAt(0) === "&") {
var variableName = thisTag.substring(1).trim();
if (thisContent.charAt(0) == "+") {
stringVariables[variableName] = (stringVariables[variableName] || "") + replaceVariableContent(thisContent.substring(1), cardParameters, true);
} else {
stringVariables[variableName] = replaceVariableContent(thisContent,cardParameters, true);
}
}
// Handle data read statements
if (thisTag.charAt(0).toLowerCase() === "d" && thisTag.charAt(1) !== "!") {
if (thisTag.charAt(1) == "<") {
scriptData = saveScriptData.slice(0);
} else {
if (scriptData.length > 0) {
stringVariables[thisTag.substring(1)] = scriptData.shift();
} else {
stringVariables[thisTag.substring(1)] = "EndOfDataError";
}
}
}
// Handle "Macro" calls (--m)
if (thisTag.charAt(0).toLowerCase() === "m") {
var characterName = thisTag.substring(1);
var macroName = thisContent.trim();
if (characterName.length >= 1) {
sendChat("ScriptCards", `%{${characterName}|${macroName}}`);
} else {
sendChat("ScriptCards", `#${macroName}`);
}
}
// 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 "%" : resultType = "next"; 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(replaceVariableContent(varValue, cardParameters), cardParameters, true);
break;
case "stringset":
if (varName && varValue) {
if (resultType == "stringset" && varValue.charAt(0) == "+") {
varValue = (stringVariables[varName] || "") + varValue.substring(1);
}
stringVariables[varName] = replaceVariableContent(varValue,cardParameters, true);
} else {
log(`ScriptCards Error: Variable name or value not specified in conditional on line ${lineCounter}`);
}
break;
case "next":
if (loopStack.length >= 1) {
var currentLoop = loopStack[loopStack.length-1];
if (loopControl[currentLoop]) {
loopControl[currentLoop].current += loopControl[currentLoop].step;
stringVariables[currentLoop] = loopControl[currentLoop].current.toString();
if ((loopControl[currentLoop].step > 0 && loopControl[currentLoop].current > loopControl[currentLoop].end) ||
(loopControl[currentLoop].step < 0 && loopControl[currentLoop].current < loopControl[currentLoop].end) ||
jumpDest.charAt(1) == "!") {
loopStack.pop();
delete loopControl[currentLoop];
} else {
lineCounter = loopControl[currentLoop].nextIndex;
}
}
} else {
log(`ScriptCards: Error - Loop end statement without and active loop 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 "character":
if (params.length >= 4) {
switch (params[1].toLowerCase()) {
case "runability":
var charid = undefined
var char = getObj("character", params[2]);
if (char === undefined) {
var actualToken = getObj("graphic", params[2]);
if (actualToken !== undefined) {
charid = actualToken.get("represents");
char = getObj("character", charid);
}
} else {
charid = char.get("_id");
}
if (char !== undefined) {
var abilname = params[3]
var ability = findObjs({type: "ability", _characterid: charid, name: abilname })
if (ability !== undefined && ability !== []) {
ability = ability[0]
if (ability !== undefined) {
sendChat(char.get("name"), ability.get('action').replace(/@\{([^|]*?|[^|]*?\|max|[^|]*?\|current)\}/g, '@{'+(char.get('name'))+'|$1}'));
}
}
}
break;
}
}
break;
case "system":
if (params.length >= 3) {
switch (params[1].toLowerCase()) {
case "date":
var d = new Date();
switch (params[2].toLowerCase()) {
case "getdatetime":
log(cardParameters.locale);
try {
stringVariables[variableName] = d.toLocaleString(cardParameters.locale, {timeZone: cardParameters.timezone});
} catch {
stringVariables[variableName] = "Unknown/Invalid Locale or TimeZone";
}
break;
case "gettime":
try {
stringVariables[variableName] = d.toLocaleTimeString(cardParameters.locale, {timeZone: cardParameters.timezone});
} catch {
stringVariables[variableName] = "Unknown/Invalid Locale or TimeZone";
}
break;
case "getdate":
try {
stringVariables[variableName] = d.toLocaleDateString(cardParameters.locale, {timeZone: cardParameters.timezone});
} catch {
stringVariables[variableName] = "Unknown/Invalid Locale or TimeZone";
}
break;
case "getraw":
stringVariables[variableName] = d.getTime();
break;
}
break;
case "readsetting":
stringVariables[variableName] = cardParameters[params[2].toLowerCase()] || "UnknownSetting";
break;
case "dumpvariables":
switch (params[2].toLowerCase()) {
case "rolls":
for (var key in rollVariables) {
log(`RollVariable: ${key}, Value: ${rollVariables[key]}`)
}
break;
case "string":
for (var key in stringVariables) {
log(`StringVariable: ${key}, Value: ${stringVariables[key]}`)
}
break;
case "array":
for (var key in arrayVariables) {
log(`ArrayVariable: ${key}, Value: ${arrayVariables[key]}`)
}
break;
}
break;
case "findability":
// Params: 2-character name, 3-ability name
stringVariables[variableName] = "AbilityNotFound";
var theChar = findObjs({_type:"character", name: params[2]});
if (theChar[0]) {
var theAbility = findObjs({_type:"ability", _characterid: theChar[0].id, name:params[3]});
if (theAbility[0]) {
stringVariables[variableName] = theAbility[0].id;
}
}
break;
}
}
break;
case "turnorder":
var variableName = thisTag.substring(1);
if (params.length == 2) {
if (params[1].toLowerCase() == "clear") {
Campaign().set("turnorder", "");
}
if (params[1].toLowerCase() == "getcurrentactor") {
var turnorder = [];
if (Campaign().get("turnorder") !== "") {
turnorder = JSON.parse(Campaign().get("turnorder"));
}
if (turnorder !== []) {
stringVariables[variableName] = turnorder[0].id
}
}
}
if (params.length == 3) {
if (params[1].toLowerCase() == "removetoken") {
var turnorder = [];
if (Campaign().get("turnorder") !== "") {
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[1].toLowerCase() == "findtoken") {
var turnorder = JSON.parse(Campaign().get("turnorder"));
for (var x=turnorder.length-1; x>=0; x--) {
if (turnorder[x].id.trim() == params[2].trim()) {
stringVariables[variableName] = turnorder[x].pr;
log(`Set variable to ${turnorder[x].pr}`)
}
}
}
}
if (params.length == 4) {
if (params[1].toLowerCase() == "addtoken") {
var turnorder = [];
if (Campaign().get("turnorder") !== "") {
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") {
var turnorder = [];
if (Campaign().get("turnorder") !== "") {
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") {
var turnorder = [];
if (Campaign().get("turnorder") !== "") {
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 "euclideanpixel":
case "euclideanlong":
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");
var x2 = token2.get("left");
var y1 = token1.get("top");
var y2 = token2.get("top");
result = Math.floor(Math.sqrt(Math.pow((x1-x2),2)+Math.pow((y1-y2),2)));
if (params[0].toLowerCase() == "euclideanlong") { result = result / 70; }
}
}
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;
}
}
if (params[1].toLowerCase() == "abs" && params.length == 3) {
if (!isNaN(parseFloat((params[2])))) {
rollVariables[variableName] = Math.abs(params[2])
}
}
// 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);
}
}
if (params.length == 4 && params[1].toLowerCase() == "angle") {
var token1 = getObj("graphic", params[2]);
var token2 = getObj("graphic", params[3]);
if (token1 && token2) {
var angle = Math.atan2(token2.get("top") - token1.get("top"), token2.get("left") - token1.get("left"));
angle *= 180 / Math.PI;
angle -= 270;
while (angle < 0) { angle = 360 + angle}
stringVariables[variableName] = angle;
}
}
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;
case "tolowercase":
stringVariables[variableName] = params[2].toLowerCase();
break;
case "touppercase":
stringVariables[variableName] = params[2].toUpperCase();
break;
case "trim":
stringVariables[variableName] = params[2].trim();
break;
case "totitlecase":
stringVariables[variableName] = params[2].toLowerCase()
.split(' ')
.map(function(word) {
return (word.charAt(0).toUpperCase() + word.slice(1));
})
.join(" ")
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])+params[2].length);
}
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;
case "replaceall":
var str = params[4];
while(str.includes(params[2])) { str = str.replace(params[2], params[3])}
stringVariables[variableName] = str;
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() == "playerobjects") {
var players = findObjs({_type:"player"});
arrayVariables[params[2]] = [];
for (var x=0; x<players.length; x++) {
arrayVariables[params[2]].push(players[x].get("id"));
}
}
if (params[1].toLowerCase() == "handoutobjects") {
var players = findObjs({_type:"handout"});
arrayVariables[params[2]] = [];
for (var x=0; x<players.length; x++) {
arrayVariables[params[2]].push(players[x].get("id"));
}
}
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]] && 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() == "setatindex") {
if (arrayVariables[params[2]]) {
var index = Number(params[3]);
if (arrayVariables[params[2]].length >= index) {
arrayVariables[params[2]][index] = params[4];
}
}
}
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;
case "object":
break;
}
}
// Handle API Call Lines
if (thisTag.charAt(0) === "@") {
var apicmd = thisTag.substring(1);
var spacer = " ";
const slash="\\";
// Replace _ with --
var params = thisContent.replace(/(^|\ +)_/g, " --");
// Remove deferral markers from deferred SelectManager/ZeroFrame calls
var regex = new RegExp(`${slash}{${slash}${cardParameters.deferralcharacter}(${slash}&.*?)${slash}}`, "g");
params = params.replace(regex, "{$1}");
// Remove deferral markers from deferred Fetch calls
regex = new RegExp(`${slash}@${slash}${cardParameters.deferralcharacter}${slash}((.*?)${slash})`,"g");
params = params.replace(regex, "@($1)");
regex = new RegExp(`${slash}*${slash}${cardParameters.deferralcharacter}${slash}((.*?)${slash})`,"g");
params = params.replace(regex, "*($1)");
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).trim();
rollVariables[rollIDName] = parseDiceRoll(replaceVariableContent(thisContent, cardParameters), cardParameters, true);
}
// Handle direct output lines
if (thisTag.charAt(0) === "+") {
var rowData = buildRowOutput(thisTag.substring(1), replaceVariableContent(thisContent,cardParameters, true));
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}; background-image: ${cardParameters.evenrowbackgroundimage}; `); }
//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}; background-image: ${cardParameters.oddrowbackgroundimage}; `); }
//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), replaceVariableContent(thisContent,cardParameters, true));
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}; background-image: ${cardParameters.evenrowbackgroundimage}; `); }
//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}; background-image: ${cardParameters.oddrowbackgroundimage}; `); }
//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;
var blockSkip = false;
var blockChar = "]";
if (isTrue && falseDest == "[") { blockSkip = true; }
if (!isTrue && trueDest == "[") { blockSkip = true; }
if (jumpDest) {
switch (jumpDest.charAt(0)) {
case ">" : resultType = "gosub"; break;
case "%" : resultType = "next"; break;
case "[" : resultType = "block"; 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(replaceVariableContent(varValue, cardParameters, false), cardParameters);
break;
case "stringset":
if (varName && varValue) {
if (resultType == "stringset" && varValue.charAt(0) == "+") {
varValue = (stringVariables[varName] || "") + varValue.substring(1);
}
stringVariables[varName] = replaceVariableContent(varValue,cardParameters, false);
} else {
log(`ScriptCards Error: Variable name or value not specified in conditional on line ${lineCounter}`);
}
break;
case "next":
if (loopStack.length >= 1) {
var currentLoop = loopStack[loopStack.length-1];
if (loopControl[currentLoop]) {
loopControl[currentLoop].current += loopControl[currentLoop].step;
stringVariables[currentLoop] = loopControl[currentLoop].current.toString();
if ((loopControl[currentLoop].step > 0 && loopControl[currentLoop].current > loopControl[currentLoop].end) ||
(loopControl[currentLoop].step < 0 && loopControl[currentLoop].current < loopControl[currentLoop].end) ||
jumpDest.charAt(1) == "!") {
loopStack.pop();
delete loopControl[currentLoop];
blockSkip = true;
blockChar = "%";
} else {
lineCounter = loopControl[currentLoop].nextIndex;
}
}
} else {
log(`ScriptCards: Error - Loop end statement without and active loop on line ${lineCounter}`);
}
break;
case "block":
lastBlockAction = "E";
break;
}
}
if (blockSkip) {
var line = lineCounter;
if (blockChar === "]") { lastBlockAction = "S"; }
for (line = lineCounter + 1; line<cardLines.length; line++) {
if (getLineTag(cardLines[line],line,"").trim() == blockChar) {
lineCounter = line + (blockChar == "]" ? 0 : 0);
break;
}
}
if (lineCounter > cardLines.length) {
log(`ScriptCards: Warning - no end block marker found for block started reference on line ${lineCounter}`);
lineCounter = cardLines.length + 1;
}
}
}
// 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;
case "point":
var params = thisContent.split(" ");
var x = params[0];
var y = params[1];
var pid = Campaign().get("playerpageid");
if (cardParameters.activepage !== "") {
pid = cardParameters.activepage;
}
var effectInfo = findObjs({
_type : "custfx",
name : params[2].trim()
});
if (!_.isEmpty(effectInfo)) {
spawnFxWithDefinition(x, y, effectInfo[0].get('definition'), pid);
} else {
var t = params[2].trim();
if (x && y) {
if (t !== "" && t !== "none") {
spawnFx(x,y,t,pid);
}
}
}
break;
case "line":
var params = thisContent.split(" ");
var x1 = params[0];
var y1 = params[1];
var x2 = params[2];
var y2 = params[3];
var t = params[4];
var pid = Campaign().get("playerpageid");
log(`${x1} ${y1} ${x2} ${y2} ${t} ${pid}`)
if (x1 && y1 && x2 && y2 && t && pid) {
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}`)};
}
if (thisTag.charAt(0) === "]") {
if (thisContent.charAt(0) === "[") {
if (lastBlockAction === "S") {
lastBlockAction = "";
}
if (lastBlockAction === "E") {
var line = lineCounter;
for (line = lineCounter + 1; line<cardLines.length; line++) {
if (getLineTag(cardLines[line],line,"").trim() === "]") {
lineCounter = line;
break;
}
}
if (lineCounter > cardLines.length) {
log(`ScriptCards: Warning - no end block marker found for block started on line ${lineCounter}`);
lineCounter = cardLines.length + 1;
}
lastBlockAction = "";
}
} else {
lastBlockAction = "";
}
}
// 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();
}
}
executionCounter++;
if (executionCounter > cardParameters.executionlimit) {
log("ScriptCards Error: Execution Limit Reached. Terminating Script;")
lineCounter = cardLines.length+1;
}
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);
emote = replaceVariableContent(emote, cardParameters, false);
}
}
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 [];
}
}
function replaceVariableContent(content, cardParameters, rollHilighting) {
var matchCount = 0;
var failCount = 0;
const failLimit = 1000;
var contentIn = content;
var charId = "";
while(content.match(/\[(?:[\$|\&|\@|\%|\*])[\w|À-ÖØ-öø-ÿ|\%|\(|\:|\.|\_|\>|\^|\-|\)]*?(?!\w+[\[])(\])/g) !== null) {
var thisMatch = content.match(/\[(?:[\$|\&|\@|\%|\*])[\w|À-ÖØ-öø-ÿ|\%|\(|\:|\.|\_|\>|\^|\-|\)]*?(?!\w+[\[])(\])/g)[0];
var replacement = "";
matchCount++;
switch (thisMatch.charAt(1)) {
case "&":
// Replace a string variable
var vName = thisMatch.substring(2,thisMatch.length-1);
if (stringVariables[vName] !== undefined) {
replacement = stringVariables[vName];
} else {
replacement = "";
}
if (cardParameters.debug !== "0") {
log(`ContentIn: ${content} Match: ${thisMatch}, vName: ${vName}, replacement ${replacement}`)
}
break;
case "$":
// Replace a roll variable
var vName = thisMatch.match(/(?<=\[\$|\#).*?(?=[\.|\]])/g)[0];
var vSuffix = "Total";
if (thisMatch.match(/(?<=\.).*?(?=[\.|\]])/g) !== null) {
vSuffix = thisMatch.match(/(?<=\.).*?(?=[\.|\]])/g)[0];
}
var replacement = "";
if (rollVariables[vName] !== undefined) {
replacement = vSuffix == "Raw" ? rollVariables[vName]["Total"] : rollVariables[vName][vSuffix]
}
debugOutput(`RollHilighting: ${rollHilighting}, Suffix: ${vSuffix}`);
if (rollHilighting == true && vSuffix == "Total" && rollVariables[vName] !== undefined) {
replacement = buildTooltip(replacement, "Roll: " + rollVariables[vName].RollText + "<br /><br />Result: " + rollVariables[vName].Text, rollVariables[vName].Style);
}
if (cardParameters.debug !== "0") {
log(`ContentIn: ${content} Match: ${thisMatch}, vName: ${vName}, vSuffix: ${vSuffix}, replacement ${replacement}`)
}
break;
case "@":
// Replace Array References
var vName = thisMatch.match(/(?<=\[\$|\@).*?(?=[\(])/g)[0];
var vIndex = 0;
if (thisMatch.match(/(?<=\().*?(?=[)]])/g) !== null