@linxin666/dsh-pet 0.3.3 → 0.3.5
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 +30 -13
- package/README.zh.md +30 -13
- package/assets/ouo-neko/pet.json +31 -0
- package/assets/ouo-neko/previews/failed.gif +0 -0
- package/assets/ouo-neko/previews/idle.gif +0 -0
- package/assets/ouo-neko/previews/jumping.gif +0 -0
- package/assets/ouo-neko/previews/review.gif +0 -0
- package/assets/ouo-neko/previews/running-left.gif +0 -0
- package/assets/ouo-neko/previews/running-right.gif +0 -0
- package/assets/ouo-neko/previews/running.gif +0 -0
- package/assets/ouo-neko/previews/waiting.gif +0 -0
- package/assets/ouo-neko/previews/waving.gif +0 -0
- package/assets/ouo-neko/spritesheet.webp +0 -0
- package/contracts/pet-manifest-v2.schema.json +102 -1
- package/contracts/voice-pack-v1.schema.json +23 -28
- package/lib/client.js +781 -40
- package/lib/client.js.map +1 -1
- package/lib/index.js +1567 -482
- package/lib/types/chatter.d.ts +68 -46
- package/lib/types/chatter.d.ts.map +1 -1
- package/lib/types/chatter.js +243 -301
- package/lib/types/client/PetDockEntry.d.ts +3 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +18 -2
- package/lib/types/client/PetSprite.d.ts +23 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +49 -45
- package/lib/types/client/PluginSettingsCard.d.ts +2 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +2 -2
- package/lib/types/client/drag-stream.d.ts +19 -0
- package/lib/types/client/drag-stream.d.ts.map +1 -0
- package/lib/types/client/drag-stream.js +26 -0
- package/lib/types/client/gameplay-hud.d.ts +50 -0
- package/lib/types/client/gameplay-hud.d.ts.map +1 -0
- package/lib/types/client/gameplay-hud.js +274 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +38 -9
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -0
- package/lib/types/client/pet-store.d.ts +7 -1
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +4 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts +29 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -0
- package/lib/types/client/renderers/Frames2dVisualMount.js +83 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts +6 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -1
- package/lib/types/client/renderers/PetRendererSwitch.js +14 -1
- package/lib/types/client/renderers/frames2d.d.ts +36 -0
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -0
- package/lib/types/client/renderers/frames2d.js +196 -0
- package/lib/types/defaults.d.ts +6 -0
- package/lib/types/defaults.d.ts.map +1 -0
- package/lib/types/defaults.js +5 -0
- package/lib/types/dsh-home.d.ts.map +1 -1
- package/lib/types/dsh-home.js +9 -3
- package/lib/types/event-projection.d.ts +9 -4
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +46 -14
- package/lib/types/gameplay.d.ts +182 -0
- package/lib/types/gameplay.d.ts.map +1 -0
- package/lib/types/gameplay.js +618 -0
- package/lib/types/ledger.d.ts +14 -0
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +30 -0
- package/lib/types/manifest-v2.d.ts +38 -1
- package/lib/types/manifest-v2.d.ts.map +1 -1
- package/lib/types/manifest-v2.js +182 -2
- package/lib/types/persist.d.ts +4 -4
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +41 -4
- package/lib/types/registry.d.ts +29 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +170 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +30 -3
- package/lib/types/service.d.ts +63 -11
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +208 -19
- package/lib/types/voice-pack.d.ts +6 -7
- package/lib/types/voice-pack.d.ts.map +1 -1
- package/lib/types/voice-pack.js +44 -47
- package/package.json +5 -2
- package/src/chatter.test.ts +68 -38
- package/src/chatter.ts +269 -316
- package/src/client/PetDockEntry.test.tsx +6 -0
- package/src/client/PetDockEntry.tsx +32 -1
- package/src/client/PetSprite.test.tsx +68 -21
- package/src/client/PetSprite.tsx +63 -45
- package/src/client/PluginSettingsCard.tsx +4 -2
- package/src/client/drag-stream.ts +35 -0
- package/src/client/gameplay-hud.test.tsx +338 -0
- package/src/client/gameplay-hud.tsx +403 -0
- package/src/client/index.test.tsx +10 -1
- package/src/client/index.ts +46 -11
- package/src/client/locales.ts +36 -0
- package/src/client/pet-store.ts +10 -1
- package/src/client/pet.module.css +192 -29
- package/src/client/renderers/Frames2dVisualMount.tsx +111 -0
- package/src/client/renderers/PetRendererSwitch.tsx +28 -1
- package/src/client/renderers/frames2d.test.ts +137 -0
- package/src/client/renderers/frames2d.ts +216 -0
- package/src/client/settings-card.module.css +5 -0
- package/src/defaults.ts +7 -0
- package/src/dsh-home.ts +9 -3
- package/src/event-projection.ts +57 -16
- package/src/gameplay.test.ts +239 -0
- package/src/gameplay.ts +692 -0
- package/src/ledger.test.ts +16 -0
- package/src/ledger.ts +31 -0
- package/src/manifest-v2.test.ts +130 -1
- package/src/manifest-v2.ts +181 -2
- package/src/persist.test.ts +3 -0
- package/src/persist.ts +43 -6
- package/src/registry.test.ts +168 -1
- package/src/registry.ts +199 -1
- package/src/routes.ts +28 -4
- package/src/service.ts +240 -29
- package/src/voice-pack.test.ts +48 -25
- package/src/voice-pack.ts +50 -47
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Gameplay HUD — the client half of the manifest 'gameplay' block (miku-pet
|
|
4
|
+
* generalization). One component owns everything the block needs: the stat
|
|
5
|
+
* bars and shop card (menu / shop pages), the touch-zone tap
|
|
6
|
+
* handling, the idle director rolls, the work and sleep loops, and the
|
|
7
|
+
* float-text toasts. It talks to the host through the injected verb API,
|
|
8
|
+
* writes results straight back into the store (the 2 s poll stays the
|
|
9
|
+
* backstop), and steers the frames2d renderer through the per-pet bus.
|
|
10
|
+
* @module @linxin666/dsh-pet/client/GameplayHud
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
13
|
+
import { touchZoneAt } from "../gameplay.js";
|
|
14
|
+
import styles from './pet.module.css';
|
|
15
|
+
let floatSeq = 0;
|
|
16
|
+
/** The gameplay overlay for one frames2d pet that declares 'gameplay'. */
|
|
17
|
+
export function GameplayHud(props) {
|
|
18
|
+
const { definition, store, api, bus } = props;
|
|
19
|
+
const ui = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
20
|
+
const def = definition.gameplay;
|
|
21
|
+
const view = ui.snapshot?.gameplay;
|
|
22
|
+
const phase = ui.snapshot?.phase ?? 'idle';
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
const [page, setPage] = useState('root');
|
|
25
|
+
const hudRef = useRef(null);
|
|
26
|
+
const cardRef = useRef(null);
|
|
27
|
+
const [floats, setFloats] = useState([]);
|
|
28
|
+
// Mutable driver state (refs so intervals never re-arm on a poll tick).
|
|
29
|
+
const modeRef = useRef(view?.mode ?? null);
|
|
30
|
+
modeRef.current = view?.mode ?? null;
|
|
31
|
+
const phaseRef = useRef(phase);
|
|
32
|
+
phaseRef.current = phase;
|
|
33
|
+
const draggingRef = useRef(false);
|
|
34
|
+
const touchLockUntilRef = useRef(0);
|
|
35
|
+
const missRef = useRef(0);
|
|
36
|
+
const busyRef = useRef(false);
|
|
37
|
+
// Dynamic-key lookups (stat ids / currency ids are manifest data).
|
|
38
|
+
const tr = props.t;
|
|
39
|
+
const statLabel = (name) => tr('pet.gameplay.stat.' + name);
|
|
40
|
+
const currencyLabel = (name) => tr('pet.gameplay.currency.' + name);
|
|
41
|
+
const pushFloat = (text) => {
|
|
42
|
+
const id = ++floatSeq;
|
|
43
|
+
setFloats(list => [...list.slice(-3), { id, text }]);
|
|
44
|
+
window.setTimeout(() => {
|
|
45
|
+
setFloats(list => list.filter(entry => entry.id !== id));
|
|
46
|
+
}, 1100);
|
|
47
|
+
};
|
|
48
|
+
const applyResult = (result) => {
|
|
49
|
+
if (result.view !== undefined)
|
|
50
|
+
store.actions.setGameplayView(result.view);
|
|
51
|
+
};
|
|
52
|
+
// Tap handling (registered on the bus; PetSprite reports sprite-box
|
|
53
|
+
// fractions). Sleep wakes on tap; work blocks taps; a held touch
|
|
54
|
+
// animation turns taps into the plain-click boost.
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (def === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
bus.tap = (fx, fy) => {
|
|
59
|
+
if (modeRef.current === 'sleep') {
|
|
60
|
+
void api.setMode(null).then(applyResult, () => undefined);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (modeRef.current === 'work')
|
|
64
|
+
return;
|
|
65
|
+
const box = def.hitBox ?? { x0: 0, y0: 0, x1: 1, y1: 1 };
|
|
66
|
+
const hx = (fx - box.x0) / (box.x1 - box.x0);
|
|
67
|
+
const hy = (fy - box.y0) / (box.y1 - box.y0);
|
|
68
|
+
if (hx < 0 || hx > 1 || hy < 0 || hy > 1)
|
|
69
|
+
return;
|
|
70
|
+
if (Date.now() < touchLockUntilRef.current) {
|
|
71
|
+
void api.touch().then(applyResult, () => undefined);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const zone = def.touch === undefined ? undefined : touchZoneAt(def.touch, hy);
|
|
75
|
+
if (zone === undefined)
|
|
76
|
+
return;
|
|
77
|
+
void api.touch(zone.name).then((result) => {
|
|
78
|
+
applyResult(result);
|
|
79
|
+
if (result.hit !== true)
|
|
80
|
+
return;
|
|
81
|
+
if (result.state !== undefined) {
|
|
82
|
+
bus.setTrack?.(result.state);
|
|
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
|
+
}
|
|
90
|
+
if (result.phrase !== undefined) {
|
|
91
|
+
store.actions.setFeedback({ text: result.phrase, kind: 'none', at: Date.now() });
|
|
92
|
+
}
|
|
93
|
+
}, () => undefined);
|
|
94
|
+
};
|
|
95
|
+
return () => { bus.tap = undefined; };
|
|
96
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one registration per pet definition
|
|
97
|
+
}, [definition.id, def]);
|
|
98
|
+
// Panel entry (chrome -> HUD): the hover panel's 玩法 button drives the
|
|
99
|
+
// card through this channel so the chrome never needs the card's state.
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
bus.openCard = (next) => {
|
|
102
|
+
setOpen(prev => next ?? !prev);
|
|
103
|
+
setPage('root');
|
|
104
|
+
};
|
|
105
|
+
return () => { bus.openCard = undefined; };
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one registration per bus
|
|
107
|
+
}, [bus]);
|
|
108
|
+
// Card placement: the card must not enter the bubble band above the sprite
|
|
109
|
+
// or the hover-panel band below it, so it opens beside the sprite instead
|
|
110
|
+
// of growing upward from the pet's feet. It is vertically centered on the
|
|
111
|
+
// sprite box, clamped to the sprite's height, and the side is chosen by
|
|
112
|
+
// the space available to each side (right first; a pet parked near the
|
|
113
|
+
// right viewport edge flips the card to the left).
|
|
114
|
+
useLayoutEffect(() => {
|
|
115
|
+
if (!open)
|
|
116
|
+
return undefined;
|
|
117
|
+
const hud = hudRef.current;
|
|
118
|
+
const card = cardRef.current;
|
|
119
|
+
if (hud === null || card === null)
|
|
120
|
+
return undefined;
|
|
121
|
+
const place = () => {
|
|
122
|
+
const box = hud.parentElement?.getBoundingClientRect();
|
|
123
|
+
if (box === undefined)
|
|
124
|
+
return;
|
|
125
|
+
const gap = 8;
|
|
126
|
+
const width = card.getBoundingClientRect().width;
|
|
127
|
+
const toRight = window.innerWidth - box.right;
|
|
128
|
+
const x = toRight >= width + gap ? box.width + gap : -(width + gap);
|
|
129
|
+
card.style.transform = 'translate(' + Math.round(x) + 'px, ' + Math.round(-box.height / 2) + 'px) translateY(50%)';
|
|
130
|
+
card.style.maxHeight = Math.round(box.height) + 'px';
|
|
131
|
+
};
|
|
132
|
+
place();
|
|
133
|
+
window.addEventListener('resize', place);
|
|
134
|
+
return () => { window.removeEventListener('resize', place); };
|
|
135
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one placement per open/page change
|
|
136
|
+
}, [open, page]);
|
|
137
|
+
// Drag gestures wake a sleeping pet (miku behavior); the drag stream also
|
|
138
|
+
// feeds the idle director's suppression check.
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
return props.drag.subscribe((dragging) => {
|
|
141
|
+
draggingRef.current = dragging;
|
|
142
|
+
if (dragging && modeRef.current === 'sleep') {
|
|
143
|
+
void api.setMode(null).then(applyResult, () => undefined);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one subscription per drag stream
|
|
147
|
+
}, [props.drag]);
|
|
148
|
+
// Idle director: weighted rolls between staying idle and playing an act.
|
|
149
|
+
// Runs only while the phase mapping owns the visual (idle phase, no mode,
|
|
150
|
+
// no drag, no held touch animation); maxMiss forces an act after too many
|
|
151
|
+
// idle rolls in a row.
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const director = def?.idleDirector;
|
|
154
|
+
if (def === undefined || director === undefined)
|
|
155
|
+
return undefined;
|
|
156
|
+
const total = director.idleWeight + director.acts.reduce((sum, act) => sum + act.weight, 0);
|
|
157
|
+
if (total <= 0)
|
|
158
|
+
return undefined;
|
|
159
|
+
const timer = window.setInterval(() => {
|
|
160
|
+
if (phaseRef.current !== 'idle')
|
|
161
|
+
return;
|
|
162
|
+
if (modeRef.current !== null || draggingRef.current)
|
|
163
|
+
return;
|
|
164
|
+
if (Date.now() < touchLockUntilRef.current)
|
|
165
|
+
return;
|
|
166
|
+
let pickedAct;
|
|
167
|
+
if (missRef.current >= director.maxMiss) {
|
|
168
|
+
// Forced act: pick among the acts only.
|
|
169
|
+
const actTotal = director.acts.reduce((sum, act) => sum + act.weight, 0);
|
|
170
|
+
let actRoll = Math.random() * actTotal;
|
|
171
|
+
for (const act of director.acts) {
|
|
172
|
+
actRoll -= act.weight;
|
|
173
|
+
if (actRoll < 0) {
|
|
174
|
+
pickedAct = act;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
let roll = Math.random() * total;
|
|
181
|
+
for (const act of director.acts) {
|
|
182
|
+
roll -= act.weight;
|
|
183
|
+
if (roll < 0) {
|
|
184
|
+
pickedAct = act;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (pickedAct === undefined) {
|
|
190
|
+
missRef.current += 1;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
missRef.current = 0;
|
|
194
|
+
bus.setTrack?.(pickedAct.track);
|
|
195
|
+
// Acts with a phrase pool speak one line while they play (miku parity).
|
|
196
|
+
if (pickedAct.phrases !== undefined && pickedAct.phrases.length > 0) {
|
|
197
|
+
const phrase = pickedAct.phrases[Math.floor(Math.random() * pickedAct.phrases.length)];
|
|
198
|
+
store.actions.setFeedback({ text: phrase, kind: 'none', at: Date.now() });
|
|
199
|
+
}
|
|
200
|
+
}, director.intervalMs);
|
|
201
|
+
return () => window.clearInterval(timer);
|
|
202
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one director per pet definition
|
|
203
|
+
}, [definition.id, def]);
|
|
204
|
+
// Work loop: hold the work track, adjudicate one round per tick, play the
|
|
205
|
+
// result track for its hold window, then resume. Leaving the mode
|
|
206
|
+
// releases the override so the phase mapping takes over.
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const work = def?.work;
|
|
209
|
+
if (def === undefined || work === undefined || view?.mode !== 'work')
|
|
210
|
+
return undefined;
|
|
211
|
+
bus.setTrack?.(work.state);
|
|
212
|
+
let resultTimer = 0;
|
|
213
|
+
const timer = window.setInterval(() => {
|
|
214
|
+
if (busyRef.current)
|
|
215
|
+
return;
|
|
216
|
+
busyRef.current = true;
|
|
217
|
+
void api.workTick().then((result) => {
|
|
218
|
+
busyRef.current = false;
|
|
219
|
+
applyResult(result);
|
|
220
|
+
if (result.ok !== true || result.outcome === undefined)
|
|
221
|
+
return;
|
|
222
|
+
const resultTrack = result.outcome === 'success' ? work.successState : work.failState;
|
|
223
|
+
const hold = result.outcome === 'success' ? work.resultMs?.success ?? 1300 : work.resultMs?.fail ?? 1900;
|
|
224
|
+
bus.setTrack?.(resultTrack);
|
|
225
|
+
resultTimer = window.setTimeout(() => {
|
|
226
|
+
if (modeRef.current === 'work')
|
|
227
|
+
bus.setTrack?.(work.state);
|
|
228
|
+
}, hold);
|
|
229
|
+
}, () => { busyRef.current = false; });
|
|
230
|
+
}, work.tickMs);
|
|
231
|
+
return () => {
|
|
232
|
+
window.clearInterval(timer);
|
|
233
|
+
window.clearTimeout(resultTimer);
|
|
234
|
+
bus.setTrack?.(undefined);
|
|
235
|
+
};
|
|
236
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode value
|
|
237
|
+
}, [definition.id, def, view?.mode]);
|
|
238
|
+
// Sleep loop: hold the sleep track; restore is host-side (lazy settle).
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const sleep = def?.sleep;
|
|
241
|
+
if (def === undefined || sleep === undefined || view?.mode !== 'sleep')
|
|
242
|
+
return undefined;
|
|
243
|
+
bus.setTrack?.(sleep.state);
|
|
244
|
+
return () => bus.setTrack?.(undefined);
|
|
245
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- the loop keys on the mode value
|
|
246
|
+
}, [definition.id, def, view?.mode]);
|
|
247
|
+
if (def === undefined || view === undefined)
|
|
248
|
+
return null;
|
|
249
|
+
const mode = view.mode;
|
|
250
|
+
const stats = def.stats ?? {};
|
|
251
|
+
const shop = def.shop;
|
|
252
|
+
const buy = (itemId) => {
|
|
253
|
+
void api.buy(itemId).then((result) => {
|
|
254
|
+
applyResult(result);
|
|
255
|
+
if (result.ok !== true) {
|
|
256
|
+
if (result.error === 'insufficient-funds') {
|
|
257
|
+
const item = shop?.items.find(entry => entry.id === itemId);
|
|
258
|
+
pushFloat(tr('pet.gameplay.insufficient', { currency: currencyLabel(item?.currency ?? 'treats') }));
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (result.prize !== undefined) {
|
|
263
|
+
pushFloat(tr('pet.gameplay.prize', { amount: result.prize.amount, currency: currencyLabel(result.prize.currency) }));
|
|
264
|
+
}
|
|
265
|
+
}, () => undefined);
|
|
266
|
+
};
|
|
267
|
+
const setMode = (next) => {
|
|
268
|
+
void api.setMode(next).then(applyResult, () => undefined);
|
|
269
|
+
};
|
|
270
|
+
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
|
+
const value = view.stats[name] ?? 0;
|
|
272
|
+
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.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') })), 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') }))] })] })), 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') }) })] })), _jsx("button", { type: "button", className: styles.gameplayClose, "aria-label": tr('pet.gameplay.back'), onClick: () => setOpen(false), children: "\u00D7" })] }))] }));
|
|
274
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAwB,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AA4EnI,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;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAgT9C"}
|
|
@@ -16,6 +16,7 @@ import { createPetStore } from "./pet-store.js";
|
|
|
16
16
|
import { PetDockEntry } from "./PetDockEntry.js";
|
|
17
17
|
import { defaultPetRendererRegistry } from "./renderers/registry.js";
|
|
18
18
|
import { live2dRenderer } from "./renderers/live2d.js";
|
|
19
|
+
import { frames2dRenderer } from "./renderers/frames2d.js";
|
|
19
20
|
import { registerPetUiTeardown, takeoverPetUiTeardown } from "./ui-teardown.js";
|
|
20
21
|
import { PetSettingsSection, PetSettingsCardController } from "./PetSettingsCard.js";
|
|
21
22
|
import { NS, en, zh, t } from "./locales.js";
|
|
@@ -36,13 +37,18 @@ async function petFetch(path, body) {
|
|
|
36
37
|
}
|
|
37
38
|
/** The live host API instance (always defined; failures surface per call). */
|
|
38
39
|
const petApi = {
|
|
39
|
-
state: () => petFetch('/api/pet/state'
|
|
40
|
+
state: (currentSessionId) => petFetch('/api/pet/state'
|
|
41
|
+
+ (currentSessionId === undefined ? '' : '?current=' + encodeURIComponent(currentSessionId))),
|
|
40
42
|
pets: () => petFetch('/api/pet/pets'),
|
|
41
43
|
interact: (kind) => petFetch('/api/pet/interact', { kind }),
|
|
42
44
|
setVisible: (visible) => petFetch('/api/pet/set-visible', { visible }),
|
|
43
45
|
setConfig: (patch) => petFetch('/api/pet/set-config', patch),
|
|
44
46
|
setName: (name) => petFetch('/api/pet/set-name', { name }),
|
|
45
47
|
setPet: (petId) => petFetch('/api/pet/set-pet', { petId }),
|
|
48
|
+
gameplayTouch: (zone) => petFetch('/api/pet/gameplay/touch', zone === undefined ? {} : { zone }),
|
|
49
|
+
gameplaySetMode: (mode) => petFetch('/api/pet/gameplay/mode', { mode }),
|
|
50
|
+
gameplayWorkTick: () => petFetch('/api/pet/gameplay/work-tick', {}),
|
|
51
|
+
gameplayBuy: (item) => petFetch('/api/pet/gameplay/buy', { item }),
|
|
46
52
|
};
|
|
47
53
|
/** Poll interval for the host snapshot. */
|
|
48
54
|
const POLL_MS = 2000;
|
|
@@ -71,6 +77,7 @@ export function apply(ctx) {
|
|
|
71
77
|
// Built-in renderers dispatch through the plugin-wide registry (pet-center
|
|
72
78
|
// M3). Registration is idempotent (id wins), so re-applies stay clean.
|
|
73
79
|
defaultPetRendererRegistry.register(live2dRenderer);
|
|
80
|
+
defaultPetRendererRegistry.register(frames2dRenderer);
|
|
74
81
|
const binder = ctx.get('webUiSettings') ?? ctx.settingsScope;
|
|
75
82
|
const settingsScope = binder.bind({ namespace: PET_SETTINGS_NS });
|
|
76
83
|
const enabled = () => {
|
|
@@ -136,6 +143,19 @@ export function apply(ctx) {
|
|
|
136
143
|
const setPets = petStore.actions.setPets;
|
|
137
144
|
const setState = petStore.actions.setState;
|
|
138
145
|
const setFeedback = petStore.actions.setFeedback;
|
|
146
|
+
// Clicking a session bubble jumps the GUI to that session; the same
|
|
147
|
+
// sessions face reports which session the user is currently on, so the
|
|
148
|
+
// host can lead the bubble stack with it. A bubble can outlive its
|
|
149
|
+
// disposed session by one poll tick, and the sessions service fails
|
|
150
|
+
// loud on unknown ids, so consult the live list first. The pet's type
|
|
151
|
+
// program also loads the host-side dsh-session package through the
|
|
152
|
+
// service types, whose Context merge declares a different 'sessions'
|
|
153
|
+
// face; pin the browser runtime's outward face here.
|
|
154
|
+
const sessions = ctx.sessions;
|
|
155
|
+
const currentSessionId = () => {
|
|
156
|
+
const current = sessions.list.getSnapshot().current;
|
|
157
|
+
return current === undefined ? undefined : String(current);
|
|
158
|
+
};
|
|
139
159
|
// The registry list is fetched lazily with retries baked into the poll
|
|
140
160
|
// cycle: until it lands, the dock entry renders nothing and every 2s
|
|
141
161
|
// tick tries again. After it lands, one list feeds both the sprite and
|
|
@@ -157,7 +177,7 @@ export function apply(ctx) {
|
|
|
157
177
|
}
|
|
158
178
|
const seq = stateSeq + 1;
|
|
159
179
|
stateSeq = seq;
|
|
160
|
-
petApi.state().then((snapshot) => {
|
|
180
|
+
petApi.state(currentSessionId()).then((snapshot) => {
|
|
161
181
|
if (seq !== stateSeq)
|
|
162
182
|
return;
|
|
163
183
|
setSnapshot(snapshot);
|
|
@@ -201,13 +221,16 @@ export function apply(ctx) {
|
|
|
201
221
|
document.removeEventListener('visibilitychange', onVisibility);
|
|
202
222
|
};
|
|
203
223
|
}, 'pet: poll');
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
// A current-session switch should re-order the bubble stack right away,
|
|
225
|
+
// not on the next 2s tick. Poll only while the tab is visible, as the
|
|
226
|
+
// poll loop itself does.
|
|
227
|
+
const disposeSessionWatch = ctx.effect(() => {
|
|
228
|
+
const unsubscribe = sessions.list.subscribe(() => {
|
|
229
|
+
if (document.visibilityState === 'visible')
|
|
230
|
+
pollNow();
|
|
231
|
+
});
|
|
232
|
+
return unsubscribe;
|
|
233
|
+
}, 'pet: current-session watch');
|
|
211
234
|
const openSession = (sessionId) => {
|
|
212
235
|
const list = sessions.list.getSnapshot();
|
|
213
236
|
if (list.byId[sessionId] === undefined)
|
|
@@ -272,6 +295,12 @@ export function apply(ctx) {
|
|
|
272
295
|
feedbackDone: () => {
|
|
273
296
|
setFeedback(null);
|
|
274
297
|
},
|
|
298
|
+
gameplay: {
|
|
299
|
+
touch: (zone) => petApi.gameplayTouch(zone),
|
|
300
|
+
setMode: (mode) => petApi.gameplaySetMode(mode),
|
|
301
|
+
workTick: () => petApi.gameplayWorkTick(),
|
|
302
|
+
buy: (item) => petApi.gameplayBuy(item),
|
|
303
|
+
},
|
|
275
304
|
});
|
|
276
305
|
// The pet is host-global (its state/display/interactions have no session
|
|
277
306
|
// dimension), and the official rc.6 shell declares no root-scoped slot
|
|
@@ -22,6 +22,23 @@ export declare const zh: {
|
|
|
22
22
|
readonly 'pet.live2d.vendor-missing': "Live2D 组件缺失,请升级宠物插件。";
|
|
23
23
|
readonly 'pet.live2d.load-failed': "Live2D 模型加载失败,请检查该宠物目录的完整性。";
|
|
24
24
|
readonly 'pet.openSessionHint': "点击跳转到对应会话";
|
|
25
|
+
readonly 'pet.gameplay.menu': "玩法";
|
|
26
|
+
readonly 'pet.gameplay.work': "打工";
|
|
27
|
+
readonly 'pet.gameplay.stopWork': "收工";
|
|
28
|
+
readonly 'pet.gameplay.sleep': "睡觉";
|
|
29
|
+
readonly 'pet.gameplay.wake': "起床";
|
|
30
|
+
readonly 'pet.gameplay.shop': "商店";
|
|
31
|
+
readonly 'pet.gameplay.back': "返回";
|
|
32
|
+
readonly 'pet.gameplay.buy': "购买";
|
|
33
|
+
readonly 'pet.gameplay.insufficient': "{currency}不足";
|
|
34
|
+
readonly 'pet.gameplay.prize': "中奖 +{amount} {currency}";
|
|
35
|
+
readonly 'pet.gameplay.working': "打工中";
|
|
36
|
+
readonly 'pet.gameplay.sleeping': "睡觉中";
|
|
37
|
+
readonly 'pet.gameplay.stat.hunger': "饱食";
|
|
38
|
+
readonly 'pet.gameplay.stat.mood': "心情";
|
|
39
|
+
readonly 'pet.gameplay.stat.energy': "精力";
|
|
40
|
+
readonly 'pet.gameplay.stat.affection': "好感";
|
|
41
|
+
readonly 'pet.gameplay.currency.treats': "小鱼干";
|
|
25
42
|
readonly 'pet.moreSessions': "展开其余 {n} 个会话的气泡";
|
|
26
43
|
readonly 'pet.collapseSessions': "收起会话气泡";
|
|
27
44
|
readonly 'settings.title': "宠物";
|
|
@@ -75,6 +92,23 @@ export declare const en: {
|
|
|
75
92
|
readonly 'pet.live2d.vendor-missing': "The Live2D component is missing; please update the pet plugin.";
|
|
76
93
|
readonly 'pet.live2d.load-failed': "The Live2D model failed to load; check the pet directory is complete.";
|
|
77
94
|
readonly 'pet.openSessionHint': "Click to jump to this session";
|
|
95
|
+
readonly 'pet.gameplay.menu': "Play";
|
|
96
|
+
readonly 'pet.gameplay.work': "Work";
|
|
97
|
+
readonly 'pet.gameplay.stopWork': "Stop work";
|
|
98
|
+
readonly 'pet.gameplay.sleep': "Sleep";
|
|
99
|
+
readonly 'pet.gameplay.wake': "Wake up";
|
|
100
|
+
readonly 'pet.gameplay.shop': "Shop";
|
|
101
|
+
readonly 'pet.gameplay.back': "Back";
|
|
102
|
+
readonly 'pet.gameplay.buy': "Buy";
|
|
103
|
+
readonly 'pet.gameplay.insufficient': "Not enough {currency}";
|
|
104
|
+
readonly 'pet.gameplay.prize': "Prize +{amount} {currency}";
|
|
105
|
+
readonly 'pet.gameplay.working': "Working";
|
|
106
|
+
readonly 'pet.gameplay.sleeping': "Sleeping";
|
|
107
|
+
readonly 'pet.gameplay.stat.hunger': "Hunger";
|
|
108
|
+
readonly 'pet.gameplay.stat.mood': "Mood";
|
|
109
|
+
readonly 'pet.gameplay.stat.energy': "Energy";
|
|
110
|
+
readonly 'pet.gameplay.stat.affection': "Affection";
|
|
111
|
+
readonly 'pet.gameplay.currency.treats': "Treats";
|
|
78
112
|
readonly 'pet.moreSessions': "Expand {n} more session bubbles";
|
|
79
113
|
readonly 'pet.collapseSessions': "Collapse session bubbles";
|
|
80
114
|
readonly 'settings.title': "Pet";
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsEL,CAAA;AAEV,oBAAoB;AACpB,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsEL,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"}
|
|
@@ -22,6 +22,24 @@ export const zh = {
|
|
|
22
22
|
'pet.live2d.vendor-missing': 'Live2D 组件缺失,请升级宠物插件。',
|
|
23
23
|
'pet.live2d.load-failed': 'Live2D 模型加载失败,请检查该宠物目录的完整性。',
|
|
24
24
|
'pet.openSessionHint': '点击跳转到对应会话',
|
|
25
|
+
// 玩法 HUD(miku-pet 泛化:属性条 / 打工 / 睡觉 / 商店;统一小鱼干经济)。
|
|
26
|
+
'pet.gameplay.menu': '玩法',
|
|
27
|
+
'pet.gameplay.work': '打工',
|
|
28
|
+
'pet.gameplay.stopWork': '收工',
|
|
29
|
+
'pet.gameplay.sleep': '睡觉',
|
|
30
|
+
'pet.gameplay.wake': '起床',
|
|
31
|
+
'pet.gameplay.shop': '商店',
|
|
32
|
+
'pet.gameplay.back': '返回',
|
|
33
|
+
'pet.gameplay.buy': '购买',
|
|
34
|
+
'pet.gameplay.insufficient': '{currency}不足',
|
|
35
|
+
'pet.gameplay.prize': '中奖 +{amount} {currency}',
|
|
36
|
+
'pet.gameplay.working': '打工中',
|
|
37
|
+
'pet.gameplay.sleeping': '睡觉中',
|
|
38
|
+
'pet.gameplay.stat.hunger': '饱食',
|
|
39
|
+
'pet.gameplay.stat.mood': '心情',
|
|
40
|
+
'pet.gameplay.stat.energy': '精力',
|
|
41
|
+
'pet.gameplay.stat.affection': '好感',
|
|
42
|
+
'pet.gameplay.currency.treats': '小鱼干',
|
|
25
43
|
'pet.moreSessions': '展开其余 {n} 个会话的气泡',
|
|
26
44
|
'pet.collapseSessions': '收起会话气泡',
|
|
27
45
|
// 一级设置页(settings.section 席位)。
|
|
@@ -76,6 +94,24 @@ export const en = {
|
|
|
76
94
|
'pet.live2d.vendor-missing': 'The Live2D component is missing; please update the pet plugin.',
|
|
77
95
|
'pet.live2d.load-failed': 'The Live2D model failed to load; check the pet directory is complete.',
|
|
78
96
|
'pet.openSessionHint': 'Click to jump to this session',
|
|
97
|
+
// Gameplay HUD (miku-pet generalization: stat bars / work / sleep / shop; unified treats economy).
|
|
98
|
+
'pet.gameplay.menu': 'Play',
|
|
99
|
+
'pet.gameplay.work': 'Work',
|
|
100
|
+
'pet.gameplay.stopWork': 'Stop work',
|
|
101
|
+
'pet.gameplay.sleep': 'Sleep',
|
|
102
|
+
'pet.gameplay.wake': 'Wake up',
|
|
103
|
+
'pet.gameplay.shop': 'Shop',
|
|
104
|
+
'pet.gameplay.back': 'Back',
|
|
105
|
+
'pet.gameplay.buy': 'Buy',
|
|
106
|
+
'pet.gameplay.insufficient': 'Not enough {currency}',
|
|
107
|
+
'pet.gameplay.prize': 'Prize +{amount} {currency}',
|
|
108
|
+
'pet.gameplay.working': 'Working',
|
|
109
|
+
'pet.gameplay.sleeping': 'Sleeping',
|
|
110
|
+
'pet.gameplay.stat.hunger': 'Hunger',
|
|
111
|
+
'pet.gameplay.stat.mood': 'Mood',
|
|
112
|
+
'pet.gameplay.stat.energy': 'Energy',
|
|
113
|
+
'pet.gameplay.stat.affection': 'Affection',
|
|
114
|
+
'pet.gameplay.currency.treats': 'Treats',
|
|
79
115
|
'pet.moreSessions': 'Expand {n} more session bubbles',
|
|
80
116
|
'pet.collapseSessions': 'Collapse session bubbles',
|
|
81
117
|
// First-level settings section (the `settings.section` seat).
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module @linxin666/dsh-pet/client/pet-store
|
|
7
7
|
*/
|
|
8
8
|
import type { EngineStoreHandle, EngineStoreInstance } from '@deepseek-ai/dsh-client-runtime/client';
|
|
9
|
-
import type { PetStateView } from '../service.ts';
|
|
9
|
+
import type { PetGameplayStateView, PetStateView } from '../service.ts';
|
|
10
10
|
import type { PetInteraction } from '../affinity.ts';
|
|
11
11
|
import type { PetDefinition } from '../registry.ts';
|
|
12
12
|
/** One transient reaction bubble on the pet. */
|
|
@@ -41,6 +41,12 @@ export type PetUiActions = {
|
|
|
41
41
|
setState: (draft: PetUiState, state: PetUiState['state'], error: string | null) => void;
|
|
42
42
|
/** Show a reaction bubble. */
|
|
43
43
|
setFeedback: (draft: PetUiState, feedback: PetFeedback | null) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Patch the gameplay slice of the snapshot (verb write-back), so the HUD
|
|
46
|
+
* reflects a touch/mode/buy result immediately instead of waiting one
|
|
47
|
+
* poll tick (2 s).
|
|
48
|
+
*/
|
|
49
|
+
setGameplayView: (draft: PetUiState, view: PetGameplayStateView) => void;
|
|
44
50
|
};
|
|
45
51
|
/** Create the pet store handle (apply world only; never module-level). */
|
|
46
52
|
export declare function createPetStore(): EngineStoreHandle<PetUiState, PetUiActions>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pet-store.d.ts","sourceRoot":"","sources":["../../../src/client/pet-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"pet-store.d.ts","sourceRoot":"","sources":["../../../src/client/pet-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AACpG,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,IAAI,EAAE,cAAc,GAAG,MAAM,CAAA;IAC7B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAA;CACX;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,IAAI,EAAE,aAAa,EAAE,CAAA;IACrB,uBAAuB;IACvB,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;IACpC,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;CAC7B;AAED,uBAAuB;AACvB,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;IAChE,iCAAiC;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,IAAI,CAAA;IAC3D,gCAAgC;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACvF,8BAA8B;IAC9B,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IACtE;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAA;CACzE,CAAA;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,iBAAiB,CAAC,UAAU,EAAE,YAAY,CAAC,CA8B5E;AAED,YAAY,EAAE,cAAc,EAAE,CAAA;AAE9B;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA"}
|
|
@@ -32,6 +32,10 @@ export function createPetStore() {
|
|
|
32
32
|
setFeedback: (draft, feedback) => {
|
|
33
33
|
draft.feedback = feedback;
|
|
34
34
|
},
|
|
35
|
+
setGameplayView: (draft, view) => {
|
|
36
|
+
if (draft.snapshot !== null)
|
|
37
|
+
draft.snapshot = { ...draft.snapshot, gameplay: view };
|
|
38
|
+
},
|
|
35
39
|
},
|
|
36
40
|
});
|
|
37
41
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frames2d visual mount — the React bridge between the pet center chrome
|
|
3
|
+
* and the imperative frames2d renderer, mirroring the live2d mount. The
|
|
4
|
+
* bridge owns the contract context (asset base, phase stream, interaction
|
|
5
|
+
* write-back, activation cleanups), feeds the polled phase into the stream,
|
|
6
|
+
* forwards the chrome's drag gesture onto the conventional 'drag' track
|
|
7
|
+
* (when the pet declares one), and renders the localized fallback card when
|
|
8
|
+
* the served config is invalid.
|
|
9
|
+
* @module @linxin666/dsh-pet/client/renderers/Frames2dVisualMount
|
|
10
|
+
*/
|
|
11
|
+
import { type ReactElement } from 'react';
|
|
12
|
+
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
13
|
+
import type { PetDefinition } from '../../registry.ts';
|
|
14
|
+
import type { ActivityPhase } from '../../state.ts';
|
|
15
|
+
import type { DragStream } from '../drag-stream.ts';
|
|
16
|
+
import type { GameplayBus } from '../gameplay-hud.tsx';
|
|
17
|
+
import type { NS } from '../locales.ts';
|
|
18
|
+
/** Mount the frames2d renderer as the sprite's visual (inside the chrome). */
|
|
19
|
+
export declare function Frames2dVisualMount(props: {
|
|
20
|
+
definition: PetDefinition;
|
|
21
|
+
phase: ActivityPhase;
|
|
22
|
+
onPet: () => void;
|
|
23
|
+
/** Chrome drag gesture stream (the renderer switch owns it). */
|
|
24
|
+
drag: DragStream;
|
|
25
|
+
/** Gameplay coordination bus; the mount registers its track override. */
|
|
26
|
+
bus?: GameplayBus;
|
|
27
|
+
t: PropsLocale<typeof NS>['t'];
|
|
28
|
+
}): ReactElement;
|
|
29
|
+
//# sourceMappingURL=Frames2dVisualMount.d.ts.map
|
|
@@ -0,0 +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,CA6Ef"}
|