@linxin666/dsh-pet 0.3.24 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +4 -3
- package/README.zh.md +4 -3
- package/icon.svg +3 -0
- package/lib/client.js +522 -194
- package/lib/client.js.map +1 -1
- package/lib/index.js +706 -422
- package/lib/live2d-vendor.js +96 -96
- package/lib/live2d-vendor.js.map +1 -1
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +1 -1
- package/lib/types/client/PetSettingsCard.d.ts +20 -9
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +116 -5
- package/lib/types/client/PetSprite.d.ts +6 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +95 -0
- package/lib/types/client/PluginSettingsCard.d.ts +4 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/gameplay-hud.d.ts +10 -1
- package/lib/types/client/gameplay-hud.d.ts.map +1 -1
- package/lib/types/client/gameplay-hud.js +137 -23
- package/lib/types/client/index.d.ts +8 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +61 -27
- package/lib/types/client/main-session.d.ts +33 -0
- package/lib/types/client/main-session.d.ts.map +1 -0
- package/lib/types/client/main-session.js +34 -0
- package/lib/types/client/settings-form.d.ts +17 -17
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +30 -27
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +5 -3
- package/lib/types/gameplay.d.ts +79 -5
- package/lib/types/gameplay.d.ts.map +1 -1
- package/lib/types/gameplay.js +212 -17
- package/lib/types/http.d.ts +18 -1
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +22 -1
- package/lib/types/index.d.ts +100 -29
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +113 -64
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +3 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +3 -1
- package/lib/types/service.d.ts +8 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -4
- package/package.json +46 -26
- package/src/client/PetDockEntry.tsx +1 -0
- package/src/client/PetSettingsCard.test.tsx +103 -0
- package/src/client/PetSettingsCard.tsx +129 -26
- package/src/client/PetSprite.tsx +96 -0
- package/src/client/PluginSettingsCard.tsx +7 -2
- package/src/client/gameplay-hud.test.tsx +106 -8
- package/src/client/gameplay-hud.tsx +143 -28
- package/src/client/index.test.tsx +37 -4
- package/src/client/index.ts +74 -34
- package/src/client/main-session.ts +43 -0
- package/src/client/settings-form.ts +34 -31
- package/src/event-projection.ts +5 -3
- package/src/gameplay.test.ts +138 -1
- package/src/gameplay.ts +253 -16
- package/src/http.ts +23 -1
- package/src/index.test.ts +107 -11
- package/src/index.ts +162 -73
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +3 -2
- package/src/routes.ts +3 -1
- package/src/service.ts +27 -5
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as RemarkPicker, a as AFFINITY_MAX, c as applyInteraction, d as emptyAffinity, f as rankOf, g as REMARK_LINE_MAX, h as REMARK_LINES_MAX, i as rowOf, l as applyTurnReward, m as REMARK_KINDS, n as animationForPhase, o as AFFINITY_RANKS, p as BUILTIN_REMARKS, r as defaultPetStateConfig, s as affinityViewOf, t as PetStateMachine, u as defaultAffinityConfig, v as builtinRemark, y as normalizePetRemarks } from "./state-NvDEoCln.js";
|
|
2
|
-
import z from "schemastery";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { Service } from "@deepseek-ai/cordis";
|
|
4
4
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
@@ -894,9 +894,9 @@ function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
894
894
|
};
|
|
895
895
|
}
|
|
896
896
|
case "tool/result": {
|
|
897
|
-
const
|
|
898
|
-
const callId = String(
|
|
899
|
-
const failed = event.data.error !== void 0 ||
|
|
897
|
+
const { message } = event.data;
|
|
898
|
+
const callId = String(message.toolCallId);
|
|
899
|
+
const failed = event.data.error !== void 0 || message.isError === true;
|
|
900
900
|
const wasTest = runtime.testCalls.delete(callId);
|
|
901
901
|
runtime.activeTools.delete(callId);
|
|
902
902
|
runtime.stepHadFailure ||= failed;
|
|
@@ -1000,7 +1000,7 @@ function projectAssistantStreamFrame(frame, runtime, nowMs = Date.now()) {
|
|
|
1000
1000
|
//#region src/treats.ts
|
|
1001
1001
|
const defaultTreatConfig = {
|
|
1002
1002
|
turnsPerTreat: 30,
|
|
1003
|
-
timeTreatMs:
|
|
1003
|
+
timeTreatMs: 18e6,
|
|
1004
1004
|
maxTreats: 20
|
|
1005
1005
|
};
|
|
1006
1006
|
function emptyTreatLedger() {
|
|
@@ -1474,7 +1474,7 @@ function loadGameplay(parsed) {
|
|
|
1474
1474
|
const item = {
|
|
1475
1475
|
stats,
|
|
1476
1476
|
currencies,
|
|
1477
|
-
mode: record.mode === "
|
|
1477
|
+
mode: typeof record.mode === "string" && record.mode.length > 0 && record.mode.length <= 24 ? record.mode : null,
|
|
1478
1478
|
settledAt: clamp(finiteNum(record.settledAt, 0), Number.MAX_SAFE_INTEGER)
|
|
1479
1479
|
};
|
|
1480
1480
|
if (typeof record.incomeCarryMs === "number" && Number.isFinite(record.incomeCarryMs)) item.incomeCarryMs = Math.max(0, record.incomeCarryMs);
|
|
@@ -1664,49 +1664,55 @@ function normalizePanel(raw, onWarning = () => {}) {
|
|
|
1664
1664
|
}
|
|
1665
1665
|
const panel = {};
|
|
1666
1666
|
const labelsRaw = raw.labels;
|
|
1667
|
-
if (labelsRaw !== void 0)
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1667
|
+
if (labelsRaw !== void 0) {
|
|
1668
|
+
if (!isRecord$3(labelsRaw)) onWarning("panel.labels must be an object");
|
|
1669
|
+
else {
|
|
1670
|
+
const labels = {};
|
|
1671
|
+
for (const key of PANEL_LABEL_KEYS) {
|
|
1672
|
+
const value = labelsRaw[key];
|
|
1673
|
+
if (value === void 0) continue;
|
|
1674
|
+
if (typeof value !== "string") {
|
|
1675
|
+
onWarning("panel.labels." + key + " must be a string");
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
const line = normalizeLine(value, "label", onWarning);
|
|
1679
|
+
if (line !== void 0) labels[key] = line.slice(0, 40);
|
|
1676
1680
|
}
|
|
1677
|
-
|
|
1678
|
-
if (line !== void 0) labels[key] = line.slice(0, 40);
|
|
1681
|
+
if (Object.keys(labels).length > 0) panel.labels = labels;
|
|
1679
1682
|
}
|
|
1680
|
-
if (Object.keys(labels).length > 0) panel.labels = labels;
|
|
1681
1683
|
}
|
|
1682
1684
|
const statsRaw = raw.stats;
|
|
1683
|
-
if (statsRaw !== void 0)
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1685
|
+
if (statsRaw !== void 0) {
|
|
1686
|
+
if (!isRecord$3(statsRaw)) onWarning("panel.stats must be an object");
|
|
1687
|
+
else {
|
|
1688
|
+
const stats = {};
|
|
1689
|
+
for (const key of PANEL_STAT_KEYS) {
|
|
1690
|
+
const value = statsRaw[key];
|
|
1691
|
+
if (value === void 0) continue;
|
|
1692
|
+
if (typeof value !== "string") {
|
|
1693
|
+
onWarning("panel.stats." + key + " must be a string");
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
const line = normalizeLine(value, "stat", onWarning);
|
|
1697
|
+
if (line !== void 0) stats[key] = line.slice(0, 80);
|
|
1692
1698
|
}
|
|
1693
|
-
|
|
1694
|
-
if (line !== void 0) stats[key] = line.slice(0, 80);
|
|
1699
|
+
if (Object.keys(stats).length > 0) panel.stats = stats;
|
|
1695
1700
|
}
|
|
1696
|
-
if (Object.keys(stats).length > 0) panel.stats = stats;
|
|
1697
1701
|
}
|
|
1698
1702
|
const actionsRaw = raw.actions;
|
|
1699
|
-
if (actionsRaw !== void 0)
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1703
|
+
if (actionsRaw !== void 0) {
|
|
1704
|
+
if (!Array.isArray(actionsRaw)) onWarning("panel.actions must be an array");
|
|
1705
|
+
else {
|
|
1706
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1707
|
+
for (const entry of actionsRaw) {
|
|
1708
|
+
if (typeof entry !== "string" || !PANEL_ACTIONS.includes(entry)) {
|
|
1709
|
+
onWarning("unknown panel action dropped: " + String(entry));
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
seen.add(entry);
|
|
1706
1713
|
}
|
|
1707
|
-
seen.
|
|
1714
|
+
panel.actions = PANEL_ACTIONS.filter((action) => seen.has(action));
|
|
1708
1715
|
}
|
|
1709
|
-
panel.actions = PANEL_ACTIONS.filter((action) => seen.has(action));
|
|
1710
1716
|
}
|
|
1711
1717
|
if (panel.labels === void 0 && panel.stats === void 0 && panel.actions === void 0) return void 0;
|
|
1712
1718
|
return panel;
|
|
@@ -1741,30 +1747,34 @@ function normalizeVoicePack(raw, onWarning = () => {}) {
|
|
|
1741
1747
|
if (version !== void 0 && (typeof version !== "number" || version !== 1)) onWarning("voicePackVersion " + String(version) + " is not supported; reading as v1 best-effort");
|
|
1742
1748
|
const overrides = {};
|
|
1743
1749
|
const statusRaw = raw.status;
|
|
1744
|
-
if (statusRaw !== void 0)
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1750
|
+
if (statusRaw !== void 0) {
|
|
1751
|
+
if (!isRecord$3(statusRaw)) onWarning("status must be an object");
|
|
1752
|
+
else for (const key of Object.keys(statusRaw)) {
|
|
1753
|
+
if (!STATUS_SCENES.includes(key)) {
|
|
1754
|
+
onWarning("unknown status scene " + key + " ignored");
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
const pool = normalizePool(statusRaw[key], "status", onWarning);
|
|
1758
|
+
if (pool !== void 0 && pool.length > 0) overrides.status = {
|
|
1759
|
+
...overrides.status,
|
|
1760
|
+
[key]: pool
|
|
1761
|
+
};
|
|
1749
1762
|
}
|
|
1750
|
-
const pool = normalizePool(statusRaw[key], "status", onWarning);
|
|
1751
|
-
if (pool !== void 0 && pool.length > 0) overrides.status = {
|
|
1752
|
-
...overrides.status,
|
|
1753
|
-
[key]: pool
|
|
1754
|
-
};
|
|
1755
1763
|
}
|
|
1756
1764
|
const toolsRaw = raw.tools;
|
|
1757
|
-
if (toolsRaw !== void 0)
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1765
|
+
if (toolsRaw !== void 0) {
|
|
1766
|
+
if (!isRecord$3(toolsRaw)) onWarning("tools must be an object");
|
|
1767
|
+
else for (const key of Object.keys(toolsRaw)) {
|
|
1768
|
+
if (!TOOL_CATEGORIES.includes(key)) {
|
|
1769
|
+
onWarning("unknown tool family " + key + " ignored");
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
const pool = normalizePool(toolsRaw[key], "tools", onWarning);
|
|
1773
|
+
if (pool !== void 0 && pool.length > 0) overrides.tools = {
|
|
1774
|
+
...overrides.tools,
|
|
1775
|
+
[key]: pool
|
|
1776
|
+
};
|
|
1762
1777
|
}
|
|
1763
|
-
const pool = normalizePool(toolsRaw[key], "tools", onWarning);
|
|
1764
|
-
if (pool !== void 0 && pool.length > 0) overrides.tools = {
|
|
1765
|
-
...overrides.tools,
|
|
1766
|
-
[key]: pool
|
|
1767
|
-
};
|
|
1768
1778
|
}
|
|
1769
1779
|
const remainingRaw = raw.toolRemaining;
|
|
1770
1780
|
if (remainingRaw !== void 0) {
|
|
@@ -1772,27 +1782,31 @@ function normalizeVoicePack(raw, onWarning = () => {}) {
|
|
|
1772
1782
|
if (pool !== void 0 && pool.length > 0) overrides.toolRemaining = pool;
|
|
1773
1783
|
}
|
|
1774
1784
|
const whispersRaw = raw.whispers;
|
|
1775
|
-
if (whispersRaw !== void 0)
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
+
if (whispersRaw !== void 0) {
|
|
1786
|
+
if (!isRecord$3(whispersRaw)) onWarning("whispers must be an object");
|
|
1787
|
+
else {
|
|
1788
|
+
for (const key of Object.keys(whispersRaw)) if (key === "generic" || key === "rules") onWarning("whispers." + key + " is no longer supported and was ignored");
|
|
1789
|
+
else if (!WHISPER_KEYS.has(key)) onWarning("unknown whispers field " + key + " ignored");
|
|
1790
|
+
const categories = normalizeWhisperCategories(whispersRaw.categories, onWarning);
|
|
1791
|
+
const results = normalizeWhisperResults(whispersRaw.results, onWarning);
|
|
1792
|
+
if (categories !== void 0 || results !== void 0) overrides.whispers = {
|
|
1793
|
+
...categories === void 0 ? {} : { categories },
|
|
1794
|
+
...results === void 0 ? {} : { results }
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1785
1797
|
}
|
|
1786
1798
|
const panel = raw.panel === void 0 ? void 0 : normalizePanel(raw.panel, onWarning);
|
|
1787
1799
|
const remarks = raw.remarks === void 0 ? void 0 : normalizePetRemarks(raw.remarks, onWarning);
|
|
1788
1800
|
const ranksRaw = raw.ranks;
|
|
1789
1801
|
let ranks;
|
|
1790
|
-
if (ranksRaw !== void 0)
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1802
|
+
if (ranksRaw !== void 0) {
|
|
1803
|
+
if (!isRecord$3(ranksRaw)) onWarning("ranks must be an object");
|
|
1804
|
+
else {
|
|
1805
|
+
const out = {};
|
|
1806
|
+
for (const [key, value] of Object.entries(ranksRaw)) if (typeof value === "string" && value.trim() !== "") out[key] = value.trim().slice(0, 80);
|
|
1807
|
+
else onWarning("invalid rank name for " + key);
|
|
1808
|
+
if (Object.keys(out).length > 0) ranks = out;
|
|
1809
|
+
}
|
|
1796
1810
|
}
|
|
1797
1811
|
if (overrides.status === void 0 && overrides.tools === void 0 && overrides.toolRemaining === void 0 && overrides.whispers === void 0 && panel === void 0 && remarks === void 0 && ranks === void 0) return;
|
|
1798
1812
|
return {
|
|
@@ -1864,6 +1878,13 @@ function mergeVoicePacks(...layers) {
|
|
|
1864
1878
|
}
|
|
1865
1879
|
//#endregion
|
|
1866
1880
|
//#region src/gameplay.ts
|
|
1881
|
+
/** Every roam direction, in roll order (equal chance each unless restricted). */
|
|
1882
|
+
const PET_ROAM_DIRECTIONS = [
|
|
1883
|
+
"up",
|
|
1884
|
+
"down",
|
|
1885
|
+
"left",
|
|
1886
|
+
"right"
|
|
1887
|
+
];
|
|
1867
1888
|
const KEBAB = /^[a-z0-9][a-z0-9-]*$/;
|
|
1868
1889
|
const MAX_STATS = 16;
|
|
1869
1890
|
const MAX_ZONES = 8;
|
|
@@ -1873,6 +1894,8 @@ const MAX_SHOP_ITEMS = 32;
|
|
|
1873
1894
|
const MAX_LOTTERY_TIERS = 16;
|
|
1874
1895
|
const MAX_PHRASES = 64;
|
|
1875
1896
|
const PHRASE_MAX_LENGTH = 120;
|
|
1897
|
+
const MAX_MODES = 8;
|
|
1898
|
+
const MODE_LABEL_MAX_LENGTH = 40;
|
|
1876
1899
|
const STAT_VALUE_MAX = 1e6;
|
|
1877
1900
|
const CURRENCY_MAX = 9999999;
|
|
1878
1901
|
const KNOWN_GAMEPLAY = /* @__PURE__ */ new Set([
|
|
@@ -1882,6 +1905,8 @@ const KNOWN_GAMEPLAY = /* @__PURE__ */ new Set([
|
|
|
1882
1905
|
"touch",
|
|
1883
1906
|
"work",
|
|
1884
1907
|
"sleep",
|
|
1908
|
+
"modes",
|
|
1909
|
+
"roam",
|
|
1885
1910
|
"passiveIncome",
|
|
1886
1911
|
"shop",
|
|
1887
1912
|
"dragState",
|
|
@@ -1928,6 +1953,26 @@ const KNOWN_SLEEP = /* @__PURE__ */ new Set([
|
|
|
1928
1953
|
"wakeState",
|
|
1929
1954
|
"restore"
|
|
1930
1955
|
]);
|
|
1956
|
+
const KNOWN_MODE = /* @__PURE__ */ new Set([
|
|
1957
|
+
"state",
|
|
1958
|
+
"label",
|
|
1959
|
+
"activeLabel",
|
|
1960
|
+
"restore"
|
|
1961
|
+
]);
|
|
1962
|
+
const KNOWN_ROAM = /* @__PURE__ */ new Set([
|
|
1963
|
+
"state",
|
|
1964
|
+
"intervalMs",
|
|
1965
|
+
"probability",
|
|
1966
|
+
"distanceMin",
|
|
1967
|
+
"distanceMax",
|
|
1968
|
+
"speed",
|
|
1969
|
+
"directions"
|
|
1970
|
+
]);
|
|
1971
|
+
const KNOWN_RESTORE = /* @__PURE__ */ new Set([
|
|
1972
|
+
"stat",
|
|
1973
|
+
"amount",
|
|
1974
|
+
"intervalMs"
|
|
1975
|
+
]);
|
|
1931
1976
|
const KNOWN_SHOP_ITEM = /* @__PURE__ */ new Set([
|
|
1932
1977
|
"id",
|
|
1933
1978
|
"label",
|
|
@@ -2024,6 +2069,25 @@ function parseStateRef(raw, field, hooks) {
|
|
|
2024
2069
|
}
|
|
2025
2070
|
return raw;
|
|
2026
2071
|
}
|
|
2072
|
+
/** Parse one mode restore rule "{ stat (declared), amount, intervalMs }". */
|
|
2073
|
+
function parseModeRestore(raw, field, stats, fail) {
|
|
2074
|
+
if (!isRecord$2(raw)) {
|
|
2075
|
+
fail(field + " must be an object { stat, amount, intervalMs }");
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const extra = unknownKeys$2(raw, KNOWN_RESTORE);
|
|
2079
|
+
if (extra.length > 0) fail(field + ": unknown field(s) " + extra.map((k) => JSON.stringify(k)).join(", "));
|
|
2080
|
+
const stat = typeof raw.stat === "string" && stats[raw.stat] !== void 0 ? raw.stat : void 0;
|
|
2081
|
+
if (stat === void 0) fail(field + ".stat must reference a declared stat");
|
|
2082
|
+
if (!intIn(raw.amount, 1, 1e3)) fail(field + ".amount must be an integer in [1, 1000]");
|
|
2083
|
+
if (!intIn(raw.intervalMs, 1e3, 6e5)) fail(field + ".intervalMs must be an integer in [1000, 600000]");
|
|
2084
|
+
if (stat === void 0 || !intIn(raw.amount, 1, 1e3) || !intIn(raw.intervalMs, 1e3, 6e5)) return void 0;
|
|
2085
|
+
return {
|
|
2086
|
+
stat,
|
|
2087
|
+
amount: raw.amount,
|
|
2088
|
+
intervalMs: raw.intervalMs
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2027
2091
|
/**
|
|
2028
2092
|
* Validate the manifest 'gameplay' block (fail-closed). Only frames2d pets
|
|
2029
2093
|
* may declare gameplay today: every state reference checks against the
|
|
@@ -2043,72 +2107,80 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2043
2107
|
error(message);
|
|
2044
2108
|
};
|
|
2045
2109
|
const stats = {};
|
|
2046
|
-
if (raw.stats !== void 0)
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2110
|
+
if (raw.stats !== void 0) {
|
|
2111
|
+
if (!isRecord$2(raw.stats)) fail("gameplay.stats must be an object keyed by stat id");
|
|
2112
|
+
else {
|
|
2113
|
+
const entries = Object.entries(raw.stats);
|
|
2114
|
+
if (entries.length > MAX_STATS) fail("gameplay.stats declares too many stats (max 16)");
|
|
2115
|
+
for (const [name, value] of entries) {
|
|
2116
|
+
if (!validName(name, 24)) {
|
|
2117
|
+
fail("gameplay.stats: invalid stat id " + JSON.stringify(name));
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
if (!isRecord$2(value)) {
|
|
2121
|
+
fail("gameplay.stats." + name + " must be an object");
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
const statExtra = unknownKeys$2(value, KNOWN_STAT);
|
|
2125
|
+
if (statExtra.length > 0) fail("gameplay.stats." + name + ": unknown field(s) " + statExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2126
|
+
if (!intIn(value.max, 1, STAT_VALUE_MAX)) {
|
|
2127
|
+
fail("gameplay.stats." + name + ".max must be an integer in [1, 1000000]");
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
const def = { max: value.max };
|
|
2131
|
+
if (value.initial !== void 0) {
|
|
2132
|
+
if (!numIn(value.initial, 0, value.max)) fail("gameplay.stats." + name + ".initial must be within [0, max]");
|
|
2133
|
+
else def.initial = value.initial;
|
|
2134
|
+
}
|
|
2135
|
+
for (const key of [
|
|
2136
|
+
"decayPerMinute",
|
|
2137
|
+
"workingDecayPerMinute",
|
|
2138
|
+
"idleDecayPerMinute"
|
|
2139
|
+
]) if (value[key] !== void 0) {
|
|
2140
|
+
if (!numIn(value[key], 0, 1e3)) fail("gameplay.stats." + name + "." + key + " must be a number in [0, 1000]");
|
|
2141
|
+
else def[key] = value[key];
|
|
2142
|
+
}
|
|
2143
|
+
stats[name] = def;
|
|
2064
2144
|
}
|
|
2065
|
-
const def = { max: value.max };
|
|
2066
|
-
if (value.initial !== void 0) if (!numIn(value.initial, 0, value.max)) fail("gameplay.stats." + name + ".initial must be within [0, max]");
|
|
2067
|
-
else def.initial = value.initial;
|
|
2068
|
-
for (const key of [
|
|
2069
|
-
"decayPerMinute",
|
|
2070
|
-
"workingDecayPerMinute",
|
|
2071
|
-
"idleDecayPerMinute"
|
|
2072
|
-
]) if (value[key] !== void 0) if (!numIn(value[key], 0, 1e3)) fail("gameplay.stats." + name + "." + key + " must be a number in [0, 1000]");
|
|
2073
|
-
else def[key] = value[key];
|
|
2074
|
-
stats[name] = def;
|
|
2075
2145
|
}
|
|
2076
2146
|
}
|
|
2077
2147
|
const block = {};
|
|
2078
2148
|
if (Object.keys(stats).length > 0) block.stats = stats;
|
|
2079
|
-
if (raw.idleDirector !== void 0)
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2149
|
+
if (raw.idleDirector !== void 0) {
|
|
2150
|
+
if (!isRecord$2(raw.idleDirector) || !Array.isArray(raw.idleDirector.acts)) fail("gameplay.idleDirector must be an object with an acts array");
|
|
2151
|
+
else {
|
|
2152
|
+
const d = raw.idleDirector;
|
|
2153
|
+
const dExtra = unknownKeys$2(d, KNOWN_IDLE_DIRECTOR);
|
|
2154
|
+
if (dExtra.length > 0) fail("gameplay.idleDirector: unknown field(s) " + dExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2155
|
+
if (d.intervalMs !== void 0 && !intIn(d.intervalMs, 1e3, 6e4)) fail("gameplay.idleDirector.intervalMs must be an integer in [1000, 60000]");
|
|
2156
|
+
if (d.maxMiss !== void 0 && !intIn(d.maxMiss, 0, 10)) fail("gameplay.idleDirector.maxMiss must be an integer in [0, 10]");
|
|
2157
|
+
if (d.idleWeight !== void 0 && !intIn(d.idleWeight, 0, 1e4)) fail("gameplay.idleDirector.idleWeight must be an integer in [0, 10000]");
|
|
2158
|
+
if (d.acts.length === 0 || d.acts.length > MAX_ACTS) fail("gameplay.idleDirector.acts must declare 1..16 acts");
|
|
2159
|
+
const acts = [];
|
|
2160
|
+
for (const act of d.acts) {
|
|
2161
|
+
if (!isRecord$2(act) || !intIn(act.weight, 1, 1e4)) {
|
|
2162
|
+
fail("gameplay.idleDirector.acts entries need a weight integer in [1, 10000]");
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
const aExtra = unknownKeys$2(act, KNOWN_ACT);
|
|
2166
|
+
if (aExtra.length > 0) fail("gameplay.idleDirector.acts: unknown field(s) " + aExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2167
|
+
const track = parseStateRef(act.track, "gameplay.idleDirector.acts.track", hooks);
|
|
2168
|
+
if (track === void 0) continue;
|
|
2169
|
+
const entry = {
|
|
2170
|
+
track,
|
|
2171
|
+
weight: act.weight
|
|
2172
|
+
};
|
|
2173
|
+
const phrases = parsePhrases(act.phrases, "gameplay.idleDirector.acts.phrases", hooks);
|
|
2174
|
+
if (phrases !== void 0) entry.phrases = phrases;
|
|
2175
|
+
acts.push(entry);
|
|
2093
2176
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
track,
|
|
2100
|
-
weight: act.weight
|
|
2177
|
+
if (acts.length > 0) block.idleDirector = {
|
|
2178
|
+
intervalMs: intIn(d.intervalMs, 1e3, 6e4) ? d.intervalMs : 5e3,
|
|
2179
|
+
maxMiss: intIn(d.maxMiss, 0, 10) ? d.maxMiss : 2,
|
|
2180
|
+
idleWeight: intIn(d.idleWeight, 0, 1e4) ? d.idleWeight : 0,
|
|
2181
|
+
acts
|
|
2101
2182
|
};
|
|
2102
|
-
const phrases = parsePhrases(act.phrases, "gameplay.idleDirector.acts.phrases", hooks);
|
|
2103
|
-
if (phrases !== void 0) entry.phrases = phrases;
|
|
2104
|
-
acts.push(entry);
|
|
2105
2183
|
}
|
|
2106
|
-
if (acts.length > 0) block.idleDirector = {
|
|
2107
|
-
intervalMs: intIn(d.intervalMs, 1e3, 6e4) ? d.intervalMs : 5e3,
|
|
2108
|
-
maxMiss: intIn(d.maxMiss, 0, 10) ? d.maxMiss : 2,
|
|
2109
|
-
idleWeight: intIn(d.idleWeight, 0, 1e4) ? d.idleWeight : 0,
|
|
2110
|
-
acts
|
|
2111
|
-
};
|
|
2112
2184
|
}
|
|
2113
2185
|
if (raw.hitBox !== void 0) {
|
|
2114
2186
|
const b = raw.hitBox;
|
|
@@ -2120,66 +2192,70 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2120
2192
|
y1: b.y1
|
|
2121
2193
|
};
|
|
2122
2194
|
}
|
|
2123
|
-
if (raw.touch !== void 0)
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
const zones = [];
|
|
2138
|
-
if (raw.touch.zones.length === 0 || raw.touch.zones.length > MAX_ZONES) fail("gameplay.touch.zones must declare 1..8 zones");
|
|
2139
|
-
for (const zoneRaw of raw.touch.zones) {
|
|
2140
|
-
if (!isRecord$2(zoneRaw) || !validName(zoneRaw.name) || !numIn(zoneRaw.y0, 0, 1) || !numIn(zoneRaw.y1, 0, 1) || !(zoneRaw.y0 < zoneRaw.y1)) {
|
|
2141
|
-
fail("gameplay.touch.zones entries need a kebab name and 0 <= y0 < y1 <= 1");
|
|
2142
|
-
continue;
|
|
2143
|
-
}
|
|
2144
|
-
const zExtra = unknownKeys$2(zoneRaw, KNOWN_ZONE);
|
|
2145
|
-
if (zExtra.length > 0) fail("gameplay.touch." + zoneRaw.name + ": unknown field(s) " + zExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2146
|
-
if (!Array.isArray(zoneRaw.branches) || zoneRaw.branches.length === 0 || zoneRaw.branches.length > MAX_BRANCHES) {
|
|
2147
|
-
fail("gameplay.touch." + zoneRaw.name + ".branches must declare 1..8 branches");
|
|
2148
|
-
continue;
|
|
2195
|
+
if (raw.touch !== void 0) {
|
|
2196
|
+
if (!isRecord$2(raw.touch) || !Array.isArray(raw.touch.zones)) fail("gameplay.touch must be an object with a zones array");
|
|
2197
|
+
else {
|
|
2198
|
+
const tExtra = unknownKeys$2(raw.touch, KNOWN_TOUCH);
|
|
2199
|
+
if (tExtra.length > 0) fail("gameplay.touch: unknown field(s) " + tExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2200
|
+
let clickBoost;
|
|
2201
|
+
if (raw.touch.clickBoost !== void 0) {
|
|
2202
|
+
const cb = raw.touch.clickBoost;
|
|
2203
|
+
if (!isRecord$2(cb) || typeof cb.stat !== "string" || stats[cb.stat] === void 0 || !intIn(cb.min, 0, 1e3) || !intIn(cb.max, 0, 1e3) || cb.min > cb.max) fail("gameplay.touch.clickBoost must be { stat (declared), min, max } integers with 0 <= min <= max <= 1000");
|
|
2204
|
+
else clickBoost = {
|
|
2205
|
+
stat: cb.stat,
|
|
2206
|
+
min: cb.min,
|
|
2207
|
+
max: cb.max
|
|
2208
|
+
};
|
|
2149
2209
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
for (const
|
|
2153
|
-
if (!isRecord$2(
|
|
2154
|
-
fail("gameplay.touch.
|
|
2210
|
+
const zones = [];
|
|
2211
|
+
if (raw.touch.zones.length === 0 || raw.touch.zones.length > MAX_ZONES) fail("gameplay.touch.zones must declare 1..8 zones");
|
|
2212
|
+
for (const zoneRaw of raw.touch.zones) {
|
|
2213
|
+
if (!isRecord$2(zoneRaw) || !validName(zoneRaw.name) || !numIn(zoneRaw.y0, 0, 1) || !numIn(zoneRaw.y1, 0, 1) || !(zoneRaw.y0 < zoneRaw.y1)) {
|
|
2214
|
+
fail("gameplay.touch.zones entries need a kebab name and 0 <= y0 < y1 <= 1");
|
|
2155
2215
|
continue;
|
|
2156
2216
|
}
|
|
2157
|
-
const
|
|
2158
|
-
if (
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2217
|
+
const zExtra = unknownKeys$2(zoneRaw, KNOWN_ZONE);
|
|
2218
|
+
if (zExtra.length > 0) fail("gameplay.touch." + zoneRaw.name + ": unknown field(s) " + zExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2219
|
+
if (!Array.isArray(zoneRaw.branches) || zoneRaw.branches.length === 0 || zoneRaw.branches.length > MAX_BRANCHES) {
|
|
2220
|
+
fail("gameplay.touch." + zoneRaw.name + ".branches must declare 1..8 branches");
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
let probabilitySum = 0;
|
|
2224
|
+
const branches = [];
|
|
2225
|
+
for (const branchRaw of zoneRaw.branches) {
|
|
2226
|
+
if (!isRecord$2(branchRaw) || !numIn(branchRaw.probability, 0, 1) || branchRaw.probability === 0) {
|
|
2227
|
+
fail("gameplay.touch." + zoneRaw.name + ".branches entries need a probability in (0, 1]");
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
const bExtra = unknownKeys$2(branchRaw, KNOWN_BRANCH);
|
|
2231
|
+
if (bExtra.length > 0) fail("gameplay.touch." + zoneRaw.name + ": unknown branch field(s) " + bExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2232
|
+
probabilitySum += branchRaw.probability;
|
|
2233
|
+
const branch = { probability: branchRaw.probability };
|
|
2234
|
+
const effects = parseEffects(branchRaw.effects, "gameplay.touch." + zoneRaw.name + ".effects", stats, hooks);
|
|
2235
|
+
if (effects !== void 0) branch.effects = effects;
|
|
2236
|
+
const state = parseStateRef(branchRaw.state, "gameplay.touch." + zoneRaw.name + ".state", hooks);
|
|
2237
|
+
if (state !== void 0) branch.state = state;
|
|
2238
|
+
if (branchRaw.stateMs !== void 0) {
|
|
2239
|
+
if (!intIn(branchRaw.stateMs, 200, 1e4)) fail("gameplay.touch." + zoneRaw.name + ".stateMs must be an integer in [200, 10000]");
|
|
2240
|
+
else branch.stateMs = branchRaw.stateMs;
|
|
2241
|
+
}
|
|
2242
|
+
const phrases = parsePhrases(branchRaw.phrases, "gameplay.touch." + zoneRaw.name + ".phrases", hooks);
|
|
2243
|
+
if (phrases !== void 0) branch.phrases = phrases;
|
|
2244
|
+
branches.push(branch);
|
|
2245
|
+
}
|
|
2246
|
+
if (probabilitySum > 1 + 1e-9) fail("gameplay.touch." + zoneRaw.name + ": branch probabilities must sum to at most 1");
|
|
2247
|
+
if (branches.length > 0) zones.push({
|
|
2248
|
+
name: zoneRaw.name,
|
|
2249
|
+
y0: zoneRaw.y0,
|
|
2250
|
+
y1: zoneRaw.y1,
|
|
2251
|
+
branches
|
|
2252
|
+
});
|
|
2170
2253
|
}
|
|
2171
|
-
if (
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
y1: zoneRaw.y1,
|
|
2176
|
-
branches
|
|
2177
|
-
});
|
|
2254
|
+
if (zones.length > 0 || clickBoost !== void 0) block.touch = {
|
|
2255
|
+
zones,
|
|
2256
|
+
...clickBoost === void 0 ? {} : { clickBoost }
|
|
2257
|
+
};
|
|
2178
2258
|
}
|
|
2179
|
-
if (zones.length > 0 || clickBoost !== void 0) block.touch = {
|
|
2180
|
-
zones,
|
|
2181
|
-
...clickBoost === void 0 ? {} : { clickBoost }
|
|
2182
|
-
};
|
|
2183
2259
|
}
|
|
2184
2260
|
if (raw.work !== void 0) {
|
|
2185
2261
|
const w = raw.work;
|
|
@@ -2200,15 +2276,19 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2200
2276
|
tickMs: w.tickMs,
|
|
2201
2277
|
successProbability: w.successProbability
|
|
2202
2278
|
};
|
|
2203
|
-
if (w.resultMs !== void 0)
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
if (
|
|
2279
|
+
if (w.resultMs !== void 0) {
|
|
2280
|
+
if (!isRecord$2(w.resultMs) || !intIn(w.resultMs.success, 200, 1e4) || !intIn(w.resultMs.fail, 200, 1e4)) fail("gameplay.work.resultMs must be { success, fail } integers in [200, 10000]");
|
|
2281
|
+
else work.resultMs = {
|
|
2282
|
+
success: w.resultMs.success,
|
|
2283
|
+
fail: w.resultMs.fail
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
for (const key of ["success", "fail"]) if (w[key] !== void 0) {
|
|
2287
|
+
if (!isRecord$2(w[key])) fail("gameplay.work." + key + " must be an object { effects }");
|
|
2288
|
+
else {
|
|
2289
|
+
const effects = parseEffects(w[key].effects, "gameplay.work." + key + ".effects", stats, hooks);
|
|
2290
|
+
if (effects !== void 0) work[key] = { effects };
|
|
2291
|
+
}
|
|
2212
2292
|
}
|
|
2213
2293
|
block.work = work;
|
|
2214
2294
|
}
|
|
@@ -2222,18 +2302,101 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2222
2302
|
if (sExtra.length > 0) fail("gameplay.sleep: unknown field(s) " + sExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2223
2303
|
const state = parseStateRef(s.state, "gameplay.sleep.state", hooks);
|
|
2224
2304
|
const wakeState = parseStateRef(s.wakeState, "gameplay.sleep.wakeState", hooks);
|
|
2225
|
-
const
|
|
2226
|
-
if (
|
|
2227
|
-
if (!intIn(s.restore.amount, 1, 1e3)) fail("gameplay.sleep.restore.amount must be an integer in [1, 1000]");
|
|
2228
|
-
if (!intIn(s.restore.intervalMs, 1e3, 6e5)) fail("gameplay.sleep.restore.intervalMs must be an integer in [1000, 600000]");
|
|
2229
|
-
if (state !== void 0 && restoreStat !== void 0 && intIn(s.restore.amount, 1, 1e3) && intIn(s.restore.intervalMs, 1e3, 6e5)) block.sleep = {
|
|
2305
|
+
const restore = parseModeRestore(s.restore, "gameplay.sleep.restore", stats, fail);
|
|
2306
|
+
if (state !== void 0 && restore !== void 0) block.sleep = {
|
|
2230
2307
|
state,
|
|
2231
2308
|
...wakeState === void 0 ? {} : { wakeState },
|
|
2232
|
-
restore
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2309
|
+
restore
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
if (raw.modes !== void 0) {
|
|
2314
|
+
if (!isRecord$2(raw.modes)) fail("gameplay.modes must be an object keyed by mode id");
|
|
2315
|
+
else {
|
|
2316
|
+
const entries = Object.entries(raw.modes);
|
|
2317
|
+
if (entries.length > MAX_MODES) fail("gameplay.modes declares too many modes (max 8)");
|
|
2318
|
+
const modes = {};
|
|
2319
|
+
for (const [name, value] of entries) {
|
|
2320
|
+
if (!validName(name, 24) || name === "work" || name === "sleep") {
|
|
2321
|
+
fail("gameplay.modes: invalid mode id " + JSON.stringify(name) + " (kebab, and not the reserved work/sleep)");
|
|
2322
|
+
continue;
|
|
2323
|
+
}
|
|
2324
|
+
if (!isRecord$2(value)) {
|
|
2325
|
+
fail("gameplay.modes." + name + " must be an object");
|
|
2326
|
+
continue;
|
|
2236
2327
|
}
|
|
2328
|
+
const mExtra = unknownKeys$2(value, KNOWN_MODE);
|
|
2329
|
+
if (mExtra.length > 0) fail("gameplay.modes." + name + ": unknown field(s) " + mExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2330
|
+
const state = parseStateRef(value.state, "gameplay.modes." + name + ".state", hooks);
|
|
2331
|
+
let usable = state !== void 0;
|
|
2332
|
+
let label;
|
|
2333
|
+
if (value.label !== void 0) {
|
|
2334
|
+
if (typeof value.label !== "string" || value.label.trim() === "" || value.label.length > MODE_LABEL_MAX_LENGTH) {
|
|
2335
|
+
fail("gameplay.modes." + name + ".label must be a non-empty string of at most 40 chars");
|
|
2336
|
+
usable = false;
|
|
2337
|
+
} else label = value.label.trim();
|
|
2338
|
+
}
|
|
2339
|
+
let activeLabel;
|
|
2340
|
+
if (value.activeLabel !== void 0) {
|
|
2341
|
+
if (typeof value.activeLabel !== "string" || value.activeLabel.trim() === "" || value.activeLabel.length > MODE_LABEL_MAX_LENGTH) {
|
|
2342
|
+
fail("gameplay.modes." + name + ".activeLabel must be a non-empty string of at most 40 chars");
|
|
2343
|
+
usable = false;
|
|
2344
|
+
} else activeLabel = value.activeLabel.trim();
|
|
2345
|
+
}
|
|
2346
|
+
const restore = value.restore === void 0 ? void 0 : parseModeRestore(value.restore, "gameplay.modes." + name + ".restore", stats, fail);
|
|
2347
|
+
if (value.restore !== void 0 && restore === void 0) usable = false;
|
|
2348
|
+
if (usable && state !== void 0) modes[name] = {
|
|
2349
|
+
state,
|
|
2350
|
+
...label === void 0 ? {} : { label },
|
|
2351
|
+
...activeLabel === void 0 ? {} : { activeLabel },
|
|
2352
|
+
...restore === void 0 ? {} : { restore }
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
if (Object.keys(modes).length > 0) block.modes = modes;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
if (raw.roam !== void 0) {
|
|
2359
|
+
const rm = raw.roam;
|
|
2360
|
+
if (!isRecord$2(rm)) fail("gameplay.roam must be an object");
|
|
2361
|
+
else {
|
|
2362
|
+
const rExtra = unknownKeys$2(rm, KNOWN_ROAM);
|
|
2363
|
+
if (rExtra.length > 0) fail("gameplay.roam: unknown field(s) " + rExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2364
|
+
const state = parseStateRef(rm.state, "gameplay.roam.state", hooks);
|
|
2365
|
+
if (!intIn(rm.intervalMs, 1e3, 36e5)) fail("gameplay.roam.intervalMs must be an integer in [1000, 3600000]");
|
|
2366
|
+
if (!numIn(rm.probability, 0, 1) || rm.probability === 0) fail("gameplay.roam.probability must be a number in (0, 1]");
|
|
2367
|
+
if (!intIn(rm.distanceMin, 1, 2e3)) fail("gameplay.roam.distanceMin must be an integer in [1, 2000]");
|
|
2368
|
+
if (!intIn(rm.distanceMax, 1, 2e3)) fail("gameplay.roam.distanceMax must be an integer in [1, 2000]");
|
|
2369
|
+
if (intIn(rm.distanceMin, 1, 2e3) && intIn(rm.distanceMax, 1, 2e3) && rm.distanceMin > rm.distanceMax) fail("gameplay.roam.distanceMin must not exceed distanceMax");
|
|
2370
|
+
if (!intIn(rm.speed, 1, 2e3)) fail("gameplay.roam.speed must be an integer in [1, 2000] (px per second)");
|
|
2371
|
+
let directions;
|
|
2372
|
+
if (rm.directions !== void 0) {
|
|
2373
|
+
if (!Array.isArray(rm.directions) || rm.directions.length === 0) fail("gameplay.roam.directions must be a non-empty array of up, down, left, right");
|
|
2374
|
+
else {
|
|
2375
|
+
const picked = [];
|
|
2376
|
+
for (const entry of rm.directions) {
|
|
2377
|
+
if (typeof entry !== "string" || !PET_ROAM_DIRECTIONS.includes(entry)) {
|
|
2378
|
+
fail("gameplay.roam.directions entries must be up, down, left or right");
|
|
2379
|
+
picked.length = 0;
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
if (picked.includes(entry)) {
|
|
2383
|
+
fail("gameplay.roam.directions must not repeat " + entry);
|
|
2384
|
+
picked.length = 0;
|
|
2385
|
+
break;
|
|
2386
|
+
}
|
|
2387
|
+
picked.push(entry);
|
|
2388
|
+
}
|
|
2389
|
+
if (picked.length > 0) directions = picked;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
if (state !== void 0 && intIn(rm.intervalMs, 1e3, 36e5) && numIn(rm.probability, 0, 1) && rm.probability !== 0 && intIn(rm.distanceMin, 1, 2e3) && intIn(rm.distanceMax, 1, 2e3) && rm.distanceMin <= rm.distanceMax && intIn(rm.speed, 1, 2e3)) block.roam = {
|
|
2393
|
+
state,
|
|
2394
|
+
intervalMs: rm.intervalMs,
|
|
2395
|
+
probability: rm.probability,
|
|
2396
|
+
distanceMin: rm.distanceMin,
|
|
2397
|
+
distanceMax: rm.distanceMax,
|
|
2398
|
+
speed: rm.speed,
|
|
2399
|
+
...directions === void 0 ? {} : { directions }
|
|
2237
2400
|
};
|
|
2238
2401
|
}
|
|
2239
2402
|
}
|
|
@@ -2283,8 +2446,10 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2283
2446
|
price: itemRaw.price,
|
|
2284
2447
|
currency: itemRaw.currency
|
|
2285
2448
|
};
|
|
2286
|
-
if (itemRaw.image !== void 0)
|
|
2287
|
-
|
|
2449
|
+
if (itemRaw.image !== void 0) {
|
|
2450
|
+
if (typeof itemRaw.image !== "string" || itemRaw.image.includes("..") || itemRaw.image.includes("\\") || itemRaw.image.startsWith("/")) fail("gameplay.shop." + itemRaw.id + ".image must be a safe manifest-relative frame path");
|
|
2451
|
+
else item.image = itemRaw.image;
|
|
2452
|
+
}
|
|
2288
2453
|
const effects = parseEffects(itemRaw.effects, "gameplay.shop." + itemRaw.id + ".effects", stats, hooks);
|
|
2289
2454
|
if (effects !== void 0) item.effects = effects;
|
|
2290
2455
|
if (itemRaw.lottery !== void 0) {
|
|
@@ -2306,11 +2471,13 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2306
2471
|
probability: tierRaw.probability,
|
|
2307
2472
|
prize: tierRaw.prize
|
|
2308
2473
|
};
|
|
2309
|
-
if (tierRaw.currency !== void 0)
|
|
2310
|
-
|
|
2474
|
+
if (tierRaw.currency !== void 0) {
|
|
2475
|
+
if (!validName(tierRaw.currency, 24)) fail("gameplay.shop." + itemRaw.id + ".lottery tier currency must be a kebab id");
|
|
2476
|
+
else tier.currency = tierRaw.currency;
|
|
2477
|
+
}
|
|
2311
2478
|
tiers.push(tier);
|
|
2312
2479
|
}
|
|
2313
|
-
if (tierSum > 1
|
|
2480
|
+
if (tierSum > 1 + 1e-9) fail("gameplay.shop." + itemRaw.id + ".lottery tier probabilities must sum to at most 1");
|
|
2314
2481
|
if (tiers.length > 0) {
|
|
2315
2482
|
const lotteryEffects = parseEffects(l.effects, "gameplay.shop." + itemRaw.id + ".lottery.effects", stats, hooks);
|
|
2316
2483
|
item.lottery = {
|
|
@@ -2343,6 +2510,29 @@ function parseGameplayManifest(raw, hooks) {
|
|
|
2343
2510
|
}
|
|
2344
2511
|
return failed ? void 0 : block;
|
|
2345
2512
|
}
|
|
2513
|
+
/**
|
|
2514
|
+
* One declared extra mode, by OWN key only. A plain `modes[id]` lookup also
|
|
2515
|
+
* answers for Object.prototype members ('constructor', 'toString', …), which
|
|
2516
|
+
* would let a crafted mode id pass the "is it declared?" test.
|
|
2517
|
+
*/
|
|
2518
|
+
function declaredModeOf(manifest, mode) {
|
|
2519
|
+
const modes = manifest.modes;
|
|
2520
|
+
if (modes === void 0 || !Object.prototype.hasOwnProperty.call(modes, mode)) return void 0;
|
|
2521
|
+
return modes[mode];
|
|
2522
|
+
}
|
|
2523
|
+
/** The restore rule of one active mode, when it declares one. */
|
|
2524
|
+
function modeRestoreOf(manifest, mode) {
|
|
2525
|
+
if (mode === null) return void 0;
|
|
2526
|
+
if (mode === "sleep") return manifest.sleep?.restore;
|
|
2527
|
+
if (mode === "work") return void 0;
|
|
2528
|
+
return declaredModeOf(manifest, mode)?.restore;
|
|
2529
|
+
}
|
|
2530
|
+
/** Whether the manifest still declares this gameplay mode id. */
|
|
2531
|
+
function isDeclaredMode(manifest, mode) {
|
|
2532
|
+
if (mode === "work") return manifest.work !== void 0;
|
|
2533
|
+
if (mode === "sleep") return manifest.sleep !== void 0;
|
|
2534
|
+
return declaredModeOf(manifest, mode) !== void 0;
|
|
2535
|
+
}
|
|
2346
2536
|
/** Fresh state for one pet: stats at their initial (default max), no currency. */
|
|
2347
2537
|
function initialGameplayState(manifest, now) {
|
|
2348
2538
|
const stats = {};
|
|
@@ -2364,9 +2554,11 @@ function clampGameplay(state, manifest) {
|
|
|
2364
2554
|
for (const [name, value] of Object.entries(state.currencies)) state.currencies[name] = Math.min(CURRENCY_MAX, Math.max(0, Math.floor(value)));
|
|
2365
2555
|
}
|
|
2366
2556
|
/**
|
|
2367
|
-
* Lazy settle: apply stat decay, passive income and
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
2557
|
+
* Lazy settle: apply stat decay, passive income and the active mode's restore
|
|
2558
|
+
* rule (sleep restores energy, a declared extra mode restores whatever it
|
|
2559
|
+
* declares) for the elapsed wall time since the last settle. Mirrors the
|
|
2560
|
+
* treats.ts discipline (no host timers; read paths settle). Returns whether
|
|
2561
|
+
* anything changed.
|
|
2370
2562
|
*/
|
|
2371
2563
|
function settleGameplay(state, manifest, now, options) {
|
|
2372
2564
|
const elapsedMs = now - state.settledAt;
|
|
@@ -2397,14 +2589,14 @@ function settleGameplay(state, manifest, now, options) {
|
|
|
2397
2589
|
changed = true;
|
|
2398
2590
|
}
|
|
2399
2591
|
}
|
|
2400
|
-
|
|
2592
|
+
const restore = modeRestoreOf(manifest, state.mode);
|
|
2593
|
+
if (restore !== void 0) {
|
|
2401
2594
|
const restoreElapsed = elapsedMs + (state.restoreCarryMs ?? 0);
|
|
2402
|
-
const interval =
|
|
2595
|
+
const interval = restore.intervalMs;
|
|
2403
2596
|
const ticks = Math.floor(restoreElapsed / interval);
|
|
2404
2597
|
state.restoreCarryMs = restoreElapsed % interval;
|
|
2405
2598
|
if (ticks > 0) {
|
|
2406
|
-
|
|
2407
|
-
state.stats[stat] = (state.stats[stat] ?? 0) + ticks * manifest.sleep.restore.amount;
|
|
2599
|
+
state.stats[restore.stat] = (state.stats[restore.stat] ?? 0) + ticks * restore.amount;
|
|
2408
2600
|
changed = true;
|
|
2409
2601
|
}
|
|
2410
2602
|
} else state.restoreCarryMs = 0;
|
|
@@ -2649,16 +2841,26 @@ function parseSprite2dBlock(raw, diag) {
|
|
|
2649
2841
|
const spritesheetPath = safeManifestPath(raw.spritesheetPath);
|
|
2650
2842
|
if (spritesheetPath === void 0) diag.error("sprite2d.spritesheetPath must be a safe manifest-relative path");
|
|
2651
2843
|
const block = { spritesheetPath: spritesheetPath ?? "" };
|
|
2652
|
-
if (raw.cell !== void 0)
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
if (raw.
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
if (raw.
|
|
2661
|
-
|
|
2844
|
+
if (raw.cell !== void 0) {
|
|
2845
|
+
if (!isRecord$1(raw.cell)) diag.error("sprite2d.cell must be an object { width?, height? }");
|
|
2846
|
+
else block.cell = raw.cell;
|
|
2847
|
+
}
|
|
2848
|
+
if (raw.columns !== void 0) {
|
|
2849
|
+
if (typeof raw.columns !== "number" || !Number.isInteger(raw.columns) || raw.columns < 1) diag.error("sprite2d.columns must be a positive integer");
|
|
2850
|
+
else block.columns = raw.columns;
|
|
2851
|
+
}
|
|
2852
|
+
if (raw.atlasRows !== void 0) {
|
|
2853
|
+
if (typeof raw.atlasRows !== "number" || !Number.isInteger(raw.atlasRows) || raw.atlasRows < 1) diag.error("sprite2d.atlasRows must be a positive integer");
|
|
2854
|
+
else block.atlasRows = raw.atlasRows;
|
|
2855
|
+
}
|
|
2856
|
+
if (raw.frames !== void 0) {
|
|
2857
|
+
if (!Array.isArray(raw.frames) || raw.frames.some((v) => typeof v !== "number" || !Number.isInteger(v) || v < 0)) diag.error("sprite2d.frames must be an array of non-negative integers");
|
|
2858
|
+
else block.frames = raw.frames;
|
|
2859
|
+
}
|
|
2860
|
+
if (raw.tracks !== void 0) {
|
|
2861
|
+
if (!isRecord$1(raw.tracks)) diag.error("sprite2d.tracks must be an object keyed by animation");
|
|
2862
|
+
else block.tracks = raw.tracks;
|
|
2863
|
+
}
|
|
2662
2864
|
return diag.hasErrors ? void 0 : block;
|
|
2663
2865
|
}
|
|
2664
2866
|
function parseLive2dBlock(raw, diag) {
|
|
@@ -2678,16 +2880,24 @@ function parseLive2dBlock(raw, diag) {
|
|
|
2678
2880
|
model: model ?? "",
|
|
2679
2881
|
motions: motions ?? { idle: "" }
|
|
2680
2882
|
};
|
|
2681
|
-
if (raw.scale !== void 0)
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2883
|
+
if (raw.scale !== void 0) {
|
|
2884
|
+
if (typeof raw.scale !== "number" || !Number.isFinite(raw.scale) || raw.scale <= 0 || raw.scale > 10) diag.error("live2d.scale must be a number in (0, 10]");
|
|
2885
|
+
else block.scale = raw.scale;
|
|
2886
|
+
}
|
|
2887
|
+
if (raw.translate !== void 0) {
|
|
2888
|
+
if (!isRecord$1(raw.translate) || raw.translate.x !== void 0 && typeof raw.translate.x !== "number" || raw.translate.y !== void 0 && typeof raw.translate.y !== "number") diag.error("live2d.translate must be an object { x?: number, y?: number }");
|
|
2889
|
+
else block.translate = raw.translate;
|
|
2890
|
+
}
|
|
2685
2891
|
const expressions = parsePhaseStringMap(raw.expressions, "live2d.expressions", diag);
|
|
2686
2892
|
if (expressions !== void 0) block.expressions = expressions;
|
|
2687
|
-
if (raw.hitAreas !== void 0)
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2893
|
+
if (raw.hitAreas !== void 0) {
|
|
2894
|
+
if (!Array.isArray(raw.hitAreas) || raw.hitAreas.some((v) => typeof v !== "string" || v.trim() === "")) diag.error("live2d.hitAreas must be an array of non-empty strings");
|
|
2895
|
+
else block.hitAreas = raw.hitAreas;
|
|
2896
|
+
}
|
|
2897
|
+
if (raw.lipSync !== void 0) {
|
|
2898
|
+
if (typeof raw.lipSync !== "boolean") diag.error("live2d.lipSync must be a boolean");
|
|
2899
|
+
else block.lipSync = raw.lipSync;
|
|
2900
|
+
}
|
|
2691
2901
|
return diag.hasErrors ? void 0 : block;
|
|
2692
2902
|
}
|
|
2693
2903
|
const FRAMES2D_TRACK_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -2724,25 +2934,33 @@ function parseFrames2dTrack(raw, field, diag) {
|
|
|
2724
2934
|
const extra = unknownKeys$1(raw, KNOWN_FRAMES2D_TRACK);
|
|
2725
2935
|
if (extra.length > 0) diag.error(field + ": unknown field(s) " + extra.map((k) => JSON.stringify(k)).join(", "));
|
|
2726
2936
|
const track = {};
|
|
2727
|
-
if (raw.frames !== void 0)
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
2937
|
+
if (raw.frames !== void 0) {
|
|
2938
|
+
if (!Array.isArray(raw.frames) || raw.frames.length === 0) diag.error(field + ".frames must be a non-empty array of frame file names");
|
|
2939
|
+
else if (raw.frames.length > FRAMES2D_MAX_FRAMES) diag.error(field + ".frames declares too many frames (" + raw.frames.length + ", max 64)");
|
|
2940
|
+
else {
|
|
2941
|
+
const frames = [];
|
|
2942
|
+
for (const entry of raw.frames) {
|
|
2943
|
+
const safe = safeFrameName(entry);
|
|
2944
|
+
if (safe === void 0) diag.error(field + ".frames entry " + JSON.stringify(String(entry)) + " is not a safe image file name");
|
|
2945
|
+
else frames.push(safe);
|
|
2946
|
+
}
|
|
2947
|
+
if (frames.length === raw.frames.length) track.frames = frames;
|
|
2735
2948
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2949
|
+
}
|
|
2950
|
+
if (raw.frameMs !== void 0) {
|
|
2951
|
+
if (!Array.isArray(raw.frameMs) || raw.frameMs.length === 0 || raw.frameMs.some((v) => typeof v !== "number" || !Number.isInteger(v) || v < FRAMES2D_MIN_FRAME_MS || v > FRAMES2D_MAX_FRAME_MS)) diag.error(field + ".frameMs must be a non-empty array of integer ms in [16, 5000]");
|
|
2952
|
+
else if (track.frames === void 0) diag.error(field + ".frameMs requires a valid explicit frames list (directory tracks use filename-encoded ms or defaultFrameMs)");
|
|
2953
|
+
else if (raw.frameMs.length !== track.frames.length) diag.error(field + ".frameMs must have the same length as frames");
|
|
2954
|
+
else track.frameMs = raw.frameMs;
|
|
2955
|
+
}
|
|
2956
|
+
if (raw.loop !== void 0) {
|
|
2957
|
+
if (typeof raw.loop !== "boolean") diag.error(field + ".loop must be a boolean");
|
|
2958
|
+
else track.loop = raw.loop;
|
|
2959
|
+
}
|
|
2960
|
+
if (raw.fallback !== void 0) {
|
|
2961
|
+
if (typeof raw.fallback !== "string" || !validTrackName(raw.fallback)) diag.error(field + ".fallback must be a track name");
|
|
2962
|
+
else track.fallback = raw.fallback;
|
|
2963
|
+
}
|
|
2746
2964
|
return diag.hasErrors ? void 0 : track;
|
|
2747
2965
|
}
|
|
2748
2966
|
function parseFrames2dBlock(raw, diag) {
|
|
@@ -2761,8 +2979,10 @@ function parseFrames2dBlock(raw, diag) {
|
|
|
2761
2979
|
if (dir === void 0) diag.error("frames2d.dir must be a safe manifest-relative directory");
|
|
2762
2980
|
else block.dir = dir;
|
|
2763
2981
|
}
|
|
2764
|
-
if (raw.defaultFrameMs !== void 0)
|
|
2765
|
-
|
|
2982
|
+
if (raw.defaultFrameMs !== void 0) {
|
|
2983
|
+
if (typeof raw.defaultFrameMs !== "number" || !Number.isInteger(raw.defaultFrameMs) || raw.defaultFrameMs < FRAMES2D_MIN_FRAME_MS || raw.defaultFrameMs > FRAMES2D_MAX_FRAME_MS) diag.error("frames2d.defaultFrameMs must be an integer in [16, 5000]");
|
|
2984
|
+
else block.defaultFrameMs = raw.defaultFrameMs;
|
|
2985
|
+
}
|
|
2766
2986
|
const tracks = {};
|
|
2767
2987
|
if (!isRecord$1(raw.tracks)) diag.error("frames2d.tracks is required and must be an object keyed by track name");
|
|
2768
2988
|
else {
|
|
@@ -2788,99 +3008,105 @@ function parseFrames2dBlock(raw, diag) {
|
|
|
2788
3008
|
for (const [phase, target] of Object.entries(block.phases)) if (target !== void 0 && tracks[target] === void 0) diag.error("frames2d.phases." + phase + " references unknown track " + JSON.stringify(target));
|
|
2789
3009
|
}
|
|
2790
3010
|
for (const [name, track] of Object.entries(tracks)) if (track.fallback !== void 0 && tracks[track.fallback] === void 0) diag.error("frames2d.tracks." + name + ".fallback references unknown track " + JSON.stringify(track.fallback));
|
|
2791
|
-
if (raw.skins !== void 0)
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
const
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
const extra = unknownKeys$1(entry, KNOWN_SKIN);
|
|
2802
|
-
if (extra.length > 0) diag.error(field + ": unknown field(s) " + extra.map((k) => JSON.stringify(k)).join(", "));
|
|
2803
|
-
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
2804
|
-
if (id === "" || id.length > 24 || !FRAMES2D_TRACK_NAME.test(id)) {
|
|
2805
|
-
diag.error(field + ".id must be a lowercase kebab id of at most 24 chars");
|
|
2806
|
-
continue;
|
|
2807
|
-
}
|
|
2808
|
-
if (seen.has(id)) {
|
|
2809
|
-
diag.error(field + ": duplicate skin id " + JSON.stringify(id));
|
|
2810
|
-
continue;
|
|
2811
|
-
}
|
|
2812
|
-
seen.add(id);
|
|
2813
|
-
const label = typeof entry.label === "string" ? entry.label.trim() : "";
|
|
2814
|
-
if (label === "" || label.length > 40) {
|
|
2815
|
-
diag.error(field + ".label must be a non-empty string of at most 40 chars");
|
|
2816
|
-
continue;
|
|
2817
|
-
}
|
|
2818
|
-
const idleTrack = typeof entry.idleTrack === "string" ? entry.idleTrack : "";
|
|
2819
|
-
if (!validTrackName(idleTrack) || tracks[idleTrack] === void 0) {
|
|
2820
|
-
diag.error(field + ".idleTrack must name a declared frames2d track");
|
|
2821
|
-
continue;
|
|
2822
|
-
}
|
|
2823
|
-
let clickActions;
|
|
2824
|
-
if (entry.clickActions !== void 0) if (!Array.isArray(entry.clickActions) || entry.clickActions.length === 0 || entry.clickActions.length > 8) diag.error(field + ".clickActions must be an array of 1..8 actions");
|
|
2825
|
-
else {
|
|
2826
|
-
const resolved = [];
|
|
2827
|
-
for (const [cIndex, action] of entry.clickActions.entries()) {
|
|
2828
|
-
const cField = field + ".clickActions[" + cIndex + "]";
|
|
2829
|
-
if (!isRecord$1(action)) {
|
|
2830
|
-
diag.error(cField + " must be an object");
|
|
2831
|
-
continue;
|
|
2832
|
-
}
|
|
2833
|
-
const cExtra = unknownKeys$1(action, KNOWN_SKIN_CLICK);
|
|
2834
|
-
if (cExtra.length > 0) diag.error(cField + ": unknown field(s) " + cExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2835
|
-
const trackName = typeof action.track === "string" ? action.track : "";
|
|
2836
|
-
if (!validTrackName(trackName) || tracks[trackName] === void 0) {
|
|
2837
|
-
diag.error(cField + ".track must name a declared frames2d track");
|
|
2838
|
-
continue;
|
|
2839
|
-
}
|
|
2840
|
-
if (typeof action.probability !== "number" || !Number.isFinite(action.probability) || action.probability <= 0 || action.probability > 1) {
|
|
2841
|
-
diag.error(cField + ".probability must be a number in (0, 1]");
|
|
2842
|
-
continue;
|
|
2843
|
-
}
|
|
2844
|
-
const phrases = parseSkinPhrases(action.phrases, cField + ".phrases", diag);
|
|
2845
|
-
resolved.push({
|
|
2846
|
-
track: trackName,
|
|
2847
|
-
probability: action.probability,
|
|
2848
|
-
...phrases === void 0 ? {} : { phrases }
|
|
2849
|
-
});
|
|
3011
|
+
if (raw.skins !== void 0) {
|
|
3012
|
+
if (!Array.isArray(raw.skins) || raw.skins.length === 0 || raw.skins.length > 16) diag.error("frames2d.skins must be an array of 1..16 skins");
|
|
3013
|
+
else {
|
|
3014
|
+
const skins = [];
|
|
3015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3016
|
+
for (const [index, entry] of raw.skins.entries()) {
|
|
3017
|
+
const field = "frames2d.skins[" + index + "]";
|
|
3018
|
+
if (!isRecord$1(entry)) {
|
|
3019
|
+
diag.error(field + " must be an object");
|
|
3020
|
+
continue;
|
|
2850
3021
|
}
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3022
|
+
const extra = unknownKeys$1(entry, KNOWN_SKIN);
|
|
3023
|
+
if (extra.length > 0) diag.error(field + ": unknown field(s) " + extra.map((k) => JSON.stringify(k)).join(", "));
|
|
3024
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
3025
|
+
if (id === "" || id.length > 24 || !FRAMES2D_TRACK_NAME.test(id)) {
|
|
3026
|
+
diag.error(field + ".id must be a lowercase kebab id of at most 24 chars");
|
|
3027
|
+
continue;
|
|
3028
|
+
}
|
|
3029
|
+
if (seen.has(id)) {
|
|
3030
|
+
diag.error(field + ": duplicate skin id " + JSON.stringify(id));
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
seen.add(id);
|
|
3034
|
+
const label = typeof entry.label === "string" ? entry.label.trim() : "";
|
|
3035
|
+
if (label === "" || label.length > 40) {
|
|
3036
|
+
diag.error(field + ".label must be a non-empty string of at most 40 chars");
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
const idleTrack = typeof entry.idleTrack === "string" ? entry.idleTrack : "";
|
|
3040
|
+
if (!validTrackName(idleTrack) || tracks[idleTrack] === void 0) {
|
|
3041
|
+
diag.error(field + ".idleTrack must name a declared frames2d track");
|
|
3042
|
+
continue;
|
|
3043
|
+
}
|
|
3044
|
+
let clickActions;
|
|
3045
|
+
if (entry.clickActions !== void 0) {
|
|
3046
|
+
if (!Array.isArray(entry.clickActions) || entry.clickActions.length === 0 || entry.clickActions.length > 8) diag.error(field + ".clickActions must be an array of 1..8 actions");
|
|
3047
|
+
else {
|
|
3048
|
+
const resolved = [];
|
|
3049
|
+
for (const [cIndex, action] of entry.clickActions.entries()) {
|
|
3050
|
+
const cField = field + ".clickActions[" + cIndex + "]";
|
|
3051
|
+
if (!isRecord$1(action)) {
|
|
3052
|
+
diag.error(cField + " must be an object");
|
|
3053
|
+
continue;
|
|
3054
|
+
}
|
|
3055
|
+
const cExtra = unknownKeys$1(action, KNOWN_SKIN_CLICK);
|
|
3056
|
+
if (cExtra.length > 0) diag.error(cField + ": unknown field(s) " + cExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
3057
|
+
const trackName = typeof action.track === "string" ? action.track : "";
|
|
3058
|
+
if (!validTrackName(trackName) || tracks[trackName] === void 0) {
|
|
3059
|
+
diag.error(cField + ".track must name a declared frames2d track");
|
|
3060
|
+
continue;
|
|
3061
|
+
}
|
|
3062
|
+
if (typeof action.probability !== "number" || !Number.isFinite(action.probability) || action.probability <= 0 || action.probability > 1) {
|
|
3063
|
+
diag.error(cField + ".probability must be a number in (0, 1]");
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3066
|
+
const phrases = parseSkinPhrases(action.phrases, cField + ".phrases", diag);
|
|
3067
|
+
resolved.push({
|
|
3068
|
+
track: trackName,
|
|
3069
|
+
probability: action.probability,
|
|
3070
|
+
...phrases === void 0 ? {} : { phrases }
|
|
3071
|
+
});
|
|
2864
3072
|
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3073
|
+
if (resolved.length > 0) clickActions = resolved;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
let gameplayTracks;
|
|
3077
|
+
if (entry.gameplayTracks !== void 0) {
|
|
3078
|
+
if (!isRecord$1(entry.gameplayTracks)) diag.error(field + ".gameplayTracks must be an object mapping gameplay state names to tracks");
|
|
3079
|
+
else {
|
|
3080
|
+
const states = Object.keys(entry.gameplayTracks);
|
|
3081
|
+
if (states.length === 0 || states.length > 8) diag.error(field + ".gameplayTracks must map 1..8 gameplay states");
|
|
3082
|
+
else {
|
|
3083
|
+
const resolvedTracks = {};
|
|
3084
|
+
for (const state of states) {
|
|
3085
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(state)) {
|
|
3086
|
+
diag.error(field + ".gameplayTracks key " + JSON.stringify(state) + " must be a lowercase kebab state name");
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
const gTrack = entry.gameplayTracks[state];
|
|
3090
|
+
if (typeof gTrack !== "string" || !validTrackName(gTrack) || tracks[gTrack] === void 0) {
|
|
3091
|
+
diag.error(field + ".gameplayTracks[" + JSON.stringify(state) + "] must name a declared frames2d track");
|
|
3092
|
+
continue;
|
|
3093
|
+
}
|
|
3094
|
+
resolvedTracks[state] = gTrack;
|
|
3095
|
+
}
|
|
3096
|
+
if (Object.keys(resolvedTracks).length > 0) gameplayTracks = resolvedTracks;
|
|
2869
3097
|
}
|
|
2870
|
-
resolvedTracks[state] = gTrack;
|
|
2871
3098
|
}
|
|
2872
|
-
if (Object.keys(resolvedTracks).length > 0) gameplayTracks = resolvedTracks;
|
|
2873
3099
|
}
|
|
3100
|
+
skins.push({
|
|
3101
|
+
id,
|
|
3102
|
+
label,
|
|
3103
|
+
idleTrack,
|
|
3104
|
+
...clickActions === void 0 ? {} : { clickActions },
|
|
3105
|
+
...gameplayTracks === void 0 ? {} : { gameplayTracks }
|
|
3106
|
+
});
|
|
2874
3107
|
}
|
|
2875
|
-
skins.
|
|
2876
|
-
id,
|
|
2877
|
-
label,
|
|
2878
|
-
idleTrack,
|
|
2879
|
-
...clickActions === void 0 ? {} : { clickActions },
|
|
2880
|
-
...gameplayTracks === void 0 ? {} : { gameplayTracks }
|
|
2881
|
-
});
|
|
3108
|
+
if (skins.length > 0) block.skins = skins;
|
|
2882
3109
|
}
|
|
2883
|
-
if (skins.length > 0) block.skins = skins;
|
|
2884
3110
|
}
|
|
2885
3111
|
return diag.hasErrors ? void 0 : block;
|
|
2886
3112
|
}
|
|
@@ -2931,14 +3157,22 @@ function parseV2(source, diag) {
|
|
|
2931
3157
|
renderer
|
|
2932
3158
|
};
|
|
2933
3159
|
if (license !== void 0) manifest.license = license;
|
|
2934
|
-
if (source.description !== void 0)
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
if (source.
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3160
|
+
if (source.description !== void 0) {
|
|
3161
|
+
if (typeof source.description !== "string" || source.description.length > 500) diag.error("description must be a string of at most 500 chars");
|
|
3162
|
+
else manifest.description = source.description;
|
|
3163
|
+
}
|
|
3164
|
+
if (source.version !== void 0) {
|
|
3165
|
+
if (typeof source.version !== "string" || !SEMVER_PATTERN.test(source.version)) diag.error("version must be a semver string (x.y.z)");
|
|
3166
|
+
else manifest.version = source.version;
|
|
3167
|
+
}
|
|
3168
|
+
if (source.author !== void 0) {
|
|
3169
|
+
if (typeof source.author !== "string" || source.author.length > 128) diag.error("author must be a string of at most 128 chars");
|
|
3170
|
+
else manifest.author = source.author;
|
|
3171
|
+
}
|
|
3172
|
+
if (source.homepage !== void 0) {
|
|
3173
|
+
if (typeof source.homepage !== "string") diag.error("homepage must be a string URL");
|
|
3174
|
+
else manifest.homepage = source.homepage;
|
|
3175
|
+
}
|
|
2942
3176
|
if (renderer === "sprite2d") {
|
|
2943
3177
|
const block = parseSprite2dBlock(source.sprite2d, diag);
|
|
2944
3178
|
if (block !== void 0) manifest.sprite2d = block;
|
|
@@ -2955,13 +3189,15 @@ function parseV2(source, diag) {
|
|
|
2955
3189
|
if (source.sprite2d !== void 0) diag.error("renderer frames2d must not declare a sprite2d block");
|
|
2956
3190
|
if (source.live2d !== void 0) diag.error("renderer frames2d must not declare a live2d block");
|
|
2957
3191
|
}
|
|
2958
|
-
if (source.gameplay !== void 0)
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
3192
|
+
if (source.gameplay !== void 0) {
|
|
3193
|
+
if (renderer !== "frames2d") diag.error("gameplay currently requires renderer frames2d (its state references name frames2d tracks)");
|
|
3194
|
+
else {
|
|
3195
|
+
const gameplay = parseGameplayManifest(source.gameplay, {
|
|
3196
|
+
stateNames: new Set(Object.keys(manifest.frames2d?.tracks ?? {})),
|
|
3197
|
+
error: (message) => diag.error("gameplay: " + message)
|
|
3198
|
+
});
|
|
3199
|
+
if (gameplay !== void 0) manifest.gameplay = gameplay;
|
|
3200
|
+
}
|
|
2965
3201
|
}
|
|
2966
3202
|
const sequences = parseSequences(source.sequences, diag);
|
|
2967
3203
|
if (sequences !== void 0) manifest.sequences = sequences;
|
|
@@ -3411,7 +3647,8 @@ function petPackageRoot(importMetaUrl) {
|
|
|
3411
3647
|
/** Resolve the hatch-pet custom pets directory (CODEX_HOME or ~/.codex). */
|
|
3412
3648
|
function codexPetsDir(env = process.env, home = homedir()) {
|
|
3413
3649
|
const raw = env.CODEX_HOME !== void 0 && env.CODEX_HOME.trim() !== "" ? env.CODEX_HOME.trim() : join(home, ".codex");
|
|
3414
|
-
|
|
3650
|
+
const expanded = raw === "~" ? home : raw.startsWith("~/") || raw.startsWith("~\\") ? join(home, raw.slice(2)) : raw;
|
|
3651
|
+
return join(expanded, "pets");
|
|
3415
3652
|
}
|
|
3416
3653
|
/** Finite non-negative integer guard, else the fallback. */
|
|
3417
3654
|
function finiteInt(value, fallback, max) {
|
|
@@ -4048,7 +4285,7 @@ function readPetJson(file, options) {
|
|
|
4048
4285
|
* symlink — must not hang or exhaust the host before the warn-and-drop
|
|
4049
4286
|
* discipline can apply (review-spd follow-up, pet-center M4/M5).
|
|
4050
4287
|
*/
|
|
4051
|
-
const PET_SCAN_JSON_CAP =
|
|
4288
|
+
const PET_SCAN_JSON_CAP = 65536;
|
|
4052
4289
|
/**
|
|
4053
4290
|
* Stat one scanned JSON descriptor with a regular-file + size guard, so a
|
|
4054
4291
|
* pathological user file is skipped with a warning instead of stalling or
|
|
@@ -4651,10 +4888,9 @@ var PetService = class extends Service {
|
|
|
4651
4888
|
return {
|
|
4652
4889
|
petId,
|
|
4653
4890
|
state: stored === void 0 ? initialGameplayState(def, now) : {
|
|
4891
|
+
...stored,
|
|
4654
4892
|
stats: { ...stored.stats },
|
|
4655
|
-
currencies: { ...stored.currencies }
|
|
4656
|
-
mode: stored.mode,
|
|
4657
|
-
settledAt: stored.settledAt
|
|
4893
|
+
currencies: { ...stored.currencies }
|
|
4658
4894
|
}
|
|
4659
4895
|
};
|
|
4660
4896
|
}
|
|
@@ -4662,9 +4898,10 @@ var PetService = class extends Service {
|
|
|
4662
4898
|
gameplayViewOf(state) {
|
|
4663
4899
|
const stats = {};
|
|
4664
4900
|
for (const [name, value] of Object.entries(state.stats)) stats[name] = Math.round(value);
|
|
4901
|
+
const def = this.gameplayDef();
|
|
4665
4902
|
return {
|
|
4666
4903
|
stats,
|
|
4667
|
-
mode: state.mode
|
|
4904
|
+
mode: state.mode !== null && def !== void 0 && !isDeclaredMode(def, state.mode) ? null : state.mode
|
|
4668
4905
|
};
|
|
4669
4906
|
}
|
|
4670
4907
|
/**
|
|
@@ -4743,7 +4980,11 @@ var PetService = class extends Service {
|
|
|
4743
4980
|
view: this.gameplayViewOf(state)
|
|
4744
4981
|
};
|
|
4745
4982
|
}
|
|
4746
|
-
/**
|
|
4983
|
+
/**
|
|
4984
|
+
* RPC: enter or leave a gameplay mode (null clears it). Every mode the
|
|
4985
|
+
* manifest declares is accepted: 'work', 'sleep', or one of the extra
|
|
4986
|
+
* 'modes' entries (a bath, a play session…).
|
|
4987
|
+
*/
|
|
4747
4988
|
async gameplaySetMode(mode) {
|
|
4748
4989
|
const def = this.gameplayDef();
|
|
4749
4990
|
if (def === void 0) return {
|
|
@@ -4758,9 +4999,14 @@ var PetService = class extends Service {
|
|
|
4758
4999
|
ok: false,
|
|
4759
5000
|
error: "no-sleep"
|
|
4760
5001
|
};
|
|
5002
|
+
if (mode !== null && !isDeclaredMode(def, mode)) return {
|
|
5003
|
+
ok: false,
|
|
5004
|
+
error: "unknown-mode"
|
|
5005
|
+
};
|
|
4761
5006
|
const now = Date.now();
|
|
4762
5007
|
const { petId, state } = this.gameplayState(def, now);
|
|
4763
5008
|
settleGameplay(state, def, now, { sessionActive: this.machine.render().sessionActive });
|
|
5009
|
+
if (state.mode !== mode) state.restoreCarryMs = 0;
|
|
4764
5010
|
state.mode = mode;
|
|
4765
5011
|
this.drainGameplayTreats(state);
|
|
4766
5012
|
this.commitGameplay(petId, state);
|
|
@@ -5103,7 +5349,7 @@ function isPetAllowed(ctx, request) {
|
|
|
5103
5349
|
//#endregion
|
|
5104
5350
|
//#region src/http.ts
|
|
5105
5351
|
/** Default body cap for readJsonBody: 64 KiB. */
|
|
5106
|
-
const DEFAULT_JSON_BODY_MAX_BYTES =
|
|
5352
|
+
const DEFAULT_JSON_BODY_MAX_BYTES = 65536;
|
|
5107
5353
|
/** Family-default JSON response headers; callers may append or override. */
|
|
5108
5354
|
const JSON_HEADERS = {
|
|
5109
5355
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -5186,11 +5432,11 @@ const PREVIEW_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
|
5186
5432
|
*/
|
|
5187
5433
|
const PET_ASSET_CAPS = {
|
|
5188
5434
|
/** pet.json manifest. */
|
|
5189
|
-
manifest:
|
|
5435
|
+
manifest: 65536,
|
|
5190
5436
|
/** Atlas, preview and Live2D texture imagery. */
|
|
5191
|
-
image:
|
|
5437
|
+
image: 20971520,
|
|
5192
5438
|
/** Live2D model closure files (.moc3, motion/physics/expression JSON; M3). */
|
|
5193
|
-
model:
|
|
5439
|
+
model: 33554432
|
|
5194
5440
|
};
|
|
5195
5441
|
/** Imagery extensions classify into the image cap; everything else served from a closure is model-class. */
|
|
5196
5442
|
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -5305,7 +5551,7 @@ function postRoute(ctx, path, run) {
|
|
|
5305
5551
|
handler: (req, res) => {
|
|
5306
5552
|
if (!guard(ctx, req, res)) return Promise.resolve();
|
|
5307
5553
|
if (!requireMethod(req, res, "POST")) return Promise.resolve();
|
|
5308
|
-
return readJsonBody(req, { maxBytes:
|
|
5554
|
+
return readJsonBody(req, { maxBytes: 65536 }).then((parsed) => {
|
|
5309
5555
|
const payload = parsed ?? {};
|
|
5310
5556
|
return run(typeof payload === "object" && payload !== null ? payload : {}).then((value) => writeJson(res, 200, value), (error) => {
|
|
5311
5557
|
writeJson(res, 400, {
|
|
@@ -5718,7 +5964,7 @@ function makePetRoutes(deps) {
|
|
|
5718
5964
|
}),
|
|
5719
5965
|
postRoute(ctx, "/api/pet/gameplay/mode", (body) => {
|
|
5720
5966
|
const mode = body.mode;
|
|
5721
|
-
if (mode !== null &&
|
|
5967
|
+
if (mode !== null && typeof mode !== "string") return Promise.reject(/* @__PURE__ */ new Error("invalid-mode"));
|
|
5722
5968
|
return service.gameplaySetMode(mode);
|
|
5723
5969
|
}),
|
|
5724
5970
|
postRoute(ctx, "/api/pet/gameplay/work-tick", () => service.gameplayWorkTick()),
|
|
@@ -5801,24 +6047,77 @@ const name = "pet";
|
|
|
5801
6047
|
/** Services required before the pet can mount its surfaces. */
|
|
5802
6048
|
const inject = ["webServer"];
|
|
5803
6049
|
/**
|
|
5804
|
-
*
|
|
5805
|
-
*
|
|
5806
|
-
*
|
|
5807
|
-
* a removed pet cannot invalidate the section (a strict union would refuse
|
|
5808
|
-
* the whole registration). The settings card renders the actual registry
|
|
5809
|
-
* choices itself from '/api/pet/pets'.
|
|
6050
|
+
* Defaults of the fields the pet's settings page edits. They are the schema
|
|
6051
|
+
* defaults of the pet row's own config, i.e. what a field the profile entry
|
|
6052
|
+
* never set resolves to.
|
|
5810
6053
|
*/
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
6054
|
+
const PET_FORM_DEFAULTS = {
|
|
6055
|
+
visible: true,
|
|
6056
|
+
size: 160,
|
|
6057
|
+
right: 24,
|
|
6058
|
+
bottom: 20,
|
|
6059
|
+
bubbleScale: 1,
|
|
6060
|
+
petId: DEFAULT_PET_ID,
|
|
6061
|
+
enabled: true,
|
|
6062
|
+
decorationEnabled: true
|
|
6063
|
+
};
|
|
6064
|
+
/**
|
|
6065
|
+
* Plugin configuration. Under the 0.1.7 settings model a plugin's own Cordis
|
|
6066
|
+
* Config IS its settings page: the Host derives one form per profile entry
|
|
6067
|
+
* from this schema, so the fields the pet card edits live here with the
|
|
6068
|
+
* defaults the card inherits, next to the tuning block a profile may still
|
|
6069
|
+
* declare.
|
|
6070
|
+
*
|
|
6071
|
+
* Every page field is `volatile()` on purpose. The Host serves exactly the
|
|
6072
|
+
* volatile fields of a Config and refuses writes to any other path, and a
|
|
6073
|
+
* volatile field is the one it can commit into the RUNNING config: the pet
|
|
6074
|
+
* reads the edited value from the live reference instead of being remounted
|
|
6075
|
+
* for each edit (see `syncSettings` in `apply`). petId stays a plain string
|
|
6076
|
+
* because the service clamps the value against the registry, so a stored
|
|
6077
|
+
* selection naming a removed pet cannot invalidate the entry.
|
|
6078
|
+
*/
|
|
6079
|
+
const Config = z.object({
|
|
6080
|
+
visible: z.boolean().default(PET_FORM_DEFAULTS.visible).volatile(),
|
|
6081
|
+
size: z.number().step(1).min(32).max(DISPLAY_SIZE_MAX).default(PET_FORM_DEFAULTS.size).volatile(),
|
|
6082
|
+
right: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(PET_FORM_DEFAULTS.right).volatile(),
|
|
6083
|
+
bottom: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(PET_FORM_DEFAULTS.bottom).volatile(),
|
|
6084
|
+
bubbleScale: z.number().step(.05).min(BUBBLE_SCALE_MIN).max(2).default(PET_FORM_DEFAULTS.bubbleScale).volatile(),
|
|
6085
|
+
petId: z.string().volatile(),
|
|
6086
|
+
enabled: z.boolean().default(PET_FORM_DEFAULTS.enabled).volatile(),
|
|
6087
|
+
decorationEnabled: z.boolean().default(PET_FORM_DEFAULTS.decorationEnabled).volatile()
|
|
6088
|
+
});
|
|
6089
|
+
/**
|
|
6090
|
+
* Read one live config field.
|
|
6091
|
+
* @param field - the field's live reference, plain value, or nothing at all.
|
|
6092
|
+
* @param fallback - value used when the field is absent.
|
|
6093
|
+
* @returns the current field value.
|
|
6094
|
+
*/
|
|
6095
|
+
function readLive(field, fallback) {
|
|
6096
|
+
if (field === void 0) return fallback;
|
|
6097
|
+
const ref = field;
|
|
6098
|
+
return typeof ref.get === "function" ? ref.get() ?? fallback : field;
|
|
6099
|
+
}
|
|
6100
|
+
/**
|
|
6101
|
+
* The settings section the pet runs with: the effective values of the row's
|
|
6102
|
+
* own config. `fallbackPetId` covers a mount whose config names no pet at all
|
|
6103
|
+
* (a direct mount outside a Loader) — under a Loader the schema default is
|
|
6104
|
+
* always present, so the persisted selection stands whenever the config
|
|
6105
|
+
* carries it.
|
|
6106
|
+
* @param config - the effective config of the pet row.
|
|
6107
|
+
* @param fallbackPetId - pet id to use when the config names none.
|
|
6108
|
+
* @returns the resolved settings section.
|
|
6109
|
+
*/
|
|
6110
|
+
function petSettingsSection(config, fallbackPetId) {
|
|
6111
|
+
return {
|
|
6112
|
+
visible: readLive(config.visible, PET_FORM_DEFAULTS.visible),
|
|
6113
|
+
size: readLive(config.size, PET_FORM_DEFAULTS.size),
|
|
6114
|
+
right: readLive(config.right, PET_FORM_DEFAULTS.right),
|
|
6115
|
+
bottom: readLive(config.bottom, PET_FORM_DEFAULTS.bottom),
|
|
6116
|
+
bubbleScale: readLive(config.bubbleScale, PET_FORM_DEFAULTS.bubbleScale),
|
|
6117
|
+
petId: readLive(config.petId, fallbackPetId),
|
|
6118
|
+
enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
|
|
6119
|
+
decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled)
|
|
6120
|
+
};
|
|
5822
6121
|
}
|
|
5823
6122
|
/** Register the pet service and its API + asset routes on the context. */
|
|
5824
6123
|
const apply = mountOnce("@linxin666/dsh-pet", applyImpl);
|
|
@@ -5829,18 +6128,11 @@ function applyImpl(ctx, config = {}) {
|
|
|
5829
6128
|
});
|
|
5830
6129
|
const service = new PetService(ctx, {
|
|
5831
6130
|
...config,
|
|
6131
|
+
enabled: readLive(config.enabled, PET_FORM_DEFAULTS.enabled),
|
|
6132
|
+
decorationEnabled: readLive(config.decorationEnabled, PET_FORM_DEFAULTS.decorationEnabled),
|
|
5832
6133
|
registry
|
|
5833
6134
|
});
|
|
5834
|
-
|
|
5835
|
-
const base = {
|
|
5836
|
-
visible: service.display().visible,
|
|
5837
|
-
size: service.display().size,
|
|
5838
|
-
right: service.display().right,
|
|
5839
|
-
bottom: service.display().bottom,
|
|
5840
|
-
petId: service.selectedPetId(),
|
|
5841
|
-
enabled: config.enabled ?? true,
|
|
5842
|
-
decorationEnabled: config.decorationEnabled ?? true
|
|
5843
|
-
};
|
|
6135
|
+
const current = () => petSettingsSection(config, service.selectedPetId());
|
|
5844
6136
|
const routes = makePetRoutes({
|
|
5845
6137
|
service,
|
|
5846
6138
|
ctx
|
|
@@ -5859,33 +6151,25 @@ function applyImpl(ctx, config = {}) {
|
|
|
5859
6151
|
disposeRoutes = void 0;
|
|
5860
6152
|
}
|
|
5861
6153
|
};
|
|
6154
|
+
const syncSettings = () => {
|
|
6155
|
+
const section = current();
|
|
6156
|
+
service.applySettingsSection(section);
|
|
6157
|
+
service.setEnabled(section.enabled ?? true);
|
|
6158
|
+
syncRoutes();
|
|
6159
|
+
};
|
|
6160
|
+
ctx.on("loader/volatile-update", () => {
|
|
6161
|
+
syncSettings();
|
|
6162
|
+
});
|
|
5862
6163
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
},
|
|
5869
|
-
onChange: () => {
|
|
5870
|
-
const section = current();
|
|
5871
|
-
service.applySettingsSection(section);
|
|
5872
|
-
service.setEnabled(section.enabled ?? true);
|
|
5873
|
-
syncRoutes();
|
|
5874
|
-
}
|
|
5875
|
-
});
|
|
5876
|
-
else if (typeof settingsCtx.settings?.register === "function") {
|
|
5877
|
-
const scope = settingsCtx.settings.register("pet", schema, { base });
|
|
5878
|
-
current = () => scope?.get?.() ?? base;
|
|
5879
|
-
scope?.watch?.(() => {
|
|
5880
|
-
const section = current();
|
|
5881
|
-
service.applySettingsSection(section);
|
|
5882
|
-
service.setEnabled(section.enabled ?? true);
|
|
5883
|
-
syncRoutes();
|
|
5884
|
-
});
|
|
6164
|
+
settingsCtx.effect(() => {
|
|
6165
|
+
try {
|
|
6166
|
+
return settingsCtx.settings.configure({ auto: false }, ctx.fiber);
|
|
6167
|
+
} catch {
|
|
6168
|
+
return () => {};
|
|
5885
6169
|
}
|
|
5886
|
-
}
|
|
6170
|
+
}, "pet: settings page policy");
|
|
5887
6171
|
});
|
|
5888
|
-
|
|
6172
|
+
syncSettings();
|
|
5889
6173
|
}
|
|
5890
6174
|
//#endregion
|
|
5891
|
-
export { AFFINITY_MAX, AFFINITY_RANKS, BUILTIN_REMARKS, DEFAULT_FRAME_COUNTS, DEFAULT_PET_CELL, DEFAULT_PET_COLUMNS, DEFAULT_PET_ID, DEFAULT_PET_NAME, DEFAULT_PET_ROW_COUNT, DEFAULT_TRACK_PATTERNS, MAX_SESSION_BUBBLES, PET_API_PREFIX, PET_ASSET_PREFIX, PET_NAME_MAX_LENGTH, PET_ROW_ORDER, PetService, PetStateMachine, REMARK_KINDS, REMARK_LINES_MAX, REMARK_LINE_MAX, RemarkPicker, animationForPhase, apply, applyInteraction, applyTurnReward, builtinRemark, codexPetsDir, consumeTreat, defaultDisplayConfig, defaultTreatConfig, emptyAffinity, emptyPersist, emptyTreatLedger, inject, loadPetPersist, loadPetRegistry, makePetRoutes,
|
|
6175
|
+
export { AFFINITY_MAX, AFFINITY_RANKS, BUILTIN_REMARKS, Config, DEFAULT_FRAME_COUNTS, DEFAULT_PET_CELL, DEFAULT_PET_COLUMNS, DEFAULT_PET_ID, DEFAULT_PET_NAME, DEFAULT_PET_ROW_COUNT, DEFAULT_TRACK_PATTERNS, MAX_SESSION_BUBBLES, PET_API_PREFIX, PET_ASSET_PREFIX, PET_FORM_DEFAULTS, PET_NAME_MAX_LENGTH, PET_ROW_ORDER, PetService, PetStateMachine, REMARK_KINDS, REMARK_LINES_MAX, REMARK_LINE_MAX, RemarkPicker, animationForPhase, apply, applyInteraction, applyTurnReward, builtinRemark, codexPetsDir, consumeTreat, defaultDisplayConfig, defaultTreatConfig, emptyAffinity, emptyPersist, emptyTreatLedger, inject, loadPetPersist, loadPetRegistry, makePetRoutes, name, normalizePetRemarks, petEntryView, petHomeDir, petPackageRoot, petSettingsSection, rankOf, resolvePetManifest, rowOf, savePetPersist, settleTreatGrants };
|