sa2kit 1.6.0 → 1.6.2

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.
Files changed (34) hide show
  1. package/README.md +93 -94
  2. package/dist/AliyunOSSProvider-2UZRIGT3.mjs +6 -0
  3. package/dist/{AliyunOSSProvider-GQMSDJGZ.mjs.map → AliyunOSSProvider-2UZRIGT3.mjs.map} +1 -1
  4. package/dist/AliyunOSSProvider-XCTK3QQA.js +15 -0
  5. package/dist/{AliyunOSSProvider-7JLMJDXK.js.map → AliyunOSSProvider-XCTK3QQA.js.map} +1 -1
  6. package/dist/{chunk-3RFBUDRA.js → chunk-EXT3IKQA.js} +39 -6
  7. package/dist/chunk-EXT3IKQA.js.map +1 -0
  8. package/dist/{chunk-YVBU7QDJ.mjs → chunk-OHUE7EL6.mjs} +34 -5
  9. package/dist/chunk-OHUE7EL6.mjs.map +1 -0
  10. package/dist/mmd/admin/index.d.mts +1 -1
  11. package/dist/mmd/admin/index.d.ts +1 -1
  12. package/dist/mmd/admin/index.js +7 -1
  13. package/dist/mmd/admin/index.js.map +1 -1
  14. package/dist/mmd/admin/index.mjs +7 -1
  15. package/dist/mmd/admin/index.mjs.map +1 -1
  16. package/dist/mmd/index.d.mts +52 -6
  17. package/dist/mmd/index.d.ts +52 -6
  18. package/dist/mmd/index.js +276 -74
  19. package/dist/mmd/index.js.map +1 -1
  20. package/dist/mmd/index.mjs +277 -76
  21. package/dist/mmd/index.mjs.map +1 -1
  22. package/dist/mmd/server/index.d.mts +1 -1
  23. package/dist/mmd/server/index.d.ts +1 -1
  24. package/dist/{types-CsTSddwu.d.mts → types-DxYJqqes.d.mts} +34 -2
  25. package/dist/{types-CsTSddwu.d.ts → types-DxYJqqes.d.ts} +34 -2
  26. package/dist/universalFile/server/index.d.mts +15 -0
  27. package/dist/universalFile/server/index.d.ts +15 -0
  28. package/dist/universalFile/server/index.js +3 -3
  29. package/dist/universalFile/server/index.mjs +2 -2
  30. package/package.json +4 -1
  31. package/dist/AliyunOSSProvider-7JLMJDXK.js +0 -15
  32. package/dist/AliyunOSSProvider-GQMSDJGZ.mjs +0 -6
  33. package/dist/chunk-3RFBUDRA.js.map +0 -1
  34. package/dist/chunk-YVBU7QDJ.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import '../chunk-BJTO5JO5.mjs';
2
2
  import React6, { forwardRef, useRef, useEffect, useImperativeHandle, useState, useCallback } from 'react';
3
3
  import * as THREE from 'three';
4
- import { OrbitControls, MMDLoader, MMDAnimationHelper } from 'three-stdlib';
4
+ import { UnrealBloomPass, OutlineEffect, EffectComposer, RenderPass, OrbitControls, MMDLoader, MMDAnimationHelper } from 'three-stdlib';
5
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
 
@@ -138,8 +138,37 @@ async function waitForMaterialsReady(object, renderer, scene, camera) {
138
138
  for (let i = 0; i < 3; i++) {
139
139
  await new Promise((resolve) => {
140
140
  requestAnimationFrame(() => {
141
- renderer.render(scene, camera);
142
- console.log(`[MMDPlayerBase] Warmup render ${i + 1}/3`);
141
+ try {
142
+ object.traverse((obj) => {
143
+ if (obj.isMesh) {
144
+ const mesh = obj;
145
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
146
+ const hasMorphAttributes = mesh.geometry.morphAttributes && Object.keys(mesh.geometry.morphAttributes).length > 0;
147
+ materials.forEach((m) => {
148
+ if (!hasMorphAttributes) {
149
+ m.morphTargets = false;
150
+ if (mesh.geometry.morphAttributes) {
151
+ mesh.geometry.morphAttributes = {};
152
+ }
153
+ if (mesh.morphTargetInfluences) {
154
+ mesh.morphTargetInfluences = [];
155
+ }
156
+ if (mesh.morphTargetDictionary) {
157
+ mesh.morphTargetDictionary = {};
158
+ }
159
+ m.needsUpdate = true;
160
+ }
161
+ });
162
+ if (mesh.updateMorphTargets) {
163
+ mesh.updateMorphTargets();
164
+ }
165
+ }
166
+ });
167
+ renderer.render(scene, camera);
168
+ console.log(`[MMDPlayerBase] Warmup render ${i + 1}/3`);
169
+ } catch (renderError) {
170
+ console.warn("[MMDPlayerBase] Warmup render failed (shader error?), skipping...", renderError);
171
+ }
143
172
  resolve();
144
173
  });
145
174
  });
@@ -167,10 +196,15 @@ var MMDPlayerBase = forwardRef((props, ref) => {
167
196
  className,
168
197
  style
169
198
  } = props;
199
+ const renderEffect = props.renderEffect || stage.renderEffect || "default";
200
+ const outlineOptions = { ...stage.outlineOptions, ...props.outlineOptions };
201
+ const bloomOptions = { ...stage.bloomOptions, ...props.bloomOptions };
170
202
  const containerRef = useRef(null);
171
203
  const sceneRef = useRef(null);
172
204
  const cameraRef = useRef(null);
173
205
  const rendererRef = useRef(null);
206
+ const outlineEffectRef = useRef(null);
207
+ const composerRef = useRef(null);
174
208
  const controlsRef = useRef(null);
175
209
  const helperRef = useRef(null);
176
210
  const axesHelperRef = useRef(null);
@@ -378,10 +412,28 @@ var MMDPlayerBase = forwardRef((props, ref) => {
378
412
  }
379
413
  container.appendChild(renderer.domElement);
380
414
  rendererRef.current = renderer;
415
+ const effect = new OutlineEffect(renderer, {
416
+ defaultThickness: outlineOptions.thickness ?? 3e-3,
417
+ defaultColor: new THREE.Color(outlineOptions.color ?? "#000000").toArray(),
418
+ defaultAlpha: 1,
419
+ defaultKeepAlive: true
420
+ });
421
+ outlineEffectRef.current = effect;
422
+ const composer = new EffectComposer(renderer);
423
+ const renderPass = new RenderPass(scene, camera);
424
+ composer.addPass(renderPass);
425
+ const bloomPass = new UnrealBloomPass(
426
+ new THREE.Vector2(width, height),
427
+ bloomOptions.strength ?? 1,
428
+ bloomOptions.radius ?? 0.4,
429
+ bloomOptions.threshold ?? 0.8
430
+ );
431
+ composer.addPass(bloomPass);
432
+ composerRef.current = composer;
381
433
  const ambientLight = new THREE.AmbientLight(16777215, stage.ambientLightIntensity ?? 0.5);
382
434
  scene.add(ambientLight);
383
435
  const dirLight = new THREE.DirectionalLight(16777215, stage.directionalLightIntensity ?? 0.8);
384
- dirLight.position.set(10, 20, 10);
436
+ dirLight.position.set(0, 10, 0);
385
437
  if (stage.enableShadow !== false) {
386
438
  dirLight.castShadow = true;
387
439
  dirLight.shadow.mapSize.width = mobileOptimization.enabled ? 1024 : 2048;
@@ -416,6 +468,9 @@ var MMDPlayerBase = forwardRef((props, ref) => {
416
468
  cameraRef.current.aspect = w / h;
417
469
  cameraRef.current.updateProjectionMatrix();
418
470
  rendererRef.current.setSize(w, h);
471
+ if (composerRef.current) {
472
+ composerRef.current.setSize(w, h);
473
+ }
419
474
  };
420
475
  const resizeObserver = new ResizeObserver(onResize);
421
476
  resizeObserver.observe(container);
@@ -423,7 +478,11 @@ var MMDPlayerBase = forwardRef((props, ref) => {
423
478
  onResize();
424
479
  console.log("[MMDPlayerBase] Starting render loop (animation paused)");
425
480
  animate();
426
- console.log("[MMDPlayerBase] Start loading resources...", resources);
481
+ console.log("[MMDPlayerBase] Start loading resources...", {
482
+ model: resources.modelPath,
483
+ stage: resources.stageModelPath,
484
+ motion: resources.motionPath
485
+ });
427
486
  const loader = new MMDLoader();
428
487
  const helper = new MMDAnimationHelper({
429
488
  afterglow: 2
@@ -596,51 +655,67 @@ var MMDPlayerBase = forwardRef((props, ref) => {
596
655
  (err) => console.error("Failed to load audio:", err)
597
656
  );
598
657
  }
599
- let stageMesh = null;
600
- if (resources.stageModelPath) {
658
+ const stagePaths = Array.isArray(resources.stageModelPath) ? resources.stageModelPath : resources.stageModelPath ? [resources.stageModelPath] : [];
659
+ for (const stagePath of stagePaths) {
601
660
  try {
602
- stageMesh = await new Promise((resolve, reject) => {
661
+ console.log(`[MMDPlayerBase] Loading stage from: ${stagePath}`);
662
+ const stageMesh = await new Promise((resolve, reject) => {
603
663
  loader.load(
604
- resources.stageModelPath,
664
+ stagePath,
605
665
  (mesh2) => resolve(mesh2),
606
- void 0,
666
+ (xhr) => {
667
+ if (xhr.lengthComputable) {
668
+ const percent = xhr.loaded / xhr.total * 100;
669
+ if (Math.round(percent) % 20 === 0) console.log(`[MMDPlayerBase] Stage loading: ${percent.toFixed(1)}%`);
670
+ }
671
+ },
607
672
  (err) => reject(err)
608
673
  );
609
674
  });
610
675
  if (checkCancelled()) return;
611
- console.log("[MMDPlayerBase] Stage model loaded:", stageMesh);
612
- stageMesh.castShadow = true;
613
- stageMesh.receiveShadow = true;
614
- console.log("[MMDPlayerBase] Waiting for stage materials and textures...");
615
- const tempStageScene = new THREE.Scene();
616
- tempStageScene.add(stageMesh);
617
- await waitForMaterialsReady(stageMesh, renderer, tempStageScene, camera);
618
- tempStageScene.remove(stageMesh);
676
+ console.log(`[MMDPlayerBase] Stage model loaded: ${stagePath}`, stageMesh);
677
+ stageMesh.traverse((child) => {
678
+ if (child instanceof THREE.Mesh) {
679
+ child.castShadow = true;
680
+ child.receiveShadow = true;
681
+ const mesh2 = child;
682
+ const materials = Array.isArray(mesh2.material) ? mesh2.material : [mesh2.material];
683
+ materials.forEach((m, idx) => {
684
+ if (m.morphTargets) {
685
+ m.morphTargets = false;
686
+ m.needsUpdate = true;
687
+ }
688
+ });
689
+ if (mesh2.geometry.morphAttributes) {
690
+ mesh2.geometry.morphAttributes = {};
691
+ }
692
+ if (mesh2.morphTargetInfluences) {
693
+ mesh2.morphTargetInfluences = [];
694
+ }
695
+ }
696
+ });
697
+ try {
698
+ await waitForMaterialsReady(stageMesh, renderer, scene, camera);
699
+ } catch (e) {
700
+ console.warn(`[MMDPlayerBase] Warmup error for stage ${stagePath}:`, e);
701
+ }
619
702
  if (checkCancelled()) return;
620
- console.log("[MMDPlayerBase] \u2705 Stage materials and textures loaded");
621
703
  scene.add(stageMesh);
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
- );
704
+ const stageBox = new THREE.Box3().setFromObject(stageMesh);
705
+ const stageSize = stageBox.getSize(new THREE.Vector3());
706
+ if (stageSize.length() < 1) {
707
+ stageMesh.scale.multiplyScalar(100);
708
+ } else if (stageSize.y < 5) {
709
+ stageMesh.scale.multiplyScalar(10);
710
+ }
711
+ console.log(`[MMDPlayerBase] \u2705 Stage added: ${stagePath}`);
712
+ if (resources.stageMotionPath) {
713
+ loader.loadAnimation(resources.stageMotionPath, stageMesh, (anim) => {
714
+ if (!checkCancelled()) helper.add(stageMesh, { animation: anim });
715
+ });
641
716
  }
642
717
  } catch (err) {
643
- console.error("Failed to load stage:", err);
718
+ console.error(`Failed to load stage ${stagePath}:`, err);
644
719
  }
645
720
  }
646
721
  if (checkCancelled()) return;
@@ -1018,6 +1093,13 @@ ${errorMessage}
1018
1093
  }
1019
1094
  if (rendererRef.current) {
1020
1095
  try {
1096
+ if (composerRef.current) {
1097
+ composerRef.current.passes.forEach((pass) => {
1098
+ if (pass.dispose) pass.dispose();
1099
+ });
1100
+ composerRef.current = null;
1101
+ }
1102
+ outlineEffectRef.current = null;
1021
1103
  const renderer = rendererRef.current;
1022
1104
  if (renderer.renderLists) {
1023
1105
  renderer.renderLists.dispose();
@@ -1084,6 +1166,17 @@ ${errorMessage}
1084
1166
  audioRef.current.setLoop(loop);
1085
1167
  }
1086
1168
  }, [loop]);
1169
+ useEffect(() => {
1170
+ if (outlineEffectRef.current) ;
1171
+ if (composerRef.current) {
1172
+ const bloomPass = composerRef.current.passes.find((p) => p instanceof UnrealBloomPass);
1173
+ if (bloomPass) {
1174
+ bloomPass.strength = bloomOptions.strength ?? 1;
1175
+ bloomPass.radius = bloomOptions.radius ?? 0.4;
1176
+ bloomPass.threshold = bloomOptions.threshold ?? 0.8;
1177
+ }
1178
+ }
1179
+ }, [bloomOptions.strength, bloomOptions.radius, bloomOptions.threshold]);
1087
1180
  useEffect(() => {
1088
1181
  if (!isReadyRef.current) return;
1089
1182
  if (sceneRef.current) {
@@ -1132,7 +1225,15 @@ ${errorMessage}
1132
1225
  latestCallbacks.current.onEnded?.();
1133
1226
  }
1134
1227
  }
1135
- rendererRef.current.render(sceneRef.current, cameraRef.current);
1228
+ const useOutline = renderEffect === "outline" || renderEffect === "outline+bloom";
1229
+ const useBloom = renderEffect === "bloom" || renderEffect === "outline+bloom";
1230
+ if (useBloom && composerRef.current) {
1231
+ composerRef.current.render();
1232
+ } else if (useOutline && outlineEffectRef.current) {
1233
+ outlineEffectRef.current.render(sceneRef.current, cameraRef.current);
1234
+ } else {
1235
+ rendererRef.current.render(sceneRef.current, cameraRef.current);
1236
+ }
1136
1237
  }
1137
1238
  };
1138
1239
  return /* @__PURE__ */ React6.createElement(
@@ -2827,6 +2928,8 @@ var MMDVisualNovel = forwardRef(
2827
2928
  const [pendingNodeIndex, setPendingNodeIndex] = useState(null);
2828
2929
  const [showChoices, setShowChoices] = useState(false);
2829
2930
  const [isCameraManual, setIsCameraManual] = useState(false);
2931
+ const [variables, setVariables] = useState({});
2932
+ const [activeEffect, setActiveEffect] = useState(null);
2830
2933
  const playerRef = useRef(null);
2831
2934
  const containerRef = useRef(null);
2832
2935
  const autoTimerRef = useRef(null);
@@ -2834,6 +2937,7 @@ var MMDVisualNovel = forwardRef(
2834
2937
  const isStartedRef = useRef(autoStart);
2835
2938
  const lastAnimationTimeRef = useRef(0);
2836
2939
  const isVmdFinishedRef = useRef(false);
2940
+ const effectTimerRef = useRef(null);
2837
2941
  const currentNode = nodes[currentNodeIndex];
2838
2942
  const currentDialogue = currentNode?.dialogues[currentDialogueIndex] || null;
2839
2943
  const addToHistory = useCallback((dialogue, nodeIndex, dialogueIndex) => {
@@ -2848,32 +2952,17 @@ var MMDVisualNovel = forwardRef(
2848
2952
  }
2849
2953
  ]);
2850
2954
  }, []);
2851
- const goToNextDialogue = useCallback(() => {
2852
- if (!currentNode) return;
2853
- if (autoTimerRef.current) {
2854
- clearTimeout(autoTimerRef.current);
2855
- autoTimerRef.current = null;
2955
+ const triggerEffect = useCallback((effect) => {
2956
+ if (!effect) return;
2957
+ if (effectTimerRef.current) {
2958
+ clearTimeout(effectTimerRef.current);
2856
2959
  }
2857
- const nextDialogueIndex = currentDialogueIndex + 1;
2858
- if (nextDialogueIndex < currentNode.dialogues.length && currentNode?.dialogues[nextDialogueIndex] !== void 0) {
2859
- const nextDialogue = currentNode.dialogues[nextDialogueIndex];
2860
- setCurrentDialogueIndex(nextDialogueIndex);
2861
- addToHistory(nextDialogue, currentNodeIndex, nextDialogueIndex);
2862
- onDialogueChange?.(nextDialogue, nextDialogueIndex, currentNodeIndex);
2863
- typingCompleteRef.current = false;
2864
- } else if (currentNode.choices && currentNode.choices.length > 0) {
2865
- setShowChoices(true);
2866
- } else {
2867
- const nextNodeIndex = currentNodeIndex + 1;
2868
- if (nextNodeIndex < nodes.length) {
2869
- goToNode(nextNodeIndex);
2870
- } else if (loop) {
2871
- goToNode(0);
2872
- } else {
2873
- onScriptComplete?.();
2874
- }
2875
- }
2876
- }, [currentNode, currentDialogueIndex, currentNodeIndex, nodes.length, loop, addToHistory, onDialogueChange, onScriptComplete]);
2960
+ setActiveEffect(effect);
2961
+ effectTimerRef.current = setTimeout(() => {
2962
+ setActiveEffect(null);
2963
+ effectTimerRef.current = null;
2964
+ }, effect.duration || 1e3);
2965
+ }, []);
2877
2966
  const goToNode = useCallback(
2878
2967
  (nodeIndex, force = false) => {
2879
2968
  if (nodeIndex < 0 || nodeIndex >= nodes.length) return;
@@ -2912,8 +3001,52 @@ var MMDVisualNovel = forwardRef(
2912
3001
  }, 100);
2913
3002
  }, 300);
2914
3003
  },
2915
- [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange, currentNodeIndex, isVmdFinished]
3004
+ [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange, currentNodeIndex]
2916
3005
  );
3006
+ const triggerNodeTransition = useCallback(() => {
3007
+ if (!currentNode) return;
3008
+ let nextNodeIndex = currentNodeIndex + 1;
3009
+ if (currentNode.nextCondition) {
3010
+ const { key, map, defaultIndex } = currentNode.nextCondition;
3011
+ const val = variables[key];
3012
+ if (val !== void 0 && map[val] !== void 0) {
3013
+ nextNodeIndex = map[val];
3014
+ console.log(`[MMDVisualNovel] Branching: ${key}=${val} -> node ${nextNodeIndex}`);
3015
+ } else {
3016
+ nextNodeIndex = defaultIndex;
3017
+ }
3018
+ }
3019
+ if (nextNodeIndex < nodes.length && nextNodeIndex >= 0) {
3020
+ goToNode(nextNodeIndex);
3021
+ } else if (loop) {
3022
+ goToNode(0);
3023
+ } else {
3024
+ onScriptComplete?.();
3025
+ }
3026
+ }, [currentNode, currentNodeIndex, nodes.length, loop, variables, goToNode, onScriptComplete]);
3027
+ const goToNextDialogue = useCallback(() => {
3028
+ if (!currentNode) return;
3029
+ if (currentDialogue?.choices && currentDialogue.choices.length > 0 && !showChoices) {
3030
+ setShowChoices(true);
3031
+ return;
3032
+ }
3033
+ if (autoTimerRef.current) {
3034
+ clearTimeout(autoTimerRef.current);
3035
+ autoTimerRef.current = null;
3036
+ }
3037
+ const nextDialogueIndex = currentDialogueIndex + 1;
3038
+ if (nextDialogueIndex < currentNode.dialogues.length && currentNode?.dialogues[nextDialogueIndex] !== void 0) {
3039
+ const nextDialogue = currentNode.dialogues[nextDialogueIndex];
3040
+ setCurrentDialogueIndex(nextDialogueIndex);
3041
+ addToHistory(nextDialogue, currentNodeIndex, nextDialogueIndex);
3042
+ onDialogueChange?.(nextDialogue, nextDialogueIndex, currentNodeIndex);
3043
+ typingCompleteRef.current = false;
3044
+ } else if (currentNode.choices && currentNode.choices.length > 0) {
3045
+ setShowChoices(true);
3046
+ } else {
3047
+ triggerNodeTransition();
3048
+ }
3049
+ }, [currentNode, currentDialogue, currentDialogueIndex, currentNodeIndex, nodes.length, loop, addToHistory, onDialogueChange, onScriptComplete, showChoices, variables, goToNode, triggerNodeTransition]);
2917
3050
  const goToDialogue = useCallback(
2918
3051
  (dialogueIndex) => {
2919
3052
  if (!currentNode) return;
@@ -2957,6 +3090,11 @@ var MMDVisualNovel = forwardRef(
2957
3090
  }
2958
3091
  return void 0;
2959
3092
  }, [currentDialogue, handleTypingComplete]);
3093
+ useEffect(() => {
3094
+ if (currentDialogue?.effect) {
3095
+ triggerEffect(currentDialogue.effect);
3096
+ }
3097
+ }, [currentNodeIndex, currentDialogueIndex, triggerEffect]);
2960
3098
  const toggleAutoMode = useCallback(() => {
2961
3099
  setIsAutoMode((prev) => !prev);
2962
3100
  }, []);
@@ -2992,12 +3130,17 @@ var MMDVisualNovel = forwardRef(
2992
3130
  getCurrentNodeIndex: () => currentNodeIndex,
2993
3131
  getCurrentDialogueIndex: () => currentDialogueIndex,
2994
3132
  getHistory: () => history,
3133
+ getVariables: () => variables,
3134
+ setVariable: (key, value) => {
3135
+ setVariables((prev) => ({ ...prev, [key]: value }));
3136
+ },
2995
3137
  setAutoMode: setIsAutoMode,
2996
3138
  skipTyping: () => {
2997
3139
  typingCompleteRef.current = true;
2998
- }
3140
+ },
3141
+ triggerEffect
2999
3142
  }),
3000
- [goToNode, goToDialogue, currentNodeIndex, currentDialogueIndex, history]
3143
+ [goToNode, goToDialogue, currentNodeIndex, currentDialogueIndex, history, triggerEffect]
3001
3144
  );
3002
3145
  useEffect(() => {
3003
3146
  if (autoStart && currentNode && currentNode.dialogues.length > 0 && history.length === 0 && currentNode?.dialogues[0] !== void 0) {
@@ -3009,6 +3152,9 @@ var MMDVisualNovel = forwardRef(
3009
3152
  if (autoTimerRef.current) {
3010
3153
  clearTimeout(autoTimerRef.current);
3011
3154
  }
3155
+ if (effectTimerRef.current) {
3156
+ clearTimeout(effectTimerRef.current);
3157
+ }
3012
3158
  };
3013
3159
  }, []);
3014
3160
  if (!currentNode) {
@@ -3081,6 +3227,38 @@ var MMDVisualNovel = forwardRef(
3081
3227
  }
3082
3228
  )
3083
3229
  ),
3230
+ activeEffect && /* @__PURE__ */ React6.createElement(
3231
+ "div",
3232
+ {
3233
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center",
3234
+ style: { zIndex: 999 }
3235
+ },
3236
+ activeEffect.type === "flash" && /* @__PURE__ */ React6.createElement(
3237
+ "div",
3238
+ {
3239
+ className: "h-full w-full",
3240
+ style: {
3241
+ backgroundColor: activeEffect.color || "white",
3242
+ animation: `flash-anim ${activeEffect.duration || 500}ms ease-out forwards`
3243
+ }
3244
+ }
3245
+ ),
3246
+ activeEffect.type === "gif" && activeEffect.url && /* @__PURE__ */ React6.createElement(
3247
+ "img",
3248
+ {
3249
+ src: activeEffect.url,
3250
+ alt: "effect",
3251
+ className: activeEffect.position === "full" ? "h-full w-full object-cover" : "max-h-full max-w-full"
3252
+ }
3253
+ ),
3254
+ /* @__PURE__ */ React6.createElement("style", null, `
3255
+ @keyframes flash-anim {
3256
+ 0% { opacity: 0; }
3257
+ 25% { opacity: 1; }
3258
+ 100% { opacity: 0; }
3259
+ }
3260
+ `)
3261
+ ),
3084
3262
  /* @__PURE__ */ React6.createElement(
3085
3263
  LoadingOverlay,
3086
3264
  {
@@ -3152,18 +3330,41 @@ var MMDVisualNovel = forwardRef(
3152
3330
  }
3153
3331
  }
3154
3332
  ),
3155
- showChoices && currentNode.choices && /* @__PURE__ */ React6.createElement(
3333
+ showChoices && (currentDialogue?.choices || currentNode.choices) && /* @__PURE__ */ React6.createElement(
3156
3334
  ChoiceMenu,
3157
3335
  {
3158
- choices: currentNode.choices,
3336
+ choices: currentDialogue?.choices || currentNode.choices,
3159
3337
  theme: dialogueTheme,
3160
3338
  onSelect: (choice) => {
3339
+ if (choice.setVariable) {
3340
+ const { key, value } = choice.setVariable;
3341
+ setVariables((prev) => ({ ...prev, [key]: value }));
3342
+ console.log(`[MMDVisualNovel] Variable set: ${key} = ${value}`);
3343
+ }
3161
3344
  choice.onSelect?.();
3162
- if (choice.nextNodeIndex === currentNodeIndex) {
3163
- goToDialogue(choice.nextDialogueIndex || 0);
3164
- setShowChoices(false);
3165
- } else {
3166
- goToNode(choice.nextNodeIndex, true);
3345
+ if (choice.effect) {
3346
+ triggerEffect(choice.effect);
3347
+ }
3348
+ setShowChoices(false);
3349
+ if (choice.nextNodeIndex !== void 0) {
3350
+ if (choice.nextNodeIndex === currentNodeIndex) {
3351
+ goToDialogue(choice.nextDialogueIndex || 0);
3352
+ } else {
3353
+ goToNode(choice.nextNodeIndex, true);
3354
+ }
3355
+ } else if (currentDialogue?.choices) {
3356
+ const nextIdx = currentDialogueIndex + 1;
3357
+ if (currentNode && nextIdx < currentNode.dialogues.length) {
3358
+ const nextDialogue = currentNode.dialogues[nextIdx];
3359
+ if (nextDialogue) {
3360
+ setCurrentDialogueIndex(nextIdx);
3361
+ addToHistory(nextDialogue, currentNodeIndex, nextIdx);
3362
+ onDialogueChange?.(nextDialogue, nextIdx, currentNodeIndex);
3363
+ typingCompleteRef.current = false;
3364
+ }
3365
+ } else {
3366
+ triggerNodeTransition();
3367
+ }
3167
3368
  }
3168
3369
  }
3169
3370
  }
@@ -3582,6 +3783,6 @@ var MMDMusicPlayer = forwardRef(
3582
3783
  );
3583
3784
  MMDMusicPlayer.displayName = "MMDMusicPlayer";
3584
3785
 
3585
- export { DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDMusicPlayer, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, MusicControls, PlaylistPanel, StartScreen, TrackInfo, loadAmmo };
3786
+ export { ChoiceMenu, DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDMusicPlayer, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, MusicControls, PlaylistPanel, StartScreen, TrackInfo, loadAmmo };
3586
3787
  //# sourceMappingURL=index.mjs.map
3587
3788
  //# sourceMappingURL=index.mjs.map