blacktrigram 0.7.47 → 0.7.49
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/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +29 -25
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +3 -11
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.d.ts.map +1 -1
- package/lib/hooks/useHUDLayout.js +3 -2
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
- package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js +15 -15
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
- package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +74 -12
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js +34 -0
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/index.d.ts +1 -1
- package/lib/systems/animation/core/index.d.ts.map +1 -1
- package/lib/systems/animation/core/types.d.ts +24 -0
- package/lib/systems/animation/core/types.d.ts.map +1 -1
- package/lib/systems/animation/core/types.js +27 -11
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js +19 -19
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js +17 -17
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js +24 -24
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js +21 -21
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js +6 -6
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/animations.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/index.js.map +1 -1
- package/lib/types/constants/layout.d.ts +21 -0
- package/lib/types/constants/layout.d.ts.map +1 -1
- package/lib/types/constants/layout.js +22 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/constants/ui.js.map +1 -1
- package/lib/types/facial.js +19 -19
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/muscle.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/types/player-visual.d.ts +1 -1
- package/lib/types/player-visual.d.ts.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js +6 -7
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.d.ts +7 -0
- package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js +16 -2
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMatchCountdown.js","names":[],"sources":["../../src/hooks/useMatchCountdown.ts"],"sourcesContent":["/**\n * useMatchCountdown Hook - Manages match start countdown sequence\n *\n * Korean: 매치 시작 카운트다운 훅 (Match Start Countdown Hook)\n *\n * Handles the state machine for match start countdown:\n * - idle: Waiting to start\n * - ready: Showing \"Ready?\" message\n * - counting: Counting down \"3... 2... 1...\"\n * - fight: Showing \"Fight!\" announcement\n * - complete: Countdown finished, combat can begin\n *\n * @module hooks/useMatchCountdown\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Match countdown states\n *\n * Korean: 매치 카운트다운 상태\n */\nexport type MatchCountdownState =\n | \"idle\"\n | \"ready\"\n | \"counting\"\n | \"fight\"\n | \"complete\";\n\n/**\n * Match countdown configuration\n */\nexport interface MatchCountdownConfig {\n /** Duration of \"Ready?\" display in seconds */\n readonly readyDuration?: number;\n /** Duration of each countdown number in seconds */\n readonly countdownInterval?: number;\n /** Duration of \"Fight!\" display in seconds */\n readonly fightDuration?: number;\n /** Starting countdown number */\n readonly startNumber?: number;\n}\n\n/**\n * Match countdown hook state\n */\nexport interface UseMatchCountdownResult {\n /** Current countdown state */\n readonly state: MatchCountdownState;\n /** Current countdown number (3, 2, 1, or 0) */\n readonly currentNumber: number;\n /** Start countdown sequence */\n readonly startCountdown: () => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset countdown to idle state */\n readonly resetCountdown: () => void;\n /** Whether countdown is in progress */\n readonly isActive: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<MatchCountdownConfig> = {\n readyDuration: 1,\n countdownInterval: 1,\n fightDuration: 1,\n startNumber: 3,\n};\n\n/**\n * useMatchCountdown Hook\n *\n * Manages the complete match start countdown flow:\n * 1. Idle state waiting for match start\n * 2. Ready state shows \"Ready?\" message (1s)\n * 3. Counting state counts down from 3 to 1 (1s intervals)\n * 4. Fight state shows \"Fight!\" message (1s)\n * 5. Complete state signals combat can begin\n *\n * @param config - Configuration for countdown timings\n * @param onComplete - Callback when countdown completes\n * @returns Match countdown state and control functions\n *\n * @example\n * ```typescript\n * const {\n * state,\n * currentNumber,\n * startCountdown,\n * skipCountdown,\n * } = useMatchCountdown(\n * { startNumber: 3 },\n * () => {\n * // Enable combat inputs\n * enableCombatControls();\n * }\n * );\n *\n * // When match initializes\n * startCountdown();\n * ```\n */\nexport function useMatchCountdown(\n config: MatchCountdownConfig = {},\n onComplete?: () => void\n): UseMatchCountdownResult {\n // Memoize with individual config values to avoid reference equality issues\n const mergedConfig = useMemo(\n () => ({\n readyDuration: config.readyDuration ?? DEFAULT_CONFIG.readyDuration,\n countdownInterval:\n config.countdownInterval ?? DEFAULT_CONFIG.countdownInterval,\n fightDuration: config.fightDuration ?? DEFAULT_CONFIG.fightDuration,\n startNumber: config.startNumber ?? DEFAULT_CONFIG.startNumber,\n }),\n [\n config.readyDuration,\n config.countdownInterval,\n config.fightDuration,\n config.startNumber,\n ]\n );\n\n const [state, setState] = useState<MatchCountdownState>(\"idle\");\n const [currentNumber, setCurrentNumber] = useState(mergedConfig.startNumber);\n\n // Use refs to track active timers\n const readyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const countdownTimer = useRef<ReturnType<typeof setInterval> | null>(null);\n const fightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Use ref to always call the latest callback\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (readyTimer.current) {\n clearTimeout(readyTimer.current);\n readyTimer.current = null;\n }\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (fightTimer.current) {\n clearTimeout(fightTimer.current);\n fightTimer.current = null;\n }\n }, []);\n\n /**\n * Start the countdown sequence\n */\n const startCountdown = useCallback(() => {\n clearTimers();\n setState(\"ready\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Show \"Ready?\" message\n readyTimer.current = setTimeout(() => {\n setState(\"counting\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Countdown timer\n let count = mergedConfig.startNumber;\n countdownTimer.current = setInterval(() => {\n count -= 1;\n setCurrentNumber(count);\n\n if (count <= 0) {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n\n // Show \"Fight!\" message\n setState(\"fight\");\n\n fightTimer.current = setTimeout(() => {\n setState(\"complete\");\n onCompleteRef.current?.();\n }, mergedConfig.fightDuration * 1000);\n }\n }, mergedConfig.countdownInterval * 1000);\n }, mergedConfig.readyDuration * 1000);\n }, [clearTimers, mergedConfig]);\n\n /**\n * Skip countdown and proceed immediately to fight state\n */\n const skipCountdown = useCallback(() => {\n clearTimers();\n setState(\"complete\");\n onCompleteRef.current?.();\n }, [clearTimers]);\n\n /**\n * Reset countdown to idle state\n */\n const resetCountdown = useCallback(() => {\n clearTimers();\n setState(\"idle\");\n setCurrentNumber(mergedConfig.startNumber);\n }, [clearTimers, mergedConfig.startNumber]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n };\n }, [clearTimers]);\n\n return {\n state,\n currentNumber,\n startCountdown,\n skipCountdown,\n resetCountdown,\n isActive: state !== \"idle\" && state !== \"complete\",\n };\n}\n\nexport default useMatchCountdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAiEA,IAAM,iBAAiD;CACrD,eAAe;CACf,mBAAmB;CACnB,eAAe;CACf,aAAa;
|
|
1
|
+
{"version":3,"file":"useMatchCountdown.js","names":[],"sources":["../../src/hooks/useMatchCountdown.ts"],"sourcesContent":["/**\n * useMatchCountdown Hook - Manages match start countdown sequence\n *\n * Korean: 매치 시작 카운트다운 훅 (Match Start Countdown Hook)\n *\n * Handles the state machine for match start countdown:\n * - idle: Waiting to start\n * - ready: Showing \"Ready?\" message\n * - counting: Counting down \"3... 2... 1...\"\n * - fight: Showing \"Fight!\" announcement\n * - complete: Countdown finished, combat can begin\n *\n * @module hooks/useMatchCountdown\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Match countdown states\n *\n * Korean: 매치 카운트다운 상태\n */\nexport type MatchCountdownState =\n | \"idle\"\n | \"ready\"\n | \"counting\"\n | \"fight\"\n | \"complete\";\n\n/**\n * Match countdown configuration\n */\nexport interface MatchCountdownConfig {\n /** Duration of \"Ready?\" display in seconds */\n readonly readyDuration?: number;\n /** Duration of each countdown number in seconds */\n readonly countdownInterval?: number;\n /** Duration of \"Fight!\" display in seconds */\n readonly fightDuration?: number;\n /** Starting countdown number */\n readonly startNumber?: number;\n}\n\n/**\n * Match countdown hook state\n */\nexport interface UseMatchCountdownResult {\n /** Current countdown state */\n readonly state: MatchCountdownState;\n /** Current countdown number (3, 2, 1, or 0) */\n readonly currentNumber: number;\n /** Start countdown sequence */\n readonly startCountdown: () => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset countdown to idle state */\n readonly resetCountdown: () => void;\n /** Whether countdown is in progress */\n readonly isActive: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<MatchCountdownConfig> = {\n readyDuration: 1,\n countdownInterval: 1,\n fightDuration: 1,\n startNumber: 3,\n};\n\n/**\n * useMatchCountdown Hook\n *\n * Manages the complete match start countdown flow:\n * 1. Idle state waiting for match start\n * 2. Ready state shows \"Ready?\" message (1s)\n * 3. Counting state counts down from 3 to 1 (1s intervals)\n * 4. Fight state shows \"Fight!\" message (1s)\n * 5. Complete state signals combat can begin\n *\n * @param config - Configuration for countdown timings\n * @param onComplete - Callback when countdown completes\n * @returns Match countdown state and control functions\n *\n * @example\n * ```typescript\n * const {\n * state,\n * currentNumber,\n * startCountdown,\n * skipCountdown,\n * } = useMatchCountdown(\n * { startNumber: 3 },\n * () => {\n * // Enable combat inputs\n * enableCombatControls();\n * }\n * );\n *\n * // When match initializes\n * startCountdown();\n * ```\n */\nexport function useMatchCountdown(\n config: MatchCountdownConfig = {},\n onComplete?: () => void\n): UseMatchCountdownResult {\n // Memoize with individual config values to avoid reference equality issues\n const mergedConfig = useMemo(\n () => ({\n readyDuration: config.readyDuration ?? DEFAULT_CONFIG.readyDuration,\n countdownInterval:\n config.countdownInterval ?? DEFAULT_CONFIG.countdownInterval,\n fightDuration: config.fightDuration ?? DEFAULT_CONFIG.fightDuration,\n startNumber: config.startNumber ?? DEFAULT_CONFIG.startNumber,\n }),\n [\n config.readyDuration,\n config.countdownInterval,\n config.fightDuration,\n config.startNumber,\n ]\n );\n\n const [state, setState] = useState<MatchCountdownState>(\"idle\");\n const [currentNumber, setCurrentNumber] = useState(mergedConfig.startNumber);\n\n // Use refs to track active timers\n const readyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const countdownTimer = useRef<ReturnType<typeof setInterval> | null>(null);\n const fightTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Use ref to always call the latest callback\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n }, [onComplete]);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (readyTimer.current) {\n clearTimeout(readyTimer.current);\n readyTimer.current = null;\n }\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (fightTimer.current) {\n clearTimeout(fightTimer.current);\n fightTimer.current = null;\n }\n }, []);\n\n /**\n * Start the countdown sequence\n */\n const startCountdown = useCallback(() => {\n clearTimers();\n setState(\"ready\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Show \"Ready?\" message\n readyTimer.current = setTimeout(() => {\n setState(\"counting\");\n setCurrentNumber(mergedConfig.startNumber);\n\n // Countdown timer\n let count = mergedConfig.startNumber;\n countdownTimer.current = setInterval(() => {\n count -= 1;\n setCurrentNumber(count);\n\n if (count <= 0) {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n\n // Show \"Fight!\" message\n setState(\"fight\");\n\n fightTimer.current = setTimeout(() => {\n setState(\"complete\");\n onCompleteRef.current?.();\n }, mergedConfig.fightDuration * 1000);\n }\n }, mergedConfig.countdownInterval * 1000);\n }, mergedConfig.readyDuration * 1000);\n }, [clearTimers, mergedConfig]);\n\n /**\n * Skip countdown and proceed immediately to fight state\n */\n const skipCountdown = useCallback(() => {\n clearTimers();\n setState(\"complete\");\n onCompleteRef.current?.();\n }, [clearTimers]);\n\n /**\n * Reset countdown to idle state\n */\n const resetCountdown = useCallback(() => {\n clearTimers();\n setState(\"idle\");\n setCurrentNumber(mergedConfig.startNumber);\n }, [clearTimers, mergedConfig.startNumber]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n };\n }, [clearTimers]);\n\n return {\n state,\n currentNumber,\n startCountdown,\n skipCountdown,\n resetCountdown,\n isActive: state !== \"idle\" && state !== \"complete\",\n };\n}\n\nexport default useMatchCountdown;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAiEA,IAAM,iBAAiD;CACrD,eAAe;CACf,mBAAmB;CACnB,eAAe;CACf,aAAa;AACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,kBACd,SAA+B,CAAC,GAChC,YACyB;CAEzB,MAAM,eAAe,eACZ;EACL,eAAe,OAAO,iBAAiB,eAAe;EACtD,mBACE,OAAO,qBAAqB,eAAe;EAC7C,eAAe,OAAO,iBAAiB,eAAe;EACtD,aAAa,OAAO,eAAe,eAAe;CACpD,IACA;EACE,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;CACT,CACF;CAEA,MAAM,CAAC,OAAO,YAAY,SAA8B,MAAM;CAC9D,MAAM,CAAC,eAAe,oBAAoB,SAAS,aAAa,WAAW;CAG3E,MAAM,aAAa,OAA6C,IAAI;CACpE,MAAM,iBAAiB,OAA8C,IAAI;CACzE,MAAM,aAAa,OAA6C,IAAI;CAGpE,MAAM,gBAAgB,OAAO,UAAU;CACvC,gBAAgB;EACd,cAAc,UAAU;CAC1B,GAAG,CAAC,UAAU,CAAC;;;;CAKf,MAAM,cAAc,kBAAkB;EACpC,IAAI,WAAW,SAAS;GACtB,aAAa,WAAW,OAAO;GAC/B,WAAW,UAAU;EACvB;EACA,IAAI,eAAe,SAAS;GAC1B,cAAc,eAAe,OAAO;GACpC,eAAe,UAAU;EAC3B;EACA,IAAI,WAAW,SAAS;GACtB,aAAa,WAAW,OAAO;GAC/B,WAAW,UAAU;EACvB;CACF,GAAG,CAAC,CAAC;;;;CAKL,MAAM,iBAAiB,kBAAkB;EACvC,YAAY;EACZ,SAAS,OAAO;EAChB,iBAAiB,aAAa,WAAW;EAGzC,WAAW,UAAU,iBAAiB;GACpC,SAAS,UAAU;GACnB,iBAAiB,aAAa,WAAW;GAGzC,IAAI,QAAQ,aAAa;GACzB,eAAe,UAAU,kBAAkB;IACzC,SAAS;IACT,iBAAiB,KAAK;IAEtB,IAAI,SAAS,GAAG;KACd,IAAI,eAAe,SAAS;MAC1B,cAAc,eAAe,OAAO;MACpC,eAAe,UAAU;KAC3B;KAGA,SAAS,OAAO;KAEhB,WAAW,UAAU,iBAAiB;MACpC,SAAS,UAAU;MACnB,cAAc,UAAU;KAC1B,GAAG,aAAa,gBAAgB,GAAI;IACtC;GACF,GAAG,aAAa,oBAAoB,GAAI;EAC1C,GAAG,aAAa,gBAAgB,GAAI;CACtC,GAAG,CAAC,aAAa,YAAY,CAAC;;;;CAK9B,MAAM,gBAAgB,kBAAkB;EACtC,YAAY;EACZ,SAAS,UAAU;EACnB,cAAc,UAAU;CAC1B,GAAG,CAAC,WAAW,CAAC;;;;CAKhB,MAAM,iBAAiB,kBAAkB;EACvC,YAAY;EACZ,SAAS,MAAM;EACf,iBAAiB,aAAa,WAAW;CAC3C,GAAG,CAAC,aAAa,aAAa,WAAW,CAAC;;;;CAK1C,gBAAgB;EACd,aAAa;GACX,YAAY;EACd;CACF,GAAG,CAAC,WAAW,CAAC;CAEhB,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,UAAU,UAAU,UAAU,UAAU;CAC1C;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMuscleActivation.js","names":[],"sources":["../../src/hooks/useMuscleActivation.ts"],"sourcesContent":["/**\n * useMuscleActivation - Shared hook for muscle activation management\n *\n * Manages muscle activation state based on current actions and stamina.\n * Reduces code duplication in skeletal animation components.\n * Includes stance-based leg muscle tension for isometric holds.\n *\n * @module hooks/useMuscleActivation\n * @category Hooks\n * @korean 근육활성화훅\n */\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n MuscleActivationManager,\n getMuscleTensionForStance,\n} from \"../systems/animation\";\nimport type { TrigramStance } from \"../types/common\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../types/muscle\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Simple lerp function for muscle tension interpolation\n * Defined at module level to avoid recreation\n * @korean 선형보간함수\n */\nconst lerp = (start: number, end: number, t: number): number =>\n start + (end - start) * t;\n\n/**\n * Options for useMuscleActivation hook\n * @korean 근육활성화훅옵션\n */\nexport interface UseMuscleActivationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current stamina level (0-100) */\n readonly stamina: number;\n /** Current trigram stance (for leg muscle tension) */\n readonly currentStance?: TrigramStance;\n}\n\n/**\n * Return type for useMuscleActivation hook\n * @korean 근육활성화훅반환타입\n */\nexport interface UseMuscleActivationReturn {\n /** Current muscle activation states (bone name -> activation 0-1) */\n readonly muscleStates: Map<string, number>;\n /** Update muscle activations (call in useFrame) */\n readonly updateMuscleActivations: (\n delta: number,\n frameCounter: number\n ) => void;\n}\n\n/**\n * useMuscleActivation hook\n *\n * Manages muscle activation based on current actions (attack, defend, movement).\n * Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Muscle activation options\n * @returns Muscle states and update function\n *\n * @example\n * ```tsx\n * const { muscleStates, updateMuscleActivations } = useMuscleActivation({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * stamina: 85,\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateMuscleActivations(delta, frameCounter);\n * });\n *\n * // Use muscle states in rendering\n * <BoneRenderer\n * rig={rig}\n * muscleStates={muscleStates}\n * isExhausted={stamina < 20}\n * />\n * ```\n *\n * @korean 근육활성화훅\n */\nexport function useMuscleActivation(\n options: UseMuscleActivationOptions\n): UseMuscleActivationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stamina,\n currentStance,\n } = options;\n\n // Muscle activation manager\n const muscleManager = useRef(new MuscleActivationManager());\n const [muscleStates, setMuscleStates] = useState<Map<string, number>>(\n new Map()\n );\n\n // Cleanup muscle manager on unmount\n useEffect(() => {\n const manager = muscleManager.current;\n return () => {\n try {\n manager.reset();\n } catch (error) {\n console.warn(\"MuscleActivationManager reset failed:\", error);\n }\n };\n }, []);\n\n // Memoize stance tension to avoid recalculation at 60fps\n const stanceTension = useMemo(() => {\n return currentStance ? getMuscleTensionForStance(currentStance) : null;\n }, [currentStance]);\n\n // Update muscle activations (called at 60fps in useFrame)\n const updateMuscleActivations = (\n delta: number,\n frameCounter: number\n ): void => {\n // Update muscle system based on current action\n if (currentAnimation === \"attack\" && attackAnimation) {\n muscleManager.current.update(attackAnimation, stamina, delta);\n } else if (currentAnimation === \"defend\" || isBlocking) {\n muscleManager.current.update(\"block\", stamina, delta);\n } else if (\n currentAnimation === \"walk\" ||\n currentAnimation === \"stance_change\"\n ) {\n // Engage stance/leg/core muscles during movement and stance changes\n muscleManager.current.update(\"stance_change\", stamina, delta);\n } else if (currentAnimation === \"idle\" && stanceTension) {\n // Apply stance-based leg muscle tension for idle/guard positions\n // This provides realistic isometric contraction visualization\n\n // Get all activation states once (not in the loop)\n const allActivations = muscleManager.current.getAllActivations();\n\n // Update manager with stance muscle activations\n stanceTension.forEach((tension, muscleGroup) => {\n // Get existing activation state\n const state = allActivations.get(muscleGroup);\n\n if (state) {\n // Set target tension for smooth interpolation\n state.targetTension = tension;\n\n // Smoothly interpolate to target (using config activation speed)\n state.tension = lerp(\n state.tension,\n state.targetTension,\n DEFAULT_MUSCLE_CONFIG.activationSpeed * delta\n );\n }\n });\n } else {\n muscleManager.current.relaxAllMuscles(delta);\n }\n\n // Sync muscle states to React state deterministically\n // (every 10 frames at 60fps = ~6 times/sec)\n // Balances animation smoothness with performance and reduces GC pressure\n if (frameCounter === 0) {\n // Reuse scratch map from manager to avoid repeated allocations\n const scratchMap = muscleManager.current.getScratchMapForSync();\n setMuscleStates(scratchMap);\n }\n };\n\n return {\n muscleStates,\n updateMuscleActivations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,QAAQ,OAAe,KAAa,MACxC,SAAS,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoE1B,SAAgB,oBACd,SAC2B;CAC3B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,SACA,kBACE;CAGJ,MAAM,gBAAgB,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"useMuscleActivation.js","names":[],"sources":["../../src/hooks/useMuscleActivation.ts"],"sourcesContent":["/**\n * useMuscleActivation - Shared hook for muscle activation management\n *\n * Manages muscle activation state based on current actions and stamina.\n * Reduces code duplication in skeletal animation components.\n * Includes stance-based leg muscle tension for isometric holds.\n *\n * @module hooks/useMuscleActivation\n * @category Hooks\n * @korean 근육활성화훅\n */\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n MuscleActivationManager,\n getMuscleTensionForStance,\n} from \"../systems/animation\";\nimport type { TrigramStance } from \"../types/common\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../types/muscle\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Simple lerp function for muscle tension interpolation\n * Defined at module level to avoid recreation\n * @korean 선형보간함수\n */\nconst lerp = (start: number, end: number, t: number): number =>\n start + (end - start) * t;\n\n/**\n * Options for useMuscleActivation hook\n * @korean 근육활성화훅옵션\n */\nexport interface UseMuscleActivationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current stamina level (0-100) */\n readonly stamina: number;\n /** Current trigram stance (for leg muscle tension) */\n readonly currentStance?: TrigramStance;\n}\n\n/**\n * Return type for useMuscleActivation hook\n * @korean 근육활성화훅반환타입\n */\nexport interface UseMuscleActivationReturn {\n /** Current muscle activation states (bone name -> activation 0-1) */\n readonly muscleStates: Map<string, number>;\n /** Update muscle activations (call in useFrame) */\n readonly updateMuscleActivations: (\n delta: number,\n frameCounter: number\n ) => void;\n}\n\n/**\n * useMuscleActivation hook\n *\n * Manages muscle activation based on current actions (attack, defend, movement).\n * Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Muscle activation options\n * @returns Muscle states and update function\n *\n * @example\n * ```tsx\n * const { muscleStates, updateMuscleActivations } = useMuscleActivation({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * stamina: 85,\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateMuscleActivations(delta, frameCounter);\n * });\n *\n * // Use muscle states in rendering\n * <BoneRenderer\n * rig={rig}\n * muscleStates={muscleStates}\n * isExhausted={stamina < 20}\n * />\n * ```\n *\n * @korean 근육활성화훅\n */\nexport function useMuscleActivation(\n options: UseMuscleActivationOptions\n): UseMuscleActivationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stamina,\n currentStance,\n } = options;\n\n // Muscle activation manager\n const muscleManager = useRef(new MuscleActivationManager());\n const [muscleStates, setMuscleStates] = useState<Map<string, number>>(\n new Map()\n );\n\n // Cleanup muscle manager on unmount\n useEffect(() => {\n const manager = muscleManager.current;\n return () => {\n try {\n manager.reset();\n } catch (error) {\n console.warn(\"MuscleActivationManager reset failed:\", error);\n }\n };\n }, []);\n\n // Memoize stance tension to avoid recalculation at 60fps\n const stanceTension = useMemo(() => {\n return currentStance ? getMuscleTensionForStance(currentStance) : null;\n }, [currentStance]);\n\n // Update muscle activations (called at 60fps in useFrame)\n const updateMuscleActivations = (\n delta: number,\n frameCounter: number\n ): void => {\n // Update muscle system based on current action\n if (currentAnimation === \"attack\" && attackAnimation) {\n muscleManager.current.update(attackAnimation, stamina, delta);\n } else if (currentAnimation === \"defend\" || isBlocking) {\n muscleManager.current.update(\"block\", stamina, delta);\n } else if (\n currentAnimation === \"walk\" ||\n currentAnimation === \"stance_change\"\n ) {\n // Engage stance/leg/core muscles during movement and stance changes\n muscleManager.current.update(\"stance_change\", stamina, delta);\n } else if (currentAnimation === \"idle\" && stanceTension) {\n // Apply stance-based leg muscle tension for idle/guard positions\n // This provides realistic isometric contraction visualization\n\n // Get all activation states once (not in the loop)\n const allActivations = muscleManager.current.getAllActivations();\n\n // Update manager with stance muscle activations\n stanceTension.forEach((tension, muscleGroup) => {\n // Get existing activation state\n const state = allActivations.get(muscleGroup);\n\n if (state) {\n // Set target tension for smooth interpolation\n state.targetTension = tension;\n\n // Smoothly interpolate to target (using config activation speed)\n state.tension = lerp(\n state.tension,\n state.targetTension,\n DEFAULT_MUSCLE_CONFIG.activationSpeed * delta\n );\n }\n });\n } else {\n muscleManager.current.relaxAllMuscles(delta);\n }\n\n // Sync muscle states to React state deterministically\n // (every 10 frames at 60fps = ~6 times/sec)\n // Balances animation smoothness with performance and reduces GC pressure\n if (frameCounter === 0) {\n // Reuse scratch map from manager to avoid repeated allocations\n const scratchMap = muscleManager.current.getScratchMapForSync();\n setMuscleStates(scratchMap);\n }\n };\n\n return {\n muscleStates,\n updateMuscleActivations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,QAAQ,OAAe,KAAa,MACxC,SAAS,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoE1B,SAAgB,oBACd,SAC2B;CAC3B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,SACA,kBACE;CAGJ,MAAM,gBAAgB,OAAO,IAAI,wBAAwB,CAAC;CAC1D,MAAM,CAAC,cAAc,mBAAmB,yBACtC,IAAI,IAAI,CACV;CAGA,gBAAgB;EACd,MAAM,UAAU,cAAc;EAC9B,aAAa;GACX,IAAI;IACF,QAAQ,MAAM;GAChB,SAAS,OAAO;IACd,QAAQ,KAAK,yCAAyC,KAAK;GAC7D;EACF;CACF,GAAG,CAAC,CAAC;CAGL,MAAM,gBAAgB,cAAc;EAClC,OAAO,gBAAgB,0BAA0B,aAAa,IAAI;CACpE,GAAG,CAAC,aAAa,CAAC;CAGlB,MAAM,2BACJ,OACA,iBACS;EAET,IAAI,qBAAqB,YAAY,iBACnC,cAAc,QAAQ,OAAO,iBAAiB,SAAS,KAAK;OACvD,IAAI,qBAAqB,YAAY,YAC1C,cAAc,QAAQ,OAAO,SAAS,SAAS,KAAK;OAC/C,IACL,qBAAqB,UACrB,qBAAqB,iBAGrB,cAAc,QAAQ,OAAO,iBAAiB,SAAS,KAAK;OACvD,IAAI,qBAAqB,UAAU,eAAe;GAKvD,MAAM,iBAAiB,cAAc,QAAQ,kBAAkB;GAG/D,cAAc,SAAS,SAAS,gBAAgB;IAE9C,MAAM,QAAQ,eAAe,IAAI,WAAW;IAE5C,IAAI,OAAO;KAET,MAAM,gBAAgB;KAGtB,MAAM,UAAU,KACd,MAAM,SACN,MAAM,eACN,sBAAsB,kBAAkB,KAC1C;IACF;GACF,CAAC;EACH,OACE,cAAc,QAAQ,gBAAgB,KAAK;EAM7C,IAAI,iBAAiB,GAGnB,gBADmB,cAAc,QAAQ,qBACzB,CAAU;CAE9B;CAEA,OAAO;EACL;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePauseMenu.js","names":[],"sources":["../../src/hooks/usePauseMenu.ts"],"sourcesContent":["/**\n * usePauseMenu Hook - State management for pause menu\n * \n * Manages:\n * - Pause menu visibility\n * - Active submenu (controls, settings)\n * - Confirmation dialog state\n */\n\nimport { useCallback, useState } from \"react\";\n\nexport type PauseSubmenu = \"main\" | \"controls\" | \"settings\" | null;\n\nexport interface ConfirmDialogState {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n}\n\nexport interface UsePauseMenuResult {\n readonly activeSubmenu: PauseSubmenu;\n readonly confirmDialog: ConfirmDialogState;\n readonly showControls: () => void;\n readonly showSettings: () => void;\n readonly closeSubmenu: () => void;\n readonly openConfirmDialog: (config: Omit<ConfirmDialogState, \"isOpen\">) => void;\n readonly closeConfirmDialog: () => void;\n readonly confirmAndClose: () => void;\n}\n\nconst defaultConfirmDialog: ConfirmDialogState = {\n isOpen: false,\n title: \"\",\n titleKorean: \"\",\n message: \"\",\n messageKorean: \"\",\n onConfirm: () => {},\n};\n\n/**\n * Hook for managing pause menu state\n */\nexport function usePauseMenu(): UsePauseMenuResult {\n const [activeSubmenu, setActiveSubmenu] = useState<PauseSubmenu>(\"main\");\n const [confirmDialog, setConfirmDialog] = useState<ConfirmDialogState>(defaultConfirmDialog);\n\n const showControls = useCallback(() => {\n setActiveSubmenu(\"controls\");\n }, []);\n\n const showSettings = useCallback(() => {\n setActiveSubmenu(\"settings\");\n }, []);\n\n const closeSubmenu = useCallback(() => {\n setActiveSubmenu(\"main\");\n }, []);\n\n const openConfirmDialog = useCallback(\n (config: Omit<ConfirmDialogState, \"isOpen\">) => {\n setConfirmDialog({\n ...config,\n isOpen: true,\n });\n },\n []\n );\n\n const closeConfirmDialog = useCallback(() => {\n setConfirmDialog(defaultConfirmDialog);\n }, []);\n\n const confirmAndClose = useCallback(() => {\n if (confirmDialog.onConfirm) {\n confirmDialog.onConfirm();\n }\n closeConfirmDialog();\n }, [confirmDialog, closeConfirmDialog]);\n\n return {\n activeSubmenu,\n confirmDialog,\n showControls,\n showSettings,\n closeSubmenu,\n openConfirmDialog,\n closeConfirmDialog,\n confirmAndClose,\n };\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,uBAA2C;CAC/C,QAAQ;CACR,OAAO;CACP,aAAa;CACb,SAAS;CACT,eAAe;CACf,iBAAiB;
|
|
1
|
+
{"version":3,"file":"usePauseMenu.js","names":[],"sources":["../../src/hooks/usePauseMenu.ts"],"sourcesContent":["/**\n * usePauseMenu Hook - State management for pause menu\n * \n * Manages:\n * - Pause menu visibility\n * - Active submenu (controls, settings)\n * - Confirmation dialog state\n */\n\nimport { useCallback, useState } from \"react\";\n\nexport type PauseSubmenu = \"main\" | \"controls\" | \"settings\" | null;\n\nexport interface ConfirmDialogState {\n readonly isOpen: boolean;\n readonly title: string;\n readonly titleKorean: string;\n readonly message: string;\n readonly messageKorean: string;\n readonly onConfirm: () => void;\n}\n\nexport interface UsePauseMenuResult {\n readonly activeSubmenu: PauseSubmenu;\n readonly confirmDialog: ConfirmDialogState;\n readonly showControls: () => void;\n readonly showSettings: () => void;\n readonly closeSubmenu: () => void;\n readonly openConfirmDialog: (config: Omit<ConfirmDialogState, \"isOpen\">) => void;\n readonly closeConfirmDialog: () => void;\n readonly confirmAndClose: () => void;\n}\n\nconst defaultConfirmDialog: ConfirmDialogState = {\n isOpen: false,\n title: \"\",\n titleKorean: \"\",\n message: \"\",\n messageKorean: \"\",\n onConfirm: () => {},\n};\n\n/**\n * Hook for managing pause menu state\n */\nexport function usePauseMenu(): UsePauseMenuResult {\n const [activeSubmenu, setActiveSubmenu] = useState<PauseSubmenu>(\"main\");\n const [confirmDialog, setConfirmDialog] = useState<ConfirmDialogState>(defaultConfirmDialog);\n\n const showControls = useCallback(() => {\n setActiveSubmenu(\"controls\");\n }, []);\n\n const showSettings = useCallback(() => {\n setActiveSubmenu(\"settings\");\n }, []);\n\n const closeSubmenu = useCallback(() => {\n setActiveSubmenu(\"main\");\n }, []);\n\n const openConfirmDialog = useCallback(\n (config: Omit<ConfirmDialogState, \"isOpen\">) => {\n setConfirmDialog({\n ...config,\n isOpen: true,\n });\n },\n []\n );\n\n const closeConfirmDialog = useCallback(() => {\n setConfirmDialog(defaultConfirmDialog);\n }, []);\n\n const confirmAndClose = useCallback(() => {\n if (confirmDialog.onConfirm) {\n confirmDialog.onConfirm();\n }\n closeConfirmDialog();\n }, [confirmDialog, closeConfirmDialog]);\n\n return {\n activeSubmenu,\n confirmDialog,\n showControls,\n showSettings,\n closeSubmenu,\n openConfirmDialog,\n closeConfirmDialog,\n confirmAndClose,\n };\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,uBAA2C;CAC/C,QAAQ;CACR,OAAO;CACP,aAAa;CACb,SAAS;CACT,eAAe;CACf,iBAAiB,CAAC;AACpB;;;;AAKA,SAAgB,eAAmC;CACjD,MAAM,CAAC,eAAe,oBAAoB,SAAuB,MAAM;CACvE,MAAM,CAAC,eAAe,oBAAoB,SAA6B,oBAAoB;CAE3F,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,UAAU;CAC7B,GAAG,CAAC,CAAC;CAEL,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,UAAU;CAC7B,GAAG,CAAC,CAAC;CAEL,MAAM,eAAe,kBAAkB;EACrC,iBAAiB,MAAM;CACzB,GAAG,CAAC,CAAC;CAEL,MAAM,oBAAoB,aACvB,WAA+C;EAC9C,iBAAiB;GACf,GAAG;GACH,QAAQ;EACV,CAAC;CACH,GACA,CAAC,CACH;CAEA,MAAM,qBAAqB,kBAAkB;EAC3C,iBAAiB,oBAAoB;CACvC,GAAG,CAAC,CAAC;CASL,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAfsB,kBAAkB;GACxC,IAAI,cAAc,WAChB,cAAc,UAAU;GAE1B,mBAAmB;EACrB,GAAG,CAAC,eAAe,kBAAkB,CAUnC;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePlayerAnimation.js","names":[],"sources":["../../src/hooks/usePlayerAnimation.ts"],"sourcesContent":["/**\n * usePlayerAnimation - React hook for player animation state\n *\n * Provides a React interface to the PlayerAnimationStateMachine\n * with automatic cleanup and event handling.\n *\n * @module hooks/usePlayerAnimation\n * @category Hooks\n * @korean 플레이어애니메이션훅\n */\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport {\n AnimationEvents,\n AnimationState,\n DEFAULT_ANIMATION_CONFIGS,\n PlayerAnimationStateMachine,\n} from \"../systems/animation\";\nimport type {\n AnimationConfig,\n AnimationUpdateResult,\n} from \"../systems/animation/core/types\";\nimport type { TrigramStance } from \"../types/common\";\n\n/**\n * Options for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅옵션\n */\nexport interface UsePlayerAnimationOptions {\n /**\n * Custom animation configurations\n * If not provided, uses DEFAULT_ANIMATION_CONFIGS\n *\n * @korean 커스텀애니메이션설정\n */\n readonly customConfigs?: Map<AnimationState, AnimationConfig>;\n\n /**\n * Animation event callbacks\n *\n * **IMPORTANT**: The events object should be stable (memoized) to prevent\n * unnecessary re-initialization of the animation system. Changes to event\n * callbacks after the hook is initialized will NOT be reflected in the\n * animation system. Use `useMemo` or define events outside the component\n * to ensure stability.\n *\n * @korean 이벤트콜백\n */\n readonly events?: AnimationEvents;\n\n /**\n * Initial animation state (defaults to \"idle\")\n *\n * @korean 초기상태\n */\n readonly initialState?: AnimationState;\n}\n\n/**\n * Return type for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅반환타입\n */\nexport interface UsePlayerAnimationReturn {\n /**\n * Current animation state\n *\n * @korean 현재상태\n */\n readonly currentState: AnimationState;\n\n /**\n * Current frame index\n *\n * @korean 현재프레임\n */\n readonly currentFrame: number;\n\n /**\n * Update animation state (call in useFrame)\n *\n * @param deltaTime - Time elapsed since last update in seconds\n * @returns Animation update result\n *\n * @korean 업데이트\n */\n readonly update: (deltaTime: number) => AnimationUpdateResult;\n\n /**\n * Transition to a new animation state\n *\n * @param newState - Target animation state\n * @returns Whether transition was successful\n *\n * @korean 상태전환\n */\n readonly transitionTo: (newState: AnimationState) => boolean;\n\n /**\n * Transition to ATTACK state with technique-specific duration.\n *\n * The default ATTACK config is 200ms (12 frames), but real techniques\n * range from 350ms to 1200ms. This method overrides the duration so\n * the state machine stays in ATTACK for the full technique animation.\n *\n * @param durationSeconds - The skeletal animation duration in seconds\n * @returns Whether transition was successful\n *\n * @korean 공격전환 (기술별 지속시간)\n */\n readonly transitionToAttack: (durationSeconds: number) => boolean;\n\n /**\n * Transition to stance-specific guard animation\n *\n * @param stance - Trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세가드전환\n */\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n\n /**\n * Transition to stance_change animation with specific transition data\n *\n * **Korean**: 자세 전환 애니메이션 시작\n *\n * Initiates a stance change animation with the specific transition data\n * from the 64-transition matrix. This provides stance-specific keyframes\n * and blend weights for smooth interpolation.\n *\n * @param fromStance - Source trigram stance\n * @param toStance - Target trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세전환애니메이션시작\n */\n readonly transitionToStanceChange: (\n fromStance: TrigramStance,\n toStance: TrigramStance,\n ) => boolean;\n\n /**\n * Check if currently in a stance guard animation\n *\n * @returns True if in any stance guard state\n *\n * @korean 자세가드확인\n */\n readonly isInStanceGuard: () => boolean;\n\n /**\n * Get current guard stance (if in guard animation)\n *\n * @returns Trigram stance or null\n *\n * @korean 현재가드자세\n */\n readonly getCurrentGuardStance: () => TrigramStance | null;\n\n /**\n * Reset animation to idle state\n *\n * @korean 초기화\n */\n readonly reset: () => void;\n}\n\n/**\n * React hook for player animation state management\n *\n * Provides frame-accurate animation control with priority system\n * and event callbacks. Integrates seamlessly with useFrame for\n * 60fps updates.\n *\n * @param options - Animation options\n * @returns Animation control interface\n *\n * @example\n * ```typescript\n * // Basic usage\n * const { currentState, currentFrame, update, transitionTo } = usePlayerAnimation({\n * events: {\n * onAnimationStart: (state) => console.log(`Started ${state}`),\n * onAnimationComplete: (state) => console.log(`Completed ${state}`),\n * onFrame: (frame, state) => {\n * if (state === \"attack\" && frame === 6) {\n * // Execute attack at midpoint\n * executeAttack();\n * }\n * }\n * }\n * });\n *\n * // In useFrame callback\n * useFrame((state, delta) => {\n * const result = update(delta);\n * // Update visuals based on result.state and result.frame\n * });\n *\n * // Trigger animations\n * const handleAttackInput = () => {\n * transitionTo(\"attack\");\n * };\n *\n * const handleMovement = (isMoving: boolean) => {\n * transitionTo(isMoving ? \"walk\" : \"idle\");\n * };\n * ```\n *\n * @korean 플레이어애니메이션훅\n */\nexport function usePlayerAnimation(\n options: UsePlayerAnimationOptions = {},\n): UsePlayerAnimationReturn {\n const { customConfigs, events, initialState = \"idle\" } = options;\n\n // Create animation configs (memoized)\n const configs = useMemo(\n () => customConfigs ?? DEFAULT_ANIMATION_CONFIGS,\n [customConfigs],\n );\n\n // Create animation state machine (persistent across renders via useMemo)\n const stateMachine = useMemo(() => {\n const machine = new PlayerAnimationStateMachine(configs, events);\n // Set initial state if not \"idle\"\n if (initialState !== \"idle\") {\n machine.transitionTo(initialState);\n }\n return machine;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []); // Empty deps - only create once\n\n // Track current state/frame as React state so they can be safely returned\n // during render. Previously these were refs paired with a forceUpdate trick,\n // but react-hooks/refs forbids reading .current during render.\n const [currentState, setCurrentState] = useState<AnimationState>(() =>\n stateMachine.getCurrentState(),\n );\n const [currentFrame, setCurrentFrame] = useState<number>(() =>\n stateMachine.getCurrentFrame(),\n );\n\n // Memoized callbacks with selective state updates\n const update = useCallback(\n (deltaTime: number) => {\n const result = stateMachine.update(deltaTime);\n const nextState = stateMachine.getCurrentState();\n const nextFrame = stateMachine.getCurrentFrame();\n\n // Only trigger re-render if state or frame changed\n setCurrentState((prev) => (prev !== nextState ? nextState : prev));\n setCurrentFrame((prev) => (prev !== nextFrame ? nextFrame : prev));\n\n return result;\n },\n [stateMachine],\n );\n\n const transitionTo = useCallback(\n (newState: AnimationState) => {\n const success = stateMachine.transitionTo(newState);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const reset = useCallback(() => {\n stateMachine.reset();\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }, [stateMachine]);\n\n const transitionToStanceGuard = useCallback(\n (stance: TrigramStance) => {\n const success = stateMachine.transitionToStanceGuard(stance);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToAttack = useCallback(\n (durationSeconds: number) => {\n const success = stateMachine.transitionToAttack(durationSeconds);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToStanceChange = useCallback(\n (fromStance: TrigramStance, toStance: TrigramStance) => {\n const success = stateMachine.transitionToStanceChange(\n fromStance,\n toStance,\n );\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const isInStanceGuard = useCallback(() => {\n return stateMachine.isInStanceGuard();\n }, [stateMachine]);\n\n const getCurrentGuardStance = useCallback(() => {\n return stateMachine.getCurrentGuardStance();\n }, [stateMachine]);\n\n // Return a fresh object each render so consumers see the latest state/frame\n return {\n currentState,\n currentFrame,\n update,\n transitionTo,\n transitionToAttack,\n transitionToStanceGuard,\n transitionToStanceChange,\n isInStanceGuard,\n getCurrentGuardStance,\n reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqNA,SAAgB,mBACd,UAAqC,EAAE,EACb;CAC1B,MAAM,EAAE,eAAe,QAAQ,eAAe,WAAW;CAGzD,MAAM,UAAU,cACR,iBAAiB,2BACvB,CAAC,cAAc,CAChB;CAGD,MAAM,eAAe,cAAc;EACjC,MAAM,UAAU,IAAI,4BAA4B,SAAS,OAAO;EAEhE,IAAI,iBAAiB,QACnB,QAAQ,aAAa,aAAa;EAEpC,OAAO;IAEN,EAAE,CAAC;CAKN,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CACD,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,iBAAiB,CAC/B;CAGD,MAAM,SAAS,aACZ,cAAsB;EACrB,MAAM,SAAS,aAAa,OAAO,UAAU;EAC7C,MAAM,YAAY,aAAa,iBAAiB;EAChD,MAAM,YAAY,aAAa,iBAAiB;EAGhD,iBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;EAClE,iBAAiB,SAAU,SAAS,YAAY,YAAY,KAAM;EAElE,OAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,eAAe,aAClB,aAA6B;EAC5B,MAAM,UAAU,aAAa,aAAa,SAAS;EACnD,IAAI,SAAS;GACX,gBAAgB,aAAa,iBAAiB,CAAC;GAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;EAEjD,OAAO;IAET,CAAC,aAAa,CACf;CAED,MAAM,QAAQ,kBAAkB;EAC9B,aAAa,OAAO;EACpB,gBAAgB,aAAa,iBAAiB,CAAC;EAC/C,gBAAgB,aAAa,iBAAiB,CAAC;IAC9C,CAAC,aAAa,CAAC;CAElB,MAAM,0BAA0B,aAC7B,WAA0B;EACzB,MAAM,UAAU,aAAa,wBAAwB,OAAO;EAC5D,IAAI,SAAS;GACX,gBAAgB,aAAa,iBAAiB,CAAC;GAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;EAEjD,OAAO;IAET,CAAC,aAAa,CACf;CAsCD,OAAO;EACL;EACA;EACA;EACA;EACA,oBAzCyB,aACxB,oBAA4B;GAC3B,MAAM,UAAU,aAAa,mBAAmB,gBAAgB;GAChE,IAAI,SAAS;IACX,gBAAgB,aAAa,iBAAiB,CAAC;IAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;GAEjD,OAAO;KAET,CAAC,aAAa,CAgCd;EACA;EACA,0BA/B+B,aAC9B,YAA2B,aAA4B;GACtD,MAAM,UAAU,aAAa,yBAC3B,YACA,SACD;GACD,IAAI,SAAS;IACX,gBAAgB,aAAa,iBAAiB,CAAC;IAC/C,gBAAgB,aAAa,iBAAiB,CAAC;;GAEjD,OAAO;KAET,CAAC,aAAa,CAmBd;EACA,iBAjBsB,kBAAkB;GACxC,OAAO,aAAa,iBAAiB;KACpC,CAAC,aAAa,CAef;EACA,uBAd4B,kBAAkB;GAC9C,OAAO,aAAa,uBAAuB;KAC1C,CAAC,aAAa,CAYf;EACA;EACD"}
|
|
1
|
+
{"version":3,"file":"usePlayerAnimation.js","names":[],"sources":["../../src/hooks/usePlayerAnimation.ts"],"sourcesContent":["/**\n * usePlayerAnimation - React hook for player animation state\n *\n * Provides a React interface to the PlayerAnimationStateMachine\n * with automatic cleanup and event handling.\n *\n * @module hooks/usePlayerAnimation\n * @category Hooks\n * @korean 플레이어애니메이션훅\n */\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport {\n AnimationEvents,\n AnimationState,\n DEFAULT_ANIMATION_CONFIGS,\n PlayerAnimationStateMachine,\n} from \"../systems/animation\";\nimport type {\n AnimationConfig,\n AnimationUpdateResult,\n} from \"../systems/animation/core/types\";\nimport type { TrigramStance } from \"../types/common\";\n\n/**\n * Options for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅옵션\n */\nexport interface UsePlayerAnimationOptions {\n /**\n * Custom animation configurations\n * If not provided, uses DEFAULT_ANIMATION_CONFIGS\n *\n * @korean 커스텀애니메이션설정\n */\n readonly customConfigs?: Map<AnimationState, AnimationConfig>;\n\n /**\n * Animation event callbacks\n *\n * **IMPORTANT**: The events object should be stable (memoized) to prevent\n * unnecessary re-initialization of the animation system. Changes to event\n * callbacks after the hook is initialized will NOT be reflected in the\n * animation system. Use `useMemo` or define events outside the component\n * to ensure stability.\n *\n * @korean 이벤트콜백\n */\n readonly events?: AnimationEvents;\n\n /**\n * Initial animation state (defaults to \"idle\")\n *\n * @korean 초기상태\n */\n readonly initialState?: AnimationState;\n}\n\n/**\n * Return type for usePlayerAnimation hook\n *\n * @korean 플레이어애니메이션훅반환타입\n */\nexport interface UsePlayerAnimationReturn {\n /**\n * Current animation state\n *\n * @korean 현재상태\n */\n readonly currentState: AnimationState;\n\n /**\n * Current frame index\n *\n * @korean 현재프레임\n */\n readonly currentFrame: number;\n\n /**\n * Update animation state (call in useFrame)\n *\n * @param deltaTime - Time elapsed since last update in seconds\n * @returns Animation update result\n *\n * @korean 업데이트\n */\n readonly update: (deltaTime: number) => AnimationUpdateResult;\n\n /**\n * Transition to a new animation state\n *\n * @param newState - Target animation state\n * @returns Whether transition was successful\n *\n * @korean 상태전환\n */\n readonly transitionTo: (newState: AnimationState) => boolean;\n\n /**\n * Transition to ATTACK state with technique-specific duration.\n *\n * The default ATTACK config is 200ms (12 frames), but real techniques\n * range from 350ms to 1200ms. This method overrides the duration so\n * the state machine stays in ATTACK for the full technique animation.\n *\n * @param durationSeconds - The skeletal animation duration in seconds\n * @returns Whether transition was successful\n *\n * @korean 공격전환 (기술별 지속시간)\n */\n readonly transitionToAttack: (durationSeconds: number) => boolean;\n\n /**\n * Transition to stance-specific guard animation\n *\n * @param stance - Trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세가드전환\n */\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n\n /**\n * Transition to stance_change animation with specific transition data\n *\n * **Korean**: 자세 전환 애니메이션 시작\n *\n * Initiates a stance change animation with the specific transition data\n * from the 64-transition matrix. This provides stance-specific keyframes\n * and blend weights for smooth interpolation.\n *\n * @param fromStance - Source trigram stance\n * @param toStance - Target trigram stance\n * @returns Whether transition was successful\n *\n * @korean 자세전환애니메이션시작\n */\n readonly transitionToStanceChange: (\n fromStance: TrigramStance,\n toStance: TrigramStance,\n ) => boolean;\n\n /**\n * Check if currently in a stance guard animation\n *\n * @returns True if in any stance guard state\n *\n * @korean 자세가드확인\n */\n readonly isInStanceGuard: () => boolean;\n\n /**\n * Get current guard stance (if in guard animation)\n *\n * @returns Trigram stance or null\n *\n * @korean 현재가드자세\n */\n readonly getCurrentGuardStance: () => TrigramStance | null;\n\n /**\n * Reset animation to idle state\n *\n * @korean 초기화\n */\n readonly reset: () => void;\n}\n\n/**\n * React hook for player animation state management\n *\n * Provides frame-accurate animation control with priority system\n * and event callbacks. Integrates seamlessly with useFrame for\n * 60fps updates.\n *\n * @param options - Animation options\n * @returns Animation control interface\n *\n * @example\n * ```typescript\n * // Basic usage\n * const { currentState, currentFrame, update, transitionTo } = usePlayerAnimation({\n * events: {\n * onAnimationStart: (state) => console.log(`Started ${state}`),\n * onAnimationComplete: (state) => console.log(`Completed ${state}`),\n * onFrame: (frame, state) => {\n * if (state === \"attack\" && frame === 6) {\n * // Execute attack at midpoint\n * executeAttack();\n * }\n * }\n * }\n * });\n *\n * // In useFrame callback\n * useFrame((state, delta) => {\n * const result = update(delta);\n * // Update visuals based on result.state and result.frame\n * });\n *\n * // Trigger animations\n * const handleAttackInput = () => {\n * transitionTo(\"attack\");\n * };\n *\n * const handleMovement = (isMoving: boolean) => {\n * transitionTo(isMoving ? \"walk\" : \"idle\");\n * };\n * ```\n *\n * @korean 플레이어애니메이션훅\n */\nexport function usePlayerAnimation(\n options: UsePlayerAnimationOptions = {},\n): UsePlayerAnimationReturn {\n const { customConfigs, events, initialState = \"idle\" } = options;\n\n // Create animation configs (memoized)\n const configs = useMemo(\n () => customConfigs ?? DEFAULT_ANIMATION_CONFIGS,\n [customConfigs],\n );\n\n // Create animation state machine (persistent across renders via useMemo)\n const stateMachine = useMemo(() => {\n const machine = new PlayerAnimationStateMachine(configs, events);\n // Set initial state if not \"idle\"\n if (initialState !== \"idle\") {\n machine.transitionTo(initialState);\n }\n return machine;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []); // Empty deps - only create once\n\n // Track current state/frame as React state so they can be safely returned\n // during render. Previously these were refs paired with a forceUpdate trick,\n // but react-hooks/refs forbids reading .current during render.\n const [currentState, setCurrentState] = useState<AnimationState>(() =>\n stateMachine.getCurrentState(),\n );\n const [currentFrame, setCurrentFrame] = useState<number>(() =>\n stateMachine.getCurrentFrame(),\n );\n\n // Memoized callbacks with selective state updates\n const update = useCallback(\n (deltaTime: number) => {\n const result = stateMachine.update(deltaTime);\n const nextState = stateMachine.getCurrentState();\n const nextFrame = stateMachine.getCurrentFrame();\n\n // Only trigger re-render if state or frame changed\n setCurrentState((prev) => (prev !== nextState ? nextState : prev));\n setCurrentFrame((prev) => (prev !== nextFrame ? nextFrame : prev));\n\n return result;\n },\n [stateMachine],\n );\n\n const transitionTo = useCallback(\n (newState: AnimationState) => {\n const success = stateMachine.transitionTo(newState);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const reset = useCallback(() => {\n stateMachine.reset();\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }, [stateMachine]);\n\n const transitionToStanceGuard = useCallback(\n (stance: TrigramStance) => {\n const success = stateMachine.transitionToStanceGuard(stance);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToAttack = useCallback(\n (durationSeconds: number) => {\n const success = stateMachine.transitionToAttack(durationSeconds);\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const transitionToStanceChange = useCallback(\n (fromStance: TrigramStance, toStance: TrigramStance) => {\n const success = stateMachine.transitionToStanceChange(\n fromStance,\n toStance,\n );\n if (success) {\n setCurrentState(stateMachine.getCurrentState());\n setCurrentFrame(stateMachine.getCurrentFrame());\n }\n return success;\n },\n [stateMachine],\n );\n\n const isInStanceGuard = useCallback(() => {\n return stateMachine.isInStanceGuard();\n }, [stateMachine]);\n\n const getCurrentGuardStance = useCallback(() => {\n return stateMachine.getCurrentGuardStance();\n }, [stateMachine]);\n\n // Return a fresh object each render so consumers see the latest state/frame\n return {\n currentState,\n currentFrame,\n update,\n transitionTo,\n transitionToAttack,\n transitionToStanceGuard,\n transitionToStanceChange,\n isInStanceGuard,\n getCurrentGuardStance,\n reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqNA,SAAgB,mBACd,UAAqC,CAAC,GACZ;CAC1B,MAAM,EAAE,eAAe,QAAQ,eAAe,WAAW;CAGzD,MAAM,UAAU,cACR,iBAAiB,2BACvB,CAAC,aAAa,CAChB;CAGA,MAAM,eAAe,cAAc;EACjC,MAAM,UAAU,IAAI,4BAA4B,SAAS,MAAM;EAE/D,IAAI,iBAAiB,QACnB,QAAQ,aAAa,YAAY;EAEnC,OAAO;CAET,GAAG,CAAC,CAAC;CAKL,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,gBAAgB,CAC/B;CACA,MAAM,CAAC,cAAc,mBAAmB,eACtC,aAAa,gBAAgB,CAC/B;CAGA,MAAM,SAAS,aACZ,cAAsB;EACrB,MAAM,SAAS,aAAa,OAAO,SAAS;EAC5C,MAAM,YAAY,aAAa,gBAAgB;EAC/C,MAAM,YAAY,aAAa,gBAAgB;EAG/C,iBAAiB,SAAU,SAAS,YAAY,YAAY,IAAK;EACjE,iBAAiB,SAAU,SAAS,YAAY,YAAY,IAAK;EAEjE,OAAO;CACT,GACA,CAAC,YAAY,CACf;CAEA,MAAM,eAAe,aAClB,aAA6B;EAC5B,MAAM,UAAU,aAAa,aAAa,QAAQ;EAClD,IAAI,SAAS;GACX,gBAAgB,aAAa,gBAAgB,CAAC;GAC9C,gBAAgB,aAAa,gBAAgB,CAAC;EAChD;EACA,OAAO;CACT,GACA,CAAC,YAAY,CACf;CAEA,MAAM,QAAQ,kBAAkB;EAC9B,aAAa,MAAM;EACnB,gBAAgB,aAAa,gBAAgB,CAAC;EAC9C,gBAAgB,aAAa,gBAAgB,CAAC;CAChD,GAAG,CAAC,YAAY,CAAC;CAEjB,MAAM,0BAA0B,aAC7B,WAA0B;EACzB,MAAM,UAAU,aAAa,wBAAwB,MAAM;EAC3D,IAAI,SAAS;GACX,gBAAgB,aAAa,gBAAgB,CAAC;GAC9C,gBAAgB,aAAa,gBAAgB,CAAC;EAChD;EACA,OAAO;CACT,GACA,CAAC,YAAY,CACf;CAsCA,OAAO;EACL;EACA;EACA;EACA;EACA,oBAzCyB,aACxB,oBAA4B;GAC3B,MAAM,UAAU,aAAa,mBAAmB,eAAe;GAC/D,IAAI,SAAS;IACX,gBAAgB,aAAa,gBAAgB,CAAC;IAC9C,gBAAgB,aAAa,gBAAgB,CAAC;GAChD;GACA,OAAO;EACT,GACA,CAAC,YAAY,CAgCb;EACA;EACA,0BA/B+B,aAC9B,YAA2B,aAA4B;GACtD,MAAM,UAAU,aAAa,yBAC3B,YACA,QACF;GACA,IAAI,SAAS;IACX,gBAAgB,aAAa,gBAAgB,CAAC;IAC9C,gBAAgB,aAAa,gBAAgB,CAAC;GAChD;GACA,OAAO;EACT,GACA,CAAC,YAAY,CAmBb;EACA,iBAjBsB,kBAAkB;GACxC,OAAO,aAAa,gBAAgB;EACtC,GAAG,CAAC,YAAY,CAed;EACA,uBAd4B,kBAAkB;GAC9C,OAAO,aAAa,sBAAsB;EAC5C,GAAG,CAAC,YAAY,CAYd;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResponsiveLayout.js","names":[],"sources":["../../src/hooks/useResponsiveLayout.ts"],"sourcesContent":["/**\n * useResponsiveLayout Hook - Enhanced Responsive Layout System\n * \n * Provides comprehensive responsive layout values for all screen sizes with:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px readability constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth transitions for resize operations (300ms ease-in-out)\n * - Touch-friendly element sizing following iOS Human Interface Guidelines\n * - Safe area insets for notch and home indicator\n * \n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n * \n * Features:\n * - Touch target minimum: 44x44px (iOS guideline)\n * - Safe area insets for notch and home indicator\n * - Responsive font sizes (14px minimum body text)\n * - Optimized spacing for mobile vs desktop\n * - Portrait and landscape mode support\n * - Tablet breakpoint support (768-1024px)\n * \n * @module hooks/useResponsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃훅\n */\n\nimport { useMemo } from 'react';\nimport { shouldUseMobileControls, getSafeAreaInsets as getDeviceSafeAreaInsets } from '../utils/deviceDetection';\nimport {\n RESPONSIVE_BREAKPOINTS,\n getScreenSize,\n calculateResponsiveValues,\n createTransitionString,\n} from '../systems/ResponsiveScaling';\n\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/**\n * Width breakpoint (in CSS pixels) below which devices are treated as\n * extra-small mobile (e.g. iPhone SE). Kept local to this hook because the\n * wider responsive scaling system uses a different bucketing scheme\n * (mobile / tablet / desktop / large / xlarge) that does not expose this\n * sub-mobile threshold.\n */\nconst SMALL_MOBILE_MAX_WIDTH = 375;\n\n/**\n * Breakpoints for responsive design exposed by this hook.\n *\n * @deprecated Prefer `RESPONSIVE_BREAKPOINTS` from `../systems/ResponsiveScaling`\n * for the standard mobile / tablet / desktop tiers. This object is retained as\n * a backward-compatible shim for existing tests and external consumers and\n * additionally exposes the hook-local `MOBILE_SMALL` (iPhone SE) threshold.\n */\nexport const BREAKPOINTS = Object.freeze({\n /** Extra small mobile devices (iPhone SE) */\n MOBILE_SMALL: SMALL_MOBILE_MAX_WIDTH,\n /** Standard mobile devices */\n MOBILE: RESPONSIVE_BREAKPOINTS.MOBILE,\n /** Tablet devices */\n TABLET: RESPONSIVE_BREAKPOINTS.TABLET,\n /** Desktop devices */\n DESKTOP: RESPONSIVE_BREAKPOINTS.LARGE,\n}) as {\n readonly MOBILE_SMALL: number;\n readonly MOBILE: number;\n readonly TABLET: number;\n readonly DESKTOP: number;\n};\n\n/**\n * Touch target sizes following iOS Human Interface Guidelines\n */\nexport interface TouchTargets {\n /** Minimum touch target (44x44px iOS guideline) */\n readonly small: number;\n /** Medium touch target for primary actions */\n readonly medium: number;\n /** Large touch target for critical actions */\n readonly large: number;\n}\n\n/**\n * Safe area insets for mobile devices\n * Accounts for notch, status bar, and home indicator\n */\nexport interface SafeAreaInsets {\n /** Top inset (status bar, notch) */\n readonly top: number;\n /** Bottom inset (home indicator) */\n readonly bottom: number;\n /** Left inset (landscape mode) */\n readonly left: number;\n /** Right inset (landscape mode) */\n readonly right: number;\n}\n\n/**\n * Responsive font sizes\n */\nexport interface FontSizes {\n /** Small text (12-14px) */\n readonly small: number;\n /** Body text (14-16px minimum for mobile) */\n readonly body: number;\n /** Title text (18-24px) */\n readonly title: number;\n /** Hero text (24-36px) */\n readonly hero: number;\n /** HUD text (16-20px for important combat info) */\n readonly hud: number;\n}\n\n/**\n * Responsive spacing scale\n */\nexport interface Spacing {\n /** Extra small (4-8px) */\n readonly xs: number;\n /** Small (8-12px) */\n readonly sm: number;\n /** Medium (12-16px) */\n readonly md: number;\n /** Large (16-24px) */\n readonly lg: number;\n /** Extra large (24-32px) */\n readonly xl: number;\n}\n\n/**\n * Complete responsive layout configuration\n */\nexport interface ResponsiveLayout {\n /** Whether current viewport is mobile-sized */\n readonly isMobile: boolean;\n /** Whether current viewport is small mobile (< 400px) */\n readonly isSmallMobile: boolean;\n /** Whether current viewport is tablet-sized */\n readonly isTablet: boolean;\n /** Whether current viewport is desktop or larger */\n readonly isDesktop: boolean;\n /** Whether in landscape orientation */\n readonly isLandscape: boolean;\n /** Current screen size category (mobile, tablet, desktop, large, xlarge) */\n readonly screenSize: ScreenSize;\n /** Safe area insets for mobile devices */\n readonly safeArea: SafeAreaInsets;\n /** Touch target sizes */\n readonly touchTarget: TouchTargets;\n /** Font sizes */\n readonly fontSize: FontSizes;\n /** Spacing scale */\n readonly spacing: Spacing;\n /** CSS transition string for smooth resizing */\n readonly transition: string;\n /** Current viewport dimensions */\n readonly viewport: {\n readonly width: number;\n readonly height: number;\n };\n}\n\n/**\n * Calculate responsive layout values based on viewport dimensions\n * \n * Enhanced with centralized responsive scaling system:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth CSS transitions for resize operations\n * \n * Optimized for:\n * - iPhone SE (375x667) - mobile\n * - iPhone 11/12/13 (414x896) - mobile\n * - iPhone 14 Pro Max (430x932) - mobile\n * - iPad (768x1024) - tablet\n * - iPad Pro (1024x1366) - desktop\n * - Standard Desktop (1280x800) - desktop\n * - HD Display (1920x1080) - xlarge\n * - 4K Display (2560x1440) - xlarge\n * \n * @param width - Viewport width in pixels\n * @param height - Viewport height in pixels\n * @returns Complete responsive layout configuration\n * \n * @example\n * ```tsx\n * const layout = useResponsiveLayout(375, 667);\n * \n * <div style={{\n * padding: layout.spacing.md,\n * fontSize: layout.fontSize.body,\n * minHeight: layout.touchTarget.small,\n * transition: layout.transition,\n * }}>\n * Responsive content\n * </div>\n * ```\n * \n * @public\n * @korean 반응형레이아웃사용\n */\nexport function useResponsiveLayout(\n width: number,\n height: number\n): ResponsiveLayout {\n return useMemo(() => {\n // Determine screen size category using centralized scaling system\n const screenSize = getScreenSize(width);\n \n // Device type detection using robust device detection utility\n // Combines user-agent and screen size for reliable mobile detection\n const isMobile = shouldUseMobileControls();\n // Extra-small mobile (e.g. iPhone SE) detection\n const isSmallMobile = width <= SMALL_MOBILE_MAX_WIDTH;\n const isTablet = screenSize === 'tablet';\n const isDesktop = screenSize === 'desktop' || screenSize === 'large' || screenSize === 'xlarge';\n const isLandscape = width > height;\n\n // Safe area insets from device detection utility\n const deviceInsets = getDeviceSafeAreaInsets();\n const safeArea: SafeAreaInsets = {\n top: deviceInsets.top,\n bottom: deviceInsets.bottom,\n left: isLandscape ? deviceInsets.left : 0,\n right: isLandscape ? deviceInsets.right : 0,\n };\n\n // Touch target sizes (iOS Human Interface Guidelines: 44x44px minimum)\n const touchTarget: TouchTargets = {\n small: 44, // Minimum iOS guideline\n medium: isSmallMobile ? 50 : 60,\n large: isSmallMobile ? 60 : 80,\n };\n\n // Calculate responsive values using centralized scaling system\n const responsiveValues = calculateResponsiveValues(width);\n\n // Map to existing font size structure for backward compatibility\n const fontSize: FontSizes = {\n small: responsiveValues.fontSize.small,\n body: responsiveValues.fontSize.body,\n title: responsiveValues.fontSize.title,\n hero: responsiveValues.fontSize.hero,\n hud: responsiveValues.fontSize.hud,\n };\n\n // Map to existing spacing structure for backward compatibility\n const spacing: Spacing = {\n xs: responsiveValues.spacing.xs,\n sm: responsiveValues.spacing.sm,\n md: responsiveValues.spacing.md,\n lg: responsiveValues.spacing.lg,\n xl: responsiveValues.spacing.xl,\n };\n\n // Get transition string for smooth resizing\n const transition = createTransitionString();\n\n return {\n isMobile,\n isSmallMobile,\n isTablet,\n isDesktop,\n isLandscape,\n screenSize,\n safeArea,\n touchTarget,\n fontSize,\n spacing,\n transition,\n viewport: { width, height },\n };\n }, [width, height]);\n}\n\n/**\n * Calculate available content area after safe areas and HUD\n * \n * @param layout - Responsive layout configuration\n * @param hudHeight - Height reserved for HUD elements\n * @param controlsHeight - Height reserved for control elements\n * @returns Available content dimensions\n */\nexport function useContentArea(\n layout: ResponsiveLayout,\n hudHeight: number = 0,\n controlsHeight: number = 0\n) {\n return useMemo(() => {\n const totalVerticalReserved =\n layout.safeArea.top +\n layout.safeArea.bottom +\n hudHeight +\n controlsHeight +\n layout.spacing.md * 2; // Padding\n\n const totalHorizontalReserved =\n layout.safeArea.left + layout.safeArea.right + layout.spacing.md * 2;\n\n return {\n width: layout.viewport.width - totalHorizontalReserved,\n height: layout.viewport.height - totalVerticalReserved,\n x: layout.safeArea.left + layout.spacing.md,\n y: layout.safeArea.top + hudHeight + layout.spacing.md,\n };\n }, [layout, hudHeight, controlsHeight]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAM,yBAAyB;;;;;;;;;AAU/B,IAAa,cAAc,OAAO,OAAO;;CAEvC,cAAc;;CAEd,QAAQ,uBAAuB;;CAE/B,QAAQ,uBAAuB;;CAE/B,SAAS,uBAAuB;CACjC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IF,SAAgB,oBACd,OACA,QACkB;CAClB,OAAO,cAAc;EAEnB,MAAM,aAAa,cAAc,MAAM;EAIvC,MAAM,WAAW,yBAAyB;EAE1C,MAAM,gBAAgB,SAAS;EAC/B,MAAM,WAAW,eAAe;EAChC,MAAM,YAAY,eAAe,aAAa,eAAe,WAAW,eAAe;EACvF,MAAM,cAAc,QAAQ;EAG5B,MAAM,eAAe,mBAAyB;EAC9C,MAAM,WAA2B;GAC/B,KAAK,aAAa;GAClB,QAAQ,aAAa;GACrB,MAAM,cAAc,aAAa,OAAO;GACxC,OAAO,cAAc,aAAa,QAAQ;GAC3C;EAGD,MAAM,cAA4B;GAChC,OAAO;GACP,QAAQ,gBAAgB,KAAK;GAC7B,OAAO,gBAAgB,KAAK;GAC7B;EAGD,MAAM,mBAAmB,0BAA0B,MAAM;EAuBzD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,UAAA;IA5BA,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,KAAK,iBAAiB,SAAS;IAwB/B;GACA,SAAA;IApBA,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAgB7B;GACA,YAbiB,wBAajB;GACA,UAAU;IAAE;IAAO;IAAQ;GAC5B;IACA,CAAC,OAAO,OAAO,CAAC;;;;;;;;;;AAWrB,SAAgB,eACd,QACA,YAAoB,GACpB,iBAAyB,GACzB;CACA,OAAO,cAAc;EACnB,MAAM,wBACJ,OAAO,SAAS,MAChB,OAAO,SAAS,SAChB,YACA,iBACA,OAAO,QAAQ,KAAK;EAEtB,MAAM,0BACJ,OAAO,SAAS,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,KAAK;EAErE,OAAO;GACL,OAAO,OAAO,SAAS,QAAQ;GAC/B,QAAQ,OAAO,SAAS,SAAS;GACjC,GAAG,OAAO,SAAS,OAAO,OAAO,QAAQ;GACzC,GAAG,OAAO,SAAS,MAAM,YAAY,OAAO,QAAQ;GACrD;IACA;EAAC;EAAQ;EAAW;EAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"useResponsiveLayout.js","names":[],"sources":["../../src/hooks/useResponsiveLayout.ts"],"sourcesContent":["/**\n * useResponsiveLayout Hook - Enhanced Responsive Layout System\n * \n * Provides comprehensive responsive layout values for all screen sizes with:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px readability constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth transitions for resize operations (300ms ease-in-out)\n * - Touch-friendly element sizing following iOS Human Interface Guidelines\n * - Safe area insets for notch and home indicator\n * \n * Uses robust device detection combining user-agent and screen size to ensure\n * mobile controls are shown on all mobile devices, including high-resolution phones.\n * \n * Features:\n * - Touch target minimum: 44x44px (iOS guideline)\n * - Safe area insets for notch and home indicator\n * - Responsive font sizes (14px minimum body text)\n * - Optimized spacing for mobile vs desktop\n * - Portrait and landscape mode support\n * - Tablet breakpoint support (768-1024px)\n * \n * @module hooks/useResponsiveLayout\n * @category Mobile UI\n * @korean 반응형레이아웃훅\n */\n\nimport { useMemo } from 'react';\nimport { shouldUseMobileControls, getSafeAreaInsets as getDeviceSafeAreaInsets } from '../utils/deviceDetection';\nimport {\n RESPONSIVE_BREAKPOINTS,\n getScreenSize,\n calculateResponsiveValues,\n createTransitionString,\n} from '../systems/ResponsiveScaling';\n\nimport type { ScreenSize } from '../systems/ResponsiveScaling';\n\n/**\n * Width breakpoint (in CSS pixels) below which devices are treated as\n * extra-small mobile (e.g. iPhone SE). Kept local to this hook because the\n * wider responsive scaling system uses a different bucketing scheme\n * (mobile / tablet / desktop / large / xlarge) that does not expose this\n * sub-mobile threshold.\n */\nconst SMALL_MOBILE_MAX_WIDTH = 375;\n\n/**\n * Breakpoints for responsive design exposed by this hook.\n *\n * @deprecated Prefer `RESPONSIVE_BREAKPOINTS` from `../systems/ResponsiveScaling`\n * for the standard mobile / tablet / desktop tiers. This object is retained as\n * a backward-compatible shim for existing tests and external consumers and\n * additionally exposes the hook-local `MOBILE_SMALL` (iPhone SE) threshold.\n */\nexport const BREAKPOINTS = Object.freeze({\n /** Extra small mobile devices (iPhone SE) */\n MOBILE_SMALL: SMALL_MOBILE_MAX_WIDTH,\n /** Standard mobile devices */\n MOBILE: RESPONSIVE_BREAKPOINTS.MOBILE,\n /** Tablet devices */\n TABLET: RESPONSIVE_BREAKPOINTS.TABLET,\n /** Desktop devices */\n DESKTOP: RESPONSIVE_BREAKPOINTS.LARGE,\n}) as {\n readonly MOBILE_SMALL: number;\n readonly MOBILE: number;\n readonly TABLET: number;\n readonly DESKTOP: number;\n};\n\n/**\n * Touch target sizes following iOS Human Interface Guidelines\n */\nexport interface TouchTargets {\n /** Minimum touch target (44x44px iOS guideline) */\n readonly small: number;\n /** Medium touch target for primary actions */\n readonly medium: number;\n /** Large touch target for critical actions */\n readonly large: number;\n}\n\n/**\n * Safe area insets for mobile devices\n * Accounts for notch, status bar, and home indicator\n */\nexport interface SafeAreaInsets {\n /** Top inset (status bar, notch) */\n readonly top: number;\n /** Bottom inset (home indicator) */\n readonly bottom: number;\n /** Left inset (landscape mode) */\n readonly left: number;\n /** Right inset (landscape mode) */\n readonly right: number;\n}\n\n/**\n * Responsive font sizes\n */\nexport interface FontSizes {\n /** Small text (12-14px) */\n readonly small: number;\n /** Body text (14-16px minimum for mobile) */\n readonly body: number;\n /** Title text (18-24px) */\n readonly title: number;\n /** Hero text (24-36px) */\n readonly hero: number;\n /** HUD text (16-20px for important combat info) */\n readonly hud: number;\n}\n\n/**\n * Responsive spacing scale\n */\nexport interface Spacing {\n /** Extra small (4-8px) */\n readonly xs: number;\n /** Small (8-12px) */\n readonly sm: number;\n /** Medium (12-16px) */\n readonly md: number;\n /** Large (16-24px) */\n readonly lg: number;\n /** Extra large (24-32px) */\n readonly xl: number;\n}\n\n/**\n * Complete responsive layout configuration\n */\nexport interface ResponsiveLayout {\n /** Whether current viewport is mobile-sized */\n readonly isMobile: boolean;\n /** Whether current viewport is small mobile (< 400px) */\n readonly isSmallMobile: boolean;\n /** Whether current viewport is tablet-sized */\n readonly isTablet: boolean;\n /** Whether current viewport is desktop or larger */\n readonly isDesktop: boolean;\n /** Whether in landscape orientation */\n readonly isLandscape: boolean;\n /** Current screen size category (mobile, tablet, desktop, large, xlarge) */\n readonly screenSize: ScreenSize;\n /** Safe area insets for mobile devices */\n readonly safeArea: SafeAreaInsets;\n /** Touch target sizes */\n readonly touchTarget: TouchTargets;\n /** Font sizes */\n readonly fontSize: FontSizes;\n /** Spacing scale */\n readonly spacing: Spacing;\n /** CSS transition string for smooth resizing */\n readonly transition: string;\n /** Current viewport dimensions */\n readonly viewport: {\n readonly width: number;\n readonly height: number;\n };\n}\n\n/**\n * Calculate responsive layout values based on viewport dimensions\n * \n * Enhanced with centralized responsive scaling system:\n * - Five screen size categories (mobile, tablet, desktop, large, xlarge)\n * - Proportional font scaling (0.8x-1.4x) with 14-24px constraints\n * - Proportional spacing scaling (0.5x-1.5x)\n * - Smooth CSS transitions for resize operations\n * \n * Optimized for:\n * - iPhone SE (375x667) - mobile\n * - iPhone 11/12/13 (414x896) - mobile\n * - iPhone 14 Pro Max (430x932) - mobile\n * - iPad (768x1024) - tablet\n * - iPad Pro (1024x1366) - desktop\n * - Standard Desktop (1280x800) - desktop\n * - HD Display (1920x1080) - xlarge\n * - 4K Display (2560x1440) - xlarge\n * \n * @param width - Viewport width in pixels\n * @param height - Viewport height in pixels\n * @returns Complete responsive layout configuration\n * \n * @example\n * ```tsx\n * const layout = useResponsiveLayout(375, 667);\n * \n * <div style={{\n * padding: layout.spacing.md,\n * fontSize: layout.fontSize.body,\n * minHeight: layout.touchTarget.small,\n * transition: layout.transition,\n * }}>\n * Responsive content\n * </div>\n * ```\n * \n * @public\n * @korean 반응형레이아웃사용\n */\nexport function useResponsiveLayout(\n width: number,\n height: number\n): ResponsiveLayout {\n return useMemo(() => {\n // Determine screen size category using centralized scaling system\n const screenSize = getScreenSize(width);\n \n // Device type detection using robust device detection utility\n // Combines user-agent and screen size for reliable mobile detection\n const isMobile = shouldUseMobileControls();\n // Extra-small mobile (e.g. iPhone SE) detection\n const isSmallMobile = width <= SMALL_MOBILE_MAX_WIDTH;\n const isTablet = screenSize === 'tablet';\n const isDesktop = screenSize === 'desktop' || screenSize === 'large' || screenSize === 'xlarge';\n const isLandscape = width > height;\n\n // Safe area insets from device detection utility\n const deviceInsets = getDeviceSafeAreaInsets();\n const safeArea: SafeAreaInsets = {\n top: deviceInsets.top,\n bottom: deviceInsets.bottom,\n left: isLandscape ? deviceInsets.left : 0,\n right: isLandscape ? deviceInsets.right : 0,\n };\n\n // Touch target sizes (iOS Human Interface Guidelines: 44x44px minimum)\n const touchTarget: TouchTargets = {\n small: 44, // Minimum iOS guideline\n medium: isSmallMobile ? 50 : 60,\n large: isSmallMobile ? 60 : 80,\n };\n\n // Calculate responsive values using centralized scaling system\n const responsiveValues = calculateResponsiveValues(width);\n\n // Map to existing font size structure for backward compatibility\n const fontSize: FontSizes = {\n small: responsiveValues.fontSize.small,\n body: responsiveValues.fontSize.body,\n title: responsiveValues.fontSize.title,\n hero: responsiveValues.fontSize.hero,\n hud: responsiveValues.fontSize.hud,\n };\n\n // Map to existing spacing structure for backward compatibility\n const spacing: Spacing = {\n xs: responsiveValues.spacing.xs,\n sm: responsiveValues.spacing.sm,\n md: responsiveValues.spacing.md,\n lg: responsiveValues.spacing.lg,\n xl: responsiveValues.spacing.xl,\n };\n\n // Get transition string for smooth resizing\n const transition = createTransitionString();\n\n return {\n isMobile,\n isSmallMobile,\n isTablet,\n isDesktop,\n isLandscape,\n screenSize,\n safeArea,\n touchTarget,\n fontSize,\n spacing,\n transition,\n viewport: { width, height },\n };\n }, [width, height]);\n}\n\n/**\n * Calculate available content area after safe areas and HUD\n * \n * @param layout - Responsive layout configuration\n * @param hudHeight - Height reserved for HUD elements\n * @param controlsHeight - Height reserved for control elements\n * @returns Available content dimensions\n */\nexport function useContentArea(\n layout: ResponsiveLayout,\n hudHeight: number = 0,\n controlsHeight: number = 0\n) {\n return useMemo(() => {\n const totalVerticalReserved =\n layout.safeArea.top +\n layout.safeArea.bottom +\n hudHeight +\n controlsHeight +\n layout.spacing.md * 2; // Padding\n\n const totalHorizontalReserved =\n layout.safeArea.left + layout.safeArea.right + layout.spacing.md * 2;\n\n return {\n width: layout.viewport.width - totalHorizontalReserved,\n height: layout.viewport.height - totalVerticalReserved,\n x: layout.safeArea.left + layout.spacing.md,\n y: layout.safeArea.top + hudHeight + layout.spacing.md,\n };\n }, [layout, hudHeight, controlsHeight]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAM,yBAAyB;;;;;;;;;AAU/B,IAAa,cAAc,OAAO,OAAO;;CAEvC,cAAc;;CAEd,QAAQ,uBAAuB;;CAE/B,QAAQ,uBAAuB;;CAE/B,SAAS,uBAAuB;AAClC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2ID,SAAgB,oBACd,OACA,QACkB;CAClB,OAAO,cAAc;EAEnB,MAAM,aAAa,cAAc,KAAK;EAItC,MAAM,WAAW,wBAAwB;EAEzC,MAAM,gBAAgB,SAAS;EAC/B,MAAM,WAAW,eAAe;EAChC,MAAM,YAAY,eAAe,aAAa,eAAe,WAAW,eAAe;EACvF,MAAM,cAAc,QAAQ;EAG5B,MAAM,eAAe,kBAAwB;EAC7C,MAAM,WAA2B;GAC/B,KAAK,aAAa;GAClB,QAAQ,aAAa;GACrB,MAAM,cAAc,aAAa,OAAO;GACxC,OAAO,cAAc,aAAa,QAAQ;EAC5C;EAGA,MAAM,cAA4B;GAChC,OAAO;GACP,QAAQ,gBAAgB,KAAK;GAC7B,OAAO,gBAAgB,KAAK;EAC9B;EAGA,MAAM,mBAAmB,0BAA0B,KAAK;EAuBxD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,UAAA;IA5BA,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,OAAO,iBAAiB,SAAS;IACjC,MAAM,iBAAiB,SAAS;IAChC,KAAK,iBAAiB,SAAS;GAwB/B;GACA,SAAA;IApBA,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;IAC7B,IAAI,iBAAiB,QAAQ;GAgB7B;GACA,YAbiB,uBAajB;GACA,UAAU;IAAE;IAAO;GAAO;EAC5B;CACF,GAAG,CAAC,OAAO,MAAM,CAAC;AACpB;;;;;;;;;AAUA,SAAgB,eACd,QACA,YAAoB,GACpB,iBAAyB,GACzB;CACA,OAAO,cAAc;EACnB,MAAM,wBACJ,OAAO,SAAS,MAChB,OAAO,SAAS,SAChB,YACA,iBACA,OAAO,QAAQ,KAAK;EAEtB,MAAM,0BACJ,OAAO,SAAS,OAAO,OAAO,SAAS,QAAQ,OAAO,QAAQ,KAAK;EAErE,OAAO;GACL,OAAO,OAAO,SAAS,QAAQ;GAC/B,QAAQ,OAAO,SAAS,SAAS;GACjC,GAAG,OAAO,SAAS,OAAO,OAAO,QAAQ;GACzC,GAAG,OAAO,SAAS,MAAM,YAAY,OAAO,QAAQ;EACtD;CACF,GAAG;EAAC;EAAQ;EAAW;CAAc,CAAC;AACxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRoundTransition.js","names":[],"sources":["../../src/hooks/useRoundTransition.ts"],"sourcesContent":["/**\n * useRoundTransition Hook - Manages round transition state and timing\n *\n * Korean: 라운드 전환 훅 (Round Transition Hook)\n *\n * Handles the state machine for round transitions:\n * - idle: Normal combat state\n * - announcing: Showing round announcement\n * - countdown: Counting down to next round\n * - transitioning: Brief transition to next round\n *\n * @module hooks/useRoundTransition\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { PlayerState } from \"../systems\";\n\n/**\n * Round transition states\n *\n * Korean: 라운드 전환 상태\n */\nexport type RoundTransitionState =\n | \"idle\"\n | \"announcing\"\n | \"countdown\"\n | \"transitioning\";\n\n/**\n * Round transition configuration\n */\nexport interface RoundTransitionConfig {\n /** Duration of announcement display in seconds */\n readonly announcementDuration?: number;\n /** Duration of countdown in seconds */\n readonly countdownDuration?: number;\n /** Duration of transition phase in milliseconds */\n readonly transitionDuration?: number;\n}\n\n/**\n * Round transition hook state\n */\nexport interface UseRoundTransitionResult {\n /** Current transition state */\n readonly transitionState: RoundTransitionState;\n /** Whether announcement should be visible */\n readonly showAnnouncement: boolean;\n /** Start round transition sequence */\n readonly startTransition: (\n winner: PlayerState | null,\n roundNumber: number\n ) => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset transition state to idle */\n readonly resetTransition: () => void;\n /** Round winner for current transition */\n readonly roundWinner: PlayerState | null;\n /** Round number for current transition */\n readonly currentRoundNumber: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<RoundTransitionConfig> = {\n announcementDuration: 2,\n countdownDuration: 3,\n transitionDuration: 500,\n};\n\n/**\n * useRoundTransition Hook\n *\n * Manages the complete round transition flow:\n * 1. Idle state during normal combat\n * 2. Announcing state shows round results\n * 3. Countdown state counts down to next round\n * 4. Transitioning state briefly transitions to next round\n * 5. Returns to idle for next round\n *\n * @param config - Configuration for transition timings\n * @param onTransitionComplete - Callback when transition completes\n * @returns Round transition state and control functions\n *\n * @example\n * ```typescript\n * const {\n * transitionState,\n * showAnnouncement,\n * startTransition,\n * skipCountdown,\n * } = useRoundTransition(\n * { countdownDuration: 3 },\n * () => {\n * // Start next round\n * initializeNextRound();\n * }\n * );\n *\n * // When round ends\n * startTransition(winner, roundNumber);\n * ```\n */\nexport function useRoundTransition(\n config: RoundTransitionConfig = {},\n onTransitionComplete?: () => void\n): UseRoundTransitionResult {\n const mergedConfig = useMemo(\n () => ({ ...DEFAULT_CONFIG, ...config }),\n [config]\n );\n\n const [transitionState, setTransitionState] =\n useState<RoundTransitionState>(\"idle\");\n const [roundWinner, setRoundWinner] = useState<PlayerState | null>(null);\n const [currentRoundNumber, setCurrentRoundNumber] = useState(0);\n\n // Use ref to track if we should continue countdown\n const countdownActive = useRef(false);\n const countdownTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const transitionTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (transitionTimer.current) {\n clearTimeout(transitionTimer.current);\n transitionTimer.current = null;\n }\n }, []);\n\n /**\n * Start the transition sequence\n */\n const startTransition = useCallback(\n (winner: PlayerState | null, roundNumber: number) => {\n clearTimers();\n setRoundWinner(winner);\n setCurrentRoundNumber(roundNumber);\n setTransitionState(\"announcing\");\n\n // Move to countdown after announcement\n const announcementTimer = setTimeout(() => {\n setTransitionState(\"countdown\");\n countdownActive.current = true;\n\n // Countdown will be managed by the RoundAnnouncement component\n // After countdownDuration, move to transitioning state\n countdownTimer.current = setTimeout(() => {\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }, mergedConfig.countdownDuration * 1000);\n }, mergedConfig.announcementDuration * 1000);\n\n // Store timer ref for cleanup\n transitionTimer.current = announcementTimer;\n },\n [clearTimers, mergedConfig, onTransitionComplete]\n );\n\n /**\n * Skip countdown and proceed immediately to next round\n */\n const skipCountdown = useCallback(() => {\n if (transitionState === \"announcing\" || transitionState === \"countdown\") {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }\n }, [\n transitionState,\n clearTimers,\n mergedConfig.transitionDuration,\n onTransitionComplete,\n ]);\n\n /**\n * Reset transition to idle state\n */\n const resetTransition = useCallback(() => {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"idle\");\n setRoundWinner(null);\n setCurrentRoundNumber(0);\n }, [clearTimers]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n countdownActive.current = false;\n };\n }, [clearTimers]);\n\n return {\n transitionState,\n showAnnouncement:\n transitionState === \"announcing\" || transitionState === \"countdown\",\n startTransition,\n skipCountdown,\n resetTransition,\n roundWinner,\n currentRoundNumber,\n };\n}\n\nexport default useRoundTransition;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmEA,IAAM,iBAAkD;CACtD,sBAAsB;CACtB,mBAAmB;CACnB,oBAAoB;
|
|
1
|
+
{"version":3,"file":"useRoundTransition.js","names":[],"sources":["../../src/hooks/useRoundTransition.ts"],"sourcesContent":["/**\n * useRoundTransition Hook - Manages round transition state and timing\n *\n * Korean: 라운드 전환 훅 (Round Transition Hook)\n *\n * Handles the state machine for round transitions:\n * - idle: Normal combat state\n * - announcing: Showing round announcement\n * - countdown: Counting down to next round\n * - transitioning: Brief transition to next round\n *\n * @module hooks/useRoundTransition\n * @category Combat Hooks\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { PlayerState } from \"../systems\";\n\n/**\n * Round transition states\n *\n * Korean: 라운드 전환 상태\n */\nexport type RoundTransitionState =\n | \"idle\"\n | \"announcing\"\n | \"countdown\"\n | \"transitioning\";\n\n/**\n * Round transition configuration\n */\nexport interface RoundTransitionConfig {\n /** Duration of announcement display in seconds */\n readonly announcementDuration?: number;\n /** Duration of countdown in seconds */\n readonly countdownDuration?: number;\n /** Duration of transition phase in milliseconds */\n readonly transitionDuration?: number;\n}\n\n/**\n * Round transition hook state\n */\nexport interface UseRoundTransitionResult {\n /** Current transition state */\n readonly transitionState: RoundTransitionState;\n /** Whether announcement should be visible */\n readonly showAnnouncement: boolean;\n /** Start round transition sequence */\n readonly startTransition: (\n winner: PlayerState | null,\n roundNumber: number\n ) => void;\n /** Skip countdown and proceed immediately */\n readonly skipCountdown: () => void;\n /** Reset transition state to idle */\n readonly resetTransition: () => void;\n /** Round winner for current transition */\n readonly roundWinner: PlayerState | null;\n /** Round number for current transition */\n readonly currentRoundNumber: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<RoundTransitionConfig> = {\n announcementDuration: 2,\n countdownDuration: 3,\n transitionDuration: 500,\n};\n\n/**\n * useRoundTransition Hook\n *\n * Manages the complete round transition flow:\n * 1. Idle state during normal combat\n * 2. Announcing state shows round results\n * 3. Countdown state counts down to next round\n * 4. Transitioning state briefly transitions to next round\n * 5. Returns to idle for next round\n *\n * @param config - Configuration for transition timings\n * @param onTransitionComplete - Callback when transition completes\n * @returns Round transition state and control functions\n *\n * @example\n * ```typescript\n * const {\n * transitionState,\n * showAnnouncement,\n * startTransition,\n * skipCountdown,\n * } = useRoundTransition(\n * { countdownDuration: 3 },\n * () => {\n * // Start next round\n * initializeNextRound();\n * }\n * );\n *\n * // When round ends\n * startTransition(winner, roundNumber);\n * ```\n */\nexport function useRoundTransition(\n config: RoundTransitionConfig = {},\n onTransitionComplete?: () => void\n): UseRoundTransitionResult {\n const mergedConfig = useMemo(\n () => ({ ...DEFAULT_CONFIG, ...config }),\n [config]\n );\n\n const [transitionState, setTransitionState] =\n useState<RoundTransitionState>(\"idle\");\n const [roundWinner, setRoundWinner] = useState<PlayerState | null>(null);\n const [currentRoundNumber, setCurrentRoundNumber] = useState(0);\n\n // Use ref to track if we should continue countdown\n const countdownActive = useRef(false);\n const countdownTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const transitionTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n /**\n * Clear all active timers\n */\n const clearTimers = useCallback(() => {\n if (countdownTimer.current) {\n clearInterval(countdownTimer.current);\n countdownTimer.current = null;\n }\n if (transitionTimer.current) {\n clearTimeout(transitionTimer.current);\n transitionTimer.current = null;\n }\n }, []);\n\n /**\n * Start the transition sequence\n */\n const startTransition = useCallback(\n (winner: PlayerState | null, roundNumber: number) => {\n clearTimers();\n setRoundWinner(winner);\n setCurrentRoundNumber(roundNumber);\n setTransitionState(\"announcing\");\n\n // Move to countdown after announcement\n const announcementTimer = setTimeout(() => {\n setTransitionState(\"countdown\");\n countdownActive.current = true;\n\n // Countdown will be managed by the RoundAnnouncement component\n // After countdownDuration, move to transitioning state\n countdownTimer.current = setTimeout(() => {\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }, mergedConfig.countdownDuration * 1000);\n }, mergedConfig.announcementDuration * 1000);\n\n // Store timer ref for cleanup\n transitionTimer.current = announcementTimer;\n },\n [clearTimers, mergedConfig, onTransitionComplete]\n );\n\n /**\n * Skip countdown and proceed immediately to next round\n */\n const skipCountdown = useCallback(() => {\n if (transitionState === \"announcing\" || transitionState === \"countdown\") {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"transitioning\");\n\n // Complete transition after brief delay\n transitionTimer.current = setTimeout(() => {\n setTransitionState(\"idle\");\n onTransitionComplete?.();\n }, mergedConfig.transitionDuration);\n }\n }, [\n transitionState,\n clearTimers,\n mergedConfig.transitionDuration,\n onTransitionComplete,\n ]);\n\n /**\n * Reset transition to idle state\n */\n const resetTransition = useCallback(() => {\n clearTimers();\n countdownActive.current = false;\n setTransitionState(\"idle\");\n setRoundWinner(null);\n setCurrentRoundNumber(0);\n }, [clearTimers]);\n\n /**\n * Cleanup on unmount\n */\n useEffect(() => {\n return () => {\n clearTimers();\n countdownActive.current = false;\n };\n }, [clearTimers]);\n\n return {\n transitionState,\n showAnnouncement:\n transitionState === \"announcing\" || transitionState === \"countdown\",\n startTransition,\n skipCountdown,\n resetTransition,\n roundWinner,\n currentRoundNumber,\n };\n}\n\nexport default useRoundTransition;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmEA,IAAM,iBAAkD;CACtD,sBAAsB;CACtB,mBAAmB;CACnB,oBAAoB;AACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,mBACd,SAAgC,CAAC,GACjC,sBAC0B;CAC1B,MAAM,eAAe,eACZ;EAAE,GAAG;EAAgB,GAAG;CAAO,IACtC,CAAC,MAAM,CACT;CAEA,MAAM,CAAC,iBAAiB,sBACtB,SAA+B,MAAM;CACvC,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,CAAC,oBAAoB,yBAAyB,SAAS,CAAC;CAG9D,MAAM,kBAAkB,OAAO,KAAK;CACpC,MAAM,iBAAiB,OAA6C,IAAI;CACxE,MAAM,kBAAkB,OAA6C,IAAI;;;;CAKzE,MAAM,cAAc,kBAAkB;EACpC,IAAI,eAAe,SAAS;GAC1B,cAAc,eAAe,OAAO;GACpC,eAAe,UAAU;EAC3B;EACA,IAAI,gBAAgB,SAAS;GAC3B,aAAa,gBAAgB,OAAO;GACpC,gBAAgB,UAAU;EAC5B;CACF,GAAG,CAAC,CAAC;;;;CAKL,MAAM,kBAAkB,aACrB,QAA4B,gBAAwB;EACnD,YAAY;EACZ,eAAe,MAAM;EACrB,sBAAsB,WAAW;EACjC,mBAAmB,YAAY;EAsB/B,gBAAgB,UAnBU,iBAAiB;GACzC,mBAAmB,WAAW;GAC9B,gBAAgB,UAAU;GAI1B,eAAe,UAAU,iBAAiB;IACxC,gBAAgB,UAAU;IAC1B,mBAAmB,eAAe;IAGlC,gBAAgB,UAAU,iBAAiB;KACzC,mBAAmB,MAAM;KACzB,uBAAuB;IACzB,GAAG,aAAa,kBAAkB;GACpC,GAAG,aAAa,oBAAoB,GAAI;EAC1C,GAAG,aAAa,uBAAuB,GAGb;CAC5B,GACA;EAAC;EAAa;EAAc;CAAoB,CAClD;;;;CAKA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,oBAAoB,gBAAgB,oBAAoB,aAAa;GACvE,YAAY;GACZ,gBAAgB,UAAU;GAC1B,mBAAmB,eAAe;GAGlC,gBAAgB,UAAU,iBAAiB;IACzC,mBAAmB,MAAM;IACzB,uBAAuB;GACzB,GAAG,aAAa,kBAAkB;EACpC;CACF,GAAG;EACD;EACA;EACA,aAAa;EACb;CACF,CAAC;;;;CAKD,MAAM,kBAAkB,kBAAkB;EACxC,YAAY;EACZ,gBAAgB,UAAU;EAC1B,mBAAmB,MAAM;EACzB,eAAe,IAAI;EACnB,sBAAsB,CAAC;CACzB,GAAG,CAAC,WAAW,CAAC;;;;CAKhB,gBAAgB;EACd,aAAa;GACX,YAAY;GACZ,gBAAgB,UAAU;EAC5B;CACF,GAAG,CAAC,WAAW,CAAC;CAEhB,OAAO;EACL;EACA,kBACE,oBAAoB,gBAAgB,oBAAoB;EAC1D;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSkeletalAnimation.d.ts","sourceRoot":"","sources":["../../src/hooks/useSkeletalAnimation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAEV,sBAAsB,EACtB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,eAAe,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,wCAAwC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrD,2DAA2D;IAC3D,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,qDAAqD;IACrD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,
|
|
1
|
+
{"version":3,"file":"useSkeletalAnimation.d.ts","sourceRoot":"","sources":["../../src/hooks/useSkeletalAnimation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAEV,sBAAsB,EACtB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,6BAA6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,eAAe,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,wCAAwC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrD,2DAA2D;IAC3D,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,qDAAqD;IACrD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,CAqN5B"}
|
|
@@ -97,7 +97,7 @@ function useSkeletalAnimation(options) {
|
|
|
97
97
|
selectedAnim = getAnimation(currentAnimation) ?? null;
|
|
98
98
|
playbackSpeed = 1;
|
|
99
99
|
} else if (currentAnimation === "stance_change") {
|
|
100
|
-
selectedAnim = getAnimation("idle_stance") ?? null;
|
|
100
|
+
selectedAnim = getAnimation("stance_change") ?? getAnimation("idle_stance") ?? null;
|
|
101
101
|
playbackSpeed = 1.2;
|
|
102
102
|
} else if (currentAnimation === "hit") {
|
|
103
103
|
setAnimState((prev) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim =\n getAnimation(\"stance_change\") ?? getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,IAAM,2BAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BD,SAAgB,qBACd,SAC4B;CAC5B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,QACA,aAAa,SACb,wBACE;CAGJ,MAAM,CAAC,WAAW,gBAAgB,SAAiC;EACjE,kBAAkB;EAClB,aAAa;EACb,WAAW;EACX,eAAe;EACf,uBAAuB;EACvB,mBAAmB;CACrB,CAAC;CAGD,MAAM,cAAc,OAAO,CAAC;CAG5B,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,IACF;CAGA,gBAAgB;EAEd,YAAY,UAAU;EAEtB,IAAI,eAAyC;EAC7C,IAAI;EACJ,IAAI,8BAA8B;EAElC,IAAI,qBAAqB,YAAY,iBAAiB;GAEpD,eACE,mBAAmB,eAAe,KAClC,aAAa,eAAe,KAC5B;GACF,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,YAAY,YAAY;GAGtD,IAAI,iBACF,eAAe,sBAAsB,eAAe,KAAK;GAG3D,iBAAiB,aAAa,OAAO,KAAK;GAC1C,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,QAAQ;GAGtC,IAAI,QAEF,eAAe,mBAAmB,UADD,QACe,KAAK;GAGvD,iBAAiB,aAAa,MAAM,KAAK;GACzC,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,QAAQ;GAEtC,IAAI,QAEF,eAAe,mBAAmB,QADH,QACiB,KAAK;GAGvD,iBAAiB,aAAa,MAAM,KAAK;GACzC,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,OAAO;GAErC,IAAI,QAEF,eAAe,mBAAmB,OADL,QACkB,KAAK;GAGtD,iBAAiB,aAAa,KAAK,KAAK;GACxC,gBAAgB;EAClB,OAAO,IAAI,kBAAkB,WAAW,OAAO,GAAG;GAEhD,eAAe,aAAa,gBAAgB,KAAK;GACjD,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,iBAAiB;GAE/C,eACE,aAAa,eAAe,KAAK,aAAa,aAAa,KAAK;GAClE,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,OAAO;GAErC,cAAc,UAAU;IACtB,GAAG;IACH,WAAW;IACX,aAAa;GACf,EAAE;GACF;EACF,OAAO,IAAI,kBAAkB,WAAW,OAAO,GAAG;GAEhD,eAAe,iBAAiB,gBAAgB,KAAK;GACrD,gBAAgB;GAGhB,IAAI,yBAAyB,IAAI,gBAAgB,GAC/C,8BAA8B;EAIlC,OAAO,IAAI,kBAAkB,WAAW,WAAW,GAAG;GAEpD,eAAe,qBAAqB,gBAAgB,KAAK;GACzD,gBAAgB;EAClB,OAAO,IAAI,kBAAkB,WAAW,SAAS,GAAG;GAGlD,eAAe,mBAAmB,gBAAgB,KAAK;GACvD,gBAAgB;EAClB,OAAO;GAEL,eAAe,aAAa,aAAa,KAAK;GAC9C,gBAAgB;EAClB;EAMA,IAAI,cACF,eAAe,gBAAgB,cAAc,UAAU;EAIzD,IAAI,6BACF,qBAAqB,IAAI;EAG3B,aAAa;GACX,kBAAkB;GAClB,aAAa;GACb,WAAW;GACX;GACA,uBAAuB;GACvB,mBAAmB;EACrB,CAAC;CACH,GAAG;EAAC;EAAkB;EAAiB;EAAY;EAAQ;CAAU,CAAC;CA8DtE,OAAO;EACL;EACA;EACA,oBA7DyB,aACxB,WAAwB,UAAkB;GACzC,IAAI,UAAU,aAAa,UAAU,kBAAkB;IACrD,MAAM,iBAAiB,YAAY,IAAI;IAGvC,IAAI,UAAU,YAAY,UAAU,QAAQ,UAAU;IACtD,IAAI,YAAY;IAGhB,IAAI,WAAW,UAAU,iBAAiB,UACxC,IAAI,UAAU,iBAAiB,MAC7B,UAAU,UAAU,UAAU,iBAAiB;SAC1C;KACL,UAAU,UAAU,iBAAiB;KACrC,YAAY;IACd;IAKF,MAAM,WAAW,0BACf,UAAU,iBAAiB,MAC3B,UAAU,kBACV,OACF;IAEA,IAAI,UAEF,iBAAiB,WAAW,QAAQ;IAItC,YAAY,UAAU;IAGtB,MAAM,YAAY,YAAY,IAAI,IAAI;IACtC,mBAAmB,YAAY,SAAS;IAGxC,IAAI,WAAW;KACb,YAAY,UAAU;KACtB,cAAc,UAAU;MACtB,GAAG;MACH,WAAW;MACX,aAAa;KACf,EAAE;KAGF,IAAI,qBACF,oBAAoB;IAExB;GACF;EACF,GACA,CAAC,WAAW,mBAAmB,CAM/B;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTechniqueSelection.js","names":[],"sources":["../../src/hooks/useTechniqueSelection.ts"],"sourcesContent":["/**\n * Custom hook for managing technique selection and execution.\n *\n * **Korean**: 기술 선택 관리 (Technique Selection Management)\n *\n * Handles technique selection state, keyboard shortcuts, cooldown tracking,\n * and validation of technique execution based on player resources and stance.\n *\n * @module hooks/useTechniqueSelection\n * @category Combat Hooks\n * @korean 기술선택훅\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getTechniquesForStanceAndArchetype } from \"../data/techniques\";\nimport { PlayerState } from \"../systems/player\";\nimport {\n Technique,\n TechniqueCooldown,\n TechniqueValidation,\n} from \"../types\";\n\n/**\n * Configuration for technique selection hook.\n */\nexport interface UseTechniqueSelectionConfig {\n /** Player state with resources and stance */\n readonly player: PlayerState;\n\n /** Whether technique selection is enabled */\n readonly enabled?: boolean;\n\n /** Callback when technique is selected */\n readonly onTechniqueSelected?: (technique: Technique) => void;\n\n /** Callback when technique execution is attempted */\n readonly onTechniqueExecute?: (technique: Technique) => void;\n}\n\n/**\n * Technique selection state and actions.\n */\nexport interface UseTechniqueSelectionResult {\n /** Available techniques for player archetype */\n readonly availableTechniques: readonly Technique[];\n\n /** Currently selected technique index */\n readonly selectedIndex: number;\n\n /** Active cooldowns for techniques */\n readonly activeCooldowns: readonly TechniqueCooldown[];\n\n /** Select technique by index */\n readonly selectTechnique: (index: number) => void;\n\n /** Execute currently selected technique */\n readonly executeTechnique: (indexOverride?: number) => void;\n\n /** Check if technique can be executed */\n readonly validateTechnique: (technique: Technique) => TechniqueValidation;\n\n /** Check if technique is on cooldown */\n readonly isOnCooldown: (techniqueId: string) => boolean;\n\n /** Get remaining cooldown time in ms */\n readonly getRemainingCooldown: (techniqueId: string) => number;\n\n /** Check if player has sufficient resources */\n readonly hasResources: (technique: Technique) => boolean;\n}\n\n/**\n * Custom hook for managing technique selection and execution.\n *\n * @param config - Configuration options\n * @returns Technique selection state and actions\n *\n * @example\n * ```typescript\n * const techniqueSelection = useTechniqueSelection({\n * player: playerState,\n * enabled: !isPaused && combatActive,\n * onTechniqueExecute: (technique) => {\n * // Execute technique logic\n * executeCombatTechnique(playerState, opponent, technique);\n * }\n * });\n * ```\n *\n * @public\n */\nexport function useTechniqueSelection(\n config: UseTechniqueSelectionConfig\n): UseTechniqueSelectionResult {\n const {\n player,\n enabled = true,\n onTechniqueSelected,\n onTechniqueExecute,\n } = config;\n\n // Get available techniques based on player's current stance and archetype\n const availableTechniques = useMemo(\n () =>\n getTechniquesForStanceAndArchetype(\n player.currentStance,\n player.archetype\n ),\n [player.currentStance, player.archetype]\n );\n\n // Selected technique state\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Cooldown tracking\n const [activeCooldowns, setActiveCooldowns] = useState<TechniqueCooldown[]>(\n []\n );\n\n // Ref for cleanup\n const cooldownUpdateIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Update cooldowns every 100ms\n useEffect(() => {\n if (activeCooldowns.length === 0) {\n // Clear any existing interval when no cooldowns\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n return;\n }\n\n let isMounted = true;\n cooldownUpdateIntervalRef.current = setInterval(() => {\n if (!isMounted) return;\n const now = Date.now();\n setActiveCooldowns((prev) => {\n return prev\n .map((cd) => ({\n ...cd,\n remaining: Math.max(0, cd.startTime + cd.duration - now),\n }))\n .filter((cd) => cd.remaining > 0);\n });\n }, 100);\n\n return () => {\n isMounted = false;\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n };\n }, [activeCooldowns.length]);\n\n // Check if technique is on cooldown\n const isOnCooldown = useCallback(\n (techniqueId: string): boolean => {\n return activeCooldowns.some(\n (cd) => cd.techniqueId === techniqueId && cd.remaining > 0\n );\n },\n [activeCooldowns]\n );\n\n // Get remaining cooldown time\n const getRemainingCooldown = useCallback(\n (techniqueId: string): number => {\n const cooldown = activeCooldowns.find(\n (cd) => cd.techniqueId === techniqueId\n );\n return cooldown?.remaining ?? 0;\n },\n [activeCooldowns]\n );\n\n // Check if player has sufficient resources\n const hasResources = useCallback(\n (technique: Technique): boolean => {\n return (\n player.stamina >= technique.staminaCost && player.ki >= technique.kiCost\n );\n },\n [player.stamina, player.ki]\n );\n\n // Validate technique execution\n const validateTechnique = useCallback(\n (technique: Technique): TechniqueValidation => {\n // Check stamina\n if (player.stamina < technique.staminaCost) {\n return {\n canExecute: false,\n reason: \"Insufficient stamina\",\n insufficientStamina: true,\n };\n }\n\n // Check Ki\n if (player.ki < technique.kiCost) {\n return {\n canExecute: false,\n reason: \"Insufficient Ki\",\n insufficientKi: true,\n };\n }\n\n // Check cooldown\n if (isOnCooldown(technique.id)) {\n return {\n canExecute: false,\n reason: \"Technique on cooldown\",\n onCooldown: true,\n };\n }\n\n // Check required stance\n if (\n technique.requiredStance &&\n player.currentStance !== technique.requiredStance\n ) {\n return {\n canExecute: false,\n reason: `Requires ${technique.requiredStance} stance`,\n wrongStance: true,\n };\n }\n\n return {\n canExecute: true,\n };\n },\n [player.stamina, player.ki, player.currentStance, isOnCooldown]\n );\n\n // Select technique by index\n const selectTechnique = useCallback(\n (index: number) => {\n if (index < 0 || index >= availableTechniques.length) return;\n if (!enabled) return;\n\n setSelectedIndex(index);\n const technique = availableTechniques[index];\n onTechniqueSelected?.(technique);\n },\n [availableTechniques, enabled, onTechniqueSelected]\n );\n\n // Execute currently selected technique\n const executeTechnique = useCallback(\n (indexOverride?: number) => {\n if (!enabled) return;\n\n const index = indexOverride ?? selectedIndex;\n const technique = availableTechniques[index];\n if (!technique) return;\n\n // Validate technique execution\n const validation = validateTechnique(technique);\n if (!validation.canExecute) {\n console.warn(`Cannot execute technique: ${validation.reason}`);\n return;\n }\n\n // Start cooldown\n const now = Date.now();\n const cooldown: TechniqueCooldown = {\n techniqueId: technique.id,\n startTime: now,\n duration: technique.cooldown,\n remaining: technique.cooldown,\n };\n setActiveCooldowns((prev) => [...prev, cooldown]);\n\n // Execute technique\n onTechniqueExecute?.(technique);\n },\n [\n enabled,\n availableTechniques,\n selectedIndex,\n validateTechnique,\n onTechniqueExecute,\n ]\n );\n\n // Keyboard shortcuts for technique selection (Q-E-R-T-Y-F-G-Z-X-C)\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyPress = (e: KeyboardEvent) => {\n // Ignore keypresses when typing in input fields\n const target = e.target as HTMLElement;\n if (\n target.tagName === \"INPUT\" ||\n target.tagName === \"TEXTAREA\" ||\n target.isContentEditable\n ) {\n return;\n }\n\n const key = e.key.toUpperCase();\n \n // Technique keys: Q, E, R, T, Y, F, G, Z, X, C (10 keys around WASD)\n const techniqueKeys = [\"Q\", \"E\", \"R\", \"T\", \"Y\", \"F\", \"G\", \"Z\", \"X\", \"C\"];\n\n // Prevent default for all technique keys during combat\n if (techniqueKeys.includes(key)) {\n e.preventDefault();\n }\n\n // Map keys to technique indices\n const techniqueIndex = techniqueKeys.indexOf(key);\n if (techniqueIndex !== -1 && techniqueIndex < availableTechniques.length) {\n selectTechnique(techniqueIndex);\n executeTechnique(techniqueIndex);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyPress);\n return () => window.removeEventListener(\"keydown\", handleKeyPress);\n }, [enabled, availableTechniques, selectTechnique, executeTechnique]);\n\n return {\n availableTechniques,\n selectedIndex,\n activeCooldowns,\n selectTechnique,\n executeTechnique,\n validateTechnique,\n isOnCooldown,\n getRemainingCooldown,\n hasResources,\n };\n}\n\nexport default useTechniqueSelection;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useTechniqueSelection.js","names":[],"sources":["../../src/hooks/useTechniqueSelection.ts"],"sourcesContent":["/**\n * Custom hook for managing technique selection and execution.\n *\n * **Korean**: 기술 선택 관리 (Technique Selection Management)\n *\n * Handles technique selection state, keyboard shortcuts, cooldown tracking,\n * and validation of technique execution based on player resources and stance.\n *\n * @module hooks/useTechniqueSelection\n * @category Combat Hooks\n * @korean 기술선택훅\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { getTechniquesForStanceAndArchetype } from \"../data/techniques\";\nimport { PlayerState } from \"../systems/player\";\nimport {\n Technique,\n TechniqueCooldown,\n TechniqueValidation,\n} from \"../types\";\n\n/**\n * Configuration for technique selection hook.\n */\nexport interface UseTechniqueSelectionConfig {\n /** Player state with resources and stance */\n readonly player: PlayerState;\n\n /** Whether technique selection is enabled */\n readonly enabled?: boolean;\n\n /** Callback when technique is selected */\n readonly onTechniqueSelected?: (technique: Technique) => void;\n\n /** Callback when technique execution is attempted */\n readonly onTechniqueExecute?: (technique: Technique) => void;\n}\n\n/**\n * Technique selection state and actions.\n */\nexport interface UseTechniqueSelectionResult {\n /** Available techniques for player archetype */\n readonly availableTechniques: readonly Technique[];\n\n /** Currently selected technique index */\n readonly selectedIndex: number;\n\n /** Active cooldowns for techniques */\n readonly activeCooldowns: readonly TechniqueCooldown[];\n\n /** Select technique by index */\n readonly selectTechnique: (index: number) => void;\n\n /** Execute currently selected technique */\n readonly executeTechnique: (indexOverride?: number) => void;\n\n /** Check if technique can be executed */\n readonly validateTechnique: (technique: Technique) => TechniqueValidation;\n\n /** Check if technique is on cooldown */\n readonly isOnCooldown: (techniqueId: string) => boolean;\n\n /** Get remaining cooldown time in ms */\n readonly getRemainingCooldown: (techniqueId: string) => number;\n\n /** Check if player has sufficient resources */\n readonly hasResources: (technique: Technique) => boolean;\n}\n\n/**\n * Custom hook for managing technique selection and execution.\n *\n * @param config - Configuration options\n * @returns Technique selection state and actions\n *\n * @example\n * ```typescript\n * const techniqueSelection = useTechniqueSelection({\n * player: playerState,\n * enabled: !isPaused && combatActive,\n * onTechniqueExecute: (technique) => {\n * // Execute technique logic\n * executeCombatTechnique(playerState, opponent, technique);\n * }\n * });\n * ```\n *\n * @public\n */\nexport function useTechniqueSelection(\n config: UseTechniqueSelectionConfig\n): UseTechniqueSelectionResult {\n const {\n player,\n enabled = true,\n onTechniqueSelected,\n onTechniqueExecute,\n } = config;\n\n // Get available techniques based on player's current stance and archetype\n const availableTechniques = useMemo(\n () =>\n getTechniquesForStanceAndArchetype(\n player.currentStance,\n player.archetype\n ),\n [player.currentStance, player.archetype]\n );\n\n // Selected technique state\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Cooldown tracking\n const [activeCooldowns, setActiveCooldowns] = useState<TechniqueCooldown[]>(\n []\n );\n\n // Ref for cleanup\n const cooldownUpdateIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Update cooldowns every 100ms\n useEffect(() => {\n if (activeCooldowns.length === 0) {\n // Clear any existing interval when no cooldowns\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n return;\n }\n\n let isMounted = true;\n cooldownUpdateIntervalRef.current = setInterval(() => {\n if (!isMounted) return;\n const now = Date.now();\n setActiveCooldowns((prev) => {\n return prev\n .map((cd) => ({\n ...cd,\n remaining: Math.max(0, cd.startTime + cd.duration - now),\n }))\n .filter((cd) => cd.remaining > 0);\n });\n }, 100);\n\n return () => {\n isMounted = false;\n if (cooldownUpdateIntervalRef.current) {\n clearInterval(cooldownUpdateIntervalRef.current);\n cooldownUpdateIntervalRef.current = null;\n }\n };\n }, [activeCooldowns.length]);\n\n // Check if technique is on cooldown\n const isOnCooldown = useCallback(\n (techniqueId: string): boolean => {\n return activeCooldowns.some(\n (cd) => cd.techniqueId === techniqueId && cd.remaining > 0\n );\n },\n [activeCooldowns]\n );\n\n // Get remaining cooldown time\n const getRemainingCooldown = useCallback(\n (techniqueId: string): number => {\n const cooldown = activeCooldowns.find(\n (cd) => cd.techniqueId === techniqueId\n );\n return cooldown?.remaining ?? 0;\n },\n [activeCooldowns]\n );\n\n // Check if player has sufficient resources\n const hasResources = useCallback(\n (technique: Technique): boolean => {\n return (\n player.stamina >= technique.staminaCost && player.ki >= technique.kiCost\n );\n },\n [player.stamina, player.ki]\n );\n\n // Validate technique execution\n const validateTechnique = useCallback(\n (technique: Technique): TechniqueValidation => {\n // Check stamina\n if (player.stamina < technique.staminaCost) {\n return {\n canExecute: false,\n reason: \"Insufficient stamina\",\n insufficientStamina: true,\n };\n }\n\n // Check Ki\n if (player.ki < technique.kiCost) {\n return {\n canExecute: false,\n reason: \"Insufficient Ki\",\n insufficientKi: true,\n };\n }\n\n // Check cooldown\n if (isOnCooldown(technique.id)) {\n return {\n canExecute: false,\n reason: \"Technique on cooldown\",\n onCooldown: true,\n };\n }\n\n // Check required stance\n if (\n technique.requiredStance &&\n player.currentStance !== technique.requiredStance\n ) {\n return {\n canExecute: false,\n reason: `Requires ${technique.requiredStance} stance`,\n wrongStance: true,\n };\n }\n\n return {\n canExecute: true,\n };\n },\n [player.stamina, player.ki, player.currentStance, isOnCooldown]\n );\n\n // Select technique by index\n const selectTechnique = useCallback(\n (index: number) => {\n if (index < 0 || index >= availableTechniques.length) return;\n if (!enabled) return;\n\n setSelectedIndex(index);\n const technique = availableTechniques[index];\n onTechniqueSelected?.(technique);\n },\n [availableTechniques, enabled, onTechniqueSelected]\n );\n\n // Execute currently selected technique\n const executeTechnique = useCallback(\n (indexOverride?: number) => {\n if (!enabled) return;\n\n const index = indexOverride ?? selectedIndex;\n const technique = availableTechniques[index];\n if (!technique) return;\n\n // Validate technique execution\n const validation = validateTechnique(technique);\n if (!validation.canExecute) {\n console.warn(`Cannot execute technique: ${validation.reason}`);\n return;\n }\n\n // Start cooldown\n const now = Date.now();\n const cooldown: TechniqueCooldown = {\n techniqueId: technique.id,\n startTime: now,\n duration: technique.cooldown,\n remaining: technique.cooldown,\n };\n setActiveCooldowns((prev) => [...prev, cooldown]);\n\n // Execute technique\n onTechniqueExecute?.(technique);\n },\n [\n enabled,\n availableTechniques,\n selectedIndex,\n validateTechnique,\n onTechniqueExecute,\n ]\n );\n\n // Keyboard shortcuts for technique selection (Q-E-R-T-Y-F-G-Z-X-C)\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyPress = (e: KeyboardEvent) => {\n // Ignore keypresses when typing in input fields\n const target = e.target as HTMLElement;\n if (\n target.tagName === \"INPUT\" ||\n target.tagName === \"TEXTAREA\" ||\n target.isContentEditable\n ) {\n return;\n }\n\n const key = e.key.toUpperCase();\n \n // Technique keys: Q, E, R, T, Y, F, G, Z, X, C (10 keys around WASD)\n const techniqueKeys = [\"Q\", \"E\", \"R\", \"T\", \"Y\", \"F\", \"G\", \"Z\", \"X\", \"C\"];\n\n // Prevent default for all technique keys during combat\n if (techniqueKeys.includes(key)) {\n e.preventDefault();\n }\n\n // Map keys to technique indices\n const techniqueIndex = techniqueKeys.indexOf(key);\n if (techniqueIndex !== -1 && techniqueIndex < availableTechniques.length) {\n selectTechnique(techniqueIndex);\n executeTechnique(techniqueIndex);\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyPress);\n return () => window.removeEventListener(\"keydown\", handleKeyPress);\n }, [enabled, availableTechniques, selectTechnique, executeTechnique]);\n\n return {\n availableTechniques,\n selectedIndex,\n activeCooldowns,\n selectTechnique,\n executeTechnique,\n validateTechnique,\n isOnCooldown,\n getRemainingCooldown,\n hasResources,\n };\n}\n\nexport default useTechniqueSelection;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useThrottle.js","names":[],"sources":["../../src/hooks/useThrottle.ts"],"sourcesContent":["/**\n * useThrottle Hook\n * \n * Throttles a function to execute at most once per specified interval.\n * Useful for high-frequency events like scroll, resize, or touch move.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the throttled function on every render.\n * \n * @module hooks/useThrottle\n * @category Performance\n * @korean 쓰로틀 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to throttle a callback function\n * \n * @param callback - Function to throttle\n * @param delay - Minimum delay between executions in milliseconds\n * @returns Throttled function\n * \n * @example\n * ```tsx\n * const handleTouchMove = useThrottle((event: TouchEvent) => {\n * // Handle touch move\n * }, 16); // ~60fps\n * ```\n */\nexport function useThrottle<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const lastRunRef = useRef<number>(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n // Cleanup pending timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n const now = Date.now();\n const timeSinceLastRun = now - lastRunRef.current;\n\n if (timeSinceLastRun >= delay) {\n // Execute immediately if enough time has passed\n lastRunRef.current = now;\n callbackRef.current(...args);\n } else if (!timeoutRef.current) {\n // Schedule execution for later\n const timeUntilNext = delay - timeSinceLastRun;\n timeoutRef.current = setTimeout(() => {\n lastRunRef.current = Date.now();\n timeoutRef.current = null;\n callbackRef.current(...args);\n }, timeUntilNext);\n }\n },\n [delay]\n ) as T;\n}\n\nexport default useThrottle;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACG;CACH,MAAM,aAAa,OAAe,
|
|
1
|
+
{"version":3,"file":"useThrottle.js","names":[],"sources":["../../src/hooks/useThrottle.ts"],"sourcesContent":["/**\n * useThrottle Hook\n * \n * Throttles a function to execute at most once per specified interval.\n * Useful for high-frequency events like scroll, resize, or touch move.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the throttled function on every render.\n * \n * @module hooks/useThrottle\n * @category Performance\n * @korean 쓰로틀 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to throttle a callback function\n * \n * @param callback - Function to throttle\n * @param delay - Minimum delay between executions in milliseconds\n * @returns Throttled function\n * \n * @example\n * ```tsx\n * const handleTouchMove = useThrottle((event: TouchEvent) => {\n * // Handle touch move\n * }, 16); // ~60fps\n * ```\n */\nexport function useThrottle<T extends (...args: never[]) => void>(\n callback: T,\n delay: number\n): T {\n const lastRunRef = useRef<number>(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n\n // Cleanup pending timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n const now = Date.now();\n const timeSinceLastRun = now - lastRunRef.current;\n\n if (timeSinceLastRun >= delay) {\n // Execute immediately if enough time has passed\n lastRunRef.current = now;\n callbackRef.current(...args);\n } else if (!timeoutRef.current) {\n // Schedule execution for later\n const timeUntilNext = delay - timeSinceLastRun;\n timeoutRef.current = setTimeout(() => {\n lastRunRef.current = Date.now();\n timeoutRef.current = null;\n callbackRef.current(...args);\n }, timeUntilNext);\n }\n },\n [delay]\n ) as T;\n}\n\nexport default useThrottle;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACG;CACH,MAAM,aAAa,OAAe,CAAC;CACnC,MAAM,aAAa,OAA6C,IAAI;CACpE,MAAM,cAAc,OAAO,QAAQ;CAGnC,sBAAsB;EACpB,YAAY,UAAU;CACxB,CAAC;CAGD,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG,CAAC,CAAC;CAEL,OAAO,aACJ,GAAG,SAAwB;EAC1B,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,mBAAmB,MAAM,WAAW;EAE1C,IAAI,oBAAoB,OAAO;GAE7B,WAAW,UAAU;GACrB,YAAY,QAAQ,GAAG,IAAI;EAC7B,OAAO,IAAI,CAAC,WAAW,SAAS;GAE9B,MAAM,gBAAgB,QAAQ;GAC9B,WAAW,UAAU,iBAAiB;IACpC,WAAW,UAAU,KAAK,IAAI;IAC9B,WAAW,UAAU;IACrB,YAAY,QAAQ,GAAG,IAAI;GAC7B,GAAG,aAAa;EAClB;CACF,GACA,CAAC,KAAK,CACR;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTouchControls.js","names":[],"sources":["../../src/hooks/useTouchControls.ts"],"sourcesContent":["/**\n * Touch Controls Hook\n * \n * Manages touch event handling and gesture recognition for mobile gameplay\n * Provides swipe detection, multi-touch support, and touch-based movement\n * \n * @module hooks/useTouchControls\n * @category Mobile Controls\n * @korean 터치 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Gesture types supported by the touch control system\n * \n * Added tactical step gestures for Korean martial arts footwork:\n * - tap-{direction}: Quick tap for tactical 30cm step\n * - hold-{direction}: Hold for continuous walk\n * \n * @korean 제스처타입\n */\nexport type GestureType =\n | 'swipe-right'\n | 'swipe-left'\n | 'swipe-up'\n | 'swipe-down'\n | 'two-finger-tap'\n | 'tap'\n | 'tap-forward'\n | 'tap-back'\n | 'tap-left'\n | 'tap-right'\n | 'tap-forward-left'\n | 'tap-forward-right'\n | 'tap-back-left'\n | 'tap-back-right'\n | 'hold-forward'\n | 'hold-back'\n | 'hold-left'\n | 'hold-right';\n\n/**\n * Gesture event data\n */\nexport interface GestureEvent {\n /** Type of gesture detected */\n readonly type: GestureType;\n /** Distance of swipe in pixels (for swipe gestures) */\n readonly distance?: number;\n /** Coordinates of touch start */\n readonly startX?: number;\n readonly startY?: number;\n /** Coordinates of touch end */\n readonly endX?: number;\n readonly endY?: number;\n}\n\n/**\n * Props for useTouchControls hook\n */\nexport interface UseTouchControlsProps {\n /** Callback when gesture is detected */\n readonly onGesture: (gesture: GestureEvent) => void;\n /** Whether touch input is enabled */\n readonly enabled?: boolean;\n /** Minimum swipe distance in pixels (default: 50) */\n readonly minSwipeDistance?: number;\n /** Maximum time for tap in ms (default: 300) */\n readonly maxTapDuration?: number;\n /** Time threshold for hold vs tap in ms (default: 200) */\n readonly holdThreshold?: number;\n /** Enable haptic feedback for steps (default: true) */\n readonly enableHaptics?: boolean;\n}\n\n/**\n * Return type for useTouchControls hook\n */\nexport interface UseTouchControlsReturn {\n /** Whether a touch is currently active */\n readonly isTouching: boolean;\n}\n\n/**\n * Custom hook for handling touch controls and gesture recognition\n * \n * Features:\n * - Swipe detection (horizontal and vertical)\n * - Two-finger tap detection for vital point mode\n * - Single tap detection\n * - Tactical step gestures (tap) vs continuous walk (hold)\n * - Distance calculation for swipe intensity\n * - Configurable thresholds\n * - Haptic feedback for tactical steps\n * \n * Gesture Mapping:\n * - Swipe Right: Advance toward opponent\n * - Swipe Left: Retreat from opponent\n * - Swipe Up: High stance mode\n * - Swipe Down: Low stance mode\n * - Two-Finger Tap: Activate vital point targeting mode\n * - Single Tap (directional): Tactical 30cm step (전술적 발걸음)\n * - Hold (directional): Continuous walk movement\n * \n * @example\n * ```typescript\n * const { isTouching } = useTouchControls({\n * onGesture: (gesture) => {\n * switch (gesture.type) {\n * case 'tap-forward':\n * handleTacticalStep('forward'); // 전진보법\n * break;\n * case 'hold-forward':\n * handleContinuousWalk('forward');\n * break;\n * case 'two-finger-tap':\n * activateVitalPointMode();\n * break;\n * }\n * },\n * enabled: !isPaused,\n * holdThreshold: 200, // 200ms to distinguish tap from hold\n * enableHaptics: true,\n * });\n * ```\n * \n * @public\n * @korean 터치컨트롤사용\n */\nexport function useTouchControls({\n onGesture,\n enabled = true,\n minSwipeDistance = 50,\n maxTapDuration = 300,\n holdThreshold = 200,\n enableHaptics = true,\n}: UseTouchControlsProps): UseTouchControlsReturn {\n const touchStartRef = useRef<Touch | null>(null);\n const touchStartTimeRef = useRef<number>(0);\n const [isTouching, setIsTouching] = useState<boolean>(false);\n const holdTimerRef = useRef<number | null>(null);\n \n /**\n * Trigger haptic feedback for tactical step\n * Light vibration (10ms) to confirm step input\n * \n * @korean 햅틱피드백\n */\n const triggerStepHaptic = useCallback(() => {\n if (!enableHaptics || !navigator.vibrate) return;\n \n try {\n // Short, light vibration for step (10ms)\n navigator.vibrate(10);\n } catch (error) {\n // Haptic feedback not supported or failed\n console.debug('Haptic feedback not available:', error);\n }\n }, [enableHaptics]);\n \n /**\n * Determine directional gesture from touch position\n * Used for D-pad style controls\n * Returns null for ambiguous/stationary taps\n * \n * @korean 방향제스처감지\n */\n const getDirectionalGesture = useCallback((\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n isTap: boolean\n ): GestureType | null => {\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n \n // If movement is too small, it's not a directional gesture\n const minDirectionalMovement = 15; // pixels\n if (absX < minDirectionalMovement && absY < minDirectionalMovement) {\n return null; // Ambiguous tap, not directional\n }\n \n // Check for diagonal gestures (45-degree threshold)\n const isDiagonal = absX > 20 && absY > 20 && Math.abs(absX - absY) < 30;\n \n const prefix = isTap ? 'tap' : 'hold';\n \n if (isDiagonal) {\n // Diagonal gestures (only for taps/steps)\n if (isTap) {\n if (deltaY < 0 && deltaX < 0) return 'tap-forward-left';\n if (deltaY < 0 && deltaX > 0) return 'tap-forward-right';\n if (deltaY > 0 && deltaX < 0) return 'tap-back-left';\n if (deltaY > 0 && deltaX > 0) return 'tap-back-right';\n }\n return null;\n }\n \n // Cardinal directions\n if (absX > absY) {\n // Horizontal\n return deltaX > 0 ? `${prefix}-right` as GestureType : `${prefix}-left` as GestureType;\n } else {\n // Vertical\n return deltaY < 0 ? `${prefix}-forward` as GestureType : `${prefix}-back` as GestureType;\n }\n }, []);\n\n /**\n * Handle touch start event\n */\n const handleTouchStart = useCallback((e: TouchEvent) => {\n if (!enabled) return;\n\n const touch = e.touches[0];\n touchStartRef.current = touch;\n touchStartTimeRef.current = Date.now();\n setIsTouching(true);\n\n // Check for two-finger tap immediately\n if (e.touches.length === 2) {\n e.preventDefault();\n onGesture({\n type: 'two-finger-tap',\n startX: touch.clientX,\n startY: touch.clientY,\n });\n return;\n }\n \n // Capture screen dimensions at touch start time to prevent incorrect\n // direction calculation if window is resized during hold\n const screenCenterX = window.innerWidth / 2;\n const screenCenterY = window.innerHeight / 2;\n \n // Set up hold detection timer\n // Note: Hold gesture direction is determined from the initial touch position\n // relative to screen center. This supports D-pad style layouts where each\n // region of the screen (or an overlaid control) corresponds to a cardinal direction.\n holdTimerRef.current = window.setTimeout(() => {\n // Touch held for longer than threshold - trigger hold gesture\n // Check touchStartRef to ensure touch hasn't ended before timer fired\n if (touchStartRef.current) {\n const { clientX, clientY } = touchStartRef.current;\n\n // Use captured screen center coordinates (from touch start time)\n const deltaX = clientX - screenCenterX;\n // Invert Y so that a touch higher on the screen is considered \"forward\"\n const deltaY = screenCenterY - clientY;\n\n const holdGesture: GestureType =\n Math.abs(deltaX) >= Math.abs(deltaY)\n ? (deltaX > 0 ? 'hold-right' : 'hold-left')\n : (deltaY > 0 ? 'hold-forward' : 'hold-back');\n\n onGesture({\n type: holdGesture,\n startX: clientX,\n startY: clientY,\n });\n }\n }, holdThreshold);\n }, [enabled, onGesture, holdThreshold]);\n\n /**\n * Handle touch end event\n */\n const handleTouchEnd = useCallback((e: TouchEvent) => {\n if (!enabled || !touchStartRef.current) return;\n\n const touchEnd = e.changedTouches[0];\n const touchStart = touchStartRef.current;\n const touchDuration = Date.now() - touchStartTimeRef.current;\n\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n\n // Calculate deltas\n const deltaX = touchEnd.clientX - touchStart.clientX;\n const deltaY = touchEnd.clientY - touchStart.clientY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n // Reset touch state\n setIsTouching(false);\n\n // Detect gesture type\n if (distance >= minSwipeDistance) {\n // Swipe gesture (for quick directional inputs)\n e.preventDefault();\n\n // Determine primary direction\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n // Horizontal swipe\n if (deltaX > 0) {\n onGesture({\n type: 'swipe-right',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-left',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n } else {\n // Vertical swipe\n if (deltaY > 0) {\n onGesture({\n type: 'swipe-down',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-up',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n } else if (touchDuration <= maxTapDuration && touchDuration < holdThreshold) {\n // Quick tap - tactical step gesture\n e.preventDefault();\n \n const tapGesture = getDirectionalGesture(\n touchStart.clientX,\n touchStart.clientY,\n touchEnd.clientX,\n touchEnd.clientY,\n true // Is a tap\n );\n \n if (tapGesture) {\n // Directional step tap\n triggerStepHaptic();\n onGesture({\n type: tapGesture,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n // Generic tap (fallback)\n onGesture({\n type: 'tap',\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n\n // Clear touch start reference\n touchStartRef.current = null;\n }, [enabled, minSwipeDistance, maxTapDuration, holdThreshold, onGesture, getDirectionalGesture, triggerStepHaptic]);\n\n /**\n * Handle touch cancel event\n */\n const handleTouchCancel = useCallback(() => {\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n \n touchStartRef.current = null;\n touchStartTimeRef.current = 0;\n setIsTouching(false);\n }, []);\n\n /**\n * Setup touch event listeners\n */\n useEffect(() => {\n if (!enabled) return;\n\n const options: AddEventListenerOptions = {\n passive: false, // Allow preventDefault for gesture handling\n };\n\n document.addEventListener('touchstart', handleTouchStart, options);\n document.addEventListener('touchend', handleTouchEnd, options);\n document.addEventListener('touchcancel', handleTouchCancel, options);\n\n return () => {\n document.removeEventListener('touchstart', handleTouchStart);\n document.removeEventListener('touchend', handleTouchEnd);\n document.removeEventListener('touchcancel', handleTouchCancel);\n };\n }, [enabled, handleTouchStart, handleTouchEnd, handleTouchCancel]);\n\n return {\n isTouching,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkIA,SAAgB,iBAAiB,EAC/B,WACA,UAAU,MACV,mBAAmB,IACnB,iBAAiB,KACjB,gBAAgB,KAChB,gBAAgB,QACgC;CAChD,MAAM,gBAAgB,OAAqB,KAAK;CAChD,MAAM,oBAAoB,OAAe,EAAE;CAC3C,MAAM,CAAC,YAAY,iBAAiB,SAAkB,MAAM;CAC5D,MAAM,eAAe,OAAsB,KAAK;;;;;;;CAQhD,MAAM,oBAAoB,kBAAkB;EAC1C,IAAI,CAAC,iBAAiB,CAAC,UAAU,SAAS;EAE1C,IAAI;GAEF,UAAU,QAAQ,GAAG;WACd,OAAO;GAEd,QAAQ,MAAM,kCAAkC,MAAM;;IAEvD,CAAC,cAAc,CAAC;;;;;;;;CASnB,MAAM,wBAAwB,aAC5B,QACA,QACA,MACA,MACA,UACuB;EACvB,MAAM,SAAS,OAAO;EACtB,MAAM,SAAS,OAAO;EACtB,MAAM,OAAO,KAAK,IAAI,OAAO;EAC7B,MAAM,OAAO,KAAK,IAAI,OAAO;EAG7B,MAAM,yBAAyB;EAC/B,IAAI,OAAO,0BAA0B,OAAO,wBAC1C,OAAO;EAIT,MAAM,aAAa,OAAO,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,GAAG;EAErE,MAAM,SAAS,QAAQ,QAAQ;EAE/B,IAAI,YAAY;GAEd,IAAI,OAAO;IACT,IAAI,SAAS,KAAK,SAAS,GAAG,OAAO;IACrC,IAAI,SAAS,KAAK,SAAS,GAAG,OAAO;IACrC,IAAI,SAAS,KAAK,SAAS,GAAG,OAAO;IACrC,IAAI,SAAS,KAAK,SAAS,GAAG,OAAO;;GAEvC,OAAO;;EAIT,IAAI,OAAO,MAET,OAAO,SAAS,IAAI,GAAG,OAAO,UAAyB,GAAG,OAAO;OAGjE,OAAO,SAAS,IAAI,GAAG,OAAO,YAA2B,GAAG,OAAO;IAEpE,EAAE,CAAC;;;;CAKN,MAAM,mBAAmB,aAAa,MAAkB;EACtD,IAAI,CAAC,SAAS;EAEd,MAAM,QAAQ,EAAE,QAAQ;EACxB,cAAc,UAAU;EACxB,kBAAkB,UAAU,KAAK,KAAK;EACtC,cAAc,KAAK;EAGnB,IAAI,EAAE,QAAQ,WAAW,GAAG;GAC1B,EAAE,gBAAgB;GAClB,UAAU;IACR,MAAM;IACN,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;GACF;;EAKF,MAAM,gBAAgB,OAAO,aAAa;EAC1C,MAAM,gBAAgB,OAAO,cAAc;EAM3C,aAAa,UAAU,OAAO,iBAAiB;GAG7C,IAAI,cAAc,SAAS;IACzB,MAAM,EAAE,SAAS,YAAY,cAAc;IAG3C,MAAM,SAAS,UAAU;IAEzB,MAAM,SAAS,gBAAgB;IAO/B,UAAU;KACR,MALA,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,GAC/B,SAAS,IAAI,eAAe,cAC5B,SAAS,IAAI,iBAAiB;KAInC,QAAQ;KACR,QAAQ;KACT,CAAC;;KAEH,cAAc;IAChB;EAAC;EAAS;EAAW;EAAc,CAAC;;;;CAKvC,MAAM,iBAAiB,aAAa,MAAkB;EACpD,IAAI,CAAC,WAAW,CAAC,cAAc,SAAS;EAExC,MAAM,WAAW,EAAE,eAAe;EAClC,MAAM,aAAa,cAAc;EACjC,MAAM,gBAAgB,KAAK,KAAK,GAAG,kBAAkB;EAGrD,IAAI,aAAa,SAAS;GACxB,aAAa,aAAa,QAAQ;GAClC,aAAa,UAAU;;EAIzB,MAAM,SAAS,SAAS,UAAU,WAAW;EAC7C,MAAM,SAAS,SAAS,UAAU,WAAW;EAC7C,MAAM,WAAW,KAAK,KAAK,SAAS,SAAS,SAAS,OAAO;EAG7D,cAAc,MAAM;EAGpB,IAAI,YAAY,kBAAkB;GAEhC,EAAE,gBAAgB;GAGlB,IAAI,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,OAAO,EAErC,IAAI,SAAS,GACX,UAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;QAEF,UAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;QAIJ,IAAI,SAAS,GACX,UAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;QAEF,UAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;SAGD,IAAI,iBAAiB,kBAAkB,gBAAgB,eAAe;GAE3E,EAAE,gBAAgB;GAElB,MAAM,aAAa,sBACjB,WAAW,SACX,WAAW,SACX,SAAS,SACT,SAAS,SACT,KACD;GAED,IAAI,YAAY;IAEd,mBAAmB;IACnB,UAAU;KACR,MAAM;KACN,QAAQ,WAAW;KACnB,QAAQ,WAAW;KACnB,MAAM,SAAS;KACf,MAAM,SAAS;KAChB,CAAC;UAGF,UAAU;IACR,MAAM;IACN,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;;EAKN,cAAc,UAAU;IACvB;EAAC;EAAS;EAAkB;EAAgB;EAAe;EAAW;EAAuB;EAAkB,CAAC;;;;CAKnH,MAAM,oBAAoB,kBAAkB;EAE1C,IAAI,aAAa,SAAS;GACxB,aAAa,aAAa,QAAQ;GAClC,aAAa,UAAU;;EAGzB,cAAc,UAAU;EACxB,kBAAkB,UAAU;EAC5B,cAAc,MAAM;IACnB,EAAE,CAAC;;;;CAKN,gBAAgB;EACd,IAAI,CAAC,SAAS;EAEd,MAAM,UAAmC,EACvC,SAAS,OACV;EAED,SAAS,iBAAiB,cAAc,kBAAkB,QAAQ;EAClE,SAAS,iBAAiB,YAAY,gBAAgB,QAAQ;EAC9D,SAAS,iBAAiB,eAAe,mBAAmB,QAAQ;EAEpE,aAAa;GACX,SAAS,oBAAoB,cAAc,iBAAiB;GAC5D,SAAS,oBAAoB,YAAY,eAAe;GACxD,SAAS,oBAAoB,eAAe,kBAAkB;;IAE/D;EAAC;EAAS;EAAkB;EAAgB;EAAkB,CAAC;CAElE,OAAO,EACL,YACD"}
|
|
1
|
+
{"version":3,"file":"useTouchControls.js","names":[],"sources":["../../src/hooks/useTouchControls.ts"],"sourcesContent":["/**\n * Touch Controls Hook\n * \n * Manages touch event handling and gesture recognition for mobile gameplay\n * Provides swipe detection, multi-touch support, and touch-based movement\n * \n * @module hooks/useTouchControls\n * @category Mobile Controls\n * @korean 터치 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Gesture types supported by the touch control system\n * \n * Added tactical step gestures for Korean martial arts footwork:\n * - tap-{direction}: Quick tap for tactical 30cm step\n * - hold-{direction}: Hold for continuous walk\n * \n * @korean 제스처타입\n */\nexport type GestureType =\n | 'swipe-right'\n | 'swipe-left'\n | 'swipe-up'\n | 'swipe-down'\n | 'two-finger-tap'\n | 'tap'\n | 'tap-forward'\n | 'tap-back'\n | 'tap-left'\n | 'tap-right'\n | 'tap-forward-left'\n | 'tap-forward-right'\n | 'tap-back-left'\n | 'tap-back-right'\n | 'hold-forward'\n | 'hold-back'\n | 'hold-left'\n | 'hold-right';\n\n/**\n * Gesture event data\n */\nexport interface GestureEvent {\n /** Type of gesture detected */\n readonly type: GestureType;\n /** Distance of swipe in pixels (for swipe gestures) */\n readonly distance?: number;\n /** Coordinates of touch start */\n readonly startX?: number;\n readonly startY?: number;\n /** Coordinates of touch end */\n readonly endX?: number;\n readonly endY?: number;\n}\n\n/**\n * Props for useTouchControls hook\n */\nexport interface UseTouchControlsProps {\n /** Callback when gesture is detected */\n readonly onGesture: (gesture: GestureEvent) => void;\n /** Whether touch input is enabled */\n readonly enabled?: boolean;\n /** Minimum swipe distance in pixels (default: 50) */\n readonly minSwipeDistance?: number;\n /** Maximum time for tap in ms (default: 300) */\n readonly maxTapDuration?: number;\n /** Time threshold for hold vs tap in ms (default: 200) */\n readonly holdThreshold?: number;\n /** Enable haptic feedback for steps (default: true) */\n readonly enableHaptics?: boolean;\n}\n\n/**\n * Return type for useTouchControls hook\n */\nexport interface UseTouchControlsReturn {\n /** Whether a touch is currently active */\n readonly isTouching: boolean;\n}\n\n/**\n * Custom hook for handling touch controls and gesture recognition\n * \n * Features:\n * - Swipe detection (horizontal and vertical)\n * - Two-finger tap detection for vital point mode\n * - Single tap detection\n * - Tactical step gestures (tap) vs continuous walk (hold)\n * - Distance calculation for swipe intensity\n * - Configurable thresholds\n * - Haptic feedback for tactical steps\n * \n * Gesture Mapping:\n * - Swipe Right: Advance toward opponent\n * - Swipe Left: Retreat from opponent\n * - Swipe Up: High stance mode\n * - Swipe Down: Low stance mode\n * - Two-Finger Tap: Activate vital point targeting mode\n * - Single Tap (directional): Tactical 30cm step (전술적 발걸음)\n * - Hold (directional): Continuous walk movement\n * \n * @example\n * ```typescript\n * const { isTouching } = useTouchControls({\n * onGesture: (gesture) => {\n * switch (gesture.type) {\n * case 'tap-forward':\n * handleTacticalStep('forward'); // 전진보법\n * break;\n * case 'hold-forward':\n * handleContinuousWalk('forward');\n * break;\n * case 'two-finger-tap':\n * activateVitalPointMode();\n * break;\n * }\n * },\n * enabled: !isPaused,\n * holdThreshold: 200, // 200ms to distinguish tap from hold\n * enableHaptics: true,\n * });\n * ```\n * \n * @public\n * @korean 터치컨트롤사용\n */\nexport function useTouchControls({\n onGesture,\n enabled = true,\n minSwipeDistance = 50,\n maxTapDuration = 300,\n holdThreshold = 200,\n enableHaptics = true,\n}: UseTouchControlsProps): UseTouchControlsReturn {\n const touchStartRef = useRef<Touch | null>(null);\n const touchStartTimeRef = useRef<number>(0);\n const [isTouching, setIsTouching] = useState<boolean>(false);\n const holdTimerRef = useRef<number | null>(null);\n \n /**\n * Trigger haptic feedback for tactical step\n * Light vibration (10ms) to confirm step input\n * \n * @korean 햅틱피드백\n */\n const triggerStepHaptic = useCallback(() => {\n if (!enableHaptics || !navigator.vibrate) return;\n \n try {\n // Short, light vibration for step (10ms)\n navigator.vibrate(10);\n } catch (error) {\n // Haptic feedback not supported or failed\n console.debug('Haptic feedback not available:', error);\n }\n }, [enableHaptics]);\n \n /**\n * Determine directional gesture from touch position\n * Used for D-pad style controls\n * Returns null for ambiguous/stationary taps\n * \n * @korean 방향제스처감지\n */\n const getDirectionalGesture = useCallback((\n startX: number,\n startY: number,\n endX: number,\n endY: number,\n isTap: boolean\n ): GestureType | null => {\n const deltaX = endX - startX;\n const deltaY = endY - startY;\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n \n // If movement is too small, it's not a directional gesture\n const minDirectionalMovement = 15; // pixels\n if (absX < minDirectionalMovement && absY < minDirectionalMovement) {\n return null; // Ambiguous tap, not directional\n }\n \n // Check for diagonal gestures (45-degree threshold)\n const isDiagonal = absX > 20 && absY > 20 && Math.abs(absX - absY) < 30;\n \n const prefix = isTap ? 'tap' : 'hold';\n \n if (isDiagonal) {\n // Diagonal gestures (only for taps/steps)\n if (isTap) {\n if (deltaY < 0 && deltaX < 0) return 'tap-forward-left';\n if (deltaY < 0 && deltaX > 0) return 'tap-forward-right';\n if (deltaY > 0 && deltaX < 0) return 'tap-back-left';\n if (deltaY > 0 && deltaX > 0) return 'tap-back-right';\n }\n return null;\n }\n \n // Cardinal directions\n if (absX > absY) {\n // Horizontal\n return deltaX > 0 ? `${prefix}-right` as GestureType : `${prefix}-left` as GestureType;\n } else {\n // Vertical\n return deltaY < 0 ? `${prefix}-forward` as GestureType : `${prefix}-back` as GestureType;\n }\n }, []);\n\n /**\n * Handle touch start event\n */\n const handleTouchStart = useCallback((e: TouchEvent) => {\n if (!enabled) return;\n\n const touch = e.touches[0];\n touchStartRef.current = touch;\n touchStartTimeRef.current = Date.now();\n setIsTouching(true);\n\n // Check for two-finger tap immediately\n if (e.touches.length === 2) {\n e.preventDefault();\n onGesture({\n type: 'two-finger-tap',\n startX: touch.clientX,\n startY: touch.clientY,\n });\n return;\n }\n \n // Capture screen dimensions at touch start time to prevent incorrect\n // direction calculation if window is resized during hold\n const screenCenterX = window.innerWidth / 2;\n const screenCenterY = window.innerHeight / 2;\n \n // Set up hold detection timer\n // Note: Hold gesture direction is determined from the initial touch position\n // relative to screen center. This supports D-pad style layouts where each\n // region of the screen (or an overlaid control) corresponds to a cardinal direction.\n holdTimerRef.current = window.setTimeout(() => {\n // Touch held for longer than threshold - trigger hold gesture\n // Check touchStartRef to ensure touch hasn't ended before timer fired\n if (touchStartRef.current) {\n const { clientX, clientY } = touchStartRef.current;\n\n // Use captured screen center coordinates (from touch start time)\n const deltaX = clientX - screenCenterX;\n // Invert Y so that a touch higher on the screen is considered \"forward\"\n const deltaY = screenCenterY - clientY;\n\n const holdGesture: GestureType =\n Math.abs(deltaX) >= Math.abs(deltaY)\n ? (deltaX > 0 ? 'hold-right' : 'hold-left')\n : (deltaY > 0 ? 'hold-forward' : 'hold-back');\n\n onGesture({\n type: holdGesture,\n startX: clientX,\n startY: clientY,\n });\n }\n }, holdThreshold);\n }, [enabled, onGesture, holdThreshold]);\n\n /**\n * Handle touch end event\n */\n const handleTouchEnd = useCallback((e: TouchEvent) => {\n if (!enabled || !touchStartRef.current) return;\n\n const touchEnd = e.changedTouches[0];\n const touchStart = touchStartRef.current;\n const touchDuration = Date.now() - touchStartTimeRef.current;\n\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n\n // Calculate deltas\n const deltaX = touchEnd.clientX - touchStart.clientX;\n const deltaY = touchEnd.clientY - touchStart.clientY;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n // Reset touch state\n setIsTouching(false);\n\n // Detect gesture type\n if (distance >= minSwipeDistance) {\n // Swipe gesture (for quick directional inputs)\n e.preventDefault();\n\n // Determine primary direction\n if (Math.abs(deltaX) > Math.abs(deltaY)) {\n // Horizontal swipe\n if (deltaX > 0) {\n onGesture({\n type: 'swipe-right',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-left',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n } else {\n // Vertical swipe\n if (deltaY > 0) {\n onGesture({\n type: 'swipe-down',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n onGesture({\n type: 'swipe-up',\n distance,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n } else if (touchDuration <= maxTapDuration && touchDuration < holdThreshold) {\n // Quick tap - tactical step gesture\n e.preventDefault();\n \n const tapGesture = getDirectionalGesture(\n touchStart.clientX,\n touchStart.clientY,\n touchEnd.clientX,\n touchEnd.clientY,\n true // Is a tap\n );\n \n if (tapGesture) {\n // Directional step tap\n triggerStepHaptic();\n onGesture({\n type: tapGesture,\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n } else {\n // Generic tap (fallback)\n onGesture({\n type: 'tap',\n startX: touchStart.clientX,\n startY: touchStart.clientY,\n endX: touchEnd.clientX,\n endY: touchEnd.clientY,\n });\n }\n }\n\n // Clear touch start reference\n touchStartRef.current = null;\n }, [enabled, minSwipeDistance, maxTapDuration, holdThreshold, onGesture, getDirectionalGesture, triggerStepHaptic]);\n\n /**\n * Handle touch cancel event\n */\n const handleTouchCancel = useCallback(() => {\n // Clear hold timer\n if (holdTimerRef.current) {\n clearTimeout(holdTimerRef.current);\n holdTimerRef.current = null;\n }\n \n touchStartRef.current = null;\n touchStartTimeRef.current = 0;\n setIsTouching(false);\n }, []);\n\n /**\n * Setup touch event listeners\n */\n useEffect(() => {\n if (!enabled) return;\n\n const options: AddEventListenerOptions = {\n passive: false, // Allow preventDefault for gesture handling\n };\n\n document.addEventListener('touchstart', handleTouchStart, options);\n document.addEventListener('touchend', handleTouchEnd, options);\n document.addEventListener('touchcancel', handleTouchCancel, options);\n\n return () => {\n document.removeEventListener('touchstart', handleTouchStart);\n document.removeEventListener('touchend', handleTouchEnd);\n document.removeEventListener('touchcancel', handleTouchCancel);\n };\n }, [enabled, handleTouchStart, handleTouchEnd, handleTouchCancel]);\n\n return {\n isTouching,\n };\n}\n"],"mappings":"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"}
|