@@ -217,7 +217,7 @@ class Omnisearch {
static $getResultLink (r) {
const href = r.c === Parser.CAT_ID_PAGE ? r.u : `${Renderer.get().baseUrl}${UrlUtil.categoryToPage(r.c)}#${r.uh || r.u}`;
- return $(`
${r.cf}: ${r.n}`);
+ return $(`
${r.cf}: ${r.n}${r.a ? ` (${r.a.toTitleCase()})` : ""}`);
}
static _pDoSearch_renderLinks (results, page = 0) {
diff --git a/js/parser.js b/js/parser.js
index ee8ccc3cba..3354b6bc55 100644
--- a/js/parser.js
+++ b/js/parser.js
@@ -1613,6 +1613,7 @@ Parser.TAG_TO_DEFAULT_SOURCE = {
"familiarAbility": SRC_CRB,
"companion": SRC_CRB,
"eidolon": SRC_SOM,
+ "optfeature": SRC_APG,
};
Parser.getTagSource = function (tag, source) {
if (source && source.trim()) return source;
diff --git a/js/render-dice.js b/js/render-dice.js
index eb1321fd36..ed45744318 100644
--- a/js/render-dice.js
+++ b/js/render-dice.js
@@ -3,6 +3,7 @@ Renderer.dice = {
name: "Desna", // 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,
@@ -116,23 +117,37 @@ Renderer.dice = {
});
const $outRoll = $(`
`);
const $iptRoll = $(`
`)
- .on("keypress", async e => {
- if (e.which === 13) { // return
- await Renderer.dice.pRoll2($iptRoll.val(), {
+ .on("keypress", async evt => {
+ evt.stopPropagation();
+ if (evt.key !== "Enter") return;
+
+ const strDice = $iptRoll.val();
+ const result = await Renderer.dice.pRoll2(
+ strDice,
+ {
isUser: true,
name: "Anon",
- });
- $iptRoll.val("");
+ },
+ );
+ $iptRoll.val("");
+
+ if (result === Renderer.dice._SYMBOL_PARSE_FAILED) {
+ Renderer.dice._showInvalid();
+ $iptRoll.addClass("form-control--error");
}
- e.stopPropagation();
- }).on("keydown", (e) => {
+ }).on("keydown", (evt) => {
+ $iptRoll.removeClass("form-control--error");
+
// arrow keys only work on keydown
- if (e.which === 38) { // up arrow
- e.preventDefault();
+ if (evt.key === "ArrowUp") {
+ evt.preventDefault();
Renderer.dice._prevHistory();
- } else if (e.which === 40) { // down arrow
- e.preventDefault();
- Renderer.dice._nextHistory()
+ return;
+ }
+
+ if (evt.key === "ArrowDown") {
+ evt.preventDefault();
+ Renderer.dice._nextHistory();
}
});
$wrpRoll.append($head).append($outRoll).append($iptRoll);
@@ -164,7 +179,7 @@ Renderer.dice = {
if (!Renderer.dice._hist.length) {
Renderer.dice._histIndex = null;
} else {
- Renderer.dice._histIndex = Math.min(Renderer.dice._hist.length, Math.max(Renderer.dice._histIndex, 0))
+ Renderer.dice._histIndex = Math.min(Renderer.dice._hist.length, Math.max(Renderer.dice._histIndex, 0));
}
},
@@ -205,7 +220,7 @@ Renderer.dice = {
shiftKey = shiftKey || evt.shiftKey;
ctrlKey = ctrlKey || (evt.ctrlKey || evt.metaKey);
cpyRollData.toRoll = it;
- return cpyRollData
+ return cpyRollData;
},
)),
]);
@@ -277,7 +292,7 @@ Renderer.dice = {
return rollDataCpy;
}
},
- )
+ );
}),
]);
@@ -306,6 +321,9 @@ Renderer.dice = {
async pRollerClick (evtMock, ele, packed, name) {
const $ele = $(ele);
const entry = JSON.parse(packed);
+ const additionalData = {...ele.dataset};
+
+ // Aka "getTableName", probably
function attemptToGetNameOfRoll () {
// try use table caption
let titleMaybe = $(ele).closest(`table:not(.stats)`).children(`caption`).text();
@@ -402,13 +420,14 @@ Renderer.dice = {
if (evt.shiftKey) {
if (entry.subType === "damage") { // If SHIFT is held, roll crit
- const dice = [];
// TODO(future) in order for this to correctly catch everything, would need to parse the toRoll as a tree and then pull all dice expressions from the first level of that tree
entry.toRoll = `(${entry.toRoll}) * 2`;
} else if (entry.subType === "d20") { // If SHIFT is held, roll advantage
// If we have a cached d20mod value, use it
if (entry.d20mod != null) entry.toRoll = `2d20dl1${entry.d20mod}`;
else entry.toRoll = entry.toRoll.replace(/^\s*1?\s*d\s*20/, "2d20dl1");
+ } else if (entry.subType === "hit") { // If SHIFT is held, roll MAPx1
+ entry.toRoll = `${entry.toRoll}${Parser.numToBonus(entry.MAP)}`;
} else out.rollCount = 2; // otherwise, just roll twice
}
@@ -419,6 +438,8 @@ Renderer.dice = {
// If we have a cached d20mod value, use it
if (entry.d20mod != null) entry.toRoll = `2d20dh1${entry.d20mod}`;
else entry.toRoll = entry.toRoll.replace(/^\s*1?\s*d\s*20/, "2d20dh1");
+ } else if (entry.subType === "hit") { // If CTRL is held, roll MAPx2
+ entry.toRoll = `${entry.toRoll}${Parser.numToBonus(entry.MAP * 2)}`;
} else out.rollCount = 2; // otherwise, just roll twice
}
@@ -455,6 +476,7 @@ Renderer.dice = {
rolledBy.label = head;
}
const wrpTree = Renderer.dice.lang.getTree3(str);
+ if (!wrpTree) return Renderer.dice._SYMBOL_PARSE_FAILED;
return Renderer.dice._pHandleRoll2(wrpTree, rolledBy, opts);
}
},
@@ -467,6 +489,7 @@ Renderer.dice = {
* @param [opts] Options object.
* @param [opts.isResultUsed] If an input box should be provided for the user to enter the result (manual mode only).
* @param [opts.rollCount]
+ * @param [opts.additionalData]
*/
async pRollEntry (entry, rolledBy, opts) {
opts = opts || {};
@@ -496,9 +519,10 @@ Renderer.dice = {
* @param [opts] Options object.
* @param [opts.fnGetMessage]
* @param [opts.isResultUsed]
+ * @param [opts.additionalData]
*/
async _pHandleRoll2 (wrpTree, rolledBy, opts) {
- opts = opts || {};
+ opts = {...opts};
if (wrpTree.meta && wrpTree.meta.hasPb) {
const userPb = await InputUiUtil.pGetUserNumber({
@@ -601,13 +625,14 @@ Renderer.dice = {
Renderer.dice._scrollBottom();
},
+ _showInvalid () {
+ Renderer.dice._showMessage("Invalid input! Try "/help"", Renderer.dice.SYSTEM_USER);
+ },
+
_validCommands: new Set(["/c", "/cls", "/clear"]),
_handleCommand (com, rolledBy) {
Renderer.dice._showMessage(`
${com}`, rolledBy); // parrot the user's command back to them
const PREF_MACRO = "/macro";
- function showInvalid () {
- Renderer.dice._showMessage("Invalid input! Try "/help"", Renderer.dice.SYSTEM_USER);
- }
function checkLength (arr, desired) {
return arr.length === desired;
@@ -651,6 +676,8 @@ Renderer.dice = {
Rounding; floor(1.5), ceil(1.5), round(1.5)
Average; avg(8d6)
+
+
Other functions; sign(1d6-3), abs(1d6-3), ...etc.
Up and down arrow keys cycle input history.
Anything before a colon is treated as a label (
Fireball: 6d6)
@@ -664,29 +691,29 @@ Use
${PREF_MACRO} list to list saved mac
} else if (com.startsWith(PREF_MACRO)) {
const [_, mode, ...others] = com.split(/\s+/);
- if (!["list", "add", "remove", "clear"].includes(mode)) showInvalid();
+ if (!["list", "add", "remove", "clear"].includes(mode)) Renderer.dice._showInvalid();
else {
switch (mode) {
case "list":
if (checkLength(others, 0)) {
Object.keys(Renderer.dice.storage).forEach(name => {
Renderer.dice._showMessage(`
#${name} \u2014 ${Renderer.dice.storage[name]}`, Renderer.dice.SYSTEM_USER);
- })
+ });
} else {
- showInvalid();
+ Renderer.dice._showInvalid();
}
break;
case "add": {
if (checkLength(others, 2)) {
const [name, macro] = others;
- if (name.includes(" ") || name.includes("#")) showInvalid();
+ if (name.includes(" ") || name.includes("#")) Renderer.dice._showInvalid();
else {
Renderer.dice.storage[name] = macro;
pSave()
.then(() => Renderer.dice._showMessage(`Saved macro
#${name}`, Renderer.dice.SYSTEM_USER));
}
} else {
- showInvalid();
+ Renderer.dice._showInvalid();
}
break;
}
@@ -700,7 +727,7 @@ Use
${PREF_MACRO} list to list saved mac
Renderer.dice._showMessage(`Macro
#${others[0]} not found`, Renderer.dice.SYSTEM_USER);
}
} else {
- showInvalid();
+ Renderer.dice._showInvalid();
}
break;
}
@@ -715,7 +742,7 @@ Use
${PREF_MACRO} list to list saved mac
Renderer.dice._$lastRolledBy = null;
break;
}
- } else showInvalid();
+ } else Renderer.dice._showInvalid();
},
_pHandleSavedRoll (id, rolledBy, opts) {
@@ -795,7 +822,7 @@ Renderer.dice.lang = {
// region Lexer
_M_NUMBER_CHAR: /[0-9.]/,
- _M_SYMBOL_CHAR: /[-+/*^=>
${symbol}`);
},
- _parse3_factor (self) {
+ _parse3_factor (self, {isSilent = false} = {}) {
if (this._parse3_accept(self, Renderer.dice.tk.TYP_NUMBER)) {
- // Workaround for comma-separated numbers--if we're inside a dice pool, treat the commas as dice pool separators.
- // Otherwise, merge together adjacent numbers.
- let braceCount = 0;
- self.syms.find(it => {
- if (it.type === Renderer.dice.tk.BRACE_OPEN.type) braceCount++;
- else if (it.type === Renderer.dice.tk.BRACE_CLOSE.type) braceCount--;
- return it === self.sym;
- });
-
- if (braceCount) {
- return new Renderer.dice.parsed.Factor(self.lastAccepted);
- } else {
+ // 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)) {
@@ -1011,26 +1045,80 @@ Renderer.dice.lang = {
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.AVERAGE)
+ || 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})
+ 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));
@@ -1038,11 +1126,15 @@ Renderer.dice.lang = {
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])
+ 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`);
}
@@ -1051,7 +1143,7 @@ Renderer.dice.lang = {
_parse3_dice (self) {
const children = [];
- // if we've omitting the X in XdY, add it here
+ // 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));
@@ -1094,7 +1186,7 @@ Renderer.dice.lang = {
|| this._parse3_match(self, Renderer.dice.tk.MARGIN_SUCCESS_LTEQ)
) {
const nxtSym = this._parse3_nextSym(self);
- const nxtFactor = this._parse3_factor(self);
+ const nxtFactor = this._parse3__dice_modifiers_nxtFactor(self, nxtSym);
if (nxtSym.isSuccessMode) modsMeta.isSuccessMode = true;
modsMeta.mods.push({modSym: nxtSym, numSym: nxtFactor});
@@ -1103,6 +1195,18 @@ Renderer.dice.lang = {
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));
@@ -1154,6 +1258,7 @@ Renderer.dice.tk = {
* @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) {
@@ -1162,6 +1267,7 @@ Renderer.dice.tk = {
this.value = value;
this._asString = asString;
if (opts.isDiceModifier) this.isDiceModifier = true;
+ if (opts.diceModifierImplicit) this.diceModifierImplicit = true;
if (opts.isSuccessMode) this.isSuccessMode = true;
}
@@ -1172,7 +1278,7 @@ Renderer.dice.tk = {
return this.toDebugString();
}
- toDebugString () { return `${this.type}${this.value ? ` :: ${this.value}` : ""}` }
+ toDebugString () { return `${this.type}${this.value ? ` :: ${this.value}` : ""}`; }
},
_new (type, asString, opts) { return new Renderer.dice.tk.Token(type, null, asString, opts); },
@@ -1198,11 +1304,22 @@ 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.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});
-Renderer.dice.tk.KEEP_HIGHEST = Renderer.dice.tk._new("KH", "kh", {isDiceModifier: true});
-Renderer.dice.tk.DROP_LOWEST = Renderer.dice.tk._new("DL", "dl", {isDiceModifier: true});
-Renderer.dice.tk.KEEP_LOWEST = Renderer.dice.tk._new("KL", "kl", {isDiceModifier: true});
+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});
@@ -1450,27 +1567,34 @@ Renderer.dice.parsed = {
_max (meta) { return this._invoke("max", meta); }
_invoke (fnName, meta) {
- const [symFunc, symExp] = this._nodes;
+ const [symFunc] = this._nodes;
switch (symFunc.type) {
- case Renderer.dice.tk.FLOOR.type: {
- this.addToMeta(meta, "floor(");
- const out = Math.floor(symExp[fnName](meta));
- this.addToMeta(meta, ")");
- return out;
- }
- case Renderer.dice.tk.CEIL.type: {
- this.addToMeta(meta, "ceil(");
- const out = Math.ceil(symExp[fnName](meta));
- this.addToMeta(meta, ")");
- return out;
- }
- case Renderer.dice.tk.ROUND.type: {
- this.addToMeta(meta, "round(");
- const out = Math.round(symExp[fnName](meta));
+ 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, `${symFunc.toString()}(`);
+ const args = [];
+ symExps.forEach((symExp, i) => {
+ if (i !== 0) this.addToMeta(meta, `, `);
+ args.push(symExp[fnName](meta));
+ });
+ const out = Math[symFunc.toString()](...args);
this.addToMeta(meta, ")");
return out;
}
case Renderer.dice.tk.AVERAGE.type: {
+ const [, symExp] = this._nodes;
return symExp.avg(meta);
}
default: throw new Error(`Unimplemented!`);
@@ -1481,10 +1605,22 @@ Renderer.dice.parsed = {
let out;
const [symFunc, symExp] = this._nodes;
switch (symFunc.type) {
- case Renderer.dice.tk.FLOOR.type: out = "floor"; break;
- case Renderer.dice.tk.CEIL.type: out = "ceil"; break;
- case Renderer.dice.tk.ROUND.type: out = "round"; break;
- case Renderer.dice.tk.AVERAGE.type: out = "avg"; break;
+ 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.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()})`;
@@ -1775,7 +1911,7 @@ Renderer.dice.parsed = {
out *= this._nodes[i + 1][fnName](meta);
} else if (this._nodes[i].eq(Renderer.dice.tk.DIV)) {
this.addToMeta(meta, " ÷ ");
- out /= this._nodes[i + 1][fnName](meta)
+ out /= this._nodes[i + 1][fnName](meta);
} else throw new Error(`Unimplemented!`);
}
diff --git a/js/render-map.js b/js/render-map.js
index bd0c87cf3a..b6f5e1c2cb 100644
--- a/js/render-map.js
+++ b/js/render-map.js
@@ -284,7 +284,7 @@ class RenderMap {
const $btnZoomPlus = $(``)
.click(() => zoomChange("in"));
- const $btnZoomReset = $(``)
+ const $btnZoomReset = $(``)
.click(() => zoomChange("reset"));
const $btnHelp = $(``)
diff --git a/js/render-optionalfeatures.js b/js/render-optionalfeatures.js
index 2216ffeeab..22062b8eb2 100644
--- a/js/render-optionalfeatures.js
+++ b/js/render-optionalfeatures.js
@@ -1,5 +1,5 @@
class RenderOptionalFeatures {
static $getRenderedOptionalFeature (it) {
- return $$`${Renderer.optionalFeature.getRenderedString(it)}`;
+ return $$`${Renderer.optionalFeature.getCompactRenderedString(it)}`;
}
}
diff --git a/js/render.js b/js/render.js
index b09670d841..6c6acb1abb 100644
--- a/js/render.js
+++ b/js/render.js
@@ -827,10 +827,12 @@ function Renderer () {
}
this._renderAttack = function (entry, textStack, meta, options) {
- const renderer = Renderer.get();
+ let MAP = -5;
+ if (entry.noMAP) MAP = 0;
+ if (entry.traits && entry.traits.map(t => t.toLowerCase()).includes("agile")) MAP = -4;
textStack[0] += `
- ${entry.range} ${renderer.render("{@as 1}")} ${entry.name} ${renderer.render(`{@hit ${entry.attack}||${entry.name.uppercaseFirst()}}`)}
- ${entry.traits != null ? ` ${renderer.render(`(${entry.traits.map((t) => `{@trait ${t.toLowerCase()}}`).join(", ")})`)}` : ""}, Damage ${renderer.render(entry.damage)}${entry.noMAP ? "; no multiple attack penalty" : ""}
`;
+ ${entry.range} ${this.render("{@as 1}")} ${entry.name} ${this.render(`{@hit ${entry.attack}||${entry.name.uppercaseFirst()}|MAP=${MAP}}`)}
+ ${entry.traits != null ? ` ${this.render(`(${entry.traits.map((t) => `{@trait ${t.toLowerCase()}}`).join(", ")})`)}` : ""}, Damage ${this.render(entry.damage)}${entry.noMAP ? "; no multiple attack penalty" : ""}`;
};
this._renderAbility = function (entry, textStack, meta, options) {
@@ -933,9 +935,8 @@ function Renderer () {
const renderer = Renderer.get();
this._handleTrackTitles(entry.name);
textStack[0] += `
- ${renderer.render(entry.name)}
- ${entry.source ? `${Parser.sourceJsonToFull(entry.source)}${entry.page != null ? `, p. ${entry.page}` : ""}` : ""}`;
- if (entry.collapsible) textStack[0] += `${entry.collapsible ? this._getCollapsibleToggle({minus: "-"}) : ""}`;
+ ${renderer.render(entry.name)}`;
+ if (entry.collapsible) textStack[0] += `${this._getCollapsibleToggle({minus: "-"})}`;
textStack[0] += `
`
}
this._firstSection = false;
@@ -983,9 +984,8 @@ function Renderer () {
const renderer = Renderer.get();
this._handleTrackTitles(entry.name);
textStack[0] += `
- ${renderer.render(entry.name)}
- ${entry.source ? `${Parser.sourceJsonToFull(entry.source)}${entry.page != null ? `, p. ${entry.page}` : ""}` : ""}`;
- if (entry.collapsible) textStack[0] += `${entry.collapsible ? this._getCollapsibleToggle({minus: "-", parents: 3}) : ""}`;
+ ${renderer.render(entry.name)}`;
+ if (entry.collapsible) textStack[0] += this._getCollapsibleToggle({minus: "-"});
textStack[0] += `
`
}
textStack[0] += ``
@@ -1006,8 +1006,7 @@ function Renderer () {
const renderer = Renderer.get();
this._handleTrackTitles(entry.name);
textStack[0] += `
- ${renderer.render(entry.name)}
- ${entry.source ? `${Parser.sourceJsonToFull(entry.source)}${entry.page != null ? `, p. ${entry.page}` : ""}` : ""}`;
+ ${renderer.render(entry.name)}`;
if (entry.level || entry.collapsible) textStack[0] += `${entry.level ? Parser.getOrdinalForm(entry.level) : ""}${entry.collapsible ? this._getCollapsibleToggle({minus: "\u2013"}) : ""}`;
textStack[0] += `
`;
}
@@ -1029,8 +1028,7 @@ function Renderer () {
const renderer = Renderer.get();
this._handleTrackTitles(entry.name);
textStack[0] += `
- ${renderer.render(entry.name)}
- ${entry.source ? `${Parser.sourceJsonToFull(entry.source)}${entry.page != null ? `, p. ${entry.page}` : ""}` : ""}`;
+ ${renderer.render(entry.name)}`;
if (entry.level || entry.collapsible) textStack[0] += `${entry.level ? Parser.getOrdinalForm(entry.level) : ""}${entry.collapsible ? this._getCollapsibleToggle({minus: "\u2013"}) : ""}`;
textStack[0] += `
`;
}
@@ -1063,7 +1061,7 @@ function Renderer () {
};
this._getCollapsibleToggle = function (opts) {
- return `
[${opts.minus}]`
@@ -1640,7 +1638,20 @@ function Renderer () {
this._recursiveRender(fauxEntry, textStack, meta);
break;
}
- case "@d20":
+ case "@d20": {
+ // format: {@d20 +1} or {@d20 -2}
+ let mod;
+ if (!isNaN(rollText)) {
+ const n = Number(rollText);
+ mod = `${n >= 0 ? "+" : ""}${n}`;
+ } else mod = rollText;
+ fauxEntry.displayText = fauxEntry.displayText || mod;
+ fauxEntry.toRoll = `1d20${mod}`;
+ fauxEntry.subType = "d20";
+ fauxEntry.d20mod = mod;
+ this._recursiveRender(fauxEntry, textStack, meta);
+ break;
+ }
case "@hit": {
// format: {@hit +1} or {@hit -2}
let mod;
@@ -1650,7 +1661,9 @@ function Renderer () {
} else mod = rollText;
fauxEntry.displayText = fauxEntry.displayText || mod;
fauxEntry.toRoll = `1d20${mod}`;
- fauxEntry.subType = "d20";
+ const MAPstr = others.find(o => o.startsWith("MAP=")) || "MAP=-5";
+ fauxEntry.MAP = Number(MAPstr.replace(/MAP=/, ""));
+ fauxEntry.subType = "hit";
fauxEntry.d20mod = mod;
this._recursiveRender(fauxEntry, textStack, meta);
break;
@@ -2260,6 +2273,14 @@ function Renderer () {
};
this._recursiveRender(fauxEntry, textStack, meta);
break;
+ case "@optfeature":
+ fauxEntry.href.path = UrlUtil.PG_OPTIONAL_FEATURES;
+ fauxEntry.href.hover = {
+ page: UrlUtil.PG_OPTIONAL_FEATURES,
+ source,
+ };
+ this._recursiveRender(fauxEntry, textStack, meta);
+ break;
case "@variantrule":
fauxEntry.href.path = UrlUtil.PG_VARIANTRULES;
fauxEntry.href.hover = {
@@ -2688,7 +2709,7 @@ Renderer.getRollableEntryDice = function (entry, name, isAddHandlers = true, toD
};
Renderer.getEntryDiceTitle = function (subType) {
- return `Click to roll. ${subType === "damage" ? "SHIFT to roll a critical hit, CTRL to half damage (rounding down)." : subType === "d20" ? "SHIFT to roll with advantage, CTRL to roll with disadvantage." : "SHIFT/CTRL to roll twice."}`
+ return `Click to roll. ${subType === "damage" ? "SHIFT to roll a critical hit, CTRL to half damage (rounding down)." : subType === "d20" ? "SHIFT to roll with advantage, CTRL to roll with disadvantage." : subType === "hit" ? "SHIFT to roll with MAP, CTRL to roll with MAP × 2." : "SHIFT/CTRL to roll twice."}`
};
Renderer.legacyDiceToString = function (array) {
@@ -3431,11 +3452,10 @@ Renderer.affliction = {
const renderer = Renderer.get();
const renderStack = [];
renderer.setFirstSection(true);
- const rLvl = isNaN(Number(affliction.level)) ? `` : ` ${affliction.level}`;
renderStack.push(`${Renderer.utils.getExcludedDiv(affliction, affliction.__prop || affliction._type, UrlUtil.PG_AFFLICTIONS)}`)
renderStack.push(`
- ${Renderer.utils.getNameDiv(affliction, {page: UrlUtil.PG_AFFLICTIONS, level: rLvl, ...opts})}
+ ${Renderer.utils.getNameDiv(affliction, {page: UrlUtil.PG_AFFLICTIONS, level: ` ${affliction.level !== null ? affliction.level : ""}`, ...opts})}
${Renderer.utils.getDividerDiv()}
${Renderer.utils.getTraitsDiv(affliction.traits || [])}
`);
@@ -3827,7 +3847,7 @@ Renderer.creature = {
if (typeof (x) === "string") {
rs.push(x)
} else {
- rs.push(`${x.name} ${x.amount}${x.note ? ` ${renderer.render(x.note)}` : ``}`)
+ rs.push(`${x.name}${x.amount ? ` ${x.amount}` : ""}${x.note ? ` ${renderer.render(x.note)}` : ``}`)
}
}
renderStack.push(rs.join(", "))
@@ -3839,49 +3859,20 @@ Renderer.creature = {
},
getSpeed (cr) {
- let renderStack = [];
- renderStack.push(`
`)
- renderStack.push(`Speed `)
- let speeds = []
- if (cr.speed.walk != null) speeds.push(`${cr.speed.walk} feet`)
- for (let key in cr.speed) {
- if (key !== "abilities" && key !== "walk") {
- speeds.push(`${key} ${cr.speed[key]} feet`)
- }
- }
- renderStack.push(speeds.join(", "))
- if (cr.speed.abilities != null) {
- renderStack.push("; ")
- renderStack.push(cr.speed.abilities.join(", "))
- }
- renderStack.push(``)
- renderStack.push(`
`)
- return renderStack.join("")
+ const renderer = Renderer.get();
+ const speeds = cr.speed.walk != null ? [`${cr.speed.walk} feet`] : [];
+ speeds.push(...Object.keys(cr.speed).filter(k => !(["abilities", "walk"].includes(k))).map(k => `${k} ${cr.speed[k]} feet`));
+ return `
+ Speed
+ ${speeds.join(", ")}${cr.speed.abilities != null ? `; ${renderer.render(cr.speed.abilities.join(", "))}` : ""}
`
},
getAttacks (cr) {
- let renderStack = [];
if (cr.attacks) {
- for (let attack of cr.attacks) {
- renderStack.push(`
`)
- renderStack.push(`${attack.range} `)
- renderStack.push(Renderer.get().render(`{@as 1} `))
- if (attack.name) renderStack.push(`${attack.name}`)
- renderStack.push(``)
- if (attack.attack != null) renderStack.push(Renderer.get().render(` {@hit ${attack.attack}||${attack.name.uppercaseFirst()} `))
- renderStack.push(``)
- if (attack.traits != null) {
- let traits = []
- attack.traits.forEach((t) => traits.push(`{@trait ${t.toLowerCase()}}`));
- renderStack.push(Renderer.get().render(` (${traits.join(", ")})`))
- }
- renderStack.push(`, Damage `)
- renderStack.push(Renderer.get().render(attack.damage))
-
- renderStack.push(``)
- renderStack.push(`
`)
- }
- return renderStack.join("")
+ const renderer = Renderer.get();
+ const renderStack = [];
+ cr.attacks.forEach(a => renderer._renderAttack(a, renderStack));
+ return renderStack.join("");
}
},
@@ -3890,18 +3881,16 @@ Renderer.creature = {
const renderer = Renderer.get()
let renderStack = [];
for (let sc of cr.spellcasting) {
- renderStack.push(`
`)
- renderStack.push(`${sc.name} Spells DC ${sc.DC}`)
- if (sc.attack != null) {
- renderStack.push(renderer.render(`, attack {@hit ${sc.attack}||Spell attack}`))
- }
+ const meta = [];
+ if (sc.DC != null) meta.push(`DC ${sc.DC}`);
+ if (sc.attack != null) meta.push(`attack {@hit ${sc.attack}||Spell attack}`);
+ if (sc.fp != null) meta.push(`${sc.fp} Focus Points`);
+ renderStack.push(`
${sc.name}${/Spell/.test(sc.name) ? "" : " Spells"} ${renderer.render(meta.join(", "))}`)
Object.keys(sc.entry).sort(SortUtil.sortSpellLvlCreature).forEach((lvl) => {
if (lvl !== "constant") {
renderStack.push(`; ${lvl === "0" ? "Cantrips" : Parser.getOrdinalForm(lvl)} `)
if (sc.entry[lvl].level != null) renderStack.push(`(${Parser.getOrdinalForm(sc.entry[lvl].level)}) `)
if (sc.entry[lvl].slots != null) renderStack.push(`(${sc.entry[lvl].slots} slots) `)
- if (sc.entry[lvl].fp != null) renderStack.push(`${sc.entry[lvl].fp} `)
- renderStack.push(``)
let spells = []
for (let spell of sc.entry[lvl].spells) {
let amount = spell.amount != null ? typeof (spell.amount) === "number" ? [`×${spell.amount}`] : [spell.amount] : []
@@ -3915,10 +3904,10 @@ Renderer.creature = {
renderStack.push(renderer.render(spells.join(", ")))
} else {
renderStack.push(`; Constant `)
- Object.keys(sc.entry[lvl]).sort().reverse().forEach((clvl) => {
+ Object.keys(sc.entry["constant"]).sort().reverse().forEach((clvl) => {
renderStack.push(`(${Parser.getOrdinalForm(clvl)}) `)
let spells = []
- for (let spell of sc.entry[lvl][clvl].spells) {
+ for (let spell of sc.entry["constant"][clvl].spells) {
let notes = spell.notes != null ? spell.notes : []
let bracket = ""
if (notes.length) {
@@ -3937,27 +3926,13 @@ Renderer.creature = {
},
getRituals (cr) {
- if (cr.rituals != null) {
- const renderer = Renderer.get()
- let renderStack = [];
- cr.rituals.forEach((feature) => {
- renderStack.push(`
`)
- renderStack.push(`${feature.tradition} Rituals DC ${feature.DC}; `)
- let rituals = []
- feature.rituals.forEach((ritual) => {
- let bracket = ""
- let notes = ritual.notes != null ? ritual.notes : []
- let level = ritual.level != null ? [Parser.getOrdinalForm(ritual.level)] : []
- if (level.length || notes.length) {
- bracket = ` (${level.concat(notes).join(", ")})`
- }
- rituals.push(`{@spell ${ritual.name}|${ritual.source || SRC_CRB}|${ritual.name}}${bracket}`)
- });
- renderStack.push(renderer.render(rituals.join(", ")))
- renderStack.push(`
`)
- })
- return renderStack.join("")
- } else return ""
+ if (cr.rituals == null) return "";
+ const renderer = Renderer.get();
+ const renderRitual = (r) => {
+ return `{@ritual ${r.name}|${r.source || ""}}${r.notes == null && r.level == null ? "" : ` (${[Parser.getOrdinalForm(r.level)].concat(...(r.notes || [])).filter(Boolean).join(", ")})`}`;
+ };
+ return `${cr.rituals.map(rf => `
${rf.tradition ? `${rf.tradition} ` : ""}Rituals DC ${rf.DC};
+ ${renderer.render(rf.rituals.map(r => renderRitual(r)).join(", "))}`)}`;
},
getCompactRenderedString (cr, opts) {
@@ -4008,7 +3983,7 @@ Renderer.creature = {
const genericAbility = Renderer.hover._getFromCache(UrlUtil.PG_ABILITIES, "Bst", hash);
renderedGenericAbility = Renderer.creature.getRenderedAbility(genericAbility, {generic: true});
}
- return $$`
${ability.generic || options.generic ? `${renderer.render(`{@ability ${ability.name}}`)}` : ability.name}
+ return $$`${ability.generic || options.generic ? `${renderer.render(`{@ability ${ability.name}}`)}` : ability.name}
${ability.activity ? renderer.render(Parser.timeToFullEntry(ability.activity)) : ""}
${(ability.generic || options.generic) && !options.noButton ? Renderer.creature.getAbilityTextButton(buttonClass, options.generic) : ""}
${trts.length ? `(${trts.join(", ")}); ` : ""}
@@ -4017,7 +3992,7 @@ Renderer.creature = {
${ability.trigger ? `Trigger ${renderer.render_addTerm(ability.trigger)}` : ""}
${ability.frequency || ability.requirements || ability.trigger ? "Effect " : ""}
${(ability.entries || []).map(it => renderer.render(it, {isAbility: true})).join(" ")}
-
+
${renderedGenericAbility || ""}`;
},
@@ -4467,8 +4442,8 @@ Renderer.item = {
if (item.category) {
renderStack.push(`
Category `);
if (item.subCategory != null) renderStack.push(`${item.subCategory} `);
- if (item.category === "Weapon") renderStack.push(`${item.ranged ? "Ranged" : "Melee"} `);
- renderStack.push(`${item.category}${item.category === "Worn" ? ` ${item.type}` : ""}`);
+ if (item.category === "Weapon") renderStack.push(`${item.range ? "Ranged" : "Melee"} `);
+ renderStack.push(`${Array.isArray(item.category) ? item.category.join(", ") : item.category}${item.category === "Worn" ? ` ${item.type}` : ""}`);
}
if (item.category != null && item.group != null) renderStack.push("; ")
if (item.group != null) renderStack.push(`
Group ${renderer.render(`{@group ${item.group}}`)}`);
@@ -4535,7 +4510,7 @@ Renderer.item = {
if (v.traits != null && v.traits.length) renderStack.push(` (${renderer.render(v.traits.map(t => `{@trait ${t.toLowerCase()}}`).join(", "))});`);
if (v.price != null) renderStack.push(`;
Price ${Parser.priceToFull(v.price)}`);
if (v.bulk != null) renderStack.push(`;
Bulk ${v.bulk}`);
- if (v.entries != null && v.entries.length) renderStack.push(`; ${renderer.render(v.entries)}`);
+ if (v.entries != null && v.entries.length) renderStack.push(`; ${renderer.render(v.entries.join("\n"))}`);
if (v.craftReq != null) renderStack.push(`;
Craft Requirements ${renderer.render(v.craftReq)}`);
if (v.shieldStats != null) renderStack.push(`; The shield has Hardness ${v.shieldStats.hardness}, HP ${v.shieldStats.hp}, and BT ${v.shieldStats.bt}.`);
renderStack.push(``);
@@ -4672,11 +4647,14 @@ Renderer.language = {
};
Renderer.optionalFeature = {
- getRenderedString (it, opts) {
+ // FIXME: Add prerequisite showing
+ getCompactRenderedString (it, opts) {
opts = opts || {};
return `
${Renderer.utils.getNameDiv(it)}
${Renderer.utils.getDividerDiv()}
+ ${Renderer.utils.getTraitsDiv(it.traits)}
+ ${it.traits ? Renderer.utils.getDividerDiv() : ""}
${Renderer.generic.getRenderedEntries(it)}
${opts.noPage ? "" : Renderer.utils.getPageP(it)}`;
},
@@ -5074,6 +5052,7 @@ Renderer.hover = {
"hazard": UrlUtil.PG_HAZARDS,
"deity": UrlUtil.PG_DEITIES,
"variantrule": UrlUtil.PG_VARIANTRULES,
+ "optfeature": UrlUtil.PG_OPTIONAL_FEATURES,
},
LinkMeta: function () {
@@ -6078,6 +6057,8 @@ Renderer.hover = {
return Renderer.hover._pCacheAndGet_pLoadSimple(page, source, hash, opts, "tables.json", ["table", "tableGroup"], (listProp, item) => item.__prop = listProp);
case UrlUtil.PG_ACTIONS:
return Renderer.hover._pCacheAndGet_pLoadSimple(page, source, hash, opts, "actions.json", "action");
+ case UrlUtil.PG_OPTIONAL_FEATURES:
+ return Renderer.hover._pCacheAndGet_pLoadSimple(page, source, hash, opts, "optionalfeatures.json", "optionalfeature");
case UrlUtil.PG_ABILITIES:
return Renderer.hover._pCacheAndGet_pLoadSimple(page, source, hash, opts, "abilities.json", "ability");
case UrlUtil.PG_LANGUAGES:
@@ -6724,6 +6705,8 @@ Renderer.hover = {
return Renderer.action.getCompactRenderedString;
case UrlUtil.PG_ABILITIES:
return Renderer.ability.getCompactRenderedString;
+ case UrlUtil.PG_OPTIONAL_FEATURES:
+ return Renderer.optionalFeature.getCompactRenderedString;
case UrlUtil.PG_LANGUAGES:
return Renderer.language.getCompactRenderedString;
case UrlUtil.PG_TRAITS:
@@ -7065,6 +7048,7 @@ Renderer._stripTagLayer = function (str) {
case "@familiar":
case "@familiarAbility":
case "@companion":
+ case "@optfeature":
case "@variantrule": {
const parts = Renderer.splitTagByPipe(text);
return parts.length >= 3 ? parts[2] : parts[0];
diff --git a/js/scalecreature.js b/js/scalecreature.js
index 864f49551f..c4659a8424 100644
--- a/js/scalecreature.js
+++ b/js/scalecreature.js
@@ -604,7 +604,7 @@ class ScaleCreature {
return `@hit ${Number(m[1]) + opts.flatAddProf}`;
});
e = e.replaceAll(/@damage (\d+d\d+)([+-]?\d*)/g, (formula, formulaNoMod, mod) => {
- if (!mod) return `@damage ${formulaNoMod}+${bonus}`;
+ if (!mod) return `@damage ${formulaNoMod}${bonus > 0 ? "+" : ""}${bonus}`;
else {
if (Number(mod) + bonus > 0) return `@damage ${formulaNoMod}+${Number(mod) + bonus}`;
else if (Number(mod) + bonus < 0) return `@damage ${formulaNoMod}${Number(mod) + bonus}`;
@@ -890,10 +890,13 @@ class ScaleCreature {
e = e.replaceAll(/@hit (\d+)/g, (...m) => {
return `@hit ${this._scaleValue(lvlIn, toLvl, Number(m[1]), this._LvlAttackBonus) + opts.flatAddProf}`;
});
- e = e.replaceAll(/@damage (\d+d\d+[+-]?\d*)/g, (...m) => {
- const scaleTo = isArea ? this._LvlAreaDamage[toLvl][Number(isLimited)] / this._LvlAreaDamage[lvlIn][Number(isLimited)] * this._getDiceEV(m[1]) : this._scaleValue(lvlIn, toLvl, this._getDiceEV(m[1]), this._LvlExpectedDamage);
- return `@damage ${this._scaleDice(m[1], scaleTo)}`;
- });
+ // Do not scale damage formulas when applying variant rules
+ if (lvlIn !== toLvl) {
+ e = e.replaceAll(/@damage (\d+d\d+[+-]?\d*)/g, (...m) => {
+ const scaleTo = isArea ? this._LvlAreaDamage[toLvl][Number(isLimited)] / this._LvlAreaDamage[lvlIn][Number(isLimited)] * this._getDiceEV(m[1]) : this._scaleValue(lvlIn, toLvl, this._getDiceEV(m[1]), this._LvlExpectedDamage);
+ return `@damage ${this._scaleDice(m[1], scaleTo)}`;
+ });
+ }
}
return e;
});
diff --git a/js/search.js b/js/search.js
index b69e4da0cb..e5e007824a 100644
--- a/js/search.js
+++ b/js/search.js
@@ -21,7 +21,7 @@ class SearchPage {
})
.val(decodeURIComponent(location.search.slice(1).replace(/\+/g, " ")))
- const $btnSearch = $(`
`)
+ const $btnSearch = $(`
`)
.click(() => {
location.search = encodeURIComponent($iptSearch.val().trim().toLowerCase());
});
diff --git a/js/utils-list.js b/js/utils-list.js
index 5d26d5b683..987de71962 100644
--- a/js/utils-list.js
+++ b/js/utils-list.js
@@ -36,7 +36,7 @@ const ListUtil = {
const _handleSearchChange = () => {
setTimeout(() => {
if ($iptSearch.val().length) $btnSearchClear.removeClass("no-events").addClass("clickable").title("Clear").html(`
`);
- else $btnSearchClear.addClass("no-events").removeClass("clickable").title(null).html(`
`);
+ else $btnSearchClear.addClass("no-events").removeClass("clickable").title(null).html(`
`);
})
};
const handleSearchChange = MiscUtil.throttle(_handleSearchChange, 50);
diff --git a/js/utils-ui.js b/js/utils-ui.js
index 5fb4db6833..0556e4f48a 100644
--- a/js/utils-ui.js
+++ b/js/utils-ui.js
@@ -2890,7 +2890,7 @@ class ComponentUiUtil {
static _$getDecor (component, prop, $ipt, decorType, side, opts) {
switch (decorType) {
case "search": {
- return $(`
`);
+ return $(`
`);
}
case "clear": {
return $(`
`)
diff --git a/js/utils.js b/js/utils.js
index a833ad87dc..51ecd3170c 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -5,7 +5,7 @@ if (typeof module !== "undefined") require("./parser.js");
// in deployment, `IS_DEPLOYED = "
";` should be set below.
IS_DEPLOYED = undefined;
-VERSION_NUMBER = /* PF2ETOOLS_VERSION__OPEN */"0.1.2"/* PF2ETOOLS_VERSION__CLOSE */;
+VERSION_NUMBER = /* PF2ETOOLS_VERSION__OPEN */"0.1.3"/* PF2ETOOLS_VERSION__CLOSE */;
DEPLOYED_STATIC_ROOT = ""; // ""; // FIXME re-enable this when we have a CDN again
IS_VTT = false;
@@ -2788,8 +2788,8 @@ DataUtil = {
otherSources: true,
},
_mergeCache: {},
- async pMergeCopy (deityList, deity, options) {
- return DataUtil.generic._pMergeCopy(DataUtil.deity, UrlUtil.PG_DEITIES, deityList, deity, options);
+ async pMergeCopy (ritualList, ritual, options) {
+ return DataUtil.generic._pMergeCopy(DataUtil.ritual, UrlUtil.PG_RITUALS, ritualList, ritual, options);
},
loadJSON: async function () {
@@ -2797,6 +2797,21 @@ DataUtil = {
},
},
+ optionalfeature: {
+ _MERGE_REQUIRES_PRESERVE: {
+ page: true,
+ otherSources: true,
+ },
+ _mergeCache: {},
+ async pMergeCopy (optionalFeatureList, optionalfeature, options) {
+ return DataUtil.generic._pMergeCopy(DataUtil.optionalfeature, UrlUtil.PG_OPTIONAL_FEATURES, optionalFeatureList, optionalfeature, options);
+ },
+
+ loadJSON: async function () {
+ return DataUtil.loadJSON(`${Renderer.get().baseUrl}data/optionalfeatures.json`);
+ },
+ },
+
creature: {
_MERGE_REQUIRES_PRESERVE: {
page: true,
@@ -4344,6 +4359,8 @@ BrewUtil = {
return ["place"];
case UrlUtil.PG_RITUALS:
return ["ritual"];
+ case UrlUtil.PG_OPTIONAL_FEATURES:
+ return ["optionalfeature"];
case UrlUtil.PG_VEHICLES:
return ["vehicle"];
case UrlUtil.PG_TRAITS:
@@ -4460,6 +4477,7 @@ BrewUtil = {
case "group":
case "domain":
case "skill":
+ case "optionalfeature":
return BrewUtil._genPDeleteGenericBrew(category);
case "subclass":
return BrewUtil._pDeleteSubclassBrew;
@@ -4553,7 +4571,7 @@ BrewUtil = {
obj.uniqueId = CryptUtil.md5(JSON.stringify(obj));
},
- _STORABLE: ["variantrule", "table", "tableGroup", "book", "bookData", "ancestry", "heritage", "versatileHeritage", "background", "class", "subclass", "classFeature", "subclassFeature", "archetype", "feat", "companion", "familiar", "eidolon", "adventure", "adventureData", "hazard", "action", "creature", "condition", "item", "baseitem", "spell", "disease", "curse", "itemcurse", "ability", "deity", "language", "place", "ritual", "vehicle", "trait", "group", "domain", "skill"],
+ _STORABLE: ["variantrule", "table", "tableGroup", "book", "bookData", "ancestry", "heritage", "versatileHeritage", "background", "class", "subclass", "classFeature", "subclassFeature", "archetype", "feat", "companion", "familiar", "eidolon", "adventure", "adventureData", "hazard", "action", "creature", "condition", "item", "baseitem", "spell", "disease", "curse", "itemcurse", "ability", "deity", "language", "place", "ritual", "vehicle", "trait", "group", "domain", "skill", "optionalfeature"],
async pDoHandleBrewJson (json, page, pFuncRefresh) {
page = BrewUtil._PAGE || page;
await BrewUtil._lockHandleBrewJson.pLock();
@@ -4697,6 +4715,7 @@ BrewUtil = {
case UrlUtil.PG_PLACES:
case UrlUtil.PG_RITUALS:
case UrlUtil.PG_VEHICLES:
+ case UrlUtil.PG_OPTIONAL_FEATURES:
case UrlUtil.PG_TRAITS:
await (BrewUtil._pHandleBrew || handleBrew)(MiscUtil.copy(toAdd));
break;
diff --git a/languages.html b/languages.html
index 25ee10abd3..57f257135a 100644
--- a/languages.html
+++ b/languages.html
@@ -89,7 +89,7 @@ Languages
+ class="glyphicon glyphicon-search" type="submit">