108 lines
3.8 KiB
JavaScript
108 lines
3.8 KiB
JavaScript
/**
|
|
* SR6 Ruleset Handlebars Helpers
|
|
* Auto-loaded by preview server for ruleset 'sr6'.
|
|
*/
|
|
module.exports = function (handlebars) {
|
|
// Compute maximum physical damage boxes: 8 + ceil(body / 2)
|
|
handlebars.registerHelper('calcPhysicalBoxes', function (body) {
|
|
const b = typeof body === 'object' && body !== null ? (body.value || body.base || 0) : Number(body || 0);
|
|
return 8 + Math.ceil(b / 2);
|
|
});
|
|
|
|
// Compute maximum stun damage boxes: 8 + ceil(willpower / 2)
|
|
handlebars.registerHelper('calcStunBoxes', function (willpower) {
|
|
const w = typeof willpower === 'object' && willpower !== null ? (willpower.value || willpower.base || 0) : Number(willpower || 0);
|
|
return 8 + Math.ceil(w / 2);
|
|
});
|
|
|
|
// Format currency with nuyen symbol
|
|
handlebars.registerHelper('formatNuyen', function (amount) {
|
|
if (amount === undefined || amount === null) return '0¥';
|
|
const num = typeof amount === 'number' ? amount : parseInt(String(amount).replace(/[^0-9]/g, ''), 10) || 0;
|
|
return `${num.toLocaleString('en-US')}¥`;
|
|
});
|
|
|
|
// Format dice pool helper: (skill rating + linked attribute value)
|
|
handlebars.registerHelper('calcDicePool', function (skill, attributes) {
|
|
if (!skill) return 0;
|
|
const rating = typeof skill.rating === 'number' ? skill.rating : (parseInt(skill.rating, 10) || 0);
|
|
const attrName = (skill.attribute || '').toLowerCase();
|
|
let attrVal = 0;
|
|
if (attributes && attrName && attributes[attrName] !== undefined) {
|
|
const a = attributes[attrName];
|
|
attrVal = typeof a === 'object' && a !== null ? (a.value || a.base || 0) : (Number(a) || 0);
|
|
}
|
|
const bonus = skill.expertise ? 3 : (skill.specialization ? 2 : 0);
|
|
return rating + attrVal + bonus;
|
|
});
|
|
|
|
// Safe attribute value extractor
|
|
handlebars.registerHelper('attr', function (attributes, name) {
|
|
if (!attributes || !name) return '—';
|
|
const val = attributes[name.toLowerCase()];
|
|
if (val === undefined || val === null) return '—';
|
|
return typeof val === 'object' ? (val.value ?? val.base ?? '—') : val;
|
|
});
|
|
|
|
// Helper to build 3-box rows for condition monitor
|
|
handlebars.registerHelper('conditionMonitorRows', function (totalBoxes, currentDamage, maxGridBoxes, options) {
|
|
const total = Number(totalBoxes) || 8;
|
|
const damage = Number(currentDamage) || 0;
|
|
const maxBoxes = Number(maxGridBoxes) || 18;
|
|
const rowsCount = Math.ceil(maxBoxes / 3);
|
|
|
|
let output = '';
|
|
for (let r = 0; r < rowsCount; r++) {
|
|
const boxes = [];
|
|
for (let c = 1; c <= 3; c++) {
|
|
const boxIdx = r * 3 + c;
|
|
if (boxIdx <= maxBoxes) {
|
|
boxes.push({
|
|
index: boxIdx,
|
|
active: boxIdx <= total,
|
|
filled: boxIdx <= damage,
|
|
blackedOut: boxIdx > total,
|
|
isThreshold: c === 3,
|
|
penalty: -(r + 1)
|
|
});
|
|
}
|
|
}
|
|
output += options.fn({
|
|
rowNumber: r + 1,
|
|
penalty: -(r + 1),
|
|
boxes: boxes
|
|
});
|
|
}
|
|
return output;
|
|
});
|
|
|
|
// Matrix condition monitor boxes generator (1 to 12)
|
|
handlebars.registerHelper('matrixMonitorBoxes', function (maxBoxes, currentDamage, options) {
|
|
const total = Number(maxBoxes) || 12;
|
|
const damage = Number(currentDamage) || 0;
|
|
let output = '';
|
|
for (let i = 1; i <= total; i++) {
|
|
output += options.fn({
|
|
index: i,
|
|
filled: i <= damage
|
|
});
|
|
}
|
|
return output;
|
|
});
|
|
|
|
// Edge points tracker generator (1 to maxEdge)
|
|
handlebars.registerHelper('edgePointsBoxes', function (maxEdge, currentEdge, options) {
|
|
const max = Number(maxEdge) || 7;
|
|
const curr = Number(currentEdge) || 0;
|
|
let output = '';
|
|
for (let i = 1; i <= Math.max(7, max); i++) {
|
|
output += options.fn({
|
|
index: i,
|
|
filled: i <= curr,
|
|
disabled: i > max
|
|
});
|
|
}
|
|
return output;
|
|
});
|
|
};
|