@linxin666/dsh-pet 0.3.16 → 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
|
@@ -52,7 +52,60 @@ function validateFrames2dConfig(config) {
|
|
|
52
52
|
if (typeof phases.idle !== 'string' || tracks[phases.idle] === undefined) {
|
|
53
53
|
throw new Error('frames2d phases.idle must name an existing track');
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
// Skins: keep entries whose idleTrack survives validation and loops —
|
|
56
|
+
// a non-looping base idle would settle into itself forever. Click actions
|
|
57
|
+
// with an unresolvable track are dropped from that skin.
|
|
58
|
+
let skins;
|
|
59
|
+
if (Array.isArray(config.skins)) {
|
|
60
|
+
const resolved = [];
|
|
61
|
+
for (const skin of config.skins) {
|
|
62
|
+
if (!isRecord(skin) || typeof skin.id !== 'string' || typeof skin.label !== 'string'
|
|
63
|
+
|| typeof skin.idleTrack !== 'string' || skin.idleTrack === '')
|
|
64
|
+
continue;
|
|
65
|
+
const target = tracks[skin.idleTrack];
|
|
66
|
+
if (target === undefined || !target.loop)
|
|
67
|
+
continue;
|
|
68
|
+
let clickActions;
|
|
69
|
+
if (Array.isArray(skin.clickActions)) {
|
|
70
|
+
const kept = [];
|
|
71
|
+
for (const action of skin.clickActions) {
|
|
72
|
+
if (!isRecord(action) || typeof action.track !== 'string' || action.track === ''
|
|
73
|
+
|| typeof action.probability !== 'number' || !(action.probability > 0) || action.probability > 1)
|
|
74
|
+
continue;
|
|
75
|
+
if (tracks[action.track] === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
kept.push({
|
|
78
|
+
track: action.track,
|
|
79
|
+
probability: action.probability,
|
|
80
|
+
...(Array.isArray(action.phrases) ? { phrases: action.phrases } : {}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (kept.length > 0)
|
|
84
|
+
clickActions = kept;
|
|
85
|
+
}
|
|
86
|
+
let gameplayTracks;
|
|
87
|
+
if (isRecord(skin.gameplayTracks)) {
|
|
88
|
+
const kept = {};
|
|
89
|
+
for (const [state, trackName] of Object.entries(skin.gameplayTracks)) {
|
|
90
|
+
if (typeof trackName !== 'string' || trackName === '' || tracks[trackName] === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
kept[state] = trackName;
|
|
93
|
+
}
|
|
94
|
+
if (Object.keys(kept).length > 0)
|
|
95
|
+
gameplayTracks = kept;
|
|
96
|
+
}
|
|
97
|
+
resolved.push({
|
|
98
|
+
id: skin.id,
|
|
99
|
+
label: skin.label,
|
|
100
|
+
idleTrack: skin.idleTrack,
|
|
101
|
+
...(clickActions === undefined ? {} : { clickActions }),
|
|
102
|
+
...(gameplayTracks === undefined ? {} : { gameplayTracks }),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (resolved.length > 0)
|
|
106
|
+
skins = resolved;
|
|
107
|
+
}
|
|
108
|
+
return { tracks, phases: phases, ...(skins === undefined ? {} : { skins }) };
|
|
56
109
|
}
|
|
57
110
|
export const frames2dRenderer = {
|
|
58
111
|
id: 'frames2d',
|
|
@@ -146,11 +199,18 @@ export const frames2dRenderer = {
|
|
|
146
199
|
let frameIndex = 0;
|
|
147
200
|
let lastAdvance = Date.now();
|
|
148
201
|
let override;
|
|
202
|
+
// Skin base idle: every "back to idle" target resolves through this
|
|
203
|
+
// (idle phase, unmapped phases and fallbacks), so a selected skin swaps
|
|
204
|
+
// the pet's resting look without touching gameplay tracks.
|
|
205
|
+
let baseIdle = config.phases.idle;
|
|
149
206
|
let drawToken = 0;
|
|
150
207
|
let lastDrawnUrl;
|
|
151
208
|
const trackForPhase = (phase) => {
|
|
152
209
|
const mapped = config.phases[phase];
|
|
153
|
-
|
|
210
|
+
if (mapped === undefined)
|
|
211
|
+
return baseIdle;
|
|
212
|
+
const target = mapped === config.phases.idle ? baseIdle : mapped;
|
|
213
|
+
return config.tracks[target] !== undefined ? target : baseIdle;
|
|
154
214
|
};
|
|
155
215
|
/** Canvas path: paints the newest requested frame; stale draws drop out. */
|
|
156
216
|
const paintCanvas = (url) => {
|
|
@@ -211,11 +271,14 @@ export const frames2dRenderer = {
|
|
|
211
271
|
schedule(def.durations[frameIndex] ?? 200);
|
|
212
272
|
return;
|
|
213
273
|
}
|
|
214
|
-
// Non-loop completion: settle into the fallback
|
|
215
|
-
//
|
|
274
|
+
// Non-loop completion: settle into the fallback — an explicit fallback to
|
|
275
|
+
// a real track wins, except when it points back at the manifest idle
|
|
276
|
+
// (that resolves through the skin base idle); anything else lands on
|
|
277
|
+
// the skin base idle. When the settle target is what the phase map
|
|
278
|
+
// plays anyway, release the gameplay override.
|
|
216
279
|
const target = def.fallback !== undefined && config.tracks[def.fallback] !== undefined
|
|
217
|
-
? def.fallback
|
|
218
|
-
:
|
|
280
|
+
? (def.fallback === config.phases.idle ? baseIdle : def.fallback)
|
|
281
|
+
: baseIdle;
|
|
219
282
|
if (target === trackForPhase(ctx.phase.get()))
|
|
220
283
|
override = undefined;
|
|
221
284
|
play(target);
|
|
@@ -224,7 +287,7 @@ export const frames2dRenderer = {
|
|
|
224
287
|
if (disposed)
|
|
225
288
|
return;
|
|
226
289
|
if (config.tracks[trackId] === undefined)
|
|
227
|
-
trackId =
|
|
290
|
+
trackId = baseIdle;
|
|
228
291
|
if (timer !== undefined)
|
|
229
292
|
clearTimeout(timer);
|
|
230
293
|
track = trackId;
|
|
@@ -313,6 +376,20 @@ export const frames2dRenderer = {
|
|
|
313
376
|
if (next !== track)
|
|
314
377
|
play(next);
|
|
315
378
|
},
|
|
379
|
+
setIdleTrack(next) {
|
|
380
|
+
if (disposed)
|
|
381
|
+
return;
|
|
382
|
+
if (next === undefined || (config.tracks[next] !== undefined && config.tracks[next].loop)) {
|
|
383
|
+
baseIdle = next ?? config.phases.idle;
|
|
384
|
+
}
|
|
385
|
+
// A skin only owns the resting look: re-resolve only when no
|
|
386
|
+
// gameplay override is active (active overrides end into baseIdle).
|
|
387
|
+
if (override === undefined) {
|
|
388
|
+
const target = trackForPhase(ctx.phase.get());
|
|
389
|
+
if (target !== track)
|
|
390
|
+
play(target);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
316
393
|
currentTrack() {
|
|
317
394
|
return track;
|
|
318
395
|
},
|
|
@@ -99,6 +99,41 @@ export interface PetManifestFrames2d {
|
|
|
99
99
|
phases: Partial<Record<ActivityPhase, string>> & {
|
|
100
100
|
idle: string;
|
|
101
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* Optional selectable skins: a skin swaps the base idle target (the idle
|
|
104
|
+
* phase, unmapped phases, and every fallback back to idle) to its own
|
|
105
|
+
* looping idle track. Gameplay tracks (shy/work/sleep...) are not replaced.
|
|
106
|
+
*/
|
|
107
|
+
skins?: PetManifestSkin[];
|
|
108
|
+
}
|
|
109
|
+
/** One selectable frames2d skin. */
|
|
110
|
+
export interface PetManifestSkin {
|
|
111
|
+
id: string;
|
|
112
|
+
label: string;
|
|
113
|
+
/** A declared, looping track that becomes the base idle while selected. */
|
|
114
|
+
idleTrack: string;
|
|
115
|
+
/**
|
|
116
|
+
* Optional click-triggered actions exclusive to this skin: while the skin
|
|
117
|
+
* is selected, a tap rolls each action by probability (in order) and plays
|
|
118
|
+
* its track once if hit. Misses fall through to the regular touch zones.
|
|
119
|
+
*/
|
|
120
|
+
clickActions?: PetManifestSkinClickAction[];
|
|
121
|
+
/**
|
|
122
|
+
* Optional gameplay-track overrides: gameplay state name (e.g. 'sleep' or
|
|
123
|
+
* 'work') -> a declared track the skin swaps in while selected. Lets a skin
|
|
124
|
+
* restyle a gameplay loop (e.g. its own sleep animation) without touching
|
|
125
|
+
* the default pet's tracks.
|
|
126
|
+
*/
|
|
127
|
+
gameplayTracks?: Record<string, string>;
|
|
128
|
+
}
|
|
129
|
+
/** One probability-rolled tap action a skin may declare. */
|
|
130
|
+
export interface PetManifestSkinClickAction {
|
|
131
|
+
/** A declared track played once on hit (usually non-looping). */
|
|
132
|
+
track: string;
|
|
133
|
+
/** Hit probability for this action (0..1). */
|
|
134
|
+
probability: number;
|
|
135
|
+
/** Optional lines spoken when the action fires. */
|
|
136
|
+
phrases?: string[];
|
|
102
137
|
}
|
|
103
138
|
/** Normalized v2 manifest the registry consumes. */
|
|
104
139
|
export interface PetManifestV2 {
|
|
@@ -150,6 +185,16 @@ export declare const KNOWN_LIVE2D: Set<string>;
|
|
|
150
185
|
export declare const KNOWN_FRAMES2D: Set<string>;
|
|
151
186
|
/** frames2d track field allow-list (drift-locked to the schema file). */
|
|
152
187
|
export declare const KNOWN_FRAMES2D_TRACK: Set<string>;
|
|
188
|
+
/** frames2d skin entry field allow-list (drift-locked to the schema file). */
|
|
189
|
+
export declare const KNOWN_SKIN: Set<string>;
|
|
190
|
+
/** frames2d skin click-action field allow-list (drift-locked to the schema file). */
|
|
191
|
+
export declare const KNOWN_SKIN_CLICK: Set<string>;
|
|
192
|
+
/** Max selectable skins a frames2d manifest may declare. */
|
|
193
|
+
export declare const FRAMES2D_MAX_SKINS = 16;
|
|
194
|
+
/** Max click actions one skin may declare. */
|
|
195
|
+
export declare const FRAMES2D_MAX_SKIN_CLICKS = 8;
|
|
196
|
+
/** Max gameplay-state overrides one skin may declare. */
|
|
197
|
+
export declare const FRAMES2D_MAX_SKIN_GAMEPLAY = 8;
|
|
153
198
|
/**
|
|
154
199
|
* Validate a manifest-relative asset path: no absolute paths, no backslashes,
|
|
155
200
|
* no traversal, plain safe segments only. Returns the normalized path.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-v2.d.ts","sourceRoot":"","sources":["../../src/manifest-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAE/E,4CAA4C;AAC5C,eAAO,MAAM,eAAe,EAAG,CAAU,CAAA;AAEzC,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,6CAA8C,CAAA;AAC7E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjE,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAEvD,CAAA;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAA;IACvB,gEAAgE;IAChE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAA;IAChD,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"manifest-v2.d.ts","sourceRoot":"","sources":["../../src/manifest-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAE/E,4CAA4C;AAC5C,eAAO,MAAM,eAAe,EAAG,CAAU,CAAA;AAEzC,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,6CAA8C,CAAA;AAC7E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjE,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAEvD,CAAA;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAA;IACvB,gEAAgE;IAChE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oCAAoC;IACpC,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,mGAAmG;IACnG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAA;IAChD,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjE;;;;OAIG;IACH,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,0BAA0B,EAAE,CAAA;IAC3C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAA;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,kBAAkB,EAAE,OAAO,eAAe,CAAA;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,eAAe,CAAA;IACzB,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC,CAAA;IAC1D,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,CAAC;IAAC,WAAW,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC9G;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAA;AAKvD;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAI1B,CAAA;AACF,yEAAyE;AACzE,eAAO,MAAM,cAAc,aAAmF,CAAA;AAC9G,uEAAuE;AACvE,eAAO,MAAM,YAAY,aAA4F,CAAA;AACrH,yEAAyE;AACzE,eAAO,MAAM,cAAc,aAAkE,CAAA;AAC7F,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,aAAqD,CAAA;AACtF,8EAA8E;AAC9E,eAAO,MAAM,UAAU,aAA0E,CAAA;AACjG,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,aAA+C,CAAA;AAC5E,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,KAAK,CAAA;AACpC,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB,IAAI,CAAA;AACzC,yDAAyD;AACzD,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAmC3C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQjE;AAqeD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAepF"}
|
package/lib/types/manifest-v2.js
CHANGED
|
@@ -50,9 +50,35 @@ export const KNOWN_SPRITE2D = new Set(['spritesheetPath', 'cell', 'columns', 'at
|
|
|
50
50
|
/** live2d block field allow-list (drift-locked to the schema file). */
|
|
51
51
|
export const KNOWN_LIVE2D = new Set(['model', 'scale', 'translate', 'motions', 'expressions', 'hitAreas', 'lipSync']);
|
|
52
52
|
/** frames2d block field allow-list (drift-locked to the schema file). */
|
|
53
|
-
export const KNOWN_FRAMES2D = new Set(['dir', 'defaultFrameMs', 'tracks', 'phases']);
|
|
53
|
+
export const KNOWN_FRAMES2D = new Set(['dir', 'defaultFrameMs', 'tracks', 'phases', 'skins']);
|
|
54
54
|
/** frames2d track field allow-list (drift-locked to the schema file). */
|
|
55
55
|
export const KNOWN_FRAMES2D_TRACK = new Set(['frames', 'frameMs', 'loop', 'fallback']);
|
|
56
|
+
/** frames2d skin entry field allow-list (drift-locked to the schema file). */
|
|
57
|
+
export const KNOWN_SKIN = new Set(['id', 'label', 'idleTrack', 'clickActions', 'gameplayTracks']);
|
|
58
|
+
/** frames2d skin click-action field allow-list (drift-locked to the schema file). */
|
|
59
|
+
export const KNOWN_SKIN_CLICK = new Set(['track', 'probability', 'phrases']);
|
|
60
|
+
/** Max selectable skins a frames2d manifest may declare. */
|
|
61
|
+
export const FRAMES2D_MAX_SKINS = 16;
|
|
62
|
+
/** Max click actions one skin may declare. */
|
|
63
|
+
export const FRAMES2D_MAX_SKIN_CLICKS = 8;
|
|
64
|
+
/** Max gameplay-state overrides one skin may declare. */
|
|
65
|
+
export const FRAMES2D_MAX_SKIN_GAMEPLAY = 8;
|
|
66
|
+
/** Max spoken lines one skin click action may declare. */
|
|
67
|
+
const SKIN_CLICK_MAX_PHRASES = 5;
|
|
68
|
+
/** Max length of one spoken line. */
|
|
69
|
+
const SKIN_CLICK_PHRASE_MAX_LENGTH = 120;
|
|
70
|
+
/** Validate a skin click action's optional phrase pool (same spirit as gameplay phrases). */
|
|
71
|
+
function parseSkinPhrases(raw, field, diag) {
|
|
72
|
+
if (raw === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > SKIN_CLICK_MAX_PHRASES
|
|
75
|
+
|| raw.some(line => typeof line !== 'string' || line.trim() === '' || line.length > SKIN_CLICK_PHRASE_MAX_LENGTH)) {
|
|
76
|
+
diag.error(field + ' must be 1..' + SKIN_CLICK_MAX_PHRASES + ' non-empty lines of at most '
|
|
77
|
+
+ SKIN_CLICK_PHRASE_MAX_LENGTH + ' chars');
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
56
82
|
class Diagnostics {
|
|
57
83
|
list = [];
|
|
58
84
|
source;
|
|
@@ -391,6 +417,118 @@ function parseFrames2dBlock(raw, diag) {
|
|
|
391
417
|
diag.error('frames2d.tracks.' + name + '.fallback references unknown track ' + JSON.stringify(track.fallback));
|
|
392
418
|
}
|
|
393
419
|
}
|
|
420
|
+
// Optional skins: each selects a declared looping idle track as the base
|
|
421
|
+
// idle while active (id/label are UI-facing, idleTrack is structure).
|
|
422
|
+
if (raw.skins !== undefined) {
|
|
423
|
+
if (!Array.isArray(raw.skins) || raw.skins.length === 0 || raw.skins.length > FRAMES2D_MAX_SKINS) {
|
|
424
|
+
diag.error('frames2d.skins must be an array of 1..' + FRAMES2D_MAX_SKINS + ' skins');
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const skins = [];
|
|
428
|
+
const seen = new Set();
|
|
429
|
+
for (const [index, entry] of raw.skins.entries()) {
|
|
430
|
+
const field = 'frames2d.skins[' + index + ']';
|
|
431
|
+
if (!isRecord(entry)) {
|
|
432
|
+
diag.error(field + ' must be an object');
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const extra = unknownKeys(entry, KNOWN_SKIN);
|
|
436
|
+
if (extra.length > 0)
|
|
437
|
+
diag.error(field + ': unknown field(s) ' + extra.map(k => JSON.stringify(k)).join(', '));
|
|
438
|
+
const id = typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
439
|
+
if (id === '' || id.length > 24 || !FRAMES2D_TRACK_NAME.test(id)) {
|
|
440
|
+
diag.error(field + '.id must be a lowercase kebab id of at most 24 chars');
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (seen.has(id)) {
|
|
444
|
+
diag.error(field + ': duplicate skin id ' + JSON.stringify(id));
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
seen.add(id);
|
|
448
|
+
const label = typeof entry.label === 'string' ? entry.label.trim() : '';
|
|
449
|
+
if (label === '' || label.length > 40) {
|
|
450
|
+
diag.error(field + '.label must be a non-empty string of at most 40 chars');
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const idleTrack = typeof entry.idleTrack === 'string' ? entry.idleTrack : '';
|
|
454
|
+
if (!validTrackName(idleTrack) || tracks[idleTrack] === undefined) {
|
|
455
|
+
diag.error(field + '.idleTrack must name a declared frames2d track');
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
// Optional click actions: each names a declared track and a hit
|
|
459
|
+
// probability; phrases reuse the gameplay phrase constraints.
|
|
460
|
+
let clickActions;
|
|
461
|
+
if (entry.clickActions !== undefined) {
|
|
462
|
+
if (!Array.isArray(entry.clickActions) || entry.clickActions.length === 0 || entry.clickActions.length > FRAMES2D_MAX_SKIN_CLICKS) {
|
|
463
|
+
diag.error(field + '.clickActions must be an array of 1..' + FRAMES2D_MAX_SKIN_CLICKS + ' actions');
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
const resolved = [];
|
|
467
|
+
for (const [cIndex, action] of entry.clickActions.entries()) {
|
|
468
|
+
const cField = field + '.clickActions[' + cIndex + ']';
|
|
469
|
+
if (!isRecord(action)) {
|
|
470
|
+
diag.error(cField + ' must be an object');
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const cExtra = unknownKeys(action, KNOWN_SKIN_CLICK);
|
|
474
|
+
if (cExtra.length > 0)
|
|
475
|
+
diag.error(cField + ': unknown field(s) ' + cExtra.map(k => JSON.stringify(k)).join(', '));
|
|
476
|
+
const trackName = typeof action.track === 'string' ? action.track : '';
|
|
477
|
+
if (!validTrackName(trackName) || tracks[trackName] === undefined) {
|
|
478
|
+
diag.error(cField + '.track must name a declared frames2d track');
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (typeof action.probability !== 'number' || !Number.isFinite(action.probability)
|
|
482
|
+
|| action.probability <= 0 || action.probability > 1) {
|
|
483
|
+
diag.error(cField + '.probability must be a number in (0, 1]');
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const phrases = parseSkinPhrases(action.phrases, cField + '.phrases', diag);
|
|
487
|
+
resolved.push({ track: trackName, probability: action.probability, ...(phrases === undefined ? {} : { phrases }) });
|
|
488
|
+
}
|
|
489
|
+
if (resolved.length > 0)
|
|
490
|
+
clickActions = resolved;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// Optional gameplay-track overrides: gameplay state name -> declared
|
|
494
|
+
// track swapped in while this skin is selected (e.g. a skin variant of
|
|
495
|
+
// the sleep loop). State names mirror gameplay blocks (sleep/work...);
|
|
496
|
+
// values must reference declared tracks.
|
|
497
|
+
let gameplayTracks;
|
|
498
|
+
if (entry.gameplayTracks !== undefined) {
|
|
499
|
+
if (!isRecord(entry.gameplayTracks)) {
|
|
500
|
+
diag.error(field + '.gameplayTracks must be an object mapping gameplay state names to tracks');
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
const states = Object.keys(entry.gameplayTracks);
|
|
504
|
+
if (states.length === 0 || states.length > FRAMES2D_MAX_SKIN_GAMEPLAY) {
|
|
505
|
+
diag.error(field + '.gameplayTracks must map 1..' + FRAMES2D_MAX_SKIN_GAMEPLAY + ' gameplay states');
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const resolvedTracks = {};
|
|
509
|
+
for (const state of states) {
|
|
510
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(state)) {
|
|
511
|
+
diag.error(field + '.gameplayTracks key ' + JSON.stringify(state) + ' must be a lowercase kebab state name');
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const gTrack = entry.gameplayTracks[state];
|
|
515
|
+
if (typeof gTrack !== 'string' || !validTrackName(gTrack) || tracks[gTrack] === undefined) {
|
|
516
|
+
diag.error(field + '.gameplayTracks[' + JSON.stringify(state) + '] must name a declared frames2d track');
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
resolvedTracks[state] = gTrack;
|
|
520
|
+
}
|
|
521
|
+
if (Object.keys(resolvedTracks).length > 0)
|
|
522
|
+
gameplayTracks = resolvedTracks;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
skins.push({ id, label, idleTrack, ...(clickActions === undefined ? {} : { clickActions }), ...(gameplayTracks === undefined ? {} : { gameplayTracks }) });
|
|
527
|
+
}
|
|
528
|
+
if (skins.length > 0)
|
|
529
|
+
block.skins = skins;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
394
532
|
return diag.hasErrors ? undefined : block;
|
|
395
533
|
}
|
|
396
534
|
/** v1 compat read: map the legacy flat manifest onto the v2 sprite2d shape. */
|
package/lib/types/persist.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface PetDisplayConfig {
|
|
|
22
22
|
export declare const defaultDisplayConfig: PetDisplayConfig;
|
|
23
23
|
/** Display value bounds (shared by load-time validation and setConfig). */
|
|
24
24
|
export declare const DISPLAY_SIZE_MIN = 32;
|
|
25
|
-
export declare const DISPLAY_SIZE_MAX =
|
|
25
|
+
export declare const DISPLAY_SIZE_MAX = 1024;
|
|
26
26
|
export declare const DISPLAY_INSET_MAX = 10000;
|
|
27
27
|
/** Everything persisted for the pet. */
|
|
28
28
|
export interface PetPersist {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CASzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA+DD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAoDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
|
package/lib/types/persist.js
CHANGED
|
@@ -19,7 +19,7 @@ export const defaultDisplayConfig = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Display value bounds (shared by load-time validation and setConfig). */
|
|
21
21
|
export const DISPLAY_SIZE_MIN = 32;
|
|
22
|
-
export const DISPLAY_SIZE_MAX =
|
|
22
|
+
export const DISPLAY_SIZE_MAX = 1024;
|
|
23
23
|
export const DISPLAY_INSET_MAX = 10_000;
|
|
24
24
|
/** Name constraints. */
|
|
25
25
|
export const PET_NAME_MAX_LENGTH = 20;
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -159,6 +159,28 @@ export interface PetFrames2dDefinition {
|
|
|
159
159
|
phases: Partial<Record<ActivityPhase, string>> & {
|
|
160
160
|
idle: string;
|
|
161
161
|
};
|
|
162
|
+
/** Selectable skins; each swaps the base idle target to its idleTrack. */
|
|
163
|
+
skins?: PetSkinDefinition[];
|
|
164
|
+
}
|
|
165
|
+
/** One selectable frames2d skin as served to the browser half. */
|
|
166
|
+
export interface PetSkinDefinition {
|
|
167
|
+
id: string;
|
|
168
|
+
label: string;
|
|
169
|
+
/** A declared looping track that becomes the base idle while selected. */
|
|
170
|
+
idleTrack: string;
|
|
171
|
+
/** Click actions exclusive to this skin (roll by probability; miss → touch zones). */
|
|
172
|
+
clickActions?: PetSkinClickActionDefinition[];
|
|
173
|
+
/** Gameplay-state overrides (state -> track) swapped in while selected. */
|
|
174
|
+
gameplayTracks?: Record<string, string>;
|
|
175
|
+
}
|
|
176
|
+
/** One probability-rolled tap action a skin may declare. */
|
|
177
|
+
export interface PetSkinClickActionDefinition {
|
|
178
|
+
/** A declared track played once on hit. */
|
|
179
|
+
track: string;
|
|
180
|
+
/** Hit probability (0..1). */
|
|
181
|
+
probability: number;
|
|
182
|
+
/** Optional lines spoken when the action fires. */
|
|
183
|
+
phrases?: string[];
|
|
162
184
|
}
|
|
163
185
|
/** A normalized pet as served to the browser half. */
|
|
164
186
|
export interface PetDefinition {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
|
package/lib/types/registry.js
CHANGED
|
@@ -478,6 +478,50 @@ function resolveFrames2dEntry(manifest, dir, options) {
|
|
|
478
478
|
phases[phase] = idleTrack;
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
|
+
// Skins: keep only entries whose idleTrack survived track resolution, and
|
|
482
|
+
// drop click actions whose track did not (the skin itself stays usable).
|
|
483
|
+
let skins;
|
|
484
|
+
if (block.skins !== undefined) {
|
|
485
|
+
const resolved = [];
|
|
486
|
+
for (const skin of block.skins) {
|
|
487
|
+
if (tracks[skin.idleTrack] === undefined) {
|
|
488
|
+
record('warning', 'pet ' + manifest.id + ': frames2d skin ' + JSON.stringify(skin.id) + ' idleTrack dropped; skipping');
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
let clickActions;
|
|
492
|
+
if (skin.clickActions !== undefined) {
|
|
493
|
+
const kept = [];
|
|
494
|
+
for (const action of skin.clickActions) {
|
|
495
|
+
if (tracks[action.track] === undefined) {
|
|
496
|
+
record('warning', 'pet ' + manifest.id + ': frames2d skin ' + JSON.stringify(skin.id)
|
|
497
|
+
+ ' click action track ' + JSON.stringify(action.track) + ' dropped');
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
kept.push(action);
|
|
501
|
+
}
|
|
502
|
+
if (kept.length > 0)
|
|
503
|
+
clickActions = kept;
|
|
504
|
+
}
|
|
505
|
+
// Gameplay track overrides: drop entries whose track did not survive.
|
|
506
|
+
let gameplayTracks;
|
|
507
|
+
if (skin.gameplayTracks !== undefined) {
|
|
508
|
+
const kept = {};
|
|
509
|
+
for (const [state, gTrack] of Object.entries(skin.gameplayTracks)) {
|
|
510
|
+
if (tracks[gTrack] === undefined) {
|
|
511
|
+
record('warning', 'pet ' + manifest.id + ': frames2d skin ' + JSON.stringify(skin.id)
|
|
512
|
+
+ ' gameplay track override ' + JSON.stringify(state) + ' -> ' + JSON.stringify(gTrack) + ' dropped');
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
kept[state] = gTrack;
|
|
516
|
+
}
|
|
517
|
+
if (Object.keys(kept).length > 0)
|
|
518
|
+
gameplayTracks = kept;
|
|
519
|
+
}
|
|
520
|
+
resolved.push({ ...skin, ...(clickActions === undefined ? {} : { clickActions }), ...(gameplayTracks === undefined ? {} : { gameplayTracks }) });
|
|
521
|
+
}
|
|
522
|
+
if (resolved.length > 0)
|
|
523
|
+
skins = resolved;
|
|
524
|
+
}
|
|
481
525
|
const remarks = normalizePetRemarks(manifest.remarks, message => record('warning', 'pet ' + manifest.id + ': ' + message));
|
|
482
526
|
// Gameplay layer: shop item icons are manifest-relative frame paths —
|
|
483
527
|
// verify them, promote them to browser URLs, and add them to the servable
|
|
@@ -516,7 +560,7 @@ function resolveFrames2dEntry(manifest, dir, options) {
|
|
|
516
560
|
displayName: manifest.displayName,
|
|
517
561
|
description: manifest.description ?? '',
|
|
518
562
|
renderer: 'frames2d',
|
|
519
|
-
frames2d: { tracks, phases },
|
|
563
|
+
frames2d: { tracks, phases, ...(skins === undefined ? {} : { skins }) },
|
|
520
564
|
...(gameplay === undefined ? {} : { gameplay }),
|
|
521
565
|
cell,
|
|
522
566
|
columns: DEFAULT_PET_COLUMNS,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"assets/whale",
|
|
35
35
|
"assets/whale-refined",
|
|
36
36
|
"assets/ouo-neko",
|
|
37
|
+
"assets/blue-throated-bee-eater",
|
|
37
38
|
"assets/decorations",
|
|
38
39
|
"contracts"
|
|
39
40
|
],
|
|
@@ -271,6 +271,70 @@ describe('GameplayHud', () => {
|
|
|
271
271
|
expect(api.touch).not.toHaveBeenCalled()
|
|
272
272
|
})
|
|
273
273
|
|
|
274
|
+
it('skin taps never fall through to the default touch zones', async () => {
|
|
275
|
+
// A skinned pet whose skin has no click actions: taps must resolve to
|
|
276
|
+
// the plain click boost (touch() without a zone) instead of playing the
|
|
277
|
+
// default pet's touch reactions (safety: skin ≠ default gameplay).
|
|
278
|
+
const def = petDefinition()
|
|
279
|
+
def.frames2d!.skins = [
|
|
280
|
+
{ id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle' },
|
|
281
|
+
]
|
|
282
|
+
const store = createPetStore().create()
|
|
283
|
+
store.actions.setSnapshot(snapshot(gameplayView()))
|
|
284
|
+
const api = {
|
|
285
|
+
touch: vi.fn(async () => ({ ok: true, view: gameplayView() })),
|
|
286
|
+
setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(),
|
|
287
|
+
} as unknown as Harness['api']
|
|
288
|
+
const setTrack = vi.fn()
|
|
289
|
+
const bus: GameplayBus = { setTrack }
|
|
290
|
+
render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
|
|
291
|
+
// Select the skin through the menu card so skinIdRef is active.
|
|
292
|
+
await act(async () => {
|
|
293
|
+
bus.openCard?.()
|
|
294
|
+
})
|
|
295
|
+
fireEvent.click(screen.getByText('皮肤'))
|
|
296
|
+
fireEvent.click(screen.getByText('蓝海霓裳'))
|
|
297
|
+
await act(async () => {
|
|
298
|
+
bus.tap!(0.5, 0.3) // head zone → default would be the 'happy' state
|
|
299
|
+
})
|
|
300
|
+
// No zone verb, no default track: the tap is a plain boost only.
|
|
301
|
+
expect(api.touch).toHaveBeenCalledWith()
|
|
302
|
+
expect(api.touch).not.toHaveBeenCalledWith('head')
|
|
303
|
+
expect(setTrack).not.toHaveBeenCalledWith('happy')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('plays the skin click action on a rolled hit and skips the default touch', async () => {
|
|
307
|
+
const def = petDefinition()
|
|
308
|
+
def.frames2d!.skins = [
|
|
309
|
+
{
|
|
310
|
+
id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle',
|
|
311
|
+
clickActions: [{ track: 'lanhainishang-lift-skirt', probability: 0.3 }],
|
|
312
|
+
},
|
|
313
|
+
]
|
|
314
|
+
const store = createPetStore().create()
|
|
315
|
+
store.actions.setSnapshot(snapshot(gameplayView()))
|
|
316
|
+
const api = {
|
|
317
|
+
touch: vi.fn(async () => ({ ok: true, view: gameplayView() })),
|
|
318
|
+
setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(),
|
|
319
|
+
} as unknown as Harness['api']
|
|
320
|
+
const setTrack = vi.fn()
|
|
321
|
+
const bus: GameplayBus = { setTrack }
|
|
322
|
+
render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
|
|
323
|
+
await act(async () => {
|
|
324
|
+
bus.openCard?.()
|
|
325
|
+
})
|
|
326
|
+
fireEvent.click(screen.getByText('皮肤'))
|
|
327
|
+
fireEvent.click(screen.getByText('蓝海霓裳'))
|
|
328
|
+
// Force a hit (roll < 0.3).
|
|
329
|
+
vi.spyOn(Math, 'random').mockReturnValue(0.1)
|
|
330
|
+
await act(async () => {
|
|
331
|
+
bus.tap!(0.5, 0.3)
|
|
332
|
+
})
|
|
333
|
+
vi.restoreAllMocks()
|
|
334
|
+
expect(setTrack).toHaveBeenCalledWith('lanhainishang-lift-skirt')
|
|
335
|
+
expect(api.touch).not.toHaveBeenCalledWith('head')
|
|
336
|
+
})
|
|
337
|
+
|
|
274
338
|
it('runs the idle director: weighted act rolls on the interval', async () => {
|
|
275
339
|
const h = harness()
|
|
276
340
|
await act(async () => {
|