sa2kit 1.5.1 → 1.6.0
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/dist/mmd/admin/index.d.mts +1 -1
- package/dist/mmd/admin/index.d.ts +1 -1
- package/dist/mmd/index.d.mts +26 -3
- package/dist/mmd/index.d.ts +26 -3
- package/dist/mmd/index.js +236 -16
- package/dist/mmd/index.js.map +1 -1
- package/dist/mmd/index.mjs +238 -18
- package/dist/mmd/index.mjs.map +1 -1
- package/dist/mmd/server/index.d.mts +1 -1
- package/dist/mmd/server/index.d.ts +1 -1
- package/dist/{types-Bc_p-zAR.d.mts → types-CsTSddwu.d.mts} +7 -0
- package/dist/{types-Bc_p-zAR.d.ts → types-CsTSddwu.d.ts} +7 -0
- package/package.json +1 -1
package/dist/mmd/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../chunk-BJTO5JO5.mjs';
|
|
2
|
-
import React6, { forwardRef, useRef,
|
|
2
|
+
import React6, { forwardRef, useRef, useEffect, useImperativeHandle, useState, useCallback } from 'react';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { OrbitControls, MMDLoader, MMDAnimationHelper } from 'three-stdlib';
|
|
5
|
-
import { SkipBack, Pause, Play, SkipForward, Repeat, Repeat1, Shuffle, ListMusic, Music, X, Grid3x3, Settings, Minimize, Maximize, Video, Check, User, Image } from 'lucide-react';
|
|
5
|
+
import { SkipBack, Pause, Play, SkipForward, Camera, Repeat, Repeat1, Shuffle, ListMusic, Music, X, Grid3x3, Settings, Minimize, Maximize, Video, Check, User, Image } from 'lucide-react';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
|
|
8
8
|
// src/mmd/utils/ammo-loader.ts
|
|
@@ -163,6 +163,7 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
163
163
|
onPause,
|
|
164
164
|
onEnded,
|
|
165
165
|
onTimeUpdate,
|
|
166
|
+
onCameraChange,
|
|
166
167
|
className,
|
|
167
168
|
style
|
|
168
169
|
} = props;
|
|
@@ -183,6 +184,10 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
183
184
|
const animationClipRef = useRef(null);
|
|
184
185
|
const loopRef = useRef(loop);
|
|
185
186
|
const audioRef = useRef(null);
|
|
187
|
+
const latestCallbacks = useRef({ onPlay, onPause, onEnded, onTimeUpdate });
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
latestCallbacks.current = { onPlay, onPause, onEnded, onTimeUpdate };
|
|
190
|
+
}, [onPlay, onPause, onEnded, onTimeUpdate]);
|
|
186
191
|
const physicsComponentsRef = useRef({
|
|
187
192
|
configs: [],
|
|
188
193
|
dispatchers: [],
|
|
@@ -197,18 +202,18 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
197
202
|
if (!isReadyRef.current) return;
|
|
198
203
|
isPlayingRef.current = true;
|
|
199
204
|
if (!clockRef.current.running) clockRef.current.start();
|
|
200
|
-
onPlay?.();
|
|
205
|
+
latestCallbacks.current.onPlay?.();
|
|
201
206
|
},
|
|
202
207
|
pause: () => {
|
|
203
208
|
if (!isPlayingRef.current) return;
|
|
204
209
|
isPlayingRef.current = false;
|
|
205
210
|
clockRef.current.stop();
|
|
206
|
-
onPause?.();
|
|
211
|
+
latestCallbacks.current.onPause?.();
|
|
207
212
|
},
|
|
208
213
|
stop: () => {
|
|
209
214
|
isPlayingRef.current = false;
|
|
210
215
|
clockRef.current.stop();
|
|
211
|
-
onPause?.();
|
|
216
|
+
latestCallbacks.current.onPause?.();
|
|
212
217
|
},
|
|
213
218
|
seek: (time) => {
|
|
214
219
|
console.warn("Seek not fully implemented in MMDPlayerBase yet");
|
|
@@ -229,6 +234,24 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
229
234
|
rendererRef.current.render(sceneRef.current, cameraRef.current);
|
|
230
235
|
}
|
|
231
236
|
return rendererRef.current.domElement.toDataURL("image/png");
|
|
237
|
+
},
|
|
238
|
+
resetCamera: () => {
|
|
239
|
+
if (!cameraRef.current || !controlsRef.current) return;
|
|
240
|
+
const { cameraPosition, cameraTarget } = stage;
|
|
241
|
+
if (cameraPosition) {
|
|
242
|
+
const pos = cameraPosition;
|
|
243
|
+
cameraRef.current.position.set(pos.x, pos.y, pos.z);
|
|
244
|
+
} else {
|
|
245
|
+
cameraRef.current.position.set(0, 20, 30);
|
|
246
|
+
}
|
|
247
|
+
if (cameraTarget) {
|
|
248
|
+
const target = cameraTarget;
|
|
249
|
+
controlsRef.current.target.set(target.x, target.y, target.z);
|
|
250
|
+
} else {
|
|
251
|
+
controlsRef.current.target.set(0, 10, 0);
|
|
252
|
+
}
|
|
253
|
+
controlsRef.current.update();
|
|
254
|
+
onCameraChange?.(false);
|
|
232
255
|
}
|
|
233
256
|
}));
|
|
234
257
|
useEffect(() => {
|
|
@@ -315,6 +338,11 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
315
338
|
const scene = new THREE.Scene();
|
|
316
339
|
if (stage.backgroundColor) {
|
|
317
340
|
scene.background = new THREE.Color(stage.backgroundColor);
|
|
341
|
+
} else if (stage.backgroundImage) {
|
|
342
|
+
const textureLoader = new THREE.TextureLoader();
|
|
343
|
+
textureLoader.load(stage.backgroundImage, (texture) => {
|
|
344
|
+
scene.background = texture;
|
|
345
|
+
});
|
|
318
346
|
}
|
|
319
347
|
sceneRef.current = scene;
|
|
320
348
|
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 2e3);
|
|
@@ -371,6 +399,9 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
371
399
|
controls.target.set(0, 10, 0);
|
|
372
400
|
}
|
|
373
401
|
controls.update();
|
|
402
|
+
controls.addEventListener("start", () => {
|
|
403
|
+
onCameraChange?.(true);
|
|
404
|
+
});
|
|
374
405
|
controlsRef.current = controls;
|
|
375
406
|
if (showAxes) {
|
|
376
407
|
const axesHelper = new THREE.AxesHelper(20);
|
|
@@ -589,6 +620,25 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
589
620
|
console.log("[MMDPlayerBase] \u2705 Stage materials and textures loaded");
|
|
590
621
|
scene.add(stageMesh);
|
|
591
622
|
console.log("[MMDPlayerBase] \u2705 Stage added to scene (fully loaded)");
|
|
623
|
+
if (resources.stageMotionPath && stageMesh) {
|
|
624
|
+
console.log("[MMDPlayerBase] Loading stage motion:", resources.stageMotionPath);
|
|
625
|
+
const anyLoader = loader;
|
|
626
|
+
const anyHelper = helper;
|
|
627
|
+
const anyStage = stageMesh;
|
|
628
|
+
anyLoader.loadAnimation(
|
|
629
|
+
resources.stageMotionPath,
|
|
630
|
+
anyStage,
|
|
631
|
+
(stageAnimation) => {
|
|
632
|
+
if (checkCancelled()) return;
|
|
633
|
+
anyHelper.add(anyStage, {
|
|
634
|
+
animation: stageAnimation
|
|
635
|
+
});
|
|
636
|
+
console.log("[MMDPlayerBase] \u2705 Stage motion bound successfully");
|
|
637
|
+
},
|
|
638
|
+
void 0,
|
|
639
|
+
(err) => console.error("Failed to load stage motion:", err)
|
|
640
|
+
);
|
|
641
|
+
}
|
|
592
642
|
} catch (err) {
|
|
593
643
|
console.error("Failed to load stage:", err);
|
|
594
644
|
}
|
|
@@ -1034,6 +1084,38 @@ ${errorMessage}
|
|
|
1034
1084
|
audioRef.current.setLoop(loop);
|
|
1035
1085
|
}
|
|
1036
1086
|
}, [loop]);
|
|
1087
|
+
useEffect(() => {
|
|
1088
|
+
if (!isReadyRef.current) return;
|
|
1089
|
+
if (sceneRef.current) {
|
|
1090
|
+
if (stage.backgroundColor) {
|
|
1091
|
+
sceneRef.current.background = new THREE.Color(stage.backgroundColor);
|
|
1092
|
+
} else if (stage.backgroundImage) {
|
|
1093
|
+
const textureLoader = new THREE.TextureLoader();
|
|
1094
|
+
textureLoader.load(stage.backgroundImage, (texture) => {
|
|
1095
|
+
if (sceneRef.current) sceneRef.current.background = texture;
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (sceneRef.current) {
|
|
1100
|
+
sceneRef.current.traverse((obj) => {
|
|
1101
|
+
if (obj instanceof THREE.AmbientLight && stage.ambientLightIntensity !== void 0) {
|
|
1102
|
+
obj.intensity = stage.ambientLightIntensity;
|
|
1103
|
+
}
|
|
1104
|
+
if (obj instanceof THREE.DirectionalLight && stage.directionalLightIntensity !== void 0) {
|
|
1105
|
+
obj.intensity = stage.directionalLightIntensity;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (cameraRef.current && stage.cameraPosition) {
|
|
1110
|
+
const pos = stage.cameraPosition;
|
|
1111
|
+
cameraRef.current.position.set(pos.x, pos.y, pos.z);
|
|
1112
|
+
}
|
|
1113
|
+
if (controlsRef.current && stage.cameraTarget) {
|
|
1114
|
+
const target = stage.cameraTarget;
|
|
1115
|
+
controlsRef.current.target.set(target.x, target.y, target.z);
|
|
1116
|
+
controlsRef.current.update();
|
|
1117
|
+
}
|
|
1118
|
+
}, [stage.backgroundColor, stage.backgroundImage, stage.ambientLightIntensity, stage.directionalLightIntensity, stage.cameraPosition, stage.cameraTarget]);
|
|
1037
1119
|
const animate = () => {
|
|
1038
1120
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
1039
1121
|
if (rendererRef.current && sceneRef.current && cameraRef.current) {
|
|
@@ -1043,11 +1125,11 @@ ${errorMessage}
|
|
|
1043
1125
|
const elapsed = clockRef.current.elapsedTime;
|
|
1044
1126
|
const duration = durationRef.current;
|
|
1045
1127
|
const currentTime = duration > 0 && loopRef.current ? elapsed % duration : elapsed;
|
|
1046
|
-
onTimeUpdate?.(currentTime);
|
|
1128
|
+
latestCallbacks.current.onTimeUpdate?.(currentTime);
|
|
1047
1129
|
if (!loopRef.current && duration > 0 && elapsed >= duration) {
|
|
1048
1130
|
isPlayingRef.current = false;
|
|
1049
1131
|
clockRef.current.stop();
|
|
1050
|
-
onEnded?.();
|
|
1132
|
+
latestCallbacks.current.onEnded?.();
|
|
1051
1133
|
}
|
|
1052
1134
|
}
|
|
1053
1135
|
rendererRef.current.render(sceneRef.current, cameraRef.current);
|
|
@@ -1076,6 +1158,7 @@ var ControlPanel = ({
|
|
|
1076
1158
|
isFullscreen,
|
|
1077
1159
|
isLooping,
|
|
1078
1160
|
isListLooping,
|
|
1161
|
+
isCameraManual = false,
|
|
1079
1162
|
showSettings,
|
|
1080
1163
|
showAxes = false,
|
|
1081
1164
|
showPrevNext = false,
|
|
@@ -1088,7 +1171,8 @@ var ControlPanel = ({
|
|
|
1088
1171
|
onToggleAxes,
|
|
1089
1172
|
onOpenSettings,
|
|
1090
1173
|
onPrevious,
|
|
1091
|
-
onNext
|
|
1174
|
+
onNext,
|
|
1175
|
+
onResetCamera
|
|
1092
1176
|
}) => {
|
|
1093
1177
|
return /* @__PURE__ */ React6.createElement("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 transition-opacity duration-300 hover:opacity-100" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center justify-between text-white" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2" }, showPrevNext && onPrevious && /* @__PURE__ */ React6.createElement(
|
|
1094
1178
|
"button",
|
|
@@ -1130,6 +1214,14 @@ var ControlPanel = ({
|
|
|
1130
1214
|
title: isLooping ? "\u5355\u66F2\u5FAA\u73AF\uFF1A\u5F00\u542F" : "\u5355\u66F2\u5FAA\u73AF\uFF1A\u5173\u95ED"
|
|
1131
1215
|
},
|
|
1132
1216
|
/* @__PURE__ */ React6.createElement(Repeat1, { size: 20 })
|
|
1217
|
+
), isCameraManual && onResetCamera && /* @__PURE__ */ React6.createElement(
|
|
1218
|
+
"button",
|
|
1219
|
+
{
|
|
1220
|
+
onClick: onResetCamera,
|
|
1221
|
+
className: "rounded-full p-2 bg-blue-500/30 text-blue-400 hover:bg-blue-500/50 hover:text-blue-300 transition-all animate-in zoom-in duration-300",
|
|
1222
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
1223
|
+
},
|
|
1224
|
+
/* @__PURE__ */ React6.createElement(Camera, { size: 20 })
|
|
1133
1225
|
), onToggleAxes && /* @__PURE__ */ React6.createElement(
|
|
1134
1226
|
"button",
|
|
1135
1227
|
{
|
|
@@ -1536,6 +1628,7 @@ var MMDPlaylist = ({
|
|
|
1536
1628
|
const [isListLooping, setIsListLooping] = useState(loop);
|
|
1537
1629
|
const [showPlaylist, setShowPlaylist] = useState(false);
|
|
1538
1630
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
1631
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
1539
1632
|
const playerRef = useRef(null);
|
|
1540
1633
|
const containerRef = useRef(null);
|
|
1541
1634
|
const preloadedRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -1687,7 +1780,7 @@ var MMDPlaylist = ({
|
|
|
1687
1780
|
key: currentNode.id,
|
|
1688
1781
|
ref: playerRef,
|
|
1689
1782
|
resources: currentNode.resources,
|
|
1690
|
-
stage,
|
|
1783
|
+
stage: { ...stage, ...currentNode.stage },
|
|
1691
1784
|
autoPlay: autoPlay && currentIndex === 0,
|
|
1692
1785
|
loop: isLooping,
|
|
1693
1786
|
showAxes,
|
|
@@ -1700,6 +1793,7 @@ var MMDPlaylist = ({
|
|
|
1700
1793
|
},
|
|
1701
1794
|
onPlay: () => setIsPlaying(true),
|
|
1702
1795
|
onPause: () => setIsPlaying(false),
|
|
1796
|
+
onCameraChange: (isManual) => setIsCameraManual(isManual),
|
|
1703
1797
|
onEnded: handleEnded,
|
|
1704
1798
|
onError
|
|
1705
1799
|
}
|
|
@@ -1715,6 +1809,7 @@ var MMDPlaylist = ({
|
|
|
1715
1809
|
isFullscreen,
|
|
1716
1810
|
isLooping,
|
|
1717
1811
|
isListLooping,
|
|
1812
|
+
isCameraManual,
|
|
1718
1813
|
showSettings: true,
|
|
1719
1814
|
showAxes,
|
|
1720
1815
|
showPrevNext,
|
|
@@ -1727,7 +1822,11 @@ var MMDPlaylist = ({
|
|
|
1727
1822
|
onToggleLoop: () => setIsLooping(!isLooping),
|
|
1728
1823
|
onToggleListLoop: () => setIsListLooping(!isListLooping),
|
|
1729
1824
|
onToggleAxes: () => setShowAxes(!showAxes),
|
|
1730
|
-
onOpenSettings: () => setShowPlaylist(!showPlaylist)
|
|
1825
|
+
onOpenSettings: () => setShowPlaylist(!showPlaylist),
|
|
1826
|
+
onResetCamera: () => {
|
|
1827
|
+
playerRef.current?.resetCamera();
|
|
1828
|
+
setIsCameraManual(false);
|
|
1829
|
+
}
|
|
1731
1830
|
}
|
|
1732
1831
|
)
|
|
1733
1832
|
), showPlaylist && /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 z-20 flex items-end bg-black/80 backdrop-blur-sm" }, /* @__PURE__ */ React6.createElement("div", { className: "w-full max-h-[60vh] overflow-y-auto bg-gray-900/95 rounded-t-xl" }, /* @__PURE__ */ React6.createElement("div", { className: "sticky top-0 flex items-center justify-between bg-gray-800 px-4 py-3 border-b border-gray-700" }, /* @__PURE__ */ React6.createElement("div", null, /* @__PURE__ */ React6.createElement("h3", { className: "text-white font-semibold" }, playlist.name), /* @__PURE__ */ React6.createElement("p", { className: "text-xs text-gray-400 mt-0.5" }, "\u5171 ", nodes.length, " \u4E2A\u8282\u70B9")), /* @__PURE__ */ React6.createElement(
|
|
@@ -1829,6 +1928,8 @@ var DialogueBox = ({
|
|
|
1829
1928
|
onToggleAuto,
|
|
1830
1929
|
onOpenHistory,
|
|
1831
1930
|
onSkip,
|
|
1931
|
+
onResetCamera,
|
|
1932
|
+
isCameraManual = false,
|
|
1832
1933
|
showControls = true,
|
|
1833
1934
|
showSkipButton = true,
|
|
1834
1935
|
showAutoButton = true,
|
|
@@ -1992,6 +2093,21 @@ var DialogueBox = ({
|
|
|
1992
2093
|
title: "\u5386\u53F2\u8BB0\u5F55"
|
|
1993
2094
|
},
|
|
1994
2095
|
"\u{1F4DC} \u5386\u53F2"
|
|
2096
|
+
), isCameraManual && /* @__PURE__ */ React6.createElement(
|
|
2097
|
+
"button",
|
|
2098
|
+
{
|
|
2099
|
+
onClick: (e) => {
|
|
2100
|
+
e.stopPropagation();
|
|
2101
|
+
onResetCamera?.();
|
|
2102
|
+
},
|
|
2103
|
+
className: "px-4 py-2 text-xs rounded-xl text-white font-medium hover:text-white transition-all backdrop-blur-lg border border-blue-400/50 hover:border-blue-400 hover:scale-105 active:scale-95 shadow-lg animate-in zoom-in duration-300",
|
|
2104
|
+
style: {
|
|
2105
|
+
background: "linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(37, 99, 235, 0.2))",
|
|
2106
|
+
boxShadow: "0 4px 16px rgba(59, 130, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2)"
|
|
2107
|
+
},
|
|
2108
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
2109
|
+
},
|
|
2110
|
+
"\u{1F3A5} \u6062\u590D\u89C6\u89D2"
|
|
1995
2111
|
), showAutoButton && /* @__PURE__ */ React6.createElement(
|
|
1996
2112
|
"button",
|
|
1997
2113
|
{
|
|
@@ -2630,6 +2746,50 @@ var SkipConfirmDialog = ({
|
|
|
2630
2746
|
}
|
|
2631
2747
|
return createPortal(content, portalContainer);
|
|
2632
2748
|
};
|
|
2749
|
+
var ChoiceMenu = ({
|
|
2750
|
+
choices,
|
|
2751
|
+
onSelect,
|
|
2752
|
+
theme
|
|
2753
|
+
}) => {
|
|
2754
|
+
const [isMounted, setIsMounted] = React6.useState(false);
|
|
2755
|
+
React6.useEffect(() => {
|
|
2756
|
+
setIsMounted(true);
|
|
2757
|
+
}, []);
|
|
2758
|
+
if (!isMounted) return null;
|
|
2759
|
+
const content = /* @__PURE__ */ React6.createElement(
|
|
2760
|
+
"div",
|
|
2761
|
+
{
|
|
2762
|
+
className: "fixed inset-0 flex flex-col items-center justify-center bg-black/20 backdrop-blur-sm pointer-events-auto transition-all animate-in fade-in duration-500",
|
|
2763
|
+
style: { zIndex: 1e6 }
|
|
2764
|
+
},
|
|
2765
|
+
/* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-4 w-full max-w-md px-6" }, choices.map((choice, index) => /* @__PURE__ */ React6.createElement(
|
|
2766
|
+
"button",
|
|
2767
|
+
{
|
|
2768
|
+
key: index,
|
|
2769
|
+
onClick: () => onSelect(choice),
|
|
2770
|
+
className: "w-full py-4 px-8 rounded-2xl text-white font-bold text-lg transition-all border border-white/20 shadow-xl hover:scale-105 active:scale-95 group relative overflow-hidden",
|
|
2771
|
+
style: {
|
|
2772
|
+
background: `linear-gradient(135deg,
|
|
2773
|
+
rgba(255, 255, 255, 0.2) 0%,
|
|
2774
|
+
rgba(255, 255, 255, 0.1) 100%)`,
|
|
2775
|
+
backdropFilter: "blur(32px) saturate(200%)",
|
|
2776
|
+
WebkitBackdropFilter: "blur(32px) saturate(200%)"
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
/* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity" }),
|
|
2780
|
+
/* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 ease-in-out" }),
|
|
2781
|
+
/* @__PURE__ */ React6.createElement("span", { className: "relative z-10 drop-shadow-md" }, choice.text)
|
|
2782
|
+
)))
|
|
2783
|
+
);
|
|
2784
|
+
let portalContainer = document.getElementById("dialogue-portal-root");
|
|
2785
|
+
if (!portalContainer) {
|
|
2786
|
+
portalContainer = document.createElement("div");
|
|
2787
|
+
portalContainer.id = "dialogue-portal-root";
|
|
2788
|
+
portalContainer.style.cssText = "position: fixed; inset: 0; pointer-events: none; z-index: 999999;";
|
|
2789
|
+
document.body.appendChild(portalContainer);
|
|
2790
|
+
}
|
|
2791
|
+
return createPortal(content, portalContainer);
|
|
2792
|
+
};
|
|
2633
2793
|
|
|
2634
2794
|
// src/mmd/visual-novel/MMDVisualNovel.tsx
|
|
2635
2795
|
var MMDVisualNovel = forwardRef(
|
|
@@ -2665,12 +2825,15 @@ var MMDVisualNovel = forwardRef(
|
|
|
2665
2825
|
const [isStarted, setIsStarted] = useState(autoStart);
|
|
2666
2826
|
const [isVmdFinished, setIsVmdFinished] = useState(false);
|
|
2667
2827
|
const [pendingNodeIndex, setPendingNodeIndex] = useState(null);
|
|
2828
|
+
const [showChoices, setShowChoices] = useState(false);
|
|
2829
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
2668
2830
|
const playerRef = useRef(null);
|
|
2669
2831
|
const containerRef = useRef(null);
|
|
2670
2832
|
const autoTimerRef = useRef(null);
|
|
2671
2833
|
const typingCompleteRef = useRef(false);
|
|
2672
2834
|
const isStartedRef = useRef(autoStart);
|
|
2673
2835
|
const lastAnimationTimeRef = useRef(0);
|
|
2836
|
+
const isVmdFinishedRef = useRef(false);
|
|
2674
2837
|
const currentNode = nodes[currentNodeIndex];
|
|
2675
2838
|
const currentDialogue = currentNode?.dialogues[currentDialogueIndex] || null;
|
|
2676
2839
|
const addToHistory = useCallback((dialogue, nodeIndex, dialogueIndex) => {
|
|
@@ -2698,6 +2861,8 @@ var MMDVisualNovel = forwardRef(
|
|
|
2698
2861
|
addToHistory(nextDialogue, currentNodeIndex, nextDialogueIndex);
|
|
2699
2862
|
onDialogueChange?.(nextDialogue, nextDialogueIndex, currentNodeIndex);
|
|
2700
2863
|
typingCompleteRef.current = false;
|
|
2864
|
+
} else if (currentNode.choices && currentNode.choices.length > 0) {
|
|
2865
|
+
setShowChoices(true);
|
|
2701
2866
|
} else {
|
|
2702
2867
|
const nextNodeIndex = currentNodeIndex + 1;
|
|
2703
2868
|
if (nextNodeIndex < nodes.length) {
|
|
@@ -2716,7 +2881,7 @@ var MMDVisualNovel = forwardRef(
|
|
|
2716
2881
|
const node = nodes[nodeIndex];
|
|
2717
2882
|
if (!node) return;
|
|
2718
2883
|
const currentResources = nodes[currentNodeIndex]?.resources;
|
|
2719
|
-
if (!force && currentResources?.motionPath && !
|
|
2884
|
+
if (!force && currentResources?.motionPath && !isVmdFinishedRef.current) {
|
|
2720
2885
|
console.log("[MMDVisualNovel] VMD not finished, showing confirmation");
|
|
2721
2886
|
setPendingNodeIndex(nodeIndex);
|
|
2722
2887
|
return;
|
|
@@ -2726,7 +2891,9 @@ var MMDVisualNovel = forwardRef(
|
|
|
2726
2891
|
setIsLoading(true);
|
|
2727
2892
|
setIsAnimationPlaying(false);
|
|
2728
2893
|
setIsVmdFinished(false);
|
|
2894
|
+
isVmdFinishedRef.current = false;
|
|
2729
2895
|
setPendingNodeIndex(null);
|
|
2896
|
+
setShowChoices(false);
|
|
2730
2897
|
lastAnimationTimeRef.current = 0;
|
|
2731
2898
|
setTimeout(() => {
|
|
2732
2899
|
setCurrentNodeIndex(nodeIndex);
|
|
@@ -2794,6 +2961,10 @@ var MMDVisualNovel = forwardRef(
|
|
|
2794
2961
|
setIsAutoMode((prev) => !prev);
|
|
2795
2962
|
}, []);
|
|
2796
2963
|
const handleSkip = useCallback(() => {
|
|
2964
|
+
if (currentNode?.choices && currentNode.choices.length > 0) {
|
|
2965
|
+
setShowChoices(true);
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2797
2968
|
const nextNodeIndex = currentNodeIndex + 1;
|
|
2798
2969
|
if (nextNodeIndex < nodes.length) {
|
|
2799
2970
|
goToNode(nextNodeIndex);
|
|
@@ -2867,7 +3038,7 @@ var MMDVisualNovel = forwardRef(
|
|
|
2867
3038
|
key: currentNode.id,
|
|
2868
3039
|
ref: playerRef,
|
|
2869
3040
|
resources: currentNode.resources,
|
|
2870
|
-
stage,
|
|
3041
|
+
stage: { ...stage, ...currentNode.stage },
|
|
2871
3042
|
autoPlay: isStarted,
|
|
2872
3043
|
loop: currentNode.loopAnimation === true,
|
|
2873
3044
|
mobileOptimization,
|
|
@@ -2886,9 +3057,13 @@ var MMDVisualNovel = forwardRef(
|
|
|
2886
3057
|
setIsAnimationPlaying(true);
|
|
2887
3058
|
},
|
|
2888
3059
|
onTimeUpdate: (time) => {
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
3060
|
+
const duration = playerRef.current?.getDuration() || 0;
|
|
3061
|
+
const isNearEnd = duration > 0 && time > duration * 0.98;
|
|
3062
|
+
const isLooped = time < lastAnimationTimeRef.current && lastAnimationTimeRef.current > 0;
|
|
3063
|
+
if (isNearEnd || isLooped) {
|
|
3064
|
+
if (!isVmdFinishedRef.current) {
|
|
3065
|
+
console.log("[MMDVisualNovel] VMD finished/looped, marking as finished");
|
|
3066
|
+
isVmdFinishedRef.current = true;
|
|
2892
3067
|
setIsVmdFinished(true);
|
|
2893
3068
|
}
|
|
2894
3069
|
}
|
|
@@ -2896,8 +3071,12 @@ var MMDVisualNovel = forwardRef(
|
|
|
2896
3071
|
},
|
|
2897
3072
|
onEnded: () => {
|
|
2898
3073
|
console.log("[MMDVisualNovel] VMD ended, marking as finished");
|
|
3074
|
+
isVmdFinishedRef.current = true;
|
|
2899
3075
|
setIsVmdFinished(true);
|
|
2900
3076
|
},
|
|
3077
|
+
onCameraChange: (isManual) => {
|
|
3078
|
+
setIsCameraManual(isManual);
|
|
3079
|
+
},
|
|
2901
3080
|
onError
|
|
2902
3081
|
}
|
|
2903
3082
|
)
|
|
@@ -2924,12 +3103,13 @@ var MMDVisualNovel = forwardRef(
|
|
|
2924
3103
|
}
|
|
2925
3104
|
),
|
|
2926
3105
|
(() => {
|
|
2927
|
-
const shouldShow = isStarted && isAnimationPlaying && currentDialogue && !showHistory;
|
|
3106
|
+
const shouldShow = isStarted && isAnimationPlaying && currentDialogue && !showHistory && !showChoices;
|
|
2928
3107
|
console.log("[MMDVisualNovel] DialogueBox render condition:", {
|
|
2929
3108
|
isStarted,
|
|
2930
3109
|
isAnimationPlaying,
|
|
2931
3110
|
hasDialogue: !!currentDialogue,
|
|
2932
3111
|
showHistory,
|
|
3112
|
+
showChoices,
|
|
2933
3113
|
shouldShow,
|
|
2934
3114
|
dialogue: currentDialogue
|
|
2935
3115
|
});
|
|
@@ -2947,6 +3127,11 @@ var MMDVisualNovel = forwardRef(
|
|
|
2947
3127
|
onToggleAuto: toggleAutoMode,
|
|
2948
3128
|
onOpenHistory: () => setShowHistory(true),
|
|
2949
3129
|
onSkip: handleSkip,
|
|
3130
|
+
onResetCamera: () => {
|
|
3131
|
+
playerRef.current?.resetCamera();
|
|
3132
|
+
setIsCameraManual(false);
|
|
3133
|
+
},
|
|
3134
|
+
isCameraManual,
|
|
2950
3135
|
showControls: true,
|
|
2951
3136
|
showSkipButton,
|
|
2952
3137
|
showAutoButton,
|
|
@@ -2967,6 +3152,22 @@ var MMDVisualNovel = forwardRef(
|
|
|
2967
3152
|
}
|
|
2968
3153
|
}
|
|
2969
3154
|
),
|
|
3155
|
+
showChoices && currentNode.choices && /* @__PURE__ */ React6.createElement(
|
|
3156
|
+
ChoiceMenu,
|
|
3157
|
+
{
|
|
3158
|
+
choices: currentNode.choices,
|
|
3159
|
+
theme: dialogueTheme,
|
|
3160
|
+
onSelect: (choice) => {
|
|
3161
|
+
choice.onSelect?.();
|
|
3162
|
+
if (choice.nextNodeIndex === currentNodeIndex) {
|
|
3163
|
+
goToDialogue(choice.nextDialogueIndex || 0);
|
|
3164
|
+
setShowChoices(false);
|
|
3165
|
+
} else {
|
|
3166
|
+
goToNode(choice.nextNodeIndex, true);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
),
|
|
2970
3171
|
showHistory && /* @__PURE__ */ React6.createElement(
|
|
2971
3172
|
HistoryPanel,
|
|
2972
3173
|
{
|
|
@@ -2984,12 +3185,14 @@ var MusicControls = ({
|
|
|
2984
3185
|
currentTime,
|
|
2985
3186
|
duration,
|
|
2986
3187
|
loopMode,
|
|
3188
|
+
isCameraManual = false,
|
|
2987
3189
|
onPlayPause,
|
|
2988
3190
|
onNext,
|
|
2989
3191
|
onPrevious,
|
|
2990
3192
|
onSeek,
|
|
2991
3193
|
onToggleLoop,
|
|
2992
3194
|
onTogglePlaylist,
|
|
3195
|
+
onResetCamera,
|
|
2993
3196
|
className = ""
|
|
2994
3197
|
}) => {
|
|
2995
3198
|
const formatTime = (seconds) => {
|
|
@@ -3041,7 +3244,15 @@ var MusicControls = ({
|
|
|
3041
3244
|
className: "text-white/80 hover:text-white transition-colors"
|
|
3042
3245
|
},
|
|
3043
3246
|
/* @__PURE__ */ React6.createElement(SkipForward, { className: "w-6 h-6 fill-current" })
|
|
3044
|
-
)), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-4 w-32 justify-end" }, /* @__PURE__ */ React6.createElement(
|
|
3247
|
+
)), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-4 w-32 justify-end" }, isCameraManual && /* @__PURE__ */ React6.createElement(
|
|
3248
|
+
"button",
|
|
3249
|
+
{
|
|
3250
|
+
onClick: onResetCamera,
|
|
3251
|
+
className: "text-blue-400 hover:text-blue-300 transition-colors animate-in zoom-in duration-300",
|
|
3252
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
3253
|
+
},
|
|
3254
|
+
/* @__PURE__ */ React6.createElement(Camera, { className: "w-5 h-5" })
|
|
3255
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3045
3256
|
"button",
|
|
3046
3257
|
{
|
|
3047
3258
|
onClick: onToggleLoop,
|
|
@@ -3158,6 +3369,7 @@ var MMDMusicPlayer = forwardRef(
|
|
|
3158
3369
|
const [loopMode, setLoopMode] = useState(defaultLoopMode);
|
|
3159
3370
|
const [showPlaylist, setShowPlaylist] = useState(false);
|
|
3160
3371
|
const [isUIVisible, setIsUIVisible] = useState(true);
|
|
3372
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
3161
3373
|
const playerRef = useRef(null);
|
|
3162
3374
|
const containerRef = useRef(null);
|
|
3163
3375
|
const isStartedRef = useRef(autoPlay);
|
|
@@ -3291,7 +3503,7 @@ var MMDMusicPlayer = forwardRef(
|
|
|
3291
3503
|
key: currentTrack.id,
|
|
3292
3504
|
ref: playerRef,
|
|
3293
3505
|
resources: currentTrack.resources,
|
|
3294
|
-
stage,
|
|
3506
|
+
stage: { ...stage, ...currentTrack.stage },
|
|
3295
3507
|
autoPlay: isStartedRef.current,
|
|
3296
3508
|
loop: loopMode === "single",
|
|
3297
3509
|
mobileOptimization,
|
|
@@ -3311,6 +3523,9 @@ var MMDMusicPlayer = forwardRef(
|
|
|
3311
3523
|
setIsPlaying(false);
|
|
3312
3524
|
onPlayPause?.(false);
|
|
3313
3525
|
},
|
|
3526
|
+
onCameraChange: (isManual) => {
|
|
3527
|
+
setIsCameraManual(isManual);
|
|
3528
|
+
},
|
|
3314
3529
|
onTimeUpdate: handleTimeUpdate,
|
|
3315
3530
|
onEnded: handleEnded,
|
|
3316
3531
|
onError
|
|
@@ -3331,10 +3546,15 @@ var MMDMusicPlayer = forwardRef(
|
|
|
3331
3546
|
currentTime,
|
|
3332
3547
|
duration,
|
|
3333
3548
|
loopMode,
|
|
3549
|
+
isCameraManual,
|
|
3334
3550
|
onPlayPause: () => isPlaying ? playerRef.current?.pause() : playerRef.current?.play(),
|
|
3335
3551
|
onNext: next,
|
|
3336
3552
|
onPrevious: previous,
|
|
3337
3553
|
onSeek: (time) => playerRef.current?.seek(time),
|
|
3554
|
+
onResetCamera: () => {
|
|
3555
|
+
playerRef.current?.resetCamera();
|
|
3556
|
+
setIsCameraManual(false);
|
|
3557
|
+
},
|
|
3338
3558
|
onToggleLoop: () => {
|
|
3339
3559
|
const modes = ["list", "single", "shuffle"];
|
|
3340
3560
|
const nextMode = modes[(modes.indexOf(loopMode) + 1) % modes.length];
|