" + "
" + PowerCard.name;
// ADD SUBTITLES...
var Diamond = " &" + "#x2666; ";
var Subtitle = "
";
Subtitle += (PowerCard.leftsub !== undefined) ? PowerCard.leftsub : "";
Subtitle += (PowerCard.leftsub !== undefined && PowerCard.rightsub !== undefined) ? Diamond : "";
Subtitle += (PowerCard.rightsub !== undefined) ? PowerCard.rightsub : "";
// ADD TITLE AND SUBTITLE TO DISPLAY OBJECT...
if (PowerCard.name !== undefined) Display += doInlineFormatting(Title + Subtitle + "
", ALLOW_URLS, ALLOW_HIDDEN_URLS);
else Display += "
";
// CREATE ROW STYLES & OTHER INFO...
var OddRow = "color: " + PowerCard.orowtx + "; background-color: " + PowerCard.orowbg + "; ";
var EvenRow = "color: " + PowerCard.erowtx + "; background-color: " + PowerCard.erowbg + "; ";
var RowBackground = OddRow;
var RowNumber = 1;
var Indent = 0;
// ROW STYLE...
var RowStyle = "" + "line-height: " + PowerCard.lineheight + "; " + "vertical-align: middle; " + "font-family: " + PowerCard.bodyfont + "; " + "font-size: " + PowerCard.bodyfontsize + "; " + "font-weight: normal; font-style: normal; text-align: left; " + "margin 0px; " + "padding: 4px 5px 2px 5px; " + "border-left: " + PowerCard.border + "; " + "border-right: " + PowerCard.border + ";";
// ALT ROW STYLES...
var FirstRowStyle = RowStyle + "border-top: " + PowerCard.border + "; border-radius: " + PowerCard.corners + "px " + PowerCard.corners + "px 0px 0px;";
var LastRowStyle = RowStyle + " border-bottom: " + PowerCard.border + "; border-radius: 0px 0px " + PowerCard.corners + "px " + PowerCard.corners + "px;";
var OneRowStyle = RowStyle + " border: " + PowerCard.border + "; border-radius: " + PowerCard.corners + "px;";
// KEY INFO...
var KeyCount = 0;
var Keys = Object.keys(PowerCard);
// CONDITIONAL STATEMENTS TO REMOVE TAGS FROM KEYS...
KeyCount = 0;
Keys.forEach(function(Tag) {
var Result = "";
var Conditional = "";
var LeftVal = "";
var OP = "";
var RightVal = "";
var Operand = "";
var Success = false;
var OriginalTag = Tag;
while ((Tag != undefined) && (Tag.charAt(0) === "?") && (Tag.charAt(1) === "?")) {
Conditional = Tag.match(/\?\?(.*?)\?\?/g)[0].replace(/\?\?/g, "").trim().split(" ");
while (Operand !== undefined) {
LeftVal = Conditional.shift();
OP = Conditional.shift();
RightVal = Conditional.shift();
// GET LEFT SIDE VALUES...
if (LeftVal !== undefined && LeftVal.match(/\$\[\[/)) {
LeftVal = parseFloat(x[0].inlinerolls[LeftVal.match(/[0-9]+/)].results.total);
} else if (LeftVal !== undefined && LeftVal.charAt(0) === "$") {
LeftVal = LeftVal.split(".");
if (LeftVal[1]) {
if (LeftVal[1] == "ss") LeftVal[1] = "successes";
} else LeftVal[1] = "total";
if (Rolls[LeftVal[0]]) LeftVal = parseFloat(Rolls[LeftVal[0]][LeftVal[1]]);
} else {
if (isFinite(LeftVal) && !isNaN(LeftVal)) {
LeftVal = (parseFloat(LeftVal) || 0);
} else {
// Not a number, left empty for possible future use...
if (LeftVal === "NULL") {
LeftVal = "";
}
if (LeftVal != undefined && LeftVal.charAt(0) === "&") {
var l=LeftVal.split(".");
LeftVal = getAttrByName(l[0].slice(1), l[1]);
}
}
}
// GET RIGHT SIDE VALUES...
if (RightVal !== undefined && RightVal.match(/\$\[\[/)) {
RightVal = parseFloat(x[0].inlinerolls[RightVal.match(/[0-9]+/)].results.total);
} else if (RightVal !== undefined && RightVal.charAt(0) === "$") {
RightVal = RightVal.split(".");
if (RightVal[1]) {
if (RightVal[1] == "ss") RightVal[1] = "successes";
} else RightVal[1] = "total";
if (Rolls[RightVal[0]]) RightVal = parseFloat(Rolls[RightVal[0]][RightVal[1]]);
} else {
if (isFinite(RightVal) && !isNaN(RightVal)) {
RightVal = (parseFloat(RightVal) || 0);
} else {
// Not a number, left empty for possible future use...
if (RightVal === "NULL") {
RightVal = "";
}
if (RightVal != undefined && RightVal.charAt(0) === "&") {
var l=RightVal.split(".");
RightVal = getAttrByName(l[0].slice(1), l[1]);
}
}
}
switch (OP) {
case ">":
Success = (LeftVal > RightVal);
break;
case ">=":
Success = (LeftVal >= RightVal);
break;
case "==":
Success = (LeftVal == RightVal);
break;
case "<=":
Success = (LeftVal <= RightVal);
break;
case "<":
Success = (LeftVal < RightVal);
break;
case "<>":
Success = (LeftVal != RightVal);
break;
case "%":
Success = ((LeftVal % RightVal) == 0);
break;
case "~%":
Success = ((LeftVal % RightVal) != 0);
break;
default:
Success = false;
}
Operand = Conditional.shift();
if (Operand !== undefined) {
if (Operand.toLowerCase() === "and" && Success === false) break;
if (Operand.toLowerCase() === "or" && Success === true) break;
}
}
if (Success) Tag = Tag.replace(/\?\?(.*?)\?\?/, "").trim();
else Tag = Tag.replace(/\?\?(.*?)\?\?/, "$").trim();
//log(Tag);
}
PowerCard[Tag] = PowerCard[OriginalTag];
Keys[KeyCount] = Tag;
KeyCount++;
});
// Check for Eval-style conditionals
KeyCount = 0;
Keys.forEach(function(Tag) {
var Conditional = "";
var Success = false;
var OriginalTag = Tag;
while ((Tag != undefined) && (Tag.charAt(0) === "?") && (Tag.charAt(1) === "!")) {
Conditional = Tag.match(/\?\!(.*?)\!\?/g)[0].replace(/\?\!/g, "").replace(/\!\?/g, "").trim();
var parts = Conditional.split(" ");
for (var p = 0; p < parts.length; p++ ) {
if (parts[p] !== undefined && parts[p].match(/\$\[\[/)) {
parts[p] = parseFloat(x[0].inlinerolls[parts[p].match(/[0-9]+/)].results.total);
} else {
if (parts[p] !== undefined && parts[p].charAt(0) === "$") {
var suffix = parts[p].split(".");
if (suffix[1]) {
if (suffix[1] == "ss") {
parts[p] = parts[p].replace(/\.\s\s/g,".successes");
}
} else {
parts[p] += ".total";
}
if (Rolls[parts[p].split(".")[0]]) {
parts[p] = parseFloat(Rolls[parts[p].split(".")[0]][parts[p].split(".")[1]]);
}
}
}
}
Conditional = parts.join(' ');
try {
if (eval(sanitize(Conditional)) === false) {
Success = false;
} else {
Success = true;
}
} catch (e) { }
if (Success) {
Tag = Tag.replace(/\?\!(.*?)\!\?/, "").trim();
} else {
Tag = Tag.replace(/\?\!(.*?)\!\?/, "$").trim();
}
//log(Tag);
}
PowerCard[Tag] = PowerCard[OriginalTag];
Keys[KeyCount] = Tag;
KeyCount++;
//log(PowerCard[Tag]);
});
// REMOVE IGNORED TAGS...
var IgnoredTags = ["charid", "tokenid", "emote", "leftsub", "rightsub", "name", "txcolor", "bgcolor", "erowbg", "erowtx", "orowbg", "orowtx", "whisper", "format", "template", "title", "target_list", "titlefont", "subtitlefont", "bodyfont", "corners", "titlefontsize", "subtitlefontsize", "bodyfontsize", "border", "boxshadow", "titlefontvariant", "subtitlefontvariant", "titlefontshadow", "titlebackground", "lineheight", "emotefont"];
IgnoredTags.forEach(function(Tag) {
if (Keys.indexOf(Tag) !== -1) Keys.splice(Keys.indexOf(Tag), 1);
});
// LOOP THROUGH SOUNDFX & ALTERBAR TAGS
var NewKeys = [];
var timeout = 0;
var skipEffect = false;
Keys.forEach(function(Tag) {
if (Tag.substring(0,7) == "soundfx" || Tag.substring(0,8) == "alterbar" || Tag.substring(0,7) == "vfx_opt" || Tag.substring(0,4) == "api_" || Tag.substring(0,9) == "audioattr" || Tag.substring(0,7) == "vfxattr" ) {
//if (Tag.substring(0,7) == "soundfx") sendChat("", "!roll20AM " + PowerCard[Tag].replace(/(?:\s+|\b)_/g, " --").replace(/--play/,"--audio,play"));
if (Tag.substring(0,7) == "soundfx") sendChat("", "!roll20AM " + PowerCard[Tag].replace(/(?:\s+|\b)_/g, " --"));
// As an alternative to the --soundfx tag, --audioattr will allow you to specify a token_id and an attribute name.
// If the specified token has the specified attribute, it's value will be passed as a command line to Roll20 Audio Master
if (Tag.substring(0,9) == "audioattr") {
var audiocmd = (PowerCard[Tag].replace(/ +(?= )/g,'') + " ").split(" ");
if (audiocmd.length == 3) {
var audioToken = getObj("graphic", audiocmd[0]);
if (audioToken !== undefined) {
var audioChar = getObj("character", audioToken.get("represents"));
if (findObjs({ type: 'attribute', characterid: audioChar.id, name: audiocmd[1] }).length > 0 ) {
var audioParams = getAttrByName(audioChar.id, audiocmd[1]);
//sendChat("", "!roll20AM " + audioParams.replace(/--play/,"--audio,play"));
sendChat("", "!roll20AM " + audioParams);
}
}
}
}
if (Tag.substring(0,7) == "vfxattr") {
// Start off assuming the attribute isn't going to be present on the character and that we won'table
// actually be displaying a visual effect
skipEffect = true;
var vfxatcmd = (PowerCard[Tag].replace(/ +(?= )/g,'') + " ").split(" ");
if (vfxatcmd.length == 3) {
var vfxToken = getObj("graphic", vfxatcmd[0]);
if (vfxToken !== undefined) {
var vfxChar = getObj("character", vfxToken.get("represents"));
if (findObjs({ type: 'attribute', characterid: vfxChar.id, name: vfxatcmd[1] }).length > 0 ) {
// The attribute exists, so get its value and split it into parts. Because we can't decode things
// like @{selected|token_id} in the API (that happens in the chat system before it gets to us)
// we use a special code where "s" is selected and "t" is target. Neither, one, or both can be
// specified, and the order determines where a point-to-point effect starts and ends. The final
// parameter is the effect descriptor.
var vfxParams = (getAttrByName(vfxChar.id, vfxatcmd[1]).replace(/ +(?= )/g,'') + " ").split(" ");
var p_from = "";
var p_to = "";
var fx = vfxParams[vfxParams.length-2];
if (vfxParams[0].toLowerCase() == "s" && PowerCard.tokenid !== undefined) { p_from = PowerCard.tokenid + " "; }
if (vfxParams[0].toLowerCase() == "t" && TargetToken !== undefined) { p_from = TargetToken.id + " "; }
if (vfxParams[1].toLowerCase() == "s" && PowerCard.tokenid !== undefined) { p_to = PowerCard.tokenid + " "; }
if (vfxParams[1].toLowerCase() == "t" && TargetToken !== undefined) { p_to = TargetToken.id + " "; }
// We update the content of the tag and let the existing code for vfx_opt handle the effect.
PowerCard[Tag] = p_from + p_to + fx;
skipEffect = false;
}
}
}
}
if (Tag.substring(0,7) == "vfx_opt" || Tag.substring(0,7) == "vfxattr") {
var vfxcmd = (PowerCard[Tag].replace(/ +(?= )/g,'') + " ").split(" ");
if (!skipEffect) {
var vfxEffect = 0
// Determine effect properties based on the number of parameters passed
if (vfxcmd.length == 2) { Token = getObj("graphic", PowerCard.tokenid); vfxEffect = 0; }
if (vfxcmd.length >= 3 ) { Token = getObj("graphic", vfxcmd[0]); vfxEffect = 1; }
if (vfxcmd.length == 4) { TargetToken = getObj("graphic", vfxcmd[1]); vfxEffect = 2; }
// Create the effect. If the descriptor is parameter 0 or 1, this is a single target effect.
if (vfxEffect <= 1) {
if (Token !== undefined) {
var effectInfo = findObjs({_type:"custfx", name:vfxcmd[vfxEffect]});
if (!_.isEmpty(effectInfo)) {
spawnFxWithDefinition(Token.get('left'), Token.get('top'), effectInfo[0].get('definition'), Campaign().get('playerpageid'));
} else {
spawnFx(Token.get('left'), Token.get('top'), vfxcmd[vfxEffect], Campaign().get('playerpageid'));
}
}
// Otherwise, it is a point-to-point effect, so use that function.
} else {
if (Token !== undefined && TargetToken !== undefined) {
// Check to see if this a custom effect that exists in the campaign and use it if so.
var effectInfo = findObjs({_type:"custfx", name:vfxcmd[vfxEffect]});
if (!_.isEmpty(effectInfo)) {
var angleDeg = Math.atan2(TargetToken.get('top') - Token.get('top'), TargetToken.get('left') - Token.get('left')) * 180 / Math.PI;
if (angleDeg < 0) { angleDeg += 360; }
var definition = effectInfo[0].get('definition');
definition.angle = angleDeg;
spawnFxWithDefinition(Token.get('left'), Token.get('top'), effectInfo[0].get('definition'), Campaign().get('playerpageid'));
} else {
spawnFxBetweenPoints({x:Token.get('left'), y:Token.get('top')}, {x:TargetToken.get('left'), y:TargetToken.get('top')}, vfxcmd[vfxEffect], Campaign().get('playerpageid'));
}
}
}
}
}
if (Tag.substring(0,8) == "alterbar") {
PowerCard[Tag] = doInlineFormatting(PowerCard[Tag], ALLOW_URLS, ALLOW_HIDDEN_URLS, Rolls);
setTimeout( function() { sendChat(msg.who, "!alter " + PowerCard[Tag].replace(/(?:\s+|\b)_/g, " --")) }, timeout);
timeout = timeout + 250;
}
// Generic API tags. Usage is --api_command|parameter1 parameter2 etc. where command is the api command (leave off the ! at the beginning).
// Parameters are separated by spaces
if (Tag.substring(0,4) == "api_") {
// Support multi-tag (*1, *2, etc.) by only taking up to a space or an asterisk as the command
var apicmd = Tag.substring(4);
if (apicmd.indexOf(" ") !== -1) {
apicmd = apicmd.substring(0,apicmd.indexOf(" "));
}
if (apicmd.indexOf("*") !== -1) {
apicmd = apicmd.substring(0,apicmd.indexOf("*"));
}
// Some macros check their number of parameters, and if they expect 0 and we include a space after the API command they
// can get confused. We assume there will be a space, but if the "text" of the tag is empty, we eliminate the space for
// the purposes of the API call.
var spacer = " ";
if (PowerCard[Tag].replace(/(?:\s+|\b)_/g, " --").length == 0) {
spacer = "";
}
PowerCard[Tag] = doInlineFormatting(PowerCard[Tag],ALLOW_URLS, ALLOW_HIDDEN_URLS, Rolls);
setTimeout( function () { sendChat(msg.who, "!" + apicmd + spacer + PowerCard[Tag].replace(/(?:\s+|\b)_/g, " --")) }, timeout);
timeout = timeout + 250;
}
} else {
if (Tag != undefined && Tag.charAt(0) !== "$" && Tag !== "hroll" && Tag !== "hrolls") NewKeys.push(Tag);
}
});
Keys = NewKeys;
// LOOP THROUGH REMAINING KEYS TO CREATE ROW DIVS FROM POWERCARD OBJECT...
KeyCount = 0;
Keys.forEach(function(Tag) {
KeyCount++;
Content = doInlineFormatting(PowerCard[Tag], ALLOW_URLS, ALLOW_HIDDEN_URLS, Rolls);
RowBackground = (RowNumber % 2 == 1) ? OddRow : EvenRow;
if (PowerCard.name === undefined) {
if (Keys.length !== 1 && KeyCount === 1) RowBackground += FirstRowStyle;
else if (Keys.length !== 1 && KeyCount === Keys.length) RowBackground += LastRowStyle;
else if (Keys.length === 1) RowBackground += OneRowStyle;
else RowBackground += RowStyle;
} else {
if (KeyCount === Keys.length) RowBackground += LastRowStyle;
else RowBackground += RowStyle;
}
if (Content.indexOf("$[[") === -1) RowBackground = RowBackground.replace("padding: 4px 5px 2px 5px", "padding: 4px 5px 3px 5px");
RowNumber += 1;
Tag = Tag.replace(/( #[0-9]+)/g, ""); // Hides multitag numbers...
Tag = Tag.replace(/( \*[0-9]+)/g, ""); // Hides same name tag numbers...
if (Tag != undefined && Tag.charAt(0) !== "!") {
if (Tag != undefined && Tag.charAt(0) === "^") {
Indent = (parseInt(Tag.charAt(1)) > 0) ? " padding-left: " + (Tag.charAt(1) * 1.5) + "em;" : "";
Tag = (parseInt(Tag.charAt(1)) >= 0) ? Tag.substring(2) : Tag.substring(1);
Display += "
" + Tag + " " + Content + "
";
} else {
Display += "
" + Tag + " " + Content + "
";
}
} else {
if (Tag != undefined && Tag.charAt(1) === "^") {
Indent = (parseInt(Tag.charAt(2)) > 0) ? " padding-left: " + (Tag.charAt(2) * 1.5) + "em;" : "";
Display += "
" + Content + "
";
} else {
Display += "
" + Content + "
";
}
}
});
// CLOSE SHADOWBOX DIV...
Display += "
";
// REPLACE INLINE ROLLS WITH VALUES
if (x[0].inlinerolls !== undefined) {
// SAVE TOKEN OR CHARACTER ID FOR USE WITH TRKR ROLL OPTION...
var TrackerID = "-1";
TrackerID = (PowerCard.charid !== undefined) ? "C|" + PowerCard.charid : TrackerID;
TrackerID = (PowerCard.tokenid !== undefined) ? "T|" + PowerCard.tokenid : TrackerID;
var RollExpression = "";
var RollValue = 0;
var i = 1;
Object.keys(x[0].inlinerolls).forEach(function(i) {
RollValue = x[0].inlinerolls[i];
if (PowerCard.emote && PowerCard.emote.indexOf("$[[" + i + "]]") !== -1) PowerCard.emote = PowerCard.emote.replace("$[[" + i + "]]", buildInline(RollValue, TrackerID, msg.who));
if (Display.indexOf("$[[" + i + "]]") !== -1) Display = Display.replace("$[[" + i + "]]", buildInline(RollValue, TrackerID, msg.who));
});
}
// WHISPER
if (PowerCard.whisper !== undefined) {
if (PowerCard.emote !== undefined) {
if (PowerCard.charid !== undefined || PowerCard.tokenid !== undefined) {
safeSendChat("", "/desc " + PowerCard.emote)
} else {
safeSendChat("", '/emas " " ' + PowerCard.emote);
}
}
_.each(PowerCard.whisper.split(","), function(y) {
var WhisperTarget = ('self' === y.trim() ? msg.who : y.trim());
safeSendChat(msg.who, "/w " + WhisperTarget + " " + Display);
});
} else {
if (PowerCard.emote !== undefined) {
if (PowerCard.charid !== undefined || PowerCard.tokenid !== undefined) {
safeSendChat("", "/desc " + PowerCard.emote + Display);
} else {
safeSendChat("", '/emas " " ' + PowerCard.emote);
safeSendChat("", "/desc " + Display);
}
} else {
safeSendChat("", "/desc " + Display);
}
}
});
};
// FUNCTIONS ///////////////////////////////////////////////////////////////////
function buildInline(inlineroll, TrackerID, who) {
var InlineColorOverride = "";
var values = [];
var critRoll = false;
var failRoll = false;
var critCheck = false;
var failCheck = false;
var expandedCheck = false;
var highRoll = false;
var lowRoll = false;
var noHighlight = false;
var expandedRoll = false;
var expandedRollReversed = false;
var notInline = false;
var addToTracker = false;
inlineroll.results.rolls.forEach(function(roll) {
var result = processRoll(roll, noHighlight, expandedRoll, expandedRollReversed, critCheck, failCheck, notInline);
if (result["critCheck"]) critCheck = true;
if (result["failCheck"]) failCheck = true;
if (result["noHighlight"]) noHighlight = true;
if (result["expandedRoll"]) expandedRoll = true;
if (result["expandedRollReversed"]) expandedRollReversed = true;
if (result["notInline"]) notInline = true;
if (result["addToTracker"]) {
// ADD TOKEN OR CHARACTER OR DISPLAY NAME TO TURN ORDER TRACKER...
var TrackerName = "";
if (TrackerID != undefined && TrackerID.charAt(0) === "C") {
var Char = getObj("character", TrackerID.substring(2));
var Tok = findObjs({
type: 'graphic',
pageid: Campaign().get("playerpageid"),
represents: TrackerID.substring(2)
});
if (_.isEmpty(Tok) && Char !== undefined) TrackerName = Char.get("name");
else if (_.isEmpty(Tok)) { TrackerID=undefined; } else { TrackerID = Tok[0].id; }
} else if (TrackerID != undefined && TrackerID.charAt(0) === "T") TrackerID = TrackerID.substring(2);
else TrackerName = who;
// CHECK TURN ORDER FOR EXISTING ID... REPLACE PR VALUE IF FOUND...
var turn_order = ("" === Campaign().get("turnorder")) ? [] : JSON.parse(Campaign().get("turnorder"));
var pos = turn_order.map(function(z) {
return z.id;
}).indexOf(TrackerID);
if (pos === -1) turn_order.push({
id: TrackerID,
pr: inlineroll.results.total,
custom: TrackerName
});
else turn_order[pos]["pr"] = inlineroll.results.total;
// OPEN THE INITIATIVE WINDOW IF IT'S CLOSED...
if (!Campaign().get("initiativepage")) Campaign().set("initiativepage", true);
// SEND TURN ORDER BACK TO THE CAMPAIGN() OBJECT...
Campaign().set("turnorder", JSON.stringify(turn_order));
}
if (result.value !== "") values.push(result.value);
});
// OVERRIDE THE ROLL20 INLINE ROLL COLORS...
if (critCheck && failCheck) InlineColorOverride = INLINE_ROLL_CRIT_BOTH;
else if (critCheck && !failCheck) InlineColorOverride = INLINE_ROLL_CRIT_HIGH;
else if (!critCheck && failCheck) InlineColorOverride = INLINE_ROLL_CRIT_LOW;
else InlineColorOverride = INLINE_ROLL_DEFAULT;
// PARSE TABLE RESULTS
inlineroll.results.tableentries = _.chain(inlineroll.results.rolls)
.filter(function(r) {
var tbl = _.has(r, 'table');
return _.has(r, 'table');
})
.reduce(function(memo, r) {
_.each(r.results, function(i) {
i = i.tableItem;
if (!/^[+\-]?(0|[1-9][0-9]*)([.]+[0-9]*)?([eE][+\-]?[0-9]+)?$/.test(i.name)) {
memo.push({
name: i.name,
weight: i.weight,
table: r.table
});
}
});
return memo;
}, [])
.value();
// REMOVE ROLL OPTIONS LIKE NH, XPND, EMPTY BRACKETS, & ADD SPACING...
inlineroll.expression = inlineroll.expression
.replace(/\|nh|nh/, "")
.replace(/\|rpnd|rpnd/i, "")
.replace(/\|xpnd|xpnd/i, "")
.replace(/\|trkr|trkr/i, "")
.replace(/\[\]/, "")
.replace("<", "&" + "amp;" + "lt;")
.replace(/\+/g, " + ")
.replace(/\-/g, " - ")
.replace(/\*/g, " * ")
.replace(/\//g, " / ");
// END ROLL OPTIONS
// FINAL STEP...
var rollOut = "";
if (expandedRoll) {
if (notInline) {
rollOut = values.join("") + " = " + inlineroll.results.total;
} else {
rollOut = '
' + values.join("") + ' = ' + inlineroll.results.total + '';
}
} else if (expandedRollReversed) {
if (notInline) {
rollOut = inlineroll.results.total + " = " + valus.join("");
} else {
rollOut = '
' + inlineroll.results.total + ' = ' + values.join("") + '';
}
} else {
if (notInline) {
rollOut = inlineroll.results.total;
} else {
rollOut = '
' + inlineroll.results.total + '';
}
}
rollOut = (inlineroll.results.tableentries.length) ? '' : rollOut;
rollOut += _.map(inlineroll.results.tableentries, function(l) {
return (notInline) ? l.name : '
' + l.name + '';
}).join('');
return rollOut;
};
function processRoll(roll, noHighlight, expandedRoll, expandedRollReversed, critCheck, failCheck, notInline, addToTracker) {
var critRoll = false;
var failRoll = false;
if (roll.type === "C") {
return {
value: " " + roll.text + " "
};
} else if (roll.type === "L") {
if (roll.text.match(/nh/i) !== null) noHighlight = true;
if (roll.text.match(/xpnd/i) !== null) expandedRoll = true;
if (roll.text.match(/rpnd/i) !== null) expandedRollReversed = true;
if (roll.text.match(/txt/i) !== null) notInline = true;
if (roll.text.match(/trkr/i) !== null) addToTracker = true;
return {
noHighlight: noHighlight,
expandedRoll: expandedRoll,
expandedRollReversed: expandedRollReversed,
notInline: notInline,
addToTracker: addToTracker
};
} else if (roll.type === "M") {
if (roll.expr.toString().match(/\+|\-|\*|\\/g)) roll.expr = roll.expr.toString().replace(/\+/g, " + ").replace(/\-/g, " - ").replace(/\*/g, " * ").replace(/\//g, " / ");
return {
value: roll.expr
};
} else if (roll.type === "R") {
var rollValues = [];
_.each(roll.results, function(result) {
if (result.tableItem !== undefined) {
rollValues.push(result.tableItem.name);
} else {
critRoll = false;
failRoll = false;
if (noHighlight) {
critRoll = false;
failRoll = false;
} else {
var Sides = roll.sides;
// CRITROLL CHECKS...
if (roll.mods && roll.mods["customCrit"]) {
var p = 0;
_.each(roll.mods["customCrit"], function() {
if (roll.mods["customCrit"][p]["comp"] === "<=" && result.v <= roll.mods["customCrit"][p]["point"]) critRoll = true;
if (roll.mods["customCrit"][p]["comp"] === "==" && result.v == roll.mods["customCrit"][p]["point"]) critRoll = true;
if (roll.mods["customCrit"][p]["comp"] === ">=" && result.v >= roll.mods["customCrit"][p]["point"]) critRoll = true;
p++;
});
} else {
if (result.v === Sides) critRoll = true;
}
// FAILROLL CHECKS...
if (roll.mods && roll.mods["customFumble"]) {
var p = 0;
_.each(roll.mods["customFumble"], function() {
if (roll.mods["customFumble"][p]["comp"] === "<=" && result.v <= roll.mods["customFumble"][p]["point"]) failRoll = true;
if (roll.mods["customFumble"][p]["comp"] === "==" && result.v == roll.mods["customFumble"][p]["point"]) failRoll = true;
if (roll.mods["customFumble"][p]["comp"] === ">=" && result.v >= roll.mods["customFumble"][p]["point"]) failRoll = true;
p++;
});
} else {
if (result.v === 1) failRoll = true;
}
}
if (expandedRoll) result.v = "
" + result.v + "";
else result.v = "
" + result.v + "";
rollValues.push(result.v);
if (critRoll) critCheck = true;
if (failRoll) failCheck = true;
}
});
return {
value: "(" + rollValues.join(" + ") + ")",
noHighlight: noHighlight,
expandedRoll: expandedRoll,
expandedRollReversed: expandedRollReversed,
critCheck: critCheck,
failCheck: failCheck,
notInline: notInline,
addToTracker: addToTracker
};
} else if (roll.type === "G") {
var grollVal = [];
_.each(roll.rolls, function(groll) {
_.each(groll, function(groll2) {
var result = processRoll(groll2, noHighlight, expandedRoll, expandedRollReversed, critCheck, failCheck, notInline);
grollVal.push(result.value);
critCheck = critCheck || result.critCheck;
failCheck = failCheck || result.failCheck;
noHighlight = noHighlight || result.noHighlight;
expandedRoll = expandedRoll || result.expandedRoll;
expandedRollReversed = expandedRollReversed || result.expandedRollReversed;
notInline = notInline || result.notInline;
addToTracker = addToTracker || result.addToTracker;
});
});
return {
value: "{" + grollVal.join(" ") + "}",
noHighlight: noHighlight,
expandedRoll: expandedRoll,
expandedRollReversed: expandedRollReversed,
critCheck: critCheck,
failCheck: failCheck,
notInline: notInline,
addToTracker: addToTracker
};
}
};
function statusSymbol(symbol, altForReplace) {
var icon;
var info;
var statuses = [
'red', 'blue', 'green', 'brown', 'purple', 'pink', 'yellow', // 0-6
'skull', 'sleepy', 'half-heart', 'half-haze', 'interdiction',
'snail', 'lightning-helix', 'spanner', 'chained-heart',
'chemical-bolt', 'death-zone', 'drink-me', 'edge-crack',
'ninja-mask', 'stopwatch', 'fishing-net', 'overdrive', 'strong',
'fist', 'padlock', 'three-leaves', 'fluffy-wing', 'pummeled',
'tread', 'arrowed', 'aura', 'back-pain', 'black-flag',
'bleeding-eye', 'bolt-shield', 'broken-heart', 'cobweb',
'broken-shield', 'flying-flag', 'radioactive', 'trophy',
'broken-skull', 'frozen-orb', 'rolling-bomb', 'white-tower',
'grab', 'screaming', 'grenade', 'sentry-gun', 'all-for-one',
'angel-outfit', 'archery-target'
],
statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75'],
i;
symbol = altForReplace || symbol;
if (!_.contains(statuses, symbol)) {
if (state.PowerCard_StatusList[symbol] !== undefined) {
icon = state.PowerCard_StatusList[symbol].icon;
info = state.PowerCard_StatusList[symbol].info;
}
}
if (_.contains(statuses, icon)){
i=_.indexOf(statuses, icon);
if (i < 7) {
return '
' + info;
}
return '
' + info;
}
return "";
};
function doInlineFormatting(content, ALLOW_URLS, ALLOW_HIDDEN_URLS, Rolls) {
// REPLACE [^ID] with value...
var RollIDs = content.match(/\[\^(.*?)\]/g);
if (RollIDs) {
var rID;
var rOpt;
var rCount = 0;
_.each(RollIDs, function(r) {
rCount++;
rID = r.split(".")[0].split("[^")[1].replace("]", "");
rOpt = (r.split(".")[1] !== undefined) ? r.split(".")[1].replace("]", "") : "total";
if (Rolls["$" + rID]) {
switch (rOpt) {
case "base":
content = content.replace(r, Rolls["$" + rID].base);
break;
case "total":
content = content.replace(r, Rolls["$" + rID].total);
break;
case "ss":
content = content.replace(r, Rolls["$" + rID].successes);
break;
case "ones":
content = content.replace(r, Rolls["$" + rID].ones);
break;
case "tens":
content = content.replace(r, Rolls["$" + rID].tens);
break;
}
} else {
content = content.replace(r, " > ROLL ID NOT FOUND < ");
}
});
}
// PARSE FOR INLINE FORMATTING
var urls = [],
str,
formatter = function(s) {
return s
.replace(/\[\+(.*?)\]/g, statusSymbol)
.replace(/__(.*?)__/g, "
$1")
.replace(/\*\*(.*?)\*\*/g, "
$1")
.replace(/\/\/(.*?)\/\//g, "
$1")
.replace(/\^\^/g, "
")
.replace(/\^\*/g, "
")
.replace(/\$\$(#([a-fA-F0-9]{3}|[a-fA-F0-9]{6}))\|(.*?)\$\$/g, "
$3")
.replace(/\~\~\~/g, "
")
.replace(/\~\J(.*?)\~\J/g, "
$1
")
.replace(/\~\L(.*?)\~\L/g, "
$1")
.replace(/\~\C(.*?)\~\C/g, "
$1
")
.replace(/\~\R(.*?)\~\R/g, "
$1
")
.replace(/\[\!(.*?)\!\]/g, "
$1")
.replace(/\[\TTB(.*?)\]/g, "
")
.replace(/\[\TTE\]/g, "
")
.replace(/\[\TRB(.*?)\]/g, "
")
.replace(/\[\TRE\]/g, "
")
.replace(/\[\TDB(.*?)\]/g, "
")
.replace(/\[\TDE\]/g, " | ")
.replace(/\&\&/g, "")
;
};
str = _.reduce(
content.match(/@@.*?@@/g),
function(m, s, i) {
var parts = s.replace(/@@(.*)@@/, '$1').split(/\|\|/),
url = parts.shift().replace(/^\s*(http(s)?:\/\/|\/\/()|())/, 'http$2://'),
text = formatter(parts.join('||'));
if (ALLOW_URLS) {
if (ALLOW_HIDDEN_URLS) {
urls[i] = '
' + (text || url) + '';
} else {
urls[i] = '
' + text + ' [' + url + ']';
}
} else {
urls[i] = s;
}
return m.replace(s, '@@' + i + '@@');
},
content
);
str = formatter(str);
return _.reduce(
urls,
function(m, s, i) {
return m.replace('@@' + i + '@@', s);
},
str
);
};
function getBrightness(hex) {
hex = hex.replace('#', '');
var c_r = getHex2Dec(hex.substr(0, 2));
var c_g = getHex2Dec(hex.substr(2, 2));
var c_b = getHex2Dec(hex.substr(4, 2));
return ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
};
function getAttrRefValues(char_id, attribute) {
var n_ref = 0;
var attr = attribute;
if (/.*@{.*/.test(attribute)) {
n_ref = attribute.match(/@{/g).length;
}
if (n_ref > 0) { //there are references to attributes
for (var i = 0; i < n_ref; i++) {
attr = attr.replace(/@{(.*?)}/, function(e) {
var attr_ref = e.replace(/@{/, '').replace(/}/, '');
attr_ref = getAttrByName(char_id, attr_ref);
return getAttrRefValues(char_id, attr_ref);
});
}
}
return attr;
};
function getCurrentTime() {
var d = new Date();
var h = ((d.getHours() + 1) < 10 ? "0" : "") + (d.getHours() + 1);
var m = (d.getMinutes() < 10 ? "0" : "") + d.getMinutes();
var currentTime = h + ":" + m;
return currentTime;
};
function getHex2Dec(hex_string) {
hex_string = (hex_string + '').replace(/[^a-f0-9]/gi, '');
return parseInt(hex_string, 16);
};
function getOnlinePlayerNames() {
var PlayerDisplayNames = [];
var Players = findObjs({_type: "player"});
_.each(Players, function (p) {
if (p.get("online")) PlayerDisplayNames.push(p.get("displayname"));
});
return PlayerDisplayNames.join();
};
// getPowerCardHandouts - Retrieves all of the handouts in the game, and looks for any with names starting
// with "PowerCard Formats", "PowerCard Templates", or "PowerCard Replacements". Matching handouts are then
// processed to extract their data and save the results into the game state for use in cards.
function getPowerCardHandouts() {
var HandoutBase = findObjs({
_type: "handout",
});
var PowerCard_Templates = {};
var PowerCard_Formats = {};
var PowerCard_Replacements = {};
HandoutBase.forEach(function(handout) {
// Debugging info :)
// handout.get("notes", function(notes) {
// log(notes.replace(/\
/g,"").replace(/\<\/p\>/g,"
"));
// });
if (handout.get("name").startsWith("PowerCard Templates")) {
var TemplateName = "";
var TemplateContent = "";
handout.get("notes", function(notes) {
notes = notes.replace(/\
/g,"").replace(/\<\/p\>/g,"
").split("
");
notes.forEach(function(notes) {
TemplateName = notes.substring(0, notes.indexOf(":")).replace(/ /g,"").trim();
TemplateContent = notes.substring(notes.indexOf(":") + 1).trim();
TemplateContent = TemplateContent.replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&").replace(/ /g," ").trim();
if (TemplateName !== "" && TemplateContent !== "") PowerCard_Templates[TemplateName] = " " + TemplateContent;
});
});
//log("PowerCards: Processed Template Handout named " + handout.get("name"));
}
if (handout.get("name").startsWith("PowerCard Replacements")) {
var ReplacementName = "";
var ReplacementContent = "";
handout.get("notes", function(notes) {
notes = notes.replace(/\
/g,"").replace(/\<\/p\>/g,"
").split("
");
notes.forEach(function(notes) {
ReplacementName = notes.substring(0, notes.indexOf(":")).replace(/ /g,"").trim();
ReplacementContent = notes.substring(notes.indexOf(":") + 1).trim();
ReplacementContent = ReplacementContent.replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&").replace(/ /g,"").trim();
if (ReplacementName !== "" && ReplacementContent !== "") {
PowerCard_Replacements[ReplacementName] = " " + ReplacementContent;
}
});
});
//log("PowerCards: Processed Replacements Handout named " + handout.get("name"));
}
if (handout.get("name").startsWith("PowerCard Formats")) {
var FormatName = "";
var FormatContent = "";
handout.get("notes", function(notes) {
notes = notes.replace(/\
/g,"").replace(/\<\/p\>/g,"
").split("
");
notes.forEach(function(notes) {
FormatName = notes.substring(0, notes.indexOf(":")).replace(/<(?:.|\n)*?>/g,"").replace(/ /g,"").trim();
FormatContent = notes.substring(notes.indexOf(":") + 1).replace(/<(?:.|\n)*?>/g,"").replace(/ /g,"").trim();
if (FormatName !== "" && FormatContent !== "") PowerCard_Formats[FormatName] = " " + FormatContent;
});
});
//log("PowerCards: Processed Formats Handout named " + handout.get("name"));
}
});
state.PowerCard_Templates = PowerCard_Templates;
state.PowerCard_Replacements = PowerCard_Replacements;
state.PowerCard_Formats = PowerCard_Formats;
};
// Retrieves the contents of a handout named "PowerCard Status List" and parses the notes field
// to create a table of token status markers and their meanings/descriptions.
function getPowerCardStatusList() {
var PowerCard_FormatStatusList = findObjs({
_type: "handout",
name: "PowerCard Status List"
})[0] || findObjs({
_type: "handout",
name: "PowerCards Status List"
})[0];
if (PowerCard_FormatStatusList !== undefined) {
var PowerCard_StatusList = {};
var Status;
var StatusName = "";
var StatusIcon = "";
var StatusInfo = "";
PowerCard_FormatStatusList.get("notes", function(notes) {
notes = notes.split("
");
notes.forEach(function(notes) {
Status = notes.split("|");
if (!_.isEmpty(Status)) {
StatusName = Status[0];
StatusIcon = Status[1];
StatusInfo = Status[2];
if (StatusName !== "" && StatusName !== undefined) {
PowerCard_StatusList[StatusName] = {
"icon": StatusIcon || "",
"info": StatusInfo || ""
}
}
}
});
state.PowerCard_StatusList = PowerCard_StatusList;
});
}
};
function getTargetInfo(content, TargetList) {
// PARSE FOR TARGET INFO REPLACEMENT CHARMS
var Token = getObj("graphic", TargetList[0]);
if (Token === undefined) return content;
var Character = getObj("character", Token.get("represents"));
// TOKEN CHARMS
return content.replace(/%%(.*?)%%/g, function(m, charm) {
var attr;
switch (charm) {
case 'token_name':
return Token.get('name');
case 'bar1':
case 'bar2':
case 'bar3':
return Token.get(charm + '_value');
case 'bar1_max':
case 'bar2_max':
case 'bar3_max':
return Token.get(charm);
default:
attr = getAttrByName(Character.id, charm);
attr = getAttrRefValues(Character.id, attr);
return (Character && attr) || 'ERROR';
}
});
};
// Instead of throwing an error or returning undefined, return an empty string if
// the attribute doesn't exist on the character.
function getAttrOrEmptyString(characterID, attributeName) {
var o = findObjs({ type: 'attribute', characterid: characterID, name: attributeName})
if (!_.isEmpty(o)) {
return getAttrByName(characterID, attributeName);
} else {
return "";
}
}
// If "testvalue" is not undefined or an empty string, will return the result of
// adding the prefix, the testvalue, and the postfix together. Otherwise, the
// value of alternative is returned.
function outputIfNotEmpty(prefix, testvalue, postfix, alternative) {
if (testvalue !== undefined && testvalue !== "") {
return prefix + testvalue + postfix;
} else {
return alternative;
}
}
// Check the "character_sheet" attribute to determine which sheet is being used.
// Based on this information, map the 5E OGL sheet attribute names to the equivalent
// attributes for that sheet. Note that we assume the sheet in use is 5E OGL
function getSheetSpecificAttrName(characterID, OGLName) {
var sheet="5E OGL";
var attr = getAttrOrEmptyString(characterID, "character_sheet");
if (attr == "" ) {
return OGLName;
} else {
if (attr.substr(0,7) == "Shaped ") {
switch (OGLName) {
case "npc_ac": return "AC";
case "npc_challenge": return "challenge";
case "lvl" : return "spell_slots_level_";
case "_slots_total" : return "";
case "_slots_expended" : return "_remaining";
case "npc_senses" : return "senses_string";
case "npc_immunities" : return "damage_immunities";
case "npc_acrobatics_base" : return "acrobatics";
case "npc_animal_handling_base": return "animalhandling";
case "npc_arcana_base": return "arcana";
case "npc_athletics_base": return "athletics";
case "npc_deception_base": return "deception";
case "npc_history_base": return "history";
case "npc_insight_base": return "insight";
case "npc_intimidation_base": return "intimidation";
case "npc_investigation_base": return "investigation";
case "npc_medicine_base": return "medicine";
case "npc_nature_base": return "nature";
case "npc_perception_base": return "perception";
case "npc_performance_base": return "performance";
case "npc_persuasion_base": return "persuasion";
case "npc_religion_base": return "religion";
case "npc_sleight_of_hand_base": return "sleightofhand";
case "npc_stealth_base": return "stealth";
case "npc_survival_base": return "survival";
default: return OGLName;
}
}
}
}
// Uses getSheetSpecificAttrName to retrieve a character sheet attribute independant of the
// sheet being used by specifying he 5E OGL name. If the attribute doesn't exist, returns an
// empty string.
function getSheetAttr(characterID, attrName) {
if (!attrName.substr(0,10) == "repeating_") {
return getAttrOrEmptyString(characterID, getSheetSpecificAttrName(characterID, attrName));
} else {
return getAttrByName(characterID, getSheetSpecificAttrName(characterID, attrName)) || "";
}
}
function getRepeatingCount(characterID, prefix, postfix) {
var attrs = findObjs({ type: 'attribute', characterid: characterID });
var attrCount = 0;
attrs.forEach(function (s) { if (s.get("name").startsWith(prefix) && s.get("name").endsWith(postfix)) { attrCount += 1;} } );
return attrCount;
}
function safeSendChat(speakingAs, message, callback) {
try {
sendChat(speakingAs, message, callback);
}
catch (e) {
sendChat("PowerCards", JSON.stringify(e));
}
}
function sortByKey(array, key) {
return array.sort(function(a, b) {
var x = a.get(key);
var y = b.get(key);
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
function getSpellInfo(charid, spellname) {
var return_set = [];
try {
var spell_prefix = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").endsWith("spellname")); })
.filter(entry => entry.get("current") == spellname)[0]
.get("name").slice(0,-9);
} catch (e) { return(return_set) };
return_set.push("SP-NAME|" + getAttrByName(charid, spell_prefix + "spellname"));
return_set.push("SP-LEVEL|" + (spell_prefix.charAt(16) == "c" ? 0 : spell_prefix.charAt(16)));
return_set.push("SP-SCHOOL|" + getAttrByName(charid, spell_prefix + "spellschool"));
return_set.push("SP-RANGE|" + getAttrByName(charid, spell_prefix + "spellrange"));
return_set.push("SP-DURATION|" + getAttrByName(charid, spell_prefix + "spellduration"));
return_set.push("SP-ATTACK|" + getAttrByName(charid, spell_prefix + "spellattack"));
return_set.push("SP-DAMAGE|" + getAttrByName(charid, spell_prefix + "spelldamage"));
return_set.push("SP-DAMAGETYPE|" + getAttrByName(charid, spell_prefix + "spelldamagetype"));
return_set.push("SP-DAMAGE2|" + getAttrByName(charid, spell_prefix + "spelldamage"));
return_set.push("SP-DAMAGETYPE2|" + getAttrByName(charid, spell_prefix + "spelldamagetype"));
return_set.push("SP-HLDIE|" + getAttrByName(charid, spell_prefix + "spellhldie"));
return_set.push("SP-HLDIETYPE|" + getAttrByName(charid, spell_prefix + "spellhldietype"));
return_set.push("SP-OUTPUT|" + getAttrByName(charid, spell_prefix + "spelloutput"));
var desc = getAttrByName(charid, spell_prefix + "spelldescription").replace(/\
/g,"").replace(/\<\/p\>/g,"").replace(/\n/g,"
")
return_set.push("SP-DESCRIPTION|" + desc);
return_set.push("SP-MORECANTRIPDAMAGE|" + (desc.includes("damage increases by") ? 1 : 0));
return_set.push("SP-ATHIGHER|" + getAttrByName(charid, spell_prefix + "spellathigherlevels"));
return_set.push("SP-ADDABILITY|" + (getAttrByName(charid, spell_prefix + "spelldmgmod") == "Yes" ?1:0));
return_set.push("SP-CONCENTRATION|" + (getAttrByName(charid, spell_prefix + "spellconcentration") == "{{concentration=1}}" ? "Concentration" : " "));
return_set.push("SP-CASTINGTIME|" + getAttrByName(charid, spell_prefix + "spellcastingtime"));
return_set.push("SP-SAVE|" + getAttrByName(charid, spell_prefix + "spellsave"));
return_set.push("SP-SAVESUCCESS|" + getAttrByName(charid, spell_prefix + "spellsavesuccess"));
return_set.push("SP-HEALING|" + getAttrByName(charid, spell_prefix + "spellhealing"));
return (return_set);
}
function getNPCAction(charid, actionname) {
var return_set = [];
try {
var action_prefix = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").startsWith("repeating_npcaction") && z.get("name").endsWith("name")); })
.filter(entry => entry.get("current") == actionname)[0]
.get("name").slice(0,-4);
} catch (e) { return(return_set) };
var actionType = (getAttrByName(charid, action_prefix + "attack_flag") == "on" ? "ATTACK" : "INFOCARD");
return_set.push("NPCA-NAME|" + actionname);
return_set.push("NPCA-TYPE|" + actionType);
if (actionType == "ATTACK") {
return_set.push("NPCA-ATYPE|" + getAttrByName(charid, action_prefix + "attack_type"));
return_set.push("NPCA-RANGE|" + getAttrByName(charid, action_prefix + "attack_range"));
return_set.push("NPCA-TOHIT|" + getAttrByName(charid, action_prefix + "attack_tohit"));
return_set.push("NPCA-TARGET|" + getAttrByName(charid, action_prefix + "attack_target"));
var baseDamage = getAttrByName(charid, action_prefix + "attack_damage");
return_set.push("NPCA-DAMAGE|" + baseDamage);
var crit = (parseInt(baseDamage.split("d")[0])*2).toString() + "d" + baseDamage.split("d")[1];
return_set.push("NPCA-CRIT|" + crit);
return_set.push("NPCA-DAMAGETYPE|" + getAttrByName(charid, action_prefix + "attack_damagetype"));
baseDamage = getAttrByName(charid, action_prefix + "attack_damage2");
return_set.push("NPCA-DAMAGE2|" + baseDamage);
if (baseDamage !== "") {
crit = (parseInt(baseDamage.split("d")[0])*2).toString() + "d" + baseDamage.split("d")[1];
return_set.push("NPCA-CRIT2|" + crit);
} else {
return_set.push("NPCA-CRIT2|");
}
return_set.push("NPCA-DAMAGETYPE2|" + getAttrByName(charid, action_prefix + "attack_damagetype2"));
}
return_set.push("NPCA-DESCRIPTION|" + getAttrByName(charid, action_prefix + "description"));
return (return_set);
}
function getPCAttack(charid, attackname) {
var return_set = [];
try {
var action_prefix = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").startsWith("repeating_attack") && z.get("name").endsWith("atkname")); })
.filter(entry => entry.get("current") == attackname)[0]
.get("name").slice(0,-7);
} catch (e) { return(return_set) };
// var actionType = (getAttrByName(charid, action_prefix + "attack_flag") == "on" ? "ATTACK" : "INFOCARD");
// return_set.push("PCA-NAME|" + attackname);
// return_set.push("PCA-DMGBASE|" + getAttrByName(charid, action_prefix + "dmgbase"));
// return_set.push("PCA-DMGTYPE|" + getAttrByName(charid, action_prefix + "dmgtype"));
// var atkattr = getAttrByName(charid, action_prefix + "atkattr_base");
// return_set.push("PCA-ATKATTR|" + atkattr.substring(2,3).toUpperCase();
// return_set.push("PCA-ATKATTRBONUS|" + atkattr.substring(
// return_set.push("PCA-DMGATTR|" + getAttrByName(charid, action_prefix + "dmgattr").substring(2,3)).toUpperCase();
// return_set.push("PCA-PROF|" + + getAttrByName(charid, action_prefix + "atkprofflag"));
// return_set.push("NPCA-DESCRIPTION|" + getAttrByName(charid, action_prefix + "description"));
var action_attrs = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").startsWith(action_prefix)); })
action_attrs.forEach(function(z) {
if (!(z.get("name").startsWith(action_prefix + "rollbase"))) {
return_set.push("PCA-"+z.get("name").replace(action_prefix,"") + "|" + z.get("current"));
}
})
//log(return_set);
return (return_set);
}
function getSectionAttrs(charid, entryname, sectionname, searchtext, replacementprefix) {
var return_set = [];
try {
var action_prefix = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").startsWith("repeating_" + sectionname) && z.get("name").endsWith(searchtext)); })
.filter(entry => entry.get("current") == entryname)[0]
.get("name").slice(0,-searchtext.length);
} catch (e) { return(return_set) };
var action_attrs = filterObjs(function(z) { return (z.get("characterid") == charid && z.get("name").startsWith(action_prefix)); })
action_attrs.forEach(function(z) {
return_set.push(replacementprefix+z.get("name").replace(action_prefix,"") + "|" + z.get("current"));
return_set.push(replacementprefix+z.get("name").replace(action_prefix,"") + "_max|" + z.get("max"));
})
return (return_set);
}
function getSpellMacro(charid, spellname, spellattrname) {
var spellAttr = getSheetAttr(charid, "spell_" + spellname.replace(/ /g,"_") + "_macro");
if (spellAttr !== "") {
return("(!
" + spellAttr + ")");
} else {
return("(~selected|" + spellattrname.replace("name","")+")");
}
}
function sanitize(str) {
var res;
if (str) {
log(str);
res = str.replace(/\=/g,"").replace(/\;/g,"").replace(/\/g,"").replace(/\{/g,"").replace(/\}/g,"").replace(/\[/g,"").replace(/\]/g,"").replace(/\(/g,"").replace(/\)/g,"").replace(/\./g,"").replace(/\&/g,"").replace(/|>/g,"");
res = res.replace(/\-eq/g,"==").replace(/\-ne/g,"!==").replace(/\-gt/g,">").replace(/\-lt/g,"<").replace(/\-ge/g,">=").replace(/\-le/g,"<=").replace(/\-and/g,"&&").replace(/\-or/g,"||");
log(res);
}
return res;
}
RegExp.quote = function(str) {
return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
};
const getRepeatingSectionAttrs = function (charid, prefix) {
// Input
// charid: character id
// prefix: repeating section name, e.g. 'repeating_weapons'
// Output
// repRowIds: array containing all repeating section IDs for the given prefix, ordered in the same way that the rows appear on the sheet
// repeatingAttrs: object containing all repeating attributes that exist for this section, indexed by their name
const repeatingAttrs = {},
regExp = new RegExp(`^${prefix}_(-[-A-Za-z0-9]+?|\\d+)_`);
let repOrder;
// Get attributes
findObjs({
_type: 'attribute',
_characterid: charid
}).forEach(o => {
const attrName = o.get('name');
if (attrName.search(regExp) === 0) repeatingAttrs[attrName] = o;
else if (attrName === `_reporder_${prefix}`) repOrder = o.get('current').split(',');
});
if (!repOrder) repOrder = [];
// Get list of repeating row ids by prefix from repeatingAttrs
const unorderedIds = [...new Set(Object.keys(repeatingAttrs)
.map(n => n.match(regExp))
.filter(x => !!x)
.map(a => a[1]))];
const repRowIds = [...new Set(repOrder.filter(x => unorderedIds.includes(x)).concat(unorderedIds))];
return [repRowIds, repeatingAttrs];
}
return { Process: PowerCard.Process };
}());