blacktrigram 0.7.39 → 0.7.40
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.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.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.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -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.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.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +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.map +1 -1
- 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.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.js.map +1 -1
- 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.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.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.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.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 +3 -90
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- 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/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.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.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/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- 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/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.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.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- 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.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.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/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.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/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.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/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/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/physics.js.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.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.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 +5 -5
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FA,SAAgB,sBACd,QAC6B;CAC7B,MAAM,EACJ,QACA,UAAU,MACV,qBACA,uBACE;CAGJ,MAAM,sBAAsB,cAExB,mCACE,OAAO,eACP,OAAO,UACR,EACH,CAAC,OAAO,eAAe,OAAO,UAAU,CACzC;CAGD,MAAM,CAAC,eAAe,oBAAoB,SAAS,EAAE;CAGrD,MAAM,CAAC,iBAAiB,sBAAsB,SAC5C,EAAE,CACH;CAGD,MAAM,4BAA4B,OAA8C,KAAK;AAGrF,iBAAgB;AACd,MAAI,gBAAgB,WAAW,GAAG;AAEhC,OAAI,0BAA0B,SAAS;AACrC,kBAAc,0BAA0B,QAAQ;AAChD,8BAA0B,UAAU;;AAEtC;;EAGF,IAAI,YAAY;AAChB,4BAA0B,UAAU,kBAAkB;AACpD,OAAI,CAAC,UAAW;GAChB,MAAM,MAAM,KAAK,KAAK;AACtB,uBAAoB,SAAS;AAC3B,WAAO,KACJ,KAAK,QAAQ;KACZ,GAAG;KACH,WAAW,KAAK,IAAI,GAAG,GAAG,YAAY,GAAG,WAAW,IAAI;KACzD,EAAE,CACF,QAAQ,OAAO,GAAG,YAAY,EAAE;KACnC;KACD,IAAI;AAEP,eAAa;AACX,eAAY;AACZ,OAAI,0BAA0B,SAAS;AACrC,kBAAc,0BAA0B,QAAQ;AAChD,8BAA0B,UAAU;;;IAGvC,CAAC,gBAAgB,OAAO,CAAC;CAG5B,MAAM,eAAe,aAClB,gBAAiC;AAChC,SAAO,gBAAgB,MACpB,OAAO,GAAG,gBAAgB,eAAe,GAAG,YAAY,EAC1D;IAEH,CAAC,gBAAgB,CAClB;CAGD,MAAM,uBAAuB,aAC1B,gBAAgC;AAI/B,SAHiB,gBAAgB,MAC9B,OAAO,GAAG,gBAAgB,YAEtB,EAAU,aAAa;IAEhC,CAAC,gBAAgB,CAClB;CAGD,MAAM,eAAe,aAClB,cAAkC;AACjC,SACE,OAAO,WAAW,UAAU,eAAe,OAAO,MAAM,UAAU;IAGtE,CAAC,OAAO,SAAS,OAAO,GAAG,CAC5B;CAGD,MAAM,oBAAoB,aACvB,cAA8C;AAE7C,MAAI,OAAO,UAAU,UAAU,YAC7B,QAAO;GACL,YAAY;GACZ,QAAQ;GACR,qBAAqB;GACtB;AAIH,MAAI,OAAO,KAAK,UAAU,OACxB,QAAO;GACL,YAAY;GACZ,QAAQ;GACR,gBAAgB;GACjB;AAIH,MAAI,aAAa,UAAU,GAAG,CAC5B,QAAO;GACL,YAAY;GACZ,QAAQ;GACR,YAAY;GACb;AAIH,MACE,UAAU,kBACV,OAAO,kBAAkB,UAAU,eAEnC,QAAO;GACL,YAAY;GACZ,QAAQ,YAAY,UAAU,eAAe;GAC7C,aAAa;GACd;AAGH,SAAO,EACL,YAAY,MACb;IAEH;EAAC,OAAO;EAAS,OAAO;EAAI,OAAO;EAAe;EAAa,CAChE;CAGD,MAAM,kBAAkB,aACrB,UAAkB;AACjB,MAAI,QAAQ,KAAK,SAAS,oBAAoB,OAAQ;AACtD,MAAI,CAAC,QAAS;AAEd,mBAAiB,MAAM;EACvB,MAAM,YAAY,oBAAoB;AACtC,wBAAsB,UAAU;IAElC;EAAC;EAAqB;EAAS;EAAoB,CACpD;CAGD,MAAM,mBAAmB,aACtB,kBAA2B;AAC1B,MAAI,CAAC,QAAS;EAGd,MAAM,YAAY,oBADJ,iBAAiB;AAE/B,MAAI,CAAC,UAAW;EAGhB,MAAM,aAAa,kBAAkB,UAAU;AAC/C,MAAI,CAAC,WAAW,YAAY;AAC1B,WAAQ,KAAK,6BAA6B,WAAW,SAAS;AAC9D;;EAIF,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,WAA8B;GAClC,aAAa,UAAU;GACvB,WAAW;GACX,UAAU,UAAU;GACpB,WAAW,UAAU;GACtB;AACD,sBAAoB,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC;AAGjD,uBAAqB,UAAU;IAEjC;EACE;EACA;EACA;EACA;EACA;EACD,CACF;AAGD,iBAAgB;AACd,MAAI,CAAC,QAAS;EAEd,MAAM,kBAAkB,MAAqB;GAE3C,MAAM,SAAS,EAAE;AACjB,OACE,OAAO,YAAY,WACnB,OAAO,YAAY,cACnB,OAAO,kBAEP;GAGF,MAAM,MAAM,EAAE,IAAI,aAAa;GAG/B,MAAM,gBAAgB;IAAC;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAK;IAAI;AAGxE,OAAI,cAAc,SAAS,IAAI,CAC7B,GAAE,gBAAgB;GAIpB,MAAM,iBAAiB,cAAc,QAAQ,IAAI;AACjD,OAAI,mBAAmB,MAAM,iBAAiB,oBAAoB,QAAQ;AACxE,oBAAgB,eAAe;AAC/B,qBAAiB,eAAe;;;AAIpC,SAAO,iBAAiB,WAAW,eAAe;AAClD,eAAa,OAAO,oBAAoB,WAAW,eAAe;IACjE;EAAC;EAAS;EAAqB;EAAiB;EAAiB,CAAC;AAErE,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
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,EAAE;CACpC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;
|
|
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,EAAE;CACpC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CAGpC,sBAAsB;EACpB,YAAY,UAAU;GACtB;CAGF,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;CAEN,OAAO,aACJ,GAAG,SAAwB;EAC1B,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,mBAAmB,MAAM,WAAW;EAE1C,IAAI,oBAAoB,OAAO;GAE7B,WAAW,UAAU;GACrB,YAAY,QAAQ,GAAG,KAAK;SACvB,IAAI,CAAC,WAAW,SAAS;GAE9B,MAAM,gBAAgB,QAAQ;GAC9B,WAAW,UAAU,iBAAiB;IACpC,WAAW,UAAU,KAAK,KAAK;IAC/B,WAAW,UAAU;IACrB,YAAY,QAAQ,GAAG,KAAK;MAC3B,cAAc;;IAGrB,CAAC,MAAM,CACR"}
|
|
@@ -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;AAC1C,MAAI,CAAC,iBAAiB,CAAC,UAAU,QAAS;AAE1C,MAAI;AAEF,aAAU,QAAQ,GAAG;WACd,OAAO;AAEd,WAAQ,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;AAC/B,MAAI,OAAO,0BAA0B,OAAO,uBAC1C,QAAO;EAIT,MAAM,aAAa,OAAO,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,GAAG;EAErE,MAAM,SAAS,QAAQ,QAAQ;AAE/B,MAAI,YAAY;AAEd,OAAI,OAAO;AACT,QAAI,SAAS,KAAK,SAAS,EAAG,QAAO;AACrC,QAAI,SAAS,KAAK,SAAS,EAAG,QAAO;AACrC,QAAI,SAAS,KAAK,SAAS,EAAG,QAAO;AACrC,QAAI,SAAS,KAAK,SAAS,EAAG,QAAO;;AAEvC,UAAO;;AAIT,MAAI,OAAO,KAET,QAAO,SAAS,IAAI,GAAG,OAAO,UAAyB,GAAG,OAAO;MAGjE,QAAO,SAAS,IAAI,GAAG,OAAO,YAA2B,GAAG,OAAO;IAEpE,EAAE,CAAC;;;;CAKN,MAAM,mBAAmB,aAAa,MAAkB;AACtD,MAAI,CAAC,QAAS;EAEd,MAAM,QAAQ,EAAE,QAAQ;AACxB,gBAAc,UAAU;AACxB,oBAAkB,UAAU,KAAK,KAAK;AACtC,gBAAc,KAAK;AAGnB,MAAI,EAAE,QAAQ,WAAW,GAAG;AAC1B,KAAE,gBAAgB;AAClB,aAAU;IACR,MAAM;IACN,QAAQ,MAAM;IACd,QAAQ,MAAM;IACf,CAAC;AACF;;EAKF,MAAM,gBAAgB,OAAO,aAAa;EAC1C,MAAM,gBAAgB,OAAO,cAAc;AAM3C,eAAa,UAAU,OAAO,iBAAiB;AAG7C,OAAI,cAAc,SAAS;IACzB,MAAM,EAAE,SAAS,YAAY,cAAc;IAG3C,MAAM,SAAS,UAAU;IAEzB,MAAM,SAAS,gBAAgB;AAO/B,cAAU;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;AACpD,MAAI,CAAC,WAAW,CAAC,cAAc,QAAS;EAExC,MAAM,WAAW,EAAE,eAAe;EAClC,MAAM,aAAa,cAAc;EACjC,MAAM,gBAAgB,KAAK,KAAK,GAAG,kBAAkB;AAGrD,MAAI,aAAa,SAAS;AACxB,gBAAa,aAAa,QAAQ;AAClC,gBAAa,UAAU;;EAIzB,MAAM,SAAS,SAAS,UAAU,WAAW;EAC7C,MAAM,SAAS,SAAS,UAAU,WAAW;EAC7C,MAAM,WAAW,KAAK,KAAK,SAAS,SAAS,SAAS,OAAO;AAG7D,gBAAc,MAAM;AAGpB,MAAI,YAAY,kBAAkB;AAEhC,KAAE,gBAAgB;AAGlB,OAAI,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,OAAO,CAErC,KAAI,SAAS,EACX,WAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;OAEF,WAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;YAIA,SAAS,EACX,WAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;OAEF,WAAU;IACR,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;aAGG,iBAAiB,kBAAkB,gBAAgB,eAAe;AAE3E,KAAE,gBAAgB;GAElB,MAAM,aAAa,sBACjB,WAAW,SACX,WAAW,SACX,SAAS,SACT,SAAS,SACT,KACD;AAED,OAAI,YAAY;AAEd,uBAAmB;AACnB,cAAU;KACR,MAAM;KACN,QAAQ,WAAW;KACnB,QAAQ,WAAW;KACnB,MAAM,SAAS;KACf,MAAM,SAAS;KAChB,CAAC;SAGF,WAAU;IACR,MAAM;IACN,QAAQ,WAAW;IACnB,QAAQ,WAAW;IACnB,MAAM,SAAS;IACf,MAAM,SAAS;IAChB,CAAC;;AAKN,gBAAc,UAAU;IACvB;EAAC;EAAS;EAAkB;EAAgB;EAAe;EAAW;EAAuB;EAAkB,CAAC;;;;CAKnH,MAAM,oBAAoB,kBAAkB;AAE1C,MAAI,aAAa,SAAS;AACxB,gBAAa,aAAa,QAAQ;AAClC,gBAAa,UAAU;;AAGzB,gBAAc,UAAU;AACxB,oBAAkB,UAAU;AAC5B,gBAAc,MAAM;IACnB,EAAE,CAAC;;;;AAKN,iBAAgB;AACd,MAAI,CAAC,QAAS;EAEd,MAAM,UAAmC,EACvC,SAAS,OACV;AAED,WAAS,iBAAiB,cAAc,kBAAkB,QAAQ;AAClE,WAAS,iBAAiB,YAAY,gBAAgB,QAAQ;AAC9D,WAAS,iBAAiB,eAAe,mBAAmB,QAAQ;AAEpE,eAAa;AACX,YAAS,oBAAoB,cAAc,iBAAiB;AAC5D,YAAS,oBAAoB,YAAY,eAAe;AACxD,YAAS,oBAAoB,eAAe,kBAAkB;;IAE/D;EAAC;EAAS;EAAkB;EAAgB;EAAkB,CAAC;AAElE,QAAO,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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebGLContextLossHandler.js","names":[],"sources":["../../src/hooks/useWebGLContextLossHandler.ts"],"sourcesContent":["/**\n * useWebGLContextLossHandler - React hook for handling WebGL context loss\n *\n * This hook sets up event listeners for WebGL context loss and restoration,\n * which can occur due to GPU issues, memory pressure, or browser tab switching.\n *\n * @example\n * ```tsx\n * const Canvas = () => {\n * useWebGLContextLossHandler();\n * return <Canvas>...</Canvas>;\n * };\n * ```\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Global WebGL context state tracking\n * Helps coordinate context recovery across multiple Canvas components\n */\nlet globalContextLossCount = 0;\nlet globalIsContextLost = false;\nlet lastContextLossTime = 0;\n\n/**\n * Minimum delay between context loss events to prevent thrashing\n */\nexport const MIN_CONTEXT_RECOVERY_DELAY = 100; // ms\n\n/**\n * Get global WebGL context state\n */\nexport const getWebGLContextState = (): {\n isLost: boolean;\n lossCount: number;\n timeSinceLastLoss: number;\n} => ({\n isLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n timeSinceLastLoss: Date.now() - lastContextLossTime,\n});\n\nexport interface WebGLContextLossOptions {\n /**\n * Callback when context is lost\n */\n readonly onContextLost?: () => void;\n\n /**\n * Callback when context is restored\n */\n readonly onContextRestored?: () => void;\n\n /**\n * Whether to attempt automatic restoration (default: true)\n */\n readonly autoRestore?: boolean;\n\n /**\n * Optional canvas ref to attach to a specific canvas element\n * If not provided, will query for the first canvas in the document\n */\n readonly canvasRef?: React.RefObject<HTMLCanvasElement>;\n\n /**\n * Delay in ms before attempting to query for canvas (default: 50)\n * Helps avoid race conditions during screen transitions\n */\n readonly mountDelay?: number;\n\n /**\n * Maximum attempts to find canvas element (default: 5)\n */\n readonly maxRetries?: number;\n}\n\nexport interface WebGLContextState {\n /** Whether context is currently lost */\n readonly isContextLost: boolean;\n /** Number of times context has been lost */\n readonly lossCount: number;\n /** Whether the canvas element has been found */\n readonly isCanvasMounted: boolean;\n}\n\n/**\n * Hook to handle WebGL context loss and restoration\n * Returns state information about the WebGL context\n */\nexport const useWebGLContextLossHandler = (\n options: WebGLContextLossOptions = {}\n): WebGLContextState => {\n const {\n onContextLost,\n onContextRestored,\n autoRestore = true,\n canvasRef,\n mountDelay = 50,\n maxRetries = 5,\n } = options;\n\n const [contextState, setContextState] = useState<WebGLContextState>({\n isContextLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n isCanvasMounted: false,\n });\n\n // Use refs to store the latest callbacks to avoid re-registering event listeners\n const onContextLostRef = useRef(onContextLost);\n const onContextRestoredRef = useRef(onContextRestored);\n const retryCountRef = useRef(0);\n const mountedRef = useRef(true);\n\n // Update refs when callbacks change\n useEffect(() => {\n onContextLostRef.current = onContextLost;\n onContextRestoredRef.current = onContextRestored;\n });\n\n // Track component mount state\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const handleContextLost = useCallback(\n (event: Event) => {\n const now = Date.now();\n globalIsContextLost = true;\n globalContextLossCount++;\n lastContextLossTime = now;\n\n console.warn(\n `⚠️ WebGL context lost (count: ${globalContextLossCount}, time since mount: ${\n now - lastContextLossTime\n }ms)`\n );\n\n // Prevent default behavior to allow restoration\n if (autoRestore) {\n event.preventDefault();\n }\n\n if (mountedRef.current) {\n setContextState({\n isContextLost: true,\n lossCount: globalContextLossCount,\n isCanvasMounted: true,\n });\n }\n\n onContextLostRef.current?.();\n },\n [autoRestore]\n );\n\n const handleContextRestored = useCallback(() => {\n globalIsContextLost = false;\n console.log(\"✅ WebGL context restored successfully\");\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isContextLost: false,\n }));\n }\n\n onContextRestoredRef.current?.();\n }, []);\n\n useEffect(() => {\n let canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n let cleanupFn: (() => void) | undefined;\n let retryTimeout: ReturnType<typeof setTimeout> | undefined;\n let observer: MutationObserver | undefined;\n\n const attachListeners = (canvasEl: HTMLCanvasElement) => {\n canvasEl.addEventListener(\"webglcontextlost\", handleContextLost, false);\n canvasEl.addEventListener(\n \"webglcontextrestored\",\n handleContextRestored,\n false\n );\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isCanvasMounted: true,\n }));\n }\n\n return () => {\n canvasEl.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvasEl.removeEventListener(\n \"webglcontextrestored\",\n handleContextRestored\n );\n };\n };\n\n // Retry finding canvas with delay to handle screen transitions\n const tryFindCanvas = () => {\n if (!mountedRef.current) return;\n\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n retryCountRef.current = 0;\n } else if (retryCountRef.current < maxRetries) {\n retryCountRef.current++;\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n } else {\n // Fall back to MutationObserver\n observer = new MutationObserver(() => {\n canvas = document.querySelector(\"canvas\");\n if (canvas && mountedRef.current) {\n observer?.disconnect();\n cleanupFn = attachListeners(canvas);\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n };\n\n // Attach immediately if a canvas is already mounted, otherwise retry\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n } else {\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n }\n\n // Cleanup\n return () => {\n if (retryTimeout) clearTimeout(retryTimeout);\n observer?.disconnect();\n cleanupFn?.();\n };\n }, [\n autoRestore,\n canvasRef,\n handleContextLost,\n handleContextRestored,\n maxRetries,\n mountDelay,\n ]);\n\n return contextState;\n};\n\n/**\n * Check if WebGL is available in the current browser\n */\nexport const isWebGLAvailable = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl =\n canvas.getContext(\"webgl\") ?? canvas.getContext(\"experimental-webgl\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n\n/**\n * Check if WebGL2 is available in the current browser\n */\nexport const isWebGL2Available = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl2\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useWebGLContextLossHandler.js","names":[],"sources":["../../src/hooks/useWebGLContextLossHandler.ts"],"sourcesContent":["/**\n * useWebGLContextLossHandler - React hook for handling WebGL context loss\n *\n * This hook sets up event listeners for WebGL context loss and restoration,\n * which can occur due to GPU issues, memory pressure, or browser tab switching.\n *\n * @example\n * ```tsx\n * const Canvas = () => {\n * useWebGLContextLossHandler();\n * return <Canvas>...</Canvas>;\n * };\n * ```\n */\n\nimport type React from \"react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Global WebGL context state tracking\n * Helps coordinate context recovery across multiple Canvas components\n */\nlet globalContextLossCount = 0;\nlet globalIsContextLost = false;\nlet lastContextLossTime = 0;\n\n/**\n * Minimum delay between context loss events to prevent thrashing\n */\nexport const MIN_CONTEXT_RECOVERY_DELAY = 100; // ms\n\n/**\n * Get global WebGL context state\n */\nexport const getWebGLContextState = (): {\n isLost: boolean;\n lossCount: number;\n timeSinceLastLoss: number;\n} => ({\n isLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n timeSinceLastLoss: Date.now() - lastContextLossTime,\n});\n\nexport interface WebGLContextLossOptions {\n /**\n * Callback when context is lost\n */\n readonly onContextLost?: () => void;\n\n /**\n * Callback when context is restored\n */\n readonly onContextRestored?: () => void;\n\n /**\n * Whether to attempt automatic restoration (default: true)\n */\n readonly autoRestore?: boolean;\n\n /**\n * Optional canvas ref to attach to a specific canvas element\n * If not provided, will query for the first canvas in the document\n */\n readonly canvasRef?: React.RefObject<HTMLCanvasElement>;\n\n /**\n * Delay in ms before attempting to query for canvas (default: 50)\n * Helps avoid race conditions during screen transitions\n */\n readonly mountDelay?: number;\n\n /**\n * Maximum attempts to find canvas element (default: 5)\n */\n readonly maxRetries?: number;\n}\n\nexport interface WebGLContextState {\n /** Whether context is currently lost */\n readonly isContextLost: boolean;\n /** Number of times context has been lost */\n readonly lossCount: number;\n /** Whether the canvas element has been found */\n readonly isCanvasMounted: boolean;\n}\n\n/**\n * Hook to handle WebGL context loss and restoration\n * Returns state information about the WebGL context\n */\nexport const useWebGLContextLossHandler = (\n options: WebGLContextLossOptions = {}\n): WebGLContextState => {\n const {\n onContextLost,\n onContextRestored,\n autoRestore = true,\n canvasRef,\n mountDelay = 50,\n maxRetries = 5,\n } = options;\n\n const [contextState, setContextState] = useState<WebGLContextState>({\n isContextLost: globalIsContextLost,\n lossCount: globalContextLossCount,\n isCanvasMounted: false,\n });\n\n // Use refs to store the latest callbacks to avoid re-registering event listeners\n const onContextLostRef = useRef(onContextLost);\n const onContextRestoredRef = useRef(onContextRestored);\n const retryCountRef = useRef(0);\n const mountedRef = useRef(true);\n\n // Update refs when callbacks change\n useEffect(() => {\n onContextLostRef.current = onContextLost;\n onContextRestoredRef.current = onContextRestored;\n });\n\n // Track component mount state\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const handleContextLost = useCallback(\n (event: Event) => {\n const now = Date.now();\n globalIsContextLost = true;\n globalContextLossCount++;\n lastContextLossTime = now;\n\n console.warn(\n `⚠️ WebGL context lost (count: ${globalContextLossCount}, time since mount: ${\n now - lastContextLossTime\n }ms)`\n );\n\n // Prevent default behavior to allow restoration\n if (autoRestore) {\n event.preventDefault();\n }\n\n if (mountedRef.current) {\n setContextState({\n isContextLost: true,\n lossCount: globalContextLossCount,\n isCanvasMounted: true,\n });\n }\n\n onContextLostRef.current?.();\n },\n [autoRestore]\n );\n\n const handleContextRestored = useCallback(() => {\n globalIsContextLost = false;\n console.log(\"✅ WebGL context restored successfully\");\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isContextLost: false,\n }));\n }\n\n onContextRestoredRef.current?.();\n }, []);\n\n useEffect(() => {\n let canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n let cleanupFn: (() => void) | undefined;\n let retryTimeout: ReturnType<typeof setTimeout> | undefined;\n let observer: MutationObserver | undefined;\n\n const attachListeners = (canvasEl: HTMLCanvasElement) => {\n canvasEl.addEventListener(\"webglcontextlost\", handleContextLost, false);\n canvasEl.addEventListener(\n \"webglcontextrestored\",\n handleContextRestored,\n false\n );\n\n if (mountedRef.current) {\n setContextState((prev) => ({\n ...prev,\n isCanvasMounted: true,\n }));\n }\n\n return () => {\n canvasEl.removeEventListener(\"webglcontextlost\", handleContextLost);\n canvasEl.removeEventListener(\n \"webglcontextrestored\",\n handleContextRestored\n );\n };\n };\n\n // Retry finding canvas with delay to handle screen transitions\n const tryFindCanvas = () => {\n if (!mountedRef.current) return;\n\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n retryCountRef.current = 0;\n } else if (retryCountRef.current < maxRetries) {\n retryCountRef.current++;\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n } else {\n // Fall back to MutationObserver\n observer = new MutationObserver(() => {\n canvas = document.querySelector(\"canvas\");\n if (canvas && mountedRef.current) {\n observer?.disconnect();\n cleanupFn = attachListeners(canvas);\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n };\n\n // Attach immediately if a canvas is already mounted, otherwise retry\n canvas = canvasRef?.current ?? document.querySelector(\"canvas\");\n if (canvas) {\n cleanupFn = attachListeners(canvas);\n } else {\n retryTimeout = setTimeout(tryFindCanvas, mountDelay);\n }\n\n // Cleanup\n return () => {\n if (retryTimeout) clearTimeout(retryTimeout);\n observer?.disconnect();\n cleanupFn?.();\n };\n }, [\n autoRestore,\n canvasRef,\n handleContextLost,\n handleContextRestored,\n maxRetries,\n mountDelay,\n ]);\n\n return contextState;\n};\n\n/**\n * Check if WebGL is available in the current browser\n */\nexport const isWebGLAvailable = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl =\n canvas.getContext(\"webgl\") ?? canvas.getContext(\"experimental-webgl\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n\n/**\n * Check if WebGL2 is available in the current browser\n */\nexport const isWebGL2Available = (): boolean => {\n try {\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl2\");\n const available = gl !== null;\n // Help GC by cleaning up WebGL context\n if (gl && \"getExtension\" in gl) {\n const loseContext = gl.getExtension(\"WEBGL_lose_context\");\n loseContext?.loseContext();\n }\n return available;\n } catch {\n return false;\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWindowSize.js","names":[],"sources":["../../src/hooks/useWindowSize.ts"],"sourcesContent":["/**\n * useWindowSize - Shared hook for responsive window dimensions\n *\n * @korean 윈도우크기훅 - 반응형 창 크기를 위한 공유 훅\n *\n * Eliminates duplication across screen components\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nexport interface WindowSize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface UseWindowSizeOptions {\n /**\n * Initial width if window is not available (SSR)\n * @default 1200\n */\n readonly initialWidth?: number;\n\n /**\n * Initial height if window is not available (SSR)\n * @default 800\n */\n readonly initialHeight?: number;\n\n /**\n * Debounce delay in milliseconds\n * @default 0\n */\n readonly debounceMs?: number;\n}\n\n/**\n * Hook to track window dimensions with optional debouncing\n *\n * @korean 윈도우 크기를 추적하는 훅 (선택적 디바운싱 지원)\n *\n * @param options - Configuration options\n * @returns Current window dimensions\n *\n * @example\n * ```tsx\n * const { width, height } = useWindowSize();\n * const isMobile = width < 768;\n * ```\n */\nexport function useWindowSize(options: UseWindowSizeOptions = {}): WindowSize {\n const { initialWidth = 1200, initialHeight = 800, debounceMs = 0 } = options;\n\n const [size, setSize] = useState<WindowSize>(() => {\n // Check if window is available (SSR safety)\n if (typeof window !== \"undefined\") {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n }\n return {\n width: initialWidth,\n height: initialHeight,\n };\n });\n\n const handleResize = useCallback(() => {\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n }, []);\n\n useEffect(() => {\n // Safety check for SSR\n if (typeof window === \"undefined\") return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const debouncedResize = () => {\n if (debounceMs > 0) {\n if (timeoutId) clearTimeout(timeoutId);\n timeoutId = setTimeout(handleResize, debounceMs);\n } else {\n handleResize();\n }\n };\n\n window.addEventListener(\"resize\", debouncedResize);\n\n return () => {\n window.removeEventListener(\"resize\", debouncedResize);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [handleResize, debounceMs]);\n\n return size;\n}\n\n/**\n * Hook to determine if current viewport is mobile-sized\n *\n * @korean 현재 뷰포트가 모바일 크기인지 확인하는 훅\n *\n * @param breakpoint - Width threshold for mobile (default: 768)\n * @returns True if viewport width is less than breakpoint\n *\n * @example\n * ```tsx\n * const isMobile = useIsMobile();\n * const isMobileCustom = useIsMobile(640);\n * ```\n */\nexport function useIsMobile(breakpoint = 768): boolean {\n const { width } = useWindowSize();\n return width < breakpoint;\n}\n\nexport default useWindowSize;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,cAAc,UAAgC,EAAE,EAAc;CAC5E,MAAM,EAAE,eAAe,MAAM,gBAAgB,KAAK,aAAa,MAAM;CAErE,MAAM,CAAC,MAAM,WAAW,eAA2B;
|
|
1
|
+
{"version":3,"file":"useWindowSize.js","names":[],"sources":["../../src/hooks/useWindowSize.ts"],"sourcesContent":["/**\n * useWindowSize - Shared hook for responsive window dimensions\n *\n * @korean 윈도우크기훅 - 반응형 창 크기를 위한 공유 훅\n *\n * Eliminates duplication across screen components\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\n\nexport interface WindowSize {\n readonly width: number;\n readonly height: number;\n}\n\nexport interface UseWindowSizeOptions {\n /**\n * Initial width if window is not available (SSR)\n * @default 1200\n */\n readonly initialWidth?: number;\n\n /**\n * Initial height if window is not available (SSR)\n * @default 800\n */\n readonly initialHeight?: number;\n\n /**\n * Debounce delay in milliseconds\n * @default 0\n */\n readonly debounceMs?: number;\n}\n\n/**\n * Hook to track window dimensions with optional debouncing\n *\n * @korean 윈도우 크기를 추적하는 훅 (선택적 디바운싱 지원)\n *\n * @param options - Configuration options\n * @returns Current window dimensions\n *\n * @example\n * ```tsx\n * const { width, height } = useWindowSize();\n * const isMobile = width < 768;\n * ```\n */\nexport function useWindowSize(options: UseWindowSizeOptions = {}): WindowSize {\n const { initialWidth = 1200, initialHeight = 800, debounceMs = 0 } = options;\n\n const [size, setSize] = useState<WindowSize>(() => {\n // Check if window is available (SSR safety)\n if (typeof window !== \"undefined\") {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n }\n return {\n width: initialWidth,\n height: initialHeight,\n };\n });\n\n const handleResize = useCallback(() => {\n setSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n }, []);\n\n useEffect(() => {\n // Safety check for SSR\n if (typeof window === \"undefined\") return;\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const debouncedResize = () => {\n if (debounceMs > 0) {\n if (timeoutId) clearTimeout(timeoutId);\n timeoutId = setTimeout(handleResize, debounceMs);\n } else {\n handleResize();\n }\n };\n\n window.addEventListener(\"resize\", debouncedResize);\n\n return () => {\n window.removeEventListener(\"resize\", debouncedResize);\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [handleResize, debounceMs]);\n\n return size;\n}\n\n/**\n * Hook to determine if current viewport is mobile-sized\n *\n * @korean 현재 뷰포트가 모바일 크기인지 확인하는 훅\n *\n * @param breakpoint - Width threshold for mobile (default: 768)\n * @returns True if viewport width is less than breakpoint\n *\n * @example\n * ```tsx\n * const isMobile = useIsMobile();\n * const isMobileCustom = useIsMobile(640);\n * ```\n */\nexport function useIsMobile(breakpoint = 768): boolean {\n const { width } = useWindowSize();\n return width < breakpoint;\n}\n\nexport default useWindowSize;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,cAAc,UAAgC,EAAE,EAAc;CAC5E,MAAM,EAAE,eAAe,MAAM,gBAAgB,KAAK,aAAa,MAAM;CAErE,MAAM,CAAC,MAAM,WAAW,eAA2B;EAEjD,IAAI,OAAO,WAAW,aACpB,OAAO;GACL,OAAO,OAAO;GACd,QAAQ,OAAO;GAChB;EAEH,OAAO;GACL,OAAO;GACP,QAAQ;GACT;GACD;CAEF,MAAM,eAAe,kBAAkB;EACrC,QAAQ;GACN,OAAO,OAAO;GACd,QAAQ,OAAO;GAChB,CAAC;IACD,EAAE,CAAC;CAEN,gBAAgB;EAEd,IAAI,OAAO,WAAW,aAAa;EAEnC,IAAI,YAAkD;EAEtD,MAAM,wBAAwB;GAC5B,IAAI,aAAa,GAAG;IAClB,IAAI,WAAW,aAAa,UAAU;IACtC,YAAY,WAAW,cAAc,WAAW;UAEhD,cAAc;;EAIlB,OAAO,iBAAiB,UAAU,gBAAgB;EAElD,aAAa;GACX,OAAO,oBAAoB,UAAU,gBAAgB;GACrD,IAAI,WAAW,aAAa,UAAU;;IAEvC,CAAC,cAAc,WAAW,CAAC;CAE9B,OAAO;;;;;;;;;;;;;;;;AAiBT,SAAgB,YAAY,aAAa,KAAc;CACrD,MAAM,EAAE,UAAU,eAAe;CACjC,OAAO,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatSystem.js","names":[],"sources":["../../src/systems/CombatSystem.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { getTechniqueById } from \"../data/techniques\";\nimport { BodyRegion, CombatAttackType, CombatState, DamageType, GrappleTarget } from \"../types\";\nimport { VitalPointCategory, VitalPointSeverity } from \"../types/common\";\nimport { BASE_STAMINA_REGEN_RATE } from \"../types/physicsConstants\";\nimport { Technique } from \"../types/technique\";\nimport { calculateDistance3D } from \"../utils/math\";\nimport { calculateBodyRadius } from \"../utils/skeletonScaling\";\nimport type { DefensiveAnimationType } from \"./animation\";\nimport {\n AnimationType,\n calculateSpeedModifierForDamage,\n determineAnimationTypeForTechnique,\n getAdjustedAnimationDuration,\n getAnimationNameForType,\n isWithinHitWindow,\n} from \"./animation\";\nimport { applyDamageToBodyParts } from \"./bodypart/BodyPartDamageIntegration\";\nimport { playerInjuryManager } from \"./bodypart\";\nimport {\n applyBreathingDisruptionFromVitalPoint,\n applyBreathingDisruptionFromTorsoDamage,\n BreathingDisruptionSystem,\n causesBreathingDisruption,\n updateBreathingDisruption,\n} from \"./breathing\";\nimport BalanceSystem from \"./combat/BalanceSystem\";\nimport ConsciousnessSystem from \"./combat/ConsciousnessSystem\";\nimport {\n extractVitalPointCategory,\n isHeadTraumaHit,\n} from \"./combat/painConsciousnessUtils\";\nimport PainResponseSystem, {\n ShockPainEffect,\n} from \"./combat/PainResponseSystem\";\nimport { CombatResult, CombatSystemInterface } from \"./combat/types\";\nimport {\n CollisionDetection,\n KnockbackPhysics,\n physicalReachCalculator,\n type KnockbackConfig,\n} from \"./physics\";\nimport { PlayerState } from \"./player\";\nimport {\n addEffectsToPlayer,\n getEffectModifiers,\n removeExpiredEffects,\n} from \"./PlayerEffectManager\";\nimport { TRIGRAM_TECHNIQUES } from \"./trigram\";\nimport { TrigramSystem } from \"./TrigramSystem\";\nimport { StatusEffect } from \"./types\";\nimport { KoreanTechnique, VitalPointHitResult } from \"./vitalpoint/types\";\nimport { VitalPointSystem } from \"./VitalPointSystem\";\nimport { GrappleSystem } from \"./combat/GrappleSystem\";\n\n/**\n * Enhanced Combat System with Pain Response and Consciousness integration.\n *\n * Integrates realistic pain accumulation and consciousness tracking for\n * progressive combat impairment.\n */\nexport class CombatSystem implements CombatSystemInterface {\n private vitalPointSystem: VitalPointSystem;\n protected trigramSystem: TrigramSystem;\n private painSystem: PainResponseSystem;\n private consciousnessSystem: ConsciousnessSystem;\n private balanceSystem: BalanceSystem;\n private knockbackPhysics: KnockbackPhysics;\n private collisionDetection: CollisionDetection;\n private grappleSystem: GrappleSystem;\n\n // Track shock pain effects per player\n private shockPainEffects: Map<string, ShockPainEffect>;\n // Track last head trauma time per player for consciousness recovery\n private lastHeadTraumaTime: Map<string, number>;\n\n // Vital point severity thresholds\n private readonly SEVERITY_MAJOR_THRESHOLD = 30;\n private readonly SEVERITY_MODERATE_THRESHOLD = 20;\n\n constructor() {\n this.vitalPointSystem = new VitalPointSystem();\n this.trigramSystem = new TrigramSystem();\n this.painSystem = new PainResponseSystem();\n this.consciousnessSystem = new ConsciousnessSystem();\n this.balanceSystem = new BalanceSystem();\n this.knockbackPhysics = new KnockbackPhysics();\n this.collisionDetection = new CollisionDetection();\n this.grappleSystem = new GrappleSystem();\n this.shockPainEffects = new Map();\n this.lastHeadTraumaTime = new Map();\n }\n\n /**\n * Cleanup per-player combat state.\n *\n * Call this when a player permanently leaves the match or when\n * match-level cleanup is performed to avoid unbounded Map growth.\n *\n * @param playerId - ID of the player to cleanup\n *\n * @public\n * @korean 플레이어데이터정리\n */\n public cleanupPlayerData(playerId: string): void {\n this.shockPainEffects.delete(playerId);\n this.lastHeadTraumaTime.delete(playerId);\n }\n\n /**\n * Dispose of all combat system resources.\n *\n * **Korean**: 전투 시스템 자원 정리\n *\n * Cleans up Three.js resources (geometries, raycaster) used by the collision\n * detection system to prevent memory leaks. Should be called when the\n * CombatSystem is destroyed or reinitialized.\n *\n * @public\n * @korean 전투시스템자원정리\n */\n public dispose(): void {\n // Dispose collision detection resources (cached geometries, raycaster)\n this.collisionDetection.dispose();\n }\n\n /**\n * Get the balance system instance for fall checking.\n *\n * @returns BalanceSystem instance\n * @public\n * @korean 균형시스템가져오기\n */\n public getBalanceSystem(): BalanceSystem {\n return this.balanceSystem;\n }\n\n /**\n * Get the consciousness system instance for fall checking.\n *\n * @returns ConsciousnessSystem instance\n * @public\n * @korean 의식시스템가져오기\n */\n public getConsciousnessSystem(): ConsciousnessSystem {\n return this.consciousnessSystem;\n }\n\n /**\n * Get the collision detection system instance.\n *\n * @returns CollisionDetection instance\n * @public\n * @korean 충돌감지시스템가져오기\n */\n public getCollisionDetection(): CollisionDetection {\n return this.collisionDetection;\n }\n\n /**\n * Calculate knockback physics for combat hit.\n *\n * **Korean**: 밀침 계산 (Calculate Knockback)\n *\n * Determines knockback displacement, duration, and fall state based on:\n * - Attack damage amount\n * - Defender's balance state\n * - Defender's stance resistance\n * - Attack direction vector\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param damage - Total damage dealt\n * @returns Knockback information or undefined if no knockback\n *\n * @example\n * ```typescript\n * const knockback = this.calculateKnockback(attacker, defender, 80);\n * // Returns: { displacement: {x:2.5,y:0,z:0}, duration:0.8, recoveryWindow:0.7, shouldFall:false }\n * ```\n *\n * @private\n * @korean 밀침계산\n */\n private calculateKnockback(\n attacker: PlayerState,\n defender: PlayerState,\n damage: number,\n ): CombatResult[\"knockback\"] {\n // Calculate attack direction vector (attacker → defender)\n const attackDirection = new THREE.Vector3(\n defender.position.x - attacker.position.x,\n 0, // Keep knockback on horizontal plane\n defender.position.y - attacker.position.y,\n );\n\n // If attacker and defender are at the exact same position, skip knockback to avoid NaN direction\n if (attackDirection.lengthSq() === 0) {\n return undefined;\n }\n\n attackDirection.normalize();\n\n // Create knockback configuration\n const config: KnockbackConfig = {\n force: damage * 10, // Convert damage to force (arbitrary scaling)\n direction: attackDirection,\n duration: 0, // Will be calculated by physics engine\n balanceState: {\n current: defender.balance,\n max: 100, // Assuming max balance is always 100\n },\n currentStance: defender.currentStance,\n };\n\n // Calculate knockback result\n const result = this.knockbackPhysics.calculateKnockback(config, damage);\n\n // Convert Three.js Vector3 to plain object for serialization\n return {\n displacement: {\n x: result.displacement.x,\n y: result.displacement.y,\n z: result.displacement.z,\n },\n duration: result.duration,\n recoveryWindow: result.recoveryWindow,\n shouldFall: result.shouldFall,\n };\n }\n\n /**\n * Fix: Update resolveAttack to match interface signature and add animation-aware hit detection\n *\n * **Korean**: 공격 해결 (애니메이션 인식)\n *\n * Integrates animation timing and physical reach calculation for reality-based hit detection.\n * Hits only register when:\n * 1. Animation is in hit window (extension phase)\n * 2. Attacker is within effective reach based on limb length and animation\n * 3. Existing accuracy and stance checks pass\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param technique - Technique being executed\n * @param targetedVitalPointId - Optional specific vital point target\n * @param animationContext - Optional animation timing context for reality-based hit detection\n * @returns Combat result with hit/miss and damage information\n */\n resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: {\n animationType: AnimationType;\n currentTime: number;\n },\n ): CombatResult {\n const timestamp = Date.now();\n\n // Check if attacker can execute the technique\n if (!this.canExecuteTechnique(attacker, technique)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Check if attacker is being grappled - cannot attack while controlled\n if (attacker.combatState === CombatState.GRAPPLED) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // === ANIMATION-AWARE HIT DETECTION ===\n // If animation context provided, validate timing and reach\n if (animationContext) {\n const { animationType, currentTime } = animationContext;\n\n // Check if within hit window (extension phase)\n if (!isWithinHitWindow(animationType, currentTime)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Calculate effective reach based on physical attributes and animation\n const attackerPhysical = getArchetypePhysicalAttributes(\n attacker.archetype,\n );\n const reachResult = physicalReachCalculator.calculateReach(\n attackerPhysical,\n animationType,\n currentTime,\n attacker.currentStance,\n technique.reachConfig, // Pass reachConfig for hybrid reach calculation\n );\n\n // Check distance to defender using 3D Euclidean distance\n // Physics-first: Position type is 2D in METERS\n // No conversion needed - positions are already in meters\n const centerToCenterDistance = calculateDistance3D(\n [attacker.position.x, attacker.position.y, 0],\n [defender.position.x, defender.position.y, 0],\n );\n\n // Calculate body radii\n // Note: PhysicalReachCalculator already includes attacker body pivot/offset in reach calculation\n // (shoulder offset for punches, hip rotation for kicks), so we only subtract defender radius\n // to avoid double-counting the attacker's body dimension.\n // 타격 거리 계산: 수비자 몸체 반경만 제외 (공격자 몸체 오프셋은 이미 도달 거리에 포함됨)\n const defenderPhysical = getArchetypePhysicalAttributes(\n defender.archetype,\n );\n const defenderBodyRadius = calculateBodyRadius(defenderPhysical);\n\n // Effective distance = center-to-center minus defender body radius only\n // Reach is calculated from attacker center to striking limb surface (includes body pivot),\n // so we measure from attacker center to defender surface (subtracting defender radius only).\n // 유효 거리 = 중심간 거리 - 수비자 몸체 반경\n const distance = Math.max(0, centerToCenterDistance - defenderBodyRadius);\n\n // If out of reach, miss\n if (distance > reachResult.effectiveReach) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n }\n\n // Fix: Use correct method signature\n const stanceEffectiveness = this.trigramSystem.calculateStanceEffectiveness(\n attacker.currentStance,\n defender.currentStance,\n );\n\n // Calculate base hit chance\n const baseAccuracy = technique.accuracy * stanceEffectiveness;\n const hitRoll = Math.random();\n const hit = hitRoll <= baseAccuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Special handling for grapple techniques\n if (technique.type === CombatAttackType.GRAPPLE) {\n const grappleResult = this.handleGrappleTechnique(\n attacker,\n defender,\n technique,\n timestamp\n );\n\n // Grapples don't deal immediate damage, they establish control\n // Small damage on successful grapple to represent the initial impact\n const grappleDamage = grappleResult.grappleSuccess\n ? technique.damage * 0.3\n : 0;\n\n return {\n hit: grappleResult.grappleSuccess,\n damage: grappleDamage,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker: grappleResult.updatedAttacker,\n defender: grappleResult.updatedDefender,\n success: grappleResult.grappleSuccess,\n isCritical: false,\n isBlocked: false,\n animation: this.getAnimationInfoForTechnique(technique),\n };\n }\n\n // Process vital point hit if targeted (with archetype parameters)\n let vitalPointResult: VitalPointHitResult | null = null;\n if (targetedVitalPointId) {\n vitalPointResult = this.processVitalPointHit(\n targetedVitalPointId,\n technique.damage ?? 15,\n attacker,\n defender,\n );\n }\n\n // Calculate damage using the interface method\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointResult ?? {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n },\n );\n\n // Check for critical hit\n const critRoll = Math.random();\n const isCritical = critRoll <= (technique.critChance ?? 0.1);\n\n // Determine animation information for technique\n const animationInfo = this.getAnimationInfoForTechnique(technique);\n\n // Calculate knockback physics (밀침 물리)\n const knockbackInfo = this.calculateKnockback(\n attacker,\n defender,\n damageResult.totalDamage,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: isCritical,\n vitalPointHit: vitalPointResult?.hit ?? false,\n effects: damageResult.effectsApplied,\n timestamp,\n technique,\n attacker,\n defender,\n success: true,\n isCritical: vitalPointResult?.hit ?? false,\n isBlocked: false,\n targetedVitalPointId, // Pass through the targeted vital point ID\n animation: animationInfo, // Add animation information\n knockback: knockbackInfo, // Add knockback information\n };\n }\n\n /**\n * Get animation information for a technique\n *\n * Determines the skeletal animation to play, duration, and speed modifier\n * based on technique configuration or automatic determination.\n *\n * @param technique - Korean technique to execute\n * @returns Animation information or undefined\n *\n * @private\n * @korean 기술애니메이션정보가져오기\n */\n private getAnimationInfoForTechnique(\n technique: KoreanTechnique,\n ): CombatResult[\"animation\"] {\n // Check if technique has explicit animation config (from Technique interface)\n // KoreanTechnique may not have animation field, so check the technique data\n const techniqueData = this.getTechniqueData(technique);\n\n let animationType;\n let speedModifier;\n\n if (techniqueData?.animation) {\n // Use explicit animation configuration\n animationType = techniqueData.animation.type;\n speedModifier = techniqueData.animation.speedModifier;\n } else {\n // Auto-determine animation from technique characteristics\n const techniqueName =\n technique.name?.english || technique.englishName || \"\";\n const techniqueId = technique.id || \"\";\n const damageType = technique.damageType || \"\";\n\n animationType = determineAnimationTypeForTechnique(\n techniqueName,\n techniqueId,\n damageType,\n );\n\n // Calculate speed modifier based on technique damage\n speedModifier = calculateSpeedModifierForDamage(technique.damage || 15);\n }\n\n // Get base animation name from animation type\n const animationName = getAnimationNameForType(animationType);\n\n // Calculate adjusted duration\n const duration = getAdjustedAnimationDuration(animationName, speedModifier);\n\n // Get Korean technique name for display\n const techniqueDisplayName =\n technique.name?.korean || technique.koreanName || technique.id;\n\n return {\n animationName,\n duration,\n speedModifier,\n techniqueDisplayName,\n };\n }\n\n /**\n * Get Technique data if this KoreanTechnique has an associated Technique definition\n *\n * @param technique - Korean technique\n * @returns Technique data or null\n *\n * @private\n * @korean 기술데이터가져오기\n */\n private getTechniqueData(technique: KoreanTechnique): Technique | null {\n return getTechniqueById(technique.id) ?? null;\n }\n\n /**\n * Handle grapple technique execution.\n *\n * **Korean**: 잡기 기술 처리 (Handle Grapple Technique)\n *\n * Initiates or transitions grapple control based on technique.\n *\n * @param attacker - Player executing grapple\n * @param defender - Target player\n * @param technique - Grapple technique being used\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states with grapple control\n */\n handleGrappleTechnique(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n currentTime: number\n ): {\n updatedAttacker: PlayerState;\n updatedDefender: PlayerState;\n grappleSuccess: boolean;\n } {\n // Determine grapple target from technique\n const target = this.getGrappleTargetFromTechnique(technique);\n\n // Attempt grapple\n const result = this.grappleSystem.attemptGrapple(\n attacker,\n defender,\n target,\n currentTime\n );\n\n if (result.success && result.grappleControl) {\n // Grapple succeeded - update both players\n return {\n updatedAttacker: {\n ...attacker,\n combatState: CombatState.GRAPPLING,\n grappleControl: result.grappleControl,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: {\n ...defender,\n combatState: CombatState.GRAPPLED,\n grappleControl: result.grappleControl,\n },\n grappleSuccess: true,\n };\n }\n\n // Grapple failed\n return {\n updatedAttacker: {\n ...attacker,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: defender,\n grappleSuccess: false,\n };\n }\n\n /**\n * Determine grapple target from technique characteristics.\n *\n * **Korean**: 기술에서 잡기 목표 결정 (Determine Grapple Target from Technique)\n *\n * @private\n */\n private getGrappleTargetFromTechnique(\n technique: KoreanTechnique\n ): GrappleTarget {\n // Check technique name/ID for hints (both English and Korean)\n const techName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techNameKorean = (\n technique.name?.korean ||\n technique.koreanName ||\n \"\"\n );\n const techId = technique.id.toLowerCase();\n\n // Check for wrist/hand - 손목 (sonmok)\n if (\n techName.includes(\"wrist\") ||\n techId.includes(\"wrist\") ||\n techNameKorean.includes(\"손목\")\n ) {\n return GrappleTarget.HAND;\n }\n\n // Check for arm - 팔 (pal)\n if (\n techName.includes(\"arm\") ||\n techId.includes(\"arm\") ||\n techNameKorean.includes(\"팔\")\n ) {\n return GrappleTarget.ARM;\n }\n\n // Check for leg - 다리 (dari)\n if (\n techName.includes(\"leg\") ||\n techId.includes(\"leg\") ||\n techNameKorean.includes(\"다리\")\n ) {\n return GrappleTarget.LEG;\n }\n\n // Check for neck - 목 (mok)\n if (\n techName.includes(\"neck\") ||\n techId.includes(\"neck\") ||\n techNameKorean.includes(\"목\")\n ) {\n return GrappleTarget.NECK;\n }\n\n // Check for both arms - 양팔 (yangpal)\n if (\n techName.includes(\"both\") ||\n techName.includes(\"double\") ||\n techId.includes(\"both\") ||\n techNameKorean.includes(\"양팔\") ||\n techNameKorean.includes(\"쌍\")\n ) {\n return GrappleTarget.BOTH_ARMS;\n }\n\n // Check for torso/body/hip - 몸통 (momtong), 허리 (heori), 몸 (mom)\n if (\n techName.includes(\"torso\") ||\n techName.includes(\"body\") ||\n techName.includes(\"hip\") ||\n techId.includes(\"torso\") ||\n techNameKorean.includes(\"몸통\") ||\n techNameKorean.includes(\"허리\") ||\n techNameKorean.includes(\"몸\")\n ) {\n return GrappleTarget.TORSO;\n }\n\n // Default to arm for unspecified grapples\n return GrappleTarget.ARM;\n }\n\n /**\n * Update grapple state for players over time.\n *\n * **Korean**: 잡기 상태 업데이트 (Update Grapple State)\n *\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed in seconds\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states\n */\n updateGrappleState(\n controller: PlayerState,\n target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): {\n updatedController: PlayerState;\n updatedTarget: PlayerState;\n } {\n if (!controller.grappleControl) {\n // No active grapple\n return {\n updatedController: controller,\n updatedTarget: target,\n };\n }\n\n // Validate grapple control consistency\n if (\n controller.grappleControl.controllerId !== controller.id ||\n controller.grappleControl.targetId !== target.id\n ) {\n // Inconsistent state - break grapple\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Update grapple control\n const updatedControl = this.grappleSystem.updateGrapple(\n controller.grappleControl,\n controller,\n target,\n deltaTime,\n currentTime\n );\n\n if (!updatedControl) {\n // Grapple broken - reset both players\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Deduct stamina from controller\n const staminaCost = updatedControl.staminaCostPerSecond * deltaTime;\n\n return {\n updatedController: {\n ...controller,\n grappleControl: updatedControl,\n stamina: Math.max(0, controller.stamina - staminaCost),\n },\n updatedTarget: {\n ...target,\n grappleControl: updatedControl,\n },\n };\n }\n\n /**\n * Fix: Make applyCombatResult non-static instance method with effect application\n * Enhanced with Pain Response and Consciousness System integration\n */\n applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Start with base result\n const { updatedAttacker, updatedDefender: initialDefender } =\n CombatSystem.applyCombatResult(result, attacker, defender);\n let updatedDefender = initialDefender;\n\n if (result.hit && result.damage > 0) {\n // Determine vital point category and severity from hit result\n const category = this.getVitalPointCategory(result);\n const severity = this.getVitalPointSeverity(result);\n\n // Apply pain from damage\n const { player: defenderWithPain, shockEffect: newShockEffect } =\n this.painSystem.applyPain(\n updatedDefender,\n result.damage,\n severity,\n category,\n );\n updatedDefender = defenderWithPain;\n\n // Store shock pain effect if triggered\n if (newShockEffect) {\n this.shockPainEffects.set(updatedDefender.id, newShockEffect);\n }\n\n // Check for pain overload stun\n if (this.painSystem.shouldTriggerStun(updatedDefender)) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n };\n }\n\n // Apply consciousness damage for head/neurological hits\n if (this.isHeadTrauma(result, category)) {\n updatedDefender = this.consciousnessSystem.applyDamage(\n updatedDefender,\n result.damage,\n category,\n );\n\n // Track head trauma time for recovery gating\n this.lastHeadTraumaTime.set(updatedDefender.id, Date.now());\n\n // Check incapacitation threshold\n if (\n this.consciousnessSystem.isAtIncapacitationThreshold(updatedDefender)\n ) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n // Could add helpless duration tracking here if needed\n };\n }\n }\n\n // Apply pain and consciousness effects to stats\n const currentShockEffect = this.shockPainEffects.get(updatedDefender.id);\n updatedDefender = this.painSystem.applyEffects(\n updatedDefender,\n currentShockEffect,\n );\n updatedDefender = this.consciousnessSystem.applyEffects(updatedDefender);\n\n // Apply balance disruption from the hit\n // Determine body region from vital point or use default\n const bodyRegion = this.getBodyRegionFromResult(result);\n updatedDefender = this.balanceSystem.disruptBalance(\n updatedDefender,\n result.damage,\n bodyRegion,\n );\n\n // Apply breathing disruption for torso strikes\n if (result.vitalPointHit && result.targetedVitalPointId) {\n // Vital point strike to torso\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n if (vitalPoint && causesBreathingDisruption(vitalPoint.id)) {\n updatedDefender = applyBreathingDisruptionFromVitalPoint(\n updatedDefender,\n vitalPoint,\n Date.now(),\n );\n }\n } else if (bodyRegion === BodyRegion.TORSO && result.damage >= 10) {\n // General torso damage (non-vital point hits)\n // Apply breathing disruption if damage is significant enough\n // Note: Solar plexus detection is best-effort; vital point system is primary mechanism\n const techniqueId = (result.technique?.id ?? \"\").toLowerCase();\n const isSolarPlexusArea = \n techniqueId.includes(\"solar\") ||\n techniqueId.includes(\"myeongchi\");\n updatedDefender = applyBreathingDisruptionFromTorsoDamage(\n updatedDefender,\n result.damage,\n isSolarPlexusArea,\n Date.now(),\n );\n }\n }\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determines if a hit caused head trauma (affects consciousness).\n *\n * @param result - Combat result\n * @param category - Vital point category\n * @returns True if hit should affect consciousness\n */\n private isHeadTrauma(\n result: CombatResult,\n category?: VitalPointCategory,\n ): boolean {\n return isHeadTraumaHit(result, category);\n }\n\n /**\n * Extracts vital point category from combat result.\n *\n * @param result - Combat result\n * @returns Vital point category if available\n */\n private getVitalPointCategory(\n result: CombatResult,\n ): VitalPointCategory | undefined {\n return extractVitalPointCategory(result);\n }\n\n /**\n * Extracts vital point severity from combat result.\n *\n * @param result - Combat result\n * @returns Vital point severity if critical hit\n */\n private getVitalPointSeverity(\n result: CombatResult,\n ): VitalPointSeverity | undefined {\n if (result.vitalPointHit) {\n if (result.isCritical) {\n return VitalPointSeverity.CRITICAL;\n }\n if (result.damage > this.SEVERITY_MAJOR_THRESHOLD) {\n return VitalPointSeverity.MAJOR;\n }\n if (result.damage > this.SEVERITY_MODERATE_THRESHOLD) {\n return VitalPointSeverity.MODERATE;\n }\n return VitalPointSeverity.MINOR;\n }\n return undefined;\n }\n\n /**\n * Determines body region from combat result for balance disruption.\n *\n * Maps vital points to body regions for balance system integration.\n * Uses string matching on vital point IDs as a pragmatic heuristic since\n * VitalPoint interface doesn't currently include a bodyRegion property.\n *\n * Future improvement: Add bodyRegion: BodyRegion to VitalPoint interface\n * for more robust region mapping without string pattern matching.\n *\n * @param result - Combat result\n * @returns Body region that was struck\n * @private\n * @korean 신체부위결정\n */\n private getBodyRegionFromResult(result: CombatResult): BodyRegion {\n // If we have a targeted vital point, try to determine region\n if (result.targetedVitalPointId) {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n\n if (vitalPoint) {\n const pointId = vitalPoint.id.toLowerCase();\n\n // Check for leg/lower body targets\n if (\n pointId.includes(\"leg\") ||\n pointId.includes(\"knee\") ||\n pointId.includes(\"ankle\") ||\n pointId.includes(\"thigh\")\n ) {\n return BodyRegion.LEFT_LEG; // Generic leg for balance disruption\n }\n\n // Check for head targets\n if (\n pointId.includes(\"head\") ||\n pointId.includes(\"temple\") ||\n pointId.includes(\"jaw\") ||\n pointId.includes(\"nose\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Check for arm targets\n if (\n pointId.includes(\"arm\") ||\n pointId.includes(\"elbow\") ||\n pointId.includes(\"wrist\") ||\n pointId.includes(\"shoulder\")\n ) {\n return BodyRegion.LEFT_ARM;\n }\n }\n }\n\n // Default to torso for general strikes\n return BodyRegion.TORSO;\n }\n\n /**\n * Updates player states for recovery (pain dissipation, consciousness recovery, balance recovery).\n * Call this regularly in game loop.\n *\n * @param player - Player to update\n * @param deltaTime - Time elapsed since last update (ms)\n * @returns Updated player state\n */\n applyRecovery(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = player;\n\n // Apply pain recovery\n updatedPlayer = this.painSystem.applyDissipation(updatedPlayer, deltaTime);\n\n // Apply consciousness recovery (only if enough time since head trauma)\n const lastTrauma = this.lastHeadTraumaTime.get(player.id);\n updatedPlayer = this.consciousnessSystem.applyRecovery(\n updatedPlayer,\n deltaTime,\n lastTrauma,\n );\n\n // Apply balance recovery\n updatedPlayer = this.balanceSystem.applyRecovery(updatedPlayer, deltaTime);\n\n // Clean up expired shock pain effects\n const shockEffect = this.shockPainEffects.get(player.id);\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed >= shockEffect.duration) {\n this.shockPainEffects.delete(player.id);\n }\n }\n\n return updatedPlayer;\n }\n\n /**\n * Static version for backwards compatibility with comprehensive effect application\n * Enhanced with Pain Response and Consciousness System integration\n * Updated to apply damage to body parts for 8-body-part health visualization\n */\n static applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Apply damage and effects\n let updatedDefender = defender;\n let updatedAttacker = attacker;\n\n if (result.hit) {\n // Determine body region from technique or use random distribution\n const bodyRegion = CombatSystem.getBodyRegionFromTechnique(\n result.technique,\n );\n\n // Apply damage to body parts (this also updates aggregate health)\n updatedDefender = applyDamageToBodyParts(\n defender,\n result.damage,\n bodyRegion,\n );\n\n // Record injury for trauma visualization (외상 시각화)\n // Use per-player injury tracking to prevent mixing injuries between characters\n // Automatically tracks bruising, cuts, and blood effects based on damage type\n // Records injury even without specific technique (defaults to BRUISE)\n if (result.damage > 0) {\n const defenderInjuryIntegration = playerInjuryManager.getIntegrationForPlayer(defender.id);\n defenderInjuryIntegration.recordCombatDamage({\n damage: result.damage,\n bodyRegion,\n damageType: (result.technique?.damageType as DamageType) || undefined,\n });\n }\n\n // Update tracking stats (applyDamageToBodyParts already sets health)\n updatedDefender = {\n ...updatedDefender,\n totalDamageReceived:\n updatedDefender.totalDamageReceived + result.damage,\n hitsTaken: updatedDefender.hitsTaken + 1,\n };\n\n // Apply status effects from vital point hit\n if (result.effects && result.effects.length > 0) {\n updatedDefender = addEffectsToPlayer(updatedDefender, result.effects);\n }\n\n // Track vital point hits\n if (result.vitalPointHit) {\n updatedAttacker = {\n ...updatedAttacker,\n vitalPointHits: attacker.vitalPointHits + 1,\n };\n }\n\n // Track perfect strikes (high accuracy)\n if (result.isCritical) {\n updatedAttacker = {\n ...updatedAttacker,\n perfectStrikes: attacker.perfectStrikes + 1,\n };\n }\n }\n\n // Apply technique costs to attacker\n updatedAttacker = {\n ...updatedAttacker,\n ki: Math.max(0, attacker.ki - 5),\n stamina: Math.max(0, attacker.stamina - 10),\n totalDamageDealt:\n attacker.totalDamageDealt + (result.hit ? result.damage : 0),\n hitsLanded: attacker.hitsLanded + (result.hit ? 1 : 0),\n };\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determine body region from technique name or type\n *\n * **Korean**: 기술에서 신체 영역 결정\n *\n * @param technique - The technique used in the attack\n * @returns Body region to apply damage to\n */\n private static getBodyRegionFromTechnique(\n technique?: KoreanTechnique,\n ): BodyRegion {\n if (!technique) {\n // Default to torso if no technique specified\n return BodyRegion.TORSO;\n }\n\n const techniqueName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techniqueId = technique.id?.toLowerCase() || \"\";\n\n // Head/face targeting techniques\n if (\n techniqueName.includes(\"head\") ||\n techniqueName.includes(\"temple\") ||\n techniqueName.includes(\"jaw\") ||\n techniqueName.includes(\"face\") ||\n techniqueId.includes(\"head\") ||\n techniqueId.includes(\"skull\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Neck targeting techniques\n if (\n techniqueName.includes(\"neck\") ||\n techniqueName.includes(\"throat\") ||\n techniqueName.includes(\"choke\") ||\n techniqueId.includes(\"neck\")\n ) {\n return BodyRegion.NECK;\n }\n\n // Leg targeting techniques\n if (\n techniqueName.includes(\"kick\") ||\n techniqueName.includes(\"sweep\") ||\n techniqueName.includes(\"leg\") ||\n techniqueName.includes(\"knee\") ||\n techniqueId.includes(\"kick\") ||\n techniqueId.includes(\"leg\")\n ) {\n // Randomly choose left or right leg\n return Math.random() < 0.5 ? BodyRegion.LEFT_LEG : BodyRegion.RIGHT_LEG;\n }\n\n // Arm targeting techniques\n if (\n techniqueName.includes(\"arm\") ||\n techniqueName.includes(\"shoulder\") ||\n techniqueName.includes(\"elbow\") ||\n techniqueId.includes(\"arm\")\n ) {\n // Randomly choose left or right arm\n return Math.random() < 0.5 ? BodyRegion.LEFT_ARM : BodyRegion.RIGHT_ARM;\n }\n\n // Punch/strike techniques typically target torso\n if (\n techniqueName.includes(\"punch\") ||\n techniqueName.includes(\"strike\") ||\n techniqueName.includes(\"jab\") ||\n techniqueName.includes(\"cross\") ||\n techniqueName.includes(\"hook\") ||\n techniqueName.includes(\"uppercut\")\n ) {\n // Mix of torso and head for punches\n return Math.random() < 0.7 ? BodyRegion.TORSO : BodyRegion.HEAD;\n }\n\n // Body/core targeting techniques\n if (\n techniqueName.includes(\"body\") ||\n techniqueName.includes(\"solar\") ||\n techniqueName.includes(\"liver\") ||\n techniqueName.includes(\"ribs\") ||\n techniqueName.includes(\"abdomen\") ||\n techniqueId.includes(\"torso\") ||\n techniqueId.includes(\"core\")\n ) {\n return BodyRegion.TORSO;\n }\n\n // Default to torso for unmatched techniques\n return BodyRegion.TORSO;\n }\n\n /**\n * Fix: Add missing getAvailableTechniques method required by interface\n */\n getAvailableTechniques(player: PlayerState): readonly KoreanTechnique[] {\n const allTechniques = TRIGRAM_TECHNIQUES[player.currentStance] ?? [];\n\n // Filter techniques based on available resources using canExecuteTechnique\n return allTechniques.filter((technique) =>\n this.canExecuteTechnique(player, technique as KoreanTechnique),\n ) as readonly KoreanTechnique[];\n }\n\n /**\n * Check if attacker can execute technique\n */\n private canExecuteTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n ): boolean {\n return (\n player.ki >= technique.kiCost &&\n player.stamina >= technique.staminaCost &&\n player.currentStance === technique.stance &&\n !player.isStunned\n );\n }\n\n /**\n * Static methods for backwards compatibility\n */\n static resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const instance = new CombatSystem();\n return instance.executeAttack(attacker, defender, technique);\n }\n\n /**\n * Check if a player is defeated\n */\n isPlayerDefeated(player: PlayerState): boolean {\n return player.health <= 0 || player.consciousness <= 0;\n }\n\n /**\n * Update player state over time with effect management\n */\n updatePlayerState(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = { ...player };\n\n // Remove expired effects first\n updatedPlayer = removeExpiredEffects(updatedPlayer);\n\n // Get effect modifiers for resource regeneration\n const effectModifiers = getEffectModifiers(updatedPlayer);\n\n // Apply natural regeneration with effect modifiers\n const regenRate = deltaTime / 1000; // Convert to seconds\n\n // Ki regeneration (slower during combat) - affected by effects\n if (updatedPlayer.ki < updatedPlayer.maxKi) {\n updatedPlayer.ki = Math.min(\n updatedPlayer.maxKi,\n updatedPlayer.ki + regenRate * 2 * effectModifiers.kiRegen,\n );\n }\n\n // Stamina regeneration - affected by effects and breathing disruption\n // Uses BASE_STAMINA_REGEN_RATE constant for better gameplay\n // Allows players to move around and attack frequently without running out\n // 체력 재생 속도 - 더 활발한 전투를 위해\n if (updatedPlayer.stamina < updatedPlayer.maxStamina) {\n const baseStaminaRegen = regenRate * BASE_STAMINA_REGEN_RATE * effectModifiers.staminaRegen;\n // Apply breathing disruption system's stamina regen modifier\n const modifiedStaminaRegen =\n BreathingDisruptionSystem.calculateStaminaRegen(\n updatedPlayer,\n baseStaminaRegen,\n );\n updatedPlayer.stamina = Math.min(\n updatedPlayer.maxStamina,\n updatedPlayer.stamina + modifiedStaminaRegen,\n );\n }\n\n // Health regeneration (very slow)\n if (\n updatedPlayer.health < updatedPlayer.maxHealth &&\n updatedPlayer.health > 0\n ) {\n updatedPlayer.health = Math.min(\n updatedPlayer.maxHealth,\n updatedPlayer.health + regenRate * 0.5,\n );\n }\n\n // Update breathing disruption effects (gradual recovery if torso health > 50%)\n updatedPlayer = updateBreathingDisruption(\n updatedPlayer,\n deltaTime,\n Date.now(),\n );\n\n // Clear temporary combat states\n const currentTime = Date.now();\n if (\n updatedPlayer.lastActionTime &&\n currentTime - updatedPlayer.lastActionTime > updatedPlayer.recoveryTime\n ) {\n updatedPlayer.isStunned = false;\n updatedPlayer.isCountering = false;\n }\n\n return updatedPlayer;\n }\n\n /**\n * Get combat statistics\n */\n getCombatStatistics(player: PlayerState): {\n healthPercent: number;\n kiPercent: number;\n staminaPercent: number;\n balancePercent: number;\n } {\n return {\n healthPercent: (player.health / player.maxHealth) * 100,\n kiPercent: (player.ki / player.maxKi) * 100,\n staminaPercent: (player.stamina / player.maxStamina) * 100,\n balancePercent: player.balance,\n };\n }\n\n /**\n * Fix: Integrate processVitalPointHit into the combat system with archetype parameters\n */\n private processVitalPointHit(\n vitalPointId: string,\n _baseDamage: number, // Unused but kept for interface compatibility\n attacker: PlayerState,\n defender: PlayerState,\n ): VitalPointHitResult {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(vitalPointId);\n\n if (!vitalPoint) {\n return {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n };\n }\n\n // Use VitalPointSystem's processHit with full archetype support\n return this.vitalPointSystem.processHit(\n vitalPoint.position, // Use vital point position for hit calculation\n { width: 10, height: 10 }, // Standard hit box\n vitalPointId, // Targeted vital point\n undefined, // Use current hour from system\n attacker.archetype, // Attacker archetype for offensive modifiers\n defender.archetype, // Defender archetype for defensive modifiers\n );\n }\n\n /**\n * Process defensive action and determine animation type.\n *\n * Determines the appropriate defensive animation based on:\n * - Defender's balance and stamina (defensive power)\n * - Attacker's technique power\n * - Combat readiness state\n *\n * **Korean**: 방어 행동 처리\n *\n * @param defender - Defending player state\n * @param attacker - Attacking player state (unused but kept for future enhancements)\n * @param attackPower - Power of the incoming attack\n * @returns Defensive animation type to play (parry_deflect, block_success, or guard_break)\n *\n * @example\n * ```typescript\n * const animType = combatSystem.processDefensiveAction(\n * defender,\n * attacker,\n * technique.damage\n * );\n * // Returns: 'parry_deflect', 'block_success', or 'guard_break'\n * ```\n *\n * @public\n * @korean 방어행동처리\n */\n public processDefensiveAction(\n defender: PlayerState,\n _attacker: PlayerState,\n attackPower: number,\n ): Exclude<DefensiveAnimationType, \"guard_recovery\"> {\n // Guard Break: Check balance threshold first (highest priority condition)\n if (defender.balance < 30) {\n return \"guard_break\";\n }\n\n // Calculate defensive power based on balance and stamina\n // Balance represents physical stability (0-100)\n // Stamina represents energy reserves (0-100)\n const balanceFactor = defender.balance / 100;\n const staminaFactor = defender.stamina / 100;\n\n // Apply defensive modifiers from effects\n const effectModifiers = getEffectModifiers(defender);\n const defenseMultiplier = effectModifiers.defense;\n\n // Defense stat provides moderate bonus (normalized to 0.5-1.5 range for typical 5-15 defense)\n const defenseBonus = Math.max(0.5, Math.min(1.5, defender.defense / 10));\n\n // Calculate final defensive power\n const defensePower =\n balanceFactor * staminaFactor * 100 * defenseMultiplier * defenseBonus;\n\n // Determine defensive outcome based on power ratio\n // Parry: Strong defense (1.8x attack power or more) - Perfect deflection\n if (defensePower >= attackPower * 1.8) {\n return \"parry_deflect\";\n }\n // Block Success: Adequate defense (1.0x to 1.8x attack power) - Absorb impact\n else if (defensePower >= attackPower) {\n return \"block_success\";\n }\n // Guard Break: Defense insufficient against powerful attack (<60% of attack power)\n else if (defensePower < attackPower * 0.6) {\n return \"guard_break\";\n }\n // Block Success: Marginal defense (60-100% of attack power) - barely hold\n else {\n return \"block_success\";\n }\n }\n\n /**\n * Execute attack with technique\n */\n protected executeAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const hitRoll = Math.random();\n const accuracy = technique.accuracy || 0.8;\n const hit = hitRoll <= accuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Calculate damage using the interface method\n const vitalPointHit: VitalPointHitResult = {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR, // Use the enum directly\n };\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointHit,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: Math.random() < (technique.critChance || 0.1),\n vitalPointHit: false,\n effects: damageResult.effectsApplied,\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: true,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n /**\n * Fix: Add missing calculateDamage method required by interface\n */\n calculateDamage(\n technique: KoreanTechnique,\n attacker: PlayerState,\n defender: PlayerState,\n hitResult: VitalPointHitResult,\n ): {\n baseDamage: number;\n modifierDamage: number;\n totalDamage: number;\n effectsApplied: readonly StatusEffect[];\n finalDefenderState?: Partial<PlayerState>;\n } {\n // Calculate base damage from technique\n const baseDamage = technique.damage || 15;\n\n // Apply attacker modifiers\n const attackerBonus = attacker.attackPower * 0.1;\n\n // Apply vital point modifiers if hit\n let vitalPointMultiplier = 1.0;\n if (hitResult.hit && hitResult.vitalPointHit) {\n // Use the correct property name\n const severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 1.1,\n [VitalPointSeverity.MODERATE]: 1.3,\n [VitalPointSeverity.MAJOR]: 1.6,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n // Use the severity property directly\n vitalPointMultiplier = severityMultipliers[hitResult.severity] ?? 1.0;\n }\n\n // Calculate total modifier damage\n const modifierDamage = attackerBonus * vitalPointMultiplier;\n\n // Apply defense reduction\n const defenseReduction = defender.defense * 0.05;\n const totalDamage = Math.max(\n 1,\n baseDamage + modifierDamage - defenseReduction,\n );\n\n // Combine effects from technique and vital point hit\n const effectsApplied = [...technique.effects, ...hitResult.effects];\n\n return {\n baseDamage,\n modifierDamage,\n totalDamage: Math.floor(totalDamage),\n effectsApplied,\n finalDefenderState: {\n health: Math.max(0, defender.health - totalDamage),\n },\n };\n }\n}\n\n/**\n * Creates a standardized CombatResult with all required fields\n * Ensures both 'critical' and 'criticalHit' are present for API compatibility\n */\nexport function createCombatResult(\n partialResult: Partial<CombatResult>,\n): CombatResult {\n // Set default values\n const result: CombatResult = {\n success: partialResult.success ?? false,\n damage: partialResult.damage ?? 0,\n isCritical: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n hit: partialResult.hit ?? partialResult.success ?? false,\n isBlocked: partialResult.isBlocked ?? false,\n vitalPointHit: partialResult.vitalPointHit ?? false,\n effects: partialResult.effects ?? [],\n attacker: partialResult.attacker,\n defender: partialResult.defender,\n technique: partialResult.technique,\n // Always set criticalHit to match critical for consistency\n criticalHit: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n timestamp: Date.now(),\n };\n\n return result;\n}\n\nexport default CombatSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatSystem.js","names":[],"sources":["../../src/systems/CombatSystem.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { getTechniqueById } from \"../data/techniques\";\nimport { BodyRegion, CombatAttackType, CombatState, DamageType, GrappleTarget } from \"../types\";\nimport { VitalPointCategory, VitalPointSeverity } from \"../types/common\";\nimport { BASE_STAMINA_REGEN_RATE } from \"../types/physicsConstants\";\nimport { Technique } from \"../types/technique\";\nimport { calculateDistance3D } from \"../utils/math\";\nimport { calculateBodyRadius } from \"../utils/skeletonScaling\";\nimport type { DefensiveAnimationType } from \"./animation\";\nimport {\n AnimationType,\n calculateSpeedModifierForDamage,\n determineAnimationTypeForTechnique,\n getAdjustedAnimationDuration,\n getAnimationNameForType,\n isWithinHitWindow,\n} from \"./animation\";\nimport { applyDamageToBodyParts } from \"./bodypart/BodyPartDamageIntegration\";\nimport { playerInjuryManager } from \"./bodypart\";\nimport {\n applyBreathingDisruptionFromVitalPoint,\n applyBreathingDisruptionFromTorsoDamage,\n BreathingDisruptionSystem,\n causesBreathingDisruption,\n updateBreathingDisruption,\n} from \"./breathing\";\nimport BalanceSystem from \"./combat/BalanceSystem\";\nimport ConsciousnessSystem from \"./combat/ConsciousnessSystem\";\nimport {\n extractVitalPointCategory,\n isHeadTraumaHit,\n} from \"./combat/painConsciousnessUtils\";\nimport PainResponseSystem, {\n ShockPainEffect,\n} from \"./combat/PainResponseSystem\";\nimport { CombatResult, CombatSystemInterface } from \"./combat/types\";\nimport {\n CollisionDetection,\n KnockbackPhysics,\n physicalReachCalculator,\n type KnockbackConfig,\n} from \"./physics\";\nimport { PlayerState } from \"./player\";\nimport {\n addEffectsToPlayer,\n getEffectModifiers,\n removeExpiredEffects,\n} from \"./PlayerEffectManager\";\nimport { TRIGRAM_TECHNIQUES } from \"./trigram\";\nimport { TrigramSystem } from \"./TrigramSystem\";\nimport { StatusEffect } from \"./types\";\nimport { KoreanTechnique, VitalPointHitResult } from \"./vitalpoint/types\";\nimport { VitalPointSystem } from \"./VitalPointSystem\";\nimport { GrappleSystem } from \"./combat/GrappleSystem\";\n\n/**\n * Enhanced Combat System with Pain Response and Consciousness integration.\n *\n * Integrates realistic pain accumulation and consciousness tracking for\n * progressive combat impairment.\n */\nexport class CombatSystem implements CombatSystemInterface {\n private vitalPointSystem: VitalPointSystem;\n protected trigramSystem: TrigramSystem;\n private painSystem: PainResponseSystem;\n private consciousnessSystem: ConsciousnessSystem;\n private balanceSystem: BalanceSystem;\n private knockbackPhysics: KnockbackPhysics;\n private collisionDetection: CollisionDetection;\n private grappleSystem: GrappleSystem;\n\n // Track shock pain effects per player\n private shockPainEffects: Map<string, ShockPainEffect>;\n // Track last head trauma time per player for consciousness recovery\n private lastHeadTraumaTime: Map<string, number>;\n\n // Vital point severity thresholds\n private readonly SEVERITY_MAJOR_THRESHOLD = 30;\n private readonly SEVERITY_MODERATE_THRESHOLD = 20;\n\n constructor() {\n this.vitalPointSystem = new VitalPointSystem();\n this.trigramSystem = new TrigramSystem();\n this.painSystem = new PainResponseSystem();\n this.consciousnessSystem = new ConsciousnessSystem();\n this.balanceSystem = new BalanceSystem();\n this.knockbackPhysics = new KnockbackPhysics();\n this.collisionDetection = new CollisionDetection();\n this.grappleSystem = new GrappleSystem();\n this.shockPainEffects = new Map();\n this.lastHeadTraumaTime = new Map();\n }\n\n /**\n * Cleanup per-player combat state.\n *\n * Call this when a player permanently leaves the match or when\n * match-level cleanup is performed to avoid unbounded Map growth.\n *\n * @param playerId - ID of the player to cleanup\n *\n * @public\n * @korean 플레이어데이터정리\n */\n public cleanupPlayerData(playerId: string): void {\n this.shockPainEffects.delete(playerId);\n this.lastHeadTraumaTime.delete(playerId);\n }\n\n /**\n * Dispose of all combat system resources.\n *\n * **Korean**: 전투 시스템 자원 정리\n *\n * Cleans up Three.js resources (geometries, raycaster) used by the collision\n * detection system to prevent memory leaks. Should be called when the\n * CombatSystem is destroyed or reinitialized.\n *\n * @public\n * @korean 전투시스템자원정리\n */\n public dispose(): void {\n // Dispose collision detection resources (cached geometries, raycaster)\n this.collisionDetection.dispose();\n }\n\n /**\n * Get the balance system instance for fall checking.\n *\n * @returns BalanceSystem instance\n * @public\n * @korean 균형시스템가져오기\n */\n public getBalanceSystem(): BalanceSystem {\n return this.balanceSystem;\n }\n\n /**\n * Get the consciousness system instance for fall checking.\n *\n * @returns ConsciousnessSystem instance\n * @public\n * @korean 의식시스템가져오기\n */\n public getConsciousnessSystem(): ConsciousnessSystem {\n return this.consciousnessSystem;\n }\n\n /**\n * Get the collision detection system instance.\n *\n * @returns CollisionDetection instance\n * @public\n * @korean 충돌감지시스템가져오기\n */\n public getCollisionDetection(): CollisionDetection {\n return this.collisionDetection;\n }\n\n /**\n * Calculate knockback physics for combat hit.\n *\n * **Korean**: 밀침 계산 (Calculate Knockback)\n *\n * Determines knockback displacement, duration, and fall state based on:\n * - Attack damage amount\n * - Defender's balance state\n * - Defender's stance resistance\n * - Attack direction vector\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param damage - Total damage dealt\n * @returns Knockback information or undefined if no knockback\n *\n * @example\n * ```typescript\n * const knockback = this.calculateKnockback(attacker, defender, 80);\n * // Returns: { displacement: {x:2.5,y:0,z:0}, duration:0.8, recoveryWindow:0.7, shouldFall:false }\n * ```\n *\n * @private\n * @korean 밀침계산\n */\n private calculateKnockback(\n attacker: PlayerState,\n defender: PlayerState,\n damage: number,\n ): CombatResult[\"knockback\"] {\n // Calculate attack direction vector (attacker → defender)\n const attackDirection = new THREE.Vector3(\n defender.position.x - attacker.position.x,\n 0, // Keep knockback on horizontal plane\n defender.position.y - attacker.position.y,\n );\n\n // If attacker and defender are at the exact same position, skip knockback to avoid NaN direction\n if (attackDirection.lengthSq() === 0) {\n return undefined;\n }\n\n attackDirection.normalize();\n\n // Create knockback configuration\n const config: KnockbackConfig = {\n force: damage * 10, // Convert damage to force (arbitrary scaling)\n direction: attackDirection,\n duration: 0, // Will be calculated by physics engine\n balanceState: {\n current: defender.balance,\n max: 100, // Assuming max balance is always 100\n },\n currentStance: defender.currentStance,\n };\n\n // Calculate knockback result\n const result = this.knockbackPhysics.calculateKnockback(config, damage);\n\n // Convert Three.js Vector3 to plain object for serialization\n return {\n displacement: {\n x: result.displacement.x,\n y: result.displacement.y,\n z: result.displacement.z,\n },\n duration: result.duration,\n recoveryWindow: result.recoveryWindow,\n shouldFall: result.shouldFall,\n };\n }\n\n /**\n * Fix: Update resolveAttack to match interface signature and add animation-aware hit detection\n *\n * **Korean**: 공격 해결 (애니메이션 인식)\n *\n * Integrates animation timing and physical reach calculation for reality-based hit detection.\n * Hits only register when:\n * 1. Animation is in hit window (extension phase)\n * 2. Attacker is within effective reach based on limb length and animation\n * 3. Existing accuracy and stance checks pass\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param technique - Technique being executed\n * @param targetedVitalPointId - Optional specific vital point target\n * @param animationContext - Optional animation timing context for reality-based hit detection\n * @returns Combat result with hit/miss and damage information\n */\n resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: {\n animationType: AnimationType;\n currentTime: number;\n },\n ): CombatResult {\n const timestamp = Date.now();\n\n // Check if attacker can execute the technique\n if (!this.canExecuteTechnique(attacker, technique)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Check if attacker is being grappled - cannot attack while controlled\n if (attacker.combatState === CombatState.GRAPPLED) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // === ANIMATION-AWARE HIT DETECTION ===\n // If animation context provided, validate timing and reach\n if (animationContext) {\n const { animationType, currentTime } = animationContext;\n\n // Check if within hit window (extension phase)\n if (!isWithinHitWindow(animationType, currentTime)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Calculate effective reach based on physical attributes and animation\n const attackerPhysical = getArchetypePhysicalAttributes(\n attacker.archetype,\n );\n const reachResult = physicalReachCalculator.calculateReach(\n attackerPhysical,\n animationType,\n currentTime,\n attacker.currentStance,\n technique.reachConfig, // Pass reachConfig for hybrid reach calculation\n );\n\n // Check distance to defender using 3D Euclidean distance\n // Physics-first: Position type is 2D in METERS\n // No conversion needed - positions are already in meters\n const centerToCenterDistance = calculateDistance3D(\n [attacker.position.x, attacker.position.y, 0],\n [defender.position.x, defender.position.y, 0],\n );\n\n // Calculate body radii\n // Note: PhysicalReachCalculator already includes attacker body pivot/offset in reach calculation\n // (shoulder offset for punches, hip rotation for kicks), so we only subtract defender radius\n // to avoid double-counting the attacker's body dimension.\n // 타격 거리 계산: 수비자 몸체 반경만 제외 (공격자 몸체 오프셋은 이미 도달 거리에 포함됨)\n const defenderPhysical = getArchetypePhysicalAttributes(\n defender.archetype,\n );\n const defenderBodyRadius = calculateBodyRadius(defenderPhysical);\n\n // Effective distance = center-to-center minus defender body radius only\n // Reach is calculated from attacker center to striking limb surface (includes body pivot),\n // so we measure from attacker center to defender surface (subtracting defender radius only).\n // 유효 거리 = 중심간 거리 - 수비자 몸체 반경\n const distance = Math.max(0, centerToCenterDistance - defenderBodyRadius);\n\n // If out of reach, miss\n if (distance > reachResult.effectiveReach) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n }\n\n // Fix: Use correct method signature\n const stanceEffectiveness = this.trigramSystem.calculateStanceEffectiveness(\n attacker.currentStance,\n defender.currentStance,\n );\n\n // Calculate base hit chance\n const baseAccuracy = technique.accuracy * stanceEffectiveness;\n const hitRoll = Math.random();\n const hit = hitRoll <= baseAccuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Special handling for grapple techniques\n if (technique.type === CombatAttackType.GRAPPLE) {\n const grappleResult = this.handleGrappleTechnique(\n attacker,\n defender,\n technique,\n timestamp\n );\n\n // Grapples don't deal immediate damage, they establish control\n // Small damage on successful grapple to represent the initial impact\n const grappleDamage = grappleResult.grappleSuccess\n ? technique.damage * 0.3\n : 0;\n\n return {\n hit: grappleResult.grappleSuccess,\n damage: grappleDamage,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker: grappleResult.updatedAttacker,\n defender: grappleResult.updatedDefender,\n success: grappleResult.grappleSuccess,\n isCritical: false,\n isBlocked: false,\n animation: this.getAnimationInfoForTechnique(technique),\n };\n }\n\n // Process vital point hit if targeted (with archetype parameters)\n let vitalPointResult: VitalPointHitResult | null = null;\n if (targetedVitalPointId) {\n vitalPointResult = this.processVitalPointHit(\n targetedVitalPointId,\n technique.damage ?? 15,\n attacker,\n defender,\n );\n }\n\n // Calculate damage using the interface method\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointResult ?? {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n },\n );\n\n // Check for critical hit\n const critRoll = Math.random();\n const isCritical = critRoll <= (technique.critChance ?? 0.1);\n\n // Determine animation information for technique\n const animationInfo = this.getAnimationInfoForTechnique(technique);\n\n // Calculate knockback physics (밀침 물리)\n const knockbackInfo = this.calculateKnockback(\n attacker,\n defender,\n damageResult.totalDamage,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: isCritical,\n vitalPointHit: vitalPointResult?.hit ?? false,\n effects: damageResult.effectsApplied,\n timestamp,\n technique,\n attacker,\n defender,\n success: true,\n isCritical: vitalPointResult?.hit ?? false,\n isBlocked: false,\n targetedVitalPointId, // Pass through the targeted vital point ID\n animation: animationInfo, // Add animation information\n knockback: knockbackInfo, // Add knockback information\n };\n }\n\n /**\n * Get animation information for a technique\n *\n * Determines the skeletal animation to play, duration, and speed modifier\n * based on technique configuration or automatic determination.\n *\n * @param technique - Korean technique to execute\n * @returns Animation information or undefined\n *\n * @private\n * @korean 기술애니메이션정보가져오기\n */\n private getAnimationInfoForTechnique(\n technique: KoreanTechnique,\n ): CombatResult[\"animation\"] {\n // Check if technique has explicit animation config (from Technique interface)\n // KoreanTechnique may not have animation field, so check the technique data\n const techniqueData = this.getTechniqueData(technique);\n\n let animationType;\n let speedModifier;\n\n if (techniqueData?.animation) {\n // Use explicit animation configuration\n animationType = techniqueData.animation.type;\n speedModifier = techniqueData.animation.speedModifier;\n } else {\n // Auto-determine animation from technique characteristics\n const techniqueName =\n technique.name?.english || technique.englishName || \"\";\n const techniqueId = technique.id || \"\";\n const damageType = technique.damageType || \"\";\n\n animationType = determineAnimationTypeForTechnique(\n techniqueName,\n techniqueId,\n damageType,\n );\n\n // Calculate speed modifier based on technique damage\n speedModifier = calculateSpeedModifierForDamage(technique.damage || 15);\n }\n\n // Get base animation name from animation type\n const animationName = getAnimationNameForType(animationType);\n\n // Calculate adjusted duration\n const duration = getAdjustedAnimationDuration(animationName, speedModifier);\n\n // Get Korean technique name for display\n const techniqueDisplayName =\n technique.name?.korean || technique.koreanName || technique.id;\n\n return {\n animationName,\n duration,\n speedModifier,\n techniqueDisplayName,\n };\n }\n\n /**\n * Get Technique data if this KoreanTechnique has an associated Technique definition\n *\n * @param technique - Korean technique\n * @returns Technique data or null\n *\n * @private\n * @korean 기술데이터가져오기\n */\n private getTechniqueData(technique: KoreanTechnique): Technique | null {\n return getTechniqueById(technique.id) ?? null;\n }\n\n /**\n * Handle grapple technique execution.\n *\n * **Korean**: 잡기 기술 처리 (Handle Grapple Technique)\n *\n * Initiates or transitions grapple control based on technique.\n *\n * @param attacker - Player executing grapple\n * @param defender - Target player\n * @param technique - Grapple technique being used\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states with grapple control\n */\n handleGrappleTechnique(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n currentTime: number\n ): {\n updatedAttacker: PlayerState;\n updatedDefender: PlayerState;\n grappleSuccess: boolean;\n } {\n // Determine grapple target from technique\n const target = this.getGrappleTargetFromTechnique(technique);\n\n // Attempt grapple\n const result = this.grappleSystem.attemptGrapple(\n attacker,\n defender,\n target,\n currentTime\n );\n\n if (result.success && result.grappleControl) {\n // Grapple succeeded - update both players\n return {\n updatedAttacker: {\n ...attacker,\n combatState: CombatState.GRAPPLING,\n grappleControl: result.grappleControl,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: {\n ...defender,\n combatState: CombatState.GRAPPLED,\n grappleControl: result.grappleControl,\n },\n grappleSuccess: true,\n };\n }\n\n // Grapple failed\n return {\n updatedAttacker: {\n ...attacker,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: defender,\n grappleSuccess: false,\n };\n }\n\n /**\n * Determine grapple target from technique characteristics.\n *\n * **Korean**: 기술에서 잡기 목표 결정 (Determine Grapple Target from Technique)\n *\n * @private\n */\n private getGrappleTargetFromTechnique(\n technique: KoreanTechnique\n ): GrappleTarget {\n // Check technique name/ID for hints (both English and Korean)\n const techName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techNameKorean = (\n technique.name?.korean ||\n technique.koreanName ||\n \"\"\n );\n const techId = technique.id.toLowerCase();\n\n // Check for wrist/hand - 손목 (sonmok)\n if (\n techName.includes(\"wrist\") ||\n techId.includes(\"wrist\") ||\n techNameKorean.includes(\"손목\")\n ) {\n return GrappleTarget.HAND;\n }\n\n // Check for arm - 팔 (pal)\n if (\n techName.includes(\"arm\") ||\n techId.includes(\"arm\") ||\n techNameKorean.includes(\"팔\")\n ) {\n return GrappleTarget.ARM;\n }\n\n // Check for leg - 다리 (dari)\n if (\n techName.includes(\"leg\") ||\n techId.includes(\"leg\") ||\n techNameKorean.includes(\"다리\")\n ) {\n return GrappleTarget.LEG;\n }\n\n // Check for neck - 목 (mok)\n if (\n techName.includes(\"neck\") ||\n techId.includes(\"neck\") ||\n techNameKorean.includes(\"목\")\n ) {\n return GrappleTarget.NECK;\n }\n\n // Check for both arms - 양팔 (yangpal)\n if (\n techName.includes(\"both\") ||\n techName.includes(\"double\") ||\n techId.includes(\"both\") ||\n techNameKorean.includes(\"양팔\") ||\n techNameKorean.includes(\"쌍\")\n ) {\n return GrappleTarget.BOTH_ARMS;\n }\n\n // Check for torso/body/hip - 몸통 (momtong), 허리 (heori), 몸 (mom)\n if (\n techName.includes(\"torso\") ||\n techName.includes(\"body\") ||\n techName.includes(\"hip\") ||\n techId.includes(\"torso\") ||\n techNameKorean.includes(\"몸통\") ||\n techNameKorean.includes(\"허리\") ||\n techNameKorean.includes(\"몸\")\n ) {\n return GrappleTarget.TORSO;\n }\n\n // Default to arm for unspecified grapples\n return GrappleTarget.ARM;\n }\n\n /**\n * Update grapple state for players over time.\n *\n * **Korean**: 잡기 상태 업데이트 (Update Grapple State)\n *\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed in seconds\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states\n */\n updateGrappleState(\n controller: PlayerState,\n target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): {\n updatedController: PlayerState;\n updatedTarget: PlayerState;\n } {\n if (!controller.grappleControl) {\n // No active grapple\n return {\n updatedController: controller,\n updatedTarget: target,\n };\n }\n\n // Validate grapple control consistency\n if (\n controller.grappleControl.controllerId !== controller.id ||\n controller.grappleControl.targetId !== target.id\n ) {\n // Inconsistent state - break grapple\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Update grapple control\n const updatedControl = this.grappleSystem.updateGrapple(\n controller.grappleControl,\n controller,\n target,\n deltaTime,\n currentTime\n );\n\n if (!updatedControl) {\n // Grapple broken - reset both players\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Deduct stamina from controller\n const staminaCost = updatedControl.staminaCostPerSecond * deltaTime;\n\n return {\n updatedController: {\n ...controller,\n grappleControl: updatedControl,\n stamina: Math.max(0, controller.stamina - staminaCost),\n },\n updatedTarget: {\n ...target,\n grappleControl: updatedControl,\n },\n };\n }\n\n /**\n * Fix: Make applyCombatResult non-static instance method with effect application\n * Enhanced with Pain Response and Consciousness System integration\n */\n applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Start with base result\n const { updatedAttacker, updatedDefender: initialDefender } =\n CombatSystem.applyCombatResult(result, attacker, defender);\n let updatedDefender = initialDefender;\n\n if (result.hit && result.damage > 0) {\n // Determine vital point category and severity from hit result\n const category = this.getVitalPointCategory(result);\n const severity = this.getVitalPointSeverity(result);\n\n // Apply pain from damage\n const { player: defenderWithPain, shockEffect: newShockEffect } =\n this.painSystem.applyPain(\n updatedDefender,\n result.damage,\n severity,\n category,\n );\n updatedDefender = defenderWithPain;\n\n // Store shock pain effect if triggered\n if (newShockEffect) {\n this.shockPainEffects.set(updatedDefender.id, newShockEffect);\n }\n\n // Check for pain overload stun\n if (this.painSystem.shouldTriggerStun(updatedDefender)) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n };\n }\n\n // Apply consciousness damage for head/neurological hits\n if (this.isHeadTrauma(result, category)) {\n updatedDefender = this.consciousnessSystem.applyDamage(\n updatedDefender,\n result.damage,\n category,\n );\n\n // Track head trauma time for recovery gating\n this.lastHeadTraumaTime.set(updatedDefender.id, Date.now());\n\n // Check incapacitation threshold\n if (\n this.consciousnessSystem.isAtIncapacitationThreshold(updatedDefender)\n ) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n // Could add helpless duration tracking here if needed\n };\n }\n }\n\n // Apply pain and consciousness effects to stats\n const currentShockEffect = this.shockPainEffects.get(updatedDefender.id);\n updatedDefender = this.painSystem.applyEffects(\n updatedDefender,\n currentShockEffect,\n );\n updatedDefender = this.consciousnessSystem.applyEffects(updatedDefender);\n\n // Apply balance disruption from the hit\n // Determine body region from vital point or use default\n const bodyRegion = this.getBodyRegionFromResult(result);\n updatedDefender = this.balanceSystem.disruptBalance(\n updatedDefender,\n result.damage,\n bodyRegion,\n );\n\n // Apply breathing disruption for torso strikes\n if (result.vitalPointHit && result.targetedVitalPointId) {\n // Vital point strike to torso\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n if (vitalPoint && causesBreathingDisruption(vitalPoint.id)) {\n updatedDefender = applyBreathingDisruptionFromVitalPoint(\n updatedDefender,\n vitalPoint,\n Date.now(),\n );\n }\n } else if (bodyRegion === BodyRegion.TORSO && result.damage >= 10) {\n // General torso damage (non-vital point hits)\n // Apply breathing disruption if damage is significant enough\n // Note: Solar plexus detection is best-effort; vital point system is primary mechanism\n const techniqueId = (result.technique?.id ?? \"\").toLowerCase();\n const isSolarPlexusArea = \n techniqueId.includes(\"solar\") ||\n techniqueId.includes(\"myeongchi\");\n updatedDefender = applyBreathingDisruptionFromTorsoDamage(\n updatedDefender,\n result.damage,\n isSolarPlexusArea,\n Date.now(),\n );\n }\n }\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determines if a hit caused head trauma (affects consciousness).\n *\n * @param result - Combat result\n * @param category - Vital point category\n * @returns True if hit should affect consciousness\n */\n private isHeadTrauma(\n result: CombatResult,\n category?: VitalPointCategory,\n ): boolean {\n return isHeadTraumaHit(result, category);\n }\n\n /**\n * Extracts vital point category from combat result.\n *\n * @param result - Combat result\n * @returns Vital point category if available\n */\n private getVitalPointCategory(\n result: CombatResult,\n ): VitalPointCategory | undefined {\n return extractVitalPointCategory(result);\n }\n\n /**\n * Extracts vital point severity from combat result.\n *\n * @param result - Combat result\n * @returns Vital point severity if critical hit\n */\n private getVitalPointSeverity(\n result: CombatResult,\n ): VitalPointSeverity | undefined {\n if (result.vitalPointHit) {\n if (result.isCritical) {\n return VitalPointSeverity.CRITICAL;\n }\n if (result.damage > this.SEVERITY_MAJOR_THRESHOLD) {\n return VitalPointSeverity.MAJOR;\n }\n if (result.damage > this.SEVERITY_MODERATE_THRESHOLD) {\n return VitalPointSeverity.MODERATE;\n }\n return VitalPointSeverity.MINOR;\n }\n return undefined;\n }\n\n /**\n * Determines body region from combat result for balance disruption.\n *\n * Maps vital points to body regions for balance system integration.\n * Uses string matching on vital point IDs as a pragmatic heuristic since\n * VitalPoint interface doesn't currently include a bodyRegion property.\n *\n * Future improvement: Add bodyRegion: BodyRegion to VitalPoint interface\n * for more robust region mapping without string pattern matching.\n *\n * @param result - Combat result\n * @returns Body region that was struck\n * @private\n * @korean 신체부위결정\n */\n private getBodyRegionFromResult(result: CombatResult): BodyRegion {\n // If we have a targeted vital point, try to determine region\n if (result.targetedVitalPointId) {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n\n if (vitalPoint) {\n const pointId = vitalPoint.id.toLowerCase();\n\n // Check for leg/lower body targets\n if (\n pointId.includes(\"leg\") ||\n pointId.includes(\"knee\") ||\n pointId.includes(\"ankle\") ||\n pointId.includes(\"thigh\")\n ) {\n return BodyRegion.LEFT_LEG; // Generic leg for balance disruption\n }\n\n // Check for head targets\n if (\n pointId.includes(\"head\") ||\n pointId.includes(\"temple\") ||\n pointId.includes(\"jaw\") ||\n pointId.includes(\"nose\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Check for arm targets\n if (\n pointId.includes(\"arm\") ||\n pointId.includes(\"elbow\") ||\n pointId.includes(\"wrist\") ||\n pointId.includes(\"shoulder\")\n ) {\n return BodyRegion.LEFT_ARM;\n }\n }\n }\n\n // Default to torso for general strikes\n return BodyRegion.TORSO;\n }\n\n /**\n * Updates player states for recovery (pain dissipation, consciousness recovery, balance recovery).\n * Call this regularly in game loop.\n *\n * @param player - Player to update\n * @param deltaTime - Time elapsed since last update (ms)\n * @returns Updated player state\n */\n applyRecovery(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = player;\n\n // Apply pain recovery\n updatedPlayer = this.painSystem.applyDissipation(updatedPlayer, deltaTime);\n\n // Apply consciousness recovery (only if enough time since head trauma)\n const lastTrauma = this.lastHeadTraumaTime.get(player.id);\n updatedPlayer = this.consciousnessSystem.applyRecovery(\n updatedPlayer,\n deltaTime,\n lastTrauma,\n );\n\n // Apply balance recovery\n updatedPlayer = this.balanceSystem.applyRecovery(updatedPlayer, deltaTime);\n\n // Clean up expired shock pain effects\n const shockEffect = this.shockPainEffects.get(player.id);\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed >= shockEffect.duration) {\n this.shockPainEffects.delete(player.id);\n }\n }\n\n return updatedPlayer;\n }\n\n /**\n * Static version for backwards compatibility with comprehensive effect application\n * Enhanced with Pain Response and Consciousness System integration\n * Updated to apply damage to body parts for 8-body-part health visualization\n */\n static applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Apply damage and effects\n let updatedDefender = defender;\n let updatedAttacker = attacker;\n\n if (result.hit) {\n // Determine body region from technique or use random distribution\n const bodyRegion = CombatSystem.getBodyRegionFromTechnique(\n result.technique,\n );\n\n // Apply damage to body parts (this also updates aggregate health)\n updatedDefender = applyDamageToBodyParts(\n defender,\n result.damage,\n bodyRegion,\n );\n\n // Record injury for trauma visualization (외상 시각화)\n // Use per-player injury tracking to prevent mixing injuries between characters\n // Automatically tracks bruising, cuts, and blood effects based on damage type\n // Records injury even without specific technique (defaults to BRUISE)\n if (result.damage > 0) {\n const defenderInjuryIntegration = playerInjuryManager.getIntegrationForPlayer(defender.id);\n defenderInjuryIntegration.recordCombatDamage({\n damage: result.damage,\n bodyRegion,\n damageType: (result.technique?.damageType as DamageType) || undefined,\n });\n }\n\n // Update tracking stats (applyDamageToBodyParts already sets health)\n updatedDefender = {\n ...updatedDefender,\n totalDamageReceived:\n updatedDefender.totalDamageReceived + result.damage,\n hitsTaken: updatedDefender.hitsTaken + 1,\n };\n\n // Apply status effects from vital point hit\n if (result.effects && result.effects.length > 0) {\n updatedDefender = addEffectsToPlayer(updatedDefender, result.effects);\n }\n\n // Track vital point hits\n if (result.vitalPointHit) {\n updatedAttacker = {\n ...updatedAttacker,\n vitalPointHits: attacker.vitalPointHits + 1,\n };\n }\n\n // Track perfect strikes (high accuracy)\n if (result.isCritical) {\n updatedAttacker = {\n ...updatedAttacker,\n perfectStrikes: attacker.perfectStrikes + 1,\n };\n }\n }\n\n // Apply technique costs to attacker\n updatedAttacker = {\n ...updatedAttacker,\n ki: Math.max(0, attacker.ki - 5),\n stamina: Math.max(0, attacker.stamina - 10),\n totalDamageDealt:\n attacker.totalDamageDealt + (result.hit ? result.damage : 0),\n hitsLanded: attacker.hitsLanded + (result.hit ? 1 : 0),\n };\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determine body region from technique name or type\n *\n * **Korean**: 기술에서 신체 영역 결정\n *\n * @param technique - The technique used in the attack\n * @returns Body region to apply damage to\n */\n private static getBodyRegionFromTechnique(\n technique?: KoreanTechnique,\n ): BodyRegion {\n if (!technique) {\n // Default to torso if no technique specified\n return BodyRegion.TORSO;\n }\n\n const techniqueName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techniqueId = technique.id?.toLowerCase() || \"\";\n\n // Head/face targeting techniques\n if (\n techniqueName.includes(\"head\") ||\n techniqueName.includes(\"temple\") ||\n techniqueName.includes(\"jaw\") ||\n techniqueName.includes(\"face\") ||\n techniqueId.includes(\"head\") ||\n techniqueId.includes(\"skull\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Neck targeting techniques\n if (\n techniqueName.includes(\"neck\") ||\n techniqueName.includes(\"throat\") ||\n techniqueName.includes(\"choke\") ||\n techniqueId.includes(\"neck\")\n ) {\n return BodyRegion.NECK;\n }\n\n // Leg targeting techniques\n if (\n techniqueName.includes(\"kick\") ||\n techniqueName.includes(\"sweep\") ||\n techniqueName.includes(\"leg\") ||\n techniqueName.includes(\"knee\") ||\n techniqueId.includes(\"kick\") ||\n techniqueId.includes(\"leg\")\n ) {\n // Randomly choose left or right leg\n return Math.random() < 0.5 ? BodyRegion.LEFT_LEG : BodyRegion.RIGHT_LEG;\n }\n\n // Arm targeting techniques\n if (\n techniqueName.includes(\"arm\") ||\n techniqueName.includes(\"shoulder\") ||\n techniqueName.includes(\"elbow\") ||\n techniqueId.includes(\"arm\")\n ) {\n // Randomly choose left or right arm\n return Math.random() < 0.5 ? BodyRegion.LEFT_ARM : BodyRegion.RIGHT_ARM;\n }\n\n // Punch/strike techniques typically target torso\n if (\n techniqueName.includes(\"punch\") ||\n techniqueName.includes(\"strike\") ||\n techniqueName.includes(\"jab\") ||\n techniqueName.includes(\"cross\") ||\n techniqueName.includes(\"hook\") ||\n techniqueName.includes(\"uppercut\")\n ) {\n // Mix of torso and head for punches\n return Math.random() < 0.7 ? BodyRegion.TORSO : BodyRegion.HEAD;\n }\n\n // Body/core targeting techniques\n if (\n techniqueName.includes(\"body\") ||\n techniqueName.includes(\"solar\") ||\n techniqueName.includes(\"liver\") ||\n techniqueName.includes(\"ribs\") ||\n techniqueName.includes(\"abdomen\") ||\n techniqueId.includes(\"torso\") ||\n techniqueId.includes(\"core\")\n ) {\n return BodyRegion.TORSO;\n }\n\n // Default to torso for unmatched techniques\n return BodyRegion.TORSO;\n }\n\n /**\n * Fix: Add missing getAvailableTechniques method required by interface\n */\n getAvailableTechniques(player: PlayerState): readonly KoreanTechnique[] {\n const allTechniques = TRIGRAM_TECHNIQUES[player.currentStance] ?? [];\n\n // Filter techniques based on available resources using canExecuteTechnique\n return allTechniques.filter((technique) =>\n this.canExecuteTechnique(player, technique as KoreanTechnique),\n ) as readonly KoreanTechnique[];\n }\n\n /**\n * Check if attacker can execute technique\n */\n private canExecuteTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n ): boolean {\n return (\n player.ki >= technique.kiCost &&\n player.stamina >= technique.staminaCost &&\n player.currentStance === technique.stance &&\n !player.isStunned\n );\n }\n\n /**\n * Static methods for backwards compatibility\n */\n static resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const instance = new CombatSystem();\n return instance.executeAttack(attacker, defender, technique);\n }\n\n /**\n * Check if a player is defeated\n */\n isPlayerDefeated(player: PlayerState): boolean {\n return player.health <= 0 || player.consciousness <= 0;\n }\n\n /**\n * Update player state over time with effect management\n */\n updatePlayerState(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = { ...player };\n\n // Remove expired effects first\n updatedPlayer = removeExpiredEffects(updatedPlayer);\n\n // Get effect modifiers for resource regeneration\n const effectModifiers = getEffectModifiers(updatedPlayer);\n\n // Apply natural regeneration with effect modifiers\n const regenRate = deltaTime / 1000; // Convert to seconds\n\n // Ki regeneration (slower during combat) - affected by effects\n if (updatedPlayer.ki < updatedPlayer.maxKi) {\n updatedPlayer.ki = Math.min(\n updatedPlayer.maxKi,\n updatedPlayer.ki + regenRate * 2 * effectModifiers.kiRegen,\n );\n }\n\n // Stamina regeneration - affected by effects and breathing disruption\n // Uses BASE_STAMINA_REGEN_RATE constant for better gameplay\n // Allows players to move around and attack frequently without running out\n // 체력 재생 속도 - 더 활발한 전투를 위해\n if (updatedPlayer.stamina < updatedPlayer.maxStamina) {\n const baseStaminaRegen = regenRate * BASE_STAMINA_REGEN_RATE * effectModifiers.staminaRegen;\n // Apply breathing disruption system's stamina regen modifier\n const modifiedStaminaRegen =\n BreathingDisruptionSystem.calculateStaminaRegen(\n updatedPlayer,\n baseStaminaRegen,\n );\n updatedPlayer.stamina = Math.min(\n updatedPlayer.maxStamina,\n updatedPlayer.stamina + modifiedStaminaRegen,\n );\n }\n\n // Health regeneration (very slow)\n if (\n updatedPlayer.health < updatedPlayer.maxHealth &&\n updatedPlayer.health > 0\n ) {\n updatedPlayer.health = Math.min(\n updatedPlayer.maxHealth,\n updatedPlayer.health + regenRate * 0.5,\n );\n }\n\n // Update breathing disruption effects (gradual recovery if torso health > 50%)\n updatedPlayer = updateBreathingDisruption(\n updatedPlayer,\n deltaTime,\n Date.now(),\n );\n\n // Clear temporary combat states\n const currentTime = Date.now();\n if (\n updatedPlayer.lastActionTime &&\n currentTime - updatedPlayer.lastActionTime > updatedPlayer.recoveryTime\n ) {\n updatedPlayer.isStunned = false;\n updatedPlayer.isCountering = false;\n }\n\n return updatedPlayer;\n }\n\n /**\n * Get combat statistics\n */\n getCombatStatistics(player: PlayerState): {\n healthPercent: number;\n kiPercent: number;\n staminaPercent: number;\n balancePercent: number;\n } {\n return {\n healthPercent: (player.health / player.maxHealth) * 100,\n kiPercent: (player.ki / player.maxKi) * 100,\n staminaPercent: (player.stamina / player.maxStamina) * 100,\n balancePercent: player.balance,\n };\n }\n\n /**\n * Fix: Integrate processVitalPointHit into the combat system with archetype parameters\n */\n private processVitalPointHit(\n vitalPointId: string,\n _baseDamage: number, // Unused but kept for interface compatibility\n attacker: PlayerState,\n defender: PlayerState,\n ): VitalPointHitResult {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(vitalPointId);\n\n if (!vitalPoint) {\n return {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n };\n }\n\n // Use VitalPointSystem's processHit with full archetype support\n return this.vitalPointSystem.processHit(\n vitalPoint.position, // Use vital point position for hit calculation\n { width: 10, height: 10 }, // Standard hit box\n vitalPointId, // Targeted vital point\n undefined, // Use current hour from system\n attacker.archetype, // Attacker archetype for offensive modifiers\n defender.archetype, // Defender archetype for defensive modifiers\n );\n }\n\n /**\n * Process defensive action and determine animation type.\n *\n * Determines the appropriate defensive animation based on:\n * - Defender's balance and stamina (defensive power)\n * - Attacker's technique power\n * - Combat readiness state\n *\n * **Korean**: 방어 행동 처리\n *\n * @param defender - Defending player state\n * @param attacker - Attacking player state (unused but kept for future enhancements)\n * @param attackPower - Power of the incoming attack\n * @returns Defensive animation type to play (parry_deflect, block_success, or guard_break)\n *\n * @example\n * ```typescript\n * const animType = combatSystem.processDefensiveAction(\n * defender,\n * attacker,\n * technique.damage\n * );\n * // Returns: 'parry_deflect', 'block_success', or 'guard_break'\n * ```\n *\n * @public\n * @korean 방어행동처리\n */\n public processDefensiveAction(\n defender: PlayerState,\n _attacker: PlayerState,\n attackPower: number,\n ): Exclude<DefensiveAnimationType, \"guard_recovery\"> {\n // Guard Break: Check balance threshold first (highest priority condition)\n if (defender.balance < 30) {\n return \"guard_break\";\n }\n\n // Calculate defensive power based on balance and stamina\n // Balance represents physical stability (0-100)\n // Stamina represents energy reserves (0-100)\n const balanceFactor = defender.balance / 100;\n const staminaFactor = defender.stamina / 100;\n\n // Apply defensive modifiers from effects\n const effectModifiers = getEffectModifiers(defender);\n const defenseMultiplier = effectModifiers.defense;\n\n // Defense stat provides moderate bonus (normalized to 0.5-1.5 range for typical 5-15 defense)\n const defenseBonus = Math.max(0.5, Math.min(1.5, defender.defense / 10));\n\n // Calculate final defensive power\n const defensePower =\n balanceFactor * staminaFactor * 100 * defenseMultiplier * defenseBonus;\n\n // Determine defensive outcome based on power ratio\n // Parry: Strong defense (1.8x attack power or more) - Perfect deflection\n if (defensePower >= attackPower * 1.8) {\n return \"parry_deflect\";\n }\n // Block Success: Adequate defense (1.0x to 1.8x attack power) - Absorb impact\n else if (defensePower >= attackPower) {\n return \"block_success\";\n }\n // Guard Break: Defense insufficient against powerful attack (<60% of attack power)\n else if (defensePower < attackPower * 0.6) {\n return \"guard_break\";\n }\n // Block Success: Marginal defense (60-100% of attack power) - barely hold\n else {\n return \"block_success\";\n }\n }\n\n /**\n * Execute attack with technique\n */\n protected executeAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const hitRoll = Math.random();\n const accuracy = technique.accuracy || 0.8;\n const hit = hitRoll <= accuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Calculate damage using the interface method\n const vitalPointHit: VitalPointHitResult = {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR, // Use the enum directly\n };\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointHit,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: Math.random() < (technique.critChance || 0.1),\n vitalPointHit: false,\n effects: damageResult.effectsApplied,\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: true,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n /**\n * Fix: Add missing calculateDamage method required by interface\n */\n calculateDamage(\n technique: KoreanTechnique,\n attacker: PlayerState,\n defender: PlayerState,\n hitResult: VitalPointHitResult,\n ): {\n baseDamage: number;\n modifierDamage: number;\n totalDamage: number;\n effectsApplied: readonly StatusEffect[];\n finalDefenderState?: Partial<PlayerState>;\n } {\n // Calculate base damage from technique\n const baseDamage = technique.damage || 15;\n\n // Apply attacker modifiers\n const attackerBonus = attacker.attackPower * 0.1;\n\n // Apply vital point modifiers if hit\n let vitalPointMultiplier = 1.0;\n if (hitResult.hit && hitResult.vitalPointHit) {\n // Use the correct property name\n const severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 1.1,\n [VitalPointSeverity.MODERATE]: 1.3,\n [VitalPointSeverity.MAJOR]: 1.6,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n // Use the severity property directly\n vitalPointMultiplier = severityMultipliers[hitResult.severity] ?? 1.0;\n }\n\n // Calculate total modifier damage\n const modifierDamage = attackerBonus * vitalPointMultiplier;\n\n // Apply defense reduction\n const defenseReduction = defender.defense * 0.05;\n const totalDamage = Math.max(\n 1,\n baseDamage + modifierDamage - defenseReduction,\n );\n\n // Combine effects from technique and vital point hit\n const effectsApplied = [...technique.effects, ...hitResult.effects];\n\n return {\n baseDamage,\n modifierDamage,\n totalDamage: Math.floor(totalDamage),\n effectsApplied,\n finalDefenderState: {\n health: Math.max(0, defender.health - totalDamage),\n },\n };\n }\n}\n\n/**\n * Creates a standardized CombatResult with all required fields\n * Ensures both 'critical' and 'criticalHit' are present for API compatibility\n */\nexport function createCombatResult(\n partialResult: Partial<CombatResult>,\n): CombatResult {\n // Set default values\n const result: CombatResult = {\n success: partialResult.success ?? false,\n damage: partialResult.damage ?? 0,\n isCritical: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n hit: partialResult.hit ?? partialResult.success ?? false,\n isBlocked: partialResult.isBlocked ?? false,\n vitalPointHit: partialResult.vitalPointHit ?? false,\n effects: partialResult.effects ?? [],\n attacker: partialResult.attacker,\n defender: partialResult.defender,\n technique: partialResult.technique,\n // Always set criticalHit to match critical for consistency\n criticalHit: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n timestamp: Date.now(),\n };\n\n return result;\n}\n\nexport default CombatSystem;\n"],"mappings":"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"}
|