UNPKG

dicelang

Version:

JavaScript interpreter of the Roll20 dice language

536 lines (535 loc) 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var Constants_1 = require("../Common/Constants"); var Dice_1 = require("./Dice"); var DiceMod = (function () { function DiceMod(modExpr) { this._successes = null; this._failures = null; this._exploding = null; this._compounding = null; this._penetrating = null; this._keepDrop = null; this._reroll = []; this._sort = null; var originalExpr = modExpr; if (modExpr) { modExpr = modExpr.trim(); } var result; var explodeRegExp = DiceMod.explodeModRegExpr; while (result = explodeRegExp.exec(modExpr)) { this._parseExplodeResult(result); modExpr = modExpr.replace(result[0], ''); } var keepDropRegExp = DiceMod.keepDropModRegExp; while (result = keepDropRegExp.exec(modExpr)) { this._parseKeepDropResult(result); modExpr = modExpr.replace(result[0], ''); } var rerollRegExp = DiceMod.rerollModRegExp; while (result = rerollRegExp.exec(modExpr)) { this._parseRerollResult(result); modExpr = modExpr.replace(result[0], ''); } var successesRegExp = DiceMod.successesModRegExpr; while (result = successesRegExp.exec(modExpr)) { this._parseSucessesResult(result); modExpr = modExpr.replace(result[0], ''); } var sortRegExp = DiceMod.sortModRegExp; while (result = sortRegExp.exec(modExpr)) { this._parseSortResult(result); modExpr = modExpr.replace(result[0], ''); } if (modExpr && modExpr.length > 0) { throw new Error("\"" + originalExpr + "\" is not a valid modifier: \"" + modExpr + "\" could not be parsed."); } } DiceMod.comparePointToString = function (cp) { switch (cp) { case '<': return 'less than or equal to'; case '>': return 'greater than or equal to'; case '=': return 'equal to'; default: throw new Error("A compare point must be \"<\", \">\", or \"=\", but got \"" + cp + "\"."); } }; DiceMod.comparePoint = function (cp, n, value) { switch (cp) { case '<': return value <= n; case '>': return value >= n; case '=': return value === n; default: throw new Error("A compare point must be \"<\", \">\", or \"=\", but got \"" + cp + "\"."); } }; Object.defineProperty(DiceMod, "maxN", { get: function () { return Constants_1.MAX_JS_INT; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod, "successesModRegExpr", { get: function () { return /([\<\=\>])(\d+)(?:f([\<\=\>])?(\d+))?/i; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod, "explodeModRegExpr", { get: function () { return /(![!p]?)(?:([\<\=\>])?(\d+)|(?![\<\=\>]))/i; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod, "keepDropModRegExp", { get: function () { return /([kd])([lh])?(\d+)/i; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod, "rerollModRegExp", { get: function () { return /r(o)?(?:([\<\=\>])?(\d+)|(?![\<\=\>]))/i; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod, "sortModRegExp", { get: function () { return /s([ad])?/i; }, enumerable: true, configurable: true }); DiceMod.sortAscComparator = function (a, b) { return a - b; }; DiceMod.sortDesComparator = function (a, b) { return b - a; }; DiceMod.checkN = function (n) { if (n < 0 || n > DiceMod.maxN) { throw new Error("The value of n must be between 0 and " + DiceMod.maxN + " inclusive (got " + n + ")"); } }; DiceMod.prototype.rolled = function (roll, result, dice) { var rolls = this._applyHotModifiers(roll, dice); rolls.forEach(function (r) { return result.push(r); }); }; DiceMod.prototype.modResult = function (result) { return this._applySettledModifiers(result); }; Object.defineProperty(DiceMod.prototype, "successesProperties", { get: function () { return this._successes === null ? null : { cp: this._successes.cp, n: this._successes.n, f: this._failures === null ? null : { cp: this._failures.cp, n: this._failures.n, }, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "successes", { get: function () { return this._successes === null ? '' : "" + this._successes.cp + this._successes.n + (this._failures === null ? '' : "f" + this._failures.cp + this._failures.n); }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "successesPlaintext", { get: function () { return this._successes === null ? "" : "Count rolls " + DiceMod.comparePointToString(this._successes.cp) + " " + this._successes.n + (this._failures === null ? '' : " minus rolls " + DiceMod.comparePointToString(this._failures.cp) + " " + this._failures.n) + " as successes."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "explodingProperties", { get: function () { return this._exploding === null ? null : { cp: this._exploding.cp, n: this._exploding.n, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "exploding", { get: function () { return this._exploding === null ? '' : "!" + (this._exploding.n === null ? '' : "" + this._exploding.cp + this._exploding.n); }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "explodingPlaintext", { get: function () { if (this._exploding === null) { return ''; } var explodeOn; if (this._exploding.n === null) { explodeOn = 'the maximum value'; } else { explodeOn = "rolls " + DiceMod.comparePointToString(this._exploding.cp) + " " + this._exploding.n; } return this._exploding === null ? "" : "Explode on " + explodeOn + "."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "compoundingProperties", { get: function () { return this._compounding === null ? null : { cp: this._compounding.cp, n: this._compounding.n, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "compounding", { get: function () { return this._compounding === null ? '' : "!!" + (this._compounding.n === null ? '' : "" + this._compounding.cp + this._compounding.n); }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "compoundingPlaintext", { get: function () { if (this._compounding === null) { return ''; } var explodeOn; if (this._compounding.n === null) { explodeOn = 'the maximum value'; } else { explodeOn = "rolls " + DiceMod.comparePointToString(this._compounding.cp) + " " + this._compounding.n; } return this._compounding === null ? "" : "Compound on " + explodeOn + "."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "penetratingProperties", { get: function () { return this._penetrating === null ? null : { cp: this._penetrating.cp, n: this._penetrating.n, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "penetrating", { get: function () { return this._penetrating === null ? '' : "!p" + (this._penetrating.n === null ? '' : "" + this._penetrating.cp + this._penetrating.n); }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "penetratingPlaintext", { get: function () { if (this._penetrating === null) { return ''; } var explodeOn; if (this._penetrating.n === null) { explodeOn = 'the maximum value'; } else { explodeOn = "rolls " + DiceMod.comparePointToString(this._penetrating.cp) + " " + this._penetrating.n; } return this._penetrating === null ? "" : "Penetrate on " + explodeOn + "."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "keepDropProperties", { get: function () { return this._keepDrop === null ? null : { kd: this._keepDrop.kd, lh: this._keepDrop.lh, n: this._keepDrop.n, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "keepDrop", { get: function () { return this._keepDrop === null ? '' : "" + this._keepDrop.kd + this._keepDrop.lh + this._keepDrop.n; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "keepDropPlaintext", { get: function () { return this._keepDrop === null ? "" : (this._keepDrop.kd === 'k' ? 'Keep' : 'Drop') + " the " + (this._keepDrop.lh === 'l' ? 'lowest' : 'highest') + " " + this._keepDrop.n + " roll" + (this._keepDrop.n > 1 ? 's' : '') + "."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "rerollProperties", { get: function () { var result = []; this._reroll.forEach(function (rr) { return result.push({ cp: rr.cp, o: rr.o, n: rr.n, }); }); return result; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "reroll", { get: function () { return this._reroll.length < 1 ? '' : this._reroll.map(function (x) { return "r" + (x.o ? 'o' : '') + (x.n === null ? "" : "" + x.cp + x.n); }).join(""); }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "rerollPlaintext", { get: function () { var rerolls; rerolls = this._reroll.map(function (x) { return "" + (x.n === null ? 'the lowest value' : "values " + DiceMod.comparePointToString(x.cp) + " " + x.n) + (x.o ? " only once" : ""); }); if (rerolls.length > 1) { rerolls[rerolls.length - 1] = "and " + rerolls[rerolls.length - 1]; } return this._reroll.length === 0 ? "" : "Reroll on " + rerolls.join(', ') + "."; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "sortProperties", { get: function () { return this._sort === null ? null : { ad: this._sort.ad, }; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "sort", { get: function () { return this._sort === null ? '' : "s" + this._sort.ad; }, enumerable: true, configurable: true }); Object.defineProperty(DiceMod.prototype, "sortPlaintext", { get: function () { return this._sort === null ? "" : "Sort " + (this._sort.ad === 'a' ? 'ascending' : 'descending') + "."; }, enumerable: true, configurable: true }); DiceMod.prototype.toString = function () { return "" + this.penetrating + this.compounding + this.exploding + this.keepDrop + this.reroll + this.successes + this.sort; }; DiceMod.prototype.toStringPlaintext = function () { var mods = [ this.penetratingPlaintext, this.compoundingPlaintext, this.explodingPlaintext, this.keepDropPlaintext, this.rerollPlaintext, this.successesPlaintext, this.sortPlaintext, ]; return "" + mods.join(' '); }; DiceMod.prototype._parseExplodeResult = function (result) { var n = result[3] === undefined ? null : parseInt(result[3], 10); DiceMod.checkN(n); var cp = result[2] || '='; var parsed = { cp: cp, n: n }; switch (result[1]) { case '!': if (this._exploding !== null) { throw new Error("Exploding already set as \"" + this.exploding + "\" but parsed \"" + result[0] + "\" as well."); } this._exploding = parsed; break; case '!!': if (this._compounding !== null) { throw new Error("Compounding already set as \"" + this.compounding + "\" but parsed \"" + result[0] + "\" as well."); } this._compounding = parsed; break; case '!p': if (this._penetrating !== null) { throw new Error("Penetrating already set as \"" + this.penetrating + "\" but parsed \"" + result[0] + "\" as well."); } this._penetrating = parsed; break; default: throw new Error("explodeModRegExp should only match \"!\", \"!!\", or \"!p\" but it matched \"" + result[1] + "\"."); } }; DiceMod.prototype._parseKeepDropResult = function (result) { if (this._keepDrop !== null) { throw new Error("Keep/Drop already set as \"" + this.keepDrop + "\" but parsed \"" + result[0] + "\" as well."); } var n = parseInt(result[3], 10); DiceMod.checkN(n); var kd = result[1]; var lh = result[2] || (result[1] === 'k' ? 'h' : 'l'); this._keepDrop = { kd: kd, lh: lh, n: n }; }; DiceMod.prototype._parseRerollResult = function (result) { var n = result[3] === undefined ? null : parseInt(result[3], 10); DiceMod.checkN(n); var o = result[1] !== undefined; var cp = result[2] === undefined ? '=' : result[2]; if (this._reroll.filter(function (x) { return x.cp === cp && x.n === n; }).length > 0) { throw new Error("Reroll on \"" + cp + n + "\" is already set but parsed \"" + result[0] + "\" as well."); } this._reroll.push({ o: o, cp: cp, n: n }); }; DiceMod.prototype._parseSucessesResult = function (result) { if (this._successes !== null) { throw new Error("Successes/failures already set as \"" + this.successes + "\" but parsed \"" + result[0] + "\" as well."); } var sn = parseInt(result[2], 10); DiceMod.checkN(sn); var scp = result[1] || '='; if (result[4]) { var fn = parseInt(result[4], 10); DiceMod.checkN(fn); var fcp = result[3] || '='; this._failures = { cp: fcp, n: fn, }; } this._successes = { cp: scp, n: sn, }; }; DiceMod.prototype._parseSortResult = function (result) { if (this._sort !== null) { throw new Error("Sort already set as \"" + this.sort + "\" but parsed \"" + result[0] + "\" as well."); } var ad = result[1] || 'a'; this._sort = { ad: ad }; }; DiceMod.prototype._applyHotModifiers = function (roll, dice) { var _this = this; var queue = [{ value: roll }]; var result = []; var shouldExplode = function (value) { return _this._exploding !== null && DiceMod.comparePoint(_this._exploding.cp, _this._exploding.n === null ? dice.d : _this._exploding.n, value); }; var shouldPenetrate = function (value) { return _this._penetrating !== null && DiceMod.comparePoint(_this._penetrating.cp, _this._penetrating.n === null ? dice.d : _this._penetrating.n, value); }; var shouldReroll = function (value, rerolled) { if (_this._reroll !== null) { for (var _i = 0, _a = _this._reroll; _i < _a.length; _i++) { var reroll = _a[_i]; var o = reroll.o; var cp = reroll.cp ? reroll.cp : '='; var n = reroll.n ? reroll.n : dice.minRoll; if (!(o && rerolled) && DiceMod.comparePoint(cp, n, value)) { return true; } } } return false; }; while (queue.length > 0) { var next = queue.pop(); if (shouldExplode(next.value)) { queue.push({ value: Dice_1.Dice.roll(dice.d)[0] }); } if (shouldPenetrate(next.value)) { queue.push({ value: Dice_1.Dice.roll(dice.d)[0], penetrated: true }); } var compoundedValue = this._applyCompounding(next.value, dice); if (shouldReroll(compoundedValue, next.rerolled)) { queue.push({ value: Dice_1.Dice.roll(dice.d)[0], rerolled: true }); } else { result.push(compoundedValue - (next.penetrated ? 1 : 0)); } } return result; }; DiceMod.prototype._applySettledModifiers = function (result) { var _this = this; this._applyKeepDrop(result); if (this._sort !== null) { result.sort(this._sort.ad === 'a' ? DiceMod.sortAscComparator : DiceMod.sortDesComparator); } if (this._successes !== null) { return result.reduce(function (acc, val) { if (DiceMod.comparePoint(_this._successes.cp, _this._successes.n, val)) { ++acc; } if (_this._failures !== null && DiceMod.comparePoint(_this._failures.cp, _this._failures.n, val)) { --acc; } return acc; }, 0); } else { return result.reduce(function (acc, val) { return acc + val; }, 0); } }; DiceMod.prototype._applyCompounding = function (roll, dice) { var _this = this; var shouldCompound = function (value) { return _this._compounding !== null && DiceMod.comparePoint(_this._compounding.cp, _this._compounding.n === null ? dice.d : _this._compounding.n, value); }; var compoundRoll = roll; while (shouldCompound(compoundRoll)) { if (roll >= Constants_1.MAX_JS_INT) { throw new Error('Exceeded maximum roll value while applying Compounding modifier.'); } compoundRoll = Dice_1.Dice.roll(dice.d)[0]; roll += compoundRoll; } return roll; }; DiceMod.prototype._applyKeepDrop = function (result) { if (this._keepDrop !== null) { result.sort(DiceMod.sortAscComparator); switch ("" + this._keepDrop.kd + this._keepDrop.lh) { case 'kh': result.splice(0, result.length - this._keepDrop.n); break; case 'kl': result.splice(this._keepDrop.n); break; case 'dh': result.splice(result.length - this._keepDrop.n); break; case 'dl': result.splice(0, this._keepDrop.n); break; default: throw new Error("Keep/Drop may be one of \"kh\", \"kl\", \"dh\" or \"dl\", but got \"" + this._keepDrop.kd + this._keepDrop.lh + "\"."); } } }; return DiceMod; }()); exports.DiceMod = DiceMod;