Files
roll20-character-sheets-tester/Ironsworn/src/test/test-roll-templates.js
T

150 lines
5.3 KiB
JavaScript

$(document).ready(() => {
// store roll templates
const rollTemplates = {};
const template = $('rolltemplate');
template.each(function () {
const templateId = $(this).attr('class').substring('sheet-rolltemplate-'.length);
rollTemplates[templateId] = this.innerHTML;
});
template.remove();
// render templates from specs
rollSpecs.filter(_ => _.display === undefined || _.display).forEach(spec => {
const template = rollTemplates[spec.templateId];
// preprocess values
const rollValues = {};
for (const name in spec.values) {
let value = spec.values[name];
// we want to check that roll values are formatted correctly (i.e. [[value]]),
// but they must appear with their value when rendering the template,
// so we store them aside and replace the "[[value]]" with "value" in the spec values.
if (value.startsWith('[[') && value.endsWith(']]') && value.indexOf(' ') == -1) {
const numericValue = Number.parseInt(value.substring(2, value.length - 2)).valueOf();
rollValues[name] = numericValue;
const rollType = numericValue == 1 ? 'fullfail' : (numericValue == 6 || numericValue == 10) ? 'fullcrit' : '';
spec.values[name] = `<span class="inlinerollresult showtip tipsy-n-right ${rollType}">${rollValues[name]}</span>`;
} else {
// localize values (currently roll20 does not localize inline rolls)
for (const group of value.matchAll(/\^{(.+?)}/g)) {
const i18nValue = i18next.t(group[1], '');
if (i18nValue) {
value = value.replace(group[0], i18nValue);
}
}
spec.values[name] = value;
}
}
// handle sections with helper functions: parse the template,
// resolve section that contain a function and store the section name with its computed value
const computedValues = {};
Mustache.parse(template).forEach(token => {
processToken(token);
function processToken(token) {
if (token[0] == '#') {
// WARNING mustache uses ^ and / to open/close an inverted section,
// but for whatever reason (old mustache version perhaps),
// roll20 uses #^ and /^, which means the inversion must be computed here,
// using ^... as section name.
const sectionName = token[1];
const isInverted = sectionName.startsWith('^');
const parts = sectionName.split(' ');
const functionName = isInverted ? parts[0].substring(1) : parts[0];
switch (functionName) {
case 'rollTotal()':
case 'rollGreater()':
case 'rollLess()':
case 'rollBetween()':
if (computedValues[sectionName] === undefined) {
computedValues[sectionName] = evaluateFunction(functionName, parts.slice(1), isInverted);
}
break;
default: {
if (functionName.endsWith('()')) {
console.warn(`Unsupported function '${functionName}' in template ${spec.templateId}`);
}
}
}
}
// continue into the token tree
if (token[4]) {
token[4].forEach(token => processToken(token));
}
function evaluateFunction(functionName, args, isInverted) {
const rollValue = getRollValue(args[0]);
const values = args.slice(1).map(_ => getFunctionValue(_));
if (rollValue === undefined || values.some(_ => _ === undefined)) {
return false;
} else {
let result;
switch (functionName) {
case 'rollTotal()':
result = rollValue == values[0];
break;
case 'rollGreater()':
result = rollValue > values[0];
break;
case 'rollLess()':
result = rollValue < values[0];
break;
case 'rollBetween()':
result = values[0] <= rollValue && rollValue <= values[1];
break;
default:
throw `Unhandled function name '${functionName}'`;
}
return isInverted ? !result : result;
}
}
function getFunctionValue(value) {
const number = Number.parseInt(value);
if (Number.isNaN(number)) {
return getRollValue(value);
}
return number.valueOf();
}
function getRollValue(rollName) {
const value = rollValues[rollName];
if (value !== undefined) {
return value;
} else {
// if the roll was also not in the spec list, that's fine
if (spec.values[rollName] === undefined) {
console.warn('Usage of unspecified roll name: ' + rollName);
return undefined;
// otherwise this is an error
} else {
throw `'${rollName}' is not formatted as a valid inline roll, i.e. [[rollValue]]`;
}
}
}
}
});
const container = $(`<div class="sheet-rolltemplate-${spec.templateId}" style="margin-bottom: 10px"></div>`)
.append(Mustache.render(template, { ...spec.values, ...computedValues }, undefined, { escape: _ => _ }));
localize(container);
$('#rolls').append(container);
});
});