@linxin666/dsh-pet 0.2.2 → 0.2.3
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 +66 -3
- package/README.zh.md +66 -3
- package/assets/whale/pet.json +169 -20
- package/assets/whale-refined/pet.json +17 -2
- package/lib/client.js +438 -21
- package/lib/client.js.map +1 -1
- package/lib/index.js +752 -36
- package/lib/live2d-vendor.js +995 -0
- package/lib/live2d-vendor.js.map +1 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -1
- package/lib/types/client/PetSettingsCard.d.ts +10 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +22 -1
- package/lib/types/client/PetSprite.d.ts +7 -1
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +17 -8
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +5 -0
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +10 -0
- package/lib/types/client/phase-stream.d.ts +22 -0
- package/lib/types/client/phase-stream.d.ts.map +1 -0
- package/lib/types/client/phase-stream.js +28 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts +25 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -0
- package/lib/types/client/renderers/PetRendererSwitch.js +24 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts +22 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/Live2dVisualMount.js +75 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts +89 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/runtime.js +63 -0
- package/lib/types/client/renderers/live2d/vendor-entry.d.ts +15 -0
- package/lib/types/client/renderers/live2d/vendor-entry.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d/vendor-entry.js +14 -0
- package/lib/types/client/renderers/live2d.d.ts +52 -0
- package/lib/types/client/renderers/live2d.d.ts.map +1 -0
- package/lib/types/client/renderers/live2d.js +180 -0
- package/lib/types/client/renderers/registry.d.ts +32 -0
- package/lib/types/client/renderers/registry.d.ts.map +1 -0
- package/lib/types/client/renderers/registry.js +49 -0
- package/lib/types/contracts/renderer.d.ts +50 -0
- package/lib/types/contracts/renderer.d.ts.map +1 -0
- package/lib/types/contracts/renderer.js +15 -0
- package/lib/types/manifest-v2.d.ts +129 -0
- package/lib/types/manifest-v2.d.ts.map +1 -0
- package/lib/types/manifest-v2.js +387 -0
- package/lib/types/model3.d.ts +26 -0
- package/lib/types/model3.d.ts.map +1 -0
- package/lib/types/model3.js +96 -0
- package/lib/types/registry.d.ts +57 -3
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +208 -33
- package/lib/types/routes.d.ts +39 -2
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +183 -12
- package/lib/types/service.d.ts +5 -1
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +4 -0
- package/package.json +3 -1
- package/src/client/PetDockEntry.tsx +19 -11
- package/src/client/PetSettingsCard.tsx +39 -0
- package/src/client/PetSprite.test.tsx +11 -0
- package/src/client/PetSprite.tsx +24 -9
- package/src/client/index.ts +6 -0
- package/src/client/locales.ts +10 -0
- package/src/client/phase-stream.test.ts +76 -0
- package/src/client/phase-stream.ts +39 -0
- package/src/client/renderers/PetRendererSwitch.test.tsx +66 -0
- package/src/client/renderers/PetRendererSwitch.tsx +48 -0
- package/src/client/renderers/live2d/Live2dVisualMount.tsx +98 -0
- package/src/client/renderers/live2d/runtime.test.ts +61 -0
- package/src/client/renderers/live2d/runtime.ts +119 -0
- package/src/client/renderers/live2d/vendor-entry.ts +14 -0
- package/src/client/renderers/live2d.test.ts +242 -0
- package/src/client/renderers/live2d.ts +218 -0
- package/src/client/renderers/registry.ts +58 -0
- package/src/client/settings-section.module.css +23 -0
- package/src/contracts/renderer.ts +54 -0
- package/src/manifest-v2.test.ts +251 -0
- package/src/manifest-v2.ts +414 -0
- package/src/model3.test.ts +79 -0
- package/src/model3.ts +91 -0
- package/src/registry.test.ts +215 -2
- package/src/registry.ts +246 -34
- package/src/routes.ts +202 -13
- package/src/service.ts +6 -0
package/lib/client.js
CHANGED
|
@@ -254,6 +254,7 @@ window.__ModuleLoader__.load({
|
|
|
254
254
|
const tracks = definition.tracks;
|
|
255
255
|
const sequences = definition.sequences;
|
|
256
256
|
(0, react.useEffect)(() => {
|
|
257
|
+
if (props.visual !== void 0) return;
|
|
257
258
|
let cancelled = false;
|
|
258
259
|
const img = new Image();
|
|
259
260
|
img.onload = () => {
|
|
@@ -264,13 +265,14 @@ window.__ModuleLoader__.load({
|
|
|
264
265
|
cancelled = true;
|
|
265
266
|
img.onload = null;
|
|
266
267
|
};
|
|
267
|
-
}, [definition.atlasUrl]);
|
|
268
|
+
}, [definition.atlasUrl, props.visual]);
|
|
268
269
|
const spriteScale = display.size / cell.height;
|
|
269
270
|
const phase = snapshot?.phase ?? "idle";
|
|
270
271
|
const animation = snapshot?.animation ?? "idle";
|
|
271
272
|
const scaleRef = (0, react.useRef)(spriteScale);
|
|
272
273
|
scaleRef.current = spriteScale;
|
|
273
274
|
(0, react.useEffect)(() => {
|
|
275
|
+
if (props.visual !== void 0) return;
|
|
274
276
|
const reduceMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches === true;
|
|
275
277
|
const sequence = animation === animationForPhase(phase) ? sequences?.[phase] : void 0;
|
|
276
278
|
const leadAnimation = sequence?.[0] ?? animation;
|
|
@@ -326,7 +328,8 @@ window.__ModuleLoader__.load({
|
|
|
326
328
|
columns,
|
|
327
329
|
rows,
|
|
328
330
|
tracks,
|
|
329
|
-
sequences
|
|
331
|
+
sequences,
|
|
332
|
+
props.visual
|
|
330
333
|
]);
|
|
331
334
|
const feedbackDoneRef = (0, react.useRef)(props.onFeedbackDone);
|
|
332
335
|
feedbackDoneRef.current = props.onFeedbackDone;
|
|
@@ -447,10 +450,12 @@ window.__ModuleLoader__.load({
|
|
|
447
450
|
style: {
|
|
448
451
|
width: spriteWidth,
|
|
449
452
|
height: spriteHeight,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
453
|
+
...props.visual === void 0 ? {
|
|
454
|
+
backgroundImage: imageReady ? "url(" + definition.atlasUrl + ")" : void 0,
|
|
455
|
+
backgroundSize: cell.width * columns * spriteScale + "px " + cell.height * (definition.atlasRows ?? rows.length) * spriteScale + "px",
|
|
456
|
+
backgroundRepeat: "no-repeat",
|
|
457
|
+
backgroundPosition: "0 0"
|
|
458
|
+
} : {},
|
|
454
459
|
cursor: dragRef.current === null ? "grab" : "grabbing"
|
|
455
460
|
},
|
|
456
461
|
onPointerDown,
|
|
@@ -461,7 +466,8 @@ window.__ModuleLoader__.load({
|
|
|
461
466
|
props.onPet();
|
|
462
467
|
},
|
|
463
468
|
role: "button",
|
|
464
|
-
"aria-label": definition.displayName
|
|
469
|
+
"aria-label": definition.displayName,
|
|
470
|
+
children: props.visual
|
|
465
471
|
}),
|
|
466
472
|
feedback !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
467
473
|
ref: bubbleRef,
|
|
@@ -606,6 +612,181 @@ window.__ModuleLoader__.load({
|
|
|
606
612
|
}), document.body);
|
|
607
613
|
}
|
|
608
614
|
//#endregion
|
|
615
|
+
//#region src/client/renderers/registry.ts
|
|
616
|
+
/** Renderer dispatch table. */
|
|
617
|
+
var RendererRegistry = class {
|
|
618
|
+
renderers = /* @__PURE__ */ new Map();
|
|
619
|
+
/** Register one renderer implementation (id wins on re-register). */
|
|
620
|
+
register(renderer) {
|
|
621
|
+
this.renderers.set(renderer.id, renderer);
|
|
622
|
+
}
|
|
623
|
+
/** Whether a renderer kind is available in this build. */
|
|
624
|
+
has(id) {
|
|
625
|
+
return this.renderers.has(id);
|
|
626
|
+
}
|
|
627
|
+
/** The registered renderer kinds (for diagnostics). */
|
|
628
|
+
kinds() {
|
|
629
|
+
return [...this.renderers.keys()].sort();
|
|
630
|
+
}
|
|
631
|
+
/** Remove every registration (tests; the client index registers once). */
|
|
632
|
+
clear() {
|
|
633
|
+
this.renderers.clear();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Mount a renderer for one activation. An unknown kind renders a clear
|
|
637
|
+
* diagnostic card into the container instead of failing silently.
|
|
638
|
+
*/
|
|
639
|
+
mount(kind, ctx, config) {
|
|
640
|
+
const renderer = this.renderers.get(kind);
|
|
641
|
+
if (renderer === void 0) {
|
|
642
|
+
const note = document.createElement("div");
|
|
643
|
+
note.dataset.dshPetRendererFallback = kind;
|
|
644
|
+
note.textContent = "Pet renderer \"" + kind + "\" is not available in this build (supported: " + this.kinds().join(", ") + ").";
|
|
645
|
+
ctx.container.appendChild(note);
|
|
646
|
+
ctx.onCleanup(() => note.remove());
|
|
647
|
+
return { dispose: () => note.remove() };
|
|
648
|
+
}
|
|
649
|
+
return renderer.mount(ctx, renderer.validateConfig(config));
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* The plugin-wide renderer registry. The client entry registers the
|
|
654
|
+
* built-in renderers at apply time; the renderer switch and the live2d
|
|
655
|
+
* bridge dispatch through this instance.
|
|
656
|
+
*/
|
|
657
|
+
const defaultPetRendererRegistry = new RendererRegistry();
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/client/phase-stream.ts
|
|
660
|
+
/** Create the stream (one per pet entry lifetime, owned by the plugin body). */
|
|
661
|
+
function createPhaseStream(initial = "idle") {
|
|
662
|
+
let current = initial;
|
|
663
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
664
|
+
return {
|
|
665
|
+
get: () => current,
|
|
666
|
+
subscribe(listener) {
|
|
667
|
+
listeners.add(listener);
|
|
668
|
+
return () => {
|
|
669
|
+
listeners.delete(listener);
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
push(phase) {
|
|
673
|
+
if (phase === current) return;
|
|
674
|
+
current = phase;
|
|
675
|
+
for (const listener of [...listeners]) listener(phase);
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/client/renderers/live2d/Live2dVisualMount.tsx
|
|
681
|
+
/**
|
|
682
|
+
* Live2D visual mount (pet-center M3) — the React bridge between the pet
|
|
683
|
+
* center chrome and the imperative live2d renderer. The bridge owns the
|
|
684
|
+
* contract context (asset base, phase stream, interaction write-back,
|
|
685
|
+
* activation cleanups), feeds the polled phase into the stream, forwards
|
|
686
|
+
* sub-4px taps as hit-test coordinates, and renders the localized error
|
|
687
|
+
* card when the renderer reports a fatal boot failure.
|
|
688
|
+
* @module @linxin666/dsh-pet/client/renderers/live2d/Live2dVisualMount
|
|
689
|
+
*/
|
|
690
|
+
/** Mount the live2d renderer as the sprite's visual (inside the chrome). */
|
|
691
|
+
function Live2dVisualMount(props) {
|
|
692
|
+
const containerRef = (0, react.useRef)(null);
|
|
693
|
+
const streamRef = (0, react.useRef)(null);
|
|
694
|
+
const handleRef = (0, react.useRef)(null);
|
|
695
|
+
const downRef = (0, react.useRef)(null);
|
|
696
|
+
const [error, setError] = (0, react.useState)(null);
|
|
697
|
+
(0, react.useEffect)(() => {
|
|
698
|
+
const container = containerRef.current;
|
|
699
|
+
const live2d = props.definition.live2d;
|
|
700
|
+
if (container === null || live2d === void 0) return void 0;
|
|
701
|
+
streamRef.current ??= createPhaseStream(props.phase);
|
|
702
|
+
const cleanups = [];
|
|
703
|
+
const ctx = {
|
|
704
|
+
petId: props.definition.id,
|
|
705
|
+
assetBase: "/pet/" + encodeURIComponent(props.definition.id),
|
|
706
|
+
container,
|
|
707
|
+
phase: streamRef.current,
|
|
708
|
+
interact: props.onPet,
|
|
709
|
+
onCleanup: (fn) => {
|
|
710
|
+
cleanups.push(fn);
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
let handle;
|
|
714
|
+
try {
|
|
715
|
+
handle = defaultPetRendererRegistry.mount("live2d", ctx, live2d);
|
|
716
|
+
} catch {
|
|
717
|
+
setError("load-failed");
|
|
718
|
+
return () => {
|
|
719
|
+
for (const fn of cleanups.splice(0)) fn();
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
handleRef.current = handle;
|
|
723
|
+
handle.onError?.(setError);
|
|
724
|
+
return () => {
|
|
725
|
+
handleRef.current = null;
|
|
726
|
+
for (const fn of cleanups.splice(0)) fn();
|
|
727
|
+
handle.dispose();
|
|
728
|
+
};
|
|
729
|
+
}, [props.definition]);
|
|
730
|
+
(0, react.useEffect)(() => {
|
|
731
|
+
streamRef.current?.push(props.phase);
|
|
732
|
+
}, [props.phase]);
|
|
733
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
734
|
+
ref: containerRef,
|
|
735
|
+
"data-dsh-pet-live2d": props.definition.id,
|
|
736
|
+
style: {
|
|
737
|
+
width: "100%",
|
|
738
|
+
height: "100%"
|
|
739
|
+
},
|
|
740
|
+
onPointerDown: (e) => {
|
|
741
|
+
downRef.current = {
|
|
742
|
+
x: e.clientX,
|
|
743
|
+
y: e.clientY
|
|
744
|
+
};
|
|
745
|
+
},
|
|
746
|
+
onPointerUp: (e) => {
|
|
747
|
+
const down = downRef.current;
|
|
748
|
+
downRef.current = null;
|
|
749
|
+
if (down === null) return;
|
|
750
|
+
if (Math.abs(e.clientX - down.x) > 4 || Math.abs(e.clientY - down.y) > 4) return;
|
|
751
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
752
|
+
handleRef.current?.tap(e.clientX - rect.left, e.clientY - rect.top);
|
|
753
|
+
},
|
|
754
|
+
children: error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
755
|
+
"data-dsh-pet-live2d-error": error,
|
|
756
|
+
children: error === "core-missing" ? props.t("pet.live2d.core-missing") : error === "vendor-missing" ? props.t("pet.live2d.vendor-missing") : props.t("pet.live2d.load-failed")
|
|
757
|
+
})
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/client/renderers/PetRendererSwitch.tsx
|
|
762
|
+
/**
|
|
763
|
+
* Renderer switch — the client dispatch seam of the pet center (issue #623,
|
|
764
|
+
* milestone M2 P5 / M3). The pet's manifest picks the renderer: sprite2d
|
|
765
|
+
* hands straight through to the sprite; live2d injects its visual INTO the
|
|
766
|
+
* sprite chrome (the dock, bubbles and panel belong to the pet center, not
|
|
767
|
+
* the renderer); a renderer this build cannot serve renders a clear
|
|
768
|
+
* diagnostic card instead of blanking.
|
|
769
|
+
* @module @linxin666/dsh-pet/client/renderers/PetRendererSwitch
|
|
770
|
+
*/
|
|
771
|
+
/** Dispatch one pet definition to its renderer; unknown kinds get a card. */
|
|
772
|
+
function PetRendererSwitch(props) {
|
|
773
|
+
const renderer = props.definition.renderer ?? "sprite2d";
|
|
774
|
+
if (renderer === "sprite2d") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: props.children });
|
|
775
|
+
if (renderer === "live2d" && defaultPetRendererRegistry.has("live2d") && (0, react.isValidElement)(props.children)) {
|
|
776
|
+
const visual = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Live2dVisualMount, {
|
|
777
|
+
definition: props.definition,
|
|
778
|
+
phase: props.phase,
|
|
779
|
+
onPet: props.onPet,
|
|
780
|
+
t: props.t
|
|
781
|
+
});
|
|
782
|
+
return (0, react.cloneElement)(props.children, { visual });
|
|
783
|
+
}
|
|
784
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
785
|
+
"data-dsh-pet-renderer-fallback": renderer,
|
|
786
|
+
children: props.t("pet.renderer.unavailable", { renderer })
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
//#endregion
|
|
609
790
|
//#region src/client/PetDockEntry.tsx
|
|
610
791
|
/**
|
|
611
792
|
* Global floating pet entry. The pet is host-global (its state, display and
|
|
@@ -645,19 +826,25 @@ window.__ModuleLoader__.load({
|
|
|
645
826
|
if (visible) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
646
827
|
"data-pet-dock": true,
|
|
647
828
|
"data-testid": "pet-dock",
|
|
648
|
-
children: snapshot === null || definition === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
649
|
-
snapshot,
|
|
829
|
+
children: snapshot === null || definition === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PetRendererSwitch, {
|
|
650
830
|
definition,
|
|
651
|
-
|
|
652
|
-
feedback,
|
|
831
|
+
phase: snapshot?.phase ?? "idle",
|
|
653
832
|
onPet: props.pet,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
833
|
+
t: props.t,
|
|
834
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PetSprite, {
|
|
835
|
+
snapshot,
|
|
836
|
+
definition,
|
|
837
|
+
display: snapshot.display,
|
|
838
|
+
feedback,
|
|
839
|
+
onPet: props.pet,
|
|
840
|
+
onFeed: props.feed,
|
|
841
|
+
onHide: props.hide,
|
|
842
|
+
onDragEnd: props.dragEnd,
|
|
843
|
+
onRename: props.rename,
|
|
844
|
+
onOpenSession: props.openSession,
|
|
845
|
+
onFeedbackDone: props.feedbackDone,
|
|
846
|
+
t: props.t
|
|
847
|
+
})
|
|
661
848
|
})
|
|
662
849
|
});
|
|
663
850
|
const display = snapshot?.display ?? DEFAULT_DISPLAY;
|
|
@@ -677,6 +864,192 @@ window.__ModuleLoader__.load({
|
|
|
677
864
|
});
|
|
678
865
|
}
|
|
679
866
|
//#endregion
|
|
867
|
+
//#region src/contracts/renderer.ts
|
|
868
|
+
/** Contract version renderers declare against (independent of the manifest). */
|
|
869
|
+
const PET_RENDERER_API_VERSION = "x-org.linxin666.pet-center/v1alpha1";
|
|
870
|
+
//#endregion
|
|
871
|
+
//#region src/client/renderers/live2d/runtime.ts
|
|
872
|
+
/**
|
|
873
|
+
* Live2D runtime loading (pet-center M3) — the two scripts a live2d mount
|
|
874
|
+
* needs, fetched lazily through the plugin's own runtime route: the
|
|
875
|
+
* user-supplied Cubism Core (proprietary; the plugin never bundles or
|
|
876
|
+
* downloads it — issue #623 M1 §0) and the plugin-shipped MIT vendor bundle
|
|
877
|
+
* (pixi.js + untitled-pixi-live2d-engine). Each loads at most once per page;
|
|
878
|
+
* concurrent mounts share the in-flight promise, and a failure is cached as
|
|
879
|
+
* 'absent' so a broken install stops retrying the network every mount.
|
|
880
|
+
*
|
|
881
|
+
* The vendor surface below is the structural slice the renderer consumes;
|
|
882
|
+
* the real objects come from 'window.__dshPetLive2d' (lib/live2d-vendor.js),
|
|
883
|
+
* so this module never imports pixi — the client bundle stays lean.
|
|
884
|
+
* @module @linxin666/dsh-pet/client/renderers/live2d/runtime
|
|
885
|
+
*/
|
|
886
|
+
/** Runtime file URLs the host serves ('/api/pet/runtime/<name>', M3-2). */
|
|
887
|
+
const CORE_URL = "/api/pet/runtime/live2dcubismcore.min.js";
|
|
888
|
+
const VENDOR_URL = "/api/pet/runtime/live2d-vendor.js";
|
|
889
|
+
const defaultInjector = (src) => new Promise((resolve, reject) => {
|
|
890
|
+
const tag = document.createElement("script");
|
|
891
|
+
tag.src = src;
|
|
892
|
+
tag.onload = () => resolve();
|
|
893
|
+
tag.onerror = () => reject(/* @__PURE__ */ new Error("script failed to load: " + src));
|
|
894
|
+
document.head.appendChild(tag);
|
|
895
|
+
});
|
|
896
|
+
let corePromise;
|
|
897
|
+
let vendorPromise;
|
|
898
|
+
/**
|
|
899
|
+
* Ensure the Cubism Core global exists, injecting the runtime-route script
|
|
900
|
+
* once when absent. Resolves false when the user has not installed the core
|
|
901
|
+
* (a normal state — the renderer turns it into install guidance).
|
|
902
|
+
*/
|
|
903
|
+
function ensureCubismCore(probe = {}) {
|
|
904
|
+
if (typeof window !== "undefined" && window.Live2DCubismCore !== void 0) return Promise.resolve(true);
|
|
905
|
+
if (probe.inject !== void 0) return probe.inject(CORE_URL).then(() => typeof window !== "undefined" && window.Live2DCubismCore !== void 0).catch(() => false);
|
|
906
|
+
corePromise ??= defaultInjector(CORE_URL).then(() => typeof window !== "undefined" && window.Live2DCubismCore !== void 0).catch(() => false);
|
|
907
|
+
return corePromise;
|
|
908
|
+
}
|
|
909
|
+
/** Ensure the plugin vendor bundle global exists (same caching discipline). */
|
|
910
|
+
function ensureLive2dVendor(probe = {}) {
|
|
911
|
+
if (typeof window !== "undefined" && window.__dshPetLive2d !== void 0) return Promise.resolve(window.__dshPetLive2d);
|
|
912
|
+
if (probe.inject !== void 0) return probe.inject(VENDOR_URL).then(() => typeof window !== "undefined" ? window.__dshPetLive2d : void 0).catch(() => void 0);
|
|
913
|
+
vendorPromise ??= defaultInjector(VENDOR_URL).then(() => typeof window !== "undefined" ? window.__dshPetLive2d : void 0).catch(() => void 0);
|
|
914
|
+
return vendorPromise;
|
|
915
|
+
}
|
|
916
|
+
//#endregion
|
|
917
|
+
//#region src/client/renderers/live2d.ts
|
|
918
|
+
/** The de-facto tap-motion group of Cubism sample models. */
|
|
919
|
+
const TAP_GROUP = "TapBody";
|
|
920
|
+
let vendorConfigured = false;
|
|
921
|
+
/** Configure pixi extensions + the Cubism SDK once per page. */
|
|
922
|
+
function configureOnce(vendor) {
|
|
923
|
+
if (vendorConfigured) return;
|
|
924
|
+
vendorConfigured = true;
|
|
925
|
+
vendor.extensions.add(vendor.Live2DPlugin);
|
|
926
|
+
vendor.configureCubismSDK({ memorySizeMB: 32 });
|
|
927
|
+
}
|
|
928
|
+
/** Fail-closed config validation (contract: unknown manifest block in). */
|
|
929
|
+
function validateLive2dConfig(config) {
|
|
930
|
+
if (typeof config !== "object" || config === null) throw new Error("live2d config is not an object");
|
|
931
|
+
const source = config;
|
|
932
|
+
if (typeof source.modelUrl !== "string" || source.modelUrl === "") throw new Error("live2d config modelUrl is required");
|
|
933
|
+
const motions = source.motions;
|
|
934
|
+
if (typeof motions !== "object" || motions === null || typeof motions.idle !== "string") throw new Error("live2d config motions.idle is required");
|
|
935
|
+
return config;
|
|
936
|
+
}
|
|
937
|
+
/** The live2d renderer implementation. */
|
|
938
|
+
const live2dRenderer = {
|
|
939
|
+
id: "live2d",
|
|
940
|
+
apiVersion: PET_RENDERER_API_VERSION,
|
|
941
|
+
validateConfig: validateLive2dConfig,
|
|
942
|
+
mount(ctx, config) {
|
|
943
|
+
let disposed = false;
|
|
944
|
+
let app;
|
|
945
|
+
let model;
|
|
946
|
+
let errorListener;
|
|
947
|
+
let unsubscribe;
|
|
948
|
+
/** The motion group the current phase maps to (resume target after taps). */
|
|
949
|
+
let phaseGroup = config.motions.idle;
|
|
950
|
+
let tapPlaying = false;
|
|
951
|
+
const playGroup = (group) => {
|
|
952
|
+
if (model === void 0) return;
|
|
953
|
+
const groups = model.internalModel.settings.motions ?? {};
|
|
954
|
+
const count = Array.isArray(groups[group]) ? groups[group].length : 0;
|
|
955
|
+
if (count === 0) {
|
|
956
|
+
if (group !== config.motions.idle) playGroup(config.motions.idle);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const index = count > 1 ? Math.floor(Math.random() * count) : 0;
|
|
960
|
+
model.motion(group, index);
|
|
961
|
+
};
|
|
962
|
+
const applyPhase = (phase) => {
|
|
963
|
+
phaseGroup = config.motions[phase] ?? config.motions.idle;
|
|
964
|
+
playGroup(phaseGroup);
|
|
965
|
+
const expression = config.expressions?.[phase];
|
|
966
|
+
if (expression !== void 0 && model !== void 0) model.expression(expression);
|
|
967
|
+
};
|
|
968
|
+
const boot = async () => {
|
|
969
|
+
if (!await ensureCubismCore()) {
|
|
970
|
+
if (!disposed) errorListener?.("core-missing");
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const vendor = await ensureLive2dVendor();
|
|
974
|
+
if (vendor === void 0) {
|
|
975
|
+
if (!disposed) errorListener?.("vendor-missing");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
configureOnce(vendor);
|
|
979
|
+
const pixiApp = new vendor.Application();
|
|
980
|
+
await pixiApp.init({
|
|
981
|
+
width: Math.max(1, ctx.container.clientWidth || 160),
|
|
982
|
+
height: Math.max(1, ctx.container.clientHeight || 174),
|
|
983
|
+
backgroundAlpha: 0,
|
|
984
|
+
antialias: true,
|
|
985
|
+
autoDensity: true,
|
|
986
|
+
preference: "webgl"
|
|
987
|
+
});
|
|
988
|
+
if (disposed) {
|
|
989
|
+
pixiApp.destroy(true, { children: true });
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
pixiApp.canvas.style.display = "block";
|
|
993
|
+
pixiApp.canvas.style.width = "100%";
|
|
994
|
+
pixiApp.canvas.style.height = "100%";
|
|
995
|
+
ctx.container.appendChild(pixiApp.canvas);
|
|
996
|
+
const loaded = await vendor.Live2DModel.from(config.modelUrl, {
|
|
997
|
+
autoHitTest: false,
|
|
998
|
+
autoFocus: false
|
|
999
|
+
});
|
|
1000
|
+
if (disposed) {
|
|
1001
|
+
pixiApp.destroy(true, { children: true });
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
app = pixiApp;
|
|
1005
|
+
model = loaded;
|
|
1006
|
+
const fit = Math.min(pixiApp.renderer.width / Math.max(1, loaded.width), pixiApp.renderer.height / Math.max(1, loaded.height)) * .92;
|
|
1007
|
+
loaded.scale.set(fit * (config.scale ?? 1));
|
|
1008
|
+
loaded.anchor.set(.5);
|
|
1009
|
+
loaded.position.set(pixiApp.renderer.width / 2 + (config.translate?.x ?? 0), pixiApp.renderer.height / 2 + (config.translate?.y ?? 0));
|
|
1010
|
+
pixiApp.stage.addChild(loaded);
|
|
1011
|
+
loaded.on("motionFinish", () => {
|
|
1012
|
+
if (tapPlaying) {
|
|
1013
|
+
tapPlaying = false;
|
|
1014
|
+
playGroup(phaseGroup);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
applyPhase(ctx.phase.get());
|
|
1018
|
+
unsubscribe = ctx.phase.subscribe(applyPhase);
|
|
1019
|
+
};
|
|
1020
|
+
boot().catch(() => {
|
|
1021
|
+
if (!disposed) errorListener?.("load-failed");
|
|
1022
|
+
});
|
|
1023
|
+
return {
|
|
1024
|
+
dispose() {
|
|
1025
|
+
if (disposed) return;
|
|
1026
|
+
disposed = true;
|
|
1027
|
+
unsubscribe?.();
|
|
1028
|
+
model = void 0;
|
|
1029
|
+
if (app !== void 0) {
|
|
1030
|
+
app.destroy(true, { children: true });
|
|
1031
|
+
app = void 0;
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
tap(x, y) {
|
|
1035
|
+
const current = model;
|
|
1036
|
+
if (disposed || current === void 0) return;
|
|
1037
|
+
const hits = current.hitTest(x, y);
|
|
1038
|
+
const allowed = config.hitAreas;
|
|
1039
|
+
if (!(allowed === void 0 ? hits.length > 0 : hits.some((name) => allowed.includes(name)))) return;
|
|
1040
|
+
const group = (current.internalModel.settings.motions ?? {})[TAP_GROUP];
|
|
1041
|
+
if (!Array.isArray(group) || group.length === 0) return;
|
|
1042
|
+
tapPlaying = true;
|
|
1043
|
+
const index = group.length > 1 ? Math.floor(Math.random() * group.length) : 0;
|
|
1044
|
+
current.motion(TAP_GROUP, index);
|
|
1045
|
+
},
|
|
1046
|
+
onError(listener) {
|
|
1047
|
+
errorListener = listener;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
//#endregion
|
|
680
1053
|
//#region \0dsh-css:packages/dsh-pet/src/client/settings-card.module.css.mjs
|
|
681
1054
|
const css$1 = ".kKk9aW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.kKk9aW_card:hover{border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.kKk9aW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.kKk9aW_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.kKk9aW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.kKk9aW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.kKk9aW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.kKk9aW_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_chevronOpen{transform:rotate(180deg)}.kKk9aW_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.kKk9aW_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.kKk9aW_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.kKk9aW_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.kKk9aW_discard,.kKk9aW_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.kKk9aW_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.kKk9aW_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.kKk9aW_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.kKk9aW_discard:disabled,.kKk9aW_save:disabled{opacity:.4;cursor:default}.kKk9aW_discard:focus-visible,.kKk9aW_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.kKk9aW_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.kKk9aW_field+.kKk9aW_field{border-top:1px solid var(--dsw-alias-border-l2)}.kKk9aW_head{align-items:center;gap:8px;display:flex}.kKk9aW_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.kKk9aW_badges{align-items:center;gap:8px;display:inline-flex}.kKk9aW_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.kKk9aW_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.kKk9aW_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.kKk9aW_reset:disabled{cursor:default}.kKk9aW_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.kKk9aW_input,.kKk9aW_select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_input:focus-visible,.kKk9aW_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.kKk9aW_input:disabled,.kKk9aW_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.kKk9aW_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.kKk9aW_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.kKk9aW_selectWrap{position:relative}.kKk9aW_selectButton{appearance:none;text-align:left;cursor:pointer;justify-content:space-between;align-items:center;gap:8px;width:100%;display:flex}.kKk9aW_selectLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.kKk9aW_selectChevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.kKk9aW_selectChevronOpen{transform:rotate(180deg)}.kKk9aW_selectPopup{z-index:40;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);max-height:240px;box-shadow:0 8px 24px var(--dsw-alias-bg-mask-2);opacity:0;border-radius:8px;flex-direction:column;padding:4px;transition:opacity .1s,transform .1s;display:flex;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;transform:translateY(-4px)}.kKk9aW_selectPopupOpen{opacity:1;transform:none}.kKk9aW_selectPopupClose{opacity:0;pointer-events:none;transform:translateY(-4px)}.kKk9aW_selectOption{color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;text-overflow:ellipsis;border-radius:6px;padding:6px 10px;font-size:13px;line-height:1.5;overflow:hidden}.kKk9aW_selectOption:hover,.kKk9aW_selectOptionActive{background:var(--dsw-alias-interactive-bg-hover)}.kKk9aW_selectOptionSelected{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 10%, transparent);font-weight:500}.kKk9aW_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.kKk9aW_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.kKk9aW_card,.kKk9aW_header,.kKk9aW_chevron,.kKk9aW_chevronOpen,.kKk9aW_discard,.kKk9aW_save,.kKk9aW_selectChevron,.kKk9aW_selectChevronOpen,.kKk9aW_selectPopup{transition:none}}";
|
|
682
1055
|
const tagId$1 = "@linxin666/dsh-pet/settings-card.module.css";
|
|
@@ -1461,7 +1834,7 @@ window.__ModuleLoader__.load({
|
|
|
1461
1834
|
};
|
|
1462
1835
|
//#endregion
|
|
1463
1836
|
//#region \0dsh-css:packages/dsh-pet/src/client/settings-section.module.css.mjs
|
|
1464
|
-
const css = ".t0P0pa_sectionList{margin:0;padding:0;list-style:none}";
|
|
1837
|
+
const css = ".t0P0pa_sectionList{margin:0;padding:0;list-style:none}.t0P0pa_diagnostics{border:1px dashed var(--dsw-alias-border-l2);color:var(--dsw-alias-label-dimmed);border-radius:8px;margin:8px 0;padding:8px 12px;font-size:12px}.t0P0pa_diagnosticsTitle{color:var(--dsw-alias-label);margin-bottom:4px;font-weight:600;display:block}.t0P0pa_diagnostics ul{margin:0;padding-left:16px}.t0P0pa_diagnostics li[data-level=error]{color:var(--dsw-alias-error,#c04848)}";
|
|
1465
1838
|
const tagId = "@linxin666/dsh-pet/settings-section.module.css";
|
|
1466
1839
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1467
1840
|
const tag = document.createElement("style");
|
|
@@ -1470,7 +1843,11 @@ window.__ModuleLoader__.load({
|
|
|
1470
1843
|
tag.textContent = css;
|
|
1471
1844
|
document.head.appendChild(tag);
|
|
1472
1845
|
}
|
|
1473
|
-
var settings_section_module_css_default = {
|
|
1846
|
+
var settings_section_module_css_default = {
|
|
1847
|
+
"diagnostics": "t0P0pa_diagnostics",
|
|
1848
|
+
"diagnosticsTitle": "t0P0pa_diagnosticsTitle",
|
|
1849
|
+
"sectionList": "t0P0pa_sectionList"
|
|
1850
|
+
};
|
|
1474
1851
|
//#endregion
|
|
1475
1852
|
//#region src/client/PetSettingsCard.tsx
|
|
1476
1853
|
/** Fetch the registry list (the same data the sprite renders from). */
|
|
@@ -1479,12 +1856,19 @@ window.__ModuleLoader__.load({
|
|
|
1479
1856
|
if (!response.ok) throw new Error("pet pets failed: " + response.status);
|
|
1480
1857
|
return await response.json();
|
|
1481
1858
|
}
|
|
1859
|
+
/** Fetch the registry diagnostics (v1 migration hints, invalid entries). */
|
|
1860
|
+
async function fetchPetDiagnostics() {
|
|
1861
|
+
const response = await fetch("/api/pet/diagnostics");
|
|
1862
|
+
if (!response.ok) throw new Error("pet diagnostics failed: " + response.status);
|
|
1863
|
+
return (await response.json()).diagnostics ?? [];
|
|
1864
|
+
}
|
|
1482
1865
|
/** Bridges the 'pet' scope onto the card's staged form. */
|
|
1483
1866
|
var PetSettingsCardController = class {
|
|
1484
1867
|
form;
|
|
1485
1868
|
store;
|
|
1486
1869
|
petChoices = [];
|
|
1487
1870
|
petLabels = /* @__PURE__ */ new Map();
|
|
1871
|
+
diagnostics = [];
|
|
1488
1872
|
loaded = false;
|
|
1489
1873
|
attempts = 0;
|
|
1490
1874
|
/** @param scope - the bound settings scope for the 'pet' namespace. */
|
|
@@ -1499,6 +1883,16 @@ window.__ModuleLoader__.load({
|
|
|
1499
1883
|
]);
|
|
1500
1884
|
this.store = this.form.bind(() => this.projection());
|
|
1501
1885
|
this.loadPets();
|
|
1886
|
+
this.loadDiagnostics();
|
|
1887
|
+
}
|
|
1888
|
+
/** Fetch registry diagnostics once (soft-fail: an empty list on error). */
|
|
1889
|
+
async loadDiagnostics() {
|
|
1890
|
+
try {
|
|
1891
|
+
this.diagnostics = await fetchPetDiagnostics();
|
|
1892
|
+
this.store.set(this.projection());
|
|
1893
|
+
} catch {
|
|
1894
|
+
this.diagnostics = [];
|
|
1895
|
+
}
|
|
1502
1896
|
}
|
|
1503
1897
|
/** Resolve the registry choices once (retried a few times on failure). */
|
|
1504
1898
|
async loadPets() {
|
|
@@ -1528,7 +1922,8 @@ window.__ModuleLoader__.load({
|
|
|
1528
1922
|
petChoices: this.petChoices.map((id) => ({
|
|
1529
1923
|
value: id,
|
|
1530
1924
|
label: this.petLabels.get(id) ?? id
|
|
1531
|
-
}))
|
|
1925
|
+
})),
|
|
1926
|
+
petDiagnostics: this.diagnostics
|
|
1532
1927
|
};
|
|
1533
1928
|
}
|
|
1534
1929
|
/**
|
|
@@ -1604,6 +1999,17 @@ window.__ModuleLoader__.load({
|
|
|
1604
1999
|
props.resetField("petId");
|
|
1605
2000
|
}
|
|
1606
2001
|
}),
|
|
2002
|
+
state.petDiagnostics.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
2003
|
+
className: settings_section_module_css_default.diagnostics,
|
|
2004
|
+
"data-dsh-part": "diagnostics",
|
|
2005
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2006
|
+
className: settings_section_module_css_default.diagnosticsTitle,
|
|
2007
|
+
children: t("settings.diagnosticsTitle")
|
|
2008
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: state.petDiagnostics.map((diagnostic, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
2009
|
+
"data-level": diagnostic.level,
|
|
2010
|
+
children: diagnostic.message
|
|
2011
|
+
}, index)) })]
|
|
2012
|
+
}),
|
|
1607
2013
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
|
|
1608
2014
|
id: "settings-pet-visible",
|
|
1609
2015
|
label: t("settings.visible"),
|
|
@@ -1695,10 +2101,15 @@ window.__ModuleLoader__.load({
|
|
|
1695
2101
|
"pet.treats": "小鱼干 ×{n}",
|
|
1696
2102
|
"pet.state.loading": "宠物正在赶来…",
|
|
1697
2103
|
"pet.state.error": "宠物迷路了(连接失败)",
|
|
2104
|
+
"pet.renderer.unavailable": "这只宠物需要的渲染器({renderer})在当前版本不可用。",
|
|
2105
|
+
"pet.live2d.core-missing": "Live2D 核心未安装:请把官方 live2dcubismcore.min.js 放入 $DSH_HOME/pets/.runtime/ 后刷新(步骤见宠物插件 README)。",
|
|
2106
|
+
"pet.live2d.vendor-missing": "Live2D 组件缺失,请升级宠物插件。",
|
|
2107
|
+
"pet.live2d.load-failed": "Live2D 模型加载失败,请检查该宠物目录的完整性。",
|
|
1698
2108
|
"pet.openSessionHint": "点击跳转到对应会话",
|
|
1699
2109
|
"pet.moreSessions": "展开其余 {n} 个会话的气泡",
|
|
1700
2110
|
"pet.collapseSessions": "收起会话气泡",
|
|
1701
2111
|
"settings.title": "宠物",
|
|
2112
|
+
"settings.diagnosticsTitle": "宠物目录诊断",
|
|
1702
2113
|
"settings.description": "选择宠物并调整它的显示布局。",
|
|
1703
2114
|
"settings.pet": "宠物",
|
|
1704
2115
|
"settings.petHint": "选择显示哪只宠物;每只宠物独立命名,可在宠物悬浮面板改名。",
|
|
@@ -1741,10 +2152,15 @@ window.__ModuleLoader__.load({
|
|
|
1741
2152
|
"pet.treats": "Treats ×{n}",
|
|
1742
2153
|
"pet.state.loading": "The pet is on its way…",
|
|
1743
2154
|
"pet.state.error": "The pet is lost (connection failed)",
|
|
2155
|
+
"pet.renderer.unavailable": "This pet needs a renderer ({renderer}) that is not available in this build.",
|
|
2156
|
+
"pet.live2d.core-missing": "Live2D Cubism Core is not installed: place the official live2dcubismcore.min.js under $DSH_HOME/pets/.runtime/ and refresh (see the pet plugin README).",
|
|
2157
|
+
"pet.live2d.vendor-missing": "The Live2D component is missing; please update the pet plugin.",
|
|
2158
|
+
"pet.live2d.load-failed": "The Live2D model failed to load; check the pet directory is complete.",
|
|
1744
2159
|
"pet.openSessionHint": "Click to jump to this session",
|
|
1745
2160
|
"pet.moreSessions": "Expand {n} more session bubbles",
|
|
1746
2161
|
"pet.collapseSessions": "Collapse session bubbles",
|
|
1747
2162
|
"settings.title": "Pet",
|
|
2163
|
+
"settings.diagnosticsTitle": "Pet directory diagnostics",
|
|
1748
2164
|
"settings.description": "Pick a pet and tune its display layout.",
|
|
1749
2165
|
"settings.pet": "Pet",
|
|
1750
2166
|
"settings.petHint": "Choose which pet shows. Names are stored per pet; rename from the pet hover panel.",
|
|
@@ -1842,6 +2258,7 @@ window.__ModuleLoader__.load({
|
|
|
1842
2258
|
zh,
|
|
1843
2259
|
en
|
|
1844
2260
|
}), "pet: dictionaries");
|
|
2261
|
+
defaultPetRendererRegistry.register(live2dRenderer);
|
|
1845
2262
|
const settingsScope = (ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: PET_SETTINGS_NS });
|
|
1846
2263
|
const enabled = () => {
|
|
1847
2264
|
const snapshot = settingsScope.getSnapshot();
|