blacktrigram 0.7.68 → 0.7.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +11 -11
@@ -1 +1 @@
1
- {"version":3,"file":"useCombatActions.js","names":[],"sources":["../../../../../src/components/screens/combat/hooks/useCombatActions.ts"],"sourcesContent":["/**\n * useCombatActions Hook - Combat Action Handlers\n *\n * Custom hook for managing combat action handlers.\n * Consolidates player attack, defend, technique, and AI action logic.\n *\n * Performance:\n * - Memoized callbacks to prevent recreation\n * - Centralized action logic for better maintainability\n * - Reduces main component complexity\n *\n * @param config Combat action configuration\n * @returns Combat action handlers\n *\n * @example\n * ```typescript\n * const {\n * handleAttack,\n * handleDefend,\n * handleTechniqueExecute,\n * handleStanceSwitch,\n * handleAIAttack,\n * handleAIDefend,\n * handleAITechnique,\n * moveAIPlayer\n * } = useCombatActions({\n * validPlayers,\n * playerPositions,\n * combatState,\n * combatActions,\n * combatSystem,\n * onPlayerUpdate,\n * addCombatMessage,\n * addHitEffect,\n * arenaBounds\n * });\n * ```\n */\n\nimport { PlayerState } from \"@/systems\";\nimport {\n AnimationType,\n getAnimationHitTiming,\n type AnimationState,\n} from \"@/systems/animation\";\nimport { movementPenaltySystem } from \"@/systems/bodypart\";\nimport { clampToArenaBounds, type PhysicsArenaBounds } from \"@/types/PhysicsTypes\";\nimport {\n checkForFall,\n getFallTypeName,\n} from \"@/systems/combat/FallIntegration\";\nimport type { CombatResult } from \"@/systems/combat/types\";\nimport { CombatSystem } from \"@/systems/CombatSystem\";\nimport { HitEffectType } from \"@/systems/effects\";\nimport { KnockbackPhysics } from \"@/systems/physics\";\nimport { StanceManager } from \"@/systems/trigram\";\nimport { KoreanTechniquesSystem } from \"@/systems/trigram/KoreanTechniques\";\nimport { getVitalPointById } from \"@/systems/vitalpoint/KoreanVitalPoints\";\nimport { KoreanTechnique } from \"@/systems/vitalpoint/types\";\nimport { Position, Technique, TrigramStance, BodyRegion } from \"@/types\";\nimport { Injury, InjuryType } from \"@/types/injury\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { AttackIntensity } from \"./useCombatAudio\";\nimport { CombatActions, CombatScreenState } from \"./useCombatState\";\n\n/**\n * Hit position variation range for randomizing strike heights\n * Produces ±0.2 absolute units (±10% of 2.0 character height)\n */\nconst HIT_Y_VARIATION_RANGE = 0.4;\n\nexport const SCREEN_SHAKE_FRAME_INTERVAL_MS = 50;\nexport const SCREEN_SHAKE_FRAME_COUNT = 5;\n\n/**\n * Clears every timeout in a tracked set and empties the set afterwards.\n *\n * @param timeoutIds Timeout identifiers to clear and remove\n */\nfunction clearTimeoutSet(timeoutIds: Set<ReturnType<typeof setTimeout>>): void {\n timeoutIds.forEach((timeoutId) => {\n clearTimeout(timeoutId);\n });\n timeoutIds.clear();\n}\n\n/**\n * Calculate randomized hit position based on defender position\n * Adds vertical variation to simulate different strike heights\n *\n * @param defenderPos - Position of the defender being struck\n * @returns Hit position with randomized Y coordinate\n */\nfunction calculateHitPosition(defenderPos: Position): { x: number; y: number } {\n const hitYVariation = (Math.random() - 0.5) * HIT_Y_VARIATION_RANGE; // ±0.2 units\n return {\n x: defenderPos.x,\n y: Math.max(0.3, Math.min(1.8, defenderPos.y + hitYVariation)),\n };\n}\n\n/**\n * Determine injury type from combat result and technique damage type\n * 전투 결과와 기술 피해 유형으로 부상 유형 결정\n *\n * @param result - Combat result with damage information\n * @param technique - Technique that was executed\n * @returns InjuryType to create\n */\nfunction determineInjuryType(\n result: CombatResult,\n technique: KoreanTechnique,\n): InjuryType {\n if (technique.damageType === \"slashing\") {\n return result.damage > 20 ? InjuryType.LACERATION : InjuryType.CUT;\n }\n\n if (result.damage > 25) {\n return InjuryType.BRUISE;\n }\n\n if (result.damage > 15) {\n return InjuryType.BRUISE;\n }\n\n return InjuryType.BRUISE;\n}\n\n/**\n * Map body region to 3D position offset on character model\n * 신체 부위를 캐릭터 모델의 3D 위치 오프셋으로 매핑\n *\n * @param region - Body region that was hit\n * @returns Position offset [x, y, z] relative to character center\n */\nfunction getBodyRegionPosition(region: BodyRegion): [number, number, number] {\n switch (region) {\n case BodyRegion.HEAD:\n return [0, 1.6, 0];\n case BodyRegion.NECK:\n return [0, 1.3, 0];\n case BodyRegion.TORSO:\n case BodyRegion.CORE:\n return [0, 0.8, 0];\n case BodyRegion.LEFT_ARM:\n return [-0.4, 1.0, 0];\n case BodyRegion.RIGHT_ARM:\n return [0.4, 1.0, 0];\n case BodyRegion.LEFT_LEG:\n return [-0.2, 0.2, 0];\n case BodyRegion.RIGHT_LEG:\n return [0.2, 0.2, 0];\n default:\n return [0, 0.8, 0]; // Default to torso\n }\n}\n\n/**\n * Create injury from combat damage result\n * 전투 피해 결과로부터 부상 생성\n *\n * @param result - Combat result with damage details\n * @param technique - Technique that caused the damage\n * @param defenderHealth - Current defender health after damage (0-100 scale)\n * @param targetPlayerIndex - Index of the player who was hit (0 or 1)\n * @returns Injury object for visualization\n */\nfunction createInjuryFromDamage(\n result: CombatResult,\n technique: KoreanTechnique,\n defenderHealth: number,\n targetPlayerIndex: number,\n): Injury {\n const bodyRegion = BodyRegion.TORSO; // TODO: Extract from result when available\n\n let injuryType = determineInjuryType(result, technique);\n\n const isLowHealth = defenderHealth <= 30; // 30% health threshold\n const isSevereDamage = result.damage >= 25; // Severe damage threshold\n if (isLowHealth && isSevereDamage && injuryType !== InjuryType.FRACTURE) {\n injuryType = InjuryType.FRACTURE;\n }\n\n const severity = Math.min(1.0, result.damage / 30);\n\n const basePosition = getBodyRegionPosition(bodyRegion);\n\n const randomOffset: [number, number, number] = [\n (Math.random() - 0.5) * 0.1,\n (Math.random() - 0.5) * 0.1,\n (Math.random() - 0.5) * 0.1,\n ];\n\n const position: [number, number, number] = [\n basePosition[0] + randomOffset[0],\n basePosition[1] + randomOffset[1],\n basePosition[2] + randomOffset[2],\n ];\n\n return {\n id: `injury_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n region: bodyRegion,\n type: injuryType,\n position,\n severity,\n hitCount: 1,\n timestamp: Date.now(),\n playerId: targetPlayerIndex === 0 ? \"player\" : \"enemy\",\n };\n}\n\n/**\n * Apply knockback displacement to defender position\n *\n * **Korean**: 밀침 적용 (Apply Knockback)\n *\n * Updates the defender's position based on knockback physics calculation.\n * The knockback displacement is applied in the direction of the attack vector\n * (attacker → defender), respecting arena boundaries.\n *\n * **Physics-First Architecture**: Both positions and knockback displacement\n * are in meters. Arena bounds are centered at origin (0, 0) with extent\n * ±worldWidthMeters/2 in X and ±worldDepthMeters/2 in Z.\n *\n * @param result - Combat result containing knockback data (in meters)\n * @param defenderPos - Current defender position (in meters)\n * @param arenaBounds - Arena boundary limits with meter dimensions\n * @returns Updated defender position after knockback (in meters)\n *\n * @example\n * ```typescript\n * // 10m × 7.5m arena, player at x=2m knocked back 2.5m to right\n * const newPosition = applyKnockbackDisplacement(\n * result, // knockback.displacement.x = 2.5m\n * { x: 2, y: 0 }, // Current position: 2m from center\n * { worldWidthMeters: 10, worldDepthMeters: 7.5, ... }\n * );\n * // Returns: { x: 4.5, y: 0 } (2m + 2.5m = 4.5m, within ±5m boundary)\n *\n * // Same knockback but would exceed boundary\n * const clampedPosition = applyKnockbackDisplacement(\n * result, // knockback.displacement.x = 2.5m\n * { x: 4, y: 0 }, // Current position: 4m from center\n * { worldWidthMeters: 10, worldDepthMeters: 7.5, ... }\n * );\n * // Returns: { x: 5, y: 0 } (4m + 2.5m = 6.5m, clamped to +5m boundary)\n * ```\n */\nfunction applyKnockbackDisplacement(\n result: CombatResult,\n defenderPos: Position,\n arenaBounds: PhysicsArenaBounds,\n): Position {\n if (!result.knockback) {\n return defenderPos;\n }\n\n const newPos = {\n x: defenderPos.x + result.knockback.displacement.x,\n y: defenderPos.y + result.knockback.displacement.z,\n };\n\n return clampToArenaBounds(newPos, arenaBounds);\n}\n\nexport interface UseCombatActionsConfig {\n readonly validPlayers: readonly [PlayerState, PlayerState];\n readonly playerPositions: readonly [Position, Position];\n readonly combatState: CombatScreenState;\n readonly combatActions: CombatActions;\n readonly combatSystem: CombatSystem;\n readonly onPlayerUpdate: (\n playerIndex: number,\n updates: Partial<PlayerState>,\n ) => void;\n readonly onPlayerPositionUpdate?: (\n playerIndex: number,\n position: Position,\n ) => void;\n readonly onLateralityUpdate?: (\n playerIndex: number,\n laterality: \"left\" | \"right\",\n ) => void;\n readonly onInjuryCreated?: (\n injury: Injury,\n targetPlayerIndex: number,\n ) => void;\n readonly addCombatMessage: (korean: string, english: string) => void;\n readonly addHitEffect: (\n type: HitEffectType,\n position: Position,\n intensity?: number,\n ) => void;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly combatAudio?: {\n readonly playAttackSound: (intensity?: AttackIntensity) => Promise<void>;\n readonly playHitSound: (damage: number) => Promise<void>;\n readonly playBoneImpactSound: (options: {\n region?: import(\"../../../../audio/types\").AudioBodyRegion;\n intensity?: import(\"../../../../audio/types\").ImpactIntensity;\n damage?: number;\n remainingHealth?: number;\n vitalPoint?: boolean;\n hitPosition?: { x: number; y: number; z?: number };\n }) => Promise<void>;\n readonly playBlockSound: (guardBroken?: boolean) => Promise<void>;\n readonly playDodgeSound: () => Promise<void>;\n readonly playStanceChangeSound: () => Promise<void>;\n readonly playSpecialTechniqueSound: () => Promise<void>;\n };\n readonly playerAnimations?: {\n readonly player1: {\n readonly transitionTo: (state: AnimationState) => boolean;\n };\n readonly player2: {\n readonly transitionTo: (state: AnimationState) => boolean;\n };\n };\n}\n\nexport interface UseCombatActionsReturn {\n readonly handleAttack: (technique?: Technique) => void;\n readonly handleDefend: () => void;\n readonly handleTechniqueExecute: () => void;\n readonly handleStanceSwitch: (stance: TrigramStance) => void;\n readonly handleStanceSideSwitch: (playerIndex: 0 | 1) => void;\n readonly handleAIAttack: (\n technique?: KoreanTechnique,\n targetVitalPoint?: string,\n ) => void;\n readonly handleAIDefend: () => void;\n readonly handleAITechnique: (\n technique?: KoreanTechnique,\n targetVitalPoint?: string,\n ) => void;\n readonly moveAIPlayer: (targetPos: Position) => void;\n}\n\n/**\n * Helper function to convert Technique to KoreanTechnique format\n * @param technique - The technique to convert\n * @param stance - Current player stance\n * @returns KoreanTechnique compatible with CombatSystem\n */\nfunction convertTechniqueToKorean(\n technique: Technique,\n stance: TrigramStance,\n): KoreanTechnique {\n return {\n id: technique.id,\n name: {\n korean: technique.name.korean,\n english: technique.name.english,\n romanized: technique.name.romanized ?? \"\",\n },\n koreanName: technique.name.korean,\n englishName: technique.name.english,\n romanized: technique.name.romanized ?? \"\",\n description: {\n korean: technique.description.korean,\n english: technique.description.english,\n },\n stance: technique.requiredStance ?? stance,\n type: \"attack\",\n damageType: technique.damageType,\n damage: (technique.damage.min + technique.damage.max) / 2, // Use average damage\n kiCost: technique.kiCost,\n staminaCost: technique.staminaCost,\n accuracy: 0.85, // Default accuracy\n reachConfig: {\n bodyPart: \"arm\" as const,\n techniqueType: \"punch\" as const,\n baseExtension: 0.9,\n },\n executionTime: technique.animationDuration ?? 400,\n recoveryTime: 300,\n critChance: technique.criticalChance ?? 0.1,\n critMultiplier: 1.5,\n effects: [],\n };\n}\n\n/**\n * Custom hook for combat action handlers\n */\nexport function useCombatActions(\n config: UseCombatActionsConfig,\n): UseCombatActionsReturn {\n const {\n validPlayers,\n playerPositions,\n combatState,\n combatActions,\n combatSystem,\n onPlayerUpdate,\n onLateralityUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n arenaBounds,\n combatAudio,\n } = config;\n\n const player1KnockbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const player2KnockbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const screenShakeTimeoutsRef = useRef<Set<ReturnType<typeof setTimeout>>>(\n new Set(),\n );\n\n useEffect(() => {\n const screenShakeTimeouts = screenShakeTimeoutsRef.current;\n return () => {\n clearTimeoutSet(screenShakeTimeouts);\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n if (player2KnockbackTimeoutRef.current) {\n clearTimeout(player2KnockbackTimeoutRef.current);\n }\n };\n }, []);\n\n const handleAttack = useCallback(\n (technique?: Technique) => {\n if (\n combatState.isExecutingTechnique ||\n !combatState.roundStarted ||\n combatState.roundEnded\n )\n return;\n\n const player = validPlayers[0];\n const currentStance = player.currentStance;\n const archetype = player.archetype;\n\n let attackTechnique: KoreanTechnique;\n\n if (technique) {\n attackTechnique = convertTechniqueToKorean(technique, currentStance);\n } else {\n const availableTechniques =\n KoreanTechniquesSystem.getAllAvailableTechniques(\n currentStance,\n archetype,\n );\n\n if (availableTechniques.length === 0) {\n console.warn(\n `No techniques found for stance: ${currentStance}, archetype: ${archetype}`,\n );\n addCombatMessage(\"기술 없음\", \"No techniques available\");\n return;\n }\n\n const selectedTechnique = availableTechniques[0];\n\n if (\n !KoreanTechniquesSystem.canExecuteTechnique(player, selectedTechnique)\n ) {\n addCombatMessage(\"기력/체력 부족\", \"Insufficient Ki/Stamina\");\n return;\n }\n\n attackTechnique = selectedTechnique;\n }\n\n combatActions.setExecutingTechnique(true);\n\n const damage = attackTechnique.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n combatAudio?.playAttackSound(intensity);\n\n const animationType = attackTechnique.animationType ?? AnimationType.JAB;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.15; // Default to typical punch peak\n const animationContext = {\n animationType,\n currentTime: peakTime, // Use peak time for synchronous hit detection\n };\n\n const result = combatSystem.resolveAttack(\n validPlayers[0],\n validPlayers[1],\n attackTechnique,\n undefined,\n animationContext,\n );\n\n const effectType = result.hit\n ? result.isCritical\n ? HitEffectType.CRITICAL_HIT\n : HitEffectType.HIT\n : HitEffectType.MISS;\n\n addHitEffect(effectType, playerPositions[0], result.hit ? 1 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[1]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[1].health - result.damage,\n vitalPoint: result.isCritical, // Critical hits are often vital points\n hitPosition,\n });\n\n const now = Date.now();\n const timeSinceLastHit = now - combatState.lastHitTime;\n const newCombo =\n timeSinceLastHit < 2000 ? combatState.comboCount + 1 : 1;\n combatActions.setComboCount(newCombo);\n combatActions.setLastHitTime(now);\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(\n result,\n validPlayers[0],\n validPlayers[1],\n );\n\n onPlayerUpdate(0, updatedAttacker);\n onPlayerUpdate(1, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n attackTechnique,\n updatedDefender.health,\n 1, // Player 2 (enemy) was hit\n );\n onInjuryCreated(injury, 1);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[1],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(1, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(knockbackName.korean, knockbackName.english);\n }\n\n if (result.knockback.duration > 0) {\n if (player2KnockbackTimeoutRef.current) {\n clearTimeout(player2KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(1, { isStunned: true });\n\n player2KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(1, { isStunned: false });\n player2KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player2) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player2.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n const techniqueNameKorean =\n attackTechnique.koreanName ?? attackTechnique.name.korean;\n const techniqueNameEnglish =\n attackTechnique.englishName ?? attackTechnique.name.english;\n\n if (result.isCritical) {\n addCombatMessage(\n `치명타! ${techniqueNameKorean}`,\n `Critical Hit! ${techniqueNameEnglish}`,\n );\n } else if (newCombo > 2) {\n addCombatMessage(\n `${newCombo} 연속! ${techniqueNameKorean}`,\n `${newCombo} Combo! ${techniqueNameEnglish}`,\n );\n } else {\n addCombatMessage(\n `${techniqueNameKorean} 성공!`,\n `${techniqueNameEnglish} Hit!`,\n );\n }\n } else {\n combatActions.resetCombo();\n const techniqueNameKorean =\n attackTechnique.koreanName ?? attackTechnique.name.korean;\n const techniqueNameEnglish =\n attackTechnique.englishName ?? attackTechnique.name.english;\n addCombatMessage(\n `${techniqueNameKorean} 빗나감`,\n `${techniqueNameEnglish} Missed`,\n );\n }\n\n setTimeout(() => combatActions.setExecutingTechnique(false), 500);\n },\n [\n validPlayers,\n playerPositions,\n combatState.isExecutingTechnique,\n combatState.roundStarted,\n combatState.roundEnded,\n combatState.comboCount,\n combatState.lastHitTime,\n combatActions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n config,\n ],\n );\n\n const handleDefend = useCallback(() => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n combatAudio?.playBlockSound(false);\n\n onPlayerUpdate(0, { isBlocking: true });\n addCombatMessage(\"방어 자세\", \"Defensive Stance\");\n addHitEffect(HitEffectType.BLOCK, playerPositions[0], 0.8);\n\n setTimeout(() => {\n onPlayerUpdate(0, { isBlocking: false });\n }, 1000);\n }, [\n combatState.roundStarted,\n combatState.roundEnded,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ]);\n\n const handleTechniqueExecute = useCallback(() => {\n if (\n combatState.isExecutingTechnique ||\n !combatState.roundStarted ||\n combatState.roundEnded\n )\n return;\n if (validPlayers[0].ki < 10 || validPlayers[0].stamina < 15) {\n addCombatMessage(\"기력/체력 부족\", \"Insufficient Ki/Stamina\");\n return;\n }\n\n combatActions.setExecutingTechnique(true);\n\n combatAudio?.playSpecialTechniqueSound();\n\n addHitEffect(HitEffectType.CRITICAL_HIT, playerPositions[0], 1.5);\n\n clearTimeoutSet(screenShakeTimeoutsRef.current);\n const shakeIntensity = 8;\n const shakeFrames = [\n { x: shakeIntensity, y: -shakeIntensity * 0.5 },\n { x: -shakeIntensity * 0.7, y: shakeIntensity * 0.8 },\n { x: shakeIntensity * 0.5, y: shakeIntensity * 0.3 },\n { x: -shakeIntensity * 0.3, y: -shakeIntensity * 0.6 },\n { x: 0, y: 0 },\n ].slice(0, SCREEN_SHAKE_FRAME_COUNT);\n\n shakeFrames.forEach((shake, index) => {\n const timeoutId = setTimeout(\n () => {\n screenShakeTimeoutsRef.current.delete(timeoutId);\n combatActions.setScreenShake(shake);\n },\n index * SCREEN_SHAKE_FRAME_INTERVAL_MS,\n );\n screenShakeTimeoutsRef.current.add(timeoutId);\n });\n\n const distance = Math.sqrt(\n Math.pow(playerPositions[0].x - playerPositions[1].x, 2) +\n Math.pow(playerPositions[0].y - playerPositions[1].y, 2),\n );\n\n if (distance < 150) {\n const hitPosition = calculateHitPosition(playerPositions[1]);\n\n combatAudio?.playBoneImpactSound({\n damage: 25,\n remainingHealth: validPlayers[1].health - 25,\n vitalPoint: false,\n hitPosition,\n });\n\n onPlayerUpdate(1, {\n health: Math.max(0, validPlayers[1].health - 25),\n hitsTaken: validPlayers[1].hitsTaken + 1,\n });\n addCombatMessage(\"특수 기술 성공!\", \"Special Technique Hit!\");\n } else {\n addCombatMessage(\"기술 실패\", \"Technique Failed\");\n }\n\n onPlayerUpdate(0, {\n ki: Math.max(0, validPlayers[0].ki - 10),\n stamina: Math.max(0, validPlayers[0].stamina - 15),\n });\n\n setTimeout(() => combatActions.setExecutingTechnique(false), 800);\n }, [\n validPlayers,\n playerPositions,\n combatState.isExecutingTechnique,\n combatState.roundStarted,\n combatState.roundEnded,\n combatActions,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n ]);\n\n const handleStanceSwitch = useCallback(\n (stance: TrigramStance) => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n combatAudio?.playStanceChangeSound();\n\n onPlayerUpdate(0, { currentStance: stance });\n addCombatMessage(`자세 변경: ${stance}`, `Stance Change: ${stance}`);\n addHitEffect(HitEffectType.STATUS_EFFECT, playerPositions[0], 0.6);\n },\n [\n combatState.roundStarted,\n combatState.roundEnded,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ],\n );\n\n /**\n * Handle stance side switch (left/right)\n * @korean 자세측면전환처리\n */\n const stanceManagerRef = useRef<StanceManager>(new StanceManager());\n\n const handleStanceSideSwitch = useCallback(\n (playerIndex: 0 | 1) => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n const player = validPlayers[playerIndex];\n const currentLaterality = combatState.playerLaterality[playerIndex];\n\n const result = stanceManagerRef.current.switchStanceSide(\n player,\n currentLaterality,\n );\n\n if (result.success && result.laterality) {\n onPlayerUpdate(playerIndex, result.updatedPlayer);\n\n onLateralityUpdate?.(playerIndex, result.laterality);\n\n combatAudio?.playStanceChangeSound?.();\n\n const koreanText =\n result.laterality === \"left\" ? \"왼발서기\" : \"오른발서기\";\n const englishText =\n result.laterality === \"left\" ? \"Left Stance\" : \"Right Stance\";\n addCombatMessage(koreanText, englishText);\n\n addHitEffect(\n HitEffectType.STATUS_EFFECT,\n playerPositions[playerIndex],\n 0.5,\n );\n } else {\n if (result.message?.includes(\"stamina\")) {\n addCombatMessage(\"체력 부족\", \"Insufficient Stamina\");\n } else if (result.message?.includes(\"cooldown\")) {\n addCombatMessage(\"대기 중\", \"On Cooldown\");\n }\n }\n },\n [\n combatState.roundStarted,\n combatState.roundEnded,\n combatState.playerLaterality,\n validPlayers,\n onPlayerUpdate,\n onLateralityUpdate,\n combatAudio,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n ],\n );\n\n /**\n * Helper function to create AI technique objects\n * Reduces code duplication between basic attacks and special techniques\n */\n const createAITechnique = useCallback(\n (type: \"basic\" | \"special\", aiPlayer: PlayerState) => {\n if (type === \"basic\") {\n return {\n id: \"ai_basic_attack\",\n name: {\n korean: \"AI 기본공격\",\n english: \"AI Basic Attack\",\n romanized: \"ai_gibon_gonggyeok\",\n },\n koreanName: \"AI 기본공격\",\n englishName: \"AI Basic Attack\",\n romanized: \"ai_gibon_gonggyeok\",\n description: { korean: \"AI 기본 공격\", english: \"AI basic attack\" },\n stance: aiPlayer.currentStance,\n type: \"attack\" as const,\n damageType: \"physical\" as const,\n damage: 15,\n kiCost: 5,\n staminaCost: 8,\n accuracy: 0.8,\n reachConfig: {\n bodyPart: \"arm\" as const,\n techniqueType: \"punch\" as const,\n baseExtension: 0.95,\n },\n executionTime: 400,\n recoveryTime: 300,\n critChance: 0.1,\n critMultiplier: 1.5,\n effects: [],\n animationType: AnimationType.JAB, // Default animation for basic attack\n };\n } else {\n return {\n id: \"ai_special_technique\",\n name: {\n korean: \"AI 특수기술\",\n english: \"AI Special Technique\",\n romanized: \"ai_teuksu_gisul\",\n },\n koreanName: \"AI 특수기술\",\n englishName: \"AI Special Technique\",\n romanized: \"ai_teuksu_gisul\",\n description: {\n korean: \"AI 특수 기술\",\n english: \"AI special technique\",\n },\n stance: aiPlayer.currentStance,\n type: \"technique\" as const,\n damageType: \"physical\" as const,\n damage: 25,\n kiCost: 10,\n staminaCost: 15,\n accuracy: 0.85,\n reachConfig: {\n bodyPart: \"leg\" as const,\n techniqueType: \"kick\" as const,\n baseExtension: 1.1,\n },\n executionTime: 600,\n recoveryTime: 800,\n critChance: 0.15,\n critMultiplier: 1.8,\n effects: [],\n animationType: AnimationType.SPINNING_HOOK, // Default animation for special technique\n };\n }\n },\n [],\n );\n\n /**\n * Helper function to determine hit effect type based on combat result\n * Reduces duplication between attack and technique handlers\n */\n const getHitEffectType = useCallback(\n (result: { hit: boolean; isCritical?: boolean }): HitEffectType => {\n if (!result.hit) return HitEffectType.MISS;\n return result.isCritical ? HitEffectType.CRITICAL_HIT : HitEffectType.HIT;\n },\n [],\n );\n\n /**\n * AI attack handler with technique and vital point targeting\n *\n * @param technique - Optional Korean martial arts technique to execute. If not provided, creates a basic attack.\n * @param targetVitalPoint - Optional vital point ID to target for increased damage effectiveness.\n */\n const handleAIAttack = useCallback(\n (technique?: KoreanTechnique, targetVitalPoint?: string) => {\n const aiPlayer = validPlayers[1];\n const targetPlayer = validPlayers[0];\n\n const attackTechnique = technique ?? createAITechnique(\"basic\", aiPlayer);\n\n const damage = attackTechnique.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n combatAudio?.playAttackSound(intensity);\n\n const animationType = attackTechnique.animationType ?? AnimationType.JAB;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.15;\n const animationContext = {\n animationType,\n currentTime: peakTime,\n };\n\n const result = combatSystem.resolveAttack(\n aiPlayer,\n targetPlayer,\n attackTechnique,\n targetVitalPoint, // Pass vital point ID for targeting\n animationContext, // Pass animation context for distance/reach check\n );\n\n const effectType = getHitEffectType(result);\n addHitEffect(effectType, playerPositions[1], result.hit ? 1 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[0]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[0].health - result.damage,\n vitalPoint: result.isCritical,\n hitPosition,\n });\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(result, aiPlayer, targetPlayer);\n\n onPlayerUpdate(1, updatedAttacker);\n onPlayerUpdate(0, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n attackTechnique,\n updatedDefender.health,\n 0, // Player 1 (player) was hit by AI\n );\n onInjuryCreated(injury, 0);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[0],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(0, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(\n `AI ${knockbackName.korean}`,\n `AI ${knockbackName.english}`,\n );\n }\n\n if (result.knockback.duration > 0) {\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(0, { isStunned: true });\n\n player1KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(0, { isStunned: false });\n player1KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player1) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player1.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n if (result.vitalPointHit && targetVitalPoint) {\n const vitalPoint = getVitalPointById(targetVitalPoint);\n const vpName = vitalPoint\n ? vitalPoint.names.korean\n : targetVitalPoint;\n addCombatMessage(\n `AI 급소 타격! ${vpName}`,\n `AI Vital Point Hit! ${\n vitalPoint?.names.english ?? targetVitalPoint\n }`,\n );\n } else if (result.isCritical) {\n addCombatMessage(\"AI 치명타!\", \"AI Critical Hit!\");\n } else {\n addCombatMessage(\"AI 공격 성공!\", \"AI Attack Hit!\");\n }\n } else {\n onPlayerUpdate(1, {\n ki: Math.max(0, aiPlayer.ki - attackTechnique.kiCost),\n stamina: Math.max(0, aiPlayer.stamina - attackTechnique.staminaCost),\n });\n addCombatMessage(\"AI 공격 빗나감\", \"AI Attack Missed\");\n }\n },\n [\n validPlayers,\n playerPositions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n createAITechnique,\n getHitEffectType,\n config,\n ],\n );\n\n const handleAIDefend = useCallback(() => {\n combatAudio?.playBlockSound(false);\n\n onPlayerUpdate(1, { isBlocking: true });\n addCombatMessage(\"AI 방어 자세\", \"AI Defensive Stance\");\n addHitEffect(HitEffectType.BLOCK, playerPositions[1], 0.8);\n\n setTimeout(() => {\n onPlayerUpdate(1, { isBlocking: false });\n }, 1000);\n }, [\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ]);\n\n /**\n * AI technique handler with technique and vital point targeting\n *\n * @param technique - Optional special Korean martial arts technique to execute. If not provided, creates a special technique.\n * @param targetVitalPoint - Optional vital point ID to target for increased damage effectiveness.\n */\n const handleAITechnique = useCallback(\n (technique?: KoreanTechnique, targetVitalPoint?: string) => {\n const aiPlayer = validPlayers[1];\n const targetPlayer = validPlayers[0];\n\n const specialTechnique =\n technique ?? createAITechnique(\"special\", aiPlayer);\n\n if (\n aiPlayer.ki < specialTechnique.kiCost ||\n aiPlayer.stamina < specialTechnique.staminaCost\n ) {\n handleAIAttack(undefined, targetVitalPoint); // Fallback to basic attack with same targeting\n return;\n }\n\n combatAudio?.playSpecialTechniqueSound();\n\n const animationType =\n specialTechnique.animationType ?? AnimationType.SPINNING_HOOK;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.25; // Special techniques often have longer peak times\n const animationContext = {\n animationType,\n currentTime: peakTime,\n };\n\n const result = combatSystem.resolveAttack(\n aiPlayer,\n targetPlayer,\n specialTechnique,\n targetVitalPoint, // Pass vital point ID for targeting\n animationContext, // Pass animation context for distance/reach check\n );\n\n const effectType = result.hit\n ? HitEffectType.CRITICAL_HIT\n : HitEffectType.MISS;\n\n addHitEffect(effectType, playerPositions[1], result.hit ? 1.5 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[0]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[0].health - result.damage,\n vitalPoint: result.isCritical === true || !!targetVitalPoint, // Special techniques often target vital points\n hitPosition,\n });\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(result, aiPlayer, targetPlayer);\n\n onPlayerUpdate(1, updatedAttacker);\n onPlayerUpdate(0, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n specialTechnique,\n updatedDefender.health,\n 0, // Player 1 (player) was hit by AI technique\n );\n onInjuryCreated(injury, 0);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[0],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(0, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(\n `AI 특수 ${knockbackName.korean}`,\n `AI Special ${knockbackName.english}`,\n );\n }\n\n if (result.knockback.duration > 0) {\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(0, { isStunned: true });\n\n player1KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(0, { isStunned: false });\n player1KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player1) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player1.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n if (result.vitalPointHit && targetVitalPoint) {\n const vitalPoint = getVitalPointById(targetVitalPoint);\n const vpName = vitalPoint\n ? vitalPoint.names.korean\n : targetVitalPoint;\n addCombatMessage(\n `AI 특수 급소 기술! ${vpName}`,\n `AI Special Vital Point Technique! ${\n vitalPoint?.names.english ?? targetVitalPoint\n }`,\n );\n } else {\n addCombatMessage(\"AI 특수 기술!\", \"AI Special Technique!\");\n }\n } else {\n onPlayerUpdate(1, {\n ki: Math.max(0, aiPlayer.ki - specialTechnique.kiCost),\n stamina: Math.max(0, aiPlayer.stamina - specialTechnique.staminaCost),\n });\n addCombatMessage(\"AI 기술 빗나감\", \"AI Technique Missed\");\n }\n },\n [\n validPlayers,\n playerPositions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n handleAIAttack,\n combatAudio,\n createAITechnique,\n config,\n ],\n );\n\n const moveAIPlayer = useCallback(\n (targetPos: Position) => {\n const currentPos = playerPositions[1];\n const aiPlayer = validPlayers[1];\n\n const AI_DECISION_FREQUENCY_HZ = 20; // 20 calls/second (50ms interval)\n const baseSpeed = 2.5 / AI_DECISION_FREQUENCY_HZ; // meters per call (0.125m per call)\n\n const dx = targetPos.x - currentPos.x;\n const dy = targetPos.y - currentPos.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n let finalSpeed = baseSpeed;\n if (aiPlayer.bodyPartHealth && aiPlayer.bodyPartMaxHealth) {\n const movementDirection = {\n x: distance > 0 ? dx / distance : 0,\n y: distance > 0 ? dy / distance : 0,\n };\n\n finalSpeed = movementPenaltySystem.calculateModifiedSpeed(\n baseSpeed,\n aiPlayer.bodyPartHealth,\n aiPlayer.bodyPartMaxHealth,\n movementDirection,\n );\n }\n\n const MIN_MOVEMENT_THRESHOLD_METERS = 0.05;\n\n if (distance > MIN_MOVEMENT_THRESHOLD_METERS) {\n const newPos = {\n x: currentPos.x + (dx / distance) * finalSpeed,\n y: currentPos.y + (dy / distance) * finalSpeed,\n };\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n newPos.x = Math.max(-halfWidth, Math.min(halfWidth, newPos.x));\n newPos.y = Math.max(-halfDepth, Math.min(halfDepth, newPos.y));\n\n onPlayerUpdate(1, { position: newPos });\n }\n },\n [playerPositions, validPlayers, arenaBounds, onPlayerUpdate],\n );\n\n return {\n handleAttack,\n handleDefend,\n handleTechniqueExecute,\n handleStanceSwitch,\n handleStanceSideSwitch,\n handleAIAttack,\n handleAIDefend,\n handleAITechnique,\n moveAIPlayer,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useCombatActions.js","names":[],"sources":["../../../../../src/components/screens/combat/hooks/useCombatActions.ts"],"sourcesContent":["/**\n * useCombatActions Hook - Combat Action Handlers\n *\n * Custom hook for managing combat action handlers.\n * Consolidates player attack, defend, technique, and AI action logic.\n *\n * Performance:\n * - Memoized callbacks to prevent recreation\n * - Centralized action logic for better maintainability\n * - Reduces main component complexity\n *\n * @param config Combat action configuration\n * @returns Combat action handlers\n *\n * @example\n * ```typescript\n * const {\n * handleAttack,\n * handleDefend,\n * handleTechniqueExecute,\n * handleStanceSwitch,\n * handleAIAttack,\n * handleAIDefend,\n * handleAITechnique,\n * moveAIPlayer\n * } = useCombatActions({\n * validPlayers,\n * playerPositions,\n * combatState,\n * combatActions,\n * combatSystem,\n * onPlayerUpdate,\n * addCombatMessage,\n * addHitEffect,\n * arenaBounds\n * });\n * ```\n */\n\nimport { PlayerState } from \"@/systems\";\nimport {\n AnimationType,\n getAnimationHitTiming,\n type AnimationState,\n} from \"@/systems/animation\";\nimport { movementPenaltySystem } from \"@/systems/bodypart\";\nimport { clampToArenaBounds, type PhysicsArenaBounds } from \"@/types/PhysicsTypes\";\nimport {\n checkForFall,\n getFallTypeName,\n} from \"@/systems/combat/FallIntegration\";\nimport type { CombatResult } from \"@/systems/combat/types\";\nimport { CombatSystem } from \"@/systems/CombatSystem\";\nimport { HitEffectType } from \"@/systems/effects\";\nimport { KnockbackPhysics } from \"@/systems/physics\";\nimport { StanceManager } from \"@/systems/trigram\";\nimport { KoreanTechniquesSystem } from \"@/systems/trigram/KoreanTechniques\";\nimport { getVitalPointById } from \"@/systems/vitalpoint/KoreanVitalPoints\";\nimport { KoreanTechnique } from \"@/systems/vitalpoint/types\";\nimport { Position, Technique, TrigramStance, BodyRegion } from \"@/types\";\nimport { Injury, InjuryType } from \"@/types/injury\";\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { AttackIntensity } from \"./useCombatAudio\";\nimport { CombatActions, CombatScreenState } from \"./useCombatState\";\n\n/**\n * Hit position variation range for randomizing strike heights\n * Produces ±0.2 absolute units (±10% of 2.0 character height)\n */\nconst HIT_Y_VARIATION_RANGE = 0.4;\n\nexport const SCREEN_SHAKE_FRAME_INTERVAL_MS = 50;\nexport const SCREEN_SHAKE_FRAME_COUNT = 5;\n\n/**\n * Clears every timeout in a tracked set and empties the set afterwards.\n *\n * @param timeoutIds Timeout identifiers to clear and remove\n */\nfunction clearTimeoutSet(timeoutIds: Set<ReturnType<typeof setTimeout>>): void {\n timeoutIds.forEach((timeoutId) => {\n clearTimeout(timeoutId);\n });\n timeoutIds.clear();\n}\n\n/**\n * Calculate randomized hit position based on defender position\n * Adds vertical variation to simulate different strike heights\n *\n * @param defenderPos - Position of the defender being struck\n * @returns Hit position with randomized Y coordinate\n */\nfunction calculateHitPosition(defenderPos: Position): { x: number; y: number } {\n const hitYVariation = (Math.random() - 0.5) * HIT_Y_VARIATION_RANGE; // ±0.2 units\n return {\n x: defenderPos.x,\n y: Math.max(0.3, Math.min(1.8, defenderPos.y + hitYVariation)),\n };\n}\n\n/**\n * Determine injury type from combat result and technique damage type\n * 전투 결과와 기술 피해 유형으로 부상 유형 결정\n *\n * @param result - Combat result with damage information\n * @param technique - Technique that was executed\n * @returns InjuryType to create\n */\nfunction determineInjuryType(\n result: CombatResult,\n technique: KoreanTechnique,\n): InjuryType {\n if (technique.damageType === \"slashing\") {\n return result.damage > 20 ? InjuryType.LACERATION : InjuryType.CUT;\n }\n\n if (result.damage > 25) {\n return InjuryType.BRUISE;\n }\n\n if (result.damage > 15) {\n return InjuryType.BRUISE;\n }\n\n return InjuryType.BRUISE;\n}\n\n/**\n * Map body region to 3D position offset on character model\n * 신체 부위를 캐릭터 모델의 3D 위치 오프셋으로 매핑\n *\n * @param region - Body region that was hit\n * @returns Position offset [x, y, z] relative to character center\n */\nfunction getBodyRegionPosition(region: BodyRegion): [number, number, number] {\n switch (region) {\n case BodyRegion.HEAD:\n return [0, 1.6, 0];\n case BodyRegion.NECK:\n return [0, 1.3, 0];\n case BodyRegion.TORSO:\n case BodyRegion.CORE:\n return [0, 0.8, 0];\n case BodyRegion.LEFT_ARM:\n return [-0.4, 1.0, 0];\n case BodyRegion.RIGHT_ARM:\n return [0.4, 1.0, 0];\n case BodyRegion.LEFT_LEG:\n return [-0.2, 0.2, 0];\n case BodyRegion.RIGHT_LEG:\n return [0.2, 0.2, 0];\n default:\n return [0, 0.8, 0]; // Default to torso\n }\n}\n\n/**\n * Create injury from combat damage result\n * 전투 피해 결과로부터 부상 생성\n *\n * @param result - Combat result with damage details\n * @param technique - Technique that caused the damage\n * @param defenderHealth - Current defender health after damage (0-100 scale)\n * @param targetPlayerIndex - Index of the player who was hit (0 or 1)\n * @returns Injury object for visualization\n */\nfunction createInjuryFromDamage(\n result: CombatResult,\n technique: KoreanTechnique,\n defenderHealth: number,\n targetPlayerIndex: number,\n): Injury {\n const bodyRegion = BodyRegion.TORSO; // TODO: Extract from result when available\n\n let injuryType = determineInjuryType(result, technique);\n\n const isLowHealth = defenderHealth <= 30; // 30% health threshold\n const isSevereDamage = result.damage >= 25; // Severe damage threshold\n if (isLowHealth && isSevereDamage && injuryType !== InjuryType.FRACTURE) {\n injuryType = InjuryType.FRACTURE;\n }\n\n const severity = Math.min(1.0, result.damage / 30);\n\n const basePosition = getBodyRegionPosition(bodyRegion);\n\n const randomOffset: [number, number, number] = [\n (Math.random() - 0.5) * 0.1,\n (Math.random() - 0.5) * 0.1,\n (Math.random() - 0.5) * 0.1,\n ];\n\n const position: [number, number, number] = [\n basePosition[0] + randomOffset[0],\n basePosition[1] + randomOffset[1],\n basePosition[2] + randomOffset[2],\n ];\n\n return {\n id: `injury_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,\n region: bodyRegion,\n type: injuryType,\n position,\n severity,\n hitCount: 1,\n timestamp: Date.now(),\n playerId: targetPlayerIndex === 0 ? \"player\" : \"enemy\",\n };\n}\n\n/**\n * Apply knockback displacement to defender position\n *\n * **Korean**: 밀침 적용 (Apply Knockback)\n *\n * Updates the defender's position based on knockback physics calculation.\n * The knockback displacement is applied in the direction of the attack vector\n * (attacker → defender), respecting arena boundaries.\n *\n * **Physics-First Architecture**: Both positions and knockback displacement\n * are in meters. Arena bounds are centered at origin (0, 0) with extent\n * ±worldWidthMeters/2 in X and ±worldDepthMeters/2 in Z.\n *\n * @param result - Combat result containing knockback data (in meters)\n * @param defenderPos - Current defender position (in meters)\n * @param arenaBounds - Arena boundary limits with meter dimensions\n * @returns Updated defender position after knockback (in meters)\n *\n * @example\n * ```typescript\n * // 10m × 7.5m arena, player at x=2m knocked back 2.5m to right\n * const newPosition = applyKnockbackDisplacement(\n * result, // knockback.displacement.x = 2.5m\n * { x: 2, y: 0 }, // Current position: 2m from center\n * { worldWidthMeters: 10, worldDepthMeters: 7.5, ... }\n * );\n * // Returns: { x: 4.5, y: 0 } (2m + 2.5m = 4.5m, within ±5m boundary)\n *\n * // Same knockback but would exceed boundary\n * const clampedPosition = applyKnockbackDisplacement(\n * result, // knockback.displacement.x = 2.5m\n * { x: 4, y: 0 }, // Current position: 4m from center\n * { worldWidthMeters: 10, worldDepthMeters: 7.5, ... }\n * );\n * // Returns: { x: 5, y: 0 } (4m + 2.5m = 6.5m, clamped to +5m boundary)\n * ```\n */\nfunction applyKnockbackDisplacement(\n result: CombatResult,\n defenderPos: Position,\n arenaBounds: PhysicsArenaBounds,\n): Position {\n if (!result.knockback) {\n return defenderPos;\n }\n\n const newPos = {\n x: defenderPos.x + result.knockback.displacement.x,\n y: defenderPos.y + result.knockback.displacement.z,\n };\n\n return clampToArenaBounds(newPos, arenaBounds);\n}\n\nexport interface UseCombatActionsConfig {\n readonly validPlayers: readonly [PlayerState, PlayerState];\n readonly playerPositions: readonly [Position, Position];\n readonly combatState: CombatScreenState;\n readonly combatActions: CombatActions;\n readonly combatSystem: CombatSystem;\n readonly onPlayerUpdate: (\n playerIndex: number,\n updates: Partial<PlayerState>,\n ) => void;\n readonly onPlayerPositionUpdate?: (\n playerIndex: number,\n position: Position,\n ) => void;\n readonly onLateralityUpdate?: (\n playerIndex: number,\n laterality: \"left\" | \"right\",\n ) => void;\n readonly onInjuryCreated?: (\n injury: Injury,\n targetPlayerIndex: number,\n ) => void;\n readonly addCombatMessage: (korean: string, english: string) => void;\n readonly addHitEffect: (\n type: HitEffectType,\n position: Position,\n intensity?: number,\n ) => void;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly combatAudio?: {\n readonly playAttackSound: (intensity?: AttackIntensity) => Promise<void>;\n readonly playHitSound: (damage: number) => Promise<void>;\n readonly playBoneImpactSound: (options: {\n region?: import(\"../../../../audio/types\").AudioBodyRegion;\n intensity?: import(\"../../../../audio/types\").ImpactIntensity;\n damage?: number;\n remainingHealth?: number;\n vitalPoint?: boolean;\n hitPosition?: { x: number; y: number; z?: number };\n }) => Promise<void>;\n readonly playBlockSound: (guardBroken?: boolean) => Promise<void>;\n readonly playDodgeSound: () => Promise<void>;\n readonly playStanceChangeSound: () => Promise<void>;\n readonly playSpecialTechniqueSound: () => Promise<void>;\n };\n readonly playerAnimations?: {\n readonly player1: {\n readonly transitionTo: (state: AnimationState) => boolean;\n };\n readonly player2: {\n readonly transitionTo: (state: AnimationState) => boolean;\n };\n };\n}\n\nexport interface UseCombatActionsReturn {\n readonly handleAttack: (technique?: Technique) => void;\n readonly handleDefend: () => void;\n readonly handleTechniqueExecute: () => void;\n readonly handleStanceSwitch: (stance: TrigramStance) => void;\n readonly handleStanceSideSwitch: (playerIndex: 0 | 1) => void;\n readonly handleAIAttack: (\n technique?: KoreanTechnique,\n targetVitalPoint?: string,\n ) => void;\n readonly handleAIDefend: () => void;\n readonly handleAITechnique: (\n technique?: KoreanTechnique,\n targetVitalPoint?: string,\n ) => void;\n readonly moveAIPlayer: (targetPos: Position) => void;\n}\n\n/**\n * Helper function to convert Technique to KoreanTechnique format\n * @param technique - The technique to convert\n * @param stance - Current player stance\n * @returns KoreanTechnique compatible with CombatSystem\n */\nfunction convertTechniqueToKorean(\n technique: Technique,\n stance: TrigramStance,\n): KoreanTechnique {\n return {\n id: technique.id,\n name: {\n korean: technique.name.korean,\n english: technique.name.english,\n romanized: technique.name.romanized ?? \"\",\n },\n koreanName: technique.name.korean,\n englishName: technique.name.english,\n romanized: technique.name.romanized ?? \"\",\n description: {\n korean: technique.description.korean,\n english: technique.description.english,\n },\n stance: technique.requiredStance ?? stance,\n type: \"attack\",\n damageType: technique.damageType,\n damage: (technique.damage.min + technique.damage.max) / 2, // Use average damage\n kiCost: technique.kiCost,\n staminaCost: technique.staminaCost,\n accuracy: 0.85, // Default accuracy\n reachConfig: {\n bodyPart: \"arm\" as const,\n techniqueType: \"punch\" as const,\n baseExtension: 0.9,\n },\n executionTime: technique.animationDuration ?? 400,\n recoveryTime: 300,\n critChance: technique.criticalChance ?? 0.1,\n critMultiplier: 1.5,\n effects: [],\n };\n}\n\n/**\n * Custom hook for combat action handlers\n */\nexport function useCombatActions(\n config: UseCombatActionsConfig,\n): UseCombatActionsReturn {\n const {\n validPlayers,\n playerPositions,\n combatState,\n combatActions,\n combatSystem,\n onPlayerUpdate,\n onLateralityUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n arenaBounds,\n combatAudio,\n } = config;\n\n const player1KnockbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const player2KnockbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const screenShakeTimeoutsRef = useRef<Set<ReturnType<typeof setTimeout>>>(\n new Set(),\n );\n\n useEffect(() => {\n const screenShakeTimeouts = screenShakeTimeoutsRef.current;\n return () => {\n clearTimeoutSet(screenShakeTimeouts);\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n if (player2KnockbackTimeoutRef.current) {\n clearTimeout(player2KnockbackTimeoutRef.current);\n }\n };\n }, []);\n\n const handleAttack = useCallback(\n (technique?: Technique) => {\n if (\n combatState.isExecutingTechnique ||\n !combatState.roundStarted ||\n combatState.roundEnded\n )\n return;\n\n const player = validPlayers[0];\n const currentStance = player.currentStance;\n const archetype = player.archetype;\n\n let attackTechnique: KoreanTechnique;\n\n if (technique) {\n attackTechnique = convertTechniqueToKorean(technique, currentStance);\n } else {\n const availableTechniques =\n KoreanTechniquesSystem.getAllAvailableTechniques(\n currentStance,\n archetype,\n );\n\n if (availableTechniques.length === 0) {\n console.warn(\n `No techniques found for stance: ${currentStance}, archetype: ${archetype}`,\n );\n addCombatMessage(\"기술 없음\", \"No techniques available\");\n return;\n }\n\n const selectedTechnique = availableTechniques[0];\n\n if (\n !KoreanTechniquesSystem.canExecuteTechnique(player, selectedTechnique)\n ) {\n addCombatMessage(\"기력/체력 부족\", \"Insufficient Ki/Stamina\");\n return;\n }\n\n attackTechnique = selectedTechnique;\n }\n\n combatActions.setExecutingTechnique(true);\n\n const damage = attackTechnique.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n combatAudio?.playAttackSound(intensity);\n\n const animationType = attackTechnique.animationType ?? AnimationType.JAB;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.15; // Default to typical punch peak\n const animationContext = {\n animationType,\n currentTime: peakTime, // Use peak time for synchronous hit detection\n };\n\n const result = combatSystem.resolveAttack(\n validPlayers[0],\n validPlayers[1],\n attackTechnique,\n undefined,\n animationContext,\n );\n\n const effectType = result.hit\n ? result.isCritical\n ? HitEffectType.CRITICAL_HIT\n : HitEffectType.HIT\n : HitEffectType.MISS;\n\n addHitEffect(effectType, playerPositions[0], result.hit ? 1 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[1]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[1].health - result.damage,\n vitalPoint: result.isCritical, // Critical hits are often vital points\n hitPosition,\n });\n\n const now = Date.now();\n const timeSinceLastHit = now - combatState.lastHitTime;\n const newCombo =\n timeSinceLastHit < 2000 ? combatState.comboCount + 1 : 1;\n combatActions.setComboCount(newCombo);\n combatActions.setLastHitTime(now);\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(\n result,\n validPlayers[0],\n validPlayers[1],\n );\n\n onPlayerUpdate(0, updatedAttacker);\n onPlayerUpdate(1, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n attackTechnique,\n updatedDefender.health,\n 1, // Player 2 (enemy) was hit\n );\n onInjuryCreated(injury, 1);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[1],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(1, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(knockbackName.korean, knockbackName.english);\n }\n\n if (result.knockback.duration > 0) {\n if (player2KnockbackTimeoutRef.current) {\n clearTimeout(player2KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(1, { isStunned: true });\n\n player2KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(1, { isStunned: false });\n player2KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player2) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player2.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n const techniqueNameKorean =\n attackTechnique.koreanName ?? attackTechnique.name.korean;\n const techniqueNameEnglish =\n attackTechnique.englishName ?? attackTechnique.name.english;\n\n if (result.isCritical) {\n addCombatMessage(\n `치명타! ${techniqueNameKorean}`,\n `Critical Hit! ${techniqueNameEnglish}`,\n );\n } else if (newCombo > 2) {\n addCombatMessage(\n `${newCombo} 연속! ${techniqueNameKorean}`,\n `${newCombo} Combo! ${techniqueNameEnglish}`,\n );\n } else {\n addCombatMessage(\n `${techniqueNameKorean} 성공!`,\n `${techniqueNameEnglish} Hit!`,\n );\n }\n } else {\n combatActions.resetCombo();\n const techniqueNameKorean =\n attackTechnique.koreanName ?? attackTechnique.name.korean;\n const techniqueNameEnglish =\n attackTechnique.englishName ?? attackTechnique.name.english;\n addCombatMessage(\n `${techniqueNameKorean} 빗나감`,\n `${techniqueNameEnglish} Missed`,\n );\n }\n\n setTimeout(() => combatActions.setExecutingTechnique(false), 500);\n },\n [\n validPlayers,\n playerPositions,\n combatState.isExecutingTechnique,\n combatState.roundStarted,\n combatState.roundEnded,\n combatState.comboCount,\n combatState.lastHitTime,\n combatActions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n config,\n ],\n );\n\n const handleDefend = useCallback(() => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n combatAudio?.playBlockSound(false);\n\n onPlayerUpdate(0, { isBlocking: true });\n addCombatMessage(\"방어 자세\", \"Defensive Stance\");\n addHitEffect(HitEffectType.BLOCK, playerPositions[0], 0.8);\n\n setTimeout(() => {\n onPlayerUpdate(0, { isBlocking: false });\n }, 1000);\n }, [\n combatState.roundStarted,\n combatState.roundEnded,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ]);\n\n const handleTechniqueExecute = useCallback(() => {\n if (\n combatState.isExecutingTechnique ||\n !combatState.roundStarted ||\n combatState.roundEnded\n )\n return;\n if (validPlayers[0].ki < 10 || validPlayers[0].stamina < 15) {\n addCombatMessage(\"기력/체력 부족\", \"Insufficient Ki/Stamina\");\n return;\n }\n\n combatActions.setExecutingTechnique(true);\n\n combatAudio?.playSpecialTechniqueSound();\n\n addHitEffect(HitEffectType.CRITICAL_HIT, playerPositions[0], 1.5);\n\n clearTimeoutSet(screenShakeTimeoutsRef.current);\n const shakeIntensity = 8;\n const shakeFrames = [\n { x: shakeIntensity, y: -shakeIntensity * 0.5 },\n { x: -shakeIntensity * 0.7, y: shakeIntensity * 0.8 },\n { x: shakeIntensity * 0.5, y: shakeIntensity * 0.3 },\n { x: -shakeIntensity * 0.3, y: -shakeIntensity * 0.6 },\n { x: 0, y: 0 },\n ].slice(0, SCREEN_SHAKE_FRAME_COUNT);\n\n shakeFrames.forEach((shake, index) => {\n const timeoutId = setTimeout(\n () => {\n screenShakeTimeoutsRef.current.delete(timeoutId);\n combatActions.setScreenShake(shake);\n },\n index * SCREEN_SHAKE_FRAME_INTERVAL_MS,\n );\n screenShakeTimeoutsRef.current.add(timeoutId);\n });\n\n const distance = Math.sqrt(\n Math.pow(playerPositions[0].x - playerPositions[1].x, 2) +\n Math.pow(playerPositions[0].y - playerPositions[1].y, 2),\n );\n\n if (distance < 150) {\n const hitPosition = calculateHitPosition(playerPositions[1]);\n\n combatAudio?.playBoneImpactSound({\n damage: 25,\n remainingHealth: validPlayers[1].health - 25,\n vitalPoint: false,\n hitPosition,\n });\n\n onPlayerUpdate(1, {\n health: Math.max(0, validPlayers[1].health - 25),\n hitsTaken: validPlayers[1].hitsTaken + 1,\n });\n addCombatMessage(\"특수 기술 성공!\", \"Special Technique Hit!\");\n } else {\n addCombatMessage(\"기술 실패\", \"Technique Failed\");\n }\n\n onPlayerUpdate(0, {\n ki: Math.max(0, validPlayers[0].ki - 10),\n stamina: Math.max(0, validPlayers[0].stamina - 15),\n });\n\n setTimeout(() => combatActions.setExecutingTechnique(false), 800);\n }, [\n validPlayers,\n playerPositions,\n combatState.isExecutingTechnique,\n combatState.roundStarted,\n combatState.roundEnded,\n combatActions,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n ]);\n\n const handleStanceSwitch = useCallback(\n (stance: TrigramStance) => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n combatAudio?.playStanceChangeSound();\n\n onPlayerUpdate(0, { currentStance: stance });\n addCombatMessage(`자세 변경: ${stance}`, `Stance Change: ${stance}`);\n addHitEffect(HitEffectType.STATUS_EFFECT, playerPositions[0], 0.6);\n },\n [\n combatState.roundStarted,\n combatState.roundEnded,\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ],\n );\n\n /**\n * Handle stance side switch (left/right)\n * @korean 자세측면전환처리\n */\n const stanceManagerRef = useRef<StanceManager>(new StanceManager());\n\n const handleStanceSideSwitch = useCallback(\n (playerIndex: 0 | 1) => {\n if (!combatState.roundStarted || combatState.roundEnded) return;\n\n const player = validPlayers[playerIndex];\n const currentLaterality = combatState.playerLaterality[playerIndex];\n\n const result = stanceManagerRef.current.switchStanceSide(\n player,\n currentLaterality,\n );\n\n if (result.success && result.laterality) {\n onPlayerUpdate(playerIndex, result.updatedPlayer);\n\n onLateralityUpdate?.(playerIndex, result.laterality);\n\n combatAudio?.playStanceChangeSound?.();\n\n const koreanText =\n result.laterality === \"left\" ? \"왼발서기\" : \"오른발서기\";\n const englishText =\n result.laterality === \"left\" ? \"Left Stance\" : \"Right Stance\";\n addCombatMessage(koreanText, englishText);\n\n addHitEffect(\n HitEffectType.STATUS_EFFECT,\n playerPositions[playerIndex],\n 0.5,\n );\n } else {\n if (result.message?.includes(\"stamina\")) {\n addCombatMessage(\"체력 부족\", \"Insufficient Stamina\");\n } else if (result.message?.includes(\"cooldown\")) {\n addCombatMessage(\"대기 중\", \"On Cooldown\");\n }\n }\n },\n [\n combatState.roundStarted,\n combatState.roundEnded,\n combatState.playerLaterality,\n validPlayers,\n onPlayerUpdate,\n onLateralityUpdate,\n combatAudio,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n ],\n );\n\n /**\n * Helper function to create AI technique objects\n * Reduces code duplication between basic attacks and special techniques\n */\n const createAITechnique = useCallback(\n (type: \"basic\" | \"special\", aiPlayer: PlayerState) => {\n if (type === \"basic\") {\n return {\n id: \"ai_basic_attack\",\n name: {\n korean: \"AI 기본공격\",\n english: \"AI Basic Attack\",\n romanized: \"ai_gibon_gonggyeok\",\n },\n koreanName: \"AI 기본공격\",\n englishName: \"AI Basic Attack\",\n romanized: \"ai_gibon_gonggyeok\",\n description: { korean: \"AI 기본 공격\", english: \"AI basic attack\" },\n stance: aiPlayer.currentStance,\n type: \"attack\" as const,\n damageType: \"physical\" as const,\n damage: 15,\n kiCost: 5,\n staminaCost: 8,\n accuracy: 0.8,\n reachConfig: {\n bodyPart: \"arm\" as const,\n techniqueType: \"punch\" as const,\n baseExtension: 0.95,\n },\n executionTime: 400,\n recoveryTime: 300,\n critChance: 0.1,\n critMultiplier: 1.5,\n effects: [],\n animationType: AnimationType.JAB, // Default animation for basic attack\n };\n } else {\n return {\n id: \"ai_special_technique\",\n name: {\n korean: \"AI 특수기술\",\n english: \"AI Special Technique\",\n romanized: \"ai_teuksu_gisul\",\n },\n koreanName: \"AI 특수기술\",\n englishName: \"AI Special Technique\",\n romanized: \"ai_teuksu_gisul\",\n description: {\n korean: \"AI 특수 기술\",\n english: \"AI special technique\",\n },\n stance: aiPlayer.currentStance,\n type: \"technique\" as const,\n damageType: \"physical\" as const,\n damage: 25,\n kiCost: 10,\n staminaCost: 15,\n accuracy: 0.85,\n reachConfig: {\n bodyPart: \"leg\" as const,\n techniqueType: \"kick\" as const,\n baseExtension: 1.1,\n },\n executionTime: 600,\n recoveryTime: 800,\n critChance: 0.15,\n critMultiplier: 1.8,\n effects: [],\n animationType: AnimationType.SPINNING_HOOK, // Default animation for special technique\n };\n }\n },\n [],\n );\n\n /**\n * Helper function to determine hit effect type based on combat result\n * Reduces duplication between attack and technique handlers\n */\n const getHitEffectType = useCallback(\n (result: { hit: boolean; isCritical?: boolean }): HitEffectType => {\n if (!result.hit) return HitEffectType.MISS;\n return result.isCritical ? HitEffectType.CRITICAL_HIT : HitEffectType.HIT;\n },\n [],\n );\n\n /**\n * AI attack handler with technique and vital point targeting\n *\n * @param technique - Optional Korean martial arts technique to execute. If not provided, creates a basic attack.\n * @param targetVitalPoint - Optional vital point ID to target for increased damage effectiveness.\n */\n const handleAIAttack = useCallback(\n (technique?: KoreanTechnique, targetVitalPoint?: string) => {\n const aiPlayer = validPlayers[1];\n const targetPlayer = validPlayers[0];\n\n const attackTechnique = technique ?? createAITechnique(\"basic\", aiPlayer);\n\n const damage = attackTechnique.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n combatAudio?.playAttackSound(intensity);\n\n const animationType = attackTechnique.animationType ?? AnimationType.JAB;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.15;\n const animationContext = {\n animationType,\n currentTime: peakTime,\n };\n\n const result = combatSystem.resolveAttack(\n aiPlayer,\n targetPlayer,\n attackTechnique,\n targetVitalPoint, // Pass vital point ID for targeting\n animationContext, // Pass animation context for distance/reach check\n );\n\n const effectType = getHitEffectType(result);\n addHitEffect(effectType, playerPositions[1], result.hit ? 1 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[0]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[0].health - result.damage,\n vitalPoint: result.isCritical,\n hitPosition,\n });\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(result, aiPlayer, targetPlayer);\n\n onPlayerUpdate(1, updatedAttacker);\n onPlayerUpdate(0, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n attackTechnique,\n updatedDefender.health,\n 0, // Player 1 (player) was hit by AI\n );\n onInjuryCreated(injury, 0);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[0],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(0, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(\n `AI ${knockbackName.korean}`,\n `AI ${knockbackName.english}`,\n );\n }\n\n if (result.knockback.duration > 0) {\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(0, { isStunned: true });\n\n player1KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(0, { isStunned: false });\n player1KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player1) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player1.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n if (result.vitalPointHit && targetVitalPoint) {\n const vitalPoint = getVitalPointById(targetVitalPoint);\n const vpName = vitalPoint\n ? vitalPoint.names.korean\n : targetVitalPoint;\n addCombatMessage(\n `AI 급소 타격! ${vpName}`,\n `AI Vital Point Hit! ${\n vitalPoint?.names.english ?? targetVitalPoint\n }`,\n );\n } else if (result.isCritical) {\n addCombatMessage(\"AI 치명타!\", \"AI Critical Hit!\");\n } else {\n addCombatMessage(\"AI 공격 성공!\", \"AI Attack Hit!\");\n }\n } else {\n onPlayerUpdate(1, {\n ki: Math.max(0, aiPlayer.ki - attackTechnique.kiCost),\n stamina: Math.max(0, aiPlayer.stamina - attackTechnique.staminaCost),\n });\n addCombatMessage(\"AI 공격 빗나감\", \"AI Attack Missed\");\n }\n },\n [\n validPlayers,\n playerPositions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n combatAudio,\n createAITechnique,\n getHitEffectType,\n config,\n ],\n );\n\n const handleAIDefend = useCallback(() => {\n combatAudio?.playBlockSound(false);\n\n onPlayerUpdate(1, { isBlocking: true });\n addCombatMessage(\"AI 방어 자세\", \"AI Defensive Stance\");\n addHitEffect(HitEffectType.BLOCK, playerPositions[1], 0.8);\n\n setTimeout(() => {\n onPlayerUpdate(1, { isBlocking: false });\n }, 1000);\n }, [\n onPlayerUpdate,\n addCombatMessage,\n addHitEffect,\n playerPositions,\n combatAudio,\n ]);\n\n /**\n * AI technique handler with technique and vital point targeting\n *\n * @param technique - Optional special Korean martial arts technique to execute. If not provided, creates a special technique.\n * @param targetVitalPoint - Optional vital point ID to target for increased damage effectiveness.\n */\n const handleAITechnique = useCallback(\n (technique?: KoreanTechnique, targetVitalPoint?: string) => {\n const aiPlayer = validPlayers[1];\n const targetPlayer = validPlayers[0];\n\n const specialTechnique =\n technique ?? createAITechnique(\"special\", aiPlayer);\n\n if (\n aiPlayer.ki < specialTechnique.kiCost ||\n aiPlayer.stamina < specialTechnique.staminaCost\n ) {\n handleAIAttack(undefined, targetVitalPoint); // Fallback to basic attack with same targeting\n return;\n }\n\n combatAudio?.playSpecialTechniqueSound();\n\n const animationType =\n specialTechnique.animationType ?? AnimationType.SPINNING_HOOK;\n const hitTiming = getAnimationHitTiming(animationType);\n const peakTime = hitTiming?.hitWindow.peakTime ?? 0.25; // Special techniques often have longer peak times\n const animationContext = {\n animationType,\n currentTime: peakTime,\n };\n\n const result = combatSystem.resolveAttack(\n aiPlayer,\n targetPlayer,\n specialTechnique,\n targetVitalPoint, // Pass vital point ID for targeting\n animationContext, // Pass animation context for distance/reach check\n );\n\n const effectType = result.hit\n ? HitEffectType.CRITICAL_HIT\n : HitEffectType.MISS;\n\n addHitEffect(effectType, playerPositions[1], result.hit ? 1.5 : 0.5);\n\n if (result.hit) {\n const hitPosition = calculateHitPosition(playerPositions[0]);\n\n combatAudio?.playBoneImpactSound({\n damage: result.damage,\n remainingHealth: validPlayers[0].health - result.damage,\n vitalPoint: result.isCritical === true || !!targetVitalPoint, // Special techniques often target vital points\n hitPosition,\n });\n\n const { updatedAttacker, updatedDefender } =\n combatSystem.applyCombatResult(result, aiPlayer, targetPlayer);\n\n onPlayerUpdate(1, updatedAttacker);\n onPlayerUpdate(0, updatedDefender);\n\n if (onInjuryCreated) {\n const injury = createInjuryFromDamage(\n result,\n specialTechnique,\n updatedDefender.health,\n 0, // Player 1 (player) was hit by AI technique\n );\n onInjuryCreated(injury, 0);\n }\n\n if (result.knockback && config.onPlayerPositionUpdate) {\n const newDefenderPosition = applyKnockbackDisplacement(\n result,\n playerPositions[0],\n config.arenaBounds,\n );\n config.onPlayerPositionUpdate(0, newDefenderPosition);\n\n const knockbackDistance = Math.sqrt(\n result.knockback.displacement.x ** 2 +\n result.knockback.displacement.z ** 2,\n );\n if (knockbackDistance > 1.5) {\n const knockbackName = KnockbackPhysics.getKnockbackStateName(\n result.knockback.shouldFall,\n );\n addCombatMessage(\n `AI 특수 ${knockbackName.korean}`,\n `AI Special ${knockbackName.english}`,\n );\n }\n\n if (result.knockback.duration > 0) {\n if (player1KnockbackTimeoutRef.current) {\n clearTimeout(player1KnockbackTimeoutRef.current);\n }\n\n onPlayerUpdate(0, { isStunned: true });\n\n player1KnockbackTimeoutRef.current = setTimeout(\n () => {\n onPlayerUpdate(0, { isStunned: false });\n player1KnockbackTimeoutRef.current = null;\n },\n (result.knockback.duration + result.knockback.recoveryWindow) *\n 1000,\n );\n }\n }\n\n if (config.playerAnimations?.player1) {\n const fallCheck = checkForFall(\n updatedDefender,\n combatSystem,\n undefined, // lastImpactAngle not tracked yet\n undefined, // attackAngle not tracked yet\n );\n\n if (\n fallCheck.shouldFall &&\n fallCheck.animationState &&\n fallCheck.fallType\n ) {\n config.playerAnimations.player1.transitionTo(\n fallCheck.animationState,\n );\n const fallName = getFallTypeName(fallCheck.fallType);\n addCombatMessage(`${fallName.korean}!`, `${fallName.english}!`);\n }\n }\n\n if (result.vitalPointHit && targetVitalPoint) {\n const vitalPoint = getVitalPointById(targetVitalPoint);\n const vpName = vitalPoint\n ? vitalPoint.names.korean\n : targetVitalPoint;\n addCombatMessage(\n `AI 특수 급소 기술! ${vpName}`,\n `AI Special Vital Point Technique! ${\n vitalPoint?.names.english ?? targetVitalPoint\n }`,\n );\n } else {\n addCombatMessage(\"AI 특수 기술!\", \"AI Special Technique!\");\n }\n } else {\n onPlayerUpdate(1, {\n ki: Math.max(0, aiPlayer.ki - specialTechnique.kiCost),\n stamina: Math.max(0, aiPlayer.stamina - specialTechnique.staminaCost),\n });\n addCombatMessage(\"AI 기술 빗나감\", \"AI Technique Missed\");\n }\n },\n [\n validPlayers,\n playerPositions,\n combatSystem,\n onPlayerUpdate,\n onInjuryCreated,\n addCombatMessage,\n addHitEffect,\n handleAIAttack,\n combatAudio,\n createAITechnique,\n config,\n ],\n );\n\n const moveAIPlayer = useCallback(\n (targetPos: Position) => {\n const currentPos = playerPositions[1];\n const aiPlayer = validPlayers[1];\n\n const AI_DECISION_FREQUENCY_HZ = 20; // 20 calls/second (50ms interval)\n const baseSpeed = 2.5 / AI_DECISION_FREQUENCY_HZ; // meters per call (0.125m per call)\n\n const dx = targetPos.x - currentPos.x;\n const dy = targetPos.y - currentPos.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n let finalSpeed = baseSpeed;\n if (aiPlayer.bodyPartHealth && aiPlayer.bodyPartMaxHealth) {\n const movementDirection = {\n x: distance > 0 ? dx / distance : 0,\n y: distance > 0 ? dy / distance : 0,\n };\n\n finalSpeed = movementPenaltySystem.calculateModifiedSpeed(\n baseSpeed,\n aiPlayer.bodyPartHealth,\n aiPlayer.bodyPartMaxHealth,\n movementDirection,\n );\n }\n\n const MIN_MOVEMENT_THRESHOLD_METERS = 0.05;\n\n if (distance > MIN_MOVEMENT_THRESHOLD_METERS) {\n const newPos = {\n x: currentPos.x + (dx / distance) * finalSpeed,\n y: currentPos.y + (dy / distance) * finalSpeed,\n };\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n newPos.x = Math.max(-halfWidth, Math.min(halfWidth, newPos.x));\n newPos.y = Math.max(-halfDepth, Math.min(halfDepth, newPos.y));\n\n onPlayerUpdate(1, { position: newPos });\n }\n },\n [playerPositions, validPlayers, arenaBounds, onPlayerUpdate],\n );\n\n return {\n handleAttack,\n handleDefend,\n handleTechniqueExecute,\n handleStanceSwitch,\n handleStanceSideSwitch,\n handleAIAttack,\n handleAIDefend,\n handleAITechnique,\n moveAIPlayer,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCombatAttackMovement.js","names":[],"sources":["../../../../../src/components/screens/combat/hooks/useCombatAttackMovement.ts"],"sourcesContent":["/**\n * useCombatAttackMovement Hook - Track Attack Movement for Combat Characters\n *\n * Custom hook for managing attack movement physics during combat animations.\n * Supports two fighters with simultaneous attack movements and arena bounds.\n *\n * @korean 전투공격이동훅 - 전투 공격 이동 추적\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { AnimationType } from \"@/systems/animation\";\nimport { AttackMovementPhysics } from \"@/systems/physics\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate attack direction from attacker to defender\n * 공격자에서 방어자로의 공격 방향 계산\n *\n * @param attackerPos - Attacker position\n * @param defenderPos - Defender position\n * @returns Normalized direction vector\n */\nfunction calculateAttackDirection(\n attackerPos: [number, number, number],\n defenderPos: [number, number, number],\n): THREE.Vector3 {\n const direction = new THREE.Vector3(\n defenderPos[0] - attackerPos[0],\n 0, // Keep movement horizontal\n defenderPos[2] - attackerPos[2],\n );\n return direction.normalize();\n}\n\n/**\n * Configuration for combat attack movement\n */\nexport interface CombatAttackMovementConfig {\n /** Whether player 1 is currently attacking */\n readonly player1Attacking: boolean;\n /** Player 1 animation type */\n readonly player1AnimationType?: AnimationType;\n /** Player 1 current stance */\n readonly player1Stance: TrigramStance;\n /** Player 1 base position */\n readonly player1BasePosition: [number, number, number];\n /** Player 1 animation duration in seconds (default: 0.4) */\n readonly player1AnimationDuration?: number;\n\n /** Whether player 2 is currently attacking */\n readonly player2Attacking: boolean;\n /** Player 2 animation type */\n readonly player2AnimationType?: AnimationType;\n /** Player 2 current stance */\n readonly player2Stance: TrigramStance;\n /** Player 2 base position */\n readonly player2BasePosition: [number, number, number];\n /** Player 2 animation duration in seconds (default: 0.4) */\n readonly player2AnimationDuration?: number;\n\n /** Default animation duration in seconds for both players if individual values not specified (default: 0.4) */\n readonly animationDuration?: number;\n}\n\n/**\n * Return value from useCombatAttackMovement hook\n */\nexport interface CombatAttackMovementResult {\n /** Player 1 current position including attack movement */\n readonly player1Position: [number, number, number];\n /** Player 2 current position including attack movement */\n readonly player2Position: [number, number, number];\n /** Whether player 1 is in forward lunge phase */\n readonly player1IsLunging: boolean;\n /** Whether player 2 is in forward lunge phase */\n readonly player2IsLunging: boolean;\n}\n\n/**\n * useCombatAttackMovement hook\n *\n * Tracks attack movement physics for both fighters in combat.\n * Automatically handles lunge and recovery phases with smooth easing.\n * Calculates attack direction between fighters dynamically.\n *\n * @param config - Combat attack movement configuration\n * @returns Current positions and movement states for both fighters\n *\n * @example\n * ```typescript\n * const {\n * player1Position,\n * player2Position,\n * player1IsLunging\n * } = useCombatAttackMovement({\n * player1Attacking: isPlayer1Attacking,\n * player1AnimationType: AnimationType.ROUNDHOUSE_KICK,\n * player1Stance: player1.stance,\n * player1BasePosition: [5, 0, 0],\n * player2Attacking: false,\n * player2AnimationType: undefined,\n * player2Stance: player2.stance,\n * player2BasePosition: [-5, 0, 0],\n * });\n *\n * <Player3D position={player1Position} />\n * <Player3D position={player2Position} />\n * ```\n *\n * @korean 전투공격이동사용\n */\nexport function useCombatAttackMovement(\n config: CombatAttackMovementConfig,\n): CombatAttackMovementResult {\n const {\n player1Attacking,\n player1AnimationType,\n player1Stance,\n player1BasePosition,\n player1AnimationDuration,\n player2Attacking,\n player2AnimationType,\n player2Stance,\n player2BasePosition,\n player2AnimationDuration,\n animationDuration = 0.4,\n } = config;\n\n const p1Duration = player1AnimationDuration ?? animationDuration;\n const p2Duration = player2AnimationDuration ?? animationDuration;\n\n const player1PhysicsRef = useRef(new AttackMovementPhysics());\n const player2PhysicsRef = useRef(new AttackMovementPhysics());\n\n const player1BasePosVectorRef = useRef(new THREE.Vector3());\n const player2BasePosVectorRef = useRef(new THREE.Vector3());\n\n const player1AttackStartTimeRef = useRef<number | null>(null);\n const player1MovementResultRef = useRef<ReturnType<\n typeof player1PhysicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const player2AttackStartTimeRef = useRef<number | null>(null);\n const player2MovementResultRef = useRef<ReturnType<\n typeof player2PhysicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const [player1Position, setPlayer1Position] =\n useState<[number, number, number]>(player1BasePosition);\n const [player2Position, setPlayer2Position] =\n useState<[number, number, number]>(player2BasePosition);\n\n const [player1IsLunging, setPlayer1IsLunging] = useState(false);\n const [player2IsLunging, setPlayer2IsLunging] = useState(false);\n\n const player1WasAttackingRef = useRef(false);\n const player2WasAttackingRef = useRef(false);\n\n useEffect(() => {\n const wasAttacking = player1WasAttackingRef.current;\n player1WasAttackingRef.current = player1Attacking;\n\n if (player1Attacking && player1AnimationType && !wasAttacking) {\n player1AttackStartTimeRef.current = Date.now();\n\n const direction = calculateAttackDirection(\n player1BasePosition,\n player2BasePosition,\n );\n\n player1MovementResultRef.current =\n player1PhysicsRef.current.calculateAttackMovement({\n animationType: player1AnimationType,\n currentStance: player1Stance,\n direction,\n animationDuration: p1Duration,\n });\n\n setPlayer1IsLunging(true);\n\n let animationFrameId: number;\n\n const updatePosition = () => {\n const elapsed = Date.now() - (player1AttackStartTimeRef.current ?? 0);\n const movementResult = player1MovementResultRef.current;\n\n if (!movementResult) {\n setPlayer1Position(player1BasePosition);\n return;\n }\n\n const totalDuration = movementResult.totalDuration * 1000; // Convert to ms\n\n if (elapsed >= totalDuration) {\n setPlayer1Position(player1BasePosition);\n setPlayer1IsLunging(false);\n player1AttackStartTimeRef.current = null;\n player1MovementResultRef.current = null;\n return;\n }\n\n const elapsedSeconds = elapsed / 1000;\n const basePos = player1BasePosVectorRef.current.set(\n ...player1BasePosition,\n );\n const recovering = elapsed >= movementResult.lungeDuration * 1000;\n\n const newPos = player1PhysicsRef.current.applyAttackMovement(\n basePos,\n movementResult,\n elapsedSeconds,\n recovering,\n );\n\n const newPosition: [number, number, number] = [\n newPos.x,\n newPos.y,\n newPos.z,\n ];\n\n setPlayer1Position(newPosition);\n\n const totalProgress = elapsedSeconds / movementResult.totalDuration;\n setPlayer1IsLunging(totalProgress < 0.5);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n } else {\n setPlayer1Position(player1BasePosition);\n setPlayer1IsLunging(false);\n player1AttackStartTimeRef.current = null;\n player1MovementResultRef.current = null;\n }\n }, [\n player1Attacking,\n player1AnimationType,\n player1Stance,\n player1BasePosition,\n player2BasePosition,\n p1Duration,\n ]);\n\n useEffect(() => {\n const wasAttacking = player2WasAttackingRef.current;\n player2WasAttackingRef.current = player2Attacking;\n\n if (player2Attacking && player2AnimationType && !wasAttacking) {\n player2AttackStartTimeRef.current = Date.now();\n\n const direction = calculateAttackDirection(\n player2BasePosition,\n player1BasePosition,\n );\n\n player2MovementResultRef.current =\n player2PhysicsRef.current.calculateAttackMovement({\n animationType: player2AnimationType,\n currentStance: player2Stance,\n direction,\n animationDuration: p2Duration,\n });\n\n setPlayer2IsLunging(true);\n\n let animationFrameId: number;\n\n const updatePosition = () => {\n const elapsed = Date.now() - (player2AttackStartTimeRef.current ?? 0);\n const movementResult = player2MovementResultRef.current;\n\n if (!movementResult) {\n setPlayer2Position(player2BasePosition);\n return;\n }\n\n const totalDuration = movementResult.totalDuration * 1000;\n\n if (elapsed >= totalDuration) {\n setPlayer2Position(player2BasePosition);\n setPlayer2IsLunging(false);\n player2AttackStartTimeRef.current = null;\n player2MovementResultRef.current = null;\n return;\n }\n\n const elapsedSeconds = elapsed / 1000;\n const basePos = player2BasePosVectorRef.current.set(\n ...player2BasePosition,\n );\n const recovering = elapsed >= movementResult.lungeDuration * 1000;\n\n const newPos = player2PhysicsRef.current.applyAttackMovement(\n basePos,\n movementResult,\n elapsedSeconds,\n recovering,\n );\n\n const newPosition: [number, number, number] = [\n newPos.x,\n newPos.y,\n newPos.z,\n ];\n\n setPlayer2Position(newPosition);\n\n const totalProgress = elapsedSeconds / movementResult.totalDuration;\n setPlayer2IsLunging(totalProgress < 0.5);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n } else {\n setPlayer2Position(player2BasePosition);\n setPlayer2IsLunging(false);\n player2AttackStartTimeRef.current = null;\n player2MovementResultRef.current = null;\n }\n }, [\n player2Attacking,\n player2AnimationType,\n player2Stance,\n player1BasePosition,\n player2BasePosition,\n p2Duration,\n ]);\n\n return {\n player1Position,\n player2Position,\n player1IsLunging,\n player2IsLunging,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useCombatAttackMovement.js","names":[],"sources":["../../../../../src/components/screens/combat/hooks/useCombatAttackMovement.ts"],"sourcesContent":["/**\n * useCombatAttackMovement Hook - Track Attack Movement for Combat Characters\n *\n * Custom hook for managing attack movement physics during combat animations.\n * Supports two fighters with simultaneous attack movements and arena bounds.\n *\n * @korean 전투공격이동훅 - 전투 공격 이동 추적\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { AnimationType } from \"@/systems/animation\";\nimport { AttackMovementPhysics } from \"@/systems/physics\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate attack direction from attacker to defender\n * 공격자에서 방어자로의 공격 방향 계산\n *\n * @param attackerPos - Attacker position\n * @param defenderPos - Defender position\n * @returns Normalized direction vector\n */\nfunction calculateAttackDirection(\n attackerPos: [number, number, number],\n defenderPos: [number, number, number],\n): THREE.Vector3 {\n const direction = new THREE.Vector3(\n defenderPos[0] - attackerPos[0],\n 0, // Keep movement horizontal\n defenderPos[2] - attackerPos[2],\n );\n return direction.normalize();\n}\n\n/**\n * Configuration for combat attack movement\n */\nexport interface CombatAttackMovementConfig {\n /** Whether player 1 is currently attacking */\n readonly player1Attacking: boolean;\n /** Player 1 animation type */\n readonly player1AnimationType?: AnimationType;\n /** Player 1 current stance */\n readonly player1Stance: TrigramStance;\n /** Player 1 base position */\n readonly player1BasePosition: [number, number, number];\n /** Player 1 animation duration in seconds (default: 0.4) */\n readonly player1AnimationDuration?: number;\n\n /** Whether player 2 is currently attacking */\n readonly player2Attacking: boolean;\n /** Player 2 animation type */\n readonly player2AnimationType?: AnimationType;\n /** Player 2 current stance */\n readonly player2Stance: TrigramStance;\n /** Player 2 base position */\n readonly player2BasePosition: [number, number, number];\n /** Player 2 animation duration in seconds (default: 0.4) */\n readonly player2AnimationDuration?: number;\n\n /** Default animation duration in seconds for both players if individual values not specified (default: 0.4) */\n readonly animationDuration?: number;\n}\n\n/**\n * Return value from useCombatAttackMovement hook\n */\nexport interface CombatAttackMovementResult {\n /** Player 1 current position including attack movement */\n readonly player1Position: [number, number, number];\n /** Player 2 current position including attack movement */\n readonly player2Position: [number, number, number];\n /** Whether player 1 is in forward lunge phase */\n readonly player1IsLunging: boolean;\n /** Whether player 2 is in forward lunge phase */\n readonly player2IsLunging: boolean;\n}\n\n/**\n * useCombatAttackMovement hook\n *\n * Tracks attack movement physics for both fighters in combat.\n * Automatically handles lunge and recovery phases with smooth easing.\n * Calculates attack direction between fighters dynamically.\n *\n * @param config - Combat attack movement configuration\n * @returns Current positions and movement states for both fighters\n *\n * @example\n * ```typescript\n * const {\n * player1Position,\n * player2Position,\n * player1IsLunging\n * } = useCombatAttackMovement({\n * player1Attacking: isPlayer1Attacking,\n * player1AnimationType: AnimationType.ROUNDHOUSE_KICK,\n * player1Stance: player1.stance,\n * player1BasePosition: [5, 0, 0],\n * player2Attacking: false,\n * player2AnimationType: undefined,\n * player2Stance: player2.stance,\n * player2BasePosition: [-5, 0, 0],\n * });\n *\n * <Player3D position={player1Position} />\n * <Player3D position={player2Position} />\n * ```\n *\n * @korean 전투공격이동사용\n */\nexport function useCombatAttackMovement(\n config: CombatAttackMovementConfig,\n): CombatAttackMovementResult {\n const {\n player1Attacking,\n player1AnimationType,\n player1Stance,\n player1BasePosition,\n player1AnimationDuration,\n player2Attacking,\n player2AnimationType,\n player2Stance,\n player2BasePosition,\n player2AnimationDuration,\n animationDuration = 0.4,\n } = config;\n\n const p1Duration = player1AnimationDuration ?? animationDuration;\n const p2Duration = player2AnimationDuration ?? animationDuration;\n\n const player1PhysicsRef = useRef(new AttackMovementPhysics());\n const player2PhysicsRef = useRef(new AttackMovementPhysics());\n\n const player1BasePosVectorRef = useRef(new THREE.Vector3());\n const player2BasePosVectorRef = useRef(new THREE.Vector3());\n\n const player1AttackStartTimeRef = useRef<number | null>(null);\n const player1MovementResultRef = useRef<ReturnType<\n typeof player1PhysicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const player2AttackStartTimeRef = useRef<number | null>(null);\n const player2MovementResultRef = useRef<ReturnType<\n typeof player2PhysicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const [player1Position, setPlayer1Position] =\n useState<[number, number, number]>(player1BasePosition);\n const [player2Position, setPlayer2Position] =\n useState<[number, number, number]>(player2BasePosition);\n\n const [player1IsLunging, setPlayer1IsLunging] = useState(false);\n const [player2IsLunging, setPlayer2IsLunging] = useState(false);\n\n const player1WasAttackingRef = useRef(false);\n const player2WasAttackingRef = useRef(false);\n\n useEffect(() => {\n const wasAttacking = player1WasAttackingRef.current;\n player1WasAttackingRef.current = player1Attacking;\n\n if (player1Attacking && player1AnimationType && !wasAttacking) {\n player1AttackStartTimeRef.current = Date.now();\n\n const direction = calculateAttackDirection(\n player1BasePosition,\n player2BasePosition,\n );\n\n player1MovementResultRef.current =\n player1PhysicsRef.current.calculateAttackMovement({\n animationType: player1AnimationType,\n currentStance: player1Stance,\n direction,\n animationDuration: p1Duration,\n });\n\n setPlayer1IsLunging(true);\n\n let animationFrameId: number;\n\n const updatePosition = () => {\n const elapsed = Date.now() - (player1AttackStartTimeRef.current ?? 0);\n const movementResult = player1MovementResultRef.current;\n\n if (!movementResult) {\n setPlayer1Position(player1BasePosition);\n return;\n }\n\n const totalDuration = movementResult.totalDuration * 1000; // Convert to ms\n\n if (elapsed >= totalDuration) {\n setPlayer1Position(player1BasePosition);\n setPlayer1IsLunging(false);\n player1AttackStartTimeRef.current = null;\n player1MovementResultRef.current = null;\n return;\n }\n\n const elapsedSeconds = elapsed / 1000;\n const basePos = player1BasePosVectorRef.current.set(\n ...player1BasePosition,\n );\n const recovering = elapsed >= movementResult.lungeDuration * 1000;\n\n const newPos = player1PhysicsRef.current.applyAttackMovement(\n basePos,\n movementResult,\n elapsedSeconds,\n recovering,\n );\n\n const newPosition: [number, number, number] = [\n newPos.x,\n newPos.y,\n newPos.z,\n ];\n\n setPlayer1Position(newPosition);\n\n const totalProgress = elapsedSeconds / movementResult.totalDuration;\n setPlayer1IsLunging(totalProgress < 0.5);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n } else {\n setPlayer1Position(player1BasePosition);\n setPlayer1IsLunging(false);\n player1AttackStartTimeRef.current = null;\n player1MovementResultRef.current = null;\n }\n }, [\n player1Attacking,\n player1AnimationType,\n player1Stance,\n player1BasePosition,\n player2BasePosition,\n p1Duration,\n ]);\n\n useEffect(() => {\n const wasAttacking = player2WasAttackingRef.current;\n player2WasAttackingRef.current = player2Attacking;\n\n if (player2Attacking && player2AnimationType && !wasAttacking) {\n player2AttackStartTimeRef.current = Date.now();\n\n const direction = calculateAttackDirection(\n player2BasePosition,\n player1BasePosition,\n );\n\n player2MovementResultRef.current =\n player2PhysicsRef.current.calculateAttackMovement({\n animationType: player2AnimationType,\n currentStance: player2Stance,\n direction,\n animationDuration: p2Duration,\n });\n\n setPlayer2IsLunging(true);\n\n let animationFrameId: number;\n\n const updatePosition = () => {\n const elapsed = Date.now() - (player2AttackStartTimeRef.current ?? 0);\n const movementResult = player2MovementResultRef.current;\n\n if (!movementResult) {\n setPlayer2Position(player2BasePosition);\n return;\n }\n\n const totalDuration = movementResult.totalDuration * 1000;\n\n if (elapsed >= totalDuration) {\n setPlayer2Position(player2BasePosition);\n setPlayer2IsLunging(false);\n player2AttackStartTimeRef.current = null;\n player2MovementResultRef.current = null;\n return;\n }\n\n const elapsedSeconds = elapsed / 1000;\n const basePos = player2BasePosVectorRef.current.set(\n ...player2BasePosition,\n );\n const recovering = elapsed >= movementResult.lungeDuration * 1000;\n\n const newPos = player2PhysicsRef.current.applyAttackMovement(\n basePos,\n movementResult,\n elapsedSeconds,\n recovering,\n );\n\n const newPosition: [number, number, number] = [\n newPos.x,\n newPos.y,\n newPos.z,\n ];\n\n setPlayer2Position(newPosition);\n\n const totalProgress = elapsedSeconds / movementResult.totalDuration;\n setPlayer2IsLunging(totalProgress < 0.5);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n } else {\n setPlayer2Position(player2BasePosition);\n setPlayer2IsLunging(false);\n player2AttackStartTimeRef.current = null;\n player2MovementResultRef.current = null;\n }\n }, [\n player2Attacking,\n player2AnimationType,\n player2Stance,\n player1BasePosition,\n player2BasePosition,\n p2Duration,\n ]);\n\n return {\n player1Position,\n player2Position,\n player1IsLunging,\n player2IsLunging,\n };\n}\n"],"mappings":"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"}