cubeforge 0.6.1 → 0.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.
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { AxisLock, BoxColliderComponent, COLLISION_DYNAMIC_DYNAMIC, COLLISION_DY
10
10
  import { InputManager, ActionBindings, InputContextName, PlayerInput, InputRecorderControls, TouchPoint, InputBufferOptions, InputBuffer, ComboDefinition } from '@cubeforge/input';
11
11
  export { ActionBindings, AxisBinding, BufferedAction, ComboDefinition, ComboDetector, ComboDetectorOptions, InputBuffer, InputBufferOptions, InputContextName, InputManager, InputMap, InputRecorderControls, InputRecording, InputRecording as InputRecordingData, PlayerInput, TouchPoint, createInputMap, createInputRecorder, createPlayerInput, globalInputContext } from '@cubeforge/input';
12
12
  import { AnimationClip } from '@cubeforge/gameplay';
13
- export { AISteering, AnimationClip, AnimationControllerResult, BindingControls, CharacterControls, CutsceneControls, CutsceneStep, DialogueBox, DialogueBoxProps, DialogueBoxStyle, DialogueControls, DialogueLine, DialogueScript, ForceControls, GameState as GameStateDefinition, GameStateMachineResult, HealthControls, HealthOptions, IDBSaveControls, IDBSaveOptions, KinematicBodyControls, LevelTransitionControls, ObjectPool, PathfindingControls, PlatformerControllerOptions, RestartControls, SaveControls, SaveOptions, TopDownMovementOptions, TransitionOptions, TransitionType, TweenControls, useAISteering, useAnimationController, useCharacterController, useCutscene, useDamageZone, useDialogue, useDropThrough, useForces, useGameStateMachine, useGameStore, useHealth, useIDBSave, useKinematicBody, useLevelTransition, useObjectPool, usePathfinding, usePersistedBindings, usePlatformerController, useRestart, useSave, useTopDownMovement, useTween } from '@cubeforge/gameplay';
13
+ export { AISteering, AnimationClip, AnimationControllerResult, BindingControls, CharacterControls, CutsceneControls, CutsceneStep, DialogueBox, DialogueBoxProps, DialogueBoxStyle, DialogueControls, DialogueLine, DialogueScript, ForceControls, GameState as GameStateDefinition, GameStateMachineResult, HealthControls, HealthOptions, IDBSaveControls, IDBSaveOptions, KinematicBodyControls, LevelTransitionControls, ObjectPool, PathfindingControls, PlatformerControllerOptions, RestartControls, SaveControls, SaveOptions, SaveSlot, SaveSlotMeta, SaveSlotsControls, SaveSlotsOptions, TopDownMovementOptions, TransitionOptions, TransitionType, TweenControls, useAISteering, useAnimationController, useCharacterController, useCutscene, useDamageZone, useDialogue, useDropThrough, useForces, useGameStateMachine, useGameStore, useHealth, useIDBSave, useKinematicBody, useLevelTransition, useObjectPool, usePathfinding, usePersistedBindings, usePlatformerController, useRestart, useSave, useSaveSlots, useTopDownMovement, useTween } from '@cubeforge/gameplay';
14
14
  import { EngineState } from '@cubeforge/context';
15
15
  export { ContactData, EngineState, useCircleEnter, useCircleExit, useCircleStay, useCollidingWith, useCollisionEnter, useCollisionExit, useCollisionStay, useTriggerEnter, useTriggerExit, useTriggerStay } from '@cubeforge/context';
16
16
  export { AudioAnalyserControls, AudioAnalyserOptions, AudioGroup, AudioSchedulerControls, AudioSchedulerOptions, BarHandler, BeatHandler, CompressorEffectOptions, DelayEffectOptions, FilterEffectOptions, GroupEffectOptions, MusicControls, MusicOptions, PreloadAudioResult, ReverbEffectOptions, SoundControls, SoundOptions, SpatialSoundControls, SpatialSoundOptions, StreamedMusicControls, StreamedMusicOptions, clearGroupEffect, duck, getGroupVolume, getListenerPosition, getMasterVolume, loadAudioSettings, saveAudioSettings, setGroupEffect, setGroupMute, setGroupVolume, setGroupVolumeFaded, setListenerPosition, setMasterVolume, stopGroup, useAudioAnalyser, useAudioScheduler, useMusic, usePreloadAudio, useSound, useSpatialSound, useStreamedMusic } from '@cubeforge/audio';
@@ -2012,6 +2012,56 @@ declare function useGamepadHaptics(playerIndex?: number): {
2012
2012
  isSupported(): boolean;
2013
2013
  };
2014
2014
 
2015
+ /**
2016
+ * Touch/mobile haptic feedback via the Web Vibration API.
2017
+ *
2018
+ * Falls back silently on desktop browsers and iOS (Safari does not support
2019
+ * the Vibration API as of 2025).
2020
+ *
2021
+ * @example
2022
+ * ```tsx
2023
+ * function Player() {
2024
+ * const haptics = useTouchHaptics()
2025
+ *
2026
+ * useCollisionEnter(() => haptics.impact())
2027
+ * // Or with a pattern:
2028
+ * haptics.pattern([50, 30, 50]) // vibrate-pause-vibrate
2029
+ * }
2030
+ * ```
2031
+ */
2032
+ declare function useTouchHaptics(): TouchHapticsControls;
2033
+ interface TouchHapticsControls {
2034
+ /**
2035
+ * Short impact pulse — good for collisions, button taps.
2036
+ * @param duration Vibration duration in ms. Default 30.
2037
+ */
2038
+ impact(duration?: number): void;
2039
+ /**
2040
+ * Medium notification pulse — good for pickups, level-up, checkpoint.
2041
+ * Produces two short pulses separated by a brief pause.
2042
+ */
2043
+ notification(): void;
2044
+ /**
2045
+ * Heavy feedback burst — explosions, deaths, critical hits.
2046
+ * @param duration Vibration duration in ms. Default 80.
2047
+ */
2048
+ heavy(duration?: number): void;
2049
+ /**
2050
+ * Custom vibration pattern: alternating vibrate/pause durations in ms.
2051
+ * @param pattern Array of durations: [vibrate, pause, vibrate, ...].
2052
+ * A single number vibrates for that duration.
2053
+ */
2054
+ pattern(pattern: number | number[]): void;
2055
+ /**
2056
+ * Stop any in-progress vibration immediately.
2057
+ */
2058
+ cancel(): void;
2059
+ /**
2060
+ * Whether the current browser supports the Vibration API.
2061
+ */
2062
+ isSupported(): boolean;
2063
+ }
2064
+
2015
2065
  interface TimerControls {
2016
2066
  /** Start/restart the timer */
2017
2067
  start(): void;
@@ -3576,4 +3626,4 @@ declare function useRemotePlayer(config: {
3576
3626
  opts?: RemotePlayerOptions;
3577
3627
  }): RemotePlayerControls;
3578
3628
 
3579
- export { A11yNode, type A11yNodeProps, type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraBlendControls, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, type CinematicSequenceControls, type CinematicStep, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, type DraggableControls, type DraggableOptions, type DroppableControls, type DroppableOptions, EditableText, type EditableTextProps, Entity, type ExportOptions, FocusRing, type FocusRingProps, type FocusableOptions, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type GridCell, type GridControls, type GridOptions, type HMRControls, HUD, HUDBar, type HUDBarProps, HUDCounter, type HUDCounterProps, type HUDPosition, type HUDProps, HUDZone, type HUDZoneProps, HalfSpaceCollider, HeightFieldCollider, type HistoryControls, type HistoryOptions, type HoverableControls, type HoverableOptions, type InputContextControls, Joint, type KeyboardFocusControls, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, PARTICLE_PRESETS, ParallaxLayer, ParticleEmitter, type ParticleEmitterConfig, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, type SceneSaveOptions, type SceneTransitionControls, SceneTransitionOverlay, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SelectOptions, Selection, type SelectionControls, type SelectionProps, type SnapControls, type SnapOptions, type SnapResult, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, Stage, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, Trail, Transform, TransformHandles, type TransformHandlesProps, type TransitionEffect, TriMeshCollider, TriangleCollider, type TurnSystemControls, type TurnSystemOptions, VectorPath, type VectorPathProps, VirtualCamera, type VirtualCameraConfig, type VirtualCameraProps, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, deleteSavedScene, downloadCanvas, exportToBlob, exportToDataURL, listSavedScenes, loadScene, loadSceneFromLocalStorage, playClip, saveScene, saveSceneToLocalStorage, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraBlend, useCameraLookahead, useCinematicSequence, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useDraggable, useDroppable, useEntity, useEvent, useEvents, useFocusable, useGame, useGamepad, useGamepadHaptics, useGestures, useGrid, useHMR, useHistory, useHitstop, useHoverable, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useKeyboardFocus, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSceneTransition, useSelection, useSnap, useSnapshot, useSquashStretch, useTimer, useTouch, useTurnSystem, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };
3629
+ export { A11yNode, type A11yNodeProps, type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraBlendControls, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, type CinematicSequenceControls, type CinematicStep, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, type DraggableControls, type DraggableOptions, type DroppableControls, type DroppableOptions, EditableText, type EditableTextProps, Entity, type ExportOptions, FocusRing, type FocusRingProps, type FocusableOptions, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type GridCell, type GridControls, type GridOptions, type HMRControls, HUD, HUDBar, type HUDBarProps, HUDCounter, type HUDCounterProps, type HUDPosition, type HUDProps, HUDZone, type HUDZoneProps, HalfSpaceCollider, HeightFieldCollider, type HistoryControls, type HistoryOptions, type HoverableControls, type HoverableOptions, type InputContextControls, Joint, type KeyboardFocusControls, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, PARTICLE_PRESETS, ParallaxLayer, ParticleEmitter, type ParticleEmitterConfig, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, type SceneSaveOptions, type SceneTransitionControls, SceneTransitionOverlay, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SelectOptions, Selection, type SelectionControls, type SelectionProps, type SnapControls, type SnapOptions, type SnapResult, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, Stage, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, type TouchHapticsControls, Trail, Transform, TransformHandles, type TransformHandlesProps, type TransitionEffect, TriMeshCollider, TriangleCollider, type TurnSystemControls, type TurnSystemOptions, VectorPath, type VectorPathProps, VirtualCamera, type VirtualCameraConfig, type VirtualCameraProps, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, deleteSavedScene, downloadCanvas, exportToBlob, exportToDataURL, listSavedScenes, loadScene, loadSceneFromLocalStorage, playClip, saveScene, saveSceneToLocalStorage, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraBlend, useCameraLookahead, useCinematicSequence, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useDraggable, useDroppable, useEntity, useEvent, useEvents, useFocusable, useGame, useGamepad, useGamepadHaptics, useGestures, useGrid, useHMR, useHistory, useHitstop, useHoverable, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useKeyboardFocus, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSceneTransition, useSelection, useSnap, useSnapshot, useSquashStretch, useTimer, useTouch, useTouchHaptics, useTurnSystem, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };
package/dist/index.js CHANGED
@@ -10294,6 +10294,31 @@ function useGamepadHaptics(playerIndex = 0) {
10294
10294
  };
10295
10295
  }
10296
10296
 
10297
+ // src/hooks/useTouchHaptics.ts
10298
+ function useTouchHaptics() {
10299
+ return touchHapticsControls;
10300
+ }
10301
+ var touchHapticsControls = {
10302
+ impact(duration = 30) {
10303
+ navigator.vibrate?.(duration);
10304
+ },
10305
+ notification() {
10306
+ navigator.vibrate?.([40, 30, 40]);
10307
+ },
10308
+ heavy(duration = 80) {
10309
+ navigator.vibrate?.(duration);
10310
+ },
10311
+ pattern(pattern) {
10312
+ navigator.vibrate?.(pattern);
10313
+ },
10314
+ cancel() {
10315
+ navigator.vibrate?.(0);
10316
+ },
10317
+ isSupported() {
10318
+ return typeof navigator !== "undefined" && "vibrate" in navigator;
10319
+ }
10320
+ };
10321
+
10297
10322
  // src/hooks/useTimer.ts
10298
10323
  import { useRef as useRef23, useCallback as useCallback6, useEffect as useEffect60, useContext as useContext51 } from "react";
10299
10324
  function useTimer(duration, onComplete, opts) {
@@ -13272,6 +13297,151 @@ function useIDBSave(key, defaultValue, opts = {}) {
13272
13297
  return { save, load, clear, exists };
13273
13298
  }
13274
13299
 
13300
+ // ../gameplay/src/hooks/useSaveSlots.ts
13301
+ import { useCallback as useCallback29, useState as useState28 } from "react";
13302
+ function useSaveSlots(namespace, opts = {}) {
13303
+ const version = opts.version ?? 1;
13304
+ const indexKey = `cubeforge-saves:${namespace}:__index`;
13305
+ function slotKey(id) {
13306
+ return `cubeforge-saves:${namespace}:${id}`;
13307
+ }
13308
+ function readIndex() {
13309
+ try {
13310
+ const raw = localStorage.getItem(indexKey);
13311
+ return raw ? JSON.parse(raw) : [];
13312
+ } catch {
13313
+ return [];
13314
+ }
13315
+ }
13316
+ function writeIndex(ids) {
13317
+ try {
13318
+ localStorage.setItem(indexKey, JSON.stringify(ids));
13319
+ } catch {
13320
+ }
13321
+ }
13322
+ function addToIndex(id) {
13323
+ const ids = readIndex();
13324
+ if (!ids.includes(id)) {
13325
+ ids.push(id);
13326
+ writeIndex(ids);
13327
+ }
13328
+ }
13329
+ function removeFromIndex(id) {
13330
+ writeIndex(readIndex().filter((x) => x !== id));
13331
+ }
13332
+ function readSlot(id) {
13333
+ try {
13334
+ const raw = localStorage.getItem(slotKey(id));
13335
+ if (!raw) return null;
13336
+ return JSON.parse(raw);
13337
+ } catch {
13338
+ return null;
13339
+ }
13340
+ }
13341
+ function writeSlot(id, entry) {
13342
+ try {
13343
+ localStorage.setItem(slotKey(id), JSON.stringify(entry));
13344
+ } catch {
13345
+ console.warn("[CubeForge] useSaveSlots: failed to write slot", id);
13346
+ }
13347
+ }
13348
+ const [slots, setSlots] = useState28(() => {
13349
+ return readIndex().map((id) => readSlot(id)?.meta).filter((m) => m !== void 0).sort((a, b) => a.savedAt < b.savedAt ? 1 : -1);
13350
+ });
13351
+ function refreshSlots() {
13352
+ const metas = readIndex().map((id) => readSlot(id)?.meta).filter((m) => m !== void 0).sort((a, b) => a.savedAt < b.savedAt ? 1 : -1);
13353
+ setSlots(metas);
13354
+ }
13355
+ const save = useCallback29(
13356
+ (id, data, label, thumbnail) => {
13357
+ const meta = {
13358
+ id,
13359
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
13360
+ label,
13361
+ thumbnail
13362
+ };
13363
+ writeSlot(id, { version, meta, data });
13364
+ addToIndex(id);
13365
+ refreshSlots();
13366
+ },
13367
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13368
+ [namespace, version]
13369
+ );
13370
+ const load = useCallback29(
13371
+ (id) => {
13372
+ const entry = readSlot(id);
13373
+ if (!entry) return null;
13374
+ let data;
13375
+ if (opts.migrate && entry.version !== version) {
13376
+ data = opts.migrate(entry.data, entry.version);
13377
+ } else {
13378
+ data = entry.data;
13379
+ }
13380
+ if (opts.validate && !opts.validate(data)) return null;
13381
+ return { ...entry.meta, data };
13382
+ },
13383
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13384
+ [namespace, version]
13385
+ );
13386
+ const deleteFn = useCallback29(
13387
+ (id) => {
13388
+ try {
13389
+ localStorage.removeItem(slotKey(id));
13390
+ } catch {
13391
+ }
13392
+ removeFromIndex(id);
13393
+ refreshSlots();
13394
+ },
13395
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13396
+ [namespace]
13397
+ );
13398
+ const copy = useCallback29(
13399
+ (fromId, toId) => {
13400
+ const entry = readSlot(fromId);
13401
+ if (!entry) return false;
13402
+ const newMeta = { ...entry.meta, id: toId, savedAt: (/* @__PURE__ */ new Date()).toISOString() };
13403
+ writeSlot(toId, { ...entry, meta: newMeta });
13404
+ addToIndex(toId);
13405
+ refreshSlots();
13406
+ return true;
13407
+ },
13408
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13409
+ [namespace, version]
13410
+ );
13411
+ const rename = useCallback29(
13412
+ (id, label) => {
13413
+ const entry = readSlot(id);
13414
+ if (!entry) return;
13415
+ writeSlot(id, { ...entry, meta: { ...entry.meta, label } });
13416
+ refreshSlots();
13417
+ },
13418
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13419
+ [namespace]
13420
+ );
13421
+ const listSlots = useCallback29(() => {
13422
+ return readIndex().map((id) => readSlot(id)?.meta).filter((m) => m !== void 0).sort((a, b) => a.savedAt < b.savedAt ? 1 : -1);
13423
+ }, [namespace]);
13424
+ const exists = useCallback29(
13425
+ (id) => {
13426
+ return readSlot(id) !== null;
13427
+ },
13428
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13429
+ [namespace]
13430
+ );
13431
+ return {
13432
+ save,
13433
+ load,
13434
+ delete: deleteFn,
13435
+ copy,
13436
+ rename,
13437
+ listSlots,
13438
+ exists,
13439
+ get slots() {
13440
+ return slots;
13441
+ }
13442
+ };
13443
+ }
13444
+
13275
13445
  // ../gameplay/src/hooks/useTopDownMovement.ts
13276
13446
  import { useContext as useContext76, useEffect as useEffect78 } from "react";
13277
13447
  function moveToward(current, target, maxDelta) {
@@ -13322,18 +13492,18 @@ function useTopDownMovement(entityId, opts = {}) {
13322
13492
  }
13323
13493
 
13324
13494
  // ../gameplay/src/hooks/useDialogue.ts
13325
- import { useState as useState28, useCallback as useCallback29, useRef as useRef44 } from "react";
13495
+ import { useState as useState29, useCallback as useCallback30, useRef as useRef44 } from "react";
13326
13496
  function useDialogue() {
13327
- const [active, setActive] = useState28(false);
13328
- const [currentId, setCurrentId] = useState28(null);
13497
+ const [active, setActive] = useState29(false);
13498
+ const [currentId, setCurrentId] = useState29(null);
13329
13499
  const scriptRef = useRef44(null);
13330
- const start2 = useCallback29((script, startId) => {
13500
+ const start2 = useCallback30((script, startId) => {
13331
13501
  scriptRef.current = script;
13332
13502
  const id = startId ?? Object.keys(script)[0];
13333
13503
  setCurrentId(id);
13334
13504
  setActive(true);
13335
13505
  }, []);
13336
- const advance = useCallback29(
13506
+ const advance = useCallback30(
13337
13507
  (choiceIndex) => {
13338
13508
  if (!scriptRef.current || !currentId) return;
13339
13509
  const line = scriptRef.current[currentId];
@@ -13363,7 +13533,7 @@ function useDialogue() {
13363
13533
  },
13364
13534
  [currentId]
13365
13535
  );
13366
- const close = useCallback29(() => {
13536
+ const close = useCallback30(() => {
13367
13537
  setActive(false);
13368
13538
  setCurrentId(null);
13369
13539
  scriptRef.current = null;
@@ -13481,17 +13651,17 @@ function DialogueBox({ dialogue, onAdvance, onClose, style = {} }) {
13481
13651
  }
13482
13652
 
13483
13653
  // ../gameplay/src/hooks/useCutscene.ts
13484
- import { useState as useState29, useCallback as useCallback30, useRef as useRef45, useEffect as useEffect79, useContext as useContext77 } from "react";
13654
+ import { useState as useState30, useCallback as useCallback31, useRef as useRef45, useEffect as useEffect79, useContext as useContext77 } from "react";
13485
13655
  function useCutscene() {
13486
13656
  const engine = useContext77(EngineContext);
13487
- const [playing, setPlaying] = useState29(false);
13488
- const [stepIndex, setStepIndex] = useState29(0);
13657
+ const [playing, setPlaying] = useState30(false);
13658
+ const [stepIndex, setStepIndex] = useState30(0);
13489
13659
  const stepsRef = useRef45([]);
13490
13660
  const timerRef = useRef45(0);
13491
13661
  const idxRef = useRef45(0);
13492
13662
  const playingRef = useRef45(false);
13493
13663
  const entityRef = useRef45(null);
13494
- const finish = useCallback30(() => {
13664
+ const finish = useCallback31(() => {
13495
13665
  playingRef.current = false;
13496
13666
  setPlaying(false);
13497
13667
  setStepIndex(0);
@@ -13501,14 +13671,14 @@ function useCutscene() {
13501
13671
  entityRef.current = null;
13502
13672
  }
13503
13673
  }, [engine.ecs]);
13504
- const fireStep = useCallback30((step) => {
13674
+ const fireStep = useCallback31((step) => {
13505
13675
  if (step.type === "call") step.fn();
13506
13676
  if (step.type === "parallel")
13507
13677
  step.steps.forEach((s2) => {
13508
13678
  if (s2.type === "call") s2.fn();
13509
13679
  });
13510
13680
  }, []);
13511
- const play = useCallback30(
13681
+ const play = useCallback31(
13512
13682
  (steps) => {
13513
13683
  stepsRef.current = steps;
13514
13684
  idxRef.current = 0;
@@ -13565,7 +13735,7 @@ function useCutscene() {
13565
13735
  },
13566
13736
  [engine.ecs, finish, fireStep]
13567
13737
  );
13568
- const skip = useCallback30(() => {
13738
+ const skip = useCallback31(() => {
13569
13739
  for (let i = idxRef.current; i < stepsRef.current.length; i++) {
13570
13740
  const step = stepsRef.current[i];
13571
13741
  if (step.type === "call") step.fn();
@@ -13587,7 +13757,7 @@ function useCutscene() {
13587
13757
  }
13588
13758
 
13589
13759
  // ../gameplay/src/hooks/useGameStore.ts
13590
- import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback31 } from "react";
13760
+ import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback32 } from "react";
13591
13761
  function createStore(initialState) {
13592
13762
  let state = { ...initialState };
13593
13763
  const listeners = /* @__PURE__ */ new Set();
@@ -13613,7 +13783,7 @@ function useGameStore(key, initialState) {
13613
13783
  }
13614
13784
  const store = stores.get(key);
13615
13785
  const state = useSyncExternalStore2(store.subscribe, store.getState);
13616
- const setState = useCallback31(
13786
+ const setState = useCallback32(
13617
13787
  (partial) => {
13618
13788
  store.setState(partial);
13619
13789
  },
@@ -13623,21 +13793,21 @@ function useGameStore(key, initialState) {
13623
13793
  }
13624
13794
 
13625
13795
  // ../gameplay/src/hooks/useTween.ts
13626
- import { useRef as useRef46, useCallback as useCallback32, useEffect as useEffect80 } from "react";
13796
+ import { useRef as useRef46, useCallback as useCallback33, useEffect as useEffect80 } from "react";
13627
13797
  function useTween(opts) {
13628
13798
  const rafRef = useRef46(null);
13629
13799
  const startTimeRef = useRef46(0);
13630
13800
  const runningRef = useRef46(false);
13631
13801
  const optsRef = useRef46(opts);
13632
13802
  optsRef.current = opts;
13633
- const stop = useCallback32(() => {
13803
+ const stop = useCallback33(() => {
13634
13804
  if (rafRef.current !== null) {
13635
13805
  cancelAnimationFrame(rafRef.current);
13636
13806
  rafRef.current = null;
13637
13807
  }
13638
13808
  runningRef.current = false;
13639
13809
  }, []);
13640
- const start2 = useCallback32(() => {
13810
+ const start2 = useCallback33(() => {
13641
13811
  stop();
13642
13812
  runningRef.current = true;
13643
13813
  startTimeRef.current = performance.now();
@@ -14358,15 +14528,15 @@ function syncEntity(config) {
14358
14528
  }
14359
14529
 
14360
14530
  // ../../packages/net/src/useNetworkInput.ts
14361
- import { useState as useState30, useEffect as useEffect82, useRef as useRef48 } from "react";
14531
+ import { useState as useState31, useEffect as useEffect82, useRef as useRef48 } from "react";
14362
14532
  var INPUT_MSG_TYPE = "input:state";
14363
14533
  function useNetworkInput(config) {
14364
14534
  const { room, keys, input, tickRate = 20 } = config;
14365
14535
  const intervalMs = 1e3 / tickRate;
14366
- const [localInput, setLocalInput] = useState30(
14536
+ const [localInput, setLocalInput] = useState31(
14367
14537
  () => Object.fromEntries(keys.map((k) => [k, false]))
14368
14538
  );
14369
- const [remoteInputs] = useState30(() => /* @__PURE__ */ new Map());
14539
+ const [remoteInputs] = useState31(() => /* @__PURE__ */ new Map());
14370
14540
  const localInputRef = useRef48(localInput);
14371
14541
  useEffect82(() => {
14372
14542
  let cleanupDom = null;
@@ -14599,12 +14769,12 @@ function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
14599
14769
  }
14600
14770
 
14601
14771
  // src/hooks/useRemotePlayer.ts
14602
- import { useEffect as useEffect84, useRef as useRef50, useState as useState31 } from "react";
14772
+ import { useEffect as useEffect84, useRef as useRef50, useState as useState32 } from "react";
14603
14773
  var PEER_JOIN_MSG = "peer:join";
14604
14774
  var PEER_LEAVE_MSG = "peer:leave";
14605
14775
  function useRemotePlayer(config) {
14606
14776
  const { room, world, createEntity, destroyEntity } = config;
14607
- const [players, setPlayers] = useState31(() => /* @__PURE__ */ new Map());
14777
+ const [players, setPlayers] = useState32(() => /* @__PURE__ */ new Map());
14608
14778
  const playersRef = useRef50(players);
14609
14779
  const createRef = useRef50(createEntity);
14610
14780
  createRef.current = createEntity;
@@ -14927,6 +15097,7 @@ export {
14927
15097
  useRemotePlayer,
14928
15098
  useRestart,
14929
15099
  useSave,
15100
+ useSaveSlots,
14930
15101
  useSceneManager,
14931
15102
  useSceneTransition,
14932
15103
  useSelection,
@@ -14939,6 +15110,7 @@ export {
14939
15110
  useTimer,
14940
15111
  useTopDownMovement,
14941
15112
  useTouch,
15113
+ useTouchHaptics,
14942
15114
  useTriggerEnter,
14943
15115
  useTriggerExit,
14944
15116
  useTriggerStay,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubeforge",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "React-first 2D browser game engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",