"use strict"; Renderer.dice = { SYSTEM_USER: { name: "Avandra", // goddess of luck }, POS_INFINITE: 100000000000000000000, // larger than this, and we start to see "e" numbers appear _SYMBOL_PARSE_FAILED: Symbol("parseFailed"), _$wrpRoll: null, _$minRoll: null, _$iptRoll: null, _$outRoll: null, _$head: null, _hist: [], _histIndex: null, _$lastRolledBy: null, _storage: null, _isManualMode: false, /* -------------------------------------------- */ // region Utilities DICE: [4, 6, 8, 10, 12, 20, 100], getNextDice (faces) { const idx = Renderer.dice.DICE.indexOf(faces); if (~idx) return Renderer.dice.DICE[idx + 1]; else return null; }, getPreviousDice (faces) { const idx = Renderer.dice.DICE.indexOf(faces); if (~idx) return Renderer.dice.DICE[idx - 1]; else return null; }, // endregion /* -------------------------------------------- */ // region DM Screen integration _panel: null, bindDmScreenPanel (panel, title) { if (Renderer.dice._panel) { // there can only be one roller box Renderer.dice.unbindDmScreenPanel(); } Renderer.dice._showBox(); Renderer.dice._panel = panel; panel.doPopulate_Rollbox(title); }, unbindDmScreenPanel () { if (Renderer.dice._panel) { $(`body`).append(Renderer.dice._$wrpRoll); Renderer.dice._panel.close$TabContent(); Renderer.dice._panel = null; Renderer.dice._hideBox(); Renderer.dice._$wrpRoll.removeClass("rollbox-panel"); } }, get$Roller () { return Renderer.dice._$wrpRoll; }, // endregion /* -------------------------------------------- */ bindOnclickListener (ele) { ele.addEventListener("click", (evt) => { const eleDice = evt.target.hasAttribute("data-packed-dice") ? evt.target // Tolerate e.g. Bestiary wrapped proficiency dice rollers : evt.target.parentElement?.hasAttribute("data-packed-dice") ? evt.target.parentElement : null; if (!eleDice) return; evt.preventDefault(); evt.stopImmediatePropagation(); Renderer.dice.pRollerClickUseData(evt, eleDice).then(null); }); }, /* -------------------------------------------- */ /** * Silently roll an expression and get the result. * Note that this does not support dynamic variables (e.g. user proficiency bonus). */ parseRandomise2 (str) { if (!str || !str.trim()) return null; const wrpTree = Renderer.dice.lang.getTree3(str); if (wrpTree) return wrpTree.tree.evl({}); else return null; }, /** * Silently get the average of an expression. * Note that this does not support dynamic variables (e.g. user proficiency bonus). */ parseAverage (str) { if (!str || !str.trim()) return null; const wrpTree = Renderer.dice.lang.getTree3(str); if (wrpTree) return wrpTree.tree.avg({}); else return null; }, // region Roll box UI _showBox () { Renderer.dice._$minRoll.hideVe(); Renderer.dice._$wrpRoll.showVe(); Renderer.dice._$iptRoll.prop("placeholder", `${Renderer.dice._getRandomPlaceholder()} or "/help"`); }, _hideBox () { Renderer.dice._$minRoll.showVe(); Renderer.dice._$wrpRoll.hideVe(); }, _getRandomPlaceholder () { const count = RollerUtil.randomise(10); const faces = Renderer.dice.DICE[RollerUtil.randomise(Renderer.dice.DICE.length - 1)]; const mod = (RollerUtil.randomise(3) - 2) * RollerUtil.randomise(10); const drop = (count > 1) && RollerUtil.randomise(5) === 5; const dropDir = drop ? RollerUtil.randomise(2) === 2 ? "h" : "l" : ""; const dropAmount = drop ? RollerUtil.randomise(count - 1) : null; return `${count}d${faces}${drop ? `d${dropDir}${dropAmount}` : ""}${mod < 0 ? mod : mod > 0 ? `+${mod}` : ""}`; }, /** Initialise the roll box UI. */ async _pInit () { const $wrpRoll = $(`
`).hideVe(); const $minRoll = $(``).on("click", () => { Renderer.dice._showBox(); Renderer.dice._$iptRoll.focus(); }); const $head = $(`(`);
this._lex3_outputToken(self);
self.token = ")";
this._lex3_outputToken(self);
break;
case "{":
self.braceCount++;
this._lex3_outputToken(self);
self.token = "{";
this._lex3_outputToken(self);
break;
case "}":
self.braceCount--;
if (self.parenCount < 0) throw new Error(`Syntax error: closing } without opening (`);
this._lex3_outputToken(self);
self.token = "}";
this._lex3_outputToken(self);
break;
// single-character operators
case "+": case "-": case "*": case "/": case "^": case ",":
this._lex3_outputToken(self);
self.token += c;
this._lex3_outputToken(self);
break;
default: {
if (Renderer.dice.lang._M_NUMBER_CHAR.test(c)) {
if (self.mode === "symbol") this._lex3_outputToken(self);
self.token += c;
self.mode = "text";
} else if (Renderer.dice.lang._M_SYMBOL_CHAR.test(c)) {
if (self.mode === "text") this._lex3_outputToken(self);
self.token += c;
self.mode = "symbol";
} else throw new Error(`Syntax error: unexpected character ${c}`);
break;
}
}
}
// empty the stack of any remaining content
this._lex3_outputToken(self);
},
_lex3_outputToken (self) {
if (!self.token) return;
switch (self.token) {
case "(": self.tokenStack.push(Renderer.dice.tk.PAREN_OPEN); break;
case ")": self.tokenStack.push(Renderer.dice.tk.PAREN_CLOSE); break;
case "{": self.tokenStack.push(Renderer.dice.tk.BRACE_OPEN); break;
case "}": self.tokenStack.push(Renderer.dice.tk.BRACE_CLOSE); break;
case ",": self.tokenStack.push(Renderer.dice.tk.COMMA); break;
case "+": self.tokenStack.push(Renderer.dice.tk.ADD); break;
case "-": self.tokenStack.push(Renderer.dice.tk.SUB); break;
case "*": self.tokenStack.push(Renderer.dice.tk.MULT); break;
case "/": self.tokenStack.push(Renderer.dice.tk.DIV); break;
case "^": self.tokenStack.push(Renderer.dice.tk.POW); break;
case "pb": self.tokenStack.push(Renderer.dice.tk.PB); self.hasPb = true; break;
case "summonspelllevel": self.tokenStack.push(Renderer.dice.tk.SUMMON_SPELL_LEVEL); self.hasSummonSpellLevel = true; break;
case "summonclasslevel": self.tokenStack.push(Renderer.dice.tk.SUMMON_CLASS_LEVEL); self.hasSummonClassLevel = true; break;
case "floor": self.tokenStack.push(Renderer.dice.tk.FLOOR); break;
case "ceil": self.tokenStack.push(Renderer.dice.tk.CEIL); break;
case "round": self.tokenStack.push(Renderer.dice.tk.ROUND); break;
case "avg": self.tokenStack.push(Renderer.dice.tk.AVERAGE); break;
case "dmax": self.tokenStack.push(Renderer.dice.tk.DMAX); break;
case "dmin": self.tokenStack.push(Renderer.dice.tk.DMIN); break;
case "sign": self.tokenStack.push(Renderer.dice.tk.SIGN); break;
case "abs": self.tokenStack.push(Renderer.dice.tk.ABS); break;
case "cbrt": self.tokenStack.push(Renderer.dice.tk.CBRT); break;
case "sqrt": self.tokenStack.push(Renderer.dice.tk.SQRT); break;
case "exp": self.tokenStack.push(Renderer.dice.tk.EXP); break;
case "log": self.tokenStack.push(Renderer.dice.tk.LOG); break;
case "random": self.tokenStack.push(Renderer.dice.tk.RANDOM); break;
case "trunc": self.tokenStack.push(Renderer.dice.tk.TRUNC); break;
case "pow": self.tokenStack.push(Renderer.dice.tk.POW); break;
case "max": self.tokenStack.push(Renderer.dice.tk.MAX); break;
case "min": self.tokenStack.push(Renderer.dice.tk.MIN); break;
case "d": self.tokenStack.push(Renderer.dice.tk.DICE); break;
case "dh": self.tokenStack.push(Renderer.dice.tk.DROP_HIGHEST); break;
case "kh": self.tokenStack.push(Renderer.dice.tk.KEEP_HIGHEST); break;
case "dl": self.tokenStack.push(Renderer.dice.tk.DROP_LOWEST); break;
case "kl": self.tokenStack.push(Renderer.dice.tk.KEEP_LOWEST); break;
case "r": self.tokenStack.push(Renderer.dice.tk.REROLL_EXACT); break;
case "r>": self.tokenStack.push(Renderer.dice.tk.REROLL_GT); break;
case "r>=": self.tokenStack.push(Renderer.dice.tk.REROLL_GTEQ); break;
case "r<": self.tokenStack.push(Renderer.dice.tk.REROLL_LT); break;
case "r<=": self.tokenStack.push(Renderer.dice.tk.REROLL_LTEQ); break;
case "x": self.tokenStack.push(Renderer.dice.tk.EXPLODE_EXACT); break;
case "x>": self.tokenStack.push(Renderer.dice.tk.EXPLODE_GT); break;
case "x>=": self.tokenStack.push(Renderer.dice.tk.EXPLODE_GTEQ); break;
case "x<": self.tokenStack.push(Renderer.dice.tk.EXPLODE_LT); break;
case "x<=": self.tokenStack.push(Renderer.dice.tk.EXPLODE_LTEQ); break;
case "cs=": self.tokenStack.push(Renderer.dice.tk.COUNT_SUCCESS_EXACT); break;
case "cs>": self.tokenStack.push(Renderer.dice.tk.COUNT_SUCCESS_GT); break;
case "cs>=": self.tokenStack.push(Renderer.dice.tk.COUNT_SUCCESS_GTEQ); break;
case "cs<": self.tokenStack.push(Renderer.dice.tk.COUNT_SUCCESS_LT); break;
case "cs<=": self.tokenStack.push(Renderer.dice.tk.COUNT_SUCCESS_LTEQ); break;
case "ms=": self.tokenStack.push(Renderer.dice.tk.MARGIN_SUCCESS_EXACT); break;
case "ms>": self.tokenStack.push(Renderer.dice.tk.MARGIN_SUCCESS_GT); break;
case "ms>=": self.tokenStack.push(Renderer.dice.tk.MARGIN_SUCCESS_GTEQ); break;
case "ms<": self.tokenStack.push(Renderer.dice.tk.MARGIN_SUCCESS_LT); break;
case "ms<=": self.tokenStack.push(Renderer.dice.tk.MARGIN_SUCCESS_LTEQ); break;
default: {
if (Renderer.dice.lang._M_NUMBER.test(self.token)) {
if (self.token.split(Parser._decimalSeparator).length > 2) throw new Error(`Syntax error: too many decimal separators ${self.token}`);
self.tokenStack.push(Renderer.dice.tk.NUMBER(self.token));
} else throw new Error(`Syntax error: unexpected token ${self.token}`);
}
}
self.token = "";
},
// endregion
// region Parser
_parse3 (lexed) {
const self = {
ixSym: -1,
syms: lexed,
sym: null,
lastAccepted: null,
// Workaround for comma-separated numbers--if we're e.g. inside a dice pool, treat the commas as dice pool
// separators. Otherwise, merge together adjacent numbers, to convert e.g. "1,000,000" to "1000000".
isIgnoreCommas: true,
};
this._parse3_nextSym(self);
return this._parse3_expression(self);
},
_parse3_nextSym (self) {
const cur = self.syms[self.ixSym];
self.ixSym++;
self.sym = self.syms[self.ixSym];
return cur;
},
_parse3_match (self, symbol) {
if (self.sym == null) return false;
if (symbol.type) symbol = symbol.type; // If it's a typed token, convert it to its underlying type
return self.sym.type === symbol;
},
_parse3_accept (self, symbol) {
if (this._parse3_match(self, symbol)) {
const out = self.sym;
this._parse3_nextSym(self);
self.lastAccepted = out;
return out;
}
return false;
},
_parse3_expect (self, symbol) {
const accepted = this._parse3_accept(self, symbol);
if (accepted) return accepted;
if (self.sym) throw new Error(`Unexpected input: Expected ${symbol} but found ${self.sym}`);
else throw new Error(`Unexpected end of input: Expected ${symbol}`);
},
_parse3_factor (self, {isSilent = false} = {}) {
if (this._parse3_accept(self, Renderer.dice.tk.TYP_NUMBER)) {
// Workaround for comma-separated numbers
if (self.isIgnoreCommas) {
// Combine comma-separated parts
const syms = [self.lastAccepted];
while (this._parse3_accept(self, Renderer.dice.tk.COMMA)) {
const sym = this._parse3_expect(self, Renderer.dice.tk.TYP_NUMBER);
syms.push(sym);
}
const sym = Renderer.dice.tk.NUMBER(syms.map(it => it.value).join(""));
return new Renderer.dice.parsed.Factor(sym);
}
return new Renderer.dice.parsed.Factor(self.lastAccepted);
} else if (this._parse3_accept(self, Renderer.dice.tk.PB)) {
return new Renderer.dice.parsed.Factor(Renderer.dice.tk.PB);
} else if (this._parse3_accept(self, Renderer.dice.tk.SUMMON_SPELL_LEVEL)) {
return new Renderer.dice.parsed.Factor(Renderer.dice.tk.SUMMON_SPELL_LEVEL);
} else if (this._parse3_accept(self, Renderer.dice.tk.SUMMON_CLASS_LEVEL)) {
return new Renderer.dice.parsed.Factor(Renderer.dice.tk.SUMMON_CLASS_LEVEL);
} else if (
// Single-arg functions
this._parse3_match(self, Renderer.dice.tk.FLOOR)
|| this._parse3_match(self, Renderer.dice.tk.CEIL)
|| this._parse3_match(self, Renderer.dice.tk.ROUND)
|| this._parse3_match(self, Renderer.dice.tk.AVERAGE)
|| this._parse3_match(self, Renderer.dice.tk.DMAX)
|| this._parse3_match(self, Renderer.dice.tk.DMIN)
|| this._parse3_match(self, Renderer.dice.tk.SIGN)
|| this._parse3_match(self, Renderer.dice.tk.ABS)
|| this._parse3_match(self, Renderer.dice.tk.CBRT)
|| this._parse3_match(self, Renderer.dice.tk.SQRT)
|| this._parse3_match(self, Renderer.dice.tk.EXP)
|| this._parse3_match(self, Renderer.dice.tk.LOG)
|| this._parse3_match(self, Renderer.dice.tk.RANDOM)
|| this._parse3_match(self, Renderer.dice.tk.TRUNC)
) {
const children = [];
children.push(this._parse3_nextSym(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_OPEN);
children.push(this._parse3_expression(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_CLOSE);
return new Renderer.dice.parsed.Function(children);
} else if (
// 2-arg functions
this._parse3_match(self, Renderer.dice.tk.POW)
) {
self.isIgnoreCommas = false;
const children = [];
children.push(this._parse3_nextSym(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_OPEN);
children.push(this._parse3_expression(self));
this._parse3_expect(self, Renderer.dice.tk.COMMA);
children.push(this._parse3_expression(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_CLOSE);
self.isIgnoreCommas = true;
return new Renderer.dice.parsed.Function(children);
} else if (
// N-arg functions
this._parse3_match(self, Renderer.dice.tk.MAX)
|| this._parse3_match(self, Renderer.dice.tk.MIN)
) {
self.isIgnoreCommas = false;
const children = [];
children.push(this._parse3_nextSym(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_OPEN);
children.push(this._parse3_expression(self));
while (this._parse3_accept(self, Renderer.dice.tk.COMMA)) children.push(this._parse3_expression(self));
this._parse3_expect(self, Renderer.dice.tk.PAREN_CLOSE);
self.isIgnoreCommas = true;
return new Renderer.dice.parsed.Function(children);
} else if (this._parse3_accept(self, Renderer.dice.tk.PAREN_OPEN)) {
const exp = this._parse3_expression(self);
this._parse3_expect(self, Renderer.dice.tk.PAREN_CLOSE);
return new Renderer.dice.parsed.Factor(exp, {hasParens: true});
} else if (this._parse3_accept(self, Renderer.dice.tk.BRACE_OPEN)) {
self.isIgnoreCommas = false;
const children = [];
children.push(this._parse3_expression(self));
while (this._parse3_accept(self, Renderer.dice.tk.COMMA)) children.push(this._parse3_expression(self));
this._parse3_expect(self, Renderer.dice.tk.BRACE_CLOSE);
self.isIgnoreCommas = true;
const modPart = [];
this._parse3__dice_modifiers(self, modPart);
return new Renderer.dice.parsed.Pool(children, modPart[0]);
} else {
if (isSilent) return null;
if (self.sym) throw new Error(`Unexpected input: ${self.sym}`);
else throw new Error(`Unexpected end of input`);
}
},
_parse3_dice (self) {
const children = [];
// if we've omitted the X in XdY, add it here
if (this._parse3_match(self, Renderer.dice.tk.DICE)) children.push(new Renderer.dice.parsed.Factor(Renderer.dice.tk.NUMBER(1)));
else children.push(this._parse3_factor(self));
while (this._parse3_match(self, Renderer.dice.tk.DICE)) {
this._parse3_nextSym(self);
children.push(this._parse3_factor(self));
this._parse3__dice_modifiers(self, children);
}
return new Renderer.dice.parsed.Dice(children);
},
_parse3__dice_modifiers (self, children) { // used in both dice and dice pools
// Collect together all dice mods
const modsMeta = new Renderer.dice.lang.DiceModMeta();
while (
this._parse3_match(self, Renderer.dice.tk.DROP_HIGHEST)
|| this._parse3_match(self, Renderer.dice.tk.KEEP_HIGHEST)
|| this._parse3_match(self, Renderer.dice.tk.DROP_LOWEST)
|| this._parse3_match(self, Renderer.dice.tk.KEEP_LOWEST)
|| this._parse3_match(self, Renderer.dice.tk.REROLL_EXACT)
|| this._parse3_match(self, Renderer.dice.tk.REROLL_GT)
|| this._parse3_match(self, Renderer.dice.tk.REROLL_GTEQ)
|| this._parse3_match(self, Renderer.dice.tk.REROLL_LT)
|| this._parse3_match(self, Renderer.dice.tk.REROLL_LTEQ)
|| this._parse3_match(self, Renderer.dice.tk.EXPLODE_EXACT)
|| this._parse3_match(self, Renderer.dice.tk.EXPLODE_GT)
|| this._parse3_match(self, Renderer.dice.tk.EXPLODE_GTEQ)
|| this._parse3_match(self, Renderer.dice.tk.EXPLODE_LT)
|| this._parse3_match(self, Renderer.dice.tk.EXPLODE_LTEQ)
|| this._parse3_match(self, Renderer.dice.tk.COUNT_SUCCESS_EXACT)
|| this._parse3_match(self, Renderer.dice.tk.COUNT_SUCCESS_GT)
|| this._parse3_match(self, Renderer.dice.tk.COUNT_SUCCESS_GTEQ)
|| this._parse3_match(self, Renderer.dice.tk.COUNT_SUCCESS_LT)
|| this._parse3_match(self, Renderer.dice.tk.COUNT_SUCCESS_LTEQ)
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_EXACT)
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_GT)
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_GTEQ)
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_LT)
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_LTEQ)
) {
const nxtSym = this._parse3_nextSym(self);
const nxtFactor = this._parse3__dice_modifiers_nxtFactor(self, nxtSym);
if (nxtSym.isSuccessMode) modsMeta.isSuccessMode = true;
modsMeta.mods.push({modSym: nxtSym, numSym: nxtFactor});
}
if (modsMeta.mods.length) children.push(modsMeta);
},
_parse3__dice_modifiers_nxtFactor (self, nxtSym) {
if (nxtSym.diceModifierImplicit == null) return this._parse3_factor(self, {isSilent: true});
const fallback = new Renderer.dice.parsed.Factor(Renderer.dice.tk.NUMBER(nxtSym.diceModifierImplicit));
if (self.sym == null) return fallback;
const out = this._parse3_factor(self, {isSilent: true});
if (out) return out;
return fallback;
},
_parse3_exponent (self) {
const children = [];
children.push(this._parse3_dice(self));
while (this._parse3_match(self, Renderer.dice.tk.POW)) {
this._parse3_nextSym(self);
children.push(this._parse3_dice(self));
}
return new Renderer.dice.parsed.Exponent(children);
},
_parse3_term (self) {
const children = [];
children.push(this._parse3_exponent(self));
while (this._parse3_match(self, Renderer.dice.tk.MULT) || this._parse3_match(self, Renderer.dice.tk.DIV)) {
children.push(this._parse3_nextSym(self));
children.push(this._parse3_exponent(self));
}
return new Renderer.dice.parsed.Term(children);
},
_parse3_expression (self) {
const children = [];
if (this._parse3_match(self, Renderer.dice.tk.ADD) || this._parse3_match(self, Renderer.dice.tk.SUB)) children.push(this._parse3_nextSym(self));
children.push(this._parse3_term(self));
while (this._parse3_match(self, Renderer.dice.tk.ADD) || this._parse3_match(self, Renderer.dice.tk.SUB)) {
children.push(this._parse3_nextSym(self));
children.push(this._parse3_term(self));
}
return new Renderer.dice.parsed.Expression(children);
},
// endregion
// region Utilities
DiceModMeta: class {
constructor () {
this.isDiceModifierGroup = true;
this.isSuccessMode = false;
this.mods = [];
}
},
// endregion
};
Renderer.dice.tk = {
Token: class {
/**
* @param type
* @param value
* @param asString
* @param [opts] Options object.
* @param [opts.isDiceModifier] If the token is a dice modifier, e.g. "dl"
* @param [opts.diceModifierImplicit] If the dice modifier has an implicit value (e.g. "kh" is shorthand for "kh1")
* @param [opts.isSuccessMode] If the token is a "success"-based dice modifier, e.g. "cs="
*/
constructor (type, value, asString, opts) {
opts = opts || {};
this.type = type;
this.value = value;
this._asString = asString;
if (opts.isDiceModifier) this.isDiceModifier = true;
if (opts.diceModifierImplicit) this.diceModifierImplicit = true;
if (opts.isSuccessMode) this.isSuccessMode = true;
}
eq (other) { return other && other.type === this.type; }
toString () {
if (this._asString) return this._asString;
return this.toDebugString();
}
toDebugString () { return `${this.type}${this.value ? ` :: ${this.value}` : ""}`; }
},
_new (type, asString, opts) { return new Renderer.dice.tk.Token(type, null, asString, opts); },
TYP_NUMBER: "NUMBER",
TYP_DICE: "DICE",
TYP_SYMBOL: "SYMBOL", // Cannot be created by lexing, only parsing
NUMBER (val) { return new Renderer.dice.tk.Token(Renderer.dice.tk.TYP_NUMBER, val); },
};
Renderer.dice.tk.PAREN_OPEN = Renderer.dice.tk._new("PAREN_OPEN", "(");
Renderer.dice.tk.PAREN_CLOSE = Renderer.dice.tk._new("PAREN_CLOSE", ")");
Renderer.dice.tk.BRACE_OPEN = Renderer.dice.tk._new("BRACE_OPEN", "{");
Renderer.dice.tk.BRACE_CLOSE = Renderer.dice.tk._new("BRACE_CLOSE", "}");
Renderer.dice.tk.COMMA = Renderer.dice.tk._new("COMMA", ",");
Renderer.dice.tk.ADD = Renderer.dice.tk._new("ADD", "+");
Renderer.dice.tk.SUB = Renderer.dice.tk._new("SUB", "-");
Renderer.dice.tk.MULT = Renderer.dice.tk._new("MULT", "*");
Renderer.dice.tk.DIV = Renderer.dice.tk._new("DIV", "/");
Renderer.dice.tk.POW = Renderer.dice.tk._new("POW", "^");
Renderer.dice.tk.PB = Renderer.dice.tk._new("PB", "pb");
Renderer.dice.tk.SUMMON_SPELL_LEVEL = Renderer.dice.tk._new("SUMMON_SPELL_LEVEL", "summonspelllevel");
Renderer.dice.tk.SUMMON_CLASS_LEVEL = Renderer.dice.tk._new("SUMMON_CLASS_LEVEL", "summonclasslevel");
Renderer.dice.tk.FLOOR = Renderer.dice.tk._new("FLOOR", "floor");
Renderer.dice.tk.CEIL = Renderer.dice.tk._new("CEIL", "ceil");
Renderer.dice.tk.ROUND = Renderer.dice.tk._new("ROUND", "round");
Renderer.dice.tk.AVERAGE = Renderer.dice.tk._new("AVERAGE", "avg");
Renderer.dice.tk.DMAX = Renderer.dice.tk._new("DMAX", "avg");
Renderer.dice.tk.DMIN = Renderer.dice.tk._new("DMIN", "avg");
Renderer.dice.tk.SIGN = Renderer.dice.tk._new("SIGN", "sign");
Renderer.dice.tk.ABS = Renderer.dice.tk._new("ABS", "abs");
Renderer.dice.tk.CBRT = Renderer.dice.tk._new("CBRT", "cbrt");
Renderer.dice.tk.SQRT = Renderer.dice.tk._new("SQRT", "sqrt");
Renderer.dice.tk.EXP = Renderer.dice.tk._new("EXP", "exp");
Renderer.dice.tk.LOG = Renderer.dice.tk._new("LOG", "log");
Renderer.dice.tk.RANDOM = Renderer.dice.tk._new("RANDOM", "random");
Renderer.dice.tk.TRUNC = Renderer.dice.tk._new("TRUNC", "trunc");
Renderer.dice.tk.POW = Renderer.dice.tk._new("POW", "pow");
Renderer.dice.tk.MAX = Renderer.dice.tk._new("MAX", "max");
Renderer.dice.tk.MIN = Renderer.dice.tk._new("MIN", "min");
Renderer.dice.tk.DICE = Renderer.dice.tk._new("DICE", "d");
Renderer.dice.tk.DROP_HIGHEST = Renderer.dice.tk._new("DH", "dh", {isDiceModifier: true, diceModifierImplicit: 1});
Renderer.dice.tk.KEEP_HIGHEST = Renderer.dice.tk._new("KH", "kh", {isDiceModifier: true, diceModifierImplicit: 1});
Renderer.dice.tk.DROP_LOWEST = Renderer.dice.tk._new("DL", "dl", {isDiceModifier: true, diceModifierImplicit: 1});
Renderer.dice.tk.KEEP_LOWEST = Renderer.dice.tk._new("KL", "kl", {isDiceModifier: true, diceModifierImplicit: 1});
Renderer.dice.tk.REROLL_EXACT = Renderer.dice.tk._new("REROLL", "r", {isDiceModifier: true});
Renderer.dice.tk.REROLL_GT = Renderer.dice.tk._new("REROLL_GT", "r>", {isDiceModifier: true});
Renderer.dice.tk.REROLL_GTEQ = Renderer.dice.tk._new("REROLL_GTEQ", "r>=", {isDiceModifier: true});
Renderer.dice.tk.REROLL_LT = Renderer.dice.tk._new("REROLL_LT", "r<", {isDiceModifier: true});
Renderer.dice.tk.REROLL_LTEQ = Renderer.dice.tk._new("REROLL_LTEQ", "r<=", {isDiceModifier: true});
Renderer.dice.tk.EXPLODE_EXACT = Renderer.dice.tk._new("EXPLODE", "x", {isDiceModifier: true});
Renderer.dice.tk.EXPLODE_GT = Renderer.dice.tk._new("EXPLODE_GT", "x>", {isDiceModifier: true});
Renderer.dice.tk.EXPLODE_GTEQ = Renderer.dice.tk._new("EXPLODE_GTEQ", "x>=", {isDiceModifier: true});
Renderer.dice.tk.EXPLODE_LT = Renderer.dice.tk._new("EXPLODE_LT", "x<", {isDiceModifier: true});
Renderer.dice.tk.EXPLODE_LTEQ = Renderer.dice.tk._new("EXPLODE_LTEQ", "x<=", {isDiceModifier: true});
Renderer.dice.tk.COUNT_SUCCESS_EXACT = Renderer.dice.tk._new("COUNT_SUCCESS_EXACT", "cs=", {isDiceModifier: true, isSuccessMode: true});
Renderer.dice.tk.COUNT_SUCCESS_GT = Renderer.dice.tk._new("COUNT_SUCCESS_GT", "cs>", {isDiceModifier: true, isSuccessMode: true});
Renderer.dice.tk.COUNT_SUCCESS_GTEQ = Renderer.dice.tk._new("COUNT_SUCCESS_GTEQ", "cs>=", {isDiceModifier: true, isSuccessMode: true});
Renderer.dice.tk.COUNT_SUCCESS_LT = Renderer.dice.tk._new("COUNT_SUCCESS_LT", "cs<", {isDiceModifier: true, isSuccessMode: true});
Renderer.dice.tk.COUNT_SUCCESS_LTEQ = Renderer.dice.tk._new("COUNT_SUCCESS_LTEQ", "cs<=", {isDiceModifier: true, isSuccessMode: true});
Renderer.dice.tk.MARGIN_SUCCESS_EXACT = Renderer.dice.tk._new("MARGIN_SUCCESS_EXACT", "ms=", {isDiceModifier: true});
Renderer.dice.tk.MARGIN_SUCCESS_GT = Renderer.dice.tk._new("MARGIN_SUCCESS_GT", "ms>", {isDiceModifier: true});
Renderer.dice.tk.MARGIN_SUCCESS_GTEQ = Renderer.dice.tk._new("MARGIN_SUCCESS_GTEQ", "ms>=", {isDiceModifier: true});
Renderer.dice.tk.MARGIN_SUCCESS_LT = Renderer.dice.tk._new("MARGIN_SUCCESS_LT", "ms<", {isDiceModifier: true});
Renderer.dice.tk.MARGIN_SUCCESS_LTEQ = Renderer.dice.tk._new("MARGIN_SUCCESS_LTEQ", "ms<=", {isDiceModifier: true});
Renderer.dice.AbstractSymbol = class {
constructor () { this.type = Renderer.dice.tk.TYP_SYMBOL; }
eq (symbol) { return symbol && this.type === symbol.type; }
evl (meta) { this.meta = meta; return this._evl(meta); }
avg (meta) { this.meta = meta; return this._avg(meta); }
min (meta) { this.meta = meta; return this._min(meta); } // minimum value of all _rolls_, not the minimum possible result
max (meta) { this.meta = meta; return this._max(meta); } // maximum value of all _rolls_, not the maximum possible result
_evl () { throw new Error("Unimplemented!"); }
_avg () { throw new Error("Unimplemented!"); }
_min () { throw new Error("Unimplemented!"); } // minimum value of all _rolls_, not the minimum possible result
_max () { throw new Error("Unimplemented!"); } // maximum value of all _rolls_, not the maximum possible result
toString () { throw new Error("Unimplemented!"); }
addToMeta (meta, {text, html = null, md = null} = {}) {
if (!meta) return;
html = html || text;
md = md || text;
(meta.html = meta.html || []).push(html);
(meta.text = meta.text || []).push(text);
(meta.md = meta.md || []).push(md);
}
};
Renderer.dice.parsed = {
_PARTITION_EQ: (r, compareTo) => r === compareTo,
_PARTITION_GT: (r, compareTo) => r > compareTo,
_PARTITION_GTEQ: (r, compareTo) => r >= compareTo,
_PARTITION_LT: (r, compareTo) => r < compareTo,
_PARTITION_LTEQ: (r, compareTo) => r <= compareTo,
/**
* @param fnName
* @param meta
* @param vals
* @param nodeMod
* @param opts Options object.
* @param [opts.fnGetRerolls] Function which takes a set of rolls to be rerolled and generates the next set of rolls.
* @param [opts.fnGetExplosions] Function which takes a set of rolls to be exploded and generates the next set of rolls.
* @param [opts.faces]
*/
_handleModifiers (fnName, meta, vals, nodeMod, opts) {
opts = opts || {};
const displayVals = vals.slice(); // copy the array so we can sort the original
const {mods} = nodeMod;
for (const mod of mods) {
vals.sort(SortUtil.ascSortProp.bind(null, "val")).reverse();
const valsAlive = vals.filter(it => !it.isDropped);
const modNum = mod.numSym[fnName]();
switch (mod.modSym.type) {
case Renderer.dice.tk.DROP_HIGHEST.type:
case Renderer.dice.tk.KEEP_HIGHEST.type:
case Renderer.dice.tk.DROP_LOWEST.type:
case Renderer.dice.tk.KEEP_LOWEST.type: {
const isHighest = mod.modSym.type.endsWith("H");
const splitPoint = isHighest ? modNum : valsAlive.length - modNum;
const highSlice = valsAlive.slice(0, splitPoint);
const lowSlice = valsAlive.slice(splitPoint, valsAlive.length);
switch (mod.modSym.type) {
case Renderer.dice.tk.DROP_HIGHEST.type:
case Renderer.dice.tk.KEEP_LOWEST.type:
highSlice.forEach(val => val.isDropped = true);
break;
case Renderer.dice.tk.KEEP_HIGHEST.type:
case Renderer.dice.tk.DROP_LOWEST.type:
lowSlice.forEach(val => val.isDropped = true);
break;
default: throw new Error(`Unimplemented!`);
}
break;
}
case Renderer.dice.tk.REROLL_EXACT.type:
case Renderer.dice.tk.REROLL_GT.type:
case Renderer.dice.tk.REROLL_GTEQ.type:
case Renderer.dice.tk.REROLL_LT.type:
case Renderer.dice.tk.REROLL_LTEQ.type: {
let fnPartition;
switch (mod.modSym.type) {
case Renderer.dice.tk.REROLL_EXACT.type: fnPartition = Renderer.dice.parsed._PARTITION_EQ; break;
case Renderer.dice.tk.REROLL_GT.type: fnPartition = Renderer.dice.parsed._PARTITION_GT; break;
case Renderer.dice.tk.REROLL_GTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_GTEQ; break;
case Renderer.dice.tk.REROLL_LT.type: fnPartition = Renderer.dice.parsed._PARTITION_LT; break;
case Renderer.dice.tk.REROLL_LTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_LTEQ; break;
default: throw new Error(`Unimplemented!`);
}
const toReroll = valsAlive.filter(val => fnPartition(val.val, modNum));
toReroll.forEach(val => val.isDropped = true);
const nuVals = opts.fnGetRerolls(toReroll);
vals.push(...nuVals);
displayVals.push(...nuVals);
break;
}
case Renderer.dice.tk.EXPLODE_EXACT.type:
case Renderer.dice.tk.EXPLODE_GT.type:
case Renderer.dice.tk.EXPLODE_GTEQ.type:
case Renderer.dice.tk.EXPLODE_LT.type:
case Renderer.dice.tk.EXPLODE_LTEQ.type: {
let fnPartition;
switch (mod.modSym.type) {
case Renderer.dice.tk.EXPLODE_EXACT.type: fnPartition = Renderer.dice.parsed._PARTITION_EQ; break;
case Renderer.dice.tk.EXPLODE_GT.type: fnPartition = Renderer.dice.parsed._PARTITION_GT; break;
case Renderer.dice.tk.EXPLODE_GTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_GTEQ; break;
case Renderer.dice.tk.EXPLODE_LT.type: fnPartition = Renderer.dice.parsed._PARTITION_LT; break;
case Renderer.dice.tk.EXPLODE_LTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_LTEQ; break;
default: throw new Error(`Unimplemented!`);
}
let tries = 999; // limit the maximum explosions to a sane amount
let lastLen;
let toExplodeNext = valsAlive;
do {
lastLen = vals.length;
const [toExplode] = toExplodeNext.partition(roll => !roll.isExploded && fnPartition(roll.val, modNum));
toExplode.forEach(roll => roll.isExploded = true);
const nuVals = opts.fnGetExplosions(toExplode);
// cache the new rolls, to improve performance over massive explosion sets
toExplodeNext = nuVals;
vals.push(...nuVals);
displayVals.push(...nuVals);
} while (tries-- > 0 && vals.length !== lastLen);
if (!~tries) JqueryUtil.doToast({type: "warning", content: `Stopped exploding after 999 additional rolls.`});
break;
}
case Renderer.dice.tk.COUNT_SUCCESS_EXACT.type:
case Renderer.dice.tk.COUNT_SUCCESS_GT.type:
case Renderer.dice.tk.COUNT_SUCCESS_GTEQ.type:
case Renderer.dice.tk.COUNT_SUCCESS_LT.type:
case Renderer.dice.tk.COUNT_SUCCESS_LTEQ.type: {
let fnPartition;
switch (mod.modSym.type) {
case Renderer.dice.tk.COUNT_SUCCESS_EXACT.type: fnPartition = Renderer.dice.parsed._PARTITION_EQ; break;
case Renderer.dice.tk.COUNT_SUCCESS_GT.type: fnPartition = Renderer.dice.parsed._PARTITION_GT; break;
case Renderer.dice.tk.COUNT_SUCCESS_GTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_GTEQ; break;
case Renderer.dice.tk.COUNT_SUCCESS_LT.type: fnPartition = Renderer.dice.parsed._PARTITION_LT; break;
case Renderer.dice.tk.COUNT_SUCCESS_LTEQ.type: fnPartition = Renderer.dice.parsed._PARTITION_LTEQ; break;
default: throw new Error(`Unimplemented!`);
}
const successes = valsAlive.filter(val => fnPartition(val.val, modNum));
successes.forEach(val => val.isSuccess = true);
break;
}
case Renderer.dice.tk.MARGIN_SUCCESS_EXACT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_GT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_GTEQ.type:
case Renderer.dice.tk.MARGIN_SUCCESS_LT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_LTEQ.type: {
const total = valsAlive.map(it => it.val).reduce((valA, valB) => valA + valB, 0);
const subDisplayDice = displayVals.map(r => `[${Renderer.dice.parsed._rollToNumPart_html(r, opts.faces)}]`).join("+");
let delta;
let subDisplay;
switch (mod.modSym.type) {
case Renderer.dice.tk.MARGIN_SUCCESS_EXACT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_GT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_GTEQ.type: {
delta = total - modNum;
subDisplay = `(${subDisplayDice})-${modNum}`;
break;
}
case Renderer.dice.tk.MARGIN_SUCCESS_LT.type:
case Renderer.dice.tk.MARGIN_SUCCESS_LTEQ.type: {
delta = modNum - total;
subDisplay = `${modNum}-(${subDisplayDice})`;
break;
}
default: throw new Error(`Unimplemented!`);
}
while (vals.length) {
vals.pop();
displayVals.pop();
}
vals.push({val: delta});
displayVals.push({val: delta, htmlDisplay: subDisplay});
break;
}
default: throw new Error(`Unimplemented!`);
}
}
return displayVals;
},
_rollToNumPart_html (r, faces) {
if (faces == null) return r.val;
return r.val === faces ? `${r.val}` : r.val === 1 ? `${r.val}` : r.val;
},
Function: class extends Renderer.dice.AbstractSymbol {
constructor (nodes) {
super();
this._nodes = nodes;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
const [symFunc] = this._nodes;
switch (symFunc.type) {
case Renderer.dice.tk.FLOOR.type:
case Renderer.dice.tk.CEIL.type:
case Renderer.dice.tk.ROUND.type:
case Renderer.dice.tk.SIGN.type:
case Renderer.dice.tk.CBRT.type:
case Renderer.dice.tk.SQRT.type:
case Renderer.dice.tk.EXP.type:
case Renderer.dice.tk.LOG.type:
case Renderer.dice.tk.RANDOM.type:
case Renderer.dice.tk.TRUNC.type:
case Renderer.dice.tk.POW.type:
case Renderer.dice.tk.MAX.type:
case Renderer.dice.tk.MIN.type: {
const [, ...symExps] = this._nodes;
this.addToMeta(meta, {text: `${symFunc.toString()}(`});
const args = [];
symExps.forEach((symExp, i) => {
if (i !== 0) this.addToMeta(meta, {text: `, `});
args.push(symExp[fnName](meta));
});
const out = Math[symFunc.toString()](...args);
this.addToMeta(meta, {text: ")"});
return out;
}
case Renderer.dice.tk.AVERAGE.type: {
const [, symExp] = this._nodes;
return symExp.avg(meta);
}
case Renderer.dice.tk.DMAX.type: {
const [, symExp] = this._nodes;
return symExp.max(meta);
}
case Renderer.dice.tk.DMIN.type: {
const [, symExp] = this._nodes;
return symExp.min(meta);
}
default: throw new Error(`Unimplemented!`);
}
}
toString () {
let out;
const [symFunc, symExp] = this._nodes;
switch (symFunc.type) {
case Renderer.dice.tk.FLOOR.type:
case Renderer.dice.tk.CEIL.type:
case Renderer.dice.tk.ROUND.type:
case Renderer.dice.tk.AVERAGE.type:
case Renderer.dice.tk.DMAX.type:
case Renderer.dice.tk.DMIN.type:
case Renderer.dice.tk.SIGN.type:
case Renderer.dice.tk.ABS.type:
case Renderer.dice.tk.CBRT.type:
case Renderer.dice.tk.SQRT.type:
case Renderer.dice.tk.EXP.type:
case Renderer.dice.tk.LOG.type:
case Renderer.dice.tk.RANDOM.type:
case Renderer.dice.tk.TRUNC.type:
case Renderer.dice.tk.POW.type:
case Renderer.dice.tk.MAX.type:
case Renderer.dice.tk.MIN.type:
out = symFunc.toString(); break;
default: throw new Error(`Unimplemented!`);
}
out += `(${symExp.toString()})`;
return out;
}
},
Pool: class extends Renderer.dice.AbstractSymbol {
constructor (nodesPool, nodeMod) {
super();
this._nodesPool = nodesPool;
this._nodeMod = nodeMod;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
const vals = this._nodesPool.map(it => {
const subMeta = {};
return {node: it, val: it[fnName](subMeta), meta: subMeta};
});
if (this._nodeMod && vals.length) {
const isSuccessMode = this._nodeMod.isSuccessMode;
const modOpts = {
fnGetRerolls: toReroll => toReroll.map(val => {
const subMeta = {};
return {node: val.node, val: val.node[fnName](subMeta), meta: subMeta};
}),
fnGetExplosions: toExplode => toExplode.map(val => {
const subMeta = {};
return {node: val.node, val: val.node[fnName](subMeta), meta: subMeta};
}),
};
const displayVals = Renderer.dice.parsed._handleModifiers(fnName, meta, vals, this._nodeMod, modOpts);
const asHtml = displayVals.map(v => {
const html = v.meta.html.join("");
if (v.isDropped) return `(${html})`;
else if (v.isExploded) return `(${html})`;
else if (v.isSuccess) return `(${html})`;
else return `(${html})`;
}).join("+");
const asText = displayVals.map(v => `(${v.meta.text.join("")})`).join("+");
const asMd = displayVals.map(v => `(${v.meta.md.join("")})`).join("+");
this.addToMeta(meta, {html: asHtml, text: asText, md: asMd});
if (isSuccessMode) {
return vals.filter(it => !it.isDropped && it.isSuccess).length;
} else {
return vals.filter(it => !it.isDropped).map(it => it.val).sum();
}
} else {
this.addToMeta(
meta,
["html", "text", "md"].mergeMap(prop => ({
[prop]: `${vals.map(it => `(${it.meta[prop].join("")})`).join("+")}`,
})),
);
return vals.map(it => it.val).sum();
}
}
toString () {
return `{${this._nodesPool.map(it => it.toString()).join(", ")}}${this._nodeMod ? this._nodeMod.toString() : ""}`;
}
},
Factor: class extends Renderer.dice.AbstractSymbol {
constructor (node, opts) {
super();
opts = opts || {};
this._node = node;
this._hasParens = !!opts.hasParens;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
switch (this._node.type) {
case Renderer.dice.tk.TYP_NUMBER: {
this.addToMeta(meta, {text: this.toString()});
return Number(this._node.value);
}
case Renderer.dice.tk.TYP_SYMBOL: {
if (this._hasParens) this.addToMeta(meta, {text: "("});
const out = this._node[fnName](meta);
if (this._hasParens) this.addToMeta(meta, {text: ")"});
return out;
}
case Renderer.dice.tk.PB.type: {
this.addToMeta(meta, {text: this.toString(meta)});
return meta.pb == null ? 0 : meta.pb;
}
case Renderer.dice.tk.SUMMON_SPELL_LEVEL.type: {
this.addToMeta(meta, {text: this.toString(meta)});
return meta.summonSpellLevel == null ? 0 : meta.summonSpellLevel;
}
case Renderer.dice.tk.SUMMON_CLASS_LEVEL.type: {
this.addToMeta(meta, {text: this.toString(meta)});
return meta.summonClassLevel == null ? 0 : meta.summonClassLevel;
}
default: throw new Error(`Unimplemented!`);
}
}
toString (indent) {
let out;
switch (this._node.type) {
case Renderer.dice.tk.TYP_NUMBER: out = this._node.value; break;
case Renderer.dice.tk.TYP_SYMBOL: out = this._node.toString(); break;
case Renderer.dice.tk.PB.type: out = this.meta ? (this.meta.pb || 0) : "PB"; break;
case Renderer.dice.tk.SUMMON_SPELL_LEVEL.type: out = this.meta ? (this.meta.summonSpellLevel || 0) : "the spell's level"; break;
case Renderer.dice.tk.SUMMON_CLASS_LEVEL.type: out = this.meta ? (this.meta.summonClassLevel || 0) : "your class level"; break;
default: throw new Error(`Unimplemented!`);
}
return this._hasParens ? `(${out})` : out;
}
},
Dice: class extends Renderer.dice.AbstractSymbol {
static _facesToValue (faces, fnName) {
switch (fnName) {
case "evl": return RollerUtil.randomise(faces);
case "avg": return (faces + 1) / 2;
case "min": return 1;
case "max": return faces;
}
}
constructor (nodes) {
super();
this._nodes = nodes;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
if (this._nodes.length === 1) return this._nodes[0][fnName](meta); // if it's just a factor
// N.B. we don't pass the full "meta" to symbol evaluation inside the dice expression--we therefore won't see
// the metadata from the nested rolls, but that's OK.
const view = this._nodes.slice();
// Shift the first symbol and use that as our initial number of dice
// e.g. the "2" in 2d3d5
const numSym = view.shift();
let tmp = numSym[fnName](Renderer.dice.util.getReducedMeta(meta));
while (view.length) {
if (Math.round(tmp) !== tmp) throw new Error(`Number of dice to roll (${tmp}) was not an integer!`);
// Use the next symbol as our number of faces
// e.g. the "3" in `2d3d5`
// When looping, the number of dice may have been a complex expression with modifiers; take the next
// non-modifier symbol as the faces.
// e.g. the "20" in `(2d3kh1r1)d20` (parentheses for emphasis)
const facesSym = view.shift();
const faces = facesSym[fnName]();
if (Math.round(faces) !== faces) throw new Error(`Dice face count (${faces}) was not an integer!`);
const isLast = view.length === 0 || (view.length === 1 && view.last().isDiceModifierGroup);
tmp = this._invoke_handlePart(fnName, meta, view, tmp, faces, isLast);
}
return tmp;
}
_invoke_handlePart (fnName, meta, view, num, faces, isLast) {
const rolls = [...new Array(num)].map(() => ({val: Renderer.dice.parsed.Dice._facesToValue(faces, fnName)}));
let displayRolls;
let isSuccessMode = false;
if (view.length && view[0].isDiceModifierGroup) {
const nodeMod = view[0];
if (fnName === "evl" || fnName === "min" || fnName === "max") { // avoid handling dice modifiers in "average" mode
isSuccessMode = nodeMod.isSuccessMode;
const modOpts = {
faces,
fnGetRerolls: toReroll => [...new Array(toReroll.length)].map(() => ({val: Renderer.dice.parsed.Dice._facesToValue(faces, fnName)})),
fnGetExplosions: toExplode => [...new Array(toExplode.length)].map(() => ({val: Renderer.dice.parsed.Dice._facesToValue(faces, fnName)})),
};
displayRolls = Renderer.dice.parsed._handleModifiers(fnName, meta, rolls, nodeMod, modOpts);
}
view.shift();
} else displayRolls = rolls;
if (isLast) { // only display the dice for the final roll, e.g. in 2d3d4 show the Xd4
const asHtml = displayRolls.map(r => {
if (r.htmlDisplay) return r.htmlDisplay;
const numPart = Renderer.dice.parsed._rollToNumPart_html(r, faces);
if (r.isDropped) return `[${numPart}]`;
else if (r.isExploded) return `[${numPart}]`;
else if (r.isSuccess) return `[${numPart}]`;
else return `[${numPart}]`;
}).join("+");
const asText = displayRolls.map(r => `[${r.val}]`).join("+");
const asMd = displayRolls.map(r => {
if (r.isDropped) return `~~[${r.val}]~~`;
else if (r.isExploded) return `_[${r.val}]_`;
else if (r.isSuccess) return `**[${r.val}]**`;
else return `[${r.val}]`;
}).join("+");
this.addToMeta(
meta,
{
html: asHtml,
text: asText,
md: asMd,
},
);
}
if (fnName === "evl") {
const maxRolls = rolls.filter(it => it.val === faces && !it.isDropped);
const minRolls = rolls.filter(it => it.val === 1 && !it.isDropped);
meta.allMax = meta.allMax || [];
meta.allMin = meta.allMin || [];
meta.allMax.push(maxRolls.length && maxRolls.length === rolls.length);
meta.allMin.push(minRolls.length && minRolls.length === rolls.length);
}
if (isSuccessMode) {
return rolls.filter(it => !it.isDropped && it.isSuccess).length;
} else {
return rolls.filter(it => !it.isDropped).map(it => it.val).sum();
}
}
toString () {
if (this._nodes.length === 1) return this._nodes[0].toString(); // if it's just a factor
const [numSym, facesSym] = this._nodes;
let out = `${numSym.toString()}d${facesSym.toString()}`;
for (let i = 2; i < this._nodes.length; ++i) {
const n = this._nodes[i];
if (n.isDiceModifierGroup) out += n.mods.map(it => `${it.modSym.toString()}${it.numSym.toString()}`).join("");
else out += `d${n.toString()}`;
}
return out;
}
},
Exponent: class extends Renderer.dice.AbstractSymbol {
constructor (nodes) {
super();
this._nodes = nodes;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
const view = this._nodes.slice();
let val = view.pop()[fnName](meta);
while (view.length) {
this.addToMeta(meta, {text: "^"});
val = view.pop()[fnName](meta) ** val;
}
return val;
}
toString () {
const view = this._nodes.slice();
let out = view.pop().toString();
while (view.length) out = `${view.pop().toString()}^${out}`;
return out;
}
},
Term: class extends Renderer.dice.AbstractSymbol {
constructor (nodes) {
super();
this._nodes = nodes;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
let out = this._nodes[0][fnName](meta);
for (let i = 1; i < this._nodes.length; i += 2) {
if (this._nodes[i].eq(Renderer.dice.tk.MULT)) {
this.addToMeta(meta, {text: " × "});
out *= this._nodes[i + 1][fnName](meta);
} else if (this._nodes[i].eq(Renderer.dice.tk.DIV)) {
this.addToMeta(meta, {text: " ÷ "});
out /= this._nodes[i + 1][fnName](meta);
} else throw new Error(`Unimplemented!`);
}
return out;
}
toString () {
let out = this._nodes[0].toString();
for (let i = 1; i < this._nodes.length; i += 2) {
if (this._nodes[i].eq(Renderer.dice.tk.MULT)) out += ` * ${this._nodes[i + 1].toString()}`;
else if (this._nodes[i].eq(Renderer.dice.tk.DIV)) out += ` / ${this._nodes[i + 1].toString()}`;
else throw new Error(`Unimplemented!`);
}
return out;
}
},
Expression: class extends Renderer.dice.AbstractSymbol {
constructor (nodes) {
super();
this._nodes = nodes;
}
_evl (meta) { return this._invoke("evl", meta); }
_avg (meta) { return this._invoke("avg", meta); }
_min (meta) { return this._invoke("min", meta); }
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
const view = this._nodes.slice();
let isNeg = false;
if (view[0].eq(Renderer.dice.tk.ADD) || view[0].eq(Renderer.dice.tk.SUB)) {
isNeg = view.shift().eq(Renderer.dice.tk.SUB);
if (isNeg) this.addToMeta(meta, {text: "-"});
}
let out = view[0][fnName](meta);
if (isNeg) out = -out;
for (let i = 1; i < view.length; i += 2) {
if (view[i].eq(Renderer.dice.tk.ADD)) {
this.addToMeta(meta, {text: " + "});
out += view[i + 1][fnName](meta);
} else if (view[i].eq(Renderer.dice.tk.SUB)) {
this.addToMeta(meta, {text: " - "});
out -= view[i + 1][fnName](meta);
} else throw new Error(`Unimplemented!`);
}
return out;
}
toString (indent = 0) {
let out = "";
const view = this._nodes.slice();
let isNeg = false;
if (view[0].eq(Renderer.dice.tk.ADD) || view[0].eq(Renderer.dice.tk.SUB)) {
isNeg = view.shift().eq(Renderer.dice.tk.SUB);
if (isNeg) out += "-";
}
out += view[0].toString(indent);
for (let i = 1; i < view.length; i += 2) {
if (view[i].eq(Renderer.dice.tk.ADD)) out += ` + ${view[i + 1].toString(indent)}`;
else if (view[i].eq(Renderer.dice.tk.SUB)) out += ` - ${view[i + 1].toString(indent)}`;
else throw new Error(`Unimplemented!`);
}
return out;
}
},
};
if (!IS_VTT && typeof window !== "undefined") {
window.addEventListener("load", Renderer.dice._pInit);
}