@cruncheevos/core
Version:
Parse and generate achievements and leaderboards for RetroAchievements.org
1,625 lines • 57.8 kB
JavaScript
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 U(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 y(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 F = {
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;
},
title(e, t = "title") {
if (typeof e != "string" || e.trim().length === 0)
throw new Error(
`expected ${t} as non-empty string, but got ` + s`${e}`
);
},
andNormalizeDescription(e) {
if (e == null)
return "";
if (typeof e != "string")
throw new Error(s`expected description as string, but got ${e}`);
return 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 $(e) {
return function(...t) {
const r = new p();
return m.call(r, e, ...t), r;
};
}
const ce = $("");
ce.one = function(e) {
if (arguments.length > 1)
throw new Error("expected only one condition argument, but got " + arguments.length);
return new v(e);
};
ce.str = function(e, t) {
return Fe(
...Ee(e).map((r, o) => {
let i = t(
// prettier-ignore
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 = $("Trigger"), Te = $("ResetIf"), ke = $("PauseIf"), Ve = $("AddHits"), Be = $("SubHits"), De = $("Measured"), Ue = $("Measured%"), He = $("MeasuredIf"), We = $("ResetNextIf"), Fe = $("AndNext"), Ge = $("OrNext"), Xe = (...e) => new p().also("once", ...e), _ = /* @__PURE__ */ new WeakMap();
class p {
constructor() {
this.conditions = [], _.set(this, "");
}
/**
* Adds conditions wrapped with Trigger flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').trigger('0=2', '0=3').toString() // 0=1_T:0=2_T:0=3
*/
trigger(...t) {
return m.call(this, "Trigger", ...t), this;
}
/**
* Adds conditions wrapped with ResetIf flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').resetIf('0=2', '0=3').toString() // 0=1_R:0=2_R:0=3
*/
resetIf(...t) {
return m.call(this, "ResetIf", ...t), this;
}
/**
* Adds conditions wrapped with PauseIf flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').pauseIf('0=2', '0=3').toString() // 0=1_P:0=2_P:0=3
*/
pauseIf(...t) {
return m.call(this, "PauseIf", ...t), this;
}
/**
* Adds conditions wrapped with AddHits flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').addHits('0=2', '0=3').toString() // 0=1_C:0=2_C:0=3
*/
addHits(...t) {
return m.call(this, "AddHits", ...t), this;
}
/**
* Adds conditions wrapped with SubHits flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').subHits('0=2', '0=3').toString() // 0=1_D:0=2_D:0=3
*/
subHits(...t) {
return m.call(this, "SubHits", ...t), this;
}
/**
* Adds conditions wrapped with Measured flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').measured('0=2', '0=3').toString() // 0=1_M:0=2_M:0=3
*/
measured(...t) {
return m.call(this, "Measured", ...t), this;
}
/**
* Adds conditions wrapped with Measured% flag to the chain
*
* RAIntegration converts Measured flags to Measured% if *Track as %* checkbox is ticked
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').measuredPercent('0=2', '0=3').toString() // 0=1_G:0=2_G:0=3
*/
measuredPercent(...t) {
return m.call(this, "Measured%", ...t), this;
}
/**
* Adds conditions wrapped with Measured flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').measuredIf('0=2', '0=3').toString() // 0=1_Q:0=2_Q:0=3
*/
measuredIf(...t) {
return m.call(this, "MeasuredIf", ...t), this;
}
/**
* Adds conditions wrapped with ResetNextIf flag to the chain
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').resetNextIf('0=2', '0=3').toString() // 0=1_Z:0=2_Z:0=3
*/
resetNextIf(...t) {
return m.call(this, "ResetNextIf", ...t), this;
}
/**
* Adds conditions wrapped with AndNext flag to the chain
*
* The final condition in the chain will not have AndNext flag
* applied, because the condition will not work correctly that way
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').andNext('0=2', '0=3').toString() // 0=1_N:0=2_0=3
* $('0=1')
* .andNext('0=2', '0=3')
* .resetIf('0=4').toString() // 0=1_N:0=2_N:0=3_R:0=4
*/
andNext(...t) {
return m.call(this, "AndNext", ...t), this;
}
/**
* Adds conditions wrapped with OrNext flag to the chain
*
* The final condition in the chain will not have OrNext flag
* applied, because the condition will not work correctly that way
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1').orNext('0=2', '0=3').toString() // 0=1_O:0=2_0=3
* $('0=1')
* .orNext('0=2', '0=3')
* .resetIf('0=4').toString() // 0=1_O:0=2_O:0=3_R:0=4
*/
orNext(...t) {
return m.call(this, "OrNext", ...t), this;
}
/**
* Adds conditions to the chain as is
*
* @example
* import { define as $, resetIf } from '@cruncheevos/core'
* resetIf('0=1', '0=2')
* .also('0=3')
* .toString() // R:0=1_R:0=2_0=3
*/
also(...t) {
return m.call(this, "", ...t), this;
}
/**
* Adds conditions as is with final condition set to have 1 hit
*
* @example
* import { define as $ } from '@cruncheevos/core'
* $('0=1')
* .once(
* andNext('0=2', '0=3')
* ).toString() // 0=1_N:0=2_0=3.1.
*/
once(...t) {
return m.call(this, "", "once", ...t), this;
}
*[Symbol.iterator]() {
for (const t of this.conditions)
yield t;
}
/**
* Returns new instance of ConditionBuilder with mapped conditions
*
* Accepts a callback function that acts similar to Array.prototype.map
*
* If any conditional condition was ignored, it will not appear in the callback
*
* @example
* $('0=1', false && '0=2', '0=3')
* .map((c, i) => c.with({ hits: i + 1 }))
* .toString() // 0=1.1._0=3.2.
*/
map(t) {
const r = this.conditions.map(t);
return new p().also(...r);
}
/**
* Returns new instance of ConditionBuilder with different
* values merged into last condition
*
* `lvalue` and `rvalue` can be specified as partial array, which can be less verbose
*
* Useful when combined with pointer chains
*
* @param {Condition.PartialMergedData} data Condition.PartialMergedData
*
* @example
* $(
* ['AddAddress', 'Mem', '32bit', 0xcafe],
* ['AddAddress', 'Mem', '32bit', 0xbeef],
* ['', 'Mem', '32bit', 0, '=', 'Value', '', 120],
* ).withLast({ cmp: '!=', rvalue: { value: 9 } })
* .toString() // I:0xXcafe_I:0xXbeef_0xX0!=9
*
* $(
* ['AddAddress', 'Mem', '32bit', 0xcafe],
* ['AddAddress', 'Mem', '32bit', 0xbeef],
* ['', 'Mem', '32bit', 0, '=', 'Value', '', 120],
* ).withLast({ cmp: '!=', rvalue: rvalue: ['Delta', '32bit', 0] })
* .toString() // I:0xXcafe_I:0xXbeef_0xX0!=d0xX0
*/
withLast(t) {
return this.map((r, o, i) => o !== i.length - 1 ? r : r.with(t));
}
/**
* Returns a string with raw condition code
*
* @example
* $(
* ['AndNext', 'Mem', '32bit', 0xCAFE, '=', 'Value', '', 5],
* ['', 'Delta', '32bit', 0xCAFE, '=', 'Value', '', 4]
* ).toString() // N:0xXcafe=5_d0xXcafe=4
*/
toString() {
return this.conditions.join("_");
}
/**
* Same as {@link ConditionBuilder.prototype.toString toString()}
*
* @example
* JSON.stringify({ conditions: $('0=1', '0=2') })
* // {"conditions":"0=1_0=2"}
*/
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 v(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 I = (() => {
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 I.forReading.toRaw.hasOwnProperty(e.flag);
}
function he(e) {
return I.forCalc.toRaw.hasOwnProperty(e.flag);
}
function Se(e) {
return e.lvalue.type === "Recall" && S(e) === !1;
}
function fe(e) {
return e.flag === "Measured" && S(e) === !1;
}
function ge(e) {
return e.flag === "Measured" && S(e) && w.isLegalForCalc(e.cmp);
}
function S(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 y(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 (S(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 === "" && S(e))
throw new Error(`expected an accumulation operator (${w.forCalc.join(" ")}), but got ""`);
}
},
memoryComparisons(e) {
if (!(ue(e) === !1 || Se(e) || fe(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 (I.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(d.flag);
if (!t)
return ["", e];
const r = t[1];
if (I.fromRaw.hasOwnProperty(r.toUpperCase()) === !1)
throw new Error(s`expected a legal condition flag, but got ${t[0]}`);
return [I.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(d.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(d.valueFloat))
e = e.slice(r[0].length), t.type = "Float", t.value = Number(r[1]);
else if (r = e.match(d.memAddress)) {
if (e = e.slice(r[0].length), r[1].toLowerCase() === "0x")
if (r = e.match(d.sizesRegular))
e = e.slice(r[0].length), t.size = G.fromRaw[r[1].toUpperCase()];
else if (e.match(d.hexValue))
t.size = "16bit";
else
throw new Error(s`expected valid size specifier, but got ${e.slice(0, 6)}`);
else if (r = e.match(d.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(d.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(d.valueHex)))
e = e.slice(r[0].length), t.type = "Value", t.value = T(parseInt(r[1].replace(d.hexPrefix, "0x")));
else if (o && (r = e.match(d.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(d.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(d.hits);
if (!t)
throw new Error(s`expected hits definition, but got ${e}`);
const r = t[1];
if (U(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 Re(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 y(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 y(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 Ne(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 de(e) {
e.forEach((t, r) => {
const o = W(r);
t.forEach((i, n) => {
if (fe(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 v {
constructor(t) {
if (t instanceof v)
return t;
if (typeof t == "string")
Object.assign(this, Re(t));
else if (Array.isArray(t))
Object.assign(this, Ne(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);
}
/**
* Returns new Condition instance with different values merged.
*
* `lvalue` and `rvalue` can be specified as partial array, which can be less verbose
*
* @param {DeepPartial<Condition.Data>} data DeepPartial<Condition.Data>
*
* @example
* new Condition('0=1')
* .with({ cmp: '!=', rvalue: { value: 47 } })
* .toString() // 0!=47
*
* new Condition('0xXcafe=0xXfeed')
* .with({ rvalue: ['Delta', '16bit', 0xabcd] })
* .toString() // 0xXcafe=d0x abcd
*
* new Condition('0xXcafe=0xXfeed')
* .with({ rvalue: ['Delta'] })
* .toString() // 0xXcafe=d0xXfeed
*/
with(t) {
return new v({
...this,
...t,
lvalue: { ...this.lvalue, ...te(t.lvalue) },
rvalue: { ...this.rvalue, ...te(t.rvalue) }
});
}
/**
* Returns string representation of Condition
* suitable for RetroAchievements and local files.
* @example
* new Condition(['ResetIf', 'Mem', 'Bit0', 71, '>', 'Delta', 'Bit1', 71, 3]).toString() // 'R:0xM47>d0xN47.3.'
*/
toString() {
let t = "";
return this.flag !== "" && (t += I.toRaw[this.flag] + ":"), t += ee(this.lvalue), S(this) && (t += this.cmp, t += ee(this.rvalue), this.hits && (t += "." + this.hits + ".")), t;
}
/**
* Returns direct Array representation of Condition,
* values are exactly same as properties of Condition.
*
* @example
* new Condition(['Measured', 'Mem', '8bit', 4]).toArray()
* // [ "Measured", "Mem", "8bit", 4, "", "", "", 0, 0 ]
*/
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
];
}
/**
* Returns prettier Array representation of Condition, which is more suitable for display:
*
* * Everything is a string
* * Values are formatted as hexadecimal if they are greater or equal to 100000
* * Negative values are formatted as decimal if they are greater or equal to -4096, otherwise formatted as hexadecimal with underflow correction
* * Hits are empty string if equal to zero
*
* @example
* new cruncheevos.Condition(['ResetIf', 'Mem', '32bit', 0xfeedcafe, '>', 'Value', '', 71]).toArrayPretty()
* // [ "ResetIf", "Mem", "32bit", "0xfeedcafe", ">", "Value", "", "71", "" ]
*
* new cruncheevos.Condition(['', 'Value', '', -4097, '>', 'Value', '', -1]).toArrayPretty()
* // [ "", "Value", "", "0xffffefff", ">", "Value", "", "-1", "" ]
*/
toArrayPretty() {
const t = this.rvalue.type === "Recall", r = S(this);
return [
this.flag,
this.lvalue.type,
this.lvalue.size,
Y(this.lvalue),
this.cmp,
r ? this.rvalue.type : "",
// prettier-ignore
t ? "" : r ? this.rvalue.size : "",
// prettier-ignore
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(d.flag) === null)
);
return o.map(
(n, a) => n.map((l, u) => {
if (i) {
l.match(d.legacyTrailingFloat) && (l = l.replace(
d.legacyTrailingFloat,
(b) => "f" + b
));
const c = l.match(d.legacyValue);
if (c) {
const b = c[1] || "+";
let f = Number(c[2]);
f < 0 && (f = T(f)), f > 2147483647 && (f = 2147483647), b === "-" && f > 0 && (f = -f), l = f.toString();
}
l = (u === n.length - 1 ? "M" : "A") + ":" + l;
}
try {
return new v(l);
} catch (c) {
const h = W(a);
throw y(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 v(n));
} catch (a) {
throw y(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 y(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 v(h));
} catch (h) {
throw y(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 d = (() => {
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"], Ie = /* @__PURE__ */ new Set(["", ...pe]), N = {
points(e) {
if (U(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, Ie.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 (U(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];
return r.match(Ae) && (r = ""), {
id: F.andNormalizeId(t[0]),
title: t[2],
description: t[3],
type: r,
author: t[7],
points: Number(t[8]),
badge: N.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: F.andNormalizeId(t.id),
title: t.title,
description: t.description,
author: t.author,
points: t.points,
type: t.type,
badge: N.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}`)
);
F.title(this.title), this.description = F.andNormalizeDescription(this.description), this.author = N.andNormalizeAuthor(this.author), N.points(this.points), this.type = N.andNormalizeAchievementType(this.type), de(this.conditions), N.measuredConditionsMixing(this.conditions), P(this);
}
/**
* Returns new Achievement instance with different values merged.
*
* @param {DeepPartial<Achievement.InputObject>} data DeepPartial<Achievement.InputObject>
*
* @example
* someAchievement
* .with({ title: someAchievement.title + 'suffix' })
*/
with(t) {
return new V({
...this,
...t,
[re]: t.hasOwnProperty("conditions") === !1
});
}
/**
* Returns string representation of Achievement suitable
* for `RACache/Data/GameId-User.txt` file.
*
* @param desiredData optional parameter, set this to `'achievement'` or `'conditions'` to have corresponding string returned. Default option is `'achievement'`.
*
* @example
*
* someAchievement.toString()
* someAchievement.toString('achievement')
* // '58:"0=1":My Achievement:Do something funny::::cruncheevos:5:::::00000'
*
* someAchievement.toString('conditions') // '0=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") {
let o = "";
return o += this.id + ":", 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"
]), E = {
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 F.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" && de(e[t]), E.lackOfMeasuredPercent(e[t]);
} catch (r) {
throw y(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 D(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"
);
return {
id: E.andNormalizeLeaderboardId(t[0]),
conditions: E.andNormalizeConditions({
start: t[1],
cancel: t[2],
submit: t[3],
value: t[4]
}),
type: t[5],
title: t[6],
description: t[7],
lowerIsBetter: E.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 = E.andNormalizeConditions(t.conditions)), Object.assign(this, {
...t,
id: E.andNormalizeLeaderboardId(t.id),
title: t.title,
description: t.description,
type: t.type,
lowerIsBetter: E.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}`)
);
F.title(this.title), this.description = F.andNormalizeDescription(this.description), E.leaderboardType(this.type), E.measuredConditions(this.conditions), P(this);
}
/**
* Returns new Leaderboard instance with different values merged.
*
* @param {DeepPartial<Leaderboard.InputObject>} data DeepPartial<Leaderboard.InputObject>
*
* @example
* someLeaderboard
* .with({ title: someLeaderboard.title + 'suffix' })
*/
with(t) {
return new B({
...this,
...t,
[ne]: t.hasOwnProperty("conditions") === !1
});
}
/**
* Returns string representation of Leaderboard suitable
* for `RACache/Data/GameId-User.txt` file.
*
* @param desiredData optional parameter, set this to `'leaderboard'` or `'conditions'` to have corresponding string returned. Default option is `'leaderboard'`.
*
* @example
*
* someLeaderboard.toString()
* someLeaderboard.toString('leaderboard')
* // 'L58:"0xHfff0=1S":"0=1":"1=1":"M:0xX34440*2":SCORE:My Leaderboard:Best score while doing something funny:0'
*
* someLeaderboard.toString('conditions') // '"0xHfff0=1S":"0=1":"1=1":"M:0xX34440*2"'
*/
toString(t = "leaderboard") {
const r = [
D(this.conditions.start),
D(this.conditions.cancel),
D(this.conditions.submit),
D(this.conditions.value, "$")
].map((o) => `"${o}"`);
if (t === "conditions")
return r.join(":");
if (t === "leaderboard") {
let o = "";
return o += "L" + this.id + ":", 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 y(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 {
/**
* Creates AchievementSet.
*
* @example
* new AchievementSet({ gameId: 1234, title: 'Funny Game' })
*/
constructor(t) {
this.achievements = {
[Symbol.iterator]: se
}, this.leaderboards = {
[Symbol.iterator]: se
};
const { gameId: r, title: o } = t;
this.gameId = F.andNormalizeId(r, "gameId"), F.title(o, "achievement set title"), this.title = o, q.set(this, {
achievementIdCounter: 111000001,
leaderboardIdCounter: 111000001
});
}
/**
* Adds Achievement to the set, accepts same data as {@link Achievement} class constructor,
* but you're allowed to omit id when passing an object (id will be assigned automatically, similar to how RAIntegration does it).
*
* Also returns current AchievementSet instance, allowing you to chain calls.
*
* @example
* import { AchievementSet, define as $ } from '@cruncheevos/core'
*
* const set = new AchievementSet({ gameId: 1234, title: 'Funny Game' })
*
* set.addAchievement({
* id: 58, // optional, or numeric string
* title: 'My Achievement',
* description: 'Do something funny',
* points: 5,
* badge: `local\\\\my_achievement.png`, // optional, or ID of badge on server
* author: 'peepy', // optional and is not uploaded to server
* conditions: {
* core: [
* ['', 'Mem', '8bit', 0x00fff0, '=', 'Value', '', 0],
* ['', 'Mem', '8bit', 0x00fffb, '=', 'Value', '', 0],
* ],
* alt1: $(
* ['', 'Mem', '8bit', 0x00fe10, '>', 'Delta', '8bit', 0x00fe10],
* ['', 'Mem', '8bit', 0x00fe11, '=', 'Value', '', 0],
* ),
* alt2: '0=1'
* }
* }).addAchievement(...)
*/
addAchievement(t) {
const r = q.get(this), o = t instanceof V ? t : new V(
typeof t == "string" ? t : {
...t,
id: t.id || r.achievementIdCounter
}
), { 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;
}
/**
* Adds Leaderboard to the set, accepts same data as {@link Leaderboard} class constructor,
* but you're allowed to omit id when passing an object (id will be assigned automatically, similar to how RAIntegration does it).
*
* Also returns current AchievementSet instance, allowing you to chain calls.
*
* @example
* import { AchievementSet, define as $ } from '@cruncheevos/core'
*
* const set = new AchievementSet({ gameId: 1234, title: 'Funny Game' })
*
* set.addLeaderboard({
* id: 58, // optional, or numeric string
* title: 'My Leaderboard',
* description: 'Best score while doing something funny',
* type: 'SCORE',
* lowerIsBetter: false,
* conditions: {
* start: {
* core: [
* ['', 'Mem', '8bit', 0x00fff0, '=', 'Value', '', 0],
* ['', 'Mem', '8bit', 0x00fffb, '=', 'Value', '', 0],
* ],
* alt1: $(
* ['', 'Mem', '8bit', 0x00fe10, '>', 'Delta', '8bit', 0x00fe10],
* ['', 'Mem', '8bit', 0x00fe11, '=', 'Value', '', 0],
* ),
* alt2: '0=1',
* },
* cancel: [
* ['', 'Mem', '16bit', 0x34684, '=', 'Value', '', 0x140]
* ], // same as providing an object: { core: [ ... ] }
* submit: '0xH59d76=2',
* value: [['Measured', 'Mem', '32bit', 0x34440, '*', 'Value', '', 2]],
* },
* }).addLeaderboard(...)
*/
addLeaderboard(t) {
const r = q.get(this), o = t instanceof B ? t : new B(
typeof t == "string" ? t : {
...t,
id: t.id || r.leaderboardIdCounter
}
), { 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;
}
/**
* Allows to iterate the whole set for both achievements and leaderboards.
*
* @example
* for (const asset of achSet) {
* if (asset instanceof Achievement) {
* // ...
* }
* if (asset instanceof Leaderboard) {
* // ...
* }
* }
*/
*[Symbol.iterator]() {
for (const t of this.achievements)
yield t;
for (const t of this.leaderboards)
yield t;
}
/**
* Returns string representation of AchievementSet suitable for
* `RACache/Data/GameId-User.txt` file.
*
* First line is version, always set to 1.0, second line is game's title.
* Then come string representations of achievements and leaderboards,
* each sorted by id.
*
* @example
* new AchievementSet({ gameId: 1234, title: 'Funny Game' })
* .addAchievement(...)
* .addAchievement(...)
* .addLeaderboard(...)
* .addLeaderboard(...)
* .toString()
* // may result in:
* `
* 1.0
* Funny Game
* 57:"0x cafe=102":Ach2:Desc2::::cruncheevos:2:::::00000
* 111000001:"0x cafe=101":Ach1:Desc1::::cruncheevos:1:::::00000
* L58:"0x cafe=102":"0=1":"1=1":"M:0x feed":FRAMES:Lb2:Desc2:1
* L111000001:"0x cafe=101":"0=1":"1=1":"M:0x feed":SCORE:Lb1:Desc1:0
* `
*/
toString() {
let t = "";
return t += `1.0
`, t += this.title + `
`, Object.keys(this.achievements).sort((r, o) => Number(r) - Number(o)).forEach((r) => {
t += this.achievements[r].toString() + `
`;
}), Object.keys(this.leaderboards).sort((r, o) => Number(r) - Number(o)).forEach((r) => {
t += this.leaderboards[r].toString() + `
`;
}), t;
}
}
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 of this Rich Presence Format */
name: t,
/** Type of this Rich Presence Format, such as SCORE, FRAMES, etc. */
type: r,
/**
* Returns string representation of Rich Presence Format definition
*
* @example
* import { RichPresence } from '@cruncheevos/core'
* const format = RichPresence.format({ name: 'Score', type: 'VALUE' })
* format.toString() // 'Format:Score\nFormatType=VALUE'
*/
toString() {
return `Format:${t}
FormatType=${r}`;
},
/**
* Returns string representation of Rich PresenceFormat macro call
*
* If there's only one condition - output may be shortened.
*
* When passing Condition or ConditionBuilder - you must have
* at least one condition mark