@linxin666/dsh-pet 0.3.17 → 0.3.18
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 +16 -1
- package/README.zh.md +16 -1
- package/assets/blue-throated-bee-eater/pet.json +204 -0
- package/assets/blue-throated-bee-eater/previews/failed.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/idle.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/jumping.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/review.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/running-left.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/running-right.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/running.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/waiting.gif +0 -0
- package/assets/blue-throated-bee-eater/previews/waving.gif +0 -0
- package/assets/blue-throated-bee-eater/spritesheet.webp +0 -0
- package/assets/blue-throated-bee-eater/voice.json +13 -0
- package/contracts/pet-manifest-v2.schema.json +84 -0
- package/lib/client.js +176 -27
- package/lib/client.js.map +1 -1
- package/lib/index.js +170 -6
- package/lib/types/client/gameplay-hud.d.ts +2 -0
- package/lib/types/client/gameplay-hud.d.ts.map +1 -1
- package/lib/types/client/gameplay-hud.js +65 -12
- package/lib/types/client/index.d.ts +0 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +25 -2
- package/lib/types/client/locales.d.ts +7 -3
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +7 -3
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -1
- package/lib/types/client/renderers/Frames2dVisualMount.js +5 -1
- package/lib/types/client/renderers/frames2d.d.ts +25 -0
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
- package/lib/types/client/renderers/frames2d.js +84 -7
- package/lib/types/manifest-v2.d.ts +45 -0
- package/lib/types/manifest-v2.d.ts.map +1 -1
- package/lib/types/manifest-v2.js +139 -1
- package/lib/types/persist.d.ts +1 -1
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +1 -1
- package/lib/types/registry.d.ts +22 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +45 -1
- package/package.json +2 -1
- package/src/client/gameplay-hud.test.tsx +64 -0
- package/src/client/gameplay-hud.tsx +109 -21
- package/src/client/index.ts +26 -2
- package/src/client/locales.ts +7 -3
- package/src/client/pet.module.css +31 -1
- package/src/client/renderers/Frames2dVisualMount.tsx +5 -1
- package/src/client/renderers/frames2d.test.ts +43 -0
- package/src/client/renderers/frames2d.ts +100 -7
- package/src/manifest-v2.ts +167 -1
- package/src/persist.ts +1 -1
- package/src/registry.test.ts +19 -5
- package/src/registry.ts +66 -1
package/lib/index.js
CHANGED
|
@@ -1356,6 +1356,7 @@ const defaultDisplayConfig = {
|
|
|
1356
1356
|
right: 24,
|
|
1357
1357
|
bottom: 120
|
|
1358
1358
|
};
|
|
1359
|
+
const DISPLAY_SIZE_MAX = 1024;
|
|
1359
1360
|
const DISPLAY_INSET_MAX = 1e4;
|
|
1360
1361
|
/** Name constraints. */
|
|
1361
1362
|
const PET_NAME_MAX_LENGTH = 20;
|
|
@@ -1452,7 +1453,7 @@ function loadPetPersist(dir = petHomeDir()) {
|
|
|
1452
1453
|
const rawDisplay = parsed.display ?? {};
|
|
1453
1454
|
const display = {
|
|
1454
1455
|
visible: typeof rawDisplay.visible === "boolean" ? rawDisplay.visible : base.display.visible,
|
|
1455
|
-
size: Math.round(Math.min(
|
|
1456
|
+
size: Math.round(Math.min(DISPLAY_SIZE_MAX, Math.max(32, finiteNum(rawDisplay.size, base.display.size)))),
|
|
1456
1457
|
right: Math.round(clamp(finiteNum(rawDisplay.right, base.display.right), DISPLAY_INSET_MAX)),
|
|
1457
1458
|
bottom: Math.round(clamp(finiteNum(rawDisplay.bottom, base.display.bottom), DISPLAY_INSET_MAX))
|
|
1458
1459
|
};
|
|
@@ -2442,7 +2443,8 @@ const KNOWN_FRAMES2D = /* @__PURE__ */ new Set([
|
|
|
2442
2443
|
"dir",
|
|
2443
2444
|
"defaultFrameMs",
|
|
2444
2445
|
"tracks",
|
|
2445
|
-
"phases"
|
|
2446
|
+
"phases",
|
|
2447
|
+
"skins"
|
|
2446
2448
|
]);
|
|
2447
2449
|
/** frames2d track field allow-list (drift-locked to the schema file). */
|
|
2448
2450
|
const KNOWN_FRAMES2D_TRACK = /* @__PURE__ */ new Set([
|
|
@@ -2451,6 +2453,33 @@ const KNOWN_FRAMES2D_TRACK = /* @__PURE__ */ new Set([
|
|
|
2451
2453
|
"loop",
|
|
2452
2454
|
"fallback"
|
|
2453
2455
|
]);
|
|
2456
|
+
/** frames2d skin entry field allow-list (drift-locked to the schema file). */
|
|
2457
|
+
const KNOWN_SKIN = /* @__PURE__ */ new Set([
|
|
2458
|
+
"id",
|
|
2459
|
+
"label",
|
|
2460
|
+
"idleTrack",
|
|
2461
|
+
"clickActions",
|
|
2462
|
+
"gameplayTracks"
|
|
2463
|
+
]);
|
|
2464
|
+
/** frames2d skin click-action field allow-list (drift-locked to the schema file). */
|
|
2465
|
+
const KNOWN_SKIN_CLICK = /* @__PURE__ */ new Set([
|
|
2466
|
+
"track",
|
|
2467
|
+
"probability",
|
|
2468
|
+
"phrases"
|
|
2469
|
+
]);
|
|
2470
|
+
/** Max spoken lines one skin click action may declare. */
|
|
2471
|
+
const SKIN_CLICK_MAX_PHRASES = 5;
|
|
2472
|
+
/** Max length of one spoken line. */
|
|
2473
|
+
const SKIN_CLICK_PHRASE_MAX_LENGTH = 120;
|
|
2474
|
+
/** Validate a skin click action's optional phrase pool (same spirit as gameplay phrases). */
|
|
2475
|
+
function parseSkinPhrases(raw, field, diag) {
|
|
2476
|
+
if (raw === void 0) return void 0;
|
|
2477
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > SKIN_CLICK_MAX_PHRASES || raw.some((line) => typeof line !== "string" || line.trim() === "" || line.length > SKIN_CLICK_PHRASE_MAX_LENGTH)) {
|
|
2478
|
+
diag.error(field + " must be 1..5 non-empty lines of at most 120 chars");
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
return raw;
|
|
2482
|
+
}
|
|
2454
2483
|
var Diagnostics$1 = class {
|
|
2455
2484
|
list = [];
|
|
2456
2485
|
source;
|
|
@@ -2695,6 +2724,100 @@ function parseFrames2dBlock(raw, diag) {
|
|
|
2695
2724
|
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));
|
|
2696
2725
|
}
|
|
2697
2726
|
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));
|
|
2727
|
+
if (raw.skins !== void 0) 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");
|
|
2728
|
+
else {
|
|
2729
|
+
const skins = [];
|
|
2730
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2731
|
+
for (const [index, entry] of raw.skins.entries()) {
|
|
2732
|
+
const field = "frames2d.skins[" + index + "]";
|
|
2733
|
+
if (!isRecord$1(entry)) {
|
|
2734
|
+
diag.error(field + " must be an object");
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
const extra = unknownKeys$1(entry, KNOWN_SKIN);
|
|
2738
|
+
if (extra.length > 0) diag.error(field + ": unknown field(s) " + extra.map((k) => JSON.stringify(k)).join(", "));
|
|
2739
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
2740
|
+
if (id === "" || id.length > 24 || !FRAMES2D_TRACK_NAME.test(id)) {
|
|
2741
|
+
diag.error(field + ".id must be a lowercase kebab id of at most 24 chars");
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
if (seen.has(id)) {
|
|
2745
|
+
diag.error(field + ": duplicate skin id " + JSON.stringify(id));
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
seen.add(id);
|
|
2749
|
+
const label = typeof entry.label === "string" ? entry.label.trim() : "";
|
|
2750
|
+
if (label === "" || label.length > 40) {
|
|
2751
|
+
diag.error(field + ".label must be a non-empty string of at most 40 chars");
|
|
2752
|
+
continue;
|
|
2753
|
+
}
|
|
2754
|
+
const idleTrack = typeof entry.idleTrack === "string" ? entry.idleTrack : "";
|
|
2755
|
+
if (!validTrackName(idleTrack) || tracks[idleTrack] === void 0) {
|
|
2756
|
+
diag.error(field + ".idleTrack must name a declared frames2d track");
|
|
2757
|
+
continue;
|
|
2758
|
+
}
|
|
2759
|
+
let clickActions;
|
|
2760
|
+
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");
|
|
2761
|
+
else {
|
|
2762
|
+
const resolved = [];
|
|
2763
|
+
for (const [cIndex, action] of entry.clickActions.entries()) {
|
|
2764
|
+
const cField = field + ".clickActions[" + cIndex + "]";
|
|
2765
|
+
if (!isRecord$1(action)) {
|
|
2766
|
+
diag.error(cField + " must be an object");
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
const cExtra = unknownKeys$1(action, KNOWN_SKIN_CLICK);
|
|
2770
|
+
if (cExtra.length > 0) diag.error(cField + ": unknown field(s) " + cExtra.map((k) => JSON.stringify(k)).join(", "));
|
|
2771
|
+
const trackName = typeof action.track === "string" ? action.track : "";
|
|
2772
|
+
if (!validTrackName(trackName) || tracks[trackName] === void 0) {
|
|
2773
|
+
diag.error(cField + ".track must name a declared frames2d track");
|
|
2774
|
+
continue;
|
|
2775
|
+
}
|
|
2776
|
+
if (typeof action.probability !== "number" || !Number.isFinite(action.probability) || action.probability <= 0 || action.probability > 1) {
|
|
2777
|
+
diag.error(cField + ".probability must be a number in (0, 1]");
|
|
2778
|
+
continue;
|
|
2779
|
+
}
|
|
2780
|
+
const phrases = parseSkinPhrases(action.phrases, cField + ".phrases", diag);
|
|
2781
|
+
resolved.push({
|
|
2782
|
+
track: trackName,
|
|
2783
|
+
probability: action.probability,
|
|
2784
|
+
...phrases === void 0 ? {} : { phrases }
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
if (resolved.length > 0) clickActions = resolved;
|
|
2788
|
+
}
|
|
2789
|
+
let gameplayTracks;
|
|
2790
|
+
if (entry.gameplayTracks !== void 0) if (!isRecord$1(entry.gameplayTracks)) diag.error(field + ".gameplayTracks must be an object mapping gameplay state names to tracks");
|
|
2791
|
+
else {
|
|
2792
|
+
const states = Object.keys(entry.gameplayTracks);
|
|
2793
|
+
if (states.length === 0 || states.length > 8) diag.error(field + ".gameplayTracks must map 1..8 gameplay states");
|
|
2794
|
+
else {
|
|
2795
|
+
const resolvedTracks = {};
|
|
2796
|
+
for (const state of states) {
|
|
2797
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(state)) {
|
|
2798
|
+
diag.error(field + ".gameplayTracks key " + JSON.stringify(state) + " must be a lowercase kebab state name");
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
const gTrack = entry.gameplayTracks[state];
|
|
2802
|
+
if (typeof gTrack !== "string" || !validTrackName(gTrack) || tracks[gTrack] === void 0) {
|
|
2803
|
+
diag.error(field + ".gameplayTracks[" + JSON.stringify(state) + "] must name a declared frames2d track");
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
resolvedTracks[state] = gTrack;
|
|
2807
|
+
}
|
|
2808
|
+
if (Object.keys(resolvedTracks).length > 0) gameplayTracks = resolvedTracks;
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
skins.push({
|
|
2812
|
+
id,
|
|
2813
|
+
label,
|
|
2814
|
+
idleTrack,
|
|
2815
|
+
...clickActions === void 0 ? {} : { clickActions },
|
|
2816
|
+
...gameplayTracks === void 0 ? {} : { gameplayTracks }
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
if (skins.length > 0) block.skins = skins;
|
|
2820
|
+
}
|
|
2698
2821
|
return diag.hasErrors ? void 0 : block;
|
|
2699
2822
|
}
|
|
2700
2823
|
/** v1 compat read: map the legacy flat manifest onto the v2 sprite2d shape. */
|
|
@@ -3685,6 +3808,46 @@ function resolveFrames2dEntry(manifest, dir, options) {
|
|
|
3685
3808
|
record("warning", "pet " + manifest.id + ": frames2d phase " + phase + " maps to dropped track " + JSON.stringify(target) + "; using idle");
|
|
3686
3809
|
phases[phase] = idleTrack;
|
|
3687
3810
|
}
|
|
3811
|
+
let skins;
|
|
3812
|
+
if (block.skins !== void 0) {
|
|
3813
|
+
const resolved = [];
|
|
3814
|
+
for (const skin of block.skins) {
|
|
3815
|
+
if (tracks[skin.idleTrack] === void 0) {
|
|
3816
|
+
record("warning", "pet " + manifest.id + ": frames2d skin " + JSON.stringify(skin.id) + " idleTrack dropped; skipping");
|
|
3817
|
+
continue;
|
|
3818
|
+
}
|
|
3819
|
+
let clickActions;
|
|
3820
|
+
if (skin.clickActions !== void 0) {
|
|
3821
|
+
const kept = [];
|
|
3822
|
+
for (const action of skin.clickActions) {
|
|
3823
|
+
if (tracks[action.track] === void 0) {
|
|
3824
|
+
record("warning", "pet " + manifest.id + ": frames2d skin " + JSON.stringify(skin.id) + " click action track " + JSON.stringify(action.track) + " dropped");
|
|
3825
|
+
continue;
|
|
3826
|
+
}
|
|
3827
|
+
kept.push(action);
|
|
3828
|
+
}
|
|
3829
|
+
if (kept.length > 0) clickActions = kept;
|
|
3830
|
+
}
|
|
3831
|
+
let gameplayTracks;
|
|
3832
|
+
if (skin.gameplayTracks !== void 0) {
|
|
3833
|
+
const kept = {};
|
|
3834
|
+
for (const [state, gTrack] of Object.entries(skin.gameplayTracks)) {
|
|
3835
|
+
if (tracks[gTrack] === void 0) {
|
|
3836
|
+
record("warning", "pet " + manifest.id + ": frames2d skin " + JSON.stringify(skin.id) + " gameplay track override " + JSON.stringify(state) + " -> " + JSON.stringify(gTrack) + " dropped");
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
kept[state] = gTrack;
|
|
3840
|
+
}
|
|
3841
|
+
if (Object.keys(kept).length > 0) gameplayTracks = kept;
|
|
3842
|
+
}
|
|
3843
|
+
resolved.push({
|
|
3844
|
+
...skin,
|
|
3845
|
+
...clickActions === void 0 ? {} : { clickActions },
|
|
3846
|
+
...gameplayTracks === void 0 ? {} : { gameplayTracks }
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
if (resolved.length > 0) skins = resolved;
|
|
3850
|
+
}
|
|
3688
3851
|
const remarks = normalizePetRemarks(manifest.remarks, (message) => record("warning", "pet " + manifest.id + ": " + message));
|
|
3689
3852
|
let gameplay;
|
|
3690
3853
|
if (manifest.gameplay !== void 0) {
|
|
@@ -3729,7 +3892,8 @@ function resolveFrames2dEntry(manifest, dir, options) {
|
|
|
3729
3892
|
renderer: "frames2d",
|
|
3730
3893
|
frames2d: {
|
|
3731
3894
|
tracks,
|
|
3732
|
-
phases
|
|
3895
|
+
phases,
|
|
3896
|
+
...skins === void 0 ? {} : { skins }
|
|
3733
3897
|
},
|
|
3734
3898
|
...gameplay === void 0 ? {} : { gameplay },
|
|
3735
3899
|
cell,
|
|
@@ -4618,7 +4782,7 @@ var PetService = class extends Service {
|
|
|
4618
4782
|
...this.ledger.snapshot.display,
|
|
4619
4783
|
...patch
|
|
4620
4784
|
};
|
|
4621
|
-
next.size = Math.round(Math.min(
|
|
4785
|
+
next.size = Math.round(Math.min(DISPLAY_SIZE_MAX, Math.max(32, next.size)));
|
|
4622
4786
|
next.right = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, next.right)));
|
|
4623
4787
|
next.bottom = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, next.bottom)));
|
|
4624
4788
|
this.ledger.setDisplay(next);
|
|
@@ -4662,7 +4826,7 @@ var PetService = class extends Service {
|
|
|
4662
4826
|
} else if (section.petId !== void 0) this.syncSettingsFromPet();
|
|
4663
4827
|
const next = { ...this.ledger.snapshot.display };
|
|
4664
4828
|
next.visible = section.visible && (section.enabled ?? true);
|
|
4665
|
-
next.size = Math.round(Math.min(
|
|
4829
|
+
next.size = Math.round(Math.min(DISPLAY_SIZE_MAX, Math.max(32, section.size)));
|
|
4666
4830
|
next.right = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, section.right)));
|
|
4667
4831
|
next.bottom = Math.round(Math.min(DISPLAY_INSET_MAX, Math.max(0, section.bottom)));
|
|
4668
4832
|
this.ledger.setDisplay(next);
|
|
@@ -5528,7 +5692,7 @@ const inject = ["webServer"];
|
|
|
5528
5692
|
function makePetSettingsSchema(fallbackPetId) {
|
|
5529
5693
|
return z.object({
|
|
5530
5694
|
visible: z.boolean().default(true),
|
|
5531
|
-
size: z.number().step(1).min(32).max(
|
|
5695
|
+
size: z.number().step(1).min(32).max(DISPLAY_SIZE_MAX).default(160),
|
|
5532
5696
|
right: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(24),
|
|
5533
5697
|
bottom: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(20),
|
|
5534
5698
|
petId: z.string().default(fallbackPetId),
|
|
@@ -30,6 +30,8 @@ export interface GameplayApi {
|
|
|
30
30
|
*/
|
|
31
31
|
export interface GameplayBus {
|
|
32
32
|
setTrack?: (track?: string) => void;
|
|
33
|
+
/** Swap the pet's base idle track (skin switch); undefined restores default. */
|
|
34
|
+
setIdleTrack?: (track?: string) => void;
|
|
33
35
|
tap?: (fx: number, fy: number) => void;
|
|
34
36
|
/**
|
|
35
37
|
* Card open/close request from the chrome (the hover panel's 玩法 action):
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gameplay-hud.d.ts","sourceRoot":"","sources":["../../../src/client/gameplay-hud.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAsE,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAC7G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"gameplay-hud.d.ts","sourceRoot":"","sources":["../../../src/client/gameplay-hud.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAsE,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAC7G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAqB,MAAM,gBAAgB,CAAA;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGtC,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACxD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC9C,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;CACtD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,gFAAgF;IAChF,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAYD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,UAAU,EAAE,aAAa,CAAA;IACzB,KAAK,EAAE,gBAAgB,CAAA;IACvB,GAAG,EAAE,WAAW,CAAA;IAChB,GAAG,EAAE,WAAW,CAAA;IAChB,IAAI,EAAE,UAAU,CAAA;IAChB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;CAC/B,GAAG,YAAY,GAAG,IAAI,CAwatB"}
|
|
@@ -22,6 +22,10 @@ export function GameplayHud(props) {
|
|
|
22
22
|
const phase = ui.snapshot?.phase ?? 'idle';
|
|
23
23
|
const [open, setOpen] = useState(false);
|
|
24
24
|
const [page, setPage] = useState('root');
|
|
25
|
+
// Currently selected skin id (base idle swap); undefined = default look.
|
|
26
|
+
const [skinId, setSkinId] = useState(undefined);
|
|
27
|
+
const skinIdRef = useRef(undefined);
|
|
28
|
+
skinIdRef.current = skinId;
|
|
25
29
|
const hudRef = useRef(null);
|
|
26
30
|
const cardRef = useRef(null);
|
|
27
31
|
const [floats, setFloats] = useState([]);
|
|
@@ -52,9 +56,33 @@ export function GameplayHud(props) {
|
|
|
52
56
|
// Tap handling (registered on the bus; PetSprite reports sprite-box
|
|
53
57
|
// fractions). Sleep wakes on tap; work blocks taps; a held touch
|
|
54
58
|
// animation turns taps into the plain-click boost.
|
|
59
|
+
// Skin contract: while a skin is selected, taps only ever play that
|
|
60
|
+
// skin's own click actions — a miss resolves to the plain click boost,
|
|
61
|
+
// never the default touch zones (so a skinned pet cannot trigger the
|
|
62
|
+
// default pet's shy/work reactions).
|
|
55
63
|
useEffect(() => {
|
|
56
64
|
if (def === undefined)
|
|
57
65
|
return undefined;
|
|
66
|
+
// Play a one-shot override track and lock tap input for its duration so
|
|
67
|
+
// consecutive taps cannot retrigger mid-play (skin click actions and
|
|
68
|
+
// default touch-zone reactions share this path).
|
|
69
|
+
const holdTrack = (track, holdMs) => {
|
|
70
|
+
bus.setTrack?.(track);
|
|
71
|
+
touchLockUntilRef.current = Date.now() + holdMs;
|
|
72
|
+
window.setTimeout(() => {
|
|
73
|
+
if (Date.now() >= touchLockUntilRef.current)
|
|
74
|
+
bus.setTrack?.(undefined);
|
|
75
|
+
}, holdMs);
|
|
76
|
+
};
|
|
77
|
+
const speak = (phrases) => {
|
|
78
|
+
if (phrases !== undefined && phrases.length > 0) {
|
|
79
|
+
const phrase = phrases[Math.floor(Math.random() * phrases.length)];
|
|
80
|
+
store.actions.setFeedback({ text: phrase, kind: 'none', at: Date.now() });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
// Total play time of a track in ms (its fallback lands back in the skin
|
|
84
|
+
// base idle, so holding the lock for the full loop is unnecessary).
|
|
85
|
+
const trackDuration = (track) => definition.frames2d?.tracks[track]?.durations.reduce((sum, ms) => sum + ms, 0) ?? 0;
|
|
58
86
|
bus.tap = (fx, fy) => {
|
|
59
87
|
if (modeRef.current === 'sleep') {
|
|
60
88
|
void api.setMode(null).then(applyResult, () => undefined);
|
|
@@ -71,6 +99,29 @@ export function GameplayHud(props) {
|
|
|
71
99
|
void api.touch().then(applyResult, () => undefined);
|
|
72
100
|
return;
|
|
73
101
|
}
|
|
102
|
+
const activeSkin = definition.frames2d?.skins?.find(skin => skin.id === skinIdRef.current);
|
|
103
|
+
if (activeSkin !== undefined) {
|
|
104
|
+
// Skin click actions roll first (declared order, cumulative
|
|
105
|
+
// probabilities); on a hit play the action's track once. A miss
|
|
106
|
+
// stays on the plain click boost — never the default touch zones.
|
|
107
|
+
const actions = activeSkin.clickActions ?? [];
|
|
108
|
+
if (actions.length > 0) {
|
|
109
|
+
let roll = Math.random();
|
|
110
|
+
const fired = actions.find(action => {
|
|
111
|
+
if (roll < action.probability)
|
|
112
|
+
return true;
|
|
113
|
+
roll -= action.probability;
|
|
114
|
+
return false;
|
|
115
|
+
});
|
|
116
|
+
if (fired !== undefined) {
|
|
117
|
+
holdTrack(fired.track, trackDuration(fired.track) || 3000);
|
|
118
|
+
speak(fired.phrases);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
void api.touch().then(applyResult, () => undefined);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
74
125
|
const zone = def.touch === undefined ? undefined : touchZoneAt(def.touch, hy);
|
|
75
126
|
if (zone === undefined)
|
|
76
127
|
return;
|
|
@@ -78,15 +129,8 @@ export function GameplayHud(props) {
|
|
|
78
129
|
applyResult(result);
|
|
79
130
|
if (result.hit !== true)
|
|
80
131
|
return;
|
|
81
|
-
if (result.state !== undefined)
|
|
82
|
-
|
|
83
|
-
const holdMs = result.stateMs ?? 3000;
|
|
84
|
-
touchLockUntilRef.current = Date.now() + holdMs;
|
|
85
|
-
window.setTimeout(() => {
|
|
86
|
-
if (Date.now() >= touchLockUntilRef.current)
|
|
87
|
-
bus.setTrack?.(undefined);
|
|
88
|
-
}, holdMs);
|
|
89
|
-
}
|
|
132
|
+
if (result.state !== undefined)
|
|
133
|
+
holdTrack(result.state, result.stateMs ?? 3000);
|
|
90
134
|
if (result.phrase !== undefined) {
|
|
91
135
|
store.actions.setFeedback({ text: result.phrase, kind: 'none', at: Date.now() });
|
|
92
136
|
}
|
|
@@ -236,14 +280,18 @@ export function GameplayHud(props) {
|
|
|
236
280
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode value
|
|
237
281
|
}, [definition.id, def, view?.mode]);
|
|
238
282
|
// Sleep loop: hold the sleep track; restore is host-side (lazy settle).
|
|
283
|
+
// While a skin with a gameplayTracks.sleep override is selected, the skin's
|
|
284
|
+
// own track replaces the default sleep track (e.g. a skin-specific doze).
|
|
239
285
|
useEffect(() => {
|
|
240
286
|
const sleep = def?.sleep;
|
|
241
287
|
if (def === undefined || sleep === undefined || view?.mode !== 'sleep')
|
|
242
288
|
return undefined;
|
|
243
|
-
|
|
289
|
+
const skinGameplay = definition.frames2d?.skins?.find(skin => skin.id === skinIdRef.current)?.gameplayTracks;
|
|
290
|
+
const hold = skinGameplay?.['sleep'] ?? sleep.state;
|
|
291
|
+
bus.setTrack?.(hold);
|
|
244
292
|
return () => bus.setTrack?.(undefined);
|
|
245
293
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode value
|
|
246
|
-
}, [definition.id, def, view?.mode]);
|
|
294
|
+
}, [definition.id, def, view?.mode, skinId]);
|
|
247
295
|
if (def === undefined || view === undefined)
|
|
248
296
|
return null;
|
|
249
297
|
const mode = view.mode;
|
|
@@ -267,8 +315,13 @@ export function GameplayHud(props) {
|
|
|
267
315
|
const setMode = (next) => {
|
|
268
316
|
void api.setMode(next).then(applyResult, () => undefined);
|
|
269
317
|
};
|
|
318
|
+
const skins = definition.frames2d?.skins;
|
|
319
|
+
const selectSkin = (skin) => {
|
|
320
|
+
setSkinId(skin?.id);
|
|
321
|
+
bus.setIdleTrack?.(skin?.idleTrack);
|
|
322
|
+
};
|
|
270
323
|
return (_jsxs("div", { ref: hudRef, className: styles.gameplayHud, "data-dsh-pet-gameplay": definition.id, children: [floats.map(entry => (_jsx("div", { className: styles.gameplayFloat, children: entry.text }, entry.id))), mode !== null && (_jsx("div", { className: styles.gameplayModeChip, children: tr(mode === 'work' ? 'pet.gameplay.working' : 'pet.gameplay.sleeping') })), open && (_jsxs("div", { ref: cardRef, className: styles.gameplayCard, "data-page": page, children: [page === 'root' && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayBars, children: Object.entries(stats).map(([name, stat]) => {
|
|
271
324
|
const value = view.stats[name] ?? 0;
|
|
272
325
|
return (_jsxs("div", { className: styles.gameplayBarRow, title: statLabel(name) + ' ' + String(value) + '/' + String(stat.max), children: [_jsx("span", { className: styles.gameplayBarLabel, children: statLabel(name) }), _jsx("span", { className: styles.gameplayBarTrack, children: _jsx("span", { className: styles.gameplayBarFill, style: { width: Math.round((value / stat.max) * 100) + '%' } }) })] }, name));
|
|
273
|
-
}) }), _jsxs("div", { className: styles.gameplayActions, children: [def.
|
|
326
|
+
}) }), _jsxs("div", { className: styles.gameplayActions, children: [def.sleep !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === 'sleep' ? null : 'sleep'), children: tr(mode === 'sleep' ? 'pet.gameplay.wake' : 'pet.gameplay.sleep') })), shop !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('shop'), children: tr('pet.gameplay.shop') })), skins !== undefined && skins.length > 0 && (_jsx("button", { type: "button", className: styles.action, onClick: () => setPage('skins'), children: tr('pet.gameplay.skin') })), def.work !== undefined && (_jsx("button", { type: "button", className: styles.action, onClick: () => setMode(mode === 'work' ? null : 'work'), children: tr(mode === 'work' ? 'pet.gameplay.stopWork' : 'pet.gameplay.work') }))] })] })), page === 'shop' && shop !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.gameplayShopItems, children: shop.items.map(item => (_jsxs("button", { type: "button", className: styles.gameplayShopItem, onClick: () => buy(item.id), title: item.label + ' — ' + String(item.price) + ' ' + currencyLabel(item.currency), children: [item.image !== undefined && (_jsx("img", { className: styles.gameplayShopItemImage, src: item.image, alt: "", draggable: false })), _jsx("span", { className: styles.gameplayShopItemLabel, children: item.label }), _jsxs("span", { className: styles.gameplayShopItemPrice, children: [item.price, " ", currencyLabel(item.currency)] })] }, item.id))) }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), page === 'skins' && skins !== undefined && (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.gameplaySkinItems, children: [_jsx("button", { type: "button", className: skinId === undefined ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(undefined), children: tr('pet.gameplay.skinDefault') }), skins.map(skin => (_jsx("button", { type: "button", className: skinId === skin.id ? styles.gameplaySkinItem + ' ' + styles.gameplaySkinItemActive : styles.gameplaySkinItem, onClick: () => selectSkin(skin), children: skin.label }, skin.id)))] }), _jsx("div", { className: styles.gameplayActions, children: _jsx("button", { type: "button", className: styles.action, onClick: () => setPage('root'), children: tr('pet.gameplay.back') }) })] })), _jsx("button", { type: "button", className: styles.gameplayClose, "aria-label": tr('pet.gameplay.back'), onClick: () => setOpen(false), children: "\u00D7" })] }))] }));
|
|
274
327
|
}
|
|
@@ -35,11 +35,5 @@ declare module '@deepseek-ai/cordis' {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Client plugin body: register dictionaries, mount the global pet entry and
|
|
40
|
-
* poll loop while the plugin is enabled, and seat the settings card as a
|
|
41
|
-
* first-level settings section.
|
|
42
|
-
* @param ctx - client root context.
|
|
43
|
-
*/
|
|
44
38
|
export declare function apply(ctx: ClientContext): void;
|
|
45
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AA8ElG,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AA8ElG,wEAAwE;AACxE,eAAO,MAAM,MAAM,UAA2E,CAAA;AAE9F,qEAAqE;AACrE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAoBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiU9C"}
|
|
@@ -64,6 +64,16 @@ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote
|
|
|
64
64
|
* first-level settings section.
|
|
65
65
|
* @param ctx - client root context.
|
|
66
66
|
*/
|
|
67
|
+
/**
|
|
68
|
+
* Module-wide work-tick throttle (ms). Hot reloads can leave several
|
|
69
|
+
* GameplayHud instances alive, each running its own 10s work interval;
|
|
70
|
+
* without a shared gate every interval would call workTick and each stale
|
|
71
|
+
* call re-rolls, re-grants treats and re-plays the success/fail track, so
|
|
72
|
+
* the outcome appears to play several times per window. This shared marker
|
|
73
|
+
* accepts the first adjudication of a window and silently suppresses the
|
|
74
|
+
* duplicates that follow. Reset when (re-)entering work mode.
|
|
75
|
+
*/
|
|
76
|
+
let lastWorkTickAt = 0;
|
|
67
77
|
export function apply(ctx) {
|
|
68
78
|
// Anonymous install heartbeat (docs/telemetry.md): one beat per browser per
|
|
69
79
|
// UTC day, package name only, silent failure.
|
|
@@ -299,8 +309,21 @@ export function apply(ctx) {
|
|
|
299
309
|
},
|
|
300
310
|
gameplay: {
|
|
301
311
|
touch: (zone) => petApi.gameplayTouch(zone),
|
|
302
|
-
setMode: (mode) =>
|
|
303
|
-
|
|
312
|
+
setMode: async (mode) => {
|
|
313
|
+
if (mode === 'work')
|
|
314
|
+
lastWorkTickAt = 0;
|
|
315
|
+
return petApi.gameplaySetMode(mode);
|
|
316
|
+
},
|
|
317
|
+
workTick: async () => {
|
|
318
|
+
// One adjudication per tick window, page-wide: suppress stale
|
|
319
|
+
// duplicate intervals (HMR) re-playing the result track.
|
|
320
|
+
const now = Date.now();
|
|
321
|
+
if (now - lastWorkTickAt < 8500) {
|
|
322
|
+
return { ok: true };
|
|
323
|
+
}
|
|
324
|
+
lastWorkTickAt = now;
|
|
325
|
+
return petApi.gameplayWorkTick();
|
|
326
|
+
},
|
|
304
327
|
buy: (item) => petApi.gameplayBuy(item),
|
|
305
328
|
},
|
|
306
329
|
});
|
|
@@ -34,9 +34,11 @@ export declare const zh: {
|
|
|
34
34
|
readonly 'pet.gameplay.menu': "玩法";
|
|
35
35
|
readonly 'pet.gameplay.work': "打工";
|
|
36
36
|
readonly 'pet.gameplay.stopWork': "收工";
|
|
37
|
-
readonly 'pet.gameplay.sleep': "
|
|
37
|
+
readonly 'pet.gameplay.sleep': "休息";
|
|
38
38
|
readonly 'pet.gameplay.wake': "起床";
|
|
39
39
|
readonly 'pet.gameplay.shop': "商店";
|
|
40
|
+
readonly 'pet.gameplay.skin': "皮肤";
|
|
41
|
+
readonly 'pet.gameplay.skinDefault': "默认";
|
|
40
42
|
readonly 'pet.gameplay.back': "返回";
|
|
41
43
|
readonly 'pet.gameplay.buy': "购买";
|
|
42
44
|
readonly 'pet.gameplay.insufficient': "{currency}不足";
|
|
@@ -62,7 +64,7 @@ export declare const zh: {
|
|
|
62
64
|
readonly 'settings.visible': "显示宠物";
|
|
63
65
|
readonly 'settings.visibleHint': "关闭后宠物隐藏,可从聊天输入区重新召唤。";
|
|
64
66
|
readonly 'settings.size': "大小(px)";
|
|
65
|
-
readonly 'settings.sizeHint': "精灵单元高度,范围 32–
|
|
67
|
+
readonly 'settings.sizeHint': "精灵单元高度,范围 32–1024。";
|
|
66
68
|
readonly 'settings.right': "距右侧(px)";
|
|
67
69
|
readonly 'settings.rightHint': "距视口右边缘的水平内缩距离。";
|
|
68
70
|
readonly 'settings.bottom': "距底部(px)";
|
|
@@ -116,6 +118,8 @@ export declare const en: {
|
|
|
116
118
|
readonly 'pet.gameplay.sleep': "Sleep";
|
|
117
119
|
readonly 'pet.gameplay.wake': "Wake up";
|
|
118
120
|
readonly 'pet.gameplay.shop': "Shop";
|
|
121
|
+
readonly 'pet.gameplay.skin': "Skin";
|
|
122
|
+
readonly 'pet.gameplay.skinDefault': "Default";
|
|
119
123
|
readonly 'pet.gameplay.back': "Back";
|
|
120
124
|
readonly 'pet.gameplay.buy': "Buy";
|
|
121
125
|
readonly 'pet.gameplay.insufficient': "Not enough {currency}";
|
|
@@ -141,7 +145,7 @@ export declare const en: {
|
|
|
141
145
|
readonly 'settings.visible': "Show the pet";
|
|
142
146
|
readonly 'settings.visibleHint': "When off, the pet hides; summon it again from the input row.";
|
|
143
147
|
readonly 'settings.size': "Size (px)";
|
|
144
|
-
readonly 'settings.sizeHint': "Sprite cell height, 32–
|
|
148
|
+
readonly 'settings.sizeHint': "Sprite cell height, 32–1024.";
|
|
145
149
|
readonly 'settings.right': "Right inset (px)";
|
|
146
150
|
readonly 'settings.rightHint': "Horizontal inset from the viewport right edge.";
|
|
147
151
|
readonly 'settings.bottom': "Bottom inset (px)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,eAAO,MAAM,EAAE,QAAQ,CAAA;AAEvB,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFL,CAAA;AAEV,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAA;AAEpC,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGnD;AAED;;;;;;;GAOG;AACH,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQvE;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,uBAAuB;QACvB,GAAG,EAAE,MAAM,CAAA;KACZ;CACF"}
|
|
@@ -35,9 +35,11 @@ export const zh = {
|
|
|
35
35
|
'pet.gameplay.menu': '玩法',
|
|
36
36
|
'pet.gameplay.work': '打工',
|
|
37
37
|
'pet.gameplay.stopWork': '收工',
|
|
38
|
-
'pet.gameplay.sleep': '
|
|
38
|
+
'pet.gameplay.sleep': '休息',
|
|
39
39
|
'pet.gameplay.wake': '起床',
|
|
40
40
|
'pet.gameplay.shop': '商店',
|
|
41
|
+
'pet.gameplay.skin': '皮肤',
|
|
42
|
+
'pet.gameplay.skinDefault': '默认',
|
|
41
43
|
'pet.gameplay.back': '返回',
|
|
42
44
|
'pet.gameplay.buy': '购买',
|
|
43
45
|
'pet.gameplay.insufficient': '{currency}不足',
|
|
@@ -64,7 +66,7 @@ export const zh = {
|
|
|
64
66
|
'settings.visible': '显示宠物',
|
|
65
67
|
'settings.visibleHint': '关闭后宠物隐藏,可从聊天输入区重新召唤。',
|
|
66
68
|
'settings.size': '大小(px)',
|
|
67
|
-
'settings.sizeHint': '精灵单元高度,范围 32–
|
|
69
|
+
'settings.sizeHint': '精灵单元高度,范围 32–1024。',
|
|
68
70
|
'settings.right': '距右侧(px)',
|
|
69
71
|
'settings.rightHint': '距视口右边缘的水平内缩距离。',
|
|
70
72
|
'settings.bottom': '距底部(px)',
|
|
@@ -119,6 +121,8 @@ export const en = {
|
|
|
119
121
|
'pet.gameplay.sleep': 'Sleep',
|
|
120
122
|
'pet.gameplay.wake': 'Wake up',
|
|
121
123
|
'pet.gameplay.shop': 'Shop',
|
|
124
|
+
'pet.gameplay.skin': 'Skin',
|
|
125
|
+
'pet.gameplay.skinDefault': 'Default',
|
|
122
126
|
'pet.gameplay.back': 'Back',
|
|
123
127
|
'pet.gameplay.buy': 'Buy',
|
|
124
128
|
'pet.gameplay.insufficient': 'Not enough {currency}',
|
|
@@ -145,7 +149,7 @@ export const en = {
|
|
|
145
149
|
'settings.visible': 'Show the pet',
|
|
146
150
|
'settings.visibleHint': 'When off, the pet hides; summon it again from the input row.',
|
|
147
151
|
'settings.size': 'Size (px)',
|
|
148
|
-
'settings.sizeHint': 'Sprite cell height, 32\
|
|
152
|
+
'settings.sizeHint': 'Sprite cell height, 32\u20131024.',
|
|
149
153
|
'settings.right': 'Right inset (px)',
|
|
150
154
|
'settings.rightHint': 'Horizontal inset from the viewport right edge.',
|
|
151
155
|
'settings.bottom': 'Bottom inset (px)',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Frames2dVisualMount.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/Frames2dVisualMount.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAInD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAEvC,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,UAAU,EAAE,aAAa,CAAA;IACzB,KAAK,EAAE,aAAa,CAAA;IACpB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gEAAgE;IAChE,IAAI,EAAE,UAAU,CAAA;IAChB,yEAAyE;IACzE,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;CAC/B,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"Frames2dVisualMount.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/Frames2dVisualMount.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAInD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAEvC,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,UAAU,EAAE,aAAa,CAAA;IACzB,KAAK,EAAE,aAAa,CAAA;IACpB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gEAAgE;IAChE,IAAI,EAAE,UAAU,CAAA;IAChB,yEAAyE;IACzE,GAAG,CAAC,EAAE,WAAW,CAAA;IACjB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;CAC/B,GAAG,YAAY,CAiFf"}
|
|
@@ -51,7 +51,11 @@ export function Frames2dVisualMount(props) {
|
|
|
51
51
|
if (props.bus !== undefined) {
|
|
52
52
|
const gameplayBus = props.bus;
|
|
53
53
|
gameplayBus.setTrack = (track) => { handleRef.current?.setState(track); };
|
|
54
|
-
|
|
54
|
+
gameplayBus.setIdleTrack = (track) => { handleRef.current?.setIdleTrack(track); };
|
|
55
|
+
cleanups.push(() => {
|
|
56
|
+
gameplayBus.setTrack = undefined;
|
|
57
|
+
gameplayBus.setIdleTrack = undefined;
|
|
58
|
+
});
|
|
55
59
|
}
|
|
56
60
|
// The drag gesture drives the conventional 'drag' track when declared.
|
|
57
61
|
// On release, a declared gameplay.dragEndState (miku: standup) plays
|
|
@@ -37,10 +37,35 @@ export interface PetFrames2dConfig {
|
|
|
37
37
|
phases: Partial<Record<ActivityPhase, string>> & {
|
|
38
38
|
idle: string;
|
|
39
39
|
};
|
|
40
|
+
/** Selectable skins; each swaps the base idle target while selected. */
|
|
41
|
+
skins?: Frames2dSkinConfig[];
|
|
42
|
+
}
|
|
43
|
+
/** One selectable skins entry as served inside the pet definition. */
|
|
44
|
+
export interface Frames2dSkinConfig {
|
|
45
|
+
id: string;
|
|
46
|
+
label: string;
|
|
47
|
+
/** A declared looping track that becomes the base idle while selected. */
|
|
48
|
+
idleTrack: string;
|
|
49
|
+
/** Click actions exclusive to this skin (roll by probability; miss → touch zones). */
|
|
50
|
+
clickActions?: Frames2dSkinClickActionConfig[];
|
|
51
|
+
/** Gameplay-state overrides (state -> track) swapped in while selected. */
|
|
52
|
+
gameplayTracks?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
/** One probability-rolled tap action a skin may declare. */
|
|
55
|
+
export interface Frames2dSkinClickActionConfig {
|
|
56
|
+
track: string;
|
|
57
|
+
probability: number;
|
|
58
|
+
phrases?: string[];
|
|
40
59
|
}
|
|
41
60
|
export interface Frames2dRendererHandle extends PetRendererHandle {
|
|
42
61
|
/** Force a track id (gameplay override); undefined returns to phase mapping. */
|
|
43
62
|
setState(track: string | undefined): void;
|
|
63
|
+
/**
|
|
64
|
+
* Swap the base idle target (idle phase, unmapped phases and every
|
|
65
|
+
* fallback back to idle) to a declared looping track — a skin. undefined
|
|
66
|
+
* restores the manifest idle track.
|
|
67
|
+
*/
|
|
68
|
+
setIdleTrack(track: string | undefined): void;
|
|
44
69
|
/** The track currently playing (diagnostics and tests). */
|
|
45
70
|
currentTrack(): string;
|
|
46
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frames2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/frames2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,WAAW,EAA2B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACzI,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"frames2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/frames2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,WAAW,EAA2B,KAAK,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACzI,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAC3C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE,wEAAwE;IACxE,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAA;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAA;IACjB,sFAAsF;IACtF,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAA;IAC9C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACzC;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IAC7C,2DAA2D;IAC3D,YAAY,IAAI,MAAM,CAAA;CACvB;AAuFD,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,iBAAiB,CA2Q3D,CAAA"}
|