UNPKG

@cruncheevos/core

Version:

Parse and generate achievements and leaderboards for RetroAchievements.org

1,486 lines (1,481 loc) 44.4 kB
function H(e) { return Object.entries(e).reduce((t, r) => (t[r[1]] = r[0], t), {}); } function ae(e) { return e[0].toUpperCase() + e.slice(1); } function j(e, t = !1) { let r = Math.abs(e).toString(16); return t && (r = r.toUpperCase()), `${e < 0 ? "-" : ""}0x` + r; } function s(e, ...t) { return e.map((r, o) => { let i = t[o]; return typeof i == "string" ? i = `"${i}"` : typeof i == "symbol" ? i = String(i) : o >= t.length && (i = ""), r + i; }).join(""); } function x(e) { return Object.prototype.toString.call(e) === "[object Object]"; } function D(e, t = {}) { return !(e === null || typeof e == "symbol" || typeof e == "boolean" || typeof e == "string" && e.trim().length === 0 || (e = Number(e), Number.isNaN(e) || Number.isFinite(e) === !1) || t.isInteger && Number.isInteger(e) === !1 || t.isPositive && e < 0); } function P(e) { for (const t in e) { const r = e[t]; if (x(r)) P(r); else if (Array.isArray(r)) { for (const o of r) (x(o) || Array.isArray(o)) && P(o); Object.freeze(r); } } return Object.freeze(e); } function $(e, t) { const r = new Error(t); return r.cause = e, r; } function le(e) { const t = []; let r = !1, o = 0; for (let i = 0; i < e.length; i++) { const n = e[i]; if (t[o] = t[o] || "", r && n == "\\" && e[i + 1] == '"') { t[o] += '"', i++; continue; } if (n == '"') { r = !r; continue; } if (n == ":" && !r) { o++; continue; } t[o] += n; } return t; } function M(e) { return e.match(/[:"]/g) ? `"${e.replace(/"/g, '\\"')}"` : e; } const b = { andNormalizeId(e, t = "id") { const r = e; if (typeof e == "string") { if (e.trim().length === 0) throw new Error(`expected ${t} as unsigned integer, but got ""`); e = Number(e); } if (Number.isInteger(e) === !1) throw new Error( `expected ${t} as unsigned integer, but got ` + s`${r}` ); if (e < 0 || e >= Number.MAX_SAFE_INTEGER) throw new Error( `expected ${t} to be within the range of 0x0 .. 0xFFFFFFFF, but got ` + s`${r}` ); return e; }, string(e, t = "title") { if (typeof e != "string") throw new Error(`expected ${t} as string, but got ` + s`${e}`); }, nonEmptyString(e, t = "title") { if (typeof e != "string" || e.trim().length === 0) throw new Error( `expected ${t} as non-empty string, but got ` + s`${e}` ); } }; function W(e) { return e === 0 ? "Core" : `Alt ${e}`; } function Ee(e) { const t = new TextEncoder().encode(e), r = []; for (let o = 0; o < t.length; o += 4) { const i = [...t.slice(o, o + 4)].reverse().map((n) => n.toString(16).padStart(2, "0")).join(""); r.push(parseInt(i, 16)); } return r; } function v(e) { return function(...t) { const r = new p(); return m.call(r, e, ...t), r; }; } const ce = v(""); ce.one = function(e) { if (arguments.length > 1) throw new Error("expected only one condition argument, but got " + arguments.length); return new E(e); }; ce.str = function(e, t) { return Fe( ...Ee(e).map((r, o) => { let i = t( r > 16777215 ? "32bit" : r > 65535 ? "24bit" : r > 255 ? "16bit" : "8bit", ["Value", "", r] ); return o > 0 ? i.withLast({ lvalue: { value: i.conditions[i.conditions.length - 1].lvalue.value + o * 4 } }) : i; }) ); }; const Pe = v("Trigger"), Te = v("ResetIf"), ke = v("PauseIf"), Ve = v("AddHits"), Be = v("SubHits"), Ue = v("Measured"), De = v("Measured%"), He = v("MeasuredIf"), We = v("ResetNextIf"), Fe = v("AndNext"), Ge = v("OrNext"), Xe = (...e) => new p().also("once", ...e), _ = /* @__PURE__ */ new WeakMap(); class p { constructor() { this.conditions = [], _.set(this, ""); } trigger(...t) { return m.call(this, "Trigger", ...t), this; } resetIf(...t) { return m.call(this, "ResetIf", ...t), this; } pauseIf(...t) { return m.call(this, "PauseIf", ...t), this; } addHits(...t) { return m.call(this, "AddHits", ...t), this; } subHits(...t) { return m.call(this, "SubHits", ...t), this; } measured(...t) { return m.call(this, "Measured", ...t), this; } measuredPercent(...t) { return m.call(this, "Measured%", ...t), this; } measuredIf(...t) { return m.call(this, "MeasuredIf", ...t), this; } resetNextIf(...t) { return m.call(this, "ResetNextIf", ...t), this; } andNext(...t) { return m.call(this, "AndNext", ...t), this; } orNext(...t) { return m.call(this, "OrNext", ...t), this; } also(...t) { return m.call(this, "", ...t), this; } once(...t) { return m.call(this, "", "once", ...t), this; } *[Symbol.iterator]() { for (const t of this.conditions) yield t; } map(t) { const r = this.conditions.map(t); return new p().also(...r); } withLast(t) { return this.map((r, o, i) => o !== i.length - 1 ? r : r.with(t)); } toString() { return this.conditions.join("_"); } toJSON() { return this.toString(); } } const xe = /\s+/; function m(e, ...t) { let r = 0; const o = t.filter((n, a) => { if (typeof n == "string" && (n === "once" || n.startsWith("hits"))) { if (a > 0) throw new Error("strings 'once' and 'hits %number%' must be placed before any conditions"); return n === "once" && (r = 1), n.startsWith("hits") && (r = parseInt(n.split(xe)[1])), !1; } return n instanceof p && n.conditions.length === 0 ? !1 : !!n; }); if (o.length === 0) return; const i = _.get(this); if (i === "AndNext" || i === "OrNext") { const n = this.conditions[this.conditions.length - 1]; n.flag === "" && (this.conditions[this.conditions.length - 1] = n.with({ flag: i })); } for (let n = 0; n < o.length; n++) { const a = o[n]; if (a instanceof p) { o.splice(n, 1, ...a), n--; continue; } let l = new E(a); const u = n === o.length - 1, c = u && (e === "AndNext" || e === "OrNext"); u && r > 0 && (l = l.with({ hits: r })), e && l.flag === "" && c === !1 && (l = l.with({ flag: e })), this.conditions.push(l); } _.set(this, e); } function T(e) { const t = e < 0 ? e + 1 + 4294967295 : e; if (e < -2147483648) throw new Error( `${e} (${j(e)}) underflows into positive ${t} (${j( t )}), it's very unlikely you intended for that to happen` ); return t; } const N = (() => { const e = { "": "", PauseIf: "P", ResetIf: "R", ResetNextIf: "Z", AddHits: "C", SubHits: "D", AndNext: "N", OrNext: "O", Measured: "M", "Measured%": "G", MeasuredIf: "Q", Trigger: "T" }, t = { AddSource: "A", SubSource: "B", AddAddress: "I", Remember: "K" }, r = { ...e, ...t }, o = H(r); return delete o[""], { forReading: { toRaw: e }, forCalc: { toRaw: t }, toRaw: r, fromRaw: o }; })(), A = (() => { const e = { Mem: "", Delta: "d", Prior: "p", BCD: "b", Invert: "~" }; return { withSize: { toRaw: e, array: Object.keys(e), fromRaw: H(e) }, withoutSize: { array: ["Value", "Float", "Recall"] } }; })(), G = (() => { const e = { "": "", Bit0: "M", Bit1: "N", Bit2: "O", Bit3: "P", Bit4: "Q", Bit5: "R", Bit6: "S", Bit7: "T", Lower4: "L", Upper4: "U", "8bit": "H", "16bit": " ", "24bit": "W", "32bit": "X", "16bitBE": "I", "24bitBE": "J", "32bitBE": "G", BitCount: "K" }; return { toRaw: e, fromRaw: H(e) }; })(), k = (() => { const e = { Float: "F", FloatBE: "B", Double32: "H", Double32BE: "I", MBF32: "M", MBF32LE: "L" }; return { toRaw: e, fromRaw: H(e) }; })(), w = { forReading: ["=", "!=", "<", "<=", ">", ">="], forCalc: ["+", "-", "*", "/", "%", "&", "^"], isLegalForReading(e) { return typeof e == "string" && this.forReading.includes(e); }, isLegalForCalc(e) { return typeof e == "string" && this.forCalc.includes(e); }, isLegal(e) { return this.isLegalForReading(e) || this.isLegalForCalc(e); } }; function ue(e) { return N.forReading.toRaw.hasOwnProperty(e.flag); } function he(e) { return N.forCalc.toRaw.hasOwnProperty(e.flag); } function Ie(e) { return e.lvalue.type === "Recall" && I(e) === !1; } function de(e) { return e.flag === "Measured" && I(e) === !1; } function ge(e) { return e.flag === "Measured" && I(e) && w.isLegalForCalc(e.cmp); } function I(e) { const t = !!e.rvalue.type, r = !!e.rvalue.size; return !r && !t ? !1 : r ? t : A.withSize.toRaw.hasOwnProperty(e.rvalue.type) ? r : !0; } const O = { value(e, t) { if (A.withSize.array.some((o) => o === e.type)) { if (Number.isInteger(e.value) === !1) throw new Error( `expected ${t} memory address as unsigned integer, but got ` + s`${e.value}` ); if (e.value < 0 || e.value > 4294967295) throw new Error( `expected ${t} memory address to be within the range of 0x0 .. 0xFFFFFFFF, but got ` + s`${e.value}` ); } else if (e.type === "Recall") { if (e.value !== 0 && (t === "lvalue" || e.size !== void 0)) throw new Error( `expected Recall ${t} value to be 0, but got ` + s`${e.value}` ); } else if (e.type === "Value") { if (Number.isInteger(e.value) === !1) throw new Error(`expected ${t} as integer, but got ` + s`${e.value}`); try { var r = T(e.value); } catch (o) { throw $(o, `${t}: ${o.message}`); } if (r > 4294967295) throw new Error( `expected ${t} to be within the range of 0x0 .. 0xFFFFFFFF, but got ` + s`${e.value}` ); } else if (e.type === "Float") { if (Number.isNaN(e.value) || Number.isFinite(e.value) === !1) throw new Error(`expected ${t} as float, but got ` + s`${e.value}`); const o = -294967040, i = 4294967040; if (e.value < o || e.value > i) throw new Error( `expected ${t} to be within the range of ${o} .. ${i}, but got ` + s`${e.value}` ); } else if (e.type !== "") throw new Error(`expected valid ${t} type, but got ` + s`${e.type}`); if (A.withoutSize.array.some((o) => o === e.type) && e.size) throw new Error( `${t} value cannot have size specified, but got ` + s`${e.size}` ); return e.type === "Value" ? { ...e, value: T(e.value) } : e.type === "Recall" && t === "rvalue" ? { ...e, size: e.size === void 0 ? "" : e.size, value: e.value === void 0 ? 0 : e.value } : e; }, calculations(e) { if (he(e) !== !1) { if (e.cmp) { if (w.isLegalForCalc(e.cmp) === !1) throw new Error( `expected an accumulation operator (${w.forCalc.join(" ")}), but got ` + s`${e.cmp}` ); if (I(e) === !1) throw new Error("rvalue must be fully provided if operator is specified"); e.rvalue = O.value(e.rvalue, "rvalue"); } else if (e.cmp === "" && I(e)) throw new Error(`expected an accumulation operator (${w.forCalc.join(" ")}), but got ""`); } }, memoryComparisons(e) { if (!(ue(e) === !1 || Ie(e) || de(e) || ge(e)) && (e.rvalue = O.value(e.rvalue, "rvalue"), w.isLegalForReading(e.cmp) === !1 || !e.cmp)) throw new Error( `expected comparison operator (${w.forReading.join(" ")}), but got ` + s`${e.cmp}` ); } }, Z = { enums(e) { if (N.toRaw.hasOwnProperty(e.flag) === !1) throw new Error(s`expected valid condition flag, but got ${e.flag}`); for (const [t, r] of [ [e.lvalue, "lvalue"], [e.rvalue, "rvalue"] ]) { if (t.type === "Recall") { if (t.size !== "" && (r === "lvalue" || t.size !== void 0)) throw new Error( `expected Recall ${r} size to be empty string, but got ` + s`${t.size}` ); continue; } if (G.toRaw.hasOwnProperty(t.size) === !1 && k.toRaw.hasOwnProperty(t.size) === !1) throw new Error(`expected valid ${r} size, but got ` + s`${t.size}`); } if (e.cmp && w.isLegal(e.cmp) === !1) throw new Error(s`expected an operator or lack of it, but got ${e.cmp}`); }, hits(e) { if (Number.isInteger(e.hits) === !1) throw new Error(s`expected hits as unsigned integer, but got ${e.hits}`); if (e.hits < 0 || e.hits > 4294967295) throw new Error( `expected hits to be within the range of 0x0 .. 0xFFFFFFFF, but got ${e.hits}` ); if (he(e) && e.hits > 0) throw new Error(`hits value cannot be specified with ${e.flag} condition flag`); } }, C = { flag(e) { const t = e.match(f.flag); if (!t) return ["", e]; const r = t[1]; if (N.fromRaw.hasOwnProperty(r.toUpperCase()) === !1) throw new Error(s`expected a legal condition flag, but got ${t[0]}`); return [N.fromRaw[r.toUpperCase()], e.slice(t[0].length)]; }, value(e) { const t = { type: "Mem", size: "", value: 0 }; let r = null, o = !0; if ((r = e.match(f.type)) && (e = e.slice(r[0].length), t.type = A.withSize.fromRaw[r[1].toLowerCase()], o = !1), e.startsWith("{recall}")) e = e.slice(8), t.type = "Recall"; else if (r = e.match(f.valueFloat)) e = e.slice(r[0].length), t.type = "Float", t.value = Number(r[1]); else if (r = e.match(f.memAddress)) { if (e = e.slice(r[0].length), r[1].toLowerCase() === "0x") if (r = e.match(f.sizesRegular)) e = e.slice(r[0].length), t.size = G.fromRaw[r[1].toUpperCase()]; else if (e.match(f.hexValue)) t.size = "16bit"; else throw new Error(s`expected valid size specifier, but got ${e.slice(0, 6)}`); else if (r = e.match(f.sizesExt)) e = e.slice(r[0].length), t.size = k.fromRaw[r[1].toUpperCase()]; else throw new Error(s`expected valid size specifier, but got ${e.slice(0, 6)}`); if (r = e.match(f.hexValue)) { e = e.slice(r[0].length); const i = r[1]; t.value = +("0x" + i); } else throw new Error( s`expected memory address as hex number, but got ${e.slice(0, 6)}` ); } else if (o && (r = e.match(f.valueHex))) e = e.slice(r[0].length), t.type = "Value", t.value = T(parseInt(r[1].replace(f.hexPrefix, "0x"))); else if (o && (r = e.match(f.valueInteger))) e = e.slice(r[0].length), t.type = "Value", t.value = T(Number(r[1])); else throw new Error(s`expected proper definition, but got ${e.slice(0, 6)}`); return [t, e]; }, cmp(e) { const t = e.match(f.cmp); if (!t) throw new Error(s`expected an operator, but got ${e.slice(0, 6)}`); return [t[1], e.slice(t[0].length)]; }, hits(e) { const t = e.match(f.hits); if (!t) throw new Error(s`expected hits definition, but got ${e}`); const r = t[1]; if (D(r, { isInteger: !0, isPositive: !0 })) { const o = Number(r); if (o > 4294967295) throw new Error( `expected hits to be within the range of 0x0 .. 0xFFFFFFFF, but got ${r}` ); return [o, e.slice(t[0].length)]; } else throw new Error(s`expected hits as unsigned integer, but got ${r}`); } }; function Se(e) { e = e.trim(); const t = { flag: "", lvalue: { type: "", size: "", value: 0 }, cmp: "", rvalue: { type: "", size: "", value: 0 }, hits: 0 }; [t.flag, e] = C.flag(e); try { [t.lvalue, e] = C.value(e); } catch (r) { throw $(r, `lvalue: ${r.message}`); } if (e) { [t.cmp, e] = C.cmp(e); const r = ue(t), o = w.isLegalForReading(t.cmp); if ((o || w.isLegalForCalc(t.cmp)) === !1) throw r ? new Error( `expected comparison operator (${w.forReading.join(" ")}), but got ` + s`${t.cmp}` ) : new Error( `expected calculation operator (${w.forCalc.join(" ")}), but got ` + s`${t.cmp}` ); try { [t.rvalue, e] = C.value(e); } catch (n) { throw $(n, `rvalue: ${n.message}`); } e && ([t.hits, e] = C.hits(e)), r === !1 && o && (t.cmp = "", t.rvalue = { type: "", size: "", value: 0 }); } return t; } function Y(e) { if (e.type === "Value") { const t = e.value - 4294967295 - 1; return t >= -4096 && t < 0 ? t.toString() : e.value >= 1e5 ? j(e.value) : e.value.toString(); } else if (e.type) return e.type !== "Float" || e.value >= 1e5 ? j(e.value) : e.value.toString(); } function Re(e) { const t = e[4] === void 0 && e[5] === void 0 && e[6] === void 0 && e[7] === void 0; return { flag: e[0], lvalue: { type: e[1], size: e[2], value: e[3] }, cmp: t ? "" : e[4], rvalue: { type: t ? "" : e[5], size: t ? "" : e[6], value: t ? 0 : e[7] }, hits: e[8] === void 0 ? 0 : e[8] }; } function ee(e) { let t = ""; return e.type === "Value" ? t += e.value : e.type === "Float" ? (t += "f", t += e.value, Number.isInteger(e.value) && (t += ".0")) : e.type === "Recall" ? t += "{recall}" : (t += A.withSize.toRaw[e.type], k.toRaw.hasOwnProperty(e.size) ? (t += "f", t += k.toRaw[e.size]) : (t += "0x", t += G.toRaw[e.size]), t += e.value.toString(16)), t; } function fe(e) { e.forEach((t, r) => { const o = W(r); t.forEach((i, n) => { if (de(i)) throw new Error( `${o}, condition ${n + 1}: cannot have Measured condition without rvalue specified` ); if (ge(i)) throw new Error( `${o}, condition ${n + 1}: expected comparison operator (${w.forReading.join(" ")}), but got ` + s`${i.cmp}` ); }); }); } function te(e) { return Array.isArray(e) ? Object.assign( {}, typeof e[0] == "string" && { type: e[0] }, typeof e[1] == "string" && { size: e[1] }, typeof e[2] == "number" && { value: e[2] } ) : e; } class E { constructor(t) { if (t instanceof E) return t; if (typeof t == "string") Object.assign(this, Se(t)); else if (Array.isArray(t)) Object.assign(this, Re(t)); else if (x(t)) this.flag = t.flag, this.cmp = t.cmp, this.rvalue = { ...t.rvalue }, this.lvalue = { ...t.lvalue }, this.hits = t.hits; else throw new Error( s`condition data must be an array, object or string with condition code, but got ${t}` ); Z.enums(this), this.lvalue = O.value(this.lvalue, "lvalue"), O.memoryComparisons(this), O.calculations(this), Z.hits(this), P(this); } with(t) { return new E({ ...this, ...t, lvalue: { ...this.lvalue, ...te(t.lvalue) }, rvalue: { ...this.rvalue, ...te(t.rvalue) } }); } toString() { let t = ""; return this.flag !== "" && (t += N.toRaw[this.flag] + ":"), t += ee(this.lvalue), I(this) && (t += this.cmp, t += ee(this.rvalue), this.hits && (t += "." + this.hits + ".")), t; } toArray() { return [ this.flag, this.lvalue.type, this.lvalue.size, this.lvalue.value, this.cmp, this.rvalue.type, this.rvalue.size, this.rvalue.value, this.hits ]; } toArrayPretty() { const t = this.rvalue.type === "Recall", r = I(this); return [ this.flag, this.lvalue.type, this.lvalue.size, Y(this.lvalue), this.cmp, r ? this.rvalue.type : "", t ? "" : r ? this.rvalue.size : "", t ? "" : r ? Y(this.rvalue) : "", this.hits > 0 ? this.hits.toString() : "" ]; } } function L(e, t = {}) { const { considerLegacyValueFormat: r = !1 } = t, o = e.split(r ? "$" : new RegExp("(?<!0x)S")).map((n) => n.trim().length > 0 ? n.split("_") : []), i = r && o.every( (n) => n.every((a) => a.match(f.flag) === null) ); return o.map( (n, a) => n.map((l, u) => { if (i) { l.match(f.legacyTrailingFloat) && (l = l.replace( f.legacyTrailingFloat, (y) => "f" + y )); const c = l.match(f.legacyValue); if (c) { const y = c[1] || "+"; let d = Number(c[2]); d < 0 && (d = T(d)), d > 2147483647 && (d = 2147483647), y === "-" && d > 0 && (d = -d), l = d.toString(); } l = (u === n.length - 1 ? "M" : "A") + ":" + l; } try { return new E(l); } catch (c) { const h = W(a); throw $(c, `${h}, condition ${u + 1}: ${c.message}`); } }) ); } function Q(e, t = {}) { const r = []; if (typeof e == "string") return L(e, t); if (Array.isArray(e)) { const o = []; for (let i = 0; i < e.length; i++) { const n = e[i]; try { n instanceof p ? o.push(...n) : o.push(new E(n)); } catch (a) { throw $(a, `conditions[${i}]: ${a.message}`); } } r.push(o); } else if (e instanceof p) r.push([...e]); else if (x(e)) { let o = !1; const i = []; for (const a in e) { const l = a.match(/^(?:core|alt([1-9]\d*))$/); if (l) l[0] === "core" ? o = !0 : l[1] && i.push(Number(l[1])); else throw new Error(`conditions.${a}: group name must be "core" or "alt1", "alt2"...`); } if (!o) throw new Error('conditions: expected "core" group'); i.sort((a, l) => a - l).forEach((a, l) => { if (a !== l + 1) throw new Error( `conditions: expected "alt${l + 1}" group, but got "alt${a}", make sure there are no gaps` ); }); const n = ["core", ...i.map((a) => `alt${a}`)]; for (const a of n) { const l = e[a]; if (typeof l == "string") try { r.push(...L(l, t)); } catch (u) { throw $(u, `conditions.${a}: ${u.message}`); } else if (l instanceof p) r.push([...l]); else if (Array.isArray(l)) { const u = []; for (let c = 0; c < l.length; c++) try { const h = l[c]; h instanceof p ? u.push(...h) : u.push(new E(h)); } catch (h) { throw $(h, `conditions.${a}[${c}]: ${h.message}`); } r.push(u); } else throw new Error( `conditions.${a}: expected an array of conditions or string, but got ` + s`${l}` ); } } else throw new Error( s`expected conditions as object, array of arrays or string, but got ${e}` ); return r; } const f = (() => { const e = [...w.forCalc, ...w.forReading].sort((t, r) => r.length - t.length).map( (t) => t.split("").map((r) => `\\${r}`).join("") ); return { cmp: new RegExp(`^(${e.join("|")})`), flag: /^(.*?):/i, hits: /^\.(.*)\./, hexPrefix: /h/i, hexValue: /^([\dabcdef]+)/i, sizesRegular: new RegExp( "^(" + Object.values(G.toRaw).filter(Boolean).join("|") + ")", "i" ), sizesExt: new RegExp("^(" + Object.values(k.toRaw).filter(Boolean).join("|") + ")", "i"), memAddress: /^(0x|f)/i, type: new RegExp( "^(" + Object.values(A.withSize.toRaw).filter(Boolean).join("|") + ")", "i" ), valueHex: /^(-?h[\dabcdef]+)/i, valueInteger: /^(-?\d+)/, valueFloat: /^f(-?\d+\.\d+)/i, legacyTrailingFloat: /(-?\d+\.\d+)$/, legacyValue: /^v([-+])?([-+]?\d+)$/i }; })(), pe = ["missable", "progression", "win_condition"], Ne = /* @__PURE__ */ new Set(["", ...pe]), R = { points(e) { if (D(e, { isInteger: !0, isPositive: !0 }) === !1) throw new Error( "expected points value to be a positive integer, but got " + s`${e}` ); }, measuredConditionsMixing(e) { const t = [], r = []; if (e.forEach((o, i) => { const n = W(i); o.forEach((a, l) => { a.flag === "Measured" && t.push([n, l]), a.flag === "Measured%" && r.push([n, l]); }); }), r.length > 0 && t.length > 0) { const o = t[0], i = r[0]; throw new Error( `${o[0]}, condition ${o[1] + 1}: Measured conflicts with ${i[0]}, condition ${i[1] + 1} Measured%, make sure you exclusively use Measured or Measured%` ); } }, andNormalizeAuthor(e) { if (e == null && (e = ""), typeof e != "string") throw new Error(s`expected author as string, but got ${e}`); return e || "cruncheevos"; }, andNormalizeAchievementType(e) { if (e = e === void 0 ? "" : e, Ne.has(e) === !1) throw new Error( `expected type to be one of: [${[...pe].join(", ")}], or empty string, or undefined, but got ` + s`${e}` ); return e; }, andNormalizeBadge(e) { const t = s`expected badge as unsigned integer or filepath starting with local\\\\ and going strictly down, but got ${e}`; if (e == null) return "00000"; if (D(e, { isInteger: !0 })) { const r = Number(e); if (r < 0 || r > 4294967295) throw new Error( `expected badge id to be within the range of 0x0 .. 0xFFFFFFFF, but got ${e}` ); return e.toString().padStart(5, "0"); } else if (typeof e == "string") { const r = e.split("\\\\"); if (r.length < 2 || r[0] !== "local") throw new Error(t); for (const i of r) if (/^\.+$/.test(i)) throw new Error(`encountered ${i} within ${e}, path can only go down`); const o = r[r.length - 1]; if (/^.+\.(png|jpe?g|gif)$/.test(o) === !1) throw new Error(`expected badge filename to be *.(png|jpg|jpeg|gif) but got "${o}"`); return e; } else throw new Error(t); } }, Ae = /^\s+$/; function Le(e) { const t = le(e); if (t.length !== 13 && t.length !== 14) throw new Error( "got an unexpected amount of data when parsing raw achievement string, either there's not enough data or it's not escaped/quoted correctly" ); let r = t[6]; r.match(Ae) && (r = ""); const [o, i] = t[0].split("|"); return { id: b.andNormalizeId(o), setId: i === void 0 ? i : b.andNormalizeId(i, "setId"), title: t[2], description: t[3], type: r, author: t[7], points: Number(t[8]), badge: R.andNormalizeBadge(t[13] || ""), conditions: L(t[1]) }; } const re = Symbol(); class V { constructor(t) { const r = t instanceof V; if (typeof t == "string") Object.assign(this, Le(t)); else if (x(t) && r === !1) { let o = t.conditions; t[re] || (o = Q(t.conditions)), Object.assign(this, { id: b.andNormalizeId(t.id), setId: t.setId === void 0 ? t.setId : b.andNormalizeId(t.setId, "setId"), title: t.title, description: t.description, author: t.author, points: t.points, type: t.type, badge: R.andNormalizeBadge(t.badge), conditions: o }); } else throw new Error( "achievement data must be an object or string with achievement code, but got " + (r ? "another Achievement instance" : s`${t}`) ); b.string(this.title, "title"), b.string(this.description, "description"), this.author = R.andNormalizeAuthor(this.author), R.points(this.points), this.type = R.andNormalizeAchievementType(this.type), fe(this.conditions), R.measuredConditionsMixing(this.conditions), P(this); } with(t) { return new V({ ...this, ...t, [re]: t.hasOwnProperty("conditions") === !1 }); } toString(t = "achievement") { const r = this.conditions.map((o) => o.map((i) => i.toString()).join("_")).join("S"); if (t === "conditions") return r; if (t === "achievement" || t === "achievement-legacy") { let o = ""; return o += this.id, t === "achievement" && this.setId !== void 0 && (o += "|" + this.setId), o += ":", o += `"${r}":`, o += M(this.title) + ":", o += M(this.description), o += ":::", o += this.type + ":", o += M(this.author) + ":", o += this.points, o += ":::::", o += this.badge.startsWith("local\\\\") ? `"${this.badge}"` : this.badge, o; } else throw new Error(s`unexpected achievement data toString request: ${t}`); } } const oe = /* @__PURE__ */ new Set(["start", "cancel", "submit", "value"]), ie = /* @__PURE__ */ new Set([ "SCORE", "TIME", "FRAMES", "MILLISECS", "SECS", "TIMESECS", "MINUTES", "SECS_AS_MINS", "VALUE", "UNSIGNED", "TENS", "HUNDREDS", "THOUSANDS", "FIXED1", "FIXED2", "FIXED3" ]), F = { andNormalizeLeaderboardId(e) { if (typeof e == "string") if (e.startsWith("L")) e = e.slice(1); else throw new Error(`expected id to start with L, but got "${e}"`); return b.andNormalizeId(e); }, andNormalizeConditions(e) { let t; if (typeof e == "string") t = ze(e); else if (x(e)) t = Object.keys(e).reduce((r, o) => { if (oe.has(o) === !1) throw new Error( `expected leaderboard condition group name to be one of: [${[ ...oe ].join(", ")}], but got ` + s`${o}` ); return r[o] = Q(e[o], { considerLegacyValueFormat: o === "value" }), r; }, {}); else throw new Error(s`expected conditions to be an object, but got ${e}`); for (const r of t.value) if (r.some((i) => i.flag === "Measured") === !1) for (let i = 0; i < r.length; i++) { const n = r[i]; if (n.flag === "") { r[i] = n.with({ flag: "Measured" }); break; } } return t; }, leaderboardType(e) { if (ie.has(e) === !1) throw new Error( `expected type to be one of: [${[...ie].join(", ")}], but got ` + s`${e}` ); }, measuredConditions(e) { for (const t of ["start", "cancel", "submit", "value"]) try { t !== "value" && fe(e[t]), F.lackOfMeasuredPercent(e[t]); } catch (r) { throw $(r, `${ae(t)}, ` + r.message); } }, lackOfMeasuredPercent(e) { e.forEach((t, r) => { t.forEach((o, i) => { if (o.flag === "Measured%") { const n = W(r); throw new Error( `${n}, condition ${i + 1}: Measured% conditions are not allowed in leaderboards` ); } }); }); }, andNormalizeLowerIsBetter(e) { if (typeof e == "string") return e.length > 0 && e !== "0"; if (typeof e == "boolean") return e; throw new Error( s`expected lowerIsBetter as boolean or string, but got ${e}` ); } }; function U(e, t = "S") { return e.map((r) => r.map((o) => o.toString()).join("_")).join(t); } function Ce(e) { const t = le(e); if (t.length !== 9) throw new Error( "got an unexpected amount of data when parsing raw leaderboard string, either there's not enough data or it's not escaped/quoted correctly" ); const [r, o] = t[0].split("|"); return { id: F.andNormalizeLeaderboardId(r), setId: o === void 0 ? o : b.andNormalizeId(o, "setId"), conditions: F.andNormalizeConditions({ start: t[1], cancel: t[2], submit: t[3], value: t[4] }), type: t[5], title: t[6], description: t[7], lowerIsBetter: F.andNormalizeLowerIsBetter(t[8]) }; } const ne = Symbol(); class B { constructor(t) { const r = t instanceof B; if (typeof t == "string") Object.assign(this, Ce(t)); else if (x(t) && r === !1) { let o = t.conditions; t[ne] || (o = F.andNormalizeConditions(t.conditions)), Object.assign(this, { ...t, id: F.andNormalizeLeaderboardId(t.id), setId: t.setId === void 0 ? t.setId : b.andNormalizeId(t.setId, "setId"), title: t.title, description: t.description, type: t.type, lowerIsBetter: F.andNormalizeLowerIsBetter(t.lowerIsBetter), conditions: o }); } else throw new Error( "leaderboard data must be an object or string with leaderboard code, but got " + (r ? "another Leaderboard instance" : s`${t}`) ); b.string(this.title, "title"), b.string(this.description, "description"), F.leaderboardType(this.type), F.measuredConditions(this.conditions), P(this); } with(t) { return new B({ ...this, ...t, [ne]: t.hasOwnProperty("conditions") === !1 }); } toString(t = "leaderboard") { const r = [ U(this.conditions.start), U(this.conditions.cancel), U(this.conditions.submit), U(this.conditions.value, "$") ].map((o) => `"${o}"`); if (t === "conditions") return r.join(":"); if (t === "leaderboard" || t === "leaderboard-legacy") { let o = ""; return o += "L" + this.id, t === "leaderboard" && this.setId !== void 0 && (o += "|" + this.setId), o += ":", o += r.join(":") + ":", o += this.type + ":", o += M(this.title) + ":", o += M(this.description) + ":", o += Number(this.lowerIsBetter), o; } else throw new Error(s`unexpected leaderboard data toString request: ${t}`); } } function ze(e) { const t = { start: null, cancel: null, submit: null, value: null }; let r = null; for (const [o, i] of [ ["STA", "start"], ["CAN", "cancel"], ["SUB", "submit"], ["VAL", "value"] ]) { const n = o === "VAL"; if (r = e.match(new RegExp(n ? /VAL:(.+)/ : `${o}:(.+?)::`))) { e = e.slice(r[0].length); try { t[i] = Q(r[1], { considerLegacyValueFormat: n }); } catch (a) { throw $(a, `${ae(i)}, ${a.message}`); } } else throw new Error(`expected ${o}:<conditions>::, but got ${e.slice(0, 6)}`); } return t; } const q = /* @__PURE__ */ new WeakMap(); function* se() { for (const e in this) yield this[e]; } class qe { constructor(t) { this.achievements = { [Symbol.iterator]: se }, this.leaderboards = { [Symbol.iterator]: se }; const { gameId: r, id: o, title: i } = t; this.gameId = b.andNormalizeId(r, "gameId"), o !== void 0 && (this.id = b.andNormalizeId(o, "id")), b.nonEmptyString(i, "achievement set title"), this.title = i, q.set(this, { achievementIdCounter: 111000001, leaderboardIdCounter: 111000001 }); } addAchievement(t) { const r = q.get(this); let o = t instanceof V ? t : new V( typeof t == "string" ? t : { ...t, id: t.id || r.achievementIdCounter } ); this.id !== o.setId && (o = o.with({ setId: this.id })); const { id: i } = o; if (this.achievements[i]) throw new Error(`achievement with id ${i}: "${this.achievements[i].title}", already exists`); return this.achievements[i] = o, o.id >= r.achievementIdCounter && (r.achievementIdCounter = Math.max(r.achievementIdCounter + 1, o.id + 1)), this; } addLeaderboard(t) { const r = q.get(this); let o = t instanceof B ? t : new B( typeof t == "string" ? t : { ...t, id: t.id || r.leaderboardIdCounter } ); this.id !== o.setId && (o = o.with({ setId: this.id })); const { id: i } = o; if (this.leaderboards[i]) throw new Error(`leaderboard with id ${i}: "${this.leaderboards[i].title}", already exists`); return this.leaderboards[i] = o, o.id >= r.leaderboardIdCounter && (r.leaderboardIdCounter = Math.max(r.leaderboardIdCounter + 1, o.id + 1)), this; } *[Symbol.iterator]() { for (const t of this.achievements) yield t; for (const t of this.leaderboards) yield t; } toString(t = "set") { let r = ""; return r += `1.0 `, r += this.title + ` `, Object.keys(this.achievements).sort((o, i) => Number(o) - Number(i)).forEach((o) => { r += this.achievements[o].toString( t === "set-legacy" ? "achievement-legacy" : "achievement" ) + ` `; }), Object.keys(this.leaderboards).sort((o, i) => Number(o) - Number(i)).forEach((o) => { r += this.leaderboards[o].toString( t === "set-legacy" ? "leaderboard-legacy" : "leaderboard" ) + ` `; }), r; } } const me = Symbol("isRichLookupOrFormat"), Me = /* @__PURE__ */ new Set([ "VALUE", "SCORE", "POINTS", "TIME", "FRAMES", "MILLISECS", "SECS", "MINUTES", "SECS_AS_MINS", "FLOAT1", "FLOAT2", "FLOAT3", "FLOAT4", "FLOAT5", "FLOAT6" ]); function Oe(e) { const { numbers: t, notNumbers: r } = e.reduce( (a, l) => { const u = Number(l); return Number.isNaN(u) ? a.notNumbers.push(l) : a.numbers.push(u), a; }, { numbers: [], notNumbers: [] } ); if (t.length === 0) return { formattedRanges: "", notNumbers: r }; const o = []; let i = t[0], n = t[0]; for (let a = 1; a < t.length; a++) t[a] - n === 1 || (o.push([i, n]), i = t[a]), n = t[a]; return o.push([i, n]), { notNumbers: r, formattedRanges: o.map(([a, l]) => a === l ? `${a}` : `${a}-${l}`).join(",") }; } function je(e) { return e && typeof e != "string" && e[me]; } function we(e, ...t) { return e.map((r, o) => { let i = o === e.length - 1 ? "" : t[o]; return je(i) && (i = i.at()), `${r}${i}`; }).join(""); } function z(e) { const t = e instanceof p ? e.conditions : Array.isArray(e) ? e : [e]; return t[t.length - 1].flag !== "Measured"; } function K(e) { return (e instanceof p ? e.conditions : [e]).length === 1 ? e.toString().replace("M:", "").replace(" ", "") : e.toString(); } function be(e, t) { return `?${e}?${t}`; } function g(e) { const { name: t, type: r } = e; return { name: t, type: r, toString() { return `Format:${t} FormatType=${r}`; }, at(o) { if (typeof o == "string") { try { L(o, { considerLegacyValueFormat: !0 }); } catch (i) { throw $( i, s`Rich Presence Format ${t} got invalid string input: ${i.message}` ); } return `@${t}(${o})`; } if (o instanceof E || o instanceof p) { if (z(o)) throw new Error( s`Rich Presence Format ${t} got invalid input: must have at least one condition with Measured flag, but got ${o.toString()}` ); return `@${t}(${K(o)})`; } throw new Error(s`Rich Presence Format ${t} got invalid input: ${o}`); } }; } function ye(e) { const { name: t, type: r } = e; if (typeof t != "string") throw new Error( s`Rich Presence Format expected to have a name as string, but got ${t}` ); if (Me.has(r) === !1) throw new Error(s`Rich Presence Format ${t} got unexpected type: ${r}`); return g(e); } function $e(e) { const { name: t, values: r, defaultAt: o, compressRanges: i = !0 } = e; let n = ""; if (typeof t != "string") throw new Error( s`Rich Presence Lookup expected to have a name as string, but got ${t}` ); const a = Object.entries(r || {}); if (a.length === 0) throw new Error( s`Rich Presence Lookup ${t} must define at least one key-value pair` ); for (const [c, h] of a) if (c !== "*" && D(c, { isInteger: !0, isPositive: !0 }) === !1) throw new Error( s`Rich Presence Lookup ${t} got invalid key-value pair ${c}: ${h}, value must be positive integer or "*"` ); if (o !== void 0) if (typeof o == "string") { try { var l = L(o, { considerLegacyValueFormat: !0 }); } catch (c) { throw $( c, s`Rich Presence Lookup ${t} got invalid defaultAt: ${c.message}` ); } if (z(l[0])) throw new Error( s`Rich Presence Lookup ${t} got invalid input: must have at least one condition with Measured flag, but got ${o}` ); n = `@${t}(${o})`; } else if (o instanceof E || o instanceof p) { if (z(o)) throw new Error( s`Rich Presence Lookup ${t} got invalid defaultAt: must have at least one condition with Measured flag, but got ${o}` ); n = `@${t}(${K(o)})`; } else throw new Error( s`Rich Presence Lookup ${t} defaultAt expected to be a string, Condition or ConditionBuilder, but got ${o}` ); let u = r; if (i) { const c = a.reduce((h, [y, d]) => (h[d] || (h[d] = []), h[d].push(y), h), {}); u = Object.entries(c).reduce((h, [y, d]) => { const { notNumbers: X, formattedRanges: J } = Oe(d); J && (h[J] = y); for (const ve of X) h[ve] = y; return h; }, {}); } return { [me]: !0, name: t, toString(c = "dec") { let h = `Lookup:${t}`; for (const y in u) { let d = y; d !== "*" && c.startsWith("hex") && (d = d.replace( /\d+/g, (X) => j(Number(X), c !== "hex-lowercase") )), h += ` ${d}=${u[y]}`; } return h; }, at: function(h) { if (h === void 0) { if (n) return n; throw new Error(`Rich Presence Lookup ${t} got no input, neither defaultAt specified`); } if (typeof h == "string") { try { var y = L(h, { considerLegacyValueFormat: !0 }); } catch (d) { throw $( d, s`Rich Presence Lookup ${t} got error when parsing input: ${d.message}` ); } if (z(y[0])) throw new Error( s`Rich Presence Lookup ${t} got invalid input: must have at least one condition with Measured flag, but got ${h}` ); return `@${t}(${h})`; } else if (h instanceof E || h instanceof p) { if (z(h)) throw new Error( s`Rich Presence Lookup ${t} got invalid input: must have at least one condition with Measured flag, but got ${h}` ); return `@${t}(${K(h)})`; } else throw new Error(s`Rich Presence Lookup ${t} got invalid input: ${h}`); } }; } const S = (e) => { const { format: t = {}, lookup: r = {}, lookupDefaultParameters: o = {} } = e, i = Object.keys(t).reduce((u, c) => (u[c] = ye({ name: c, type: t[c] }), u), {}), n = Object.keys(r).reduce((u, c) => (u[c] = $e({ compressRanges: o.compressRanges, ...r[c], name: c }), u), {}), a = e.displays({ lookup: n, format: i, tag: we, macro: S.macro }); if (a.length === 0) throw new Error("Rich Presence displays must return at least one display string"); const l = a.map((u, c) => { if (typeof u == "string") return u; if (Array.isArray(u)) { if (u.length !== 2) throw new Error( `Rich Presence displays[${c}] must be either a string or an array with two strings` ); return be(u[0], u[1]); } throw new Error( `Rich Presence displays[${c}] must be either a string or an array with two strings` ); }); return { lookup: n, format: i, displayStrings: l, macro: S.macro, toString() { return [ Object.values(i).join(` `), Object.values(n).map((u) => u.toString(r[u.name].keyFormat || o.keyFormat)).join(` `) ].join(` `).trim() + ` Display: ` + l.join(` `); } }; }; S.display = be; S.format = ye; S.lookup = $e; S.tag = we; S.macro = { Number: g({ name: "Number", type: "VALUE" }), Unsigned: g({ name: "Unsigned", type: "UNSIGNED" }), Score: g({ name: "Score", type: "SCORE" }), Centiseconds: g({ name: "Centiseconds", type: "MILLISECS" }), Seconds: g({ name: "Seconds", type: "SECS" }), Minutes: g({ name: "Minutes", type: "MINUTES" }), Fixed1: g({ name: "Fixed1", type: "FIXED1" }), Fixed2: g({ name: "Fixed2", type: "FIXED2" }), Fixed3: g({ name: "Fixed3", type: "FIXED3" }), Float1: g({ name: "Float1", type: "FLOAT1" }), Float2: g({ name: "Float2", type: "FLOAT2" }), Float3: g({ name: "Float3", type: "FLOAT3" }), Float4: g({ name: "Float4", type: "FLOAT4" }), Float5: g({ name: "Float5", type: "FLOAT5" }), Float6: g({ name: "Float6", type: "FLOAT6" }), ASCIIChar: g({ name: "ASCIIChar", type: "ASCIIChar" }), UnicodeChar: g({ name: "UnicodeChar", type: "UnicodeChar" }) }; export { V as Achievement, qe as AchievementSet, E as Condition, p as ConditionBuilder, B as Leaderboard, S as RichPresence, Ve as addHits, Fe as andNext, ce as define, Ue as measured, He as measuredIf, De as measuredPercent, Xe as once, Ge as orNext, ke as pauseIf, Te as resetIf, We as resetNextIf, Ee as stringToNumberLE, Be as subHits, Pe as trigger };