blacktrigram 0.7.47 → 0.7.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +29 -25
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  21. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  22. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  31. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  32. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  36. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatBottomHUD.d.ts.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
  39. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +1 -1
  42. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  44. package/lib/components/screens/combat/components/hud/CombatTopHUD.d.ts.map +1 -1
  45. package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -1
  46. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  47. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  48. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  49. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  50. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  51. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  52. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  53. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  54. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  55. package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
  56. package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
  57. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  58. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  59. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  60. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  61. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  62. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  63. package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
  64. package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
  65. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  66. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  67. package/lib/components/screens/controls/ControlsScreen3D.js +1 -1
  68. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  69. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  72. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  74. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  75. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  76. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  77. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  78. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  79. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  84. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  85. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  86. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  87. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  88. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  90. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  93. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/philosophy/PhilosophyScreen3D.js +1 -1
  96. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  97. package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
  98. package/lib/components/screens/training/TrainingScreen3D.js +3 -11
  99. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  100. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  101. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  102. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  103. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  104. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  105. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  106. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  107. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  108. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  109. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  110. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  111. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  112. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  113. package/lib/components/screens/training/components/hud/TrainingBottomHUD.d.ts.map +1 -1
  114. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
  115. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  116. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  117. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  118. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  119. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  120. package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
  121. package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
  122. package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
  123. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  124. package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
  125. package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
  126. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  127. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  128. package/lib/components/shared/base/BaseButton.js.map +1 -1
  129. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  130. package/lib/components/shared/base/BasePanel.js.map +1 -1
  131. package/lib/components/shared/base/BaseText.js.map +1 -1
  132. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  133. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  134. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  135. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  136. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  137. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  138. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  139. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  140. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  141. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  142. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  143. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  144. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  145. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  146. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  147. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  148. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  149. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  150. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  151. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  152. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  153. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  154. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  155. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  156. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  157. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  158. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  159. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  160. package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
  161. package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
  162. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  163. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  164. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  165. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  166. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  167. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  168. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  169. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  170. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  171. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  172. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  173. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  174. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  175. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  176. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  177. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  178. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  179. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  180. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  181. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  182. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  183. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  184. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  185. package/lib/components/shared/ui/BackButton.js.map +1 -1
  186. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  187. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  188. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  189. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  190. package/lib/components/shared/ui/SplashScreen.js +2 -2
  191. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  192. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  193. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  194. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  195. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  196. package/lib/constants/bodyDimensions.js.map +1 -1
  197. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  198. package/lib/data/archetypeClothing.js.map +1 -1
  199. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  200. package/lib/data/techniqueMappings.js.map +1 -1
  201. package/lib/data/techniques.js.map +1 -1
  202. package/lib/hooks/useActionFeedback.js.map +1 -1
  203. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  204. package/lib/hooks/useCombatTimer.js.map +1 -1
  205. package/lib/hooks/useDebounce.js.map +1 -1
  206. package/lib/hooks/useHUDLayout.d.ts.map +1 -1
  207. package/lib/hooks/useHUDLayout.js +3 -2
  208. package/lib/hooks/useHUDLayout.js.map +1 -1
  209. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  210. package/lib/hooks/useKeyboardControls.js.map +1 -1
  211. package/lib/hooks/useMatchCountdown.js.map +1 -1
  212. package/lib/hooks/useMuscleActivation.js.map +1 -1
  213. package/lib/hooks/usePauseMenu.js.map +1 -1
  214. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  215. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  216. package/lib/hooks/useRoundTransition.js.map +1 -1
  217. package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
  218. package/lib/hooks/useSkeletalAnimation.js +1 -1
  219. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  220. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  221. package/lib/hooks/useThrottle.js.map +1 -1
  222. package/lib/hooks/useTouchControls.js.map +1 -1
  223. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  224. package/lib/hooks/useWindowSize.js.map +1 -1
  225. package/lib/systems/CombatSystem.js.map +1 -1
  226. package/lib/systems/EffectCalculator.js.map +1 -1
  227. package/lib/systems/LayoutSystem.js.map +1 -1
  228. package/lib/systems/PlayerEffectManager.js.map +1 -1
  229. package/lib/systems/ResponsiveScaling.js.map +1 -1
  230. package/lib/systems/TrigramSystem.js.map +1 -1
  231. package/lib/systems/VitalPointSystem.js.map +1 -1
  232. package/lib/systems/ai/AIPersonality.js.map +1 -1
  233. package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
  234. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  235. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  236. package/lib/systems/ai/ComboSystem.js.map +1 -1
  237. package/lib/systems/ai/DecisionTree.js.map +1 -1
  238. package/lib/systems/ai/TrainingAI.js.map +1 -1
  239. package/lib/systems/ai/types.js.map +1 -1
  240. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  241. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  242. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  243. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  244. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  245. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
  246. package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
  247. package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
  248. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  249. package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
  250. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  251. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
  252. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
  253. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
  254. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  255. package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
  256. package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
  257. package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
  258. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  259. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  260. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  261. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  262. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  263. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  264. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  265. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  266. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  267. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  268. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  269. package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
  270. package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
  271. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  272. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  273. package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
  274. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
  275. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
  276. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  277. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  278. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  279. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  280. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
  281. package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
  282. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
  283. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
  284. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  285. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  286. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  287. package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
  288. package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
  289. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  290. package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
  291. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  292. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  293. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  294. package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
  295. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  296. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  297. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  298. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  299. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  300. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  301. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  302. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  303. package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
  304. package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
  305. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  306. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  307. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  308. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  309. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  310. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  311. package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
  312. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  313. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  314. package/lib/systems/animation/core/AnimationPriority.js +15 -15
  315. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  316. package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
  317. package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
  318. package/lib/systems/animation/core/AnimationRegistry.js +74 -12
  319. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  320. package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
  321. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  322. package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
  323. package/lib/systems/animation/core/AnimationTransitions.js +34 -0
  324. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  325. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  326. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  327. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  328. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  329. package/lib/systems/animation/core/index.d.ts +1 -1
  330. package/lib/systems/animation/core/index.d.ts.map +1 -1
  331. package/lib/systems/animation/core/types.d.ts +24 -0
  332. package/lib/systems/animation/core/types.d.ts.map +1 -1
  333. package/lib/systems/animation/core/types.js +27 -11
  334. package/lib/systems/animation/core/types.js.map +1 -1
  335. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  336. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  337. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  338. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  339. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  340. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  341. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  342. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  343. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  344. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  345. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  346. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  347. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  348. package/lib/systems/bodypart/types.js.map +1 -1
  349. package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
  350. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  351. package/lib/systems/breathing/feedback.js.map +1 -1
  352. package/lib/systems/breathing/integration.js.map +1 -1
  353. package/lib/systems/combat/BalanceSystem.js +19 -19
  354. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  355. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  356. package/lib/systems/combat/CombatStateSystem.js +17 -17
  357. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  358. package/lib/systems/combat/ConsciousnessSystem.js +24 -24
  359. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  360. package/lib/systems/combat/FallIntegration.js.map +1 -1
  361. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  362. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  363. package/lib/systems/combat/PainResponseSystem.js +21 -21
  364. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  365. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  366. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  367. package/lib/systems/combat/typeGuards.js.map +1 -1
  368. package/lib/systems/effects.js.map +1 -1
  369. package/lib/systems/game.js.map +1 -1
  370. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  371. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  372. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  373. package/lib/systems/movement/integration.js.map +1 -1
  374. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  375. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  376. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  377. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  378. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  379. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  380. package/lib/systems/physics/SpeedModifierSystem.js +6 -6
  381. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  382. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  383. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  384. package/lib/systems/trigram/StanceManager.js.map +1 -1
  385. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  386. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  387. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  388. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  389. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  390. package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
  391. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  392. package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
  393. package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
  394. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  395. package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
  396. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  397. package/lib/systems/trigram/techniques/index.js.map +1 -1
  398. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  399. package/lib/systems/trigram/types.js.map +1 -1
  400. package/lib/systems/types.js.map +1 -1
  401. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  402. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  403. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  404. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  405. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  406. package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
  407. package/lib/types/AccessibilityTypes.js.map +1 -1
  408. package/lib/types/LayoutTypes.js.map +1 -1
  409. package/lib/types/PhysicsTypes.js.map +1 -1
  410. package/lib/types/common.js.map +1 -1
  411. package/lib/types/constants/animations.js.map +1 -1
  412. package/lib/types/constants/colors.js.map +1 -1
  413. package/lib/types/constants/designSystem.js.map +1 -1
  414. package/lib/types/constants/index.js.map +1 -1
  415. package/lib/types/constants/layout.d.ts +21 -0
  416. package/lib/types/constants/layout.d.ts.map +1 -1
  417. package/lib/types/constants/layout.js +22 -1
  418. package/lib/types/constants/layout.js.map +1 -1
  419. package/lib/types/constants/performance.js.map +1 -1
  420. package/lib/types/constants/typography.js.map +1 -1
  421. package/lib/types/constants/ui.js.map +1 -1
  422. package/lib/types/facial.js +19 -19
  423. package/lib/types/facial.js.map +1 -1
  424. package/lib/types/hand-animation.js.map +1 -1
  425. package/lib/types/injury.js.map +1 -1
  426. package/lib/types/muscle.js.map +1 -1
  427. package/lib/types/physics.js.map +1 -1
  428. package/lib/types/physicsConstants.js.map +1 -1
  429. package/lib/types/player-visual.d.ts +1 -1
  430. package/lib/types/player-visual.d.ts.map +1 -1
  431. package/lib/types/skeletal.js.map +1 -1
  432. package/lib/types/techniqueId.js.map +1 -1
  433. package/lib/utils/accessibility.js.map +1 -1
  434. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  435. package/lib/utils/assetConfig.js.map +1 -1
  436. package/lib/utils/characterScaling.js.map +1 -1
  437. package/lib/utils/colorHelpers.js.map +1 -1
  438. package/lib/utils/colorUtils.js.map +1 -1
  439. package/lib/utils/combatReadiness.js.map +1 -1
  440. package/lib/utils/controlMapping.js.map +1 -1
  441. package/lib/utils/deviceDetection.js +6 -7
  442. package/lib/utils/deviceDetection.js.map +1 -1
  443. package/lib/utils/effectUtils.js.map +1 -1
  444. package/lib/utils/fabricTextures.js.map +1 -1
  445. package/lib/utils/hapticFeedback.js.map +1 -1
  446. package/lib/utils/haptics.js.map +1 -1
  447. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  448. package/lib/utils/inputSystem.js.map +1 -1
  449. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  450. package/lib/utils/math.js.map +1 -1
  451. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  452. package/lib/utils/mobileUIUtils.js.map +1 -1
  453. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  454. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  455. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  456. package/lib/utils/performanceOptimization.js.map +1 -1
  457. package/lib/utils/player3DHelpers.js.map +1 -1
  458. package/lib/utils/playerUtils.js.map +1 -1
  459. package/lib/utils/responsiveLayout.js.map +1 -1
  460. package/lib/utils/responsiveLayoutHelpers.d.ts +7 -0
  461. package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
  462. package/lib/utils/responsiveLayoutHelpers.js +16 -2
  463. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  464. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  465. package/lib/utils/safeAreaUtils.js.map +1 -1
  466. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  467. package/lib/utils/skeletonScaling.js.map +1 -1
  468. package/lib/utils/stanceHelpers.js.map +1 -1
  469. package/lib/utils/threeObjectPool.js.map +1 -1
  470. package/lib/utils/visualEffects.js.map +1 -1
  471. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"techniques.js","names":[],"sources":["../../src/data/techniques.ts"],"sourcesContent":["/**\n * Technique definitions for all player archetypes.\n *\n * **Korean**: 기술 정의 (Technique Definitions)\n *\n * Each archetype has 3-5 unique techniques that reflect their combat philosophy\n * and specialization. Techniques are mapped to keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C.\n *\n * @module data/techniques\n * @category Combat System\n * @korean 기술데이터\n */\n\nimport { KoreanTechniquesSystem } from \"../systems/trigram/KoreanTechniques\";\nimport { KoreanTechnique } from \"../systems/vitalpoint/types\";\nimport {\n DamageType,\n PlayerArchetype,\n Technique,\n TechniqueKey,\n TrigramStance,\n} from \"../types\";\nimport { AttackAnimationType } from \"../types/skeletal\";\n\n/**\n * Technique definitions for 무사 (Musa) - Traditional Warrior.\n *\n * Philosophy: Honor through disciplined strength and overwhelming force.\n * Favored Stance: ☰ 건 (Geon) - Heaven\n */\nexport const MUSA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"musa_thunder_strike\",\n name: {\n korean: \"천둥벽력\",\n english: \"Thunder Strike\",\n },\n description: {\n korean: \"강력한 하늘의 힘으로 적을 강타합니다\",\n english: \"Strike with the power of heaven\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.BLUNT,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.9, // Powerful strike, slightly slower\n },\n },\n {\n id: \"musa_iron_defense\",\n name: {\n korean: \"철벽방어\",\n english: \"Iron Defense\",\n },\n description: {\n korean: \"산처럼 굳건한 방어 자세를 취합니다\",\n english: \"Adopt an immovable defensive stance\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 0, max: 5 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GAN,\n keyboardShortcut: \"T\",\n specialEffect: \"defense_boost\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.0, // Normal defensive speed\n },\n },\n {\n id: \"musa_dragon_fist\",\n name: {\n korean: \"용권\",\n english: \"Dragon Fist\",\n },\n description: {\n korean: \"용의 기세로 적을 관통합니다\",\n english: \"Pierce through with dragon's might\",\n },\n staminaCost: 18,\n kiCost: 12,\n damage: { min: 30, max: 40 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 0.8, // Heavy piercing strike\n },\n },\n {\n id: \"musa_mountain_breaker\",\n name: {\n korean: \"파산격\",\n english: \"Mountain Breaker\",\n },\n description: {\n korean: \"산을 깨뜨리는 강력한 일격\",\n english: \"A devastating blow that shatters mountains\",\n },\n staminaCost: 20,\n kiCost: 15,\n damage: { min: 35, max: 50 },\n damageType: DamageType.CRUSHING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"R\",\n criticalChance: 0.2,\n specialEffect: \"armor_break\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.8, // Powerful, slower strike\n },\n },\n];\n\n/**\n * Technique definitions for 암살자 (Amsalja) - Shadow Assassin.\n *\n * Philosophy: Precision through stealth and vital point mastery.\n * Favored Stance: ☲ 리 (Li) - Fire\n */\nexport const AMSALJA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"amsalja_shadow_strike\",\n name: {\n korean: \"암영격\",\n english: \"Shadow Strike\",\n },\n description: {\n korean: \"그림자처럼 빠르게 급소를 노립니다\",\n english: \"Strike vital points with shadow speed\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 35 },\n damageType: DamageType.NERVE,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.4,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.2, // Very fast, precise strike\n },\n },\n {\n id: \"amsalja_nerve_strike\",\n name: {\n korean: \"신경타\",\n english: \"Nerve Strike\",\n },\n description: {\n korean: \"정확한 신경 타격으로 적을 마비시킵니다\",\n english: \"Paralyze the enemy with precise nerve strikes\",\n },\n staminaCost: 15,\n kiCost: 12,\n damage: { min: 15, max: 25 },\n damageType: DamageType.NERVE,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n specialEffect: \"paralysis\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Fast precision\n },\n },\n {\n id: \"amsalja_deadly_precision\",\n name: {\n korean: \"치명정밀\",\n english: \"Deadly Precision\",\n },\n description: {\n korean: \"완벽한 정밀도로 치명적인 급소를 공격합니다\",\n english: \"Attack critical vital points with perfect accuracy\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 25, max: 45 },\n damageType: DamageType.PRESSURE,\n cooldown: 1400, // animationDuration (900) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.5,\n animationDuration: 900,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Precise, deliberate\n },\n },\n {\n id: \"amsalja_silent_death\",\n name: {\n korean: \"무음살\",\n english: \"Silent Death\",\n },\n description: {\n korean: \"소리 없이 치명적인 일격을 가합니다\",\n english: \"Deliver a silent, lethal strike\",\n },\n staminaCost: 22,\n kiCost: 18,\n damage: { min: 40, max: 60 },\n damageType: DamageType.NERVE,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"instant_kill_chance\",\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful lethal strike\n },\n },\n];\n\n/**\n * Technique definitions for 해커 (Hacker) - Cyber Warrior.\n *\n * Philosophy: Technology-enhanced combat with data-driven precision.\n * Favored Stance: ☳ 진 (Jin) - Thunder\n */\nexport const HACKER_TECHNIQUES: readonly Technique[] = [\n {\n id: \"hacker_electric_shock\",\n name: {\n korean: \"전격\",\n english: \"Electric Shock\",\n },\n description: {\n korean: \"사이버 임플란트로 전기 충격을 가합니다\",\n english: \"Deliver electric shock via cyber implants\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 18, max: 28 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1000, // animationDuration (500) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"Q\",\n specialEffect: \"stun\",\n animationDuration: 500,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast electric strike\n },\n },\n {\n id: \"hacker_data_strike\",\n name: {\n korean: \"데이터 타격\",\n english: \"Data Strike\",\n },\n description: {\n korean: \"전투 데이터를 분석하여 최적의 공격을 수행합니다\",\n english: \"Analyze combat data for optimal attack\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 32 },\n damageType: DamageType.PIERCING,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n criticalChance: 0.35,\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Data-enhanced precision\n },\n },\n {\n id: \"hacker_cyber_overdrive\",\n name: {\n korean: \"사이버 가속\",\n english: \"Cyber Overdrive\",\n },\n description: {\n korean: \"임플란트를 과부하시켜 초고속 공격을 수행합니다\",\n english: \"Overload implants for lightning-fast attacks\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 15, max: 25 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"E\",\n specialEffect: \"multi_hit\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT_RAPID,\n speedModifier: 1.3, // Ultra-fast combo\n },\n },\n {\n id: \"hacker_system_crash\",\n name: {\n korean: \"시스템 크래시\",\n english: \"System Crash\",\n },\n description: {\n korean: \"적의 신경 시스템을 해킹하여 무력화합니다\",\n english: \"Hack the enemy's nervous system\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 30, max: 45 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1600, // animationDuration (1100) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n specialEffect: \"system_shutdown\",\n animationDuration: 1100,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Calculated precision\n },\n },\n];\n\n/**\n * Technique definitions for 정보요원 (Jeongbo Yowon) - Intelligence Operative.\n *\n * Philosophy: Strategic analysis and exploiting weaknesses.\n * Favored Stance: ☵ 감 (Gam) - Water\n */\nexport const JEONGBO_YOWON_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jeongbo_tactical_strike\",\n name: {\n korean: \"전술타격\",\n english: \"Tactical Strike\",\n },\n description: {\n korean: \"적의 약점을 분석하여 전술적으로 공격합니다\",\n english: \"Analyze and strike enemy weaknesses tactically\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 30 },\n damageType: DamageType.PIERCING,\n cooldown: 1150, // animationDuration (650) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 650,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Strategic precision\n },\n },\n {\n id: \"jeongbo_counter_intelligence\",\n name: {\n korean: \"역정보공작\",\n english: \"Counter Intelligence\",\n },\n description: {\n korean: \"적의 움직임을 읽고 반격합니다\",\n english: \"Read and counter enemy movements\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 15, max: 30 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"counter_stance\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast counter\n },\n },\n {\n id: \"jeongbo_psychological_warfare\",\n name: {\n korean: \"심리전\",\n english: \"Psychological Warfare\",\n },\n description: {\n korean: \"적의 정신을 교란하여 약화시킵니다\",\n english: \"Disrupt enemy's mental state\",\n },\n staminaCost: 15,\n kiCost: 15,\n damage: { min: 10, max: 20 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"E\",\n specialEffect: \"confusion\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Deliberate mental attack\n },\n },\n {\n id: \"jeongbo_intelligence_strike\",\n name: {\n korean: \"정보타격\",\n english: \"Intelligence Strike\",\n },\n description: {\n korean: \"수집한 정보를 바탕으로 완벽한 공격을 수행합니다\",\n english: \"Execute perfect attack based on gathered intelligence\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 35, max: 50 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.45,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful decisive strike\n },\n },\n {\n id: \"jeongbo_precision_takedown\",\n name: {\n korean: \"정밀제압\",\n english: \"Precision Takedown\",\n },\n description: {\n korean: \"무력한 적을 정밀하게 제압합니다 - 서명 기술\",\n english: \"Precisely takedown helpless opponent - Signature Move\",\n },\n staminaCost: 25,\n kiCost: 20,\n damage: { min: 45, max: 65 },\n damageType: DamageType.PIERCING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Y\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"signature_move\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.8, // Deliberate, precise execution\n },\n },\n];\n\n/**\n * Technique definitions for 조직폭력배 (Jojik Pokryeokbae) - Organized Crime.\n *\n * Philosophy: Ruthless pragmatism and brutal efficiency.\n * Favored Stance: ☷ 곤 (Gon) - Earth\n */\nexport const JOJIK_POKRYEOKBAE_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jojik_street_brawl\",\n name: {\n korean: \"거리싸움\",\n english: \"Street Brawl\",\n },\n description: {\n korean: \"더러운 싸움으로 적을 제압합니다\",\n english: \"Overwhelm with dirty fighting\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 18, max: 28 },\n damageType: DamageType.BLUNT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.1, // Fast brawl strike\n },\n },\n {\n id: \"jojik_brutal_takedown\",\n name: {\n korean: \"잔혹제압\",\n english: \"Brutal Takedown\",\n },\n description: {\n korean: \"무자비하게 적을 쓰러뜨립니다\",\n english: \"Mercilessly takedown the enemy\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.CRUSHING,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"knockdown\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.ELBOW_STRIKE,\n speedModifier: 0.9, // Heavy takedown\n },\n },\n {\n id: \"jojik_improvised_weapon\",\n name: {\n korean: \"즉석무기\",\n english: \"Improvised Weapon\",\n },\n description: {\n korean: \"주변 물건을 이용하여 공격합니다\",\n english: \"Attack using improvised objects\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 38 },\n damageType: DamageType.SHARP,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"E\",\n criticalChance: 0.3,\n specialEffect: \"bleed\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 1.0, // Unpredictable weapon strike\n },\n },\n {\n id: \"jojik_ruthless_assault\",\n name: {\n korean: \"무자비공격\",\n english: \"Ruthless Assault\",\n },\n description: {\n korean: \"자비 없이 적을 집중 공격합니다\",\n english: \"Relentlessly assault without mercy\",\n },\n staminaCost: 22,\n kiCost: 12,\n damage: { min: 30, max: 55 },\n damageType: DamageType.CRUSHING,\n cooldown: 1800, // animationDuration (1300) + recovery (500)\n keyboardShortcut: \"R\",\n criticalChance: 0.35,\n specialEffect: \"rage\",\n animationDuration: 1300,\n animation: {\n type: AttackAnimationType.PUNCH_LOW,\n speedModifier: 0.8, // Heavy brutal assault\n },\n },\n];\n\n/**\n * Get techniques for a specific player archetype.\n *\n * @param archetype - Player archetype\n * @returns Array of techniques for the archetype\n *\n * @public\n */\nexport function getTechniquesForArchetype(\n archetype: PlayerArchetype,\n): readonly Technique[] {\n switch (archetype) {\n case PlayerArchetype.MUSA:\n return MUSA_TECHNIQUES;\n case PlayerArchetype.AMSALJA:\n return AMSALJA_TECHNIQUES;\n case PlayerArchetype.HACKER:\n return HACKER_TECHNIQUES;\n case PlayerArchetype.JEONGBO_YOWON:\n return JEONGBO_YOWON_TECHNIQUES;\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n return JOJIK_POKRYEOKBAE_TECHNIQUES;\n default:\n // Exhaustive check: if a new archetype is added, TypeScript will error here\n const _exhaustiveCheck: never = archetype;\n throw new Error(`Unknown archetype: ${_exhaustiveCheck}`);\n }\n}\n\n/**\n * Convert KoreanTechnique to Technique format for UI compatibility\n */\nfunction convertKoreanToTechnique(koreanTech: KoreanTechnique): Technique {\n // Convert string damageType to DamageType enum\n const getDamageType = (type: string): DamageType => {\n // Map common string values to DamageType enum\n const typeMap: Record<string, DamageType> = {\n blunt: DamageType.BLUNT,\n physical: DamageType.BLUNT,\n piercing: DamageType.PIERCING,\n slashing: DamageType.SLASHING,\n crushing: DamageType.CRUSHING,\n impact: DamageType.IMPACT,\n joint: DamageType.JOINT,\n electric: DamageType.ELECTRIC,\n psychic: DamageType.PSYCHIC,\n };\n return typeMap[type.toLowerCase()] ?? DamageType.BLUNT;\n };\n\n return {\n id: koreanTech.id,\n name: {\n korean: koreanTech.koreanName || koreanTech.name.korean,\n english: koreanTech.englishName || koreanTech.name.english,\n romanized: koreanTech.romanized || koreanTech.name.romanized,\n },\n description: koreanTech.description,\n staminaCost: koreanTech.staminaCost,\n kiCost: koreanTech.kiCost,\n damage: {\n min: Math.floor(koreanTech.damage * 0.8),\n max: Math.ceil(koreanTech.damage * 1.2),\n },\n damageType: getDamageType(koreanTech.damageType),\n cooldown: koreanTech.recoveryTime + koreanTech.executionTime,\n requiredStance: koreanTech.stance,\n keyboardShortcut: \"Q\", // Placeholder; immediately overridden by getTechniquesForStanceAndArchetype()\n criticalChance: koreanTech.critChance,\n animationDuration: koreanTech.executionTime,\n };\n}\n\n/**\n * Get techniques for a player based on their current stance and archetype.\n * Combines trigram stance techniques with archetype-specific bonuses.\n *\n * @param stance - Current player stance\n * @param archetype - Player archetype\n * @returns Array of available techniques with proper keyboard shortcuts assigned\n *\n * @public\n */\nexport function getTechniquesForStanceAndArchetype(\n stance: TrigramStance,\n archetype: PlayerArchetype,\n): readonly Technique[] {\n // Get stance-based techniques from Korean martial arts system\n const koreanTechniques = KoreanTechniquesSystem.getAllAvailableTechniques(\n stance,\n archetype,\n );\n\n // Convert to Technique format\n const convertedTechniques = koreanTechniques.map(convertKoreanToTechnique);\n\n // Also include archetype-specific special techniques\n const archetypeTechniques = getTechniquesForArchetype(archetype);\n\n // Filter archetype techniques to only include those matching current stance or no stance requirement\n const filteredArchetypeTechniques = archetypeTechniques.filter(\n (tech) => !tech.requiredStance || tech.requiredStance === stance,\n );\n\n // Combine both sets, prioritizing stance techniques\n const allTechniques = [\n ...convertedTechniques,\n ...filteredArchetypeTechniques,\n ];\n\n // Limit to maximum 10 techniques (keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C)\n const limitedTechniques = allTechniques.slice(0, 10);\n\n // Assign keyboard shortcuts using conflict-free keys around WASD\n const keyboardShortcuts = [\n \"Q\",\n \"E\",\n \"R\",\n \"T\",\n \"Y\",\n \"F\",\n \"G\",\n \"Z\",\n \"X\",\n \"C\",\n ] as const;\n return limitedTechniques.map((tech, index) => ({\n ...tech,\n keyboardShortcut: keyboardShortcuts[index] as TechniqueKey,\n }));\n}\n\n/**\n * Get a specific technique by ID.\n *\n * @param techniqueId - Unique technique identifier\n * @returns Technique if found, undefined otherwise\n *\n * @public\n */\nexport function getTechniqueById(techniqueId: string): Technique | undefined {\n const allTechniques = [\n ...MUSA_TECHNIQUES,\n ...AMSALJA_TECHNIQUES,\n ...HACKER_TECHNIQUES,\n ...JEONGBO_YOWON_TECHNIQUES,\n ...JOJIK_POKRYEOKBAE_TECHNIQUES,\n ];\n\n return allTechniques.find((tech) => tech.id === techniqueId);\n}\n\nexport default {\n getTechniquesForArchetype,\n getTechniquesForStanceAndArchetype,\n getTechniqueById,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"techniques.js","names":[],"sources":["../../src/data/techniques.ts"],"sourcesContent":["/**\n * Technique definitions for all player archetypes.\n *\n * **Korean**: 기술 정의 (Technique Definitions)\n *\n * Each archetype has 3-5 unique techniques that reflect their combat philosophy\n * and specialization. Techniques are mapped to keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C.\n *\n * @module data/techniques\n * @category Combat System\n * @korean 기술데이터\n */\n\nimport { KoreanTechniquesSystem } from \"../systems/trigram/KoreanTechniques\";\nimport { KoreanTechnique } from \"../systems/vitalpoint/types\";\nimport {\n DamageType,\n PlayerArchetype,\n Technique,\n TechniqueKey,\n TrigramStance,\n} from \"../types\";\nimport { AttackAnimationType } from \"../types/skeletal\";\n\n/**\n * Technique definitions for 무사 (Musa) - Traditional Warrior.\n *\n * Philosophy: Honor through disciplined strength and overwhelming force.\n * Favored Stance: ☰ 건 (Geon) - Heaven\n */\nexport const MUSA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"musa_thunder_strike\",\n name: {\n korean: \"천둥벽력\",\n english: \"Thunder Strike\",\n },\n description: {\n korean: \"강력한 하늘의 힘으로 적을 강타합니다\",\n english: \"Strike with the power of heaven\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.BLUNT,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.9, // Powerful strike, slightly slower\n },\n },\n {\n id: \"musa_iron_defense\",\n name: {\n korean: \"철벽방어\",\n english: \"Iron Defense\",\n },\n description: {\n korean: \"산처럼 굳건한 방어 자세를 취합니다\",\n english: \"Adopt an immovable defensive stance\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 0, max: 5 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GAN,\n keyboardShortcut: \"T\",\n specialEffect: \"defense_boost\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.0, // Normal defensive speed\n },\n },\n {\n id: \"musa_dragon_fist\",\n name: {\n korean: \"용권\",\n english: \"Dragon Fist\",\n },\n description: {\n korean: \"용의 기세로 적을 관통합니다\",\n english: \"Pierce through with dragon's might\",\n },\n staminaCost: 18,\n kiCost: 12,\n damage: { min: 30, max: 40 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 0.8, // Heavy piercing strike\n },\n },\n {\n id: \"musa_mountain_breaker\",\n name: {\n korean: \"파산격\",\n english: \"Mountain Breaker\",\n },\n description: {\n korean: \"산을 깨뜨리는 강력한 일격\",\n english: \"A devastating blow that shatters mountains\",\n },\n staminaCost: 20,\n kiCost: 15,\n damage: { min: 35, max: 50 },\n damageType: DamageType.CRUSHING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GEON,\n keyboardShortcut: \"R\",\n criticalChance: 0.2,\n specialEffect: \"armor_break\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 0.8, // Powerful, slower strike\n },\n },\n];\n\n/**\n * Technique definitions for 암살자 (Amsalja) - Shadow Assassin.\n *\n * Philosophy: Precision through stealth and vital point mastery.\n * Favored Stance: ☲ 리 (Li) - Fire\n */\nexport const AMSALJA_TECHNIQUES: readonly Technique[] = [\n {\n id: \"amsalja_shadow_strike\",\n name: {\n korean: \"암영격\",\n english: \"Shadow Strike\",\n },\n description: {\n korean: \"그림자처럼 빠르게 급소를 노립니다\",\n english: \"Strike vital points with shadow speed\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 35 },\n damageType: DamageType.NERVE,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.4,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.2, // Very fast, precise strike\n },\n },\n {\n id: \"amsalja_nerve_strike\",\n name: {\n korean: \"신경타\",\n english: \"Nerve Strike\",\n },\n description: {\n korean: \"정확한 신경 타격으로 적을 마비시킵니다\",\n english: \"Paralyze the enemy with precise nerve strikes\",\n },\n staminaCost: 15,\n kiCost: 12,\n damage: { min: 15, max: 25 },\n damageType: DamageType.NERVE,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n specialEffect: \"paralysis\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Fast precision\n },\n },\n {\n id: \"amsalja_deadly_precision\",\n name: {\n korean: \"치명정밀\",\n english: \"Deadly Precision\",\n },\n description: {\n korean: \"완벽한 정밀도로 치명적인 급소를 공격합니다\",\n english: \"Attack critical vital points with perfect accuracy\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 25, max: 45 },\n damageType: DamageType.PRESSURE,\n cooldown: 1400, // animationDuration (900) + recovery (500)\n requiredStance: TrigramStance.LI,\n keyboardShortcut: \"E\",\n targetsVitalPoint: true,\n criticalChance: 0.5,\n animationDuration: 900,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Precise, deliberate\n },\n },\n {\n id: \"amsalja_silent_death\",\n name: {\n korean: \"무음살\",\n english: \"Silent Death\",\n },\n description: {\n korean: \"소리 없이 치명적인 일격을 가합니다\",\n english: \"Deliver a silent, lethal strike\",\n },\n staminaCost: 22,\n kiCost: 18,\n damage: { min: 40, max: 60 },\n damageType: DamageType.NERVE,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"instant_kill_chance\",\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful lethal strike\n },\n },\n];\n\n/**\n * Technique definitions for 해커 (Hacker) - Cyber Warrior.\n *\n * Philosophy: Technology-enhanced combat with data-driven precision.\n * Favored Stance: ☳ 진 (Jin) - Thunder\n */\nexport const HACKER_TECHNIQUES: readonly Technique[] = [\n {\n id: \"hacker_electric_shock\",\n name: {\n korean: \"전격\",\n english: \"Electric Shock\",\n },\n description: {\n korean: \"사이버 임플란트로 전기 충격을 가합니다\",\n english: \"Deliver electric shock via cyber implants\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 18, max: 28 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1000, // animationDuration (500) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"Q\",\n specialEffect: \"stun\",\n animationDuration: 500,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast electric strike\n },\n },\n {\n id: \"hacker_data_strike\",\n name: {\n korean: \"데이터 타격\",\n english: \"Data Strike\",\n },\n description: {\n korean: \"전투 데이터를 분석하여 최적의 공격을 수행합니다\",\n english: \"Analyze combat data for optimal attack\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 32 },\n damageType: DamageType.PIERCING,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n keyboardShortcut: \"T\",\n targetsVitalPoint: true,\n criticalChance: 0.35,\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Data-enhanced precision\n },\n },\n {\n id: \"hacker_cyber_overdrive\",\n name: {\n korean: \"사이버 가속\",\n english: \"Cyber Overdrive\",\n },\n description: {\n korean: \"임플란트를 과부하시켜 초고속 공격을 수행합니다\",\n english: \"Overload implants for lightning-fast attacks\",\n },\n staminaCost: 18,\n kiCost: 15,\n damage: { min: 15, max: 25 },\n damageType: DamageType.ELECTRIC,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.JIN,\n keyboardShortcut: \"E\",\n specialEffect: \"multi_hit\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT_RAPID,\n speedModifier: 1.3, // Ultra-fast combo\n },\n },\n {\n id: \"hacker_system_crash\",\n name: {\n korean: \"시스템 크래시\",\n english: \"System Crash\",\n },\n description: {\n korean: \"적의 신경 시스템을 해킹하여 무력화합니다\",\n english: \"Hack the enemy's nervous system\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 30, max: 45 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1600, // animationDuration (1100) + recovery (500)\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n specialEffect: \"system_shutdown\",\n animationDuration: 1100,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Calculated precision\n },\n },\n];\n\n/**\n * Technique definitions for 정보요원 (Jeongbo Yowon) - Intelligence Operative.\n *\n * Philosophy: Strategic analysis and exploiting weaknesses.\n * Favored Stance: ☵ 감 (Gam) - Water\n */\nexport const JEONGBO_YOWON_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jeongbo_tactical_strike\",\n name: {\n korean: \"전술타격\",\n english: \"Tactical Strike\",\n },\n description: {\n korean: \"적의 약점을 분석하여 전술적으로 공격합니다\",\n english: \"Analyze and strike enemy weaknesses tactically\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 20, max: 30 },\n damageType: DamageType.PIERCING,\n cooldown: 1150, // animationDuration (650) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Q\",\n targetsVitalPoint: true,\n criticalChance: 0.3,\n animationDuration: 650,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.1, // Strategic precision\n },\n },\n {\n id: \"jeongbo_counter_intelligence\",\n name: {\n korean: \"역정보공작\",\n english: \"Counter Intelligence\",\n },\n description: {\n korean: \"적의 움직임을 읽고 반격합니다\",\n english: \"Read and counter enemy movements\",\n },\n staminaCost: 10,\n kiCost: 12,\n damage: { min: 15, max: 30 },\n damageType: DamageType.IMPACT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"counter_stance\",\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.2, // Fast counter\n },\n },\n {\n id: \"jeongbo_psychological_warfare\",\n name: {\n korean: \"심리전\",\n english: \"Psychological Warfare\",\n },\n description: {\n korean: \"적의 정신을 교란하여 약화시킵니다\",\n english: \"Disrupt enemy's mental state\",\n },\n staminaCost: 15,\n kiCost: 15,\n damage: { min: 10, max: 20 },\n damageType: DamageType.PSYCHIC,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"E\",\n specialEffect: \"confusion\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 1.0, // Deliberate mental attack\n },\n },\n {\n id: \"jeongbo_intelligence_strike\",\n name: {\n korean: \"정보타격\",\n english: \"Intelligence Strike\",\n },\n description: {\n korean: \"수집한 정보를 바탕으로 완벽한 공격을 수행합니다\",\n english: \"Execute perfect attack based on gathered intelligence\",\n },\n staminaCost: 20,\n kiCost: 18,\n damage: { min: 35, max: 50 },\n damageType: DamageType.PIERCING,\n cooldown: 1500, // animationDuration (1000) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"R\",\n targetsVitalPoint: true,\n criticalChance: 0.45,\n animationDuration: 1000,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.9, // Powerful decisive strike\n },\n },\n {\n id: \"jeongbo_precision_takedown\",\n name: {\n korean: \"정밀제압\",\n english: \"Precision Takedown\",\n },\n description: {\n korean: \"무력한 적을 정밀하게 제압합니다 - 서명 기술\",\n english: \"Precisely takedown helpless opponent - Signature Move\",\n },\n staminaCost: 25,\n kiCost: 20,\n damage: { min: 45, max: 65 },\n damageType: DamageType.PIERCING,\n cooldown: 1700, // animationDuration (1200) + recovery (500)\n requiredStance: TrigramStance.GAM,\n keyboardShortcut: \"Y\",\n targetsVitalPoint: true,\n criticalChance: 0.6,\n specialEffect: \"signature_move\",\n animationDuration: 1200,\n animation: {\n type: AttackAnimationType.PRESSURE_POINT,\n speedModifier: 0.8, // Deliberate, precise execution\n },\n },\n];\n\n/**\n * Technique definitions for 조직폭력배 (Jojik Pokryeokbae) - Organized Crime.\n *\n * Philosophy: Ruthless pragmatism and brutal efficiency.\n * Favored Stance: ☷ 곤 (Gon) - Earth\n */\nexport const JOJIK_POKRYEOKBAE_TECHNIQUES: readonly Technique[] = [\n {\n id: \"jojik_street_brawl\",\n name: {\n korean: \"거리싸움\",\n english: \"Street Brawl\",\n },\n description: {\n korean: \"더러운 싸움으로 적을 제압합니다\",\n english: \"Overwhelm with dirty fighting\",\n },\n staminaCost: 10,\n kiCost: 8,\n damage: { min: 18, max: 28 },\n damageType: DamageType.BLUNT,\n cooldown: 1100, // animationDuration (600) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"Q\",\n criticalChance: 0.25,\n animationDuration: 600,\n animation: {\n type: AttackAnimationType.PUNCH_MID,\n speedModifier: 1.1, // Fast brawl strike\n },\n },\n {\n id: \"jojik_brutal_takedown\",\n name: {\n korean: \"잔혹제압\",\n english: \"Brutal Takedown\",\n },\n description: {\n korean: \"무자비하게 적을 쓰러뜨립니다\",\n english: \"Mercilessly takedown the enemy\",\n },\n staminaCost: 15,\n kiCost: 10,\n damage: { min: 25, max: 35 },\n damageType: DamageType.CRUSHING,\n cooldown: 1300, // animationDuration (800) + recovery (500)\n keyboardShortcut: \"T\",\n specialEffect: \"knockdown\",\n animationDuration: 800,\n animation: {\n type: AttackAnimationType.ELBOW_STRIKE,\n speedModifier: 0.9, // Heavy takedown\n },\n },\n {\n id: \"jojik_improvised_weapon\",\n name: {\n korean: \"즉석무기\",\n english: \"Improvised Weapon\",\n },\n description: {\n korean: \"주변 물건을 이용하여 공격합니다\",\n english: \"Attack using improvised objects\",\n },\n staminaCost: 12,\n kiCost: 10,\n damage: { min: 22, max: 38 },\n damageType: DamageType.SHARP,\n cooldown: 1200, // animationDuration (700) + recovery (500)\n requiredStance: TrigramStance.GON,\n keyboardShortcut: \"E\",\n criticalChance: 0.3,\n specialEffect: \"bleed\",\n animationDuration: 700,\n animation: {\n type: AttackAnimationType.PUNCH_HIGH,\n speedModifier: 1.0, // Unpredictable weapon strike\n },\n },\n {\n id: \"jojik_ruthless_assault\",\n name: {\n korean: \"무자비공격\",\n english: \"Ruthless Assault\",\n },\n description: {\n korean: \"자비 없이 적을 집중 공격합니다\",\n english: \"Relentlessly assault without mercy\",\n },\n staminaCost: 22,\n kiCost: 12,\n damage: { min: 30, max: 55 },\n damageType: DamageType.CRUSHING,\n cooldown: 1800, // animationDuration (1300) + recovery (500)\n keyboardShortcut: \"R\",\n criticalChance: 0.35,\n specialEffect: \"rage\",\n animationDuration: 1300,\n animation: {\n type: AttackAnimationType.PUNCH_LOW,\n speedModifier: 0.8, // Heavy brutal assault\n },\n },\n];\n\n/**\n * Get techniques for a specific player archetype.\n *\n * @param archetype - Player archetype\n * @returns Array of techniques for the archetype\n *\n * @public\n */\nexport function getTechniquesForArchetype(\n archetype: PlayerArchetype,\n): readonly Technique[] {\n switch (archetype) {\n case PlayerArchetype.MUSA:\n return MUSA_TECHNIQUES;\n case PlayerArchetype.AMSALJA:\n return AMSALJA_TECHNIQUES;\n case PlayerArchetype.HACKER:\n return HACKER_TECHNIQUES;\n case PlayerArchetype.JEONGBO_YOWON:\n return JEONGBO_YOWON_TECHNIQUES;\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n return JOJIK_POKRYEOKBAE_TECHNIQUES;\n default:\n // Exhaustive check: if a new archetype is added, TypeScript will error here\n const _exhaustiveCheck: never = archetype;\n throw new Error(`Unknown archetype: ${_exhaustiveCheck}`);\n }\n}\n\n/**\n * Convert KoreanTechnique to Technique format for UI compatibility\n */\nfunction convertKoreanToTechnique(koreanTech: KoreanTechnique): Technique {\n // Convert string damageType to DamageType enum\n const getDamageType = (type: string): DamageType => {\n // Map common string values to DamageType enum\n const typeMap: Record<string, DamageType> = {\n blunt: DamageType.BLUNT,\n physical: DamageType.BLUNT,\n piercing: DamageType.PIERCING,\n slashing: DamageType.SLASHING,\n crushing: DamageType.CRUSHING,\n impact: DamageType.IMPACT,\n joint: DamageType.JOINT,\n electric: DamageType.ELECTRIC,\n psychic: DamageType.PSYCHIC,\n };\n return typeMap[type.toLowerCase()] ?? DamageType.BLUNT;\n };\n\n return {\n id: koreanTech.id,\n name: {\n korean: koreanTech.koreanName || koreanTech.name.korean,\n english: koreanTech.englishName || koreanTech.name.english,\n romanized: koreanTech.romanized || koreanTech.name.romanized,\n },\n description: koreanTech.description,\n staminaCost: koreanTech.staminaCost,\n kiCost: koreanTech.kiCost,\n damage: {\n min: Math.floor(koreanTech.damage * 0.8),\n max: Math.ceil(koreanTech.damage * 1.2),\n },\n damageType: getDamageType(koreanTech.damageType),\n cooldown: koreanTech.recoveryTime + koreanTech.executionTime,\n requiredStance: koreanTech.stance,\n keyboardShortcut: \"Q\", // Placeholder; immediately overridden by getTechniquesForStanceAndArchetype()\n criticalChance: koreanTech.critChance,\n animationDuration: koreanTech.executionTime,\n };\n}\n\n/**\n * Get techniques for a player based on their current stance and archetype.\n * Combines trigram stance techniques with archetype-specific bonuses.\n *\n * @param stance - Current player stance\n * @param archetype - Player archetype\n * @returns Array of available techniques with proper keyboard shortcuts assigned\n *\n * @public\n */\nexport function getTechniquesForStanceAndArchetype(\n stance: TrigramStance,\n archetype: PlayerArchetype,\n): readonly Technique[] {\n // Get stance-based techniques from Korean martial arts system\n const koreanTechniques = KoreanTechniquesSystem.getAllAvailableTechniques(\n stance,\n archetype,\n );\n\n // Convert to Technique format\n const convertedTechniques = koreanTechniques.map(convertKoreanToTechnique);\n\n // Also include archetype-specific special techniques\n const archetypeTechniques = getTechniquesForArchetype(archetype);\n\n // Filter archetype techniques to only include those matching current stance or no stance requirement\n const filteredArchetypeTechniques = archetypeTechniques.filter(\n (tech) => !tech.requiredStance || tech.requiredStance === stance,\n );\n\n // Combine both sets, prioritizing stance techniques\n const allTechniques = [\n ...convertedTechniques,\n ...filteredArchetypeTechniques,\n ];\n\n // Limit to maximum 10 techniques (keyboard shortcuts Q-E-R-T-Y-F-G-Z-X-C)\n const limitedTechniques = allTechniques.slice(0, 10);\n\n // Assign keyboard shortcuts using conflict-free keys around WASD\n const keyboardShortcuts = [\n \"Q\",\n \"E\",\n \"R\",\n \"T\",\n \"Y\",\n \"F\",\n \"G\",\n \"Z\",\n \"X\",\n \"C\",\n ] as const;\n return limitedTechniques.map((tech, index) => ({\n ...tech,\n keyboardShortcut: keyboardShortcuts[index] as TechniqueKey,\n }));\n}\n\n/**\n * Get a specific technique by ID.\n *\n * @param techniqueId - Unique technique identifier\n * @returns Technique if found, undefined otherwise\n *\n * @public\n */\nexport function getTechniqueById(techniqueId: string): Technique | undefined {\n const allTechniques = [\n ...MUSA_TECHNIQUES,\n ...AMSALJA_TECHNIQUES,\n ...HACKER_TECHNIQUES,\n ...JEONGBO_YOWON_TECHNIQUES,\n ...JOJIK_POKRYEOKBAE_TECHNIQUES,\n ];\n\n return allTechniques.find((tech) => tech.id === techniqueId);\n}\n\nexport default {\n getTechniquesForArchetype,\n getTechniquesForStanceAndArchetype,\n getTechniqueById,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useActionFeedback.js","names":[],"sources":["../../src/hooks/useActionFeedback.ts"],"sourcesContent":["/**\n * useActionFeedback Hook - Player Action Feedback State Management\n * \n * Manages state for floating damage numbers, combo counter, technique names,\n * and action indicators (Perfect, Critical, Blocked, Dodged).\n *\n * @module hooks/useActionFeedback\n * @category Combat UI\n * @korean 액션피드백\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Position } from \"../types\";\n\n/**\n * Types of action feedback indicators\n */\nexport type ActionFeedbackType = \n | \"perfect\"\n | \"critical\"\n | \"blocked\"\n | \"dodged\"\n | \"technique\"\n | \"combo_milestone\";\n\n/**\n * Damage number type for color coding\n */\nexport type DamageType = \"normal\" | \"critical\" | \"vital\";\n\n/**\n * Represents a floating damage number\n */\nexport interface DamageNumber {\n readonly id: string;\n readonly damage: number;\n readonly position: Position;\n readonly type: DamageType;\n readonly timestamp: number;\n}\n\n/**\n * Represents an action feedback indicator\n */\nexport interface ActionFeedback {\n readonly id: string;\n readonly type: ActionFeedbackType;\n readonly text: string;\n readonly textKorean: string;\n readonly position: Position;\n readonly timestamp: number;\n}\n\n/**\n * Action feedback state\n */\nexport interface ActionFeedbackState {\n readonly damageNumbers: DamageNumber[];\n readonly actionFeedbacks: ActionFeedback[];\n readonly comboCount: number;\n readonly lastHitTime: number;\n readonly currentTechnique: { korean: string; english: string } | null;\n readonly techniqueShowTime: number;\n}\n\n/**\n * Action feedback actions interface\n */\nexport interface ActionFeedbackActions {\n readonly addDamageNumber: (damage: number, position: Position, type?: DamageType) => void;\n readonly addActionFeedback: (type: ActionFeedbackType, text: string, textKorean: string, position: Position) => void;\n readonly incrementCombo: () => void;\n readonly resetCombo: () => void;\n readonly showTechnique: (korean: string, english: string) => void;\n readonly hideTechnique: () => void;\n readonly clearExpired: () => void;\n}\n\n/**\n * Configuration for useActionFeedback hook\n */\nexport interface UseActionFeedbackConfig {\n /** Duration in ms for damage numbers to display (default: 1500) */\n readonly damageNumberDuration?: number;\n /** Duration in ms for action feedback to display (default: 1200) */\n readonly actionFeedbackDuration?: number;\n /** Duration in ms for technique name to display (default: 2000) */\n readonly techniqueDuration?: number;\n /** Duration in ms before combo resets after no hits (default: 2000) */\n readonly comboResetTime?: number;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<UseActionFeedbackConfig> = {\n damageNumberDuration: 1500,\n actionFeedbackDuration: 1200,\n techniqueDuration: 2000,\n comboResetTime: 2000,\n};\n\n/**\n * useActionFeedback Hook\n * \n * Manages combat action feedback including:\n * - Floating damage numbers with color coding (normal, critical, vital)\n * - Combo counter with automatic reset\n * - Technique name display (Korean | English)\n * - Action indicators (Perfect, Critical, Blocked, Dodged)\n *\n * @param config - Optional configuration for durations and timing\n * @returns Action feedback state and actions\n *\n * @example\n * ```typescript\n * const { state, actions } = useActionFeedback({\n * damageNumberDuration: 1500,\n * comboResetTime: 2000,\n * });\n *\n * // Add damage number\n * actions.addDamageNumber(25, { x: 100, y: 200 }, 'critical');\n *\n * // Show technique name\n * actions.showTechnique('천둥벽력', 'Thunder Strike');\n *\n * // Increment combo\n * actions.incrementCombo();\n * ```\n */\nexport function useActionFeedback(config: UseActionFeedbackConfig = {}): {\n state: ActionFeedbackState;\n actions: ActionFeedbackActions;\n} {\n const {\n damageNumberDuration,\n actionFeedbackDuration,\n techniqueDuration,\n comboResetTime,\n } = { ...DEFAULT_CONFIG, ...config };\n\n // State\n const [damageNumbers, setDamageNumbers] = useState<DamageNumber[]>([]);\n const [actionFeedbacks, setActionFeedbacks] = useState<ActionFeedback[]>([]);\n const [comboCount, setComboCount] = useState(0);\n const [lastHitTime, setLastHitTime] = useState(0);\n const [currentTechnique, setCurrentTechnique] = useState<{ korean: string; english: string } | null>(null);\n const [techniqueShowTime, setTechniqueShowTime] = useState(0);\n\n // Refs for timers\n const comboResetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const techniqueTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cleanupIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const idCounterRef = useRef(0);\n\n // Generate unique ID using counter and timestamp for guaranteed uniqueness\n const generateId = useCallback(() => {\n idCounterRef.current += 1;\n return `feedback_${Date.now()}_${idCounterRef.current}`;\n }, []);\n\n // Add damage number\n const addDamageNumber = useCallback((\n damage: number,\n position: Position,\n type: DamageType = \"normal\"\n ) => {\n const damageNumber: DamageNumber = {\n id: generateId(),\n damage,\n position,\n type,\n timestamp: Date.now(),\n };\n setDamageNumbers(prev => [...prev, damageNumber]);\n }, [generateId]);\n\n // Add action feedback\n const addActionFeedback = useCallback((\n type: ActionFeedbackType,\n text: string,\n textKorean: string,\n position: Position\n ) => {\n const feedback: ActionFeedback = {\n id: generateId(),\n type,\n text,\n textKorean,\n position,\n timestamp: Date.now(),\n };\n setActionFeedbacks(prev => [...prev, feedback]);\n }, [generateId]);\n\n // Increment combo counter\n const incrementCombo = useCallback(() => {\n setComboCount(prev => prev + 1);\n setLastHitTime(Date.now());\n\n // Clear existing reset timer\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n }\n\n // Set new reset timer\n comboResetTimerRef.current = setTimeout(() => {\n setComboCount(0);\n }, comboResetTime);\n }, [comboResetTime]);\n\n // Reset combo counter\n const resetCombo = useCallback(() => {\n setComboCount(0);\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n comboResetTimerRef.current = null;\n }\n }, []);\n\n // Show technique name\n const showTechnique = useCallback((korean: string, english: string) => {\n setCurrentTechnique({ korean, english });\n setTechniqueShowTime(Date.now());\n\n // Clear existing timer\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n }\n\n // Set hide timer\n techniqueTimerRef.current = setTimeout(() => {\n setCurrentTechnique(null);\n }, techniqueDuration);\n }, [techniqueDuration]);\n\n // Hide technique name\n const hideTechnique = useCallback(() => {\n setCurrentTechnique(null);\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n techniqueTimerRef.current = null;\n }\n }, []);\n\n // Clear expired items\n const clearExpired = useCallback(() => {\n const now = Date.now();\n\n setDamageNumbers(prev =>\n prev.filter(d => now - d.timestamp < damageNumberDuration)\n );\n\n setActionFeedbacks(prev =>\n prev.filter(f => now - f.timestamp < actionFeedbackDuration)\n );\n }, [damageNumberDuration, actionFeedbackDuration]);\n\n // Setup cleanup interval\n useEffect(() => {\n cleanupIntervalRef.current = setInterval(clearExpired, 100);\n\n return () => {\n if (cleanupIntervalRef.current) {\n clearInterval(cleanupIntervalRef.current);\n }\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n }\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n }\n };\n }, [clearExpired]);\n\n return {\n state: {\n damageNumbers,\n actionFeedbacks,\n comboCount,\n lastHitTime,\n currentTechnique,\n techniqueShowTime,\n },\n actions: {\n addDamageNumber,\n addActionFeedback,\n incrementCombo,\n resetCombo,\n showTechnique,\n hideTechnique,\n clearExpired,\n },\n };\n}\n\nexport default useActionFeedback;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useActionFeedback.js","names":[],"sources":["../../src/hooks/useActionFeedback.ts"],"sourcesContent":["/**\n * useActionFeedback Hook - Player Action Feedback State Management\n * \n * Manages state for floating damage numbers, combo counter, technique names,\n * and action indicators (Perfect, Critical, Blocked, Dodged).\n *\n * @module hooks/useActionFeedback\n * @category Combat UI\n * @korean 액션피드백\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Position } from \"../types\";\n\n/**\n * Types of action feedback indicators\n */\nexport type ActionFeedbackType = \n | \"perfect\"\n | \"critical\"\n | \"blocked\"\n | \"dodged\"\n | \"technique\"\n | \"combo_milestone\";\n\n/**\n * Damage number type for color coding\n */\nexport type DamageType = \"normal\" | \"critical\" | \"vital\";\n\n/**\n * Represents a floating damage number\n */\nexport interface DamageNumber {\n readonly id: string;\n readonly damage: number;\n readonly position: Position;\n readonly type: DamageType;\n readonly timestamp: number;\n}\n\n/**\n * Represents an action feedback indicator\n */\nexport interface ActionFeedback {\n readonly id: string;\n readonly type: ActionFeedbackType;\n readonly text: string;\n readonly textKorean: string;\n readonly position: Position;\n readonly timestamp: number;\n}\n\n/**\n * Action feedback state\n */\nexport interface ActionFeedbackState {\n readonly damageNumbers: DamageNumber[];\n readonly actionFeedbacks: ActionFeedback[];\n readonly comboCount: number;\n readonly lastHitTime: number;\n readonly currentTechnique: { korean: string; english: string } | null;\n readonly techniqueShowTime: number;\n}\n\n/**\n * Action feedback actions interface\n */\nexport interface ActionFeedbackActions {\n readonly addDamageNumber: (damage: number, position: Position, type?: DamageType) => void;\n readonly addActionFeedback: (type: ActionFeedbackType, text: string, textKorean: string, position: Position) => void;\n readonly incrementCombo: () => void;\n readonly resetCombo: () => void;\n readonly showTechnique: (korean: string, english: string) => void;\n readonly hideTechnique: () => void;\n readonly clearExpired: () => void;\n}\n\n/**\n * Configuration for useActionFeedback hook\n */\nexport interface UseActionFeedbackConfig {\n /** Duration in ms for damage numbers to display (default: 1500) */\n readonly damageNumberDuration?: number;\n /** Duration in ms for action feedback to display (default: 1200) */\n readonly actionFeedbackDuration?: number;\n /** Duration in ms for technique name to display (default: 2000) */\n readonly techniqueDuration?: number;\n /** Duration in ms before combo resets after no hits (default: 2000) */\n readonly comboResetTime?: number;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<UseActionFeedbackConfig> = {\n damageNumberDuration: 1500,\n actionFeedbackDuration: 1200,\n techniqueDuration: 2000,\n comboResetTime: 2000,\n};\n\n/**\n * useActionFeedback Hook\n * \n * Manages combat action feedback including:\n * - Floating damage numbers with color coding (normal, critical, vital)\n * - Combo counter with automatic reset\n * - Technique name display (Korean | English)\n * - Action indicators (Perfect, Critical, Blocked, Dodged)\n *\n * @param config - Optional configuration for durations and timing\n * @returns Action feedback state and actions\n *\n * @example\n * ```typescript\n * const { state, actions } = useActionFeedback({\n * damageNumberDuration: 1500,\n * comboResetTime: 2000,\n * });\n *\n * // Add damage number\n * actions.addDamageNumber(25, { x: 100, y: 200 }, 'critical');\n *\n * // Show technique name\n * actions.showTechnique('천둥벽력', 'Thunder Strike');\n *\n * // Increment combo\n * actions.incrementCombo();\n * ```\n */\nexport function useActionFeedback(config: UseActionFeedbackConfig = {}): {\n state: ActionFeedbackState;\n actions: ActionFeedbackActions;\n} {\n const {\n damageNumberDuration,\n actionFeedbackDuration,\n techniqueDuration,\n comboResetTime,\n } = { ...DEFAULT_CONFIG, ...config };\n\n // State\n const [damageNumbers, setDamageNumbers] = useState<DamageNumber[]>([]);\n const [actionFeedbacks, setActionFeedbacks] = useState<ActionFeedback[]>([]);\n const [comboCount, setComboCount] = useState(0);\n const [lastHitTime, setLastHitTime] = useState(0);\n const [currentTechnique, setCurrentTechnique] = useState<{ korean: string; english: string } | null>(null);\n const [techniqueShowTime, setTechniqueShowTime] = useState(0);\n\n // Refs for timers\n const comboResetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const techniqueTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cleanupIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const idCounterRef = useRef(0);\n\n // Generate unique ID using counter and timestamp for guaranteed uniqueness\n const generateId = useCallback(() => {\n idCounterRef.current += 1;\n return `feedback_${Date.now()}_${idCounterRef.current}`;\n }, []);\n\n // Add damage number\n const addDamageNumber = useCallback((\n damage: number,\n position: Position,\n type: DamageType = \"normal\"\n ) => {\n const damageNumber: DamageNumber = {\n id: generateId(),\n damage,\n position,\n type,\n timestamp: Date.now(),\n };\n setDamageNumbers(prev => [...prev, damageNumber]);\n }, [generateId]);\n\n // Add action feedback\n const addActionFeedback = useCallback((\n type: ActionFeedbackType,\n text: string,\n textKorean: string,\n position: Position\n ) => {\n const feedback: ActionFeedback = {\n id: generateId(),\n type,\n text,\n textKorean,\n position,\n timestamp: Date.now(),\n };\n setActionFeedbacks(prev => [...prev, feedback]);\n }, [generateId]);\n\n // Increment combo counter\n const incrementCombo = useCallback(() => {\n setComboCount(prev => prev + 1);\n setLastHitTime(Date.now());\n\n // Clear existing reset timer\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n }\n\n // Set new reset timer\n comboResetTimerRef.current = setTimeout(() => {\n setComboCount(0);\n }, comboResetTime);\n }, [comboResetTime]);\n\n // Reset combo counter\n const resetCombo = useCallback(() => {\n setComboCount(0);\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n comboResetTimerRef.current = null;\n }\n }, []);\n\n // Show technique name\n const showTechnique = useCallback((korean: string, english: string) => {\n setCurrentTechnique({ korean, english });\n setTechniqueShowTime(Date.now());\n\n // Clear existing timer\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n }\n\n // Set hide timer\n techniqueTimerRef.current = setTimeout(() => {\n setCurrentTechnique(null);\n }, techniqueDuration);\n }, [techniqueDuration]);\n\n // Hide technique name\n const hideTechnique = useCallback(() => {\n setCurrentTechnique(null);\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n techniqueTimerRef.current = null;\n }\n }, []);\n\n // Clear expired items\n const clearExpired = useCallback(() => {\n const now = Date.now();\n\n setDamageNumbers(prev =>\n prev.filter(d => now - d.timestamp < damageNumberDuration)\n );\n\n setActionFeedbacks(prev =>\n prev.filter(f => now - f.timestamp < actionFeedbackDuration)\n );\n }, [damageNumberDuration, actionFeedbackDuration]);\n\n // Setup cleanup interval\n useEffect(() => {\n cleanupIntervalRef.current = setInterval(clearExpired, 100);\n\n return () => {\n if (cleanupIntervalRef.current) {\n clearInterval(cleanupIntervalRef.current);\n }\n if (comboResetTimerRef.current) {\n clearTimeout(comboResetTimerRef.current);\n }\n if (techniqueTimerRef.current) {\n clearTimeout(techniqueTimerRef.current);\n }\n };\n }, [clearExpired]);\n\n return {\n state: {\n damageNumbers,\n actionFeedbacks,\n comboCount,\n lastHitTime,\n currentTechnique,\n techniqueShowTime,\n },\n actions: {\n addDamageNumber,\n addActionFeedback,\n incrementCombo,\n resetCombo,\n showTechnique,\n hideTechnique,\n clearExpired,\n },\n };\n}\n\nexport default useActionFeedback;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBalanceAnimations.js","names":[],"sources":["../../src/hooks/useBalanceAnimations.ts"],"sourcesContent":["/**\n * useBalanceAnimations - Shared hook for balance state visual effects\n *\n * Manages sway, stumble, and lean animations based on player balance state.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useBalanceAnimations\n * @category Hooks\n * @korean 균형애니메이션훅\n */\n\nimport { useRef, useState } from \"react\";\nimport type { BalanceState } from \"../types/player-visual\";\n\n/**\n * Animation constants for balance state sway effects.\n * Defines intensity, speed, and lean parameters for visual feedback.\n * @korean 균형상태애니메이션상수\n */\nconst BALANCE_STATE_ANIMATION_CONSTANTS = {\n SHAKEN: {\n swayIntensity: 0.02, // 2% subtle sway\n swaySpeed: 2, // Hz frequency\n },\n VULNERABLE: {\n swayIntensity: 0.04, // 4% moderate sway\n swaySpeed: 3, // Hz frequency\n },\n HELPLESS: {\n stumbleIntensity: 0.08, // 8% pronounced stumble\n stumbleSpeed: 1.5, // Hz frequency (slower, more dramatic)\n leanIntensity: 0.15, // 15° forward lean angle\n lowerStance: -0.15, // Lower Y position for stumbling effect\n },\n SWAY_THRESHOLD: 0.001, // Minimum sway to consider significant\n} as const;\n\n/**\n * Checks if there is significant sway that requires animation updates.\n *\n * @param swayPosition - Current sway position [x, y, z]\n * @param helplessRotation - Current helpless rotation angle\n * @returns True if sway is above threshold\n * @korean 의미있는흔들림확인\n */\nconst hasSignificantSway = (\n swayPosition: [number, number, number],\n helplessRotation: number\n): boolean => {\n return (\n Math.abs(swayPosition[0]) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD ||\n Math.abs(swayPosition[1]) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD ||\n Math.abs(helplessRotation) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD\n );\n};\n\n/**\n * Options for useBalanceAnimations hook\n * @korean 균형애니메이션훅옵션\n */\nexport interface UseBalanceAnimationsOptions {\n /** Current balance state */\n readonly balance?: BalanceState;\n}\n\n/**\n * Return type for useBalanceAnimations hook\n * @korean 균형애니메이션훅반환타입\n */\nexport interface UseBalanceAnimationsReturn {\n /** Current sway position offset [x, y, z] */\n readonly swayPosition: [number, number, number];\n /** Current helpless rotation (forward lean) */\n readonly helplessRotation: number;\n /** Update balance animations (call in useFrame) */\n readonly updateBalanceAnimations: (delta: number, frameCounter: number) => void;\n}\n\n/**\n * useBalanceAnimations hook\n *\n * Manages balance state visual effects including sway, stumble, and lean\n * animations. Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Balance animation options\n * @returns Balance animation state and update function\n *\n * @example\n * ```tsx\n * const { swayPosition, helplessRotation, updateBalanceAnimations } =\n * useBalanceAnimations({\n * balance: \"VULNERABLE\",\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateBalanceAnimations(delta, frameCounter);\n * });\n *\n * // Apply to character group in render:\n * // position={swayPosition} rotation={[helplessRotation, 0, 0]}\n * ```\n *\n * @korean 균형애니메이션훅\n */\nexport function useBalanceAnimations(\n options: UseBalanceAnimationsOptions\n): UseBalanceAnimationsReturn {\n const { balance = \"READY\" } = options;\n\n // Sway time ref for animation\n const swayTimeRef = useRef(0);\n\n // Sway position and rotation states\n const [swayPosition, setSwayPosition] = useState<[number, number, number]>([\n 0, 0, 0,\n ]);\n const [helplessRotation, setHelplessRotation] = useState<number>(0);\n\n // Update balance animations (called at 60fps in useFrame)\n const updateBalanceAnimations = (\n delta: number,\n frameCounter: number\n ): void => {\n if (balance === \"HELPLESS\") {\n // Helpless state: pronounced stumbling motion\n swayTimeRef.current += delta;\n\n const { stumbleIntensity, stumbleSpeed, leanIntensity, lowerStance } =\n BALANCE_STATE_ANIMATION_CONSTANTS.HELPLESS;\n\n const swayX =\n Math.sin(swayTimeRef.current * stumbleSpeed) * stumbleIntensity;\n const swayY =\n Math.cos(swayTimeRef.current * stumbleSpeed * 0.5) *\n stumbleIntensity *\n 0.3 +\n lowerStance;\n const leanAngle =\n Math.sin(swayTimeRef.current * stumbleSpeed * 0.7) * leanIntensity;\n\n // Update periodically to reduce React re-renders\n if (frameCounter % 2 === 0) {\n setSwayPosition([swayX, swayY, 0]);\n setHelplessRotation(leanAngle);\n }\n } else if (balance === \"SHAKEN\" || balance === \"VULNERABLE\") {\n // Shaken/Vulnerable state: subtle sway\n swayTimeRef.current += delta;\n\n const animConfig =\n balance === \"SHAKEN\"\n ? BALANCE_STATE_ANIMATION_CONSTANTS.SHAKEN\n : BALANCE_STATE_ANIMATION_CONSTANTS.VULNERABLE;\n\n const swayX =\n Math.sin(swayTimeRef.current * animConfig.swaySpeed) *\n animConfig.swayIntensity;\n const swayY =\n Math.cos(swayTimeRef.current * animConfig.swaySpeed * 0.8) *\n animConfig.swayIntensity *\n 0.5;\n\n // Update sway position periodically\n if (frameCounter % 2 === 0) {\n setSwayPosition([swayX, swayY, 0]);\n setHelplessRotation(0);\n }\n } else {\n // Smoothly return to neutral position\n if (\n frameCounter % 2 === 0 &&\n hasSignificantSway(swayPosition, helplessRotation)\n ) {\n setSwayPosition([swayPosition[0] * 0.95, swayPosition[1] * 0.95, 0]);\n setHelplessRotation(helplessRotation * 0.95);\n }\n\n // Reset sway time when not swaying\n if (!hasSignificantSway(swayPosition, helplessRotation)) {\n swayTimeRef.current = 0;\n }\n }\n };\n\n return {\n swayPosition,\n helplessRotation,\n updateBalanceAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,IAAM,oCAAoC;CACxC,QAAQ;EACN,eAAe;EACf,WAAW;EACZ;CACD,YAAY;EACV,eAAe;EACf,WAAW;EACZ;CACD,UAAU;EACR,kBAAkB;EAClB,cAAc;EACd,eAAe;EACf,aAAa;EACd;CACD,gBAAgB;CACjB;;;;;;;;;AAUD,IAAM,sBACJ,cACA,qBACY;CACZ,OACE,KAAK,IAAI,aAAa,GAAG,GACvB,kCAAkC,kBACpC,KAAK,IAAI,aAAa,GAAG,GACvB,kCAAkC,kBACpC,KAAK,IAAI,iBAAiB,GACxB,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDxC,SAAgB,qBACd,SAC4B;CAC5B,MAAM,EAAE,UAAU,YAAY;CAG9B,MAAM,cAAc,OAAO,EAAE;CAG7B,MAAM,CAAC,cAAc,mBAAmB,SAAmC;EACzE;EAAG;EAAG;EACP,CAAC;CACF,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,EAAE;CAGnE,MAAM,2BACJ,OACA,iBACS;EACT,IAAI,YAAY,YAAY;GAE1B,YAAY,WAAW;GAEvB,MAAM,EAAE,kBAAkB,cAAc,eAAe,gBACrD,kCAAkC;GAEpC,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,aAAa,GAAG;GACjD,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,eAAe,GAAI,GAChD,mBACA,KACF;GACF,MAAM,YACJ,KAAK,IAAI,YAAY,UAAU,eAAe,GAAI,GAAG;GAGvD,IAAI,eAAe,MAAM,GAAG;IAC1B,gBAAgB;KAAC;KAAO;KAAO;KAAE,CAAC;IAClC,oBAAoB,UAAU;;SAE3B,IAAI,YAAY,YAAY,YAAY,cAAc;GAE3D,YAAY,WAAW;GAEvB,MAAM,aACJ,YAAY,WACR,kCAAkC,SAClC,kCAAkC;GAExC,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,WAAW,UAAU,GACpD,WAAW;GACb,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,WAAW,YAAY,GAAI,GAC1D,WAAW,gBACX;GAGF,IAAI,eAAe,MAAM,GAAG;IAC1B,gBAAgB;KAAC;KAAO;KAAO;KAAE,CAAC;IAClC,oBAAoB,EAAE;;SAEnB;GAEL,IACE,eAAe,MAAM,KACrB,mBAAmB,cAAc,iBAAiB,EAClD;IACA,gBAAgB;KAAC,aAAa,KAAK;KAAM,aAAa,KAAK;KAAM;KAAE,CAAC;IACpE,oBAAoB,mBAAmB,IAAK;;GAI9C,IAAI,CAAC,mBAAmB,cAAc,iBAAiB,EACrD,YAAY,UAAU;;;CAK5B,OAAO;EACL;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useBalanceAnimations.js","names":[],"sources":["../../src/hooks/useBalanceAnimations.ts"],"sourcesContent":["/**\n * useBalanceAnimations - Shared hook for balance state visual effects\n *\n * Manages sway, stumble, and lean animations based on player balance state.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useBalanceAnimations\n * @category Hooks\n * @korean 균형애니메이션훅\n */\n\nimport { useRef, useState } from \"react\";\nimport type { BalanceState } from \"../types/player-visual\";\n\n/**\n * Animation constants for balance state sway effects.\n * Defines intensity, speed, and lean parameters for visual feedback.\n * @korean 균형상태애니메이션상수\n */\nconst BALANCE_STATE_ANIMATION_CONSTANTS = {\n SHAKEN: {\n swayIntensity: 0.02, // 2% subtle sway\n swaySpeed: 2, // Hz frequency\n },\n VULNERABLE: {\n swayIntensity: 0.04, // 4% moderate sway\n swaySpeed: 3, // Hz frequency\n },\n HELPLESS: {\n stumbleIntensity: 0.08, // 8% pronounced stumble\n stumbleSpeed: 1.5, // Hz frequency (slower, more dramatic)\n leanIntensity: 0.15, // 15° forward lean angle\n lowerStance: -0.15, // Lower Y position for stumbling effect\n },\n SWAY_THRESHOLD: 0.001, // Minimum sway to consider significant\n} as const;\n\n/**\n * Checks if there is significant sway that requires animation updates.\n *\n * @param swayPosition - Current sway position [x, y, z]\n * @param helplessRotation - Current helpless rotation angle\n * @returns True if sway is above threshold\n * @korean 의미있는흔들림확인\n */\nconst hasSignificantSway = (\n swayPosition: [number, number, number],\n helplessRotation: number\n): boolean => {\n return (\n Math.abs(swayPosition[0]) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD ||\n Math.abs(swayPosition[1]) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD ||\n Math.abs(helplessRotation) >\n BALANCE_STATE_ANIMATION_CONSTANTS.SWAY_THRESHOLD\n );\n};\n\n/**\n * Options for useBalanceAnimations hook\n * @korean 균형애니메이션훅옵션\n */\nexport interface UseBalanceAnimationsOptions {\n /** Current balance state */\n readonly balance?: BalanceState;\n}\n\n/**\n * Return type for useBalanceAnimations hook\n * @korean 균형애니메이션훅반환타입\n */\nexport interface UseBalanceAnimationsReturn {\n /** Current sway position offset [x, y, z] */\n readonly swayPosition: [number, number, number];\n /** Current helpless rotation (forward lean) */\n readonly helplessRotation: number;\n /** Update balance animations (call in useFrame) */\n readonly updateBalanceAnimations: (delta: number, frameCounter: number) => void;\n}\n\n/**\n * useBalanceAnimations hook\n *\n * Manages balance state visual effects including sway, stumble, and lean\n * animations. Updates at 60fps with periodic state syncs to reduce re-renders.\n *\n * @param options - Balance animation options\n * @returns Balance animation state and update function\n *\n * @example\n * ```tsx\n * const { swayPosition, helplessRotation, updateBalanceAnimations } =\n * useBalanceAnimations({\n * balance: \"VULNERABLE\",\n * });\n *\n * // In useFrame callback\n * let frameCounter = 0;\n * useFrame((_, delta) => {\n * frameCounter = (frameCounter + 1) % 10;\n * updateBalanceAnimations(delta, frameCounter);\n * });\n *\n * // Apply to character group in render:\n * // position={swayPosition} rotation={[helplessRotation, 0, 0]}\n * ```\n *\n * @korean 균형애니메이션훅\n */\nexport function useBalanceAnimations(\n options: UseBalanceAnimationsOptions\n): UseBalanceAnimationsReturn {\n const { balance = \"READY\" } = options;\n\n // Sway time ref for animation\n const swayTimeRef = useRef(0);\n\n // Sway position and rotation states\n const [swayPosition, setSwayPosition] = useState<[number, number, number]>([\n 0, 0, 0,\n ]);\n const [helplessRotation, setHelplessRotation] = useState<number>(0);\n\n // Update balance animations (called at 60fps in useFrame)\n const updateBalanceAnimations = (\n delta: number,\n frameCounter: number\n ): void => {\n if (balance === \"HELPLESS\") {\n // Helpless state: pronounced stumbling motion\n swayTimeRef.current += delta;\n\n const { stumbleIntensity, stumbleSpeed, leanIntensity, lowerStance } =\n BALANCE_STATE_ANIMATION_CONSTANTS.HELPLESS;\n\n const swayX =\n Math.sin(swayTimeRef.current * stumbleSpeed) * stumbleIntensity;\n const swayY =\n Math.cos(swayTimeRef.current * stumbleSpeed * 0.5) *\n stumbleIntensity *\n 0.3 +\n lowerStance;\n const leanAngle =\n Math.sin(swayTimeRef.current * stumbleSpeed * 0.7) * leanIntensity;\n\n // Update periodically to reduce React re-renders\n if (frameCounter % 2 === 0) {\n setSwayPosition([swayX, swayY, 0]);\n setHelplessRotation(leanAngle);\n }\n } else if (balance === \"SHAKEN\" || balance === \"VULNERABLE\") {\n // Shaken/Vulnerable state: subtle sway\n swayTimeRef.current += delta;\n\n const animConfig =\n balance === \"SHAKEN\"\n ? BALANCE_STATE_ANIMATION_CONSTANTS.SHAKEN\n : BALANCE_STATE_ANIMATION_CONSTANTS.VULNERABLE;\n\n const swayX =\n Math.sin(swayTimeRef.current * animConfig.swaySpeed) *\n animConfig.swayIntensity;\n const swayY =\n Math.cos(swayTimeRef.current * animConfig.swaySpeed * 0.8) *\n animConfig.swayIntensity *\n 0.5;\n\n // Update sway position periodically\n if (frameCounter % 2 === 0) {\n setSwayPosition([swayX, swayY, 0]);\n setHelplessRotation(0);\n }\n } else {\n // Smoothly return to neutral position\n if (\n frameCounter % 2 === 0 &&\n hasSignificantSway(swayPosition, helplessRotation)\n ) {\n setSwayPosition([swayPosition[0] * 0.95, swayPosition[1] * 0.95, 0]);\n setHelplessRotation(helplessRotation * 0.95);\n }\n\n // Reset sway time when not swaying\n if (!hasSignificantSway(swayPosition, helplessRotation)) {\n swayTimeRef.current = 0;\n }\n }\n };\n\n return {\n swayPosition,\n helplessRotation,\n updateBalanceAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,IAAM,oCAAoC;CACxC,QAAQ;EACN,eAAe;EACf,WAAW;CACb;CACA,YAAY;EACV,eAAe;EACf,WAAW;CACb;CACA,UAAU;EACR,kBAAkB;EAClB,cAAc;EACd,eAAe;EACf,aAAa;CACf;CACA,gBAAgB;AAClB;;;;;;;;;AAUA,IAAM,sBACJ,cACA,qBACY;CACZ,OACE,KAAK,IAAI,aAAa,EAAE,IACtB,kCAAkC,kBACpC,KAAK,IAAI,aAAa,EAAE,IACtB,kCAAkC,kBACpC,KAAK,IAAI,gBAAgB,IACvB,kCAAkC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,qBACd,SAC4B;CAC5B,MAAM,EAAE,UAAU,YAAY;CAG9B,MAAM,cAAc,OAAO,CAAC;CAG5B,MAAM,CAAC,cAAc,mBAAmB,SAAmC;EACzE;EAAG;EAAG;CACR,CAAC;CACD,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,CAAC;CAGlE,MAAM,2BACJ,OACA,iBACS;EACT,IAAI,YAAY,YAAY;GAE1B,YAAY,WAAW;GAEvB,MAAM,EAAE,kBAAkB,cAAc,eAAe,gBACrD,kCAAkC;GAEpC,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,YAAY,IAAI;GACjD,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,eAAe,EAAG,IAC/C,mBACA,KACF;GACF,MAAM,YACJ,KAAK,IAAI,YAAY,UAAU,eAAe,EAAG,IAAI;GAGvD,IAAI,eAAe,MAAM,GAAG;IAC1B,gBAAgB;KAAC;KAAO;KAAO;IAAC,CAAC;IACjC,oBAAoB,SAAS;GAC/B;EACF,OAAO,IAAI,YAAY,YAAY,YAAY,cAAc;GAE3D,YAAY,WAAW;GAEvB,MAAM,aACJ,YAAY,WACR,kCAAkC,SAClC,kCAAkC;GAExC,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,WAAW,SAAS,IACnD,WAAW;GACb,MAAM,QACJ,KAAK,IAAI,YAAY,UAAU,WAAW,YAAY,EAAG,IACzD,WAAW,gBACX;GAGF,IAAI,eAAe,MAAM,GAAG;IAC1B,gBAAgB;KAAC;KAAO;KAAO;IAAC,CAAC;IACjC,oBAAoB,CAAC;GACvB;EACF,OAAO;GAEL,IACE,eAAe,MAAM,KACrB,mBAAmB,cAAc,gBAAgB,GACjD;IACA,gBAAgB;KAAC,aAAa,KAAK;KAAM,aAAa,KAAK;KAAM;IAAC,CAAC;IACnE,oBAAoB,mBAAmB,GAAI;GAC7C;GAGA,IAAI,CAAC,mBAAmB,cAAc,gBAAgB,GACpD,YAAY,UAAU;EAE1B;CACF;CAEA,OAAO;EACL;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCombatTimer.js","names":[],"sources":["../../src/hooks/useCombatTimer.ts"],"sourcesContent":["/**\n * useCombatTimer - Hook for managing combat round timer\n *\n * Korean: 전투 라운드 타이머 훅 (Combat Round Timer Hook)\n *\n * Manages countdown timer for combat rounds with:\n * - Pause/resume support\n * - Warning thresholds at 10s and 5s\n * - Audio alerts for warnings\n * - Time's up callback\n *\n * @module hooks/useCombatTimer\n * @category Combat Hooks\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAudio } from \"../audio/AudioProvider\";\n\n/**\n * Timer warning level indicating urgency\n */\nexport type TimerWarningLevel = \"none\" | \"warning\" | \"urgent\";\n\n/**\n * Configuration for the combat timer hook\n */\nexport interface UseCombatTimerConfig {\n /** Initial time in seconds */\n readonly initialTime: number;\n /** Whether the timer is paused */\n readonly isPaused: boolean;\n /** Callback when timer reaches 0 */\n readonly onTimeUp: () => void;\n /** Warning threshold in seconds (default: 10) */\n readonly warningThreshold?: number;\n /** Urgent warning threshold in seconds (default: 5) */\n readonly urgentThreshold?: number;\n /** Optional key to force timer reset (e.g., round number) */\n readonly resetKey?: string;\n}\n\n/**\n * Return value from useCombatTimer hook\n */\nexport interface UseCombatTimerReturn {\n /** Current time remaining in seconds */\n readonly timeRemaining: number;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether timer has reached 0 */\n readonly isTimeUp: boolean;\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n}\n\n/**\n * Format seconds into MM:SS format\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"03:45\", \"00:05\")\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes.toString().padStart(2, \"0\")}:${remainingSeconds\n .toString()\n .padStart(2, \"0\")}`;\n}\n\n/**\n * Get warning level based on time remaining\n */\nfunction getWarningLevel(\n timeRemaining: number,\n warningThreshold: number,\n urgentThreshold: number,\n): TimerWarningLevel {\n if (timeRemaining <= urgentThreshold) {\n return \"urgent\";\n }\n if (timeRemaining <= warningThreshold) {\n return \"warning\";\n }\n return \"none\";\n}\n\n/**\n * useCombatTimer Hook\n *\n * Manages combat round countdown timer with pause support and audio warnings.\n *\n * Features:\n * - Counts down from initial time to 0\n * - Pauses/resumes based on isPaused prop\n * - Plays audio warning at warning threshold (default 10s)\n * - Plays urgent audio warning at urgent threshold (default 5s)\n * - Calls onTimeUp when timer reaches 0\n * - Provides formatted time string (MM:SS)\n * - Returns current warning level for UI styling\n *\n * Korean: 전투 라운드 타이머 관리 훅\n *\n * @example\n * ```tsx\n * const { timeRemaining, warningLevel, formattedTime } = useCombatTimer({\n * initialTime: 180, // 3 minutes\n * isPaused: false,\n * onTimeUp: () => handleRoundEnd(),\n * warningThreshold: 10,\n * urgentThreshold: 5,\n * });\n * ```\n */\nexport function useCombatTimer(\n config: UseCombatTimerConfig,\n): UseCombatTimerReturn {\n const {\n initialTime,\n isPaused,\n onTimeUp,\n warningThreshold = 10,\n urgentThreshold = 5,\n resetKey,\n } = config;\n\n const audio = useAudio();\n const [timeRemaining, setTimeRemaining] = useState(initialTime);\n const [isTimeUp, setIsTimeUp] = useState(false);\n const lastWarningRef = useRef<TimerWarningLevel>(\"none\");\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Reset timer when initialTime or resetKey changes (new round)\n useEffect(() => {\n setTimeRemaining(initialTime);\n setIsTimeUp(false);\n lastWarningRef.current = \"none\";\n }, [initialTime, resetKey]);\n\n // Timer countdown logic\n useEffect(() => {\n // Don't run if paused or time is up\n if (isPaused || isTimeUp) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n // Track start time for precise elapsed time calculation\n const startTimeRef = Date.now();\n const startingTimeRemaining = timeRemaining;\n\n // Start interval\n intervalRef.current = setInterval(() => {\n const elapsed = (Date.now() - startTimeRef) / 1000;\n const next = Math.max(0, startingTimeRemaining - elapsed);\n\n setTimeRemaining(next);\n\n // Check if time just reached 0\n if (next <= 0 && !isTimeUp) {\n setIsTimeUp(true);\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n onTimeUp();\n }\n }, 100);\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n // timeRemaining is intentionally excluded - we capture the starting value once\n // and count down from there, not restart the interval on every tick\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isPaused, isTimeUp, onTimeUp]);\n\n // Warning level calculation\n const warningLevel = getWarningLevel(\n timeRemaining,\n warningThreshold,\n urgentThreshold,\n );\n\n // Audio warnings - timeRemaining checked via warningLevel which captures threshold transitions\n useEffect(() => {\n if (!audio.isAudioReady) return;\n if (isPaused) return;\n\n // Play warning sound at threshold\n if (\n warningLevel === \"warning\" &&\n lastWarningRef.current === \"none\" &&\n timeRemaining <= warningThreshold &&\n timeRemaining > urgentThreshold\n ) {\n audio.playSFX(\"attack_light\"); // Placeholder - will be timer_warning_10s\n lastWarningRef.current = \"warning\";\n }\n\n // Play urgent warning sound at urgent threshold\n if (\n warningLevel === \"urgent\" &&\n lastWarningRef.current !== \"urgent\" &&\n timeRemaining <= urgentThreshold &&\n timeRemaining > 0\n ) {\n audio.playSFX(\"attack_heavy\"); // Placeholder - will be timer_warning_5s\n lastWarningRef.current = \"urgent\";\n }\n }, [\n warningLevel,\n timeRemaining,\n audio,\n isPaused,\n warningThreshold,\n urgentThreshold,\n ]);\n\n // Format time for display\n const formattedTime = formatTime(timeRemaining);\n\n return {\n timeRemaining,\n warningLevel,\n isTimeUp,\n formattedTime,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,WAAW,SAAyB;CAC3C,MAAM,UAAU,KAAK,MAAM,UAAU,GAAG;CACxC,MAAM,mBAAmB,KAAK,MAAM,UAAU,GAAG;CACjD,OAAO,GAAG,QAAQ,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,iBAC9C,UAAU,CACV,SAAS,GAAG,IAAI;;;;;AAMrB,SAAS,gBACP,eACA,kBACA,iBACmB;CACnB,IAAI,iBAAiB,iBACnB,OAAO;CAET,IAAI,iBAAiB,kBACnB,OAAO;CAET,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BT,SAAgB,eACd,QACsB;CACtB,MAAM,EACJ,aACA,UACA,UACA,mBAAmB,IACnB,kBAAkB,GAClB,aACE;CAEJ,MAAM,QAAQ,UAAU;CACxB,MAAM,CAAC,eAAe,oBAAoB,SAAS,YAAY;CAC/D,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,iBAAiB,OAA0B,OAAO;CACxD,MAAM,cAAc,OAA8C,KAAK;CAGvE,gBAAgB;EACd,iBAAiB,YAAY;EAC7B,YAAY,MAAM;EAClB,eAAe,UAAU;IACxB,CAAC,aAAa,SAAS,CAAC;CAG3B,gBAAgB;EAEd,IAAI,YAAY,UAAU;GACxB,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,QAAQ;IAClC,YAAY,UAAU;;GAExB;;EAIF,MAAM,eAAe,KAAK,KAAK;EAC/B,MAAM,wBAAwB;EAG9B,YAAY,UAAU,kBAAkB;GACtC,MAAM,WAAW,KAAK,KAAK,GAAG,gBAAgB;GAC9C,MAAM,OAAO,KAAK,IAAI,GAAG,wBAAwB,QAAQ;GAEzD,iBAAiB,KAAK;GAGtB,IAAI,QAAQ,KAAK,CAAC,UAAU;IAC1B,YAAY,KAAK;IACjB,IAAI,YAAY,SAAS;KACvB,cAAc,YAAY,QAAQ;KAClC,YAAY,UAAU;;IAExB,UAAU;;KAEX,IAAI;EAEP,aAAa;GACX,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,QAAQ;IAClC,YAAY,UAAU;;;IAMzB;EAAC;EAAU;EAAU;EAAS,CAAC;CAGlC,MAAM,eAAe,gBACnB,eACA,kBACA,gBACD;CAGD,gBAAgB;EACd,IAAI,CAAC,MAAM,cAAc;EACzB,IAAI,UAAU;EAGd,IACE,iBAAiB,aACjB,eAAe,YAAY,UAC3B,iBAAiB,oBACjB,gBAAgB,iBAChB;GACA,MAAM,QAAQ,eAAe;GAC7B,eAAe,UAAU;;EAI3B,IACE,iBAAiB,YACjB,eAAe,YAAY,YAC3B,iBAAiB,mBACjB,gBAAgB,GAChB;GACA,MAAM,QAAQ,eAAe;GAC7B,eAAe,UAAU;;IAE1B;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAKF,OAAO;EACL;EACA;EACA;EACA,eANoB,WAAW,cAM/B;EACD"}
1
+ {"version":3,"file":"useCombatTimer.js","names":[],"sources":["../../src/hooks/useCombatTimer.ts"],"sourcesContent":["/**\n * useCombatTimer - Hook for managing combat round timer\n *\n * Korean: 전투 라운드 타이머 훅 (Combat Round Timer Hook)\n *\n * Manages countdown timer for combat rounds with:\n * - Pause/resume support\n * - Warning thresholds at 10s and 5s\n * - Audio alerts for warnings\n * - Time's up callback\n *\n * @module hooks/useCombatTimer\n * @category Combat Hooks\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useAudio } from \"../audio/AudioProvider\";\n\n/**\n * Timer warning level indicating urgency\n */\nexport type TimerWarningLevel = \"none\" | \"warning\" | \"urgent\";\n\n/**\n * Configuration for the combat timer hook\n */\nexport interface UseCombatTimerConfig {\n /** Initial time in seconds */\n readonly initialTime: number;\n /** Whether the timer is paused */\n readonly isPaused: boolean;\n /** Callback when timer reaches 0 */\n readonly onTimeUp: () => void;\n /** Warning threshold in seconds (default: 10) */\n readonly warningThreshold?: number;\n /** Urgent warning threshold in seconds (default: 5) */\n readonly urgentThreshold?: number;\n /** Optional key to force timer reset (e.g., round number) */\n readonly resetKey?: string;\n}\n\n/**\n * Return value from useCombatTimer hook\n */\nexport interface UseCombatTimerReturn {\n /** Current time remaining in seconds */\n readonly timeRemaining: number;\n /** Current warning level */\n readonly warningLevel: TimerWarningLevel;\n /** Whether timer has reached 0 */\n readonly isTimeUp: boolean;\n /** Formatted time string (MM:SS) */\n readonly formattedTime: string;\n}\n\n/**\n * Format seconds into MM:SS format\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"03:45\", \"00:05\")\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = Math.floor(seconds % 60);\n return `${minutes.toString().padStart(2, \"0\")}:${remainingSeconds\n .toString()\n .padStart(2, \"0\")}`;\n}\n\n/**\n * Get warning level based on time remaining\n */\nfunction getWarningLevel(\n timeRemaining: number,\n warningThreshold: number,\n urgentThreshold: number,\n): TimerWarningLevel {\n if (timeRemaining <= urgentThreshold) {\n return \"urgent\";\n }\n if (timeRemaining <= warningThreshold) {\n return \"warning\";\n }\n return \"none\";\n}\n\n/**\n * useCombatTimer Hook\n *\n * Manages combat round countdown timer with pause support and audio warnings.\n *\n * Features:\n * - Counts down from initial time to 0\n * - Pauses/resumes based on isPaused prop\n * - Plays audio warning at warning threshold (default 10s)\n * - Plays urgent audio warning at urgent threshold (default 5s)\n * - Calls onTimeUp when timer reaches 0\n * - Provides formatted time string (MM:SS)\n * - Returns current warning level for UI styling\n *\n * Korean: 전투 라운드 타이머 관리 훅\n *\n * @example\n * ```tsx\n * const { timeRemaining, warningLevel, formattedTime } = useCombatTimer({\n * initialTime: 180, // 3 minutes\n * isPaused: false,\n * onTimeUp: () => handleRoundEnd(),\n * warningThreshold: 10,\n * urgentThreshold: 5,\n * });\n * ```\n */\nexport function useCombatTimer(\n config: UseCombatTimerConfig,\n): UseCombatTimerReturn {\n const {\n initialTime,\n isPaused,\n onTimeUp,\n warningThreshold = 10,\n urgentThreshold = 5,\n resetKey,\n } = config;\n\n const audio = useAudio();\n const [timeRemaining, setTimeRemaining] = useState(initialTime);\n const [isTimeUp, setIsTimeUp] = useState(false);\n const lastWarningRef = useRef<TimerWarningLevel>(\"none\");\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Reset timer when initialTime or resetKey changes (new round)\n useEffect(() => {\n setTimeRemaining(initialTime);\n setIsTimeUp(false);\n lastWarningRef.current = \"none\";\n }, [initialTime, resetKey]);\n\n // Timer countdown logic\n useEffect(() => {\n // Don't run if paused or time is up\n if (isPaused || isTimeUp) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n // Track start time for precise elapsed time calculation\n const startTimeRef = Date.now();\n const startingTimeRemaining = timeRemaining;\n\n // Start interval\n intervalRef.current = setInterval(() => {\n const elapsed = (Date.now() - startTimeRef) / 1000;\n const next = Math.max(0, startingTimeRemaining - elapsed);\n\n setTimeRemaining(next);\n\n // Check if time just reached 0\n if (next <= 0 && !isTimeUp) {\n setIsTimeUp(true);\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n onTimeUp();\n }\n }, 100);\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n };\n // timeRemaining is intentionally excluded - we capture the starting value once\n // and count down from there, not restart the interval on every tick\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isPaused, isTimeUp, onTimeUp]);\n\n // Warning level calculation\n const warningLevel = getWarningLevel(\n timeRemaining,\n warningThreshold,\n urgentThreshold,\n );\n\n // Audio warnings - timeRemaining checked via warningLevel which captures threshold transitions\n useEffect(() => {\n if (!audio.isAudioReady) return;\n if (isPaused) return;\n\n // Play warning sound at threshold\n if (\n warningLevel === \"warning\" &&\n lastWarningRef.current === \"none\" &&\n timeRemaining <= warningThreshold &&\n timeRemaining > urgentThreshold\n ) {\n audio.playSFX(\"attack_light\"); // Placeholder - will be timer_warning_10s\n lastWarningRef.current = \"warning\";\n }\n\n // Play urgent warning sound at urgent threshold\n if (\n warningLevel === \"urgent\" &&\n lastWarningRef.current !== \"urgent\" &&\n timeRemaining <= urgentThreshold &&\n timeRemaining > 0\n ) {\n audio.playSFX(\"attack_heavy\"); // Placeholder - will be timer_warning_5s\n lastWarningRef.current = \"urgent\";\n }\n }, [\n warningLevel,\n timeRemaining,\n audio,\n isPaused,\n warningThreshold,\n urgentThreshold,\n ]);\n\n // Format time for display\n const formattedTime = formatTime(timeRemaining);\n\n return {\n timeRemaining,\n warningLevel,\n isTimeUp,\n formattedTime,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,WAAW,SAAyB;CAC3C,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;CACvC,MAAM,mBAAmB,KAAK,MAAM,UAAU,EAAE;CAChD,OAAO,GAAG,QAAQ,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE,GAAG,iBAC9C,SAAS,EACT,SAAS,GAAG,GAAG;AACpB;;;;AAKA,SAAS,gBACP,eACA,kBACA,iBACmB;CACnB,IAAI,iBAAiB,iBACnB,OAAO;CAET,IAAI,iBAAiB,kBACnB,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,eACd,QACsB;CACtB,MAAM,EACJ,aACA,UACA,UACA,mBAAmB,IACnB,kBAAkB,GAClB,aACE;CAEJ,MAAM,QAAQ,SAAS;CACvB,MAAM,CAAC,eAAe,oBAAoB,SAAS,WAAW;CAC9D,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,iBAAiB,OAA0B,MAAM;CACvD,MAAM,cAAc,OAA8C,IAAI;CAGtE,gBAAgB;EACd,iBAAiB,WAAW;EAC5B,YAAY,KAAK;EACjB,eAAe,UAAU;CAC3B,GAAG,CAAC,aAAa,QAAQ,CAAC;CAG1B,gBAAgB;EAEd,IAAI,YAAY,UAAU;GACxB,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;GACA;EACF;EAGA,MAAM,eAAe,KAAK,IAAI;EAC9B,MAAM,wBAAwB;EAG9B,YAAY,UAAU,kBAAkB;GACtC,MAAM,WAAW,KAAK,IAAI,IAAI,gBAAgB;GAC9C,MAAM,OAAO,KAAK,IAAI,GAAG,wBAAwB,OAAO;GAExD,iBAAiB,IAAI;GAGrB,IAAI,QAAQ,KAAK,CAAC,UAAU;IAC1B,YAAY,IAAI;IAChB,IAAI,YAAY,SAAS;KACvB,cAAc,YAAY,OAAO;KACjC,YAAY,UAAU;IACxB;IACA,SAAS;GACX;EACF,GAAG,GAAG;EAEN,aAAa;GACX,IAAI,YAAY,SAAS;IACvB,cAAc,YAAY,OAAO;IACjC,YAAY,UAAU;GACxB;EACF;CAIF,GAAG;EAAC;EAAU;EAAU;CAAQ,CAAC;CAGjC,MAAM,eAAe,gBACnB,eACA,kBACA,eACF;CAGA,gBAAgB;EACd,IAAI,CAAC,MAAM,cAAc;EACzB,IAAI,UAAU;EAGd,IACE,iBAAiB,aACjB,eAAe,YAAY,UAC3B,iBAAiB,oBACjB,gBAAgB,iBAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;EAGA,IACE,iBAAiB,YACjB,eAAe,YAAY,YAC3B,iBAAiB,mBACjB,gBAAgB,GAChB;GACA,MAAM,QAAQ,cAAc;GAC5B,eAAe,UAAU;EAC3B;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAKD,OAAO;EACL;EACA;EACA;EACA,eANoB,WAAW,aAM/B;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useDebounce.js","names":[],"sources":["../../src/hooks/useDebounce.ts"],"sourcesContent":["/**\n * useDebounce Hook\n * \n * Debounces a function to execute only after a delay since the last call.\n * Useful for search inputs, resize handlers, and other delayed actions.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the debounced function on every render.\n * \n * @module hooks/useDebounce\n * @category Performance\n * @korean 디바운스 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to debounce a callback function\n * \n * @param callback - Function to debounce\n * @param delay - Delay in milliseconds before execution\n * @returns Debounced function\n * \n * @example\n * ```tsx\n * const handleSearch = useDebounce((query: string) => {\n * // Perform search\n * }, 300);\n * ```\n */\nexport function useDebounce<T extends (...args: unknown[]) => void>(\n callback: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n // Clear existing timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n timeoutRef.current = null;\n }, delay);\n },\n [delay]\n );\n}\n\nexport default useDebounce;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACkC;CAClC,MAAM,aAAa,OAA6C,KAAK;CACrE,MAAM,cAAc,OAAO,SAAS;CAGpC,sBAAsB;EACpB,YAAY,UAAU;GACtB;CAGF,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,QAAQ;;IAGnC,EAAE,CAAC;CAEN,OAAO,aACJ,GAAG,SAAwB;EAE1B,IAAI,WAAW,SACb,aAAa,WAAW,QAAQ;EAIlC,WAAW,UAAU,iBAAiB;GACpC,YAAY,QAAQ,GAAG,KAAK;GAC5B,WAAW,UAAU;KACpB,MAAM;IAEX,CAAC,MAAM,CACR"}
1
+ {"version":3,"file":"useDebounce.js","names":[],"sources":["../../src/hooks/useDebounce.ts"],"sourcesContent":["/**\n * useDebounce Hook\n * \n * Debounces a function to execute only after a delay since the last call.\n * Useful for search inputs, resize handlers, and other delayed actions.\n * \n * Uses a ref pattern to ensure the latest callback is always called\n * without recreating the debounced function on every render.\n * \n * @module hooks/useDebounce\n * @category Performance\n * @korean 디바운스 훅\n */\n\nimport { useCallback, useRef, useLayoutEffect, useEffect } from 'react';\n\n/**\n * Hook to debounce a callback function\n * \n * @param callback - Function to debounce\n * @param delay - Delay in milliseconds before execution\n * @returns Debounced function\n * \n * @example\n * ```tsx\n * const handleSearch = useDebounce((query: string) => {\n * // Perform search\n * }, 300);\n * ```\n */\nexport function useDebounce<T extends (...args: unknown[]) => void>(\n callback: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const callbackRef = useRef(callback);\n \n // Keep callback ref up to date\n useLayoutEffect(() => {\n callbackRef.current = callback;\n });\n \n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return useCallback(\n (...args: Parameters<T>) => {\n // Clear existing timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Set new timeout\n timeoutRef.current = setTimeout(() => {\n callbackRef.current(...args);\n timeoutRef.current = null;\n }, delay);\n },\n [delay]\n );\n}\n\nexport default useDebounce;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,YACd,UACA,OACkC;CAClC,MAAM,aAAa,OAA6C,IAAI;CACpE,MAAM,cAAc,OAAO,QAAQ;CAGnC,sBAAsB;EACpB,YAAY,UAAU;CACxB,CAAC;CAGD,gBAAgB;EACd,aAAa;GACX,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAEnC;CACF,GAAG,CAAC,CAAC;CAEL,OAAO,aACJ,GAAG,SAAwB;EAE1B,IAAI,WAAW,SACb,aAAa,WAAW,OAAO;EAIjC,WAAW,UAAU,iBAAiB;GACpC,YAAY,QAAQ,GAAG,IAAI;GAC3B,WAAW,UAAU;EACvB,GAAG,KAAK;CACV,GACA,CAAC,KAAK,CACR;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useHUDLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useHUDLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAG5E;;;GAGG;AACH,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,UAAU,GAAG,QAAqB,EAC3C,eAAe,CAAC,EAAE,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,GACnB,eAAe,CAwEjB"}
1
+ {"version":3,"file":"useHUDLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useHUDLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAS5E;;;GAGG;AACH,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,mDAAmD;IACnD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2BAA2B;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,UAAU,GAAG,QAAqB,EAC3C,eAAe,CAAC,EAAE,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,GACnB,eAAe,CAyEjB"}
@@ -1,3 +1,4 @@
1
+ import { COMBAT_BOTTOM_HUD_HEIGHT_PERCENT, COMBAT_TOP_HUD_HEIGHT_PERCENT, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT, TRAINING_TOP_HUD_HEIGHT_PERCENT } from "../types/constants/layout.js";
1
2
  import { getHUDHeight, getResponsivePadding, getResponsiveSize } from "../utils/responsiveLayout.js";
2
3
  import { useMemo } from "react";
3
4
  //#region src/hooks/useHUDLayout.ts
@@ -44,8 +45,8 @@ function useHUDLayout(width, height, positionScale, position, context = "trainin
44
45
  tablet: 16,
45
46
  desktop: 14
46
47
  }) / 100 : 1;
47
- const topHeightPercent = context === "training" ? .06 : .08;
48
- const bottomHeightPercent = context === "training" ? .11 : .12;
48
+ const topHeightPercent = context === "training" ? TRAINING_TOP_HUD_HEIGHT_PERCENT : COMBAT_TOP_HUD_HEIGHT_PERCENT;
49
+ const bottomHeightPercent = context === "training" ? TRAINING_BOTTOM_HUD_HEIGHT_PERCENT : COMBAT_BOTTOM_HUD_HEIGHT_PERCENT;
49
50
  const scaledTopHeight = getHUDHeight(height, topHeightPercent) * positionScale;
50
51
  const scaledBottomHeight = getHUDHeight(height, bottomHeightPercent) * positionScale;
51
52
  const hudWidth = Math.round(width * hudWidthPercent);
@@ -1 +1 @@
1
- {"version":3,"file":"useHUDLayout.js","names":[],"sources":["../../src/hooks/useHUDLayout.ts"],"sourcesContent":["/**\n * useHUDLayout - Centralized HUD layout calculations\n * \n * Extracts common HUD layout patterns used across Training and Combat screens.\n * Provides resolution-based responsive sizing, positioning, and spacing calculations.\n * \n * @module hooks\n * @korean HUD레이아웃훅 - 중앙화된 HUD 레이아웃 계산\n */\n\nimport { useMemo } from 'react';\nimport type { HUDPosition } from '../components/shared/ui/BaseHUDContainer';\nimport { getResponsiveSize, getHUDHeight, getResponsivePadding } from '../utils/responsiveLayout';\n\n/**\n * HUD position type - left, right, top, or bottom\n * Re-exported from BaseHUDContainer to maintain single source of truth\n */\nexport type { HUDPosition };\n\n/**\n * Result of HUD layout calculations\n */\nexport interface HUDLayoutResult {\n /** HUD width as percentage of screen (0.0-1.0) */\n readonly hudWidthPercent: number;\n /** HUD height as percentage of screen (0.0-1.0) */\n readonly hudHeightPercent: number;\n /** HUD width in pixels */\n readonly hudWidth: number;\n /** HUD height in pixels */\n readonly hudHeight: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset: number;\n /** Bottom offset in pixels (for left/right HUDs) */\n readonly bottomOffset: number;\n /** Available height between top and bottom bars */\n readonly availableHeight: number;\n /** Internal padding in pixels */\n readonly padding: number;\n /** Gap between sections in pixels */\n readonly gap: number;\n}\n\n/**\n * Custom hook for calculating HUD layout dimensions\n * \n * Provides consistent layout calculations across Training and Combat screens.\n * Uses resolution-based responsive sizing for smooth scaling across all screen sizes.\n * \n * @param width - Screen width in pixels\n * @param height - Screen height in pixels\n * @param positionScale - Position scale multiplier for large displays (1.0-1.5)\n * @param position - HUD position (left, right, top, bottom)\n * @param context - Context ('training' or 'combat') for context-specific dimensions\n * @param paddingOverride - Optional padding override (final pixel value, already scaled). When provided, bypasses default padding calculation.\n * @param gapOverride - Optional gap override (final pixel value, already scaled). When provided, bypasses default gap calculation.\n * @returns Calculated layout dimensions and offsets\n * \n * @example\n * ```tsx\n * const layout = useHUDLayout(\n * 1920, 1080, 1.0, 'left', 'training'\n * );\n * // Resolution-based sizing interpolates smoothly between breakpoints\n * // layout.hudWidth = 269 (14% of 1920 for desktop)\n * // layout.topOffset = 64.8 (getHUDHeight(1080, 0.06) = 64.8)\n * // layout.bottomOffset = 118.8 (getHUDHeight(1080, 0.11) = 118.8)\n * // layout.availableHeight = 896.4 (1080 - 64.8 - 118.8)\n * ```\n */\nexport function useHUDLayout(\n width: number,\n height: number,\n positionScale: number,\n position: HUDPosition,\n context: 'training' | 'combat' = 'training',\n paddingOverride?: number,\n gapOverride?: number\n): HUDLayoutResult {\n return useMemo(() => {\n\n // Resolution-based width calculation: interpolates smoothly between breakpoints\n // Left/Right HUDs: 14-18% of screen width (mobile: 18%, tablet: 16%, desktop: 14%)\n // Top/Bottom HUDs: 100% width\n const hudWidthPercent = (position === 'left' || position === 'right')\n ? getResponsiveSize(width, {\n mobile: 18, // 18% for small screens\n tablet: 16, // 16% for medium screens\n desktop: 14, // 14% for large screens\n }) / 100 // Convert to decimal for percentage calculation\n : 1.0; // Top and bottom HUDs use full width\n\n // Resolution-based height calculation for top/bottom bars\n // Training: ~6% for top, ~11% for bottom\n // Combat: ~8% for top, ~12% for bottom\n const topHeightPercent = context === 'training' ? 0.06 : 0.08;\n const bottomHeightPercent = context === 'training' ? 0.11 : 0.12;\n \n const scaledTopHeight = getHUDHeight(height, topHeightPercent) * positionScale;\n const scaledBottomHeight = getHUDHeight(height, bottomHeightPercent) * positionScale;\n\n // Calculate HUD dimensions in pixels\n const hudWidth = Math.round(width * hudWidthPercent);\n const hudHeight = position === 'top' || position === 'bottom'\n ? (position === 'top' ? scaledTopHeight : scaledBottomHeight)\n : Math.max(0, height - scaledTopHeight - scaledBottomHeight);\n\n // Calculate offsets for left/right HUDs\n const topOffset = scaledTopHeight;\n const bottomOffset = scaledBottomHeight;\n const availableHeight = Math.max(0, height - topOffset - bottomOffset);\n\n // Resolution-based padding using responsive utility\n const defaultPadding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap: context-specific values\n // Training uses slightly larger gaps than combat for better readability\n const defaultGap = context === 'training'\n ? getResponsiveSize(width, {\n mobile: 12,\n tablet: 15,\n desktop: 18,\n }) * positionScale\n : getResponsiveSize(width, {\n mobile: 10,\n tablet: 12,\n desktop: 14,\n }) * positionScale;\n \n // Use overrides if provided, otherwise use defaults\n // Overrides allow per-position customization (e.g., TrainingRightHUD uses tighter spacing)\n const padding = paddingOverride ?? defaultPadding;\n const gap = gapOverride ?? defaultGap;\n\n // Guard against division by zero and ensure valid percentage\n const safeHeight = Math.max(height, 1);\n const hudHeightPercent = Math.max(0, Math.min(1, hudHeight / safeHeight));\n\n return {\n hudWidthPercent,\n hudHeightPercent,\n hudWidth,\n hudHeight,\n topOffset,\n bottomOffset,\n availableHeight,\n padding,\n gap,\n };\n }, [width, height, positionScale, position, context, paddingOverride, gapOverride]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,aACd,OACA,QACA,eACA,UACA,UAAiC,YACjC,iBACA,aACiB;CACjB,OAAO,cAAc;EAKnB,MAAM,kBAAmB,aAAa,UAAU,aAAa,UACzD,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,MACL;EAKJ,MAAM,mBAAmB,YAAY,aAAa,MAAO;EACzD,MAAM,sBAAsB,YAAY,aAAa,MAAO;EAE5D,MAAM,kBAAkB,aAAa,QAAQ,iBAAiB,GAAG;EACjE,MAAM,qBAAqB,aAAa,QAAQ,oBAAoB,GAAG;EAGvE,MAAM,WAAW,KAAK,MAAM,QAAQ,gBAAgB;EACpD,MAAM,YAAY,aAAa,SAAS,aAAa,WAChD,aAAa,QAAQ,kBAAkB,qBACxC,KAAK,IAAI,GAAG,SAAS,kBAAkB,mBAAmB;EAG9D,MAAM,YAAY;EAClB,MAAM,eAAe;EACrB,MAAM,kBAAkB,KAAK,IAAI,GAAG,SAAS,YAAY,aAAa;EAGtE,MAAM,iBAAiB,qBAAqB,MAAM,GAAG;EAIrD,MAAM,aAAa,YAAY,aAC3B,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG,gBACL,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;GACV,CAAC,GAAG;EAIT,MAAM,UAAU,mBAAmB;EACnC,MAAM,MAAM,eAAe;EAM3B,OAAO;GACL;GACA,kBAJuB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAD9B,KAAK,IAAI,QAAQ,EACyB,CAAW,CAItE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;IACA;EAAC;EAAO;EAAQ;EAAe;EAAU;EAAS;EAAiB;EAAY,CAAC"}
1
+ {"version":3,"file":"useHUDLayout.js","names":[],"sources":["../../src/hooks/useHUDLayout.ts"],"sourcesContent":["/**\n * useHUDLayout - Centralized HUD layout calculations\n * \n * Extracts common HUD layout patterns used across Training and Combat screens.\n * Provides resolution-based responsive sizing, positioning, and spacing calculations.\n * \n * @module hooks\n * @korean HUD레이아웃훅 - 중앙화된 HUD 레이아웃 계산\n */\n\nimport { useMemo } from 'react';\nimport type { HUDPosition } from '../components/shared/ui/BaseHUDContainer';\nimport {\n COMBAT_BOTTOM_HUD_HEIGHT_PERCENT,\n COMBAT_TOP_HUD_HEIGHT_PERCENT,\n TRAINING_BOTTOM_HUD_HEIGHT_PERCENT,\n TRAINING_TOP_HUD_HEIGHT_PERCENT,\n} from '../types/constants/layout';\nimport { getResponsiveSize, getHUDHeight, getResponsivePadding } from '../utils/responsiveLayout';\n\n/**\n * HUD position type - left, right, top, or bottom\n * Re-exported from BaseHUDContainer to maintain single source of truth\n */\nexport type { HUDPosition };\n\n/**\n * Result of HUD layout calculations\n */\nexport interface HUDLayoutResult {\n /** HUD width as percentage of screen (0.0-1.0) */\n readonly hudWidthPercent: number;\n /** HUD height as percentage of screen (0.0-1.0) */\n readonly hudHeightPercent: number;\n /** HUD width in pixels */\n readonly hudWidth: number;\n /** HUD height in pixels */\n readonly hudHeight: number;\n /** Top offset in pixels (for left/right HUDs) */\n readonly topOffset: number;\n /** Bottom offset in pixels (for left/right HUDs) */\n readonly bottomOffset: number;\n /** Available height between top and bottom bars */\n readonly availableHeight: number;\n /** Internal padding in pixels */\n readonly padding: number;\n /** Gap between sections in pixels */\n readonly gap: number;\n}\n\n/**\n * Custom hook for calculating HUD layout dimensions\n * \n * Provides consistent layout calculations across Training and Combat screens.\n * Uses resolution-based responsive sizing for smooth scaling across all screen sizes.\n * \n * @param width - Screen width in pixels\n * @param height - Screen height in pixels\n * @param positionScale - Position scale multiplier for large displays (1.0-1.5)\n * @param position - HUD position (left, right, top, bottom)\n * @param context - Context ('training' or 'combat') for context-specific dimensions\n * @param paddingOverride - Optional padding override (final pixel value, already scaled). When provided, bypasses default padding calculation.\n * @param gapOverride - Optional gap override (final pixel value, already scaled). When provided, bypasses default gap calculation.\n * @returns Calculated layout dimensions and offsets\n * \n * @example\n * ```tsx\n * const layout = useHUDLayout(\n * 1920, 1080, 1.0, 'left', 'training'\n * );\n * // Resolution-based sizing interpolates smoothly between breakpoints\n * // layout.hudWidth = 269 (14% of 1920 for desktop)\n * // layout.topOffset = 64.8 (getHUDHeight(1080, 0.06) = 64.8)\n * // layout.bottomOffset = 118.8 (getHUDHeight(1080, 0.11) = 118.8)\n * // layout.availableHeight = 896.4 (1080 - 64.8 - 118.8)\n * ```\n */\nexport function useHUDLayout(\n width: number,\n height: number,\n positionScale: number,\n position: HUDPosition,\n context: 'training' | 'combat' = 'training',\n paddingOverride?: number,\n gapOverride?: number\n): HUDLayoutResult {\n return useMemo(() => {\n\n // Resolution-based width calculation: interpolates smoothly between breakpoints\n // Left/Right HUDs: 14-18% of screen width (mobile: 18%, tablet: 16%, desktop: 14%)\n // Top/Bottom HUDs: 100% width\n const hudWidthPercent = (position === 'left' || position === 'right')\n ? getResponsiveSize(width, {\n mobile: 18, // 18% for small screens\n tablet: 16, // 16% for medium screens\n desktop: 14, // 14% for large screens\n }) / 100 // Convert to decimal for percentage calculation\n : 1.0; // Top and bottom HUDs use full width\n\n const topHeightPercent = context === 'training'\n ? TRAINING_TOP_HUD_HEIGHT_PERCENT\n : COMBAT_TOP_HUD_HEIGHT_PERCENT;\n const bottomHeightPercent = context === 'training'\n ? TRAINING_BOTTOM_HUD_HEIGHT_PERCENT\n : COMBAT_BOTTOM_HUD_HEIGHT_PERCENT;\n \n const scaledTopHeight = getHUDHeight(height, topHeightPercent) * positionScale;\n const scaledBottomHeight = getHUDHeight(height, bottomHeightPercent) * positionScale;\n\n // Calculate HUD dimensions in pixels\n const hudWidth = Math.round(width * hudWidthPercent);\n const hudHeight = position === 'top' || position === 'bottom'\n ? (position === 'top' ? scaledTopHeight : scaledBottomHeight)\n : Math.max(0, height - scaledTopHeight - scaledBottomHeight);\n\n // Calculate offsets for left/right HUDs\n const topOffset = scaledTopHeight;\n const bottomOffset = scaledBottomHeight;\n const availableHeight = Math.max(0, height - topOffset - bottomOffset);\n\n // Resolution-based padding using responsive utility\n const defaultPadding = getResponsivePadding(width) * positionScale;\n \n // Resolution-based gap: context-specific values\n // Training uses slightly larger gaps than combat for better readability\n const defaultGap = context === 'training'\n ? getResponsiveSize(width, {\n mobile: 12,\n tablet: 15,\n desktop: 18,\n }) * positionScale\n : getResponsiveSize(width, {\n mobile: 10,\n tablet: 12,\n desktop: 14,\n }) * positionScale;\n \n // Use overrides if provided, otherwise use defaults\n // Overrides allow per-position customization (e.g., TrainingRightHUD uses tighter spacing)\n const padding = paddingOverride ?? defaultPadding;\n const gap = gapOverride ?? defaultGap;\n\n // Guard against division by zero and ensure valid percentage\n const safeHeight = Math.max(height, 1);\n const hudHeightPercent = Math.max(0, Math.min(1, hudHeight / safeHeight));\n\n return {\n hudWidthPercent,\n hudHeightPercent,\n hudWidth,\n hudHeight,\n topOffset,\n bottomOffset,\n availableHeight,\n padding,\n gap,\n };\n }, [width, height, positionScale, position, context, paddingOverride, gapOverride]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA,SAAgB,aACd,OACA,QACA,eACA,UACA,UAAiC,YACjC,iBACA,aACiB;CACjB,OAAO,cAAc;EAKnB,MAAM,kBAAmB,aAAa,UAAU,aAAa,UACzD,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;EACX,CAAC,IAAI,MACL;EAEJ,MAAM,mBAAmB,YAAY,aACjC,kCACA;EACJ,MAAM,sBAAsB,YAAY,aACpC,qCACA;EAEJ,MAAM,kBAAkB,aAAa,QAAQ,gBAAgB,IAAI;EACjE,MAAM,qBAAqB,aAAa,QAAQ,mBAAmB,IAAI;EAGvE,MAAM,WAAW,KAAK,MAAM,QAAQ,eAAe;EACnD,MAAM,YAAY,aAAa,SAAS,aAAa,WAChD,aAAa,QAAQ,kBAAkB,qBACxC,KAAK,IAAI,GAAG,SAAS,kBAAkB,kBAAkB;EAG7D,MAAM,YAAY;EAClB,MAAM,eAAe;EACrB,MAAM,kBAAkB,KAAK,IAAI,GAAG,SAAS,YAAY,YAAY;EAGrE,MAAM,iBAAiB,qBAAqB,KAAK,IAAI;EAIrD,MAAM,aAAa,YAAY,aAC3B,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;EACX,CAAC,IAAI,gBACL,kBAAkB,OAAO;GACvB,QAAQ;GACR,QAAQ;GACR,SAAS;EACX,CAAC,IAAI;EAIT,MAAM,UAAU,mBAAmB;EACnC,MAAM,MAAM,eAAe;EAM3B,OAAO;GACL;GACA,kBAJuB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAD9B,KAAK,IAAI,QAAQ,CACyB,CAAU,CAIrE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,GAAG;EAAC;EAAO;EAAQ;EAAe;EAAU;EAAS;EAAiB;CAAW,CAAC;AACpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useHandPoseTransitions.js","names":[],"sources":["../../src/hooks/useHandPoseTransitions.ts"],"sourcesContent":["/**\n * useHandPoseTransitions - Shared hook for hand animation management\n *\n * Manages hand pose transitions and animation state for both hands.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useHandPoseTransitions\n * @category Hooks\n * @korean 손자세전환훅\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n createInitialHandAnimationState,\n getTechniqueHandPose,\n updateHandAnimationState,\n} from \"../systems/animation\";\nimport type { HandAnimationState } from \"../types/hand-animation\";\nimport { HandPoseType } from \"../types/hand-animation\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Default transition duration for hand pose changes (in seconds)\n * @korean 손자세전환기본지속시간\n */\nconst DEFAULT_HAND_TRANSITION_DURATION = 0.2;\n\n/**\n * Frequency of React state syncs during hand animations.\n * Value of 20 means sync every 5% progress (~every 3 frames at 60fps).\n * Reduces React re-renders from 60/sec to ~20/sec during transitions.\n * @korean 손상태동기화빈도\n */\nconst HAND_STATE_SYNC_FREQUENCY = 20;\n\n/**\n * Options for useHandPoseTransitions hook\n * @korean 손자세전환훅옵션\n */\nexport interface UseHandPoseTransitionsOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n}\n\n/**\n * Return type for useHandPoseTransitions hook\n * @korean 손자세전환훅반환타입\n */\nexport interface UseHandPoseTransitionsReturn {\n /** Left hand animation state */\n readonly leftHandState: HandAnimationState;\n /** Right hand animation state */\n readonly rightHandState: HandAnimationState;\n /** Update hand animations (call in useFrame) */\n readonly updateHandAnimations: (delta: number) => void;\n}\n\n/**\n * Updates hand animation state for a single hand at 60fps.\n * Uses refs to avoid triggering React re-renders on every frame.\n * Only syncs to React state periodically or when transition completes.\n *\n * @param handStateRef - Ref storing current hand animation state\n * @param setHandState - React setState function to update hand state\n * @param delta - Time elapsed since last frame (in seconds)\n * @korean 손애니메이션프레임업데이트\n */\nconst updateHandAnimationFrame = (\n handStateRef: React.MutableRefObject<HandAnimationState>,\n setHandState: React.Dispatch<React.SetStateAction<HandAnimationState>>,\n delta: number\n): void => {\n if (handStateRef.current && handStateRef.current.targetPose !== null) {\n const previousState = handStateRef.current;\n const updatedState = updateHandAnimationState(\n previousState,\n previousState.targetPose,\n delta,\n DEFAULT_HAND_TRANSITION_DURATION\n );\n\n handStateRef.current = updatedState;\n\n // Sync to React state periodically (approximately every 3 frames at 60fps)\n if (\n updatedState.targetPose === null ||\n Math.floor(updatedState.transitionProgress * HAND_STATE_SYNC_FREQUENCY) !==\n Math.floor(\n previousState.transitionProgress * HAND_STATE_SYNC_FREQUENCY\n )\n ) {\n setHandState(updatedState);\n }\n }\n};\n\n/**\n * useHandPoseTransitions hook\n *\n * Manages hand pose transitions for both hands based on current animation.\n * Automatically selects appropriate hand poses for different techniques\n * and handles smooth transitions at 60fps.\n *\n * @param options - Hand animation options\n * @returns Hand states and update function\n *\n * @example\n * ```tsx\n * const { leftHandState, rightHandState, updateHandAnimations } =\n * useHandPoseTransitions({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateHandAnimations(delta);\n * });\n * ```\n *\n * @korean 손자세전환훅\n */\nexport function useHandPoseTransitions(\n options: UseHandPoseTransitionsOptions\n): UseHandPoseTransitionsReturn {\n const { currentAnimation, attackAnimation, isBlocking = false } = options;\n\n // Hand animation states\n const [leftHandState, setLeftHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n const [rightHandState, setRightHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n\n // Refs for 60fps animation updates without triggering React re-renders\n const leftHandStateRef = useRef<HandAnimationState>(leftHandState);\n const rightHandStateRef = useRef<HandAnimationState>(rightHandState);\n\n // Sync refs with state\n useEffect(() => {\n leftHandStateRef.current = leftHandState;\n }, [leftHandState]);\n\n useEffect(() => {\n rightHandStateRef.current = rightHandState;\n }, [rightHandState]);\n\n // Update hand poses based on current animation\n useEffect(() => {\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - use technique-specific hand poses\n const handPose = getTechniqueHandPose(attackAnimation);\n setLeftHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.leftHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n setRightHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.rightHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Open hands for blocking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation === \"walk\") {\n // Relaxed hands while walking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n } else if (currentAnimation === \"stance_change\") {\n // Guard hands during stance change\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n } else if (\n currentAnimation?.startsWith(\"step_\") ||\n currentAnimation?.startsWith(\"footwork_\")\n ) {\n // Maintain guard hands during steps and footwork\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation?.startsWith(\"stance_guard_\")) {\n // Guard hands in open position\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n } else {\n // Idle - return to relaxed hand pose\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n }\n }, [currentAnimation, attackAnimation, isBlocking]);\n\n // Update hand animations at 60fps\n const updateHandAnimations = (delta: number): void => {\n updateHandAnimationFrame(leftHandStateRef, setLeftHandState, delta);\n updateHandAnimationFrame(rightHandStateRef, setRightHandState, delta);\n };\n\n return {\n leftHandState,\n rightHandState,\n updateHandAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyBA,IAAM,mCAAmC;;;;;;;AAQzC,IAAM,4BAA4B;;;;;;;;;;;AAsClC,IAAM,4BACJ,cACA,cACA,UACS;CACT,IAAI,aAAa,WAAW,aAAa,QAAQ,eAAe,MAAM;EACpE,MAAM,gBAAgB,aAAa;EACnC,MAAM,eAAe,yBACnB,eACA,cAAc,YACd,OACA,iCACD;EAED,aAAa,UAAU;EAGvB,IACE,aAAa,eAAe,QAC5B,KAAK,MAAM,aAAa,qBAAqB,0BAA0B,KACrE,KAAK,MACH,cAAc,qBAAqB,0BACpC,EAEH,aAAa,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgChC,SAAgB,uBACd,SAC8B;CAC9B,MAAM,EAAE,kBAAkB,iBAAiB,aAAa,UAAU;CAGlE,MAAM,CAAC,eAAe,oBAAoB,SACxC,gCAAgC,aAAa,KAAK,CACnD;CACD,MAAM,CAAC,gBAAgB,qBAAqB,SAC1C,gCAAgC,aAAa,KAAK,CACnD;CAGD,MAAM,mBAAmB,OAA2B,cAAc;CAClE,MAAM,oBAAoB,OAA2B,eAAe;CAGpE,gBAAgB;EACd,iBAAiB,UAAU;IAC1B,CAAC,cAAc,CAAC;CAEnB,gBAAgB;EACd,kBAAkB,UAAU;IAC3B,CAAC,eAAe,CAAC;CAGpB,gBAAgB;EACd,IAAI,qBAAqB,YAAY,iBAAiB;GAEpD,MAAM,WAAW,qBAAqB,gBAAgB;GACtD,kBAAkB,SAChB,yBACE,MACA,SAAS,cACT,GACA,SAAS,mBACV,CACF;GACD,mBAAmB,SACjB,yBACE,MACA,SAAS,eACT,GACA,SAAS,mBACV,CACF;SACI,IAAI,qBAAqB,YAAY,YAAY;GAEtD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI,IAAI,qBAAqB,QAAQ;GAEtC,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;SACI,IAAI,qBAAqB,iBAAiB;GAE/C,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,IAAK,CAC3D;SACI,IACL,kBAAkB,WAAW,QAAQ,IACrC,kBAAkB,WAAW,YAAY,EACzC;GAEA,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI,IAAI,kBAAkB,WAAW,gBAAgB,EAAE;GAExD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC1D;SACI;GAEL,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;GACD,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,GAAI,CAC7D;;IAEF;EAAC;EAAkB;EAAiB;EAAW,CAAC;CAGnD,MAAM,wBAAwB,UAAwB;EACpD,yBAAyB,kBAAkB,kBAAkB,MAAM;EACnE,yBAAyB,mBAAmB,mBAAmB,MAAM;;CAGvE,OAAO;EACL;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useHandPoseTransitions.js","names":[],"sources":["../../src/hooks/useHandPoseTransitions.ts"],"sourcesContent":["/**\n * useHandPoseTransitions - Shared hook for hand animation management\n *\n * Manages hand pose transitions and animation state for both hands.\n * Reduces code duplication in skeletal animation components.\n *\n * @module hooks/useHandPoseTransitions\n * @category Hooks\n * @korean 손자세전환훅\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n createInitialHandAnimationState,\n getTechniqueHandPose,\n updateHandAnimationState,\n} from \"../systems/animation\";\nimport type { HandAnimationState } from \"../types/hand-animation\";\nimport { HandPoseType } from \"../types/hand-animation\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\n\n/**\n * Default transition duration for hand pose changes (in seconds)\n * @korean 손자세전환기본지속시간\n */\nconst DEFAULT_HAND_TRANSITION_DURATION = 0.2;\n\n/**\n * Frequency of React state syncs during hand animations.\n * Value of 20 means sync every 5% progress (~every 3 frames at 60fps).\n * Reduces React re-renders from 60/sec to ~20/sec during transitions.\n * @korean 손상태동기화빈도\n */\nconst HAND_STATE_SYNC_FREQUENCY = 20;\n\n/**\n * Options for useHandPoseTransitions hook\n * @korean 손자세전환훅옵션\n */\nexport interface UseHandPoseTransitionsOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n}\n\n/**\n * Return type for useHandPoseTransitions hook\n * @korean 손자세전환훅반환타입\n */\nexport interface UseHandPoseTransitionsReturn {\n /** Left hand animation state */\n readonly leftHandState: HandAnimationState;\n /** Right hand animation state */\n readonly rightHandState: HandAnimationState;\n /** Update hand animations (call in useFrame) */\n readonly updateHandAnimations: (delta: number) => void;\n}\n\n/**\n * Updates hand animation state for a single hand at 60fps.\n * Uses refs to avoid triggering React re-renders on every frame.\n * Only syncs to React state periodically or when transition completes.\n *\n * @param handStateRef - Ref storing current hand animation state\n * @param setHandState - React setState function to update hand state\n * @param delta - Time elapsed since last frame (in seconds)\n * @korean 손애니메이션프레임업데이트\n */\nconst updateHandAnimationFrame = (\n handStateRef: React.MutableRefObject<HandAnimationState>,\n setHandState: React.Dispatch<React.SetStateAction<HandAnimationState>>,\n delta: number\n): void => {\n if (handStateRef.current && handStateRef.current.targetPose !== null) {\n const previousState = handStateRef.current;\n const updatedState = updateHandAnimationState(\n previousState,\n previousState.targetPose,\n delta,\n DEFAULT_HAND_TRANSITION_DURATION\n );\n\n handStateRef.current = updatedState;\n\n // Sync to React state periodically (approximately every 3 frames at 60fps)\n if (\n updatedState.targetPose === null ||\n Math.floor(updatedState.transitionProgress * HAND_STATE_SYNC_FREQUENCY) !==\n Math.floor(\n previousState.transitionProgress * HAND_STATE_SYNC_FREQUENCY\n )\n ) {\n setHandState(updatedState);\n }\n }\n};\n\n/**\n * useHandPoseTransitions hook\n *\n * Manages hand pose transitions for both hands based on current animation.\n * Automatically selects appropriate hand poses for different techniques\n * and handles smooth transitions at 60fps.\n *\n * @param options - Hand animation options\n * @returns Hand states and update function\n *\n * @example\n * ```tsx\n * const { leftHandState, rightHandState, updateHandAnimations } =\n * useHandPoseTransitions({\n * currentAnimation: \"attack\",\n * attackAnimation: \"jab\",\n * isBlocking: false,\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateHandAnimations(delta);\n * });\n * ```\n *\n * @korean 손자세전환훅\n */\nexport function useHandPoseTransitions(\n options: UseHandPoseTransitionsOptions\n): UseHandPoseTransitionsReturn {\n const { currentAnimation, attackAnimation, isBlocking = false } = options;\n\n // Hand animation states\n const [leftHandState, setLeftHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n const [rightHandState, setRightHandState] = useState<HandAnimationState>(\n createInitialHandAnimationState(HandPoseType.OPEN)\n );\n\n // Refs for 60fps animation updates without triggering React re-renders\n const leftHandStateRef = useRef<HandAnimationState>(leftHandState);\n const rightHandStateRef = useRef<HandAnimationState>(rightHandState);\n\n // Sync refs with state\n useEffect(() => {\n leftHandStateRef.current = leftHandState;\n }, [leftHandState]);\n\n useEffect(() => {\n rightHandStateRef.current = rightHandState;\n }, [rightHandState]);\n\n // Update hand poses based on current animation\n useEffect(() => {\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - use technique-specific hand poses\n const handPose = getTechniqueHandPose(attackAnimation);\n setLeftHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.leftHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n setRightHandState((prev) =>\n updateHandAnimationState(\n prev,\n handPose.rightHandPose,\n 0,\n handPose.transitionDuration\n )\n );\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Open hands for blocking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation === \"walk\") {\n // Relaxed hands while walking\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.2)\n );\n } else if (currentAnimation === \"stance_change\") {\n // Guard hands during stance change\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.15)\n );\n } else if (\n currentAnimation?.startsWith(\"step_\") ||\n currentAnimation?.startsWith(\"footwork_\")\n ) {\n // Maintain guard hands during steps and footwork\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.1)\n );\n } else if (currentAnimation?.startsWith(\"stance_guard_\")) {\n // Guard hands in open position\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.OPEN, 0, 0.2)\n );\n } else {\n // Idle - return to relaxed hand pose\n setLeftHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n setRightHandState((prev) =>\n updateHandAnimationState(prev, HandPoseType.RELAXED, 0, 0.3)\n );\n }\n }, [currentAnimation, attackAnimation, isBlocking]);\n\n // Update hand animations at 60fps\n const updateHandAnimations = (delta: number): void => {\n updateHandAnimationFrame(leftHandStateRef, setLeftHandState, delta);\n updateHandAnimationFrame(rightHandStateRef, setRightHandState, delta);\n };\n\n return {\n leftHandState,\n rightHandState,\n updateHandAnimations,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyBA,IAAM,mCAAmC;;;;;;;AAQzC,IAAM,4BAA4B;;;;;;;;;;;AAsClC,IAAM,4BACJ,cACA,cACA,UACS;CACT,IAAI,aAAa,WAAW,aAAa,QAAQ,eAAe,MAAM;EACpE,MAAM,gBAAgB,aAAa;EACnC,MAAM,eAAe,yBACnB,eACA,cAAc,YACd,OACA,gCACF;EAEA,aAAa,UAAU;EAGvB,IACE,aAAa,eAAe,QAC5B,KAAK,MAAM,aAAa,qBAAqB,yBAAyB,MACpE,KAAK,MACH,cAAc,qBAAqB,yBACrC,GAEF,aAAa,YAAY;CAE7B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,uBACd,SAC8B;CAC9B,MAAM,EAAE,kBAAkB,iBAAiB,aAAa,UAAU;CAGlE,MAAM,CAAC,eAAe,oBAAoB,SACxC,gCAAgC,aAAa,IAAI,CACnD;CACA,MAAM,CAAC,gBAAgB,qBAAqB,SAC1C,gCAAgC,aAAa,IAAI,CACnD;CAGA,MAAM,mBAAmB,OAA2B,aAAa;CACjE,MAAM,oBAAoB,OAA2B,cAAc;CAGnE,gBAAgB;EACd,iBAAiB,UAAU;CAC7B,GAAG,CAAC,aAAa,CAAC;CAElB,gBAAgB;EACd,kBAAkB,UAAU;CAC9B,GAAG,CAAC,cAAc,CAAC;CAGnB,gBAAgB;EACd,IAAI,qBAAqB,YAAY,iBAAiB;GAEpD,MAAM,WAAW,qBAAqB,eAAe;GACrD,kBAAkB,SAChB,yBACE,MACA,SAAS,cACT,GACA,SAAS,kBACX,CACF;GACA,mBAAmB,SACjB,yBACE,MACA,SAAS,eACT,GACA,SAAS,kBACX,CACF;EACF,OAAO,IAAI,qBAAqB,YAAY,YAAY;GAEtD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;EACF,OAAO,IAAI,qBAAqB,QAAQ;GAEtC,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,EAAG,CAC7D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,EAAG,CAC7D;EACF,OAAO,IAAI,qBAAqB,iBAAiB;GAE/C,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC3D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,GAAI,CAC3D;EACF,OAAO,IACL,kBAAkB,WAAW,OAAO,KACpC,kBAAkB,WAAW,WAAW,GACxC;GAEA,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;EACF,OAAO,IAAI,kBAAkB,WAAW,eAAe,GAAG;GAExD,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,MAAM,GAAG,EAAG,CAC1D;EACF,OAAO;GAEL,kBAAkB,SAChB,yBAAyB,MAAM,aAAa,SAAS,GAAG,EAAG,CAC7D;GACA,mBAAmB,SACjB,yBAAyB,MAAM,aAAa,SAAS,GAAG,EAAG,CAC7D;EACF;CACF,GAAG;EAAC;EAAkB;EAAiB;CAAU,CAAC;CAGlD,MAAM,wBAAwB,UAAwB;EACpD,yBAAyB,kBAAkB,kBAAkB,KAAK;EAClE,yBAAyB,mBAAmB,mBAAmB,KAAK;CACtE;CAEA,OAAO;EACL;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useKeyboardControls.js","names":[],"sources":["../../src/hooks/useKeyboardControls.ts"],"sourcesContent":["/**\n * Custom hook for keyboard controls with visual feedback\n * Manages keyboard input for trigram stance switching and combat actions\n *\n * @module hooks/useKeyboardControls\n * @category Input System\n * @korean 키보드 컨트롤 훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FOOTWORK_KOREAN_TERMS } from \"../systems/animation/core/types\";\nimport { ControlMapper } from \"../utils/controlMapping\";\n\n// Korean terminology constants (module-level to avoid recreation on every keystroke)\nconst DIAGONAL_KOREAN_TERMS: Record<string, string> = {\n step_forward_left: \"전좌측보법 (Forward-Left)\",\n step_forward_right: \"전우측보법 (Forward-Right)\",\n step_back_left: \"후좌측보법 (Back-Left)\",\n step_back_right: \"후우측보법 (Back-Right)\",\n};\n\nconst STEP_KOREAN_TERMS: Record<string, string> = {\n step_forward: \"전진보법 (Forward Step)\",\n step_back: \"후퇴보법 (Retreat Step)\",\n step_left: \"좌측면보법 (Left Step)\",\n step_right: \"우측면보법 (Right Step)\",\n};\n\n// Footwork pattern Korean terminology - maps animation states to display names\n// Reuses FOOTWORK_KOREAN_TERMS from types.ts but adds specific animation state labels\nconst FOOTWORK_DISPLAY_TERMS: Record<string, string> = {\n footwork_circular_left: `${FOOTWORK_KOREAN_TERMS.circular.korean} 좌 (Circular Left)`,\n footwork_circular_right: `${FOOTWORK_KOREAN_TERMS.circular.korean} 우 (Circular Right)`,\n footwork_pivot_left: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 좌 (Pivot Left)`,\n footwork_pivot_right: `${FOOTWORK_KOREAN_TERMS.pivot.korean} 우 (Pivot Right)`,\n footwork_slide_forward: `${FOOTWORK_KOREAN_TERMS.slide.korean} 전 (Slide Forward)`,\n footwork_slide_back: `${FOOTWORK_KOREAN_TERMS.slide.korean} 후 (Slide Back)`,\n footwork_slide_left: `${FOOTWORK_KOREAN_TERMS.slide.korean} 좌 (Slide Left)`,\n footwork_slide_right: `${FOOTWORK_KOREAN_TERMS.slide.korean} 우 (Slide Right)`,\n footwork_shuffle: `${FOOTWORK_KOREAN_TERMS.shuffle.korean} (Shuffle)`,\n};\n\n// Step direction mapping (all move_* actions covered explicitly)\nconst STEP_DIRECTION_MAP: Record<string, string> = {\n move_up: \"step_forward\",\n move_down: \"step_back\",\n move_left: \"step_left\",\n move_right: \"step_right\",\n};\n\n/**\n * Queued input for input buffer display\n */\nexport interface QueuedInput {\n readonly action: string;\n readonly key: string;\n readonly timestamp: number;\n}\n\n/**\n * Props for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsProps {\n /** Callback when stance changes (receives stance index 0-7) */\n readonly onStanceChange: (stance: number) => void;\n /** Callback for combat actions (attack, block, etc.) */\n readonly onAction: (action: string) => void;\n /** Whether keyboard input is enabled */\n readonly enabled?: boolean;\n /** Current stance index (0-7) for validation */\n readonly currentStance?: number;\n /** Callback when hints toggle is requested */\n readonly onToggleHints?: () => void;\n /** Play sound effect function */\n readonly playSFX?: (soundId: string) => void;\n /** Current animation state for grounded detection */\n readonly currentAnimationState?: string;\n}\n\n/**\n * Return type for useKeyboardControls hook\n */\nexport interface UseKeyboardControlsReturn {\n /** Queued inputs for display */\n readonly queuedInputs: readonly QueuedInput[];\n /** Whether hints are visible */\n readonly showHints: boolean;\n /** Toggle hints visibility */\n readonly toggleHints: () => void;\n}\n\n/**\n * Maximum number of inputs to keep in queue\n */\nconst MAX_QUEUE_SIZE = 3;\n\n/**\n * Time to keep inputs in queue (milliseconds)\n */\nconst QUEUE_RETENTION_TIME = 2000;\n\n/**\n * Custom hook for handling keyboard controls with visual feedback\n *\n * Features:\n * - Trigram stance switching (1-8 keys or custom bindings)\n * - Combat action handling (attack, block, movement)\n * - Input queue visualization\n * - Keyboard hints toggle (F1)\n * - Invalid input prevention\n * - Audio feedback integration\n *\n * @example\n * ```typescript\n * const { queuedInputs, showHints, toggleHints } = useKeyboardControls({\n * onStanceChange: (stance) => handleStanceChange(stance),\n * onAction: (action) => handleAction(action),\n * enabled: !isPaused,\n * currentStance: player.stance,\n * playSFX: audio.playSFX,\n * });\n * ```\n *\n * @public\n * @korean 키보드컨트롤사용\n */\nexport function useKeyboardControls({\n onStanceChange,\n onAction,\n enabled = true,\n currentStance = 0,\n onToggleHints,\n playSFX,\n currentAnimationState,\n}: UseKeyboardControlsProps): UseKeyboardControlsReturn {\n const [queuedInputs, setQueuedInputs] = useState<readonly QueuedInput[]>([]);\n const [showHints, setShowHints] = useState(false);\n const controlMapperRef = useRef<ControlMapper>(new ControlMapper());\n const lastStanceChangeRef = useRef<number>(0);\n\n // Track currently pressed keys for diagonal step detection\n const pressedKeysRef = useRef<Set<string>>(new Set());\n\n /**\n * Toggle hints visibility\n */\n const toggleHints = useCallback(() => {\n setShowHints((prev) => {\n const newState = !prev;\n if (onToggleHints) {\n onToggleHints();\n }\n // Play UI sound\n if (playSFX) {\n playSFX(\"menu_select\");\n }\n return newState;\n });\n }, [onToggleHints, playSFX]);\n\n /**\n * Detect diagonal step direction from pressed keys\n * Returns step action for diagonal or null if not diagonal\n *\n * @korean 대각선발걸음감지\n */\n const getDiagonalStepAction = useCallback((): string | null => {\n const keys = pressedKeysRef.current;\n const mapper = controlMapperRef.current;\n\n // Check for forward diagonals\n if (keys.has(mapper.getBindings().movement.up.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_forward_left\"; // 전좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_forward_right\"; // 전우측보법\n }\n }\n\n // Check for backward diagonals\n if (keys.has(mapper.getBindings().movement.down.toLowerCase())) {\n if (keys.has(mapper.getBindings().movement.left.toLowerCase())) {\n return \"step_back_left\"; // 후좌측보법\n }\n if (keys.has(mapper.getBindings().movement.right.toLowerCase())) {\n return \"step_back_right\"; // 후우측보법\n }\n }\n\n return null;\n }, []);\n\n /**\n * Add input to queue\n */\n const addToQueue = useCallback((action: string, key: string) => {\n const newInput: QueuedInput = {\n action,\n key,\n timestamp: Date.now(),\n };\n\n setQueuedInputs((prev) => {\n const updated = [newInput, ...prev].slice(0, MAX_QUEUE_SIZE);\n return updated;\n });\n }, []);\n\n /**\n * Clean up old queued inputs\n * Single interval for component lifetime to avoid recreating on every input\n */\n useEffect(() => {\n const interval = setInterval(() => {\n const now = Date.now();\n setQueuedInputs((prev) => {\n if (prev.length === 0) return prev;\n return prev.filter(\n (input) => now - input.timestamp < QUEUE_RETENTION_TIME\n );\n });\n }, 500);\n\n return () => clearInterval(interval);\n }, []); // Empty deps - single interval for component lifetime\n\n /**\n * Handle keyboard input\n */\n useEffect(() => {\n if (!enabled) return;\n\n // Copy ref value for cleanup\n const pressedKeys = pressedKeysRef.current;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const mapper = controlMapperRef.current;\n\n // Track pressed keys for diagonal detection\n pressedKeysRef.current.add(e.key.toLowerCase());\n\n // F1: Toggle hints (prevent default browser help)\n if (e.key === \"F1\") {\n e.preventDefault();\n toggleHints();\n return;\n }\n\n // Escape: Close hints if open\n if (e.key === \"Escape\" && showHints) {\n e.preventDefault();\n setShowHints(false);\n return;\n }\n\n // Recovery input detection (기상 입력 감지)\n // When player is in ground state, any button triggers recovery options\n const isGrounded = currentAnimationState?.startsWith(\"ground_\");\n\n if (isGrounded) {\n // Space: Quick/default recovery\n if (e.key === \" \") {\n e.preventDefault();\n onAction(\"recovery_quick\");\n addToQueue(\"기상 (Quick Recovery)\", \"Space\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // R or Enter: Roll recovery (회전기상) - fast but costs stamina\n if (e.key.toLowerCase() === \"r\" || e.key === \"Enter\") {\n e.preventDefault();\n onAction(\"recovery_roll\");\n addToQueue(\"회전기상 (Roll Recovery)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // Shift: Defensive getup (방어기상) - slow but protected\n if (e.shiftKey) {\n e.preventDefault();\n onAction(\"recovery_defensive\");\n addToQueue(\"방어기상 (Defensive Getup)\", \"Shift\");\n if (playSFX) playSFX(\"stance_change\");\n return;\n }\n\n // If grounded, don't process other inputs (can't attack/move while down)\n return;\n }\n\n // Check for stance change (1-8 or custom bindings)\n const stanceIndex = mapper.getStanceForKey(e.key);\n if (stanceIndex !== null) {\n e.preventDefault();\n\n // Prevent switching to the same stance\n if (stanceIndex === currentStance) {\n // Invalid input feedback (shake animation trigger)\n addToQueue(`Stance ${stanceIndex + 1} (already active)`, e.key);\n\n // Play error sound\n if (playSFX) {\n playSFX(\"menu_error\");\n }\n return;\n }\n\n // Throttle stance changes (minimum 8 frames at 60fps = 133ms)\n const now = Date.now();\n if (now - lastStanceChangeRef.current < 133) {\n return;\n }\n lastStanceChangeRef.current = now;\n\n // Execute stance change\n onStanceChange(stanceIndex);\n addToQueue(`Stance ${stanceIndex + 1}`, e.key);\n\n // Play stance change sound\n if (playSFX) {\n playSFX(\"stance_change\");\n }\n return;\n }\n\n // Handle other actions\n const action = mapper.getActionForKey(e.key);\n if (action) {\n e.preventDefault();\n\n switch (action) {\n case \"attack\":\n onAction(\"attack\");\n addToQueue(\"Attack\", e.key);\n if (playSFX) playSFX(\"attack_light\");\n break;\n\n case \"block\":\n onAction(\"block\");\n addToQueue(\"Block\", e.key);\n if (playSFX) playSFX(\"block\");\n break;\n\n case \"move_up\":\n case \"move_down\":\n case \"move_left\":\n case \"move_right\":\n // Check for Alt+A/D modifier for slide left/right (MUST be checked first)\n if (e.altKey) {\n // Alt modifier for slide left/right footwork patterns\n let slideFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Alt+A: Slide left (미끄럼보 좌)\n slideFootwork = \"footwork_slide_left\";\n } else if (action === \"move_right\") {\n // Alt+D: Slide right (미끄럼보 우)\n slideFootwork = \"footwork_slide_right\";\n }\n\n if (slideFootwork) {\n onAction(slideFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[slideFootwork] ?? \"Slide Footwork\",\n `Alt+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey && e.ctrlKey) {\n // Shift+Ctrl modifier for advanced footwork patterns\n let advancedFootwork: string | null = null;\n\n if (action === \"move_left\") {\n // Shift+Ctrl+A: Pivot left (축족회전 좌)\n advancedFootwork = \"footwork_pivot_left\";\n } else if (action === \"move_right\") {\n // Shift+Ctrl+D: Pivot right (축족회전 우)\n advancedFootwork = \"footwork_pivot_right\";\n } else if (action === \"move_up\" || action === \"move_down\") {\n // Shift+Ctrl+W or Shift+Ctrl+S: Shuffle step (섞음보)\n advancedFootwork = \"footwork_shuffle\";\n }\n\n if (advancedFootwork) {\n onAction(advancedFootwork);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[advancedFootwork] ??\n \"Advanced Footwork\",\n `Shift+Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.ctrlKey) {\n // Footwork patterns with Ctrl+WASD\n let footworkAction: string | null = null;\n\n if (action === \"move_left\") {\n // Ctrl+A: Circular step left (원형보)\n footworkAction = \"footwork_circular_left\";\n } else if (action === \"move_right\") {\n // Ctrl+D: Circular step right (원형보)\n footworkAction = \"footwork_circular_right\";\n } else if (action === \"move_up\") {\n // Ctrl+W: Slide forward (미끄럼보)\n footworkAction = \"footwork_slide_forward\";\n } else if (action === \"move_down\") {\n // Ctrl+S: Slide back (미끄럼보)\n footworkAction = \"footwork_slide_back\";\n }\n\n if (footworkAction) {\n onAction(footworkAction);\n addToQueue(\n FOOTWORK_DISPLAY_TERMS[footworkAction] ?? \"Footwork\",\n `Ctrl+${e.key}`\n );\n if (playSFX) playSFX(\"footstep\");\n }\n } else if (e.shiftKey) {\n // Check if Shift is held for tactical step instead of walk\n // Check for diagonal step first\n const diagonalStep = getDiagonalStepAction();\n\n if (diagonalStep) {\n // Diagonal tactical step\n onAction(diagonalStep);\n\n addToQueue(\n DIAGONAL_KOREAN_TERMS[diagonalStep] ?? \"Diagonal Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n } else {\n // Cardinal direction tactical step\n // Map move_up/move_down to step_forward/step_back to match AnimationState\n const stepDirection = STEP_DIRECTION_MAP[action];\n onAction(stepDirection);\n\n addToQueue(\n STEP_KOREAN_TERMS[stepDirection] ?? \"Step\",\n `Shift+${e.key}`\n );\n\n if (playSFX) playSFX(\"footstep\");\n }\n } else {\n // Regular movement\n onAction(action);\n addToQueue(action, e.key);\n }\n break;\n\n case \"stance_side_switch\":\n // H key: Switch front foot (stance side switch)\n onAction(\"stance_side_switch\");\n addToQueue(\"발 바꿈 (Switch Stance Side)\", e.key);\n if (playSFX) playSFX(\"stance_change\");\n break;\n\n case \"vital_points_overlay\":\n // Vital points overlay toggle\n onAction(\"vital_points_overlay\");\n addToQueue(\"Vital Points\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n\n case \"pause\":\n // Pause menu toggle\n onAction(\"pause\");\n addToQueue(\"Pause\", e.key);\n if (playSFX) playSFX(\"menu_select\");\n break;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Remove key from pressed keys set\n pressedKeysRef.current.delete(e.key.toLowerCase());\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n // Clear pressed keys on cleanup using copied ref value\n pressedKeys.clear();\n };\n }, [\n enabled,\n currentStance,\n onStanceChange,\n onAction,\n addToQueue,\n toggleHints,\n getDiagonalStepAction,\n showHints,\n playSFX,\n currentAnimationState,\n ]);\n\n return {\n queuedInputs,\n showHints,\n toggleHints,\n };\n}\n"],"mappings":";;;;;;;;;;;;AAcA,IAAM,wBAAgD;CACpD,mBAAmB;CACnB,oBAAoB;CACpB,gBAAgB;CAChB,iBAAiB;AACnB;AAEA,IAAM,oBAA4C;CAChD,cAAc;CACd,WAAW;CACX,WAAW;CACX,YAAY;AACd;AAIA,IAAM,yBAAiD;CACrD,wBAAwB,GAAG,sBAAsB,SAAS,OAAO;CACjE,yBAAyB,GAAG,sBAAsB,SAAS,OAAO;CAClE,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,sBAAsB,GAAG,sBAAsB,MAAM,OAAO;CAC5D,wBAAwB,GAAG,sBAAsB,MAAM,OAAO;CAC9D,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,qBAAqB,GAAG,sBAAsB,MAAM,OAAO;CAC3D,sBAAsB,GAAG,sBAAsB,MAAM,OAAO;CAC5D,kBAAkB,GAAG,sBAAsB,QAAQ,OAAO;AAC5D;AAGA,IAAM,qBAA6C;CACjD,SAAS;CACT,WAAW;CACX,WAAW;CACX,YAAY;AACd;;;;AA8CA,IAAM,iBAAiB;;;;AAKvB,IAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B7B,SAAgB,oBAAoB,EAClC,gBACA,UACA,UAAU,MACV,gBAAgB,GAChB,eACA,SACA,yBACsD;CACtD,MAAM,CAAC,cAAc,mBAAmB,SAAiC,CAAC,CAAC;CAC3E,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,mBAAmB,OAAsB,IAAI,cAAc,CAAC;CAClE,MAAM,sBAAsB,OAAe,CAAC;CAG5C,MAAM,iBAAiB,uBAAoB,IAAI,IAAI,CAAC;;;;CAKpD,MAAM,cAAc,kBAAkB;EACpC,cAAc,SAAS;GACrB,MAAM,WAAW,CAAC;GAClB,IAAI,eACF,cAAc;GAGhB,IAAI,SACF,QAAQ,aAAa;GAEvB,OAAO;EACT,CAAC;CACH,GAAG,CAAC,eAAe,OAAO,CAAC;;;;;;;CAQ3B,MAAM,wBAAwB,kBAAiC;EAC7D,MAAM,OAAO,eAAe;EAC5B,MAAM,SAAS,iBAAiB;EAGhC,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,GAAG,YAAY,CAAC,GAAG;GAC5D,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,GAC3D,OAAO;GAET,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,GAC5D,OAAO;EAEX;EAGA,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,GAAG;GAC9D,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,GAC3D,OAAO;GAET,IAAI,KAAK,IAAI,OAAO,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,GAC5D,OAAO;EAEX;EAEA,OAAO;CACT,GAAG,CAAC,CAAC;;;;CAKL,MAAM,aAAa,aAAa,QAAgB,QAAgB;EAC9D,MAAM,WAAwB;GAC5B;GACA;GACA,WAAW,KAAK,IAAI;EACtB;EAEA,iBAAiB,SAAS;GAExB,OADgB,CAAC,UAAU,GAAG,IAAI,EAAE,MAAM,GAAG,cACtC;EACT,CAAC;CACH,GAAG,CAAC,CAAC;;;;;CAML,gBAAgB;EACd,MAAM,WAAW,kBAAkB;GACjC,MAAM,MAAM,KAAK,IAAI;GACrB,iBAAiB,SAAS;IACxB,IAAI,KAAK,WAAW,GAAG,OAAO;IAC9B,OAAO,KAAK,QACT,UAAU,MAAM,MAAM,YAAY,oBACrC;GACF,CAAC;EACH,GAAG,GAAG;EAEN,aAAa,cAAc,QAAQ;CACrC,GAAG,CAAC,CAAC;;;;CAKL,gBAAgB;EACd,IAAI,CAAC,SAAS;EAGd,MAAM,cAAc,eAAe;EAEnC,MAAM,iBAAiB,MAAqB;GAC1C,MAAM,SAAS,iBAAiB;GAGhC,eAAe,QAAQ,IAAI,EAAE,IAAI,YAAY,CAAC;GAG9C,IAAI,EAAE,QAAQ,MAAM;IAClB,EAAE,eAAe;IACjB,YAAY;IACZ;GACF;GAGA,IAAI,EAAE,QAAQ,YAAY,WAAW;IACnC,EAAE,eAAe;IACjB,aAAa,KAAK;IAClB;GACF;GAMA,IAFmB,uBAAuB,WAAW,SAAS,GAE9C;IAEd,IAAI,EAAE,QAAQ,KAAK;KACjB,EAAE,eAAe;KACjB,SAAS,gBAAgB;KACzB,WAAW,uBAAuB,OAAO;KACzC,IAAI,SAAS,QAAQ,eAAe;KACpC;IACF;IAGA,IAAI,EAAE,IAAI,YAAY,MAAM,OAAO,EAAE,QAAQ,SAAS;KACpD,EAAE,eAAe;KACjB,SAAS,eAAe;KACxB,WAAW,wBAAwB,EAAE,GAAG;KACxC,IAAI,SAAS,QAAQ,eAAe;KACpC;IACF;IAGA,IAAI,EAAE,UAAU;KACd,EAAE,eAAe;KACjB,SAAS,oBAAoB;KAC7B,WAAW,0BAA0B,OAAO;KAC5C,IAAI,SAAS,QAAQ,eAAe;KACpC;IACF;IAGA;GACF;GAGA,MAAM,cAAc,OAAO,gBAAgB,EAAE,GAAG;GAChD,IAAI,gBAAgB,MAAM;IACxB,EAAE,eAAe;IAGjB,IAAI,gBAAgB,eAAe;KAEjC,WAAW,UAAU,cAAc,EAAE,oBAAoB,EAAE,GAAG;KAG9D,IAAI,SACF,QAAQ,YAAY;KAEtB;IACF;IAGA,MAAM,MAAM,KAAK,IAAI;IACrB,IAAI,MAAM,oBAAoB,UAAU,KACtC;IAEF,oBAAoB,UAAU;IAG9B,eAAe,WAAW;IAC1B,WAAW,UAAU,cAAc,KAAK,EAAE,GAAG;IAG7C,IAAI,SACF,QAAQ,eAAe;IAEzB;GACF;GAGA,MAAM,SAAS,OAAO,gBAAgB,EAAE,GAAG;GAC3C,IAAI,QAAQ;IACV,EAAE,eAAe;IAEjB,QAAQ,QAAR;KACE,KAAK;MACH,SAAS,QAAQ;MACjB,WAAW,UAAU,EAAE,GAAG;MAC1B,IAAI,SAAS,QAAQ,cAAc;MACnC;KAEF,KAAK;MACH,SAAS,OAAO;MAChB,WAAW,SAAS,EAAE,GAAG;MACzB,IAAI,SAAS,QAAQ,OAAO;MAC5B;KAEF,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;MAEH,IAAI,EAAE,QAAQ;OAEZ,IAAI,gBAA+B;OAEnC,IAAI,WAAW,aAEb,gBAAgB;YACX,IAAI,WAAW,cAEpB,gBAAgB;OAGlB,IAAI,eAAe;QACjB,SAAS,aAAa;QACtB,WACE,uBAAuB,kBAAkB,kBACzC,OAAO,EAAE,KACX;QACA,IAAI,SAAS,QAAQ,UAAU;OACjC;MACF,OAAO,IAAI,EAAE,YAAY,EAAE,SAAS;OAElC,IAAI,mBAAkC;OAEtC,IAAI,WAAW,aAEb,mBAAmB;YACd,IAAI,WAAW,cAEpB,mBAAmB;YACd,IAAI,WAAW,aAAa,WAAW,aAE5C,mBAAmB;OAGrB,IAAI,kBAAkB;QACpB,SAAS,gBAAgB;QACzB,WACE,uBAAuB,qBACrB,qBACF,cAAc,EAAE,KAClB;QACA,IAAI,SAAS,QAAQ,UAAU;OACjC;MACF,OAAO,IAAI,EAAE,SAAS;OAEpB,IAAI,iBAAgC;OAEpC,IAAI,WAAW,aAEb,iBAAiB;YACZ,IAAI,WAAW,cAEpB,iBAAiB;YACZ,IAAI,WAAW,WAEpB,iBAAiB;YACZ,IAAI,WAAW,aAEpB,iBAAiB;OAGnB,IAAI,gBAAgB;QAClB,SAAS,cAAc;QACvB,WACE,uBAAuB,mBAAmB,YAC1C,QAAQ,EAAE,KACZ;QACA,IAAI,SAAS,QAAQ,UAAU;OACjC;MACF,OAAO,IAAI,EAAE,UAAU;OAGrB,MAAM,eAAe,sBAAsB;OAE3C,IAAI,cAAc;QAEhB,SAAS,YAAY;QAErB,WACE,sBAAsB,iBAAiB,iBACvC,SAAS,EAAE,KACb;QAEA,IAAI,SAAS,QAAQ,UAAU;OACjC,OAAO;QAGL,MAAM,gBAAgB,mBAAmB;QACzC,SAAS,aAAa;QAEtB,WACE,kBAAkB,kBAAkB,QACpC,SAAS,EAAE,KACb;QAEA,IAAI,SAAS,QAAQ,UAAU;OACjC;MACF,OAAO;OAEL,SAAS,MAAM;OACf,WAAW,QAAQ,EAAE,GAAG;MAC1B;MACA;KAEF,KAAK;MAEH,SAAS,oBAAoB;MAC7B,WAAW,6BAA6B,EAAE,GAAG;MAC7C,IAAI,SAAS,QAAQ,eAAe;MACpC;KAEF,KAAK;MAEH,SAAS,sBAAsB;MAC/B,WAAW,gBAAgB,EAAE,GAAG;MAChC,IAAI,SAAS,QAAQ,aAAa;MAClC;KAEF,KAAK;MAEH,SAAS,OAAO;MAChB,WAAW,SAAS,EAAE,GAAG;MACzB,IAAI,SAAS,QAAQ,aAAa;MAClC;IACJ;GACF;EACF;EAEA,MAAM,eAAe,MAAqB;GAExC,eAAe,QAAQ,OAAO,EAAE,IAAI,YAAY,CAAC;EACnD;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAChD,OAAO,iBAAiB,SAAS,WAAW;EAE5C,aAAa;GACX,OAAO,oBAAoB,WAAW,aAAa;GACnD,OAAO,oBAAoB,SAAS,WAAW;GAE/C,YAAY,MAAM;EACpB;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OAAO;EACL;EACA;EACA;CACF;AACF"}