blacktrigram 0.7.97 → 0.7.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioManager.js +2 -4
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +8 -9
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js +2 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js +3 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js +16 -15
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js +0 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js +7 -6
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +3 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js +7 -6
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js +34 -30
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js +2 -2
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js +2 -2
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js +2 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +2 -4
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +10 -9
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js +14 -13
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js +8 -7
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js +15 -13
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js +3 -2
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +1 -2
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +3 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js +59 -53
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js +19 -18
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js +22 -18
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js +4 -5
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js +29 -24
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js +3 -2
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js +2 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js +3 -2
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js +6 -5
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js +3 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js +2 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js +25 -24
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js +1 -3
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js +2 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js +69 -68
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js +0 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +12 -3
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/systems/CombatSystem.js +6 -2
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js +0 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js +10 -21
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js +1 -3
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js +3 -4
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js +2 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js +3 -9
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js +8 -5
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/utils/accessibility.js +0 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/effectUtils.js +1 -3
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/inputSystem.js +0 -2
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js +6 -3
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/playerUtils.js +4 -2
- package/lib/utils/playerUtils.js.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim =\n getAnimation(\"stance_change\") ?? getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSkeletalAnimation.js","names":[],"sources":["../../src/hooks/useSkeletalAnimation.ts"],"sourcesContent":["/**\n * useSkeletalAnimation - Shared hook for skeletal animation management\n *\n * Centralizes skeletal animation state and frame updates for player characters.\n * Reduces code duplication across SkeletalPlayer3D, Player3DWithTransitions,\n * and screen components.\n *\n * PHASE 2: Now uses cached interpolation and batch bone updates for 60fps performance\n *\n * @module hooks/useSkeletalAnimation\n * @category Hooks\n * @korean 골격애니메이션훅\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n batchUpdateBones,\n getAnimation,\n getAnimationByName,\n getAttackAnimation,\n getDefensiveAnimation,\n getFootworkAnimation,\n getStepAnimation,\n interpolateKeyframeCached,\n performanceMonitor,\n} from \"../systems/animation\";\nimport { applyLaterality } from \"../systems/animation/core/LateralityTransform\";\nimport type { TrigramStance } from \"../types/common\";\nimport type { PlayerAnimation } from \"../types/player-visual\";\nimport type {\n SkeletalAnimation,\n SkeletalAnimationState,\n SkeletalRig,\n} from \"../types/skeletal\";\n\n/**\n * Options for useSkeletalAnimation hook\n * @korean 골격애니메이션훅옵션\n */\nexport interface UseSkeletalAnimationOptions {\n /** Current animation name */\n readonly currentAnimation: PlayerAnimation;\n /** Specific attack animation name (for attack state) */\n readonly attackAnimation?: string;\n /** Whether player is blocking */\n readonly isBlocking?: boolean;\n /** Current player stance for trigram-specific idle animations */\n readonly stance?: TrigramStance;\n /**\n * Stance laterality (left or right foot forward)\n *\n * - \"left\": Left foot forward (왼발서기 - Oenbal Seogi)\n * - \"right\": Right foot forward (오른발서기 - Oreun Bal Seogi)\n *\n * This affects animation mirroring - techniques will be mirrored\n * appropriately based on the laterality, creating 16 distinct stance\n * configurations (8 trigrams × 2 laterality).\n *\n * **Korean**: 측면성 (Cheugmyeonseong - Laterality/Sidedness)\n */\n readonly laterality?: \"left\" | \"right\";\n /** Callback when animation completes */\n readonly onAnimationComplete?: () => void;\n}\n\n/**\n * Return type for useSkeletalAnimation hook\n * @korean 골격애니메이션훅반환타입\n */\nexport interface UseSkeletalAnimationReturn {\n /** Current animation state */\n readonly animState: SkeletalAnimationState;\n /** Animation time reference (seconds) */\n readonly animTimeRef: React.MutableRefObject<number>;\n /** Update animation and apply to rig (call in useFrame) */\n readonly updateRigAnimation: (rig: SkeletalRig, delta: number) => void;\n /** Diagonal rotation override for step animations */\n readonly diagonalRotationY: number | null;\n}\n\n/**\n * Set of diagonal step animations for O(1) lookup\n * @korean 대각선스텝애니메이션집합\n */\nconst DIAGONAL_STEP_ANIMATIONS = new Set([\n \"step_forward_left\",\n \"step_forward_right\",\n \"step_back_left\",\n \"step_back_right\",\n]);\n\n/**\n * useSkeletalAnimation hook\n *\n * Manages skeletal animation state and frame updates for player characters.\n * Handles animation selection based on player state (idle, walk, attack, etc.)\n * and applies keyframes to the skeletal rig.\n *\n * @param options - Animation options\n * @returns Animation state and update function\n *\n * @example\n * ```tsx\n * const { animState, animTimeRef, updateRigAnimation, diagonalRotationY } =\n * useSkeletalAnimation({\n * currentAnimation: \"walk\",\n * isBlocking: false,\n * onAnimationComplete: () => console.log(\"Animation completed\"),\n * });\n *\n * // In useFrame callback\n * useFrame((_, delta) => {\n * updateRigAnimation(rig, delta);\n * });\n * ```\n *\n * @korean 골격애니메이션훅\n */\nexport function useSkeletalAnimation(\n options: UseSkeletalAnimationOptions,\n): UseSkeletalAnimationReturn {\n const {\n currentAnimation,\n attackAnimation,\n isBlocking = false,\n stance,\n laterality = \"right\",\n onAnimationComplete,\n } = options;\n\n // Animation state\n const [animState, setAnimState] = useState<SkeletalAnimationState>({\n currentAnimation: null,\n currentTime: 0,\n isPlaying: false,\n playbackSpeed: 1.0,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n\n // Animation time ref (updated at 60fps without triggering re-renders)\n const animTimeRef = useRef(0);\n\n // Diagonal step rotation override (Y-axis rotation in radians)\n const [diagonalRotationY, setDiagonalRotationY] = useState<number | null>(\n null,\n );\n\n // Load animation when currentAnimation, blocking state, or laterality changes\n useEffect(() => {\n // Reset animation time whenever animation changes\n animTimeRef.current = 0;\n\n let selectedAnim: SkeletalAnimation | null = null;\n let playbackSpeed: number;\n let shouldClearDiagonalRotation = true;\n\n if (currentAnimation === \"attack\" && attackAnimation) {\n // Attack animation - first check stance-specific attacks, then generic\n selectedAnim =\n getAttackAnimation(attackAnimation) ??\n getAnimation(attackAnimation) ??\n null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"defend\" || isBlocking) {\n // Block/defend animation - check stance-specific defensive animations first\n // If attackAnimation contains a defensive animation name, use it\n if (attackAnimation) {\n selectedAnim = getDefensiveAnimation(attackAnimation) ?? null;\n }\n // Fall back to generic block animation\n selectedAnim ??= getAnimation(\"block\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"idle\") {\n // Idle animation - use trigram-specific stance idle if stance is provided\n // Otherwise fall back to generic idle breathing animation\n if (stance) {\n const stanceIdleAnim = `stance_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceIdleAnim) ?? null;\n }\n // Fall back to generic idle if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"idle\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n } else if (currentAnimation === \"walk\") {\n // Walking animation - use trigram-specific walk if stance is provided\n if (stance) {\n const stanceWalkAnim = `walk_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceWalkAnim) ?? null;\n }\n // Fall back to generic walk if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"walk\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"run\") {\n // Running animation - use trigram-specific run if stance is provided\n if (stance) {\n const stanceRunAnim = `run_${stance}` as PlayerAnimation;\n selectedAnim = getAnimationByName(stanceRunAnim) ?? null;\n }\n // Fall back to generic run if no stance or stance animation not found\n selectedAnim ??= getAnimation(\"run\") ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"fall_\")) {\n // Fall animations - directional falls from BasicAnimations\n selectedAnim = getAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation === \"stance_change\") {\n // Stance change animation\n selectedAnim =\n getAnimation(\"stance_change\") ?? getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 1.2; // Slightly faster for responsiveness\n } else if (currentAnimation === \"hit\") {\n // Hit reaction - stop animation\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n return;\n } else if (currentAnimation?.startsWith(\"step_\")) {\n // Tactical step animation\n selectedAnim = getStepAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n\n // Handle diagonal step rotation\n if (DIAGONAL_STEP_ANIMATIONS.has(currentAnimation)) {\n shouldClearDiagonalRotation = false;\n // Diagonal rotation will be handled by parent component\n // This hook only manages the flag\n }\n } else if (currentAnimation?.startsWith(\"footwork_\")) {\n // Footwork pattern animation\n selectedAnim = getFootworkAnimation(currentAnimation) ?? null;\n playbackSpeed = 1.0;\n } else if (currentAnimation?.startsWith(\"stance_\")) {\n // Stance-specific idle animation with proper biomechanics\n // Use getAnimationByName which searches ALL_ANIMATIONS (includes STANCE_ANIMATIONS)\n selectedAnim = getAnimationByName(currentAnimation) ?? null;\n playbackSpeed = 0.5; // Slow breathing animation for stance idle\n } else {\n // Idle animation (fallback)\n selectedAnim = getAnimation(\"idle_stance\") ?? null;\n playbackSpeed = 0.5; // Slow breathing animation\n }\n\n // Apply laterality transformation if selectedAnim exists\n // laterality directly affects animation mirroring:\n // \"left\" = left foot forward (왼발서기) → animations mirrored\n // \"right\" = right foot forward (오른발서기) → base animations (default)\n if (selectedAnim) {\n selectedAnim = applyLaterality(selectedAnim, laterality);\n }\n\n // Clear diagonal rotation for non-diagonal animations\n if (shouldClearDiagonalRotation) {\n setDiagonalRotationY(null);\n }\n\n setAnimState({\n currentAnimation: selectedAnim,\n currentTime: 0,\n isPlaying: true,\n playbackSpeed,\n previousKeyframeIndex: 0,\n nextKeyframeIndex: 1,\n });\n }, [currentAnimation, attackAnimation, isBlocking, stance, laterality]);\n\n // Update animation and apply to rig (called at 60fps in useFrame)\n // PHASE 2: Now uses cached interpolation and batch bone updates\n const updateRigAnimation = useCallback(\n (targetRig: SkeletalRig, delta: number) => {\n if (animState.isPlaying && animState.currentAnimation) {\n const frameStartTime = performance.now();\n\n // Advance animation time\n let newTime = animTimeRef.current + delta * animState.playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animState.currentAnimation.duration) {\n if (animState.currentAnimation.loop) {\n newTime = newTime % animState.currentAnimation.duration;\n } else {\n newTime = animState.currentAnimation.duration;\n completed = true;\n }\n }\n\n // Use cached interpolation for 90%+ cache hit rate\n // Use animation.name as the unique identifier\n const keyframe = interpolateKeyframeCached(\n animState.currentAnimation.name,\n animState.currentAnimation,\n newTime,\n );\n\n if (keyframe) {\n // Batch update bones (60% faster than individual updates)\n batchUpdateBones(targetRig, keyframe);\n }\n\n // Update time ref\n animTimeRef.current = newTime;\n\n // Record performance metrics\n const frameTime = performance.now() - frameStartTime;\n performanceMonitor.recordFrame(frameTime);\n\n // Handle animation completion\n if (completed) {\n animTimeRef.current = 0;\n setAnimState((prev) => ({\n ...prev,\n isPlaying: false,\n currentTime: 0,\n }));\n\n // Trigger callback\n if (onAnimationComplete) {\n onAnimationComplete();\n }\n }\n }\n },\n [animState, onAnimationComplete],\n );\n\n return {\n animState,\n animTimeRef,\n updateRigAnimation,\n diagonalRotationY,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,IAAM,2CAA2B,IAAI,IAAI;CACvC;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BD,SAAgB,qBACd,SAC4B;CAC5B,MAAM,EACJ,kBACA,iBACA,aAAa,OACb,QACA,aAAa,SACb,wBACE;CAGJ,MAAM,CAAC,WAAW,gBAAgB,SAAiC;EACjE,kBAAkB;EAClB,aAAa;EACb,WAAW;EACX,eAAe;EACf,uBAAuB;EACvB,mBAAmB;CACrB,CAAC;CAGD,MAAM,cAAc,OAAO,CAAC;CAG5B,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,IACF;CAGA,gBAAgB;EAEd,YAAY,UAAU;EAEtB,IAAI,eAAyC;EAC7C,IAAI;EACJ,IAAI,8BAA8B;EAElC,IAAI,qBAAqB,YAAY,iBAAiB;GAEpD,eACE,mBAAmB,eAAe,KAClC,aAAa,eAAe,KAC5B;GACF,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,YAAY,YAAY;GAGtD,IAAI,iBACF,eAAe,sBAAsB,eAAe,KAAK;GAG3D,iBAAiB,aAAa,OAAO,KAAK;GAC1C,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,QAAQ;GAGtC,IAAI,QAAQ;IACV,MAAM,iBAAiB,UAAU;IACjC,eAAe,mBAAmB,cAAc,KAAK;GACvD;GAEA,iBAAiB,aAAa,MAAM,KAAK;GACzC,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,QAAQ;GAEtC,IAAI,QAAQ;IACV,MAAM,iBAAiB,QAAQ;IAC/B,eAAe,mBAAmB,cAAc,KAAK;GACvD;GAEA,iBAAiB,aAAa,MAAM,KAAK;GACzC,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,OAAO;GAErC,IAAI,QAAQ;IACV,MAAM,gBAAgB,OAAO;IAC7B,eAAe,mBAAmB,aAAa,KAAK;GACtD;GAEA,iBAAiB,aAAa,KAAK,KAAK;GACxC,gBAAgB;EAClB,OAAO,IAAI,kBAAkB,WAAW,OAAO,GAAG;GAEhD,eAAe,aAAa,gBAAgB,KAAK;GACjD,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,iBAAiB;GAE/C,eACE,aAAa,eAAe,KAAK,aAAa,aAAa,KAAK;GAClE,gBAAgB;EAClB,OAAO,IAAI,qBAAqB,OAAO;GAErC,cAAc,UAAU;IACtB,GAAG;IACH,WAAW;IACX,aAAa;GACf,EAAE;GACF;EACF,OAAO,IAAI,kBAAkB,WAAW,OAAO,GAAG;GAEhD,eAAe,iBAAiB,gBAAgB,KAAK;GACrD,gBAAgB;GAGhB,IAAI,yBAAyB,IAAI,gBAAgB,GAC/C,8BAA8B;EAIlC,OAAO,IAAI,kBAAkB,WAAW,WAAW,GAAG;GAEpD,eAAe,qBAAqB,gBAAgB,KAAK;GACzD,gBAAgB;EAClB,OAAO,IAAI,kBAAkB,WAAW,SAAS,GAAG;GAGlD,eAAe,mBAAmB,gBAAgB,KAAK;GACvD,gBAAgB;EAClB,OAAO;GAEL,eAAe,aAAa,aAAa,KAAK;GAC9C,gBAAgB;EAClB;EAMA,IAAI,cACF,eAAe,gBAAgB,cAAc,UAAU;EAIzD,IAAI,6BACF,qBAAqB,IAAI;EAG3B,aAAa;GACX,kBAAkB;GAClB,aAAa;GACb,WAAW;GACX;GACA,uBAAuB;GACvB,mBAAmB;EACrB,CAAC;CACH,GAAG;EAAC;EAAkB;EAAiB;EAAY;EAAQ;CAAU,CAAC;CA8DtE,OAAO;EACL;EACA;EACA,oBA7DyB,aACxB,WAAwB,UAAkB;GACzC,IAAI,UAAU,aAAa,UAAU,kBAAkB;IACrD,MAAM,iBAAiB,YAAY,IAAI;IAGvC,IAAI,UAAU,YAAY,UAAU,QAAQ,UAAU;IACtD,IAAI,YAAY;IAGhB,IAAI,WAAW,UAAU,iBAAiB,UACxC,IAAI,UAAU,iBAAiB,MAC7B,UAAU,UAAU,UAAU,iBAAiB;SAC1C;KACL,UAAU,UAAU,iBAAiB;KACrC,YAAY;IACd;IAKF,MAAM,WAAW,0BACf,UAAU,iBAAiB,MAC3B,UAAU,kBACV,OACF;IAEA,IAAI,UAEF,iBAAiB,WAAW,QAAQ;IAItC,YAAY,UAAU;IAGtB,MAAM,YAAY,YAAY,IAAI,IAAI;IACtC,mBAAmB,YAAY,SAAS;IAGxC,IAAI,WAAW;KACb,YAAY,UAAU;KACtB,cAAc,UAAU;MACtB,GAAG;MACH,WAAW;MACX,aAAa;KACf,EAAE;KAGF,IAAI,qBACF,oBAAoB;IAExB;GACF;EACF,GACA,CAAC,WAAW,mBAAmB,CAM/B;EACA;CACF;AACF"}
|
|
@@ -236,7 +236,8 @@ var CombatSystem = class CombatSystem {
|
|
|
236
236
|
defender.position.y,
|
|
237
237
|
0
|
|
238
238
|
]);
|
|
239
|
-
const
|
|
239
|
+
const defenderPhysical = getArchetypePhysicalAttributes(defender.archetype);
|
|
240
|
+
const defenderBodyRadius = calculateBodyRadius(defenderPhysical);
|
|
240
241
|
if (Math.max(0, centerToCenterDistance - defenderBodyRadius) > reachResult.effectiveReach) return {
|
|
241
242
|
hit: false,
|
|
242
243
|
damage: 0,
|
|
@@ -336,7 +337,10 @@ var CombatSystem = class CombatSystem {
|
|
|
336
337
|
animationType = techniqueData.animation.type;
|
|
337
338
|
speedModifier = techniqueData.animation.speedModifier;
|
|
338
339
|
} else {
|
|
339
|
-
|
|
340
|
+
const techniqueName = technique.name?.english || technique.englishName || "";
|
|
341
|
+
const techniqueId = technique.id || "";
|
|
342
|
+
const damageType = technique.damageType || "";
|
|
343
|
+
animationType = determineAnimationTypeForTechnique(techniqueName, techniqueId, damageType);
|
|
340
344
|
speedModifier = calculateSpeedModifierForDamage(technique.damage || 15);
|
|
341
345
|
}
|
|
342
346
|
const animationName = getAnimationNameForType(animationType);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatSystem.js","names":[],"sources":["../../src/systems/CombatSystem.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { getTechniqueById } from \"../data/techniques\";\nimport { BodyRegion, CombatAttackType, CombatState, DamageType, GrappleTarget } from \"../types\";\nimport { VitalPointCategory, VitalPointSeverity } from \"../types/common\";\nimport { BASE_STAMINA_REGEN_RATE } from \"../types/physicsConstants\";\nimport { Technique } from \"../types/technique\";\nimport { calculateDistance3D } from \"../utils/math\";\nimport { calculateBodyRadius } from \"../utils/skeletonScaling\";\nimport type { DefensiveAnimationType } from \"./animation\";\nimport {\n AnimationType,\n calculateSpeedModifierForDamage,\n determineAnimationTypeForTechnique,\n getAdjustedAnimationDuration,\n getAnimationNameForType,\n isWithinHitWindow,\n} from \"./animation\";\nimport { applyDamageToBodyParts } from \"./bodypart/BodyPartDamageIntegration\";\nimport { playerInjuryManager } from \"./bodypart\";\nimport {\n applyBreathingDisruptionFromVitalPoint,\n applyBreathingDisruptionFromTorsoDamage,\n BreathingDisruptionSystem,\n causesBreathingDisruption,\n updateBreathingDisruption,\n} from \"./breathing\";\nimport BalanceSystem from \"./combat/BalanceSystem\";\nimport ConsciousnessSystem from \"./combat/ConsciousnessSystem\";\nimport {\n extractVitalPointCategory,\n isHeadTraumaHit,\n} from \"./combat/painConsciousnessUtils\";\nimport PainResponseSystem, {\n ShockPainEffect,\n} from \"./combat/PainResponseSystem\";\nimport { CombatResult, CombatSystemInterface } from \"./combat/types\";\nimport {\n CollisionDetection,\n KnockbackPhysics,\n physicalReachCalculator,\n type KnockbackConfig,\n} from \"./physics\";\nimport { PlayerState } from \"./player\";\nimport {\n addEffectsToPlayer,\n getEffectModifiers,\n removeExpiredEffects,\n} from \"./PlayerEffectManager\";\nimport { TRIGRAM_TECHNIQUES } from \"./trigram\";\nimport { TrigramSystem } from \"./TrigramSystem\";\nimport { StatusEffect } from \"./types\";\nimport { KoreanTechnique, VitalPointHitResult } from \"./vitalpoint/types\";\nimport { VitalPointSystem } from \"./VitalPointSystem\";\nimport { GrappleSystem } from \"./combat/GrappleSystem\";\n\n/**\n * Enhanced Combat System with Pain Response and Consciousness integration.\n *\n * Integrates realistic pain accumulation and consciousness tracking for\n * progressive combat impairment.\n */\nexport class CombatSystem implements CombatSystemInterface {\n private vitalPointSystem: VitalPointSystem;\n protected trigramSystem: TrigramSystem;\n private painSystem: PainResponseSystem;\n private consciousnessSystem: ConsciousnessSystem;\n private balanceSystem: BalanceSystem;\n private knockbackPhysics: KnockbackPhysics;\n private collisionDetection: CollisionDetection;\n private grappleSystem: GrappleSystem;\n\n // Track shock pain effects per player\n private shockPainEffects: Map<string, ShockPainEffect>;\n // Track last head trauma time per player for consciousness recovery\n private lastHeadTraumaTime: Map<string, number>;\n\n // Vital point severity thresholds\n private readonly SEVERITY_MAJOR_THRESHOLD = 30;\n private readonly SEVERITY_MODERATE_THRESHOLD = 20;\n\n constructor() {\n this.vitalPointSystem = new VitalPointSystem();\n this.trigramSystem = new TrigramSystem();\n this.painSystem = new PainResponseSystem();\n this.consciousnessSystem = new ConsciousnessSystem();\n this.balanceSystem = new BalanceSystem();\n this.knockbackPhysics = new KnockbackPhysics();\n this.collisionDetection = new CollisionDetection();\n this.grappleSystem = new GrappleSystem();\n this.shockPainEffects = new Map();\n this.lastHeadTraumaTime = new Map();\n }\n\n /**\n * Cleanup per-player combat state.\n *\n * Call this when a player permanently leaves the match or when\n * match-level cleanup is performed to avoid unbounded Map growth.\n *\n * @param playerId - ID of the player to cleanup\n *\n * @korean 플레이어데이터정리\n */\n public cleanupPlayerData(playerId: string): void {\n this.shockPainEffects.delete(playerId);\n this.lastHeadTraumaTime.delete(playerId);\n }\n\n /**\n * Dispose of all combat system resources.\n *\n * **Korean**: 전투 시스템 자원 정리\n *\n * Cleans up Three.js resources (geometries, raycaster) used by the collision\n * detection system to prevent memory leaks. Should be called when the\n * CombatSystem is destroyed or reinitialized.\n *\n * @korean 전투시스템자원정리\n */\n public dispose(): void {\n // Dispose collision detection resources (cached geometries, raycaster)\n this.collisionDetection.dispose();\n }\n\n /**\n * Get the balance system instance for fall checking.\n *\n * @returns BalanceSystem instance\n * @korean 균형시스템가져오기\n */\n public getBalanceSystem(): BalanceSystem {\n return this.balanceSystem;\n }\n\n /**\n * Get the consciousness system instance for fall checking.\n *\n * @returns ConsciousnessSystem instance\n * @korean 의식시스템가져오기\n */\n public getConsciousnessSystem(): ConsciousnessSystem {\n return this.consciousnessSystem;\n }\n\n /**\n * Get the collision detection system instance.\n *\n * @returns CollisionDetection instance\n * @korean 충돌감지시스템가져오기\n */\n public getCollisionDetection(): CollisionDetection {\n return this.collisionDetection;\n }\n\n /**\n * Calculate knockback physics for combat hit.\n *\n * **Korean**: 밀침 계산 (Calculate Knockback)\n *\n * Determines knockback displacement, duration, and fall state based on:\n * - Attack damage amount\n * - Defender's balance state\n * - Defender's stance resistance\n * - Attack direction vector\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param damage - Total damage dealt\n * @returns Knockback information or undefined if no knockback\n *\n * @example\n * ```typescript\n * const knockback = this.calculateKnockback(attacker, defender, 80);\n * // Returns: { displacement: {x:2.5,y:0,z:0}, duration:0.8, recoveryWindow:0.7, shouldFall:false }\n * ```\n *\n * @private\n * @korean 밀침계산\n */\n private calculateKnockback(\n attacker: PlayerState,\n defender: PlayerState,\n damage: number,\n ): CombatResult[\"knockback\"] {\n const attackDirection = new THREE.Vector3(\n defender.position.x - attacker.position.x,\n 0, // Keep knockback on horizontal plane\n defender.position.y - attacker.position.y,\n );\n\n // If attacker and defender are at the exact same position, skip knockback to avoid NaN direction\n if (attackDirection.lengthSq() === 0) {\n return undefined;\n }\n\n attackDirection.normalize();\n\n const config: KnockbackConfig = {\n force: damage * 10, // Convert damage to force (arbitrary scaling)\n direction: attackDirection,\n duration: 0, // Will be calculated by physics engine\n balanceState: {\n current: defender.balance,\n max: 100, // Assuming max balance is always 100\n },\n currentStance: defender.currentStance,\n };\n\n const result = this.knockbackPhysics.calculateKnockback(config, damage);\n\n return {\n displacement: {\n x: result.displacement.x,\n y: result.displacement.y,\n z: result.displacement.z,\n },\n duration: result.duration,\n recoveryWindow: result.recoveryWindow,\n shouldFall: result.shouldFall,\n };\n }\n\n /**\n * Fix: Update resolveAttack to match interface signature and add animation-aware hit detection\n *\n * **Korean**: 공격 해결 (애니메이션 인식)\n *\n * Integrates animation timing and physical reach calculation for reality-based hit detection.\n * Hits only register when:\n * 1. Animation is in hit window (extension phase)\n * 2. Attacker is within effective reach based on limb length and animation\n * 3. Existing accuracy and stance checks pass\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param technique - Technique being executed\n * @param targetedVitalPointId - Optional specific vital point target\n * @param animationContext - Optional animation timing context for reality-based hit detection\n * @returns Combat result with hit/miss and damage information\n */\n resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: {\n animationType: AnimationType;\n currentTime: number;\n },\n ): CombatResult {\n const timestamp = Date.now();\n\n if (!this.canExecuteTechnique(attacker, technique)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n if (attacker.combatState === CombatState.GRAPPLED) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n if (animationContext) {\n const { animationType, currentTime } = animationContext;\n\n if (!isWithinHitWindow(animationType, currentTime)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n const attackerPhysical = getArchetypePhysicalAttributes(\n attacker.archetype,\n );\n const reachResult = physicalReachCalculator.calculateReach(\n attackerPhysical,\n animationType,\n currentTime,\n attacker.currentStance,\n technique.reachConfig, // Pass reachConfig for hybrid reach calculation\n );\n\n // Physics-first: Position type is 2D in METERS\n // No conversion needed - positions are already in meters\n const centerToCenterDistance = calculateDistance3D(\n [attacker.position.x, attacker.position.y, 0],\n [defender.position.x, defender.position.y, 0],\n );\n\n // Note: PhysicalReachCalculator already includes attacker body pivot/offset in reach calculation\n // (shoulder offset for punches, hip rotation for kicks), so we only subtract defender radius\n // to avoid double-counting the attacker's body dimension.\n // 타격 거리 계산: 수비자 몸체 반경만 제외 (공격자 몸체 오프셋은 이미 도달 거리에 포함됨)\n const defenderPhysical = getArchetypePhysicalAttributes(\n defender.archetype,\n );\n const defenderBodyRadius = calculateBodyRadius(defenderPhysical);\n\n // Reach is calculated from attacker center to striking limb surface (includes body pivot),\n // so we measure from attacker center to defender surface (subtracting defender radius only).\n // 유효 거리 = 중심간 거리 - 수비자 몸체 반경\n const distance = Math.max(0, centerToCenterDistance - defenderBodyRadius);\n\n if (distance > reachResult.effectiveReach) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n }\n\n // Fix: Use correct method signature\n const stanceEffectiveness = this.trigramSystem.calculateStanceEffectiveness(\n attacker.currentStance,\n defender.currentStance,\n );\n\n // Calculate base hit chance\n const baseAccuracy = technique.accuracy * stanceEffectiveness;\n const hitRoll = Math.random();\n const hit = hitRoll <= baseAccuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Special handling for grapple techniques\n if (technique.type === CombatAttackType.GRAPPLE) {\n const grappleResult = this.handleGrappleTechnique(\n attacker,\n defender,\n technique,\n timestamp\n );\n\n // Grapples don't deal immediate damage, they establish control\n // Small damage on successful grapple to represent the initial impact\n const grappleDamage = grappleResult.grappleSuccess\n ? technique.damage * 0.3\n : 0;\n\n return {\n hit: grappleResult.grappleSuccess,\n damage: grappleDamage,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker: grappleResult.updatedAttacker,\n defender: grappleResult.updatedDefender,\n success: grappleResult.grappleSuccess,\n isCritical: false,\n isBlocked: false,\n animation: this.getAnimationInfoForTechnique(technique),\n };\n }\n\n // Process vital point hit if targeted (with archetype parameters)\n let vitalPointResult: VitalPointHitResult | null = null;\n if (targetedVitalPointId) {\n vitalPointResult = this.processVitalPointHit(\n targetedVitalPointId,\n technique.damage ?? 15,\n attacker,\n defender,\n );\n }\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointResult ?? {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n },\n );\n\n const critRoll = Math.random();\n const isCritical = critRoll <= (technique.critChance ?? 0.1);\n\n const animationInfo = this.getAnimationInfoForTechnique(technique);\n\n // Calculate knockback physics (밀침 물리)\n const knockbackInfo = this.calculateKnockback(\n attacker,\n defender,\n damageResult.totalDamage,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: isCritical,\n vitalPointHit: vitalPointResult?.hit ?? false,\n effects: damageResult.effectsApplied,\n timestamp,\n technique,\n attacker,\n defender,\n success: true,\n isCritical: vitalPointResult?.hit ?? false,\n isBlocked: false,\n targetedVitalPointId, // Pass through the targeted vital point ID\n animation: animationInfo, // Add animation information\n knockback: knockbackInfo, // Add knockback information\n };\n }\n\n /**\n * Get animation information for a technique\n *\n * Determines the skeletal animation to play, duration, and speed modifier\n * based on technique configuration or automatic determination.\n *\n * @param technique - Korean technique to execute\n * @returns Animation information or undefined\n *\n * @private\n * @korean 기술애니메이션정보가져오기\n */\n private getAnimationInfoForTechnique(\n technique: KoreanTechnique,\n ): CombatResult[\"animation\"] {\n // Check if technique has explicit animation config (from Technique interface)\n // KoreanTechnique may not have animation field, so check the technique data\n const techniqueData = this.getTechniqueData(technique);\n\n let animationType;\n let speedModifier;\n\n if (techniqueData?.animation) {\n animationType = techniqueData.animation.type;\n speedModifier = techniqueData.animation.speedModifier;\n } else {\n const techniqueName =\n technique.name?.english || technique.englishName || \"\";\n const techniqueId = technique.id || \"\";\n const damageType = technique.damageType || \"\";\n\n animationType = determineAnimationTypeForTechnique(\n techniqueName,\n techniqueId,\n damageType,\n );\n\n speedModifier = calculateSpeedModifierForDamage(technique.damage || 15);\n }\n\n const animationName = getAnimationNameForType(animationType);\n const duration = getAdjustedAnimationDuration(animationName, speedModifier);\n const techniqueDisplayName =\n technique.name?.korean || technique.koreanName || technique.id;\n\n return {\n animationName,\n duration,\n speedModifier,\n techniqueDisplayName,\n };\n }\n\n /**\n * Get Technique data if this KoreanTechnique has an associated Technique definition\n *\n * @param technique - Korean technique\n * @returns Technique data or null\n *\n * @private\n * @korean 기술데이터가져오기\n */\n private getTechniqueData(technique: KoreanTechnique): Technique | null {\n return getTechniqueById(technique.id) ?? null;\n }\n\n /**\n * Handle grapple technique execution.\n *\n * **Korean**: 잡기 기술 처리 (Handle Grapple Technique)\n *\n * Initiates or transitions grapple control based on technique.\n *\n * @param attacker - Player executing grapple\n * @param defender - Target player\n * @param technique - Grapple technique being used\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states with grapple control\n */\n handleGrappleTechnique(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n currentTime: number\n ): {\n updatedAttacker: PlayerState;\n updatedDefender: PlayerState;\n grappleSuccess: boolean;\n } {\n // Determine grapple target from technique\n const target = this.getGrappleTargetFromTechnique(technique);\n\n // Attempt grapple\n const result = this.grappleSystem.attemptGrapple(\n attacker,\n defender,\n target,\n currentTime\n );\n\n if (result.success && result.grappleControl) {\n // Grapple succeeded - update both players\n return {\n updatedAttacker: {\n ...attacker,\n combatState: CombatState.GRAPPLING,\n grappleControl: result.grappleControl,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: {\n ...defender,\n combatState: CombatState.GRAPPLED,\n grappleControl: result.grappleControl,\n },\n grappleSuccess: true,\n };\n }\n\n // Grapple failed\n return {\n updatedAttacker: {\n ...attacker,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: defender,\n grappleSuccess: false,\n };\n }\n\n /**\n * Determine grapple target from technique characteristics.\n *\n * **Korean**: 기술에서 잡기 목표 결정 (Determine Grapple Target from Technique)\n *\n * @private\n */\n private getGrappleTargetFromTechnique(\n technique: KoreanTechnique\n ): GrappleTarget {\n // Check technique name/ID for hints (both English and Korean)\n const techName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techNameKorean = (\n technique.name?.korean ||\n technique.koreanName ||\n \"\"\n );\n const techId = technique.id.toLowerCase();\n\n // Check for wrist/hand - 손목 (sonmok)\n if (\n techName.includes(\"wrist\") ||\n techId.includes(\"wrist\") ||\n techNameKorean.includes(\"손목\")\n ) {\n return GrappleTarget.HAND;\n }\n\n // Check for arm - 팔 (pal)\n if (\n techName.includes(\"arm\") ||\n techId.includes(\"arm\") ||\n techNameKorean.includes(\"팔\")\n ) {\n return GrappleTarget.ARM;\n }\n\n // Check for leg - 다리 (dari)\n if (\n techName.includes(\"leg\") ||\n techId.includes(\"leg\") ||\n techNameKorean.includes(\"다리\")\n ) {\n return GrappleTarget.LEG;\n }\n\n // Check for neck - 목 (mok)\n if (\n techName.includes(\"neck\") ||\n techId.includes(\"neck\") ||\n techNameKorean.includes(\"목\")\n ) {\n return GrappleTarget.NECK;\n }\n\n // Check for both arms - 양팔 (yangpal)\n if (\n techName.includes(\"both\") ||\n techName.includes(\"double\") ||\n techId.includes(\"both\") ||\n techNameKorean.includes(\"양팔\") ||\n techNameKorean.includes(\"쌍\")\n ) {\n return GrappleTarget.BOTH_ARMS;\n }\n\n // Check for torso/body/hip - 몸통 (momtong), 허리 (heori), 몸 (mom)\n if (\n techName.includes(\"torso\") ||\n techName.includes(\"body\") ||\n techName.includes(\"hip\") ||\n techId.includes(\"torso\") ||\n techNameKorean.includes(\"몸통\") ||\n techNameKorean.includes(\"허리\") ||\n techNameKorean.includes(\"몸\")\n ) {\n return GrappleTarget.TORSO;\n }\n\n // Default to arm for unspecified grapples\n return GrappleTarget.ARM;\n }\n\n /**\n * Update grapple state for players over time.\n *\n * **Korean**: 잡기 상태 업데이트 (Update Grapple State)\n *\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed in seconds\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states\n */\n updateGrappleState(\n controller: PlayerState,\n target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): {\n updatedController: PlayerState;\n updatedTarget: PlayerState;\n } {\n if (!controller.grappleControl) {\n // No active grapple\n return {\n updatedController: controller,\n updatedTarget: target,\n };\n }\n\n // Validate grapple control consistency\n if (\n controller.grappleControl.controllerId !== controller.id ||\n controller.grappleControl.targetId !== target.id\n ) {\n // Inconsistent state - break grapple\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Update grapple control\n const updatedControl = this.grappleSystem.updateGrapple(\n controller.grappleControl,\n controller,\n target,\n deltaTime,\n currentTime\n );\n\n if (!updatedControl) {\n // Grapple broken - reset both players\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Deduct stamina from controller\n const staminaCost = updatedControl.staminaCostPerSecond * deltaTime;\n\n return {\n updatedController: {\n ...controller,\n grappleControl: updatedControl,\n stamina: Math.max(0, controller.stamina - staminaCost),\n },\n updatedTarget: {\n ...target,\n grappleControl: updatedControl,\n },\n };\n }\n\n /**\n * Fix: Make applyCombatResult non-static instance method with effect application\n * Enhanced with Pain Response and Consciousness System integration\n */\n applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Start with base result\n const { updatedAttacker, updatedDefender: initialDefender } =\n CombatSystem.applyCombatResult(result, attacker, defender);\n let updatedDefender = initialDefender;\n\n if (result.hit && result.damage > 0) {\n // Determine vital point category and severity from hit result\n const category = this.getVitalPointCategory(result);\n const severity = this.getVitalPointSeverity(result);\n\n // Apply pain from damage\n const { player: defenderWithPain, shockEffect: newShockEffect } =\n this.painSystem.applyPain(\n updatedDefender,\n result.damage,\n severity,\n category,\n );\n updatedDefender = defenderWithPain;\n\n // Store shock pain effect if triggered\n if (newShockEffect) {\n this.shockPainEffects.set(updatedDefender.id, newShockEffect);\n }\n\n // Check for pain overload stun\n if (this.painSystem.shouldTriggerStun(updatedDefender)) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n };\n }\n\n // Apply consciousness damage for head/neurological hits\n if (this.isHeadTrauma(result, category)) {\n updatedDefender = this.consciousnessSystem.applyDamage(\n updatedDefender,\n result.damage,\n category,\n );\n\n // Track head trauma time for recovery gating\n this.lastHeadTraumaTime.set(updatedDefender.id, Date.now());\n\n // Check incapacitation threshold\n if (\n this.consciousnessSystem.isAtIncapacitationThreshold(updatedDefender)\n ) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n // Could add helpless duration tracking here if needed\n };\n }\n }\n\n // Apply pain and consciousness effects to stats\n const currentShockEffect = this.shockPainEffects.get(updatedDefender.id);\n updatedDefender = this.painSystem.applyEffects(\n updatedDefender,\n currentShockEffect,\n );\n updatedDefender = this.consciousnessSystem.applyEffects(updatedDefender);\n\n // Apply balance disruption from the hit\n // Determine body region from vital point or use default\n const bodyRegion = this.getBodyRegionFromResult(result);\n updatedDefender = this.balanceSystem.disruptBalance(\n updatedDefender,\n result.damage,\n bodyRegion,\n );\n\n // Apply breathing disruption for torso strikes\n if (result.vitalPointHit && result.targetedVitalPointId) {\n // Vital point strike to torso\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n if (vitalPoint && causesBreathingDisruption(vitalPoint.id)) {\n updatedDefender = applyBreathingDisruptionFromVitalPoint(\n updatedDefender,\n vitalPoint,\n Date.now(),\n );\n }\n } else if (bodyRegion === BodyRegion.TORSO && result.damage >= 10) {\n // General torso damage (non-vital point hits)\n // Apply breathing disruption if damage is significant enough\n // Note: Solar plexus detection is best-effort; vital point system is primary mechanism\n const techniqueId = (result.technique?.id ?? \"\").toLowerCase();\n const isSolarPlexusArea = \n techniqueId.includes(\"solar\") ||\n techniqueId.includes(\"myeongchi\");\n updatedDefender = applyBreathingDisruptionFromTorsoDamage(\n updatedDefender,\n result.damage,\n isSolarPlexusArea,\n Date.now(),\n );\n }\n }\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determines if a hit caused head trauma (affects consciousness).\n *\n * @param result - Combat result\n * @param category - Vital point category\n * @returns True if hit should affect consciousness\n */\n private isHeadTrauma(\n result: CombatResult,\n category?: VitalPointCategory,\n ): boolean {\n return isHeadTraumaHit(result, category);\n }\n\n /**\n * Extracts vital point category from combat result.\n *\n * @param result - Combat result\n * @returns Vital point category if available\n */\n private getVitalPointCategory(\n result: CombatResult,\n ): VitalPointCategory | undefined {\n return extractVitalPointCategory(result);\n }\n\n /**\n * Extracts vital point severity from combat result.\n *\n * @param result - Combat result\n * @returns Vital point severity if critical hit\n */\n private getVitalPointSeverity(\n result: CombatResult,\n ): VitalPointSeverity | undefined {\n if (result.vitalPointHit) {\n if (result.isCritical) {\n return VitalPointSeverity.CRITICAL;\n }\n if (result.damage > this.SEVERITY_MAJOR_THRESHOLD) {\n return VitalPointSeverity.MAJOR;\n }\n if (result.damage > this.SEVERITY_MODERATE_THRESHOLD) {\n return VitalPointSeverity.MODERATE;\n }\n return VitalPointSeverity.MINOR;\n }\n return undefined;\n }\n\n /**\n * Determines body region from combat result for balance disruption.\n *\n * Maps vital points to body regions for balance system integration.\n * Uses string matching on vital point IDs as a pragmatic heuristic since\n * VitalPoint interface doesn't currently include a bodyRegion property.\n *\n * Future improvement: Add bodyRegion: BodyRegion to VitalPoint interface\n * for more robust region mapping without string pattern matching.\n *\n * @param result - Combat result\n * @returns Body region that was struck\n * @private\n * @korean 신체부위결정\n */\n private getBodyRegionFromResult(result: CombatResult): BodyRegion {\n // If we have a targeted vital point, try to determine region\n if (result.targetedVitalPointId) {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n\n if (vitalPoint) {\n const pointId = vitalPoint.id.toLowerCase();\n\n // Check for leg/lower body targets\n if (\n pointId.includes(\"leg\") ||\n pointId.includes(\"knee\") ||\n pointId.includes(\"ankle\") ||\n pointId.includes(\"thigh\")\n ) {\n return BodyRegion.LEFT_LEG; // Generic leg for balance disruption\n }\n\n // Check for head targets\n if (\n pointId.includes(\"head\") ||\n pointId.includes(\"temple\") ||\n pointId.includes(\"jaw\") ||\n pointId.includes(\"nose\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Check for arm targets\n if (\n pointId.includes(\"arm\") ||\n pointId.includes(\"elbow\") ||\n pointId.includes(\"wrist\") ||\n pointId.includes(\"shoulder\")\n ) {\n return BodyRegion.LEFT_ARM;\n }\n }\n }\n\n // Default to torso for general strikes\n return BodyRegion.TORSO;\n }\n\n /**\n * Updates player states for recovery (pain dissipation, consciousness recovery, balance recovery).\n * Call this regularly in game loop.\n *\n * @param player - Player to update\n * @param deltaTime - Time elapsed since last update (ms)\n * @returns Updated player state\n */\n applyRecovery(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = player;\n\n // Apply pain recovery\n updatedPlayer = this.painSystem.applyDissipation(updatedPlayer, deltaTime);\n\n // Apply consciousness recovery (only if enough time since head trauma)\n const lastTrauma = this.lastHeadTraumaTime.get(player.id);\n updatedPlayer = this.consciousnessSystem.applyRecovery(\n updatedPlayer,\n deltaTime,\n lastTrauma,\n );\n\n // Apply balance recovery\n updatedPlayer = this.balanceSystem.applyRecovery(updatedPlayer, deltaTime);\n\n // Clean up expired shock pain effects\n const shockEffect = this.shockPainEffects.get(player.id);\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed >= shockEffect.duration) {\n this.shockPainEffects.delete(player.id);\n }\n }\n\n return updatedPlayer;\n }\n\n /**\n * Static version for backwards compatibility with comprehensive effect application\n * Enhanced with Pain Response and Consciousness System integration\n * Updated to apply damage to body parts for 8-body-part health visualization\n */\n static applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n let updatedDefender = defender;\n let updatedAttacker = attacker;\n\n if (result.hit) {\n // Determine body region from technique or use random distribution\n const bodyRegion = CombatSystem.getBodyRegionFromTechnique(\n result.technique,\n );\n\n // Apply damage to body parts (this also updates aggregate health)\n updatedDefender = applyDamageToBodyParts(\n defender,\n result.damage,\n bodyRegion,\n );\n\n // Record injury for trauma visualization (외상 시각화)\n // Use per-player injury tracking to prevent mixing injuries between characters\n // Automatically tracks bruising, cuts, and blood effects based on damage type\n // Records injury even without specific technique (defaults to BRUISE)\n if (result.damage > 0) {\n const defenderInjuryIntegration = playerInjuryManager.getIntegrationForPlayer(defender.id);\n defenderInjuryIntegration.recordCombatDamage({\n damage: result.damage,\n bodyRegion,\n damageType: (result.technique?.damageType as DamageType) || undefined,\n });\n }\n\n // Update tracking stats (applyDamageToBodyParts already sets health)\n updatedDefender = {\n ...updatedDefender,\n totalDamageReceived:\n updatedDefender.totalDamageReceived + result.damage,\n hitsTaken: updatedDefender.hitsTaken + 1,\n };\n\n if (result.effects && result.effects.length > 0) {\n updatedDefender = addEffectsToPlayer(updatedDefender, result.effects);\n }\n\n if (result.vitalPointHit) {\n updatedAttacker = {\n ...updatedAttacker,\n vitalPointHits: attacker.vitalPointHits + 1,\n };\n }\n\n if (result.isCritical) {\n updatedAttacker = {\n ...updatedAttacker,\n perfectStrikes: attacker.perfectStrikes + 1,\n };\n }\n }\n\n updatedAttacker = {\n ...updatedAttacker,\n ki: Math.max(0, attacker.ki - 5),\n stamina: Math.max(0, attacker.stamina - 10),\n totalDamageDealt:\n attacker.totalDamageDealt + (result.hit ? result.damage : 0),\n hitsLanded: attacker.hitsLanded + (result.hit ? 1 : 0),\n };\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determine body region from technique name or type\n *\n * **Korean**: 기술에서 신체 영역 결정\n *\n * @param technique - The technique used in the attack\n * @returns Body region to apply damage to\n */\n private static getBodyRegionFromTechnique(\n technique?: KoreanTechnique,\n ): BodyRegion {\n if (!technique) {\n return BodyRegion.TORSO;\n }\n\n const techniqueName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techniqueId = technique.id?.toLowerCase() || \"\";\n\n // Head/face targeting techniques\n if (\n techniqueName.includes(\"head\") ||\n techniqueName.includes(\"temple\") ||\n techniqueName.includes(\"jaw\") ||\n techniqueName.includes(\"face\") ||\n techniqueId.includes(\"head\") ||\n techniqueId.includes(\"skull\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Neck targeting techniques\n if (\n techniqueName.includes(\"neck\") ||\n techniqueName.includes(\"throat\") ||\n techniqueName.includes(\"choke\") ||\n techniqueId.includes(\"neck\")\n ) {\n return BodyRegion.NECK;\n }\n\n // Leg targeting techniques\n if (\n techniqueName.includes(\"kick\") ||\n techniqueName.includes(\"sweep\") ||\n techniqueName.includes(\"leg\") ||\n techniqueName.includes(\"knee\") ||\n techniqueId.includes(\"kick\") ||\n techniqueId.includes(\"leg\")\n ) {\n return Math.random() < 0.5 ? BodyRegion.LEFT_LEG : BodyRegion.RIGHT_LEG;\n }\n\n // Arm targeting techniques\n if (\n techniqueName.includes(\"arm\") ||\n techniqueName.includes(\"shoulder\") ||\n techniqueName.includes(\"elbow\") ||\n techniqueId.includes(\"arm\")\n ) {\n return Math.random() < 0.5 ? BodyRegion.LEFT_ARM : BodyRegion.RIGHT_ARM;\n }\n\n // Punch/strike techniques typically target torso\n if (\n techniqueName.includes(\"punch\") ||\n techniqueName.includes(\"strike\") ||\n techniqueName.includes(\"jab\") ||\n techniqueName.includes(\"cross\") ||\n techniqueName.includes(\"hook\") ||\n techniqueName.includes(\"uppercut\")\n ) {\n // Mix of torso and head for punches\n return Math.random() < 0.7 ? BodyRegion.TORSO : BodyRegion.HEAD;\n }\n\n // Body/core targeting techniques\n if (\n techniqueName.includes(\"body\") ||\n techniqueName.includes(\"solar\") ||\n techniqueName.includes(\"liver\") ||\n techniqueName.includes(\"ribs\") ||\n techniqueName.includes(\"abdomen\") ||\n techniqueId.includes(\"torso\") ||\n techniqueId.includes(\"core\")\n ) {\n return BodyRegion.TORSO;\n }\n\n // Default to torso for unmatched techniques\n return BodyRegion.TORSO;\n }\n\n /**\n * Fix: Add missing getAvailableTechniques method required by interface\n */\n getAvailableTechniques(player: PlayerState): readonly KoreanTechnique[] {\n const allTechniques = TRIGRAM_TECHNIQUES[player.currentStance] ?? [];\n\n // Filter techniques based on available resources using canExecuteTechnique\n return allTechniques.filter((technique) =>\n this.canExecuteTechnique(player, technique as KoreanTechnique),\n ) as readonly KoreanTechnique[];\n }\n\n /**\n * Check if attacker can execute technique\n */\n private canExecuteTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n ): boolean {\n return (\n player.ki >= technique.kiCost &&\n player.stamina >= technique.staminaCost &&\n player.currentStance === technique.stance &&\n !player.isStunned\n );\n }\n\n /**\n * Static methods for backwards compatibility\n */\n static resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const instance = new CombatSystem();\n return instance.executeAttack(attacker, defender, technique);\n }\n\n /**\n * Check if a player is defeated\n */\n isPlayerDefeated(player: PlayerState): boolean {\n return player.health <= 0 || player.consciousness <= 0;\n }\n\n /**\n * Update player state over time with effect management\n */\n updatePlayerState(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = { ...player };\n\n updatedPlayer = removeExpiredEffects(updatedPlayer);\n\n const effectModifiers = getEffectModifiers(updatedPlayer);\n\n const regenRate = deltaTime / 1000;\n\n // Ki regeneration (slower during combat) - affected by effects\n if (updatedPlayer.ki < updatedPlayer.maxKi) {\n updatedPlayer.ki = Math.min(\n updatedPlayer.maxKi,\n updatedPlayer.ki + regenRate * 2 * effectModifiers.kiRegen,\n );\n }\n\n // Stamina regeneration - affected by effects and breathing disruption\n // Uses BASE_STAMINA_REGEN_RATE constant for better gameplay\n // Allows players to move around and attack frequently without running out\n // 체력 재생 속도 - 더 활발한 전투를 위해\n if (updatedPlayer.stamina < updatedPlayer.maxStamina) {\n const baseStaminaRegen = regenRate * BASE_STAMINA_REGEN_RATE * effectModifiers.staminaRegen;\n const modifiedStaminaRegen =\n BreathingDisruptionSystem.calculateStaminaRegen(\n updatedPlayer,\n baseStaminaRegen,\n );\n updatedPlayer.stamina = Math.min(\n updatedPlayer.maxStamina,\n updatedPlayer.stamina + modifiedStaminaRegen,\n );\n }\n\n if (\n updatedPlayer.health < updatedPlayer.maxHealth &&\n updatedPlayer.health > 0\n ) {\n updatedPlayer.health = Math.min(\n updatedPlayer.maxHealth,\n updatedPlayer.health + regenRate * 0.5,\n );\n }\n\n // Update breathing disruption effects (gradual recovery if torso health > 50%)\n updatedPlayer = updateBreathingDisruption(\n updatedPlayer,\n deltaTime,\n Date.now(),\n );\n\n const currentTime = Date.now();\n if (\n updatedPlayer.lastActionTime &&\n currentTime - updatedPlayer.lastActionTime > updatedPlayer.recoveryTime\n ) {\n updatedPlayer.isStunned = false;\n updatedPlayer.isCountering = false;\n }\n\n return updatedPlayer;\n }\n\n /**\n * Get combat statistics\n */\n getCombatStatistics(player: PlayerState): {\n healthPercent: number;\n kiPercent: number;\n staminaPercent: number;\n balancePercent: number;\n } {\n return {\n healthPercent: (player.health / player.maxHealth) * 100,\n kiPercent: (player.ki / player.maxKi) * 100,\n staminaPercent: (player.stamina / player.maxStamina) * 100,\n balancePercent: player.balance,\n };\n }\n\n /**\n * Fix: Integrate processVitalPointHit into the combat system with archetype parameters\n */\n private processVitalPointHit(\n vitalPointId: string,\n _baseDamage: number, // Unused but kept for interface compatibility\n attacker: PlayerState,\n defender: PlayerState,\n ): VitalPointHitResult {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(vitalPointId);\n\n if (!vitalPoint) {\n return {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n };\n }\n\n return this.vitalPointSystem.processHit(\n vitalPoint.position, // Use vital point position for hit calculation\n { width: 10, height: 10 }, // Standard hit box\n vitalPointId, // Targeted vital point\n undefined, // Use current hour from system\n attacker.archetype, // Attacker archetype for offensive modifiers\n defender.archetype, // Defender archetype for defensive modifiers\n );\n }\n\n /**\n * Process defensive action and determine animation type.\n *\n * Determines the appropriate defensive animation based on:\n * - Defender's balance and stamina (defensive power)\n * - Attacker's technique power\n * - Combat readiness state\n *\n * **Korean**: 방어 행동 처리\n *\n * @param defender - Defending player state\n * @param attacker - Attacking player state (unused but kept for future enhancements)\n * @param attackPower - Power of the incoming attack\n * @returns Defensive animation type to play (parry_deflect, block_success, or guard_break)\n *\n * @example\n * ```typescript\n * const animType = combatSystem.processDefensiveAction(\n * defender,\n * attacker,\n * technique.damage\n * );\n * // Returns: 'parry_deflect', 'block_success', or 'guard_break'\n * ```\n *\n * @korean 방어행동처리\n */\n public processDefensiveAction(\n defender: PlayerState,\n _attacker: PlayerState,\n attackPower: number,\n ): Exclude<DefensiveAnimationType, \"guard_recovery\"> {\n // Guard Break: Check balance threshold first (highest priority condition)\n if (defender.balance < 30) {\n return \"guard_break\";\n }\n\n // Calculate defensive power based on balance and stamina\n // Balance represents physical stability (0-100)\n // Stamina represents energy reserves (0-100)\n const balanceFactor = defender.balance / 100;\n const staminaFactor = defender.stamina / 100;\n\n // Apply defensive modifiers from effects\n const effectModifiers = getEffectModifiers(defender);\n const defenseMultiplier = effectModifiers.defense;\n\n // Defense stat provides moderate bonus (normalized to 0.5-1.5 range for typical 5-15 defense)\n const defenseBonus = Math.max(0.5, Math.min(1.5, defender.defense / 10));\n\n const defensePower =\n balanceFactor * staminaFactor * 100 * defenseMultiplier * defenseBonus;\n\n // Determine defensive outcome based on power ratio\n // Parry: Strong defense (1.8x attack power or more) - Perfect deflection\n if (defensePower >= attackPower * 1.8) {\n return \"parry_deflect\";\n }\n // Block Success: Adequate defense (1.0x to 1.8x attack power) - Absorb impact\n else if (defensePower >= attackPower) {\n return \"block_success\";\n }\n // Guard Break: Defense insufficient against powerful attack (<60% of attack power)\n else if (defensePower < attackPower * 0.6) {\n return \"guard_break\";\n }\n // Block Success: Marginal defense (60-100% of attack power) - barely hold\n else {\n return \"block_success\";\n }\n }\n\n /**\n * Execute attack with technique\n */\n protected executeAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const hitRoll = Math.random();\n const accuracy = technique.accuracy || 0.8;\n const hit = hitRoll <= accuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n const vitalPointHit: VitalPointHitResult = {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR, // Use the enum directly\n };\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointHit,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: Math.random() < (technique.critChance || 0.1),\n vitalPointHit: false,\n effects: damageResult.effectsApplied,\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: true,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n /**\n * Fix: Add missing calculateDamage method required by interface\n */\n calculateDamage(\n technique: KoreanTechnique,\n attacker: PlayerState,\n defender: PlayerState,\n hitResult: VitalPointHitResult,\n ): {\n baseDamage: number;\n modifierDamage: number;\n totalDamage: number;\n effectsApplied: readonly StatusEffect[];\n finalDefenderState?: Partial<PlayerState>;\n } {\n const baseDamage = technique.damage || 15;\n\n const attackerBonus = attacker.attackPower * 0.1;\n\n let vitalPointMultiplier = 1.0;\n if (hitResult.hit && hitResult.vitalPointHit) {\n const severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 1.1,\n [VitalPointSeverity.MODERATE]: 1.3,\n [VitalPointSeverity.MAJOR]: 1.6,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n vitalPointMultiplier = severityMultipliers[hitResult.severity] ?? 1.0;\n }\n\n const modifierDamage = attackerBonus * vitalPointMultiplier;\n\n const defenseReduction = defender.defense * 0.05;\n const totalDamage = Math.max(\n 1,\n baseDamage + modifierDamage - defenseReduction,\n );\n\n const effectsApplied = [...technique.effects, ...hitResult.effects];\n\n return {\n baseDamage,\n modifierDamage,\n totalDamage: Math.floor(totalDamage),\n effectsApplied,\n finalDefenderState: {\n health: Math.max(0, defender.health - totalDamage),\n },\n };\n }\n}\n\n/**\n * Creates a standardized CombatResult with all required fields\n * Ensures both 'critical' and 'criticalHit' are present for API compatibility\n */\nexport function createCombatResult(\n partialResult: Partial<CombatResult>,\n): CombatResult {\n // Set default values\n const result: CombatResult = {\n success: partialResult.success ?? false,\n damage: partialResult.damage ?? 0,\n isCritical: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n hit: partialResult.hit ?? partialResult.success ?? false,\n isBlocked: partialResult.isBlocked ?? false,\n vitalPointHit: partialResult.vitalPointHit ?? false,\n effects: partialResult.effects ?? [],\n attacker: partialResult.attacker,\n defender: partialResult.defender,\n technique: partialResult.technique,\n // Always set criticalHit to match critical for consistency\n criticalHit: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n timestamp: Date.now(),\n };\n\n return result;\n}\n\nexport default CombatSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatSystem.js","names":[],"sources":["../../src/systems/CombatSystem.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { getArchetypePhysicalAttributes } from \"../data/archetypePhysicalAttributes\";\nimport { getTechniqueById } from \"../data/techniques\";\nimport { BodyRegion, CombatAttackType, CombatState, DamageType, GrappleTarget } from \"../types\";\nimport { VitalPointCategory, VitalPointSeverity } from \"../types/common\";\nimport { BASE_STAMINA_REGEN_RATE } from \"../types/physicsConstants\";\nimport { Technique } from \"../types/technique\";\nimport { calculateDistance3D } from \"../utils/math\";\nimport { calculateBodyRadius } from \"../utils/skeletonScaling\";\nimport type { DefensiveAnimationType } from \"./animation\";\nimport {\n AnimationType,\n calculateSpeedModifierForDamage,\n determineAnimationTypeForTechnique,\n getAdjustedAnimationDuration,\n getAnimationNameForType,\n isWithinHitWindow,\n} from \"./animation\";\nimport { applyDamageToBodyParts } from \"./bodypart/BodyPartDamageIntegration\";\nimport { playerInjuryManager } from \"./bodypart\";\nimport {\n applyBreathingDisruptionFromVitalPoint,\n applyBreathingDisruptionFromTorsoDamage,\n BreathingDisruptionSystem,\n causesBreathingDisruption,\n updateBreathingDisruption,\n} from \"./breathing\";\nimport BalanceSystem from \"./combat/BalanceSystem\";\nimport ConsciousnessSystem from \"./combat/ConsciousnessSystem\";\nimport {\n extractVitalPointCategory,\n isHeadTraumaHit,\n} from \"./combat/painConsciousnessUtils\";\nimport PainResponseSystem, {\n ShockPainEffect,\n} from \"./combat/PainResponseSystem\";\nimport { CombatResult, CombatSystemInterface } from \"./combat/types\";\nimport {\n CollisionDetection,\n KnockbackPhysics,\n physicalReachCalculator,\n type KnockbackConfig,\n} from \"./physics\";\nimport { PlayerState } from \"./player\";\nimport {\n addEffectsToPlayer,\n getEffectModifiers,\n removeExpiredEffects,\n} from \"./PlayerEffectManager\";\nimport { TRIGRAM_TECHNIQUES } from \"./trigram\";\nimport { TrigramSystem } from \"./TrigramSystem\";\nimport { StatusEffect } from \"./types\";\nimport { KoreanTechnique, VitalPointHitResult } from \"./vitalpoint/types\";\nimport { VitalPointSystem } from \"./VitalPointSystem\";\nimport { GrappleSystem } from \"./combat/GrappleSystem\";\n\n/**\n * Enhanced Combat System with Pain Response and Consciousness integration.\n *\n * Integrates realistic pain accumulation and consciousness tracking for\n * progressive combat impairment.\n */\nexport class CombatSystem implements CombatSystemInterface {\n private vitalPointSystem: VitalPointSystem;\n protected trigramSystem: TrigramSystem;\n private painSystem: PainResponseSystem;\n private consciousnessSystem: ConsciousnessSystem;\n private balanceSystem: BalanceSystem;\n private knockbackPhysics: KnockbackPhysics;\n private collisionDetection: CollisionDetection;\n private grappleSystem: GrappleSystem;\n\n // Track shock pain effects per player\n private shockPainEffects: Map<string, ShockPainEffect>;\n // Track last head trauma time per player for consciousness recovery\n private lastHeadTraumaTime: Map<string, number>;\n\n // Vital point severity thresholds\n private readonly SEVERITY_MAJOR_THRESHOLD = 30;\n private readonly SEVERITY_MODERATE_THRESHOLD = 20;\n\n constructor() {\n this.vitalPointSystem = new VitalPointSystem();\n this.trigramSystem = new TrigramSystem();\n this.painSystem = new PainResponseSystem();\n this.consciousnessSystem = new ConsciousnessSystem();\n this.balanceSystem = new BalanceSystem();\n this.knockbackPhysics = new KnockbackPhysics();\n this.collisionDetection = new CollisionDetection();\n this.grappleSystem = new GrappleSystem();\n this.shockPainEffects = new Map();\n this.lastHeadTraumaTime = new Map();\n }\n\n /**\n * Cleanup per-player combat state.\n *\n * Call this when a player permanently leaves the match or when\n * match-level cleanup is performed to avoid unbounded Map growth.\n *\n * @param playerId - ID of the player to cleanup\n *\n * @korean 플레이어데이터정리\n */\n public cleanupPlayerData(playerId: string): void {\n this.shockPainEffects.delete(playerId);\n this.lastHeadTraumaTime.delete(playerId);\n }\n\n /**\n * Dispose of all combat system resources.\n *\n * **Korean**: 전투 시스템 자원 정리\n *\n * Cleans up Three.js resources (geometries, raycaster) used by the collision\n * detection system to prevent memory leaks. Should be called when the\n * CombatSystem is destroyed or reinitialized.\n *\n * @korean 전투시스템자원정리\n */\n public dispose(): void {\n // Dispose collision detection resources (cached geometries, raycaster)\n this.collisionDetection.dispose();\n }\n\n /**\n * Get the balance system instance for fall checking.\n *\n * @returns BalanceSystem instance\n * @korean 균형시스템가져오기\n */\n public getBalanceSystem(): BalanceSystem {\n return this.balanceSystem;\n }\n\n /**\n * Get the consciousness system instance for fall checking.\n *\n * @returns ConsciousnessSystem instance\n * @korean 의식시스템가져오기\n */\n public getConsciousnessSystem(): ConsciousnessSystem {\n return this.consciousnessSystem;\n }\n\n /**\n * Get the collision detection system instance.\n *\n * @returns CollisionDetection instance\n * @korean 충돌감지시스템가져오기\n */\n public getCollisionDetection(): CollisionDetection {\n return this.collisionDetection;\n }\n\n /**\n * Calculate knockback physics for combat hit.\n *\n * **Korean**: 밀침 계산 (Calculate Knockback)\n *\n * Determines knockback displacement, duration, and fall state based on:\n * - Attack damage amount\n * - Defender's balance state\n * - Defender's stance resistance\n * - Attack direction vector\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param damage - Total damage dealt\n * @returns Knockback information or undefined if no knockback\n *\n * @example\n * ```typescript\n * const knockback = this.calculateKnockback(attacker, defender, 80);\n * // Returns: { displacement: {x:2.5,y:0,z:0}, duration:0.8, recoveryWindow:0.7, shouldFall:false }\n * ```\n *\n * @private\n * @korean 밀침계산\n */\n private calculateKnockback(\n attacker: PlayerState,\n defender: PlayerState,\n damage: number,\n ): CombatResult[\"knockback\"] {\n const attackDirection = new THREE.Vector3(\n defender.position.x - attacker.position.x,\n 0, // Keep knockback on horizontal plane\n defender.position.y - attacker.position.y,\n );\n\n // If attacker and defender are at the exact same position, skip knockback to avoid NaN direction\n if (attackDirection.lengthSq() === 0) {\n return undefined;\n }\n\n attackDirection.normalize();\n\n const config: KnockbackConfig = {\n force: damage * 10, // Convert damage to force (arbitrary scaling)\n direction: attackDirection,\n duration: 0, // Will be calculated by physics engine\n balanceState: {\n current: defender.balance,\n max: 100, // Assuming max balance is always 100\n },\n currentStance: defender.currentStance,\n };\n\n const result = this.knockbackPhysics.calculateKnockback(config, damage);\n\n return {\n displacement: {\n x: result.displacement.x,\n y: result.displacement.y,\n z: result.displacement.z,\n },\n duration: result.duration,\n recoveryWindow: result.recoveryWindow,\n shouldFall: result.shouldFall,\n };\n }\n\n /**\n * Fix: Update resolveAttack to match interface signature and add animation-aware hit detection\n *\n * **Korean**: 공격 해결 (애니메이션 인식)\n *\n * Integrates animation timing and physical reach calculation for reality-based hit detection.\n * Hits only register when:\n * 1. Animation is in hit window (extension phase)\n * 2. Attacker is within effective reach based on limb length and animation\n * 3. Existing accuracy and stance checks pass\n *\n * @param attacker - Attacking player state\n * @param defender - Defending player state\n * @param technique - Technique being executed\n * @param targetedVitalPointId - Optional specific vital point target\n * @param animationContext - Optional animation timing context for reality-based hit detection\n * @returns Combat result with hit/miss and damage information\n */\n resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n targetedVitalPointId?: string,\n animationContext?: {\n animationType: AnimationType;\n currentTime: number;\n },\n ): CombatResult {\n const timestamp = Date.now();\n\n if (!this.canExecuteTechnique(attacker, technique)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n if (attacker.combatState === CombatState.GRAPPLED) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n if (animationContext) {\n const { animationType, currentTime } = animationContext;\n\n if (!isWithinHitWindow(animationType, currentTime)) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n const attackerPhysical = getArchetypePhysicalAttributes(\n attacker.archetype,\n );\n const reachResult = physicalReachCalculator.calculateReach(\n attackerPhysical,\n animationType,\n currentTime,\n attacker.currentStance,\n technique.reachConfig, // Pass reachConfig for hybrid reach calculation\n );\n\n // Physics-first: Position type is 2D in METERS\n // No conversion needed - positions are already in meters\n const centerToCenterDistance = calculateDistance3D(\n [attacker.position.x, attacker.position.y, 0],\n [defender.position.x, defender.position.y, 0],\n );\n\n // Note: PhysicalReachCalculator already includes attacker body pivot/offset in reach calculation\n // (shoulder offset for punches, hip rotation for kicks), so we only subtract defender radius\n // to avoid double-counting the attacker's body dimension.\n // 타격 거리 계산: 수비자 몸체 반경만 제외 (공격자 몸체 오프셋은 이미 도달 거리에 포함됨)\n const defenderPhysical = getArchetypePhysicalAttributes(\n defender.archetype,\n );\n const defenderBodyRadius = calculateBodyRadius(defenderPhysical);\n\n // Reach is calculated from attacker center to striking limb surface (includes body pivot),\n // so we measure from attacker center to defender surface (subtracting defender radius only).\n // 유효 거리 = 중심간 거리 - 수비자 몸체 반경\n const distance = Math.max(0, centerToCenterDistance - defenderBodyRadius);\n\n if (distance > reachResult.effectiveReach) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n }\n\n // Fix: Use correct method signature\n const stanceEffectiveness = this.trigramSystem.calculateStanceEffectiveness(\n attacker.currentStance,\n defender.currentStance,\n );\n\n // Calculate base hit chance\n const baseAccuracy = technique.accuracy * stanceEffectiveness;\n const hitRoll = Math.random();\n const hit = hitRoll <= baseAccuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n // Special handling for grapple techniques\n if (technique.type === CombatAttackType.GRAPPLE) {\n const grappleResult = this.handleGrappleTechnique(\n attacker,\n defender,\n technique,\n timestamp\n );\n\n // Grapples don't deal immediate damage, they establish control\n // Small damage on successful grapple to represent the initial impact\n const grappleDamage = grappleResult.grappleSuccess\n ? technique.damage * 0.3\n : 0;\n\n return {\n hit: grappleResult.grappleSuccess,\n damage: grappleDamage,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp,\n technique,\n attacker: grappleResult.updatedAttacker,\n defender: grappleResult.updatedDefender,\n success: grappleResult.grappleSuccess,\n isCritical: false,\n isBlocked: false,\n animation: this.getAnimationInfoForTechnique(technique),\n };\n }\n\n // Process vital point hit if targeted (with archetype parameters)\n let vitalPointResult: VitalPointHitResult | null = null;\n if (targetedVitalPointId) {\n vitalPointResult = this.processVitalPointHit(\n targetedVitalPointId,\n technique.damage ?? 15,\n attacker,\n defender,\n );\n }\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointResult ?? {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n },\n );\n\n const critRoll = Math.random();\n const isCritical = critRoll <= (technique.critChance ?? 0.1);\n\n const animationInfo = this.getAnimationInfoForTechnique(technique);\n\n // Calculate knockback physics (밀침 물리)\n const knockbackInfo = this.calculateKnockback(\n attacker,\n defender,\n damageResult.totalDamage,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: isCritical,\n vitalPointHit: vitalPointResult?.hit ?? false,\n effects: damageResult.effectsApplied,\n timestamp,\n technique,\n attacker,\n defender,\n success: true,\n isCritical: vitalPointResult?.hit ?? false,\n isBlocked: false,\n targetedVitalPointId, // Pass through the targeted vital point ID\n animation: animationInfo, // Add animation information\n knockback: knockbackInfo, // Add knockback information\n };\n }\n\n /**\n * Get animation information for a technique\n *\n * Determines the skeletal animation to play, duration, and speed modifier\n * based on technique configuration or automatic determination.\n *\n * @param technique - Korean technique to execute\n * @returns Animation information or undefined\n *\n * @private\n * @korean 기술애니메이션정보가져오기\n */\n private getAnimationInfoForTechnique(\n technique: KoreanTechnique,\n ): CombatResult[\"animation\"] {\n // Check if technique has explicit animation config (from Technique interface)\n // KoreanTechnique may not have animation field, so check the technique data\n const techniqueData = this.getTechniqueData(technique);\n\n let animationType;\n let speedModifier;\n\n if (techniqueData?.animation) {\n animationType = techniqueData.animation.type;\n speedModifier = techniqueData.animation.speedModifier;\n } else {\n const techniqueName =\n technique.name?.english || technique.englishName || \"\";\n const techniqueId = technique.id || \"\";\n const damageType = technique.damageType || \"\";\n\n animationType = determineAnimationTypeForTechnique(\n techniqueName,\n techniqueId,\n damageType,\n );\n\n speedModifier = calculateSpeedModifierForDamage(technique.damage || 15);\n }\n\n const animationName = getAnimationNameForType(animationType);\n const duration = getAdjustedAnimationDuration(animationName, speedModifier);\n const techniqueDisplayName =\n technique.name?.korean || technique.koreanName || technique.id;\n\n return {\n animationName,\n duration,\n speedModifier,\n techniqueDisplayName,\n };\n }\n\n /**\n * Get Technique data if this KoreanTechnique has an associated Technique definition\n *\n * @param technique - Korean technique\n * @returns Technique data or null\n *\n * @private\n * @korean 기술데이터가져오기\n */\n private getTechniqueData(technique: KoreanTechnique): Technique | null {\n return getTechniqueById(technique.id) ?? null;\n }\n\n /**\n * Handle grapple technique execution.\n *\n * **Korean**: 잡기 기술 처리 (Handle Grapple Technique)\n *\n * Initiates or transitions grapple control based on technique.\n *\n * @param attacker - Player executing grapple\n * @param defender - Target player\n * @param technique - Grapple technique being used\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states with grapple control\n */\n handleGrappleTechnique(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n currentTime: number\n ): {\n updatedAttacker: PlayerState;\n updatedDefender: PlayerState;\n grappleSuccess: boolean;\n } {\n // Determine grapple target from technique\n const target = this.getGrappleTargetFromTechnique(technique);\n\n // Attempt grapple\n const result = this.grappleSystem.attemptGrapple(\n attacker,\n defender,\n target,\n currentTime\n );\n\n if (result.success && result.grappleControl) {\n // Grapple succeeded - update both players\n return {\n updatedAttacker: {\n ...attacker,\n combatState: CombatState.GRAPPLING,\n grappleControl: result.grappleControl,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: {\n ...defender,\n combatState: CombatState.GRAPPLED,\n grappleControl: result.grappleControl,\n },\n grappleSuccess: true,\n };\n }\n\n // Grapple failed\n return {\n updatedAttacker: {\n ...attacker,\n stamina: Math.max(0, attacker.stamina - result.staminaCost),\n },\n updatedDefender: defender,\n grappleSuccess: false,\n };\n }\n\n /**\n * Determine grapple target from technique characteristics.\n *\n * **Korean**: 기술에서 잡기 목표 결정 (Determine Grapple Target from Technique)\n *\n * @private\n */\n private getGrappleTargetFromTechnique(\n technique: KoreanTechnique\n ): GrappleTarget {\n // Check technique name/ID for hints (both English and Korean)\n const techName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techNameKorean = (\n technique.name?.korean ||\n technique.koreanName ||\n \"\"\n );\n const techId = technique.id.toLowerCase();\n\n // Check for wrist/hand - 손목 (sonmok)\n if (\n techName.includes(\"wrist\") ||\n techId.includes(\"wrist\") ||\n techNameKorean.includes(\"손목\")\n ) {\n return GrappleTarget.HAND;\n }\n\n // Check for arm - 팔 (pal)\n if (\n techName.includes(\"arm\") ||\n techId.includes(\"arm\") ||\n techNameKorean.includes(\"팔\")\n ) {\n return GrappleTarget.ARM;\n }\n\n // Check for leg - 다리 (dari)\n if (\n techName.includes(\"leg\") ||\n techId.includes(\"leg\") ||\n techNameKorean.includes(\"다리\")\n ) {\n return GrappleTarget.LEG;\n }\n\n // Check for neck - 목 (mok)\n if (\n techName.includes(\"neck\") ||\n techId.includes(\"neck\") ||\n techNameKorean.includes(\"목\")\n ) {\n return GrappleTarget.NECK;\n }\n\n // Check for both arms - 양팔 (yangpal)\n if (\n techName.includes(\"both\") ||\n techName.includes(\"double\") ||\n techId.includes(\"both\") ||\n techNameKorean.includes(\"양팔\") ||\n techNameKorean.includes(\"쌍\")\n ) {\n return GrappleTarget.BOTH_ARMS;\n }\n\n // Check for torso/body/hip - 몸통 (momtong), 허리 (heori), 몸 (mom)\n if (\n techName.includes(\"torso\") ||\n techName.includes(\"body\") ||\n techName.includes(\"hip\") ||\n techId.includes(\"torso\") ||\n techNameKorean.includes(\"몸통\") ||\n techNameKorean.includes(\"허리\") ||\n techNameKorean.includes(\"몸\")\n ) {\n return GrappleTarget.TORSO;\n }\n\n // Default to arm for unspecified grapples\n return GrappleTarget.ARM;\n }\n\n /**\n * Update grapple state for players over time.\n *\n * **Korean**: 잡기 상태 업데이트 (Update Grapple State)\n *\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed in seconds\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player states\n */\n updateGrappleState(\n controller: PlayerState,\n target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): {\n updatedController: PlayerState;\n updatedTarget: PlayerState;\n } {\n if (!controller.grappleControl) {\n // No active grapple\n return {\n updatedController: controller,\n updatedTarget: target,\n };\n }\n\n // Validate grapple control consistency\n if (\n controller.grappleControl.controllerId !== controller.id ||\n controller.grappleControl.targetId !== target.id\n ) {\n // Inconsistent state - break grapple\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Update grapple control\n const updatedControl = this.grappleSystem.updateGrapple(\n controller.grappleControl,\n controller,\n target,\n deltaTime,\n currentTime\n );\n\n if (!updatedControl) {\n // Grapple broken - reset both players\n return {\n updatedController: {\n ...controller,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n updatedTarget: {\n ...target,\n combatState: CombatState.IDLE,\n grappleControl: null,\n },\n };\n }\n\n // Deduct stamina from controller\n const staminaCost = updatedControl.staminaCostPerSecond * deltaTime;\n\n return {\n updatedController: {\n ...controller,\n grappleControl: updatedControl,\n stamina: Math.max(0, controller.stamina - staminaCost),\n },\n updatedTarget: {\n ...target,\n grappleControl: updatedControl,\n },\n };\n }\n\n /**\n * Fix: Make applyCombatResult non-static instance method with effect application\n * Enhanced with Pain Response and Consciousness System integration\n */\n applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n // Start with base result\n const { updatedAttacker, updatedDefender: initialDefender } =\n CombatSystem.applyCombatResult(result, attacker, defender);\n let updatedDefender = initialDefender;\n\n if (result.hit && result.damage > 0) {\n // Determine vital point category and severity from hit result\n const category = this.getVitalPointCategory(result);\n const severity = this.getVitalPointSeverity(result);\n\n // Apply pain from damage\n const { player: defenderWithPain, shockEffect: newShockEffect } =\n this.painSystem.applyPain(\n updatedDefender,\n result.damage,\n severity,\n category,\n );\n updatedDefender = defenderWithPain;\n\n // Store shock pain effect if triggered\n if (newShockEffect) {\n this.shockPainEffects.set(updatedDefender.id, newShockEffect);\n }\n\n // Check for pain overload stun\n if (this.painSystem.shouldTriggerStun(updatedDefender)) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n };\n }\n\n // Apply consciousness damage for head/neurological hits\n if (this.isHeadTrauma(result, category)) {\n updatedDefender = this.consciousnessSystem.applyDamage(\n updatedDefender,\n result.damage,\n category,\n );\n\n // Track head trauma time for recovery gating\n this.lastHeadTraumaTime.set(updatedDefender.id, Date.now());\n\n // Check incapacitation threshold\n if (\n this.consciousnessSystem.isAtIncapacitationThreshold(updatedDefender)\n ) {\n updatedDefender = {\n ...updatedDefender,\n isStunned: true,\n // Could add helpless duration tracking here if needed\n };\n }\n }\n\n // Apply pain and consciousness effects to stats\n const currentShockEffect = this.shockPainEffects.get(updatedDefender.id);\n updatedDefender = this.painSystem.applyEffects(\n updatedDefender,\n currentShockEffect,\n );\n updatedDefender = this.consciousnessSystem.applyEffects(updatedDefender);\n\n // Apply balance disruption from the hit\n // Determine body region from vital point or use default\n const bodyRegion = this.getBodyRegionFromResult(result);\n updatedDefender = this.balanceSystem.disruptBalance(\n updatedDefender,\n result.damage,\n bodyRegion,\n );\n\n // Apply breathing disruption for torso strikes\n if (result.vitalPointHit && result.targetedVitalPointId) {\n // Vital point strike to torso\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n if (vitalPoint && causesBreathingDisruption(vitalPoint.id)) {\n updatedDefender = applyBreathingDisruptionFromVitalPoint(\n updatedDefender,\n vitalPoint,\n Date.now(),\n );\n }\n } else if (bodyRegion === BodyRegion.TORSO && result.damage >= 10) {\n // General torso damage (non-vital point hits)\n // Apply breathing disruption if damage is significant enough\n // Note: Solar plexus detection is best-effort; vital point system is primary mechanism\n const techniqueId = (result.technique?.id ?? \"\").toLowerCase();\n const isSolarPlexusArea = \n techniqueId.includes(\"solar\") ||\n techniqueId.includes(\"myeongchi\");\n updatedDefender = applyBreathingDisruptionFromTorsoDamage(\n updatedDefender,\n result.damage,\n isSolarPlexusArea,\n Date.now(),\n );\n }\n }\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determines if a hit caused head trauma (affects consciousness).\n *\n * @param result - Combat result\n * @param category - Vital point category\n * @returns True if hit should affect consciousness\n */\n private isHeadTrauma(\n result: CombatResult,\n category?: VitalPointCategory,\n ): boolean {\n return isHeadTraumaHit(result, category);\n }\n\n /**\n * Extracts vital point category from combat result.\n *\n * @param result - Combat result\n * @returns Vital point category if available\n */\n private getVitalPointCategory(\n result: CombatResult,\n ): VitalPointCategory | undefined {\n return extractVitalPointCategory(result);\n }\n\n /**\n * Extracts vital point severity from combat result.\n *\n * @param result - Combat result\n * @returns Vital point severity if critical hit\n */\n private getVitalPointSeverity(\n result: CombatResult,\n ): VitalPointSeverity | undefined {\n if (result.vitalPointHit) {\n if (result.isCritical) {\n return VitalPointSeverity.CRITICAL;\n }\n if (result.damage > this.SEVERITY_MAJOR_THRESHOLD) {\n return VitalPointSeverity.MAJOR;\n }\n if (result.damage > this.SEVERITY_MODERATE_THRESHOLD) {\n return VitalPointSeverity.MODERATE;\n }\n return VitalPointSeverity.MINOR;\n }\n return undefined;\n }\n\n /**\n * Determines body region from combat result for balance disruption.\n *\n * Maps vital points to body regions for balance system integration.\n * Uses string matching on vital point IDs as a pragmatic heuristic since\n * VitalPoint interface doesn't currently include a bodyRegion property.\n *\n * Future improvement: Add bodyRegion: BodyRegion to VitalPoint interface\n * for more robust region mapping without string pattern matching.\n *\n * @param result - Combat result\n * @returns Body region that was struck\n * @private\n * @korean 신체부위결정\n */\n private getBodyRegionFromResult(result: CombatResult): BodyRegion {\n // If we have a targeted vital point, try to determine region\n if (result.targetedVitalPointId) {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(\n result.targetedVitalPointId,\n );\n\n if (vitalPoint) {\n const pointId = vitalPoint.id.toLowerCase();\n\n // Check for leg/lower body targets\n if (\n pointId.includes(\"leg\") ||\n pointId.includes(\"knee\") ||\n pointId.includes(\"ankle\") ||\n pointId.includes(\"thigh\")\n ) {\n return BodyRegion.LEFT_LEG; // Generic leg for balance disruption\n }\n\n // Check for head targets\n if (\n pointId.includes(\"head\") ||\n pointId.includes(\"temple\") ||\n pointId.includes(\"jaw\") ||\n pointId.includes(\"nose\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Check for arm targets\n if (\n pointId.includes(\"arm\") ||\n pointId.includes(\"elbow\") ||\n pointId.includes(\"wrist\") ||\n pointId.includes(\"shoulder\")\n ) {\n return BodyRegion.LEFT_ARM;\n }\n }\n }\n\n // Default to torso for general strikes\n return BodyRegion.TORSO;\n }\n\n /**\n * Updates player states for recovery (pain dissipation, consciousness recovery, balance recovery).\n * Call this regularly in game loop.\n *\n * @param player - Player to update\n * @param deltaTime - Time elapsed since last update (ms)\n * @returns Updated player state\n */\n applyRecovery(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = player;\n\n // Apply pain recovery\n updatedPlayer = this.painSystem.applyDissipation(updatedPlayer, deltaTime);\n\n // Apply consciousness recovery (only if enough time since head trauma)\n const lastTrauma = this.lastHeadTraumaTime.get(player.id);\n updatedPlayer = this.consciousnessSystem.applyRecovery(\n updatedPlayer,\n deltaTime,\n lastTrauma,\n );\n\n // Apply balance recovery\n updatedPlayer = this.balanceSystem.applyRecovery(updatedPlayer, deltaTime);\n\n // Clean up expired shock pain effects\n const shockEffect = this.shockPainEffects.get(player.id);\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed >= shockEffect.duration) {\n this.shockPainEffects.delete(player.id);\n }\n }\n\n return updatedPlayer;\n }\n\n /**\n * Static version for backwards compatibility with comprehensive effect application\n * Enhanced with Pain Response and Consciousness System integration\n * Updated to apply damage to body parts for 8-body-part health visualization\n */\n static applyCombatResult(\n result: CombatResult,\n attacker: PlayerState,\n defender: PlayerState,\n ): { updatedAttacker: PlayerState; updatedDefender: PlayerState } {\n let updatedDefender = defender;\n let updatedAttacker = attacker;\n\n if (result.hit) {\n // Determine body region from technique or use random distribution\n const bodyRegion = CombatSystem.getBodyRegionFromTechnique(\n result.technique,\n );\n\n // Apply damage to body parts (this also updates aggregate health)\n updatedDefender = applyDamageToBodyParts(\n defender,\n result.damage,\n bodyRegion,\n );\n\n // Record injury for trauma visualization (외상 시각화)\n // Use per-player injury tracking to prevent mixing injuries between characters\n // Automatically tracks bruising, cuts, and blood effects based on damage type\n // Records injury even without specific technique (defaults to BRUISE)\n if (result.damage > 0) {\n const defenderInjuryIntegration = playerInjuryManager.getIntegrationForPlayer(defender.id);\n defenderInjuryIntegration.recordCombatDamage({\n damage: result.damage,\n bodyRegion,\n damageType: (result.technique?.damageType as DamageType) || undefined,\n });\n }\n\n // Update tracking stats (applyDamageToBodyParts already sets health)\n updatedDefender = {\n ...updatedDefender,\n totalDamageReceived:\n updatedDefender.totalDamageReceived + result.damage,\n hitsTaken: updatedDefender.hitsTaken + 1,\n };\n\n if (result.effects && result.effects.length > 0) {\n updatedDefender = addEffectsToPlayer(updatedDefender, result.effects);\n }\n\n if (result.vitalPointHit) {\n updatedAttacker = {\n ...updatedAttacker,\n vitalPointHits: attacker.vitalPointHits + 1,\n };\n }\n\n if (result.isCritical) {\n updatedAttacker = {\n ...updatedAttacker,\n perfectStrikes: attacker.perfectStrikes + 1,\n };\n }\n }\n\n updatedAttacker = {\n ...updatedAttacker,\n ki: Math.max(0, attacker.ki - 5),\n stamina: Math.max(0, attacker.stamina - 10),\n totalDamageDealt:\n attacker.totalDamageDealt + (result.hit ? result.damage : 0),\n hitsLanded: attacker.hitsLanded + (result.hit ? 1 : 0),\n };\n\n return { updatedAttacker, updatedDefender };\n }\n\n /**\n * Determine body region from technique name or type\n *\n * **Korean**: 기술에서 신체 영역 결정\n *\n * @param technique - The technique used in the attack\n * @returns Body region to apply damage to\n */\n private static getBodyRegionFromTechnique(\n technique?: KoreanTechnique,\n ): BodyRegion {\n if (!technique) {\n return BodyRegion.TORSO;\n }\n\n const techniqueName = (\n technique.name?.english ||\n technique.englishName ||\n \"\"\n ).toLowerCase();\n const techniqueId = technique.id?.toLowerCase() || \"\";\n\n // Head/face targeting techniques\n if (\n techniqueName.includes(\"head\") ||\n techniqueName.includes(\"temple\") ||\n techniqueName.includes(\"jaw\") ||\n techniqueName.includes(\"face\") ||\n techniqueId.includes(\"head\") ||\n techniqueId.includes(\"skull\")\n ) {\n return BodyRegion.HEAD;\n }\n\n // Neck targeting techniques\n if (\n techniqueName.includes(\"neck\") ||\n techniqueName.includes(\"throat\") ||\n techniqueName.includes(\"choke\") ||\n techniqueId.includes(\"neck\")\n ) {\n return BodyRegion.NECK;\n }\n\n // Leg targeting techniques\n if (\n techniqueName.includes(\"kick\") ||\n techniqueName.includes(\"sweep\") ||\n techniqueName.includes(\"leg\") ||\n techniqueName.includes(\"knee\") ||\n techniqueId.includes(\"kick\") ||\n techniqueId.includes(\"leg\")\n ) {\n return Math.random() < 0.5 ? BodyRegion.LEFT_LEG : BodyRegion.RIGHT_LEG;\n }\n\n // Arm targeting techniques\n if (\n techniqueName.includes(\"arm\") ||\n techniqueName.includes(\"shoulder\") ||\n techniqueName.includes(\"elbow\") ||\n techniqueId.includes(\"arm\")\n ) {\n return Math.random() < 0.5 ? BodyRegion.LEFT_ARM : BodyRegion.RIGHT_ARM;\n }\n\n // Punch/strike techniques typically target torso\n if (\n techniqueName.includes(\"punch\") ||\n techniqueName.includes(\"strike\") ||\n techniqueName.includes(\"jab\") ||\n techniqueName.includes(\"cross\") ||\n techniqueName.includes(\"hook\") ||\n techniqueName.includes(\"uppercut\")\n ) {\n // Mix of torso and head for punches\n return Math.random() < 0.7 ? BodyRegion.TORSO : BodyRegion.HEAD;\n }\n\n // Body/core targeting techniques\n if (\n techniqueName.includes(\"body\") ||\n techniqueName.includes(\"solar\") ||\n techniqueName.includes(\"liver\") ||\n techniqueName.includes(\"ribs\") ||\n techniqueName.includes(\"abdomen\") ||\n techniqueId.includes(\"torso\") ||\n techniqueId.includes(\"core\")\n ) {\n return BodyRegion.TORSO;\n }\n\n // Default to torso for unmatched techniques\n return BodyRegion.TORSO;\n }\n\n /**\n * Fix: Add missing getAvailableTechniques method required by interface\n */\n getAvailableTechniques(player: PlayerState): readonly KoreanTechnique[] {\n const allTechniques = TRIGRAM_TECHNIQUES[player.currentStance] ?? [];\n\n // Filter techniques based on available resources using canExecuteTechnique\n return allTechniques.filter((technique) =>\n this.canExecuteTechnique(player, technique as KoreanTechnique),\n ) as readonly KoreanTechnique[];\n }\n\n /**\n * Check if attacker can execute technique\n */\n private canExecuteTechnique(\n player: PlayerState,\n technique: KoreanTechnique,\n ): boolean {\n return (\n player.ki >= technique.kiCost &&\n player.stamina >= technique.staminaCost &&\n player.currentStance === technique.stance &&\n !player.isStunned\n );\n }\n\n /**\n * Static methods for backwards compatibility\n */\n static resolveAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const instance = new CombatSystem();\n return instance.executeAttack(attacker, defender, technique);\n }\n\n /**\n * Check if a player is defeated\n */\n isPlayerDefeated(player: PlayerState): boolean {\n return player.health <= 0 || player.consciousness <= 0;\n }\n\n /**\n * Update player state over time with effect management\n */\n updatePlayerState(player: PlayerState, deltaTime: number): PlayerState {\n let updatedPlayer = { ...player };\n\n updatedPlayer = removeExpiredEffects(updatedPlayer);\n\n const effectModifiers = getEffectModifiers(updatedPlayer);\n\n const regenRate = deltaTime / 1000;\n\n // Ki regeneration (slower during combat) - affected by effects\n if (updatedPlayer.ki < updatedPlayer.maxKi) {\n updatedPlayer.ki = Math.min(\n updatedPlayer.maxKi,\n updatedPlayer.ki + regenRate * 2 * effectModifiers.kiRegen,\n );\n }\n\n // Stamina regeneration - affected by effects and breathing disruption\n // Uses BASE_STAMINA_REGEN_RATE constant for better gameplay\n // Allows players to move around and attack frequently without running out\n // 체력 재생 속도 - 더 활발한 전투를 위해\n if (updatedPlayer.stamina < updatedPlayer.maxStamina) {\n const baseStaminaRegen = regenRate * BASE_STAMINA_REGEN_RATE * effectModifiers.staminaRegen;\n const modifiedStaminaRegen =\n BreathingDisruptionSystem.calculateStaminaRegen(\n updatedPlayer,\n baseStaminaRegen,\n );\n updatedPlayer.stamina = Math.min(\n updatedPlayer.maxStamina,\n updatedPlayer.stamina + modifiedStaminaRegen,\n );\n }\n\n if (\n updatedPlayer.health < updatedPlayer.maxHealth &&\n updatedPlayer.health > 0\n ) {\n updatedPlayer.health = Math.min(\n updatedPlayer.maxHealth,\n updatedPlayer.health + regenRate * 0.5,\n );\n }\n\n // Update breathing disruption effects (gradual recovery if torso health > 50%)\n updatedPlayer = updateBreathingDisruption(\n updatedPlayer,\n deltaTime,\n Date.now(),\n );\n\n const currentTime = Date.now();\n if (\n updatedPlayer.lastActionTime &&\n currentTime - updatedPlayer.lastActionTime > updatedPlayer.recoveryTime\n ) {\n updatedPlayer.isStunned = false;\n updatedPlayer.isCountering = false;\n }\n\n return updatedPlayer;\n }\n\n /**\n * Get combat statistics\n */\n getCombatStatistics(player: PlayerState): {\n healthPercent: number;\n kiPercent: number;\n staminaPercent: number;\n balancePercent: number;\n } {\n return {\n healthPercent: (player.health / player.maxHealth) * 100,\n kiPercent: (player.ki / player.maxKi) * 100,\n staminaPercent: (player.stamina / player.maxStamina) * 100,\n balancePercent: player.balance,\n };\n }\n\n /**\n * Fix: Integrate processVitalPointHit into the combat system with archetype parameters\n */\n private processVitalPointHit(\n vitalPointId: string,\n _baseDamage: number, // Unused but kept for interface compatibility\n attacker: PlayerState,\n defender: PlayerState,\n ): VitalPointHitResult {\n const vitalPoint = this.vitalPointSystem.getVitalPointById(vitalPointId);\n\n if (!vitalPoint) {\n return {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR,\n };\n }\n\n return this.vitalPointSystem.processHit(\n vitalPoint.position, // Use vital point position for hit calculation\n { width: 10, height: 10 }, // Standard hit box\n vitalPointId, // Targeted vital point\n undefined, // Use current hour from system\n attacker.archetype, // Attacker archetype for offensive modifiers\n defender.archetype, // Defender archetype for defensive modifiers\n );\n }\n\n /**\n * Process defensive action and determine animation type.\n *\n * Determines the appropriate defensive animation based on:\n * - Defender's balance and stamina (defensive power)\n * - Attacker's technique power\n * - Combat readiness state\n *\n * **Korean**: 방어 행동 처리\n *\n * @param defender - Defending player state\n * @param attacker - Attacking player state (unused but kept for future enhancements)\n * @param attackPower - Power of the incoming attack\n * @returns Defensive animation type to play (parry_deflect, block_success, or guard_break)\n *\n * @example\n * ```typescript\n * const animType = combatSystem.processDefensiveAction(\n * defender,\n * attacker,\n * technique.damage\n * );\n * // Returns: 'parry_deflect', 'block_success', or 'guard_break'\n * ```\n *\n * @korean 방어행동처리\n */\n public processDefensiveAction(\n defender: PlayerState,\n _attacker: PlayerState,\n attackPower: number,\n ): Exclude<DefensiveAnimationType, \"guard_recovery\"> {\n // Guard Break: Check balance threshold first (highest priority condition)\n if (defender.balance < 30) {\n return \"guard_break\";\n }\n\n // Calculate defensive power based on balance and stamina\n // Balance represents physical stability (0-100)\n // Stamina represents energy reserves (0-100)\n const balanceFactor = defender.balance / 100;\n const staminaFactor = defender.stamina / 100;\n\n // Apply defensive modifiers from effects\n const effectModifiers = getEffectModifiers(defender);\n const defenseMultiplier = effectModifiers.defense;\n\n // Defense stat provides moderate bonus (normalized to 0.5-1.5 range for typical 5-15 defense)\n const defenseBonus = Math.max(0.5, Math.min(1.5, defender.defense / 10));\n\n const defensePower =\n balanceFactor * staminaFactor * 100 * defenseMultiplier * defenseBonus;\n\n // Determine defensive outcome based on power ratio\n // Parry: Strong defense (1.8x attack power or more) - Perfect deflection\n if (defensePower >= attackPower * 1.8) {\n return \"parry_deflect\";\n }\n // Block Success: Adequate defense (1.0x to 1.8x attack power) - Absorb impact\n else if (defensePower >= attackPower) {\n return \"block_success\";\n }\n // Guard Break: Defense insufficient against powerful attack (<60% of attack power)\n else if (defensePower < attackPower * 0.6) {\n return \"guard_break\";\n }\n // Block Success: Marginal defense (60-100% of attack power) - barely hold\n else {\n return \"block_success\";\n }\n }\n\n /**\n * Execute attack with technique\n */\n protected executeAttack(\n attacker: PlayerState,\n defender: PlayerState,\n technique: KoreanTechnique,\n ): CombatResult {\n const hitRoll = Math.random();\n const accuracy = technique.accuracy || 0.8;\n const hit = hitRoll <= accuracy;\n\n if (!hit) {\n return {\n hit: false,\n damage: 0,\n criticalHit: false,\n vitalPointHit: false,\n effects: [],\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: false,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n const vitalPointHit: VitalPointHitResult = {\n hit: false,\n damage: 0,\n effects: [],\n severity: VitalPointSeverity.MINOR, // Use the enum directly\n };\n\n const damageResult = this.calculateDamage(\n technique,\n attacker,\n defender,\n vitalPointHit,\n );\n\n return {\n hit: true,\n damage: damageResult.totalDamage,\n criticalHit: Math.random() < (technique.critChance || 0.1),\n vitalPointHit: false,\n effects: damageResult.effectsApplied,\n timestamp: Date.now(),\n technique,\n attacker,\n defender,\n success: true,\n isCritical: false,\n isBlocked: false,\n };\n }\n\n /**\n * Fix: Add missing calculateDamage method required by interface\n */\n calculateDamage(\n technique: KoreanTechnique,\n attacker: PlayerState,\n defender: PlayerState,\n hitResult: VitalPointHitResult,\n ): {\n baseDamage: number;\n modifierDamage: number;\n totalDamage: number;\n effectsApplied: readonly StatusEffect[];\n finalDefenderState?: Partial<PlayerState>;\n } {\n const baseDamage = technique.damage || 15;\n\n const attackerBonus = attacker.attackPower * 0.1;\n\n let vitalPointMultiplier = 1.0;\n if (hitResult.hit && hitResult.vitalPointHit) {\n const severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 1.1,\n [VitalPointSeverity.MODERATE]: 1.3,\n [VitalPointSeverity.MAJOR]: 1.6,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n vitalPointMultiplier = severityMultipliers[hitResult.severity] ?? 1.0;\n }\n\n const modifierDamage = attackerBonus * vitalPointMultiplier;\n\n const defenseReduction = defender.defense * 0.05;\n const totalDamage = Math.max(\n 1,\n baseDamage + modifierDamage - defenseReduction,\n );\n\n const effectsApplied = [...technique.effects, ...hitResult.effects];\n\n return {\n baseDamage,\n modifierDamage,\n totalDamage: Math.floor(totalDamage),\n effectsApplied,\n finalDefenderState: {\n health: Math.max(0, defender.health - totalDamage),\n },\n };\n }\n}\n\n/**\n * Creates a standardized CombatResult with all required fields\n * Ensures both 'critical' and 'criticalHit' are present for API compatibility\n */\nexport function createCombatResult(\n partialResult: Partial<CombatResult>,\n): CombatResult {\n // Set default values\n const result: CombatResult = {\n success: partialResult.success ?? false,\n damage: partialResult.damage ?? 0,\n isCritical: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n hit: partialResult.hit ?? partialResult.success ?? false,\n isBlocked: partialResult.isBlocked ?? false,\n vitalPointHit: partialResult.vitalPointHit ?? false,\n effects: partialResult.effects ?? [],\n attacker: partialResult.attacker,\n defender: partialResult.defender,\n technique: partialResult.technique,\n // Always set criticalHit to match critical for consistency\n criticalHit: partialResult.isCritical ?? partialResult.criticalHit ?? false,\n timestamp: Date.now(),\n };\n\n return result;\n}\n\nexport default CombatSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayerEffectManager.js","names":[],"sources":["../../src/systems/PlayerEffectManager.ts"],"sourcesContent":["/**\n * Player Effect Manager for tracking and applying status effects.\n * \n * **Korean**: 플레이어 효과 관리자 (Player Effect Manager)\n * \n * This module provides utilities for managing status effects on player state,\n * including adding effects with stacking logic, removing expired effects,\n * and applying effect-based combat modifiers.\n * \n * ## Key Features\n * \n * - **Effect Tracking**: Add, remove, and update status effects\n * - **Stacking Logic**: Manage concurrent effects (max 5)\n * - **Expiration Handling**: Auto-remove expired effects\n * - **Combat Modifiers**: Calculate effect-based stat changes\n * \n * @module systems/PlayerEffectManager\n * @category Player Management\n * @korean 플레이어효과관리\n */\n\nimport { VitalPointEffectType } from \"../types/common\";\nimport { StatusEffect } from \"./types\";\nimport { PlayerState } from \"./player\";\nimport { applyEffectStacking, MAX_CONCURRENT_EFFECTS } from \"./EffectCalculator\";\n\n/**\n * Adds status effects to a player's active effects list.\n * \n * **Korean**: 플레이어에 효과 추가 (Add Effects to Player)\n * \n * Applies stacking logic and respects the max concurrent effects limit.\n * \n * @param player - Current player state\n * @param effects - New effects to add\n * @returns Updated player state with new effects\n * \n * @example\n * ```typescript\n * const updatedPlayer = addEffectsToPlayer(\n * player,\n * [paralyze, bleed, stun]\n * );\n * ```\n * \n * @korean 효과추가\n */\nexport function addEffectsToPlayer(\n player: PlayerState,\n effects: readonly StatusEffect[]\n): PlayerState {\n const currentTime = Date.now();\n\n const updatedEffects = applyEffectStacking(\n player.statusEffects,\n effects,\n currentTime\n );\n\n const activeEffectIds = updatedEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: updatedEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes expired status effects from player.\n * \n * **Korean**: 만료 효과 제거 (Remove Expired Effects)\n * \n * Filters out effects whose endTime has passed.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (default: Date.now())\n * @returns Updated player state with expired effects removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeExpiredEffects(player, Date.now());\n * ```\n * \n * @korean 만료효과제거\n */\nexport function removeExpiredEffects(\n player: PlayerState,\n currentTime: number = Date.now()\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.endTime > currentTime\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes a specific status effect by ID.\n * \n * **Korean**: 특정 효과 제거 (Remove Specific Effect)\n * \n * @param player - Current player state\n * @param effectId - ID of effect to remove\n * @returns Updated player state with effect removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeEffectById(player, \"stun_12345\");\n * ```\n * \n * @korean 효과제거\n */\nexport function removeEffectById(\n player: PlayerState,\n effectId: string\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.id !== effectId\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes all status effects of a specific type.\n * \n * **Korean**: 특정 유형 효과 모두 제거 (Remove All Effects of Type)\n * \n * @param player - Current player state\n * @param effectType - Type of effects to remove\n * @returns Updated player state with effects removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeEffectsByType(\n * player,\n * VitalPointEffectType.PARALYSIS\n * );\n * ```\n * \n * @korean 유형별효과제거\n */\nexport function removeEffectsByType(\n player: PlayerState,\n effectType: VitalPointEffectType\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.type !== effectType\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Clears all status effects from player.\n * \n * **Korean**: 모든 효과 제거 (Clear All Effects)\n * \n * Useful for healing items, rest periods, or match resets.\n * \n * @param player - Current player state\n * @returns Updated player state with no effects\n * \n * @example\n * ```typescript\n * const clearedPlayer = clearAllEffects(player);\n * ```\n * \n * @korean 전체효과제거\n */\nexport function clearAllEffects(player: PlayerState): PlayerState {\n return {\n ...player,\n statusEffects: [],\n activeEffects: [],\n };\n}\n\n/**\n * Calculates combat stat modifiers based on active status effects.\n * \n * **Korean**: 효과 기반 전투 배율 계산 (Calculate Effect-Based Combat Modifiers)\n * \n * Returns multipliers for various combat attributes affected by status effects.\n * \n * @param player - Current player state\n * @returns Object with stat multipliers (1.0 = no change)\n * \n * @example\n * ```typescript\n * const modifiers = getEffectModifiers(player);\n * const effectiveDamage = baseDamage * modifiers.attackPower;\n * const effectiveSpeed = baseSpeed * modifiers.speed;\n * ```\n * \n * @korean 효과배율계산\n */\nexport function getEffectModifiers(player: PlayerState): {\n attackPower: number;\n defense: number;\n speed: number;\n technique: number;\n staminaRegen: number;\n kiRegen: number;\n} {\n let attackPower = 1.0;\n let defense = 1.0;\n let speed = 1.0;\n let technique = 1.0;\n let staminaRegen = 1.0;\n let kiRegen = 1.0;\n\n for (const effect of player.statusEffects) {\n // UNCONSCIOUSNESS takes precedence - early return with complete incapacitation\n if (effect.type === VitalPointEffectType.UNCONSCIOUSNESS) {\n return {\n attackPower: 0,\n defense: 0,\n speed: 0,\n technique: 0,\n staminaRegen: 0,\n kiRegen: 0,\n };\n }\n\n // Apply other effect modifiers multiplicatively\n switch (effect.type) {\n case VitalPointEffectType.PARALYSIS:\n // Severe movement and action impairment\n attackPower *= 0.3;\n speed *= 0.2;\n technique *= 0.4;\n break;\n\n case VitalPointEffectType.STUN:\n // Moderate impairment\n attackPower *= 0.5;\n defense *= 0.6;\n speed *= 0.5;\n technique *= 0.6;\n break;\n\n case VitalPointEffectType.PAIN:\n // Reduces effectiveness across the board\n attackPower *= 0.7;\n defense *= 0.8;\n speed *= 0.8;\n technique *= 0.7;\n break;\n\n case VitalPointEffectType.BLOOD_FLOW_RESTRICTION:\n // Gradual weakening from reduced circulation\n attackPower *= 0.85;\n staminaRegen *= 0.5;\n break;\n\n case VitalPointEffectType.BREATHLESSNESS:\n // NOTE: Stamina regen is handled by BreathingDisruptionSystem.calculateStaminaRegen()\n // which applies severity-specific multipliers (25%-75% depending on disruption level).\n // DO NOT apply a flat multiplier here as it would compound with the breathing system.\n // Only apply secondary effects here.\n speed *= 0.7; // Reduced movement speed due to breathing difficulty\n break;\n\n case VitalPointEffectType.DISORIENTATION:\n // Accuracy and technique affected\n technique *= 0.6;\n defense *= 0.7;\n break;\n\n case VitalPointEffectType.WEAKNESS:\n // Overall power reduction\n attackPower *= 0.7;\n defense *= 0.8;\n break;\n\n case VitalPointEffectType.NERVE_DISRUPTION:\n // Ki system and coordination impaired\n kiRegen *= 0.5;\n attackPower *= 0.8;\n technique *= 0.7;\n break;\n\n case VitalPointEffectType.ORGAN_DISRUPTION:\n // Internal damage affecting all systems\n attackPower *= 0.6;\n defense *= 0.7;\n staminaRegen *= 0.4;\n kiRegen *= 0.4;\n break;\n }\n }\n\n return {\n attackPower,\n defense,\n speed,\n technique,\n staminaRegen,\n kiRegen,\n };\n}\n\n/**\n * Checks if player has a specific effect type active.\n * \n * **Korean**: 효과 활성 여부 확인 (Check Effect Active)\n * \n * @param player - Current player state\n * @param effectType - Type to check for\n * @returns True if effect type is active\n * \n * @example\n * ```typescript\n * if (hasEffect(player, VitalPointEffectType.PARALYSIS)) {\n * console.log(\"Player is paralyzed!\");\n * }\n * ```\n * \n * @korean 효과확인\n */\nexport function hasEffect(\n player: PlayerState,\n effectType: VitalPointEffectType\n): boolean {\n return player.statusEffects.some((effect) => effect.type === effectType);\n}\n\n/**\n * Gets all active effects of a specific type.\n * \n * **Korean**: 유형별 활성 효과 조회 (Get Effects by Type)\n * \n * @param player - Current player state\n * @param effectType - Type to retrieve\n * @returns Array of matching effects\n * \n * @korean 유형별효과조회\n */\nexport function getEffectsByType(\n player: PlayerState,\n effectType: VitalPointEffectType\n): StatusEffect[] {\n return player.statusEffects.filter((effect) => effect.type === effectType);\n}\n\n/**\n * Gets the total number of active status effects.\n * \n * **Korean**: 활성 효과 개수 조회 (Get Active Effect Count)\n * \n * @param player - Current player state\n * @returns Number of active effects\n * \n * @korean 효과개수조회\n */\nexport function getActiveEffectCount(player: PlayerState): number {\n return player.statusEffects.length;\n}\n\n/**\n * Checks if player can accept new status effects.\n * \n * **Korean**: 효과 추가 가능 여부 확인 (Can Add More Effects)\n * \n * @param player - Current player state\n * @returns True if under MAX_CONCURRENT_EFFECTS limit\n * \n * @korean 효과추가가능확인\n */\nexport function canAddMoreEffects(player: PlayerState): boolean {\n return player.statusEffects.length < MAX_CONCURRENT_EFFECTS;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,mBACd,QACA,SACa;CACb,MAAM,cAAc,KAAK,IAAI;CAE7B,MAAM,iBAAiB,oBACrB,OAAO,eACP,SACA,WACF;CAEA,MAAM,kBAAkB,eAAe,KAAK,WAAW,OAAO,EAAE;CAEhE,OAAO;EACL,GAAG;EACH,eAAe;EACf,eAAe;CACjB;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,qBACd,QACA,cAAsB,KAAK,IAAI,GAClB;CACb,MAAM,gBAAgB,OAAO,cAAc,QACxC,WAAW,OAAO,UAAU,WAC/B;CAEA,MAAM,kBAAkB,cAAc,KAAK,WAAW,OAAO,EAAE;CAE/D,OAAO;EACL,GAAG;EACH,eAAe;EACf,eAAe;CACjB;AACF;;;;;;;;;;;;;;;;;;;;AAmHA,SAAgB,mBAAmB,QAOjC;CACA,IAAI,cAAc;CAClB,IAAI,UAAU;CACd,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,IAAI,eAAe;CACnB,IAAI,UAAU;CAEd,KAAK,MAAM,UAAU,OAAO,eAAe;EAEzC,IAAI,OAAO,SAAS,qBAAqB,iBACvC,OAAO;GACL,aAAa;GACb,SAAS;GACT,OAAO;GACP,WAAW;GACX,cAAc;GACd,SAAS;EACX;EAIF,QAAQ,OAAO,MAAf;GACE,KAAK,qBAAqB;IAExB,eAAe;IACf,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,gBAAgB;IAChB;GAEF,KAAK,qBAAqB;IAKxB,SAAS;IACT;GAEF,KAAK,qBAAqB;IAExB,aAAa;IACb,WAAW;IACX;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX;GAEF,KAAK,qBAAqB;IAExB,WAAW;IACX,eAAe;IACf,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,WAAW;IACX;EACJ;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"PlayerEffectManager.js","names":[],"sources":["../../src/systems/PlayerEffectManager.ts"],"sourcesContent":["/**\n * Player Effect Manager for tracking and applying status effects.\n * \n * **Korean**: 플레이어 효과 관리자 (Player Effect Manager)\n * \n * This module provides utilities for managing status effects on player state,\n * including adding effects with stacking logic, removing expired effects,\n * and applying effect-based combat modifiers.\n * \n * ## Key Features\n * \n * - **Effect Tracking**: Add, remove, and update status effects\n * - **Stacking Logic**: Manage concurrent effects (max 5)\n * - **Expiration Handling**: Auto-remove expired effects\n * - **Combat Modifiers**: Calculate effect-based stat changes\n * \n * @module systems/PlayerEffectManager\n * @category Player Management\n * @korean 플레이어효과관리\n */\n\nimport { VitalPointEffectType } from \"../types/common\";\nimport { StatusEffect } from \"./types\";\nimport { PlayerState } from \"./player\";\nimport { applyEffectStacking, MAX_CONCURRENT_EFFECTS } from \"./EffectCalculator\";\n\n/**\n * Adds status effects to a player's active effects list.\n * \n * **Korean**: 플레이어에 효과 추가 (Add Effects to Player)\n * \n * Applies stacking logic and respects the max concurrent effects limit.\n * \n * @param player - Current player state\n * @param effects - New effects to add\n * @returns Updated player state with new effects\n * \n * @example\n * ```typescript\n * const updatedPlayer = addEffectsToPlayer(\n * player,\n * [paralyze, bleed, stun]\n * );\n * ```\n * \n * @korean 효과추가\n */\nexport function addEffectsToPlayer(\n player: PlayerState,\n effects: readonly StatusEffect[]\n): PlayerState {\n const currentTime = Date.now();\n\n const updatedEffects = applyEffectStacking(\n player.statusEffects,\n effects,\n currentTime\n );\n\n const activeEffectIds = updatedEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: updatedEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes expired status effects from player.\n * \n * **Korean**: 만료 효과 제거 (Remove Expired Effects)\n * \n * Filters out effects whose endTime has passed.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (default: Date.now())\n * @returns Updated player state with expired effects removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeExpiredEffects(player, Date.now());\n * ```\n * \n * @korean 만료효과제거\n */\nexport function removeExpiredEffects(\n player: PlayerState,\n currentTime: number = Date.now()\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.endTime > currentTime\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes a specific status effect by ID.\n * \n * **Korean**: 특정 효과 제거 (Remove Specific Effect)\n * \n * @param player - Current player state\n * @param effectId - ID of effect to remove\n * @returns Updated player state with effect removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeEffectById(player, \"stun_12345\");\n * ```\n * \n * @korean 효과제거\n */\nexport function removeEffectById(\n player: PlayerState,\n effectId: string\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.id !== effectId\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Removes all status effects of a specific type.\n * \n * **Korean**: 특정 유형 효과 모두 제거 (Remove All Effects of Type)\n * \n * @param player - Current player state\n * @param effectType - Type of effects to remove\n * @returns Updated player state with effects removed\n * \n * @example\n * ```typescript\n * const updatedPlayer = removeEffectsByType(\n * player,\n * VitalPointEffectType.PARALYSIS\n * );\n * ```\n * \n * @korean 유형별효과제거\n */\nexport function removeEffectsByType(\n player: PlayerState,\n effectType: VitalPointEffectType\n): PlayerState {\n const activeEffects = player.statusEffects.filter(\n (effect) => effect.type !== effectType\n );\n\n const activeEffectIds = activeEffects.map((effect) => effect.id);\n\n return {\n ...player,\n statusEffects: activeEffects,\n activeEffects: activeEffectIds,\n };\n}\n\n/**\n * Clears all status effects from player.\n * \n * **Korean**: 모든 효과 제거 (Clear All Effects)\n * \n * Useful for healing items, rest periods, or match resets.\n * \n * @param player - Current player state\n * @returns Updated player state with no effects\n * \n * @example\n * ```typescript\n * const clearedPlayer = clearAllEffects(player);\n * ```\n * \n * @korean 전체효과제거\n */\nexport function clearAllEffects(player: PlayerState): PlayerState {\n return {\n ...player,\n statusEffects: [],\n activeEffects: [],\n };\n}\n\n/**\n * Calculates combat stat modifiers based on active status effects.\n * \n * **Korean**: 효과 기반 전투 배율 계산 (Calculate Effect-Based Combat Modifiers)\n * \n * Returns multipliers for various combat attributes affected by status effects.\n * \n * @param player - Current player state\n * @returns Object with stat multipliers (1.0 = no change)\n * \n * @example\n * ```typescript\n * const modifiers = getEffectModifiers(player);\n * const effectiveDamage = baseDamage * modifiers.attackPower;\n * const effectiveSpeed = baseSpeed * modifiers.speed;\n * ```\n * \n * @korean 효과배율계산\n */\nexport function getEffectModifiers(player: PlayerState): {\n attackPower: number;\n defense: number;\n speed: number;\n technique: number;\n staminaRegen: number;\n kiRegen: number;\n} {\n let attackPower = 1.0;\n let defense = 1.0;\n let speed = 1.0;\n let technique = 1.0;\n let staminaRegen = 1.0;\n let kiRegen = 1.0;\n\n for (const effect of player.statusEffects) {\n // UNCONSCIOUSNESS takes precedence - early return with complete incapacitation\n if (effect.type === VitalPointEffectType.UNCONSCIOUSNESS) {\n return {\n attackPower: 0,\n defense: 0,\n speed: 0,\n technique: 0,\n staminaRegen: 0,\n kiRegen: 0,\n };\n }\n\n // Apply other effect modifiers multiplicatively\n switch (effect.type) {\n case VitalPointEffectType.PARALYSIS:\n // Severe movement and action impairment\n attackPower *= 0.3;\n speed *= 0.2;\n technique *= 0.4;\n break;\n\n case VitalPointEffectType.STUN:\n // Moderate impairment\n attackPower *= 0.5;\n defense *= 0.6;\n speed *= 0.5;\n technique *= 0.6;\n break;\n\n case VitalPointEffectType.PAIN:\n // Reduces effectiveness across the board\n attackPower *= 0.7;\n defense *= 0.8;\n speed *= 0.8;\n technique *= 0.7;\n break;\n\n case VitalPointEffectType.BLOOD_FLOW_RESTRICTION:\n // Gradual weakening from reduced circulation\n attackPower *= 0.85;\n staminaRegen *= 0.5;\n break;\n\n case VitalPointEffectType.BREATHLESSNESS:\n // NOTE: Stamina regen is handled by BreathingDisruptionSystem.calculateStaminaRegen()\n // which applies severity-specific multipliers (25%-75% depending on disruption level).\n // DO NOT apply a flat multiplier here as it would compound with the breathing system.\n // Only apply secondary effects here.\n speed *= 0.7; // Reduced movement speed due to breathing difficulty\n break;\n\n case VitalPointEffectType.DISORIENTATION:\n // Accuracy and technique affected\n technique *= 0.6;\n defense *= 0.7;\n break;\n\n case VitalPointEffectType.WEAKNESS:\n // Overall power reduction\n attackPower *= 0.7;\n defense *= 0.8;\n break;\n\n case VitalPointEffectType.NERVE_DISRUPTION:\n // Ki system and coordination impaired\n kiRegen *= 0.5;\n attackPower *= 0.8;\n technique *= 0.7;\n break;\n\n case VitalPointEffectType.ORGAN_DISRUPTION:\n // Internal damage affecting all systems\n attackPower *= 0.6;\n defense *= 0.7;\n staminaRegen *= 0.4;\n kiRegen *= 0.4;\n break;\n }\n }\n\n return {\n attackPower,\n defense,\n speed,\n technique,\n staminaRegen,\n kiRegen,\n };\n}\n\n/**\n * Checks if player has a specific effect type active.\n * \n * **Korean**: 효과 활성 여부 확인 (Check Effect Active)\n * \n * @param player - Current player state\n * @param effectType - Type to check for\n * @returns True if effect type is active\n * \n * @example\n * ```typescript\n * if (hasEffect(player, VitalPointEffectType.PARALYSIS)) {\n * console.log(\"Player is paralyzed!\");\n * }\n * ```\n * \n * @korean 효과확인\n */\nexport function hasEffect(\n player: PlayerState,\n effectType: VitalPointEffectType\n): boolean {\n return player.statusEffects.some((effect) => effect.type === effectType);\n}\n\n/**\n * Gets all active effects of a specific type.\n * \n * **Korean**: 유형별 활성 효과 조회 (Get Effects by Type)\n * \n * @param player - Current player state\n * @param effectType - Type to retrieve\n * @returns Array of matching effects\n * \n * @korean 유형별효과조회\n */\nexport function getEffectsByType(\n player: PlayerState,\n effectType: VitalPointEffectType\n): StatusEffect[] {\n return player.statusEffects.filter((effect) => effect.type === effectType);\n}\n\n/**\n * Gets the total number of active status effects.\n * \n * **Korean**: 활성 효과 개수 조회 (Get Active Effect Count)\n * \n * @param player - Current player state\n * @returns Number of active effects\n * \n * @korean 효과개수조회\n */\nexport function getActiveEffectCount(player: PlayerState): number {\n return player.statusEffects.length;\n}\n\n/**\n * Checks if player can accept new status effects.\n * \n * **Korean**: 효과 추가 가능 여부 확인 (Can Add More Effects)\n * \n * @param player - Current player state\n * @returns True if under MAX_CONCURRENT_EFFECTS limit\n * \n * @korean 효과추가가능확인\n */\nexport function canAddMoreEffects(player: PlayerState): boolean {\n return player.statusEffects.length < MAX_CONCURRENT_EFFECTS;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,mBACd,QACA,SACa;CACb,MAAM,cAAc,KAAK,IAAI;CAE7B,MAAM,iBAAiB,oBACrB,OAAO,eACP,SACA,WACF;CAEA,MAAM,kBAAkB,eAAe,KAAK,WAAW,OAAO,EAAE;CAEhE,OAAO;EACL,GAAG;EACH,eAAe;EACf,eAAe;CACjB;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,qBACd,QACA,cAAsB,KAAK,IAAI,GAClB;CACb,MAAM,gBAAgB,OAAO,cAAc,QACxC,WAAW,OAAO,UAAU,WAC/B;CAEA,MAAM,kBAAkB,cAAc,KAAK,WAAW,OAAO,EAAE;CAE/D,OAAO;EACL,GAAG;EACH,eAAe;EACf,eAAe;CACjB;AACF;;;;;;;;;;;;;;;;;;;;AAmHA,SAAgB,mBAAmB,QAOjC;CACA,IAAI,cAAc;CAClB,IAAI,UAAU;CACd,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,IAAI,eAAe;CACnB,IAAI,UAAU;CAEd,KAAK,MAAM,UAAU,OAAO,eAAe;EAEzC,IAAI,OAAO,SAAS,qBAAqB,iBACvC,OAAO;GACL,aAAa;GACb,SAAS;GACT,OAAO;GACP,WAAW;GACX,cAAc;GACd,SAAS;EACX;EAIF,QAAQ,OAAO,MAAf;GACE,KAAK,qBAAqB;IAExB,eAAe;IACf,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,SAAS;IACT,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,gBAAgB;IAChB;GAEF,KAAK,qBAAqB;IAKxB,SAAS;IACT;GAEF,KAAK,qBAAqB;IAExB,aAAa;IACb,WAAW;IACX;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX;GAEF,KAAK,qBAAqB;IAExB,WAAW;IACX,eAAe;IACf,aAAa;IACb;GAEF,KAAK,qBAAqB;IAExB,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,WAAW;EAEf;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdaptiveDifficulty.js","names":[],"sources":["../../../src/systems/ai/AdaptiveDifficulty.ts"],"sourcesContent":["/**\n * Adaptive Difficulty System for Korean Martial Arts Combat\n * Tracks player skill metrics and adjusts AI difficulty dynamically\n */\n\nimport { AIPersonality } from \"./AIPersonality\";\n\n/**\n * Player skill metrics tracked for adaptive difficulty\n */\nexport interface PlayerSkillMetrics {\n averageAccuracy: number; // 0.0-1.0: Hit rate\n comboCount: number; // Total combos executed\n perfectBlocks: number; // Perfect timing blocks\n reactionTime: number; // Average reaction time in ms\n vitalPointHits: number; // Successful vital point strikes\n stanceTransitions: number; // Effective stance changes\n damageEfficiency: number; // 0.0-1.0: Damage dealt vs taken ratio\n matchesPlayed: number; // Total matches for scaling\n}\n\n/**\n * Difficulty tier levels (5 tiers for adaptive system)\n */\nexport enum DifficultyTier {\n BEGINNER = 1,\n NOVICE = 2,\n INTERMEDIATE = 3,\n ADVANCED = 4,\n EXPERT = 5,\n}\n\n/**\n * Difficulty parameters that control AI behavior\n * Applied dynamically based on player skill level\n * \n * @korean 난이도 매개변수 - 플레이어 실력에 따라 AI 행동 제어\n */\nexport interface DifficultyParameters {\n /** AI reaction time range in milliseconds */\n readonly reactionTimeMs: { readonly min: number; readonly max: number };\n /** Accuracy for vital point strikes (0.0-1.0) */\n readonly vitalPointAccuracy: number;\n /** Accuracy for basic attacks (0.0-1.0) */\n readonly basicAttackAccuracy: number;\n /** Block timing window in milliseconds (smaller = harder to block) */\n readonly blockTimingWindow: number;\n /** Decision quality affects technique selection optimality (0.0-1.0) */\n readonly decisionQuality: number;\n /** Aggression modifier multiplier (0.5-2.0) */\n readonly aggressionModifier: number;\n /** Chance to attempt combo sequences (0.0-1.0) */\n readonly comboChance: number;\n}\n\n/**\n * Difficulty parameter sets for each skill tier\n * Defines AI behavior characteristics at each difficulty level\n * \n * @korean 각 난이도 단계별 매개변수 설정\n */\nexport const DIFFICULTY_PARAMETERS: Record<DifficultyTier, DifficultyParameters> = {\n [DifficultyTier.BEGINNER]: {\n reactionTimeMs: { min: 800, max: 1200 },\n vitalPointAccuracy: 0.40,\n basicAttackAccuracy: 0.70,\n blockTimingWindow: 150,\n decisionQuality: 0.50,\n aggressionModifier: 0.7,\n comboChance: 0.20,\n },\n [DifficultyTier.NOVICE]: {\n reactionTimeMs: { min: 500, max: 800 },\n vitalPointAccuracy: 0.55,\n basicAttackAccuracy: 0.78,\n blockTimingWindow: 120,\n decisionQuality: 0.65,\n aggressionModifier: 0.9,\n comboChance: 0.35,\n },\n [DifficultyTier.INTERMEDIATE]: {\n reactionTimeMs: { min: 300, max: 500 },\n vitalPointAccuracy: 0.65,\n basicAttackAccuracy: 0.85,\n blockTimingWindow: 90,\n decisionQuality: 0.75,\n aggressionModifier: 1.1,\n comboChance: 0.50,\n },\n [DifficultyTier.ADVANCED]: {\n reactionTimeMs: { min: 150, max: 300 },\n vitalPointAccuracy: 0.75,\n basicAttackAccuracy: 0.90,\n blockTimingWindow: 70,\n decisionQuality: 0.85,\n aggressionModifier: 1.3,\n comboChance: 0.60,\n },\n [DifficultyTier.EXPERT]: {\n reactionTimeMs: { min: 50, max: 150 },\n vitalPointAccuracy: 0.85,\n basicAttackAccuracy: 0.95,\n blockTimingWindow: 50,\n decisionQuality: 0.95,\n aggressionModifier: 1.5,\n comboChance: 0.70,\n },\n};\n\n/**\n * Map skill score (0.0-1.0) to difficulty tier\n * Uses fixed thresholds for consistent tier assignment\n * \n * @korean 실력 점수를 난이도 단계로 변환\n * \n * @param score - Player skill score (0.0-1.0)\n * @returns Corresponding difficulty tier\n */\nexport function skillScoreToTier(score: number): DifficultyTier {\n if (score < 0.2) return DifficultyTier.BEGINNER;\n if (score < 0.4) return DifficultyTier.NOVICE;\n if (score < 0.6) return DifficultyTier.INTERMEDIATE;\n if (score < 0.8) return DifficultyTier.ADVANCED;\n return DifficultyTier.EXPERT;\n}\n\n/**\n * Linear interpolation helper\n * @param a - Start value\n * @param b - End value\n * @param t - Interpolation factor (0.0-1.0)\n * @returns Interpolated value\n */\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * Math.max(0, Math.min(1, t));\n}\n\n/**\n * Interpolate between two difficulty parameter sets\n * Used for smooth difficulty transitions over time\n * \n * @korean 난이도 매개변수 간 부드러운 전환\n * \n * @param from - Starting difficulty parameters\n * @param to - Target difficulty parameters\n * @param progress - Interpolation progress (0.0-1.0)\n * @returns Interpolated difficulty parameters\n */\nexport function interpolateDifficultyParameters(\n from: DifficultyParameters,\n to: DifficultyParameters,\n progress: number\n): DifficultyParameters {\n const t = Math.max(0, Math.min(1, progress));\n \n return {\n reactionTimeMs: {\n min: lerp(from.reactionTimeMs.min, to.reactionTimeMs.min, t),\n max: lerp(from.reactionTimeMs.max, to.reactionTimeMs.max, t),\n },\n vitalPointAccuracy: lerp(from.vitalPointAccuracy, to.vitalPointAccuracy, t),\n basicAttackAccuracy: lerp(from.basicAttackAccuracy, to.basicAttackAccuracy, t),\n blockTimingWindow: lerp(from.blockTimingWindow, to.blockTimingWindow, t),\n decisionQuality: lerp(from.decisionQuality, to.decisionQuality, t),\n aggressionModifier: lerp(from.aggressionModifier, to.aggressionModifier, t),\n comboChance: lerp(from.comboChance, to.comboChance, t),\n };\n}\n\n/**\n * Adaptive Difficulty System\n */\nexport class AdaptiveDifficulty {\n private playerSkillMetrics: PlayerSkillMetrics;\n private readonly skillDecay = 0.95; // Gradual skill decay between matches\n private readonly learningRate = 0.1; // How quickly to adapt\n\n constructor() {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Update player skill metrics based on match performance\n */\n updateSkillMetrics(matchData: {\n hitsLanded: number;\n totalAttacks: number;\n combosExecuted: number;\n perfectBlockCount: number;\n avgReactionTimeMs: number;\n vitalPointsHit: number;\n effectiveStanceChanges: number;\n damageDealt: number;\n damageTaken: number;\n }): void {\n const { metrics } = this;\n\n // Update accuracy with learning rate\n const matchAccuracy =\n matchData.totalAttacks > 0\n ? matchData.hitsLanded / matchData.totalAttacks\n : 0.5;\n metrics.averageAccuracy =\n metrics.averageAccuracy * (1 - this.learningRate) +\n matchAccuracy * this.learningRate;\n\n // Update combo count\n metrics.comboCount += matchData.combosExecuted;\n\n // Update perfect blocks\n metrics.perfectBlocks += matchData.perfectBlockCount;\n\n // Update reaction time\n if (matchData.avgReactionTimeMs > 0) {\n metrics.reactionTime =\n metrics.reactionTime * (1 - this.learningRate) +\n matchData.avgReactionTimeMs * this.learningRate;\n }\n\n // Update vital point hits\n metrics.vitalPointHits += matchData.vitalPointsHit;\n\n // Update stance transitions\n metrics.stanceTransitions += matchData.effectiveStanceChanges;\n\n // Update damage efficiency\n const matchEfficiency =\n matchData.damageTaken > 0\n ? Math.min(1, matchData.damageDealt / matchData.damageTaken)\n : matchData.damageDealt > 0\n ? 1.0 // Perfect defense with damage dealt\n : 0.5; // No damage on either side\n metrics.damageEfficiency =\n metrics.damageEfficiency * (1 - this.learningRate) +\n matchEfficiency * this.learningRate;\n\n // Increment matches played\n metrics.matchesPlayed += 1;\n\n // Apply skill decay to prevent over-adjustment\n this.applySkillDecay();\n }\n\n /**\n * Apply gradual skill decay to prevent over-adjustment\n */\n private applySkillDecay(): void {\n const { metrics } = this;\n metrics.averageAccuracy =\n metrics.averageAccuracy * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n metrics.damageEfficiency =\n metrics.damageEfficiency * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n }\n\n /**\n * Calculate overall player skill level (0.0 - 1.0)\n */\n calculatePlayerSkill(): number {\n const { metrics } = this;\n\n // Weight different skill components\n const accuracyScore = metrics.averageAccuracy * 0.3;\n const comboScore = Math.min(1, metrics.comboCount / 20) * 0.2;\n const blockScore = Math.min(1, metrics.perfectBlocks / 10) * 0.2;\n const reactionScore = Math.max(0, 1 - metrics.reactionTime / 1000) * 0.15;\n const vitalScore = Math.min(1, metrics.vitalPointHits / 15) * 0.15;\n\n return accuracyScore + comboScore + blockScore + reactionScore + vitalScore;\n }\n\n /**\n * Get current difficulty tier based on skill level\n */\n getDifficultyTier(): DifficultyTier {\n const skillLevel = this.calculatePlayerSkill();\n return skillScoreToTier(skillLevel);\n }\n\n /**\n * Get difficulty parameters for current skill tier\n * Returns the appropriate DifficultyParameters based on player skill\n * \n * @korean 현재 실력 단계에 맞는 난이도 매개변수 반환\n */\n getDifficultyParameters(): DifficultyParameters {\n const tier = this.getDifficultyTier();\n return DIFFICULTY_PARAMETERS[tier];\n }\n\n /**\n * Adjust AI personality based on player skill\n */\n adjustAIPersonality(personality: AIPersonality): AIPersonality {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n\n // Scale factors based on difficulty tier\n const aggressionScale = 1 + tier * 0.1; // +10% per tier\n const feintScale = 1 + tier * 0.15; // +15% per tier\n const comboScale = 1 + tier * 0.12; // +12% per tier\n const stanceScale = 1 + tier * 0.08; // +8% per tier\n\n return {\n ...personality,\n aggressionLevel: Math.min(\n 0.95,\n personality.aggressionLevel * aggressionScale\n ),\n feintChance: Math.min(0.6, personality.feintChance * feintScale),\n comboTendency: Math.min(0.85, personality.comboTendency * comboScale),\n stanceSwitchFrequency: Math.min(\n 0.9,\n personality.stanceSwitchFrequency * stanceScale\n ),\n // Adjust retreat threshold - better players face more aggressive AI\n tacticalRetreatThreshold: Math.max(\n 0.1,\n personality.tacticalRetreatThreshold * (1 - skillLevel * 0.3)\n ),\n };\n }\n\n /**\n * Get skill metrics\n */\n getMetrics(): Readonly<PlayerSkillMetrics> {\n return { ...this.playerSkillMetrics };\n }\n\n /**\n * Reset skill metrics\n */\n reset(): void {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Get difficulty adjustment recommendation\n */\n getDifficultyRecommendation(): {\n tier: DifficultyTier;\n tierName: string;\n skillLevel: number;\n shouldIncrease: boolean;\n message: string;\n } {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n const shouldIncrease = skillLevel > 0.7 && tier < DifficultyTier.EXPERT;\n\n const tierNames: Record<DifficultyTier, string> = {\n [DifficultyTier.BEGINNER]: \"Beginner (초보)\",\n [DifficultyTier.NOVICE]: \"Novice (입문)\",\n [DifficultyTier.INTERMEDIATE]: \"Intermediate (중급)\",\n [DifficultyTier.ADVANCED]: \"Advanced (고급)\",\n [DifficultyTier.EXPERT]: \"Expert (전문)\",\n };\n\n let message: string;\n if (shouldIncrease) {\n message = \"Player shows mastery - increasing difficulty\";\n } else if (skillLevel < 0.3) {\n message = \"Player struggling - maintaining current difficulty\";\n } else {\n message = \"Player performing well - difficulty appropriate\";\n }\n\n return {\n tier,\n tierName: tierNames[tier],\n skillLevel,\n shouldIncrease,\n message,\n };\n }\n\n /**\n * Export metrics for persistence\n */\n exportMetrics(): string {\n return JSON.stringify(this.playerSkillMetrics);\n }\n\n /**\n * Import metrics from persistence\n */\n importMetrics(data: string): boolean {\n try {\n const metrics = JSON.parse(data) as PlayerSkillMetrics;\n this.playerSkillMetrics = metrics;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get reference to metrics (for internal use)\n */\n private get metrics(): PlayerSkillMetrics {\n return this.playerSkillMetrics;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdaptiveDifficulty.js","names":[],"sources":["../../../src/systems/ai/AdaptiveDifficulty.ts"],"sourcesContent":["/**\n * Adaptive Difficulty System for Korean Martial Arts Combat\n * Tracks player skill metrics and adjusts AI difficulty dynamically\n */\n\nimport { AIPersonality } from \"./AIPersonality\";\n\n/**\n * Player skill metrics tracked for adaptive difficulty\n */\nexport interface PlayerSkillMetrics {\n averageAccuracy: number; // 0.0-1.0: Hit rate\n comboCount: number; // Total combos executed\n perfectBlocks: number; // Perfect timing blocks\n reactionTime: number; // Average reaction time in ms\n vitalPointHits: number; // Successful vital point strikes\n stanceTransitions: number; // Effective stance changes\n damageEfficiency: number; // 0.0-1.0: Damage dealt vs taken ratio\n matchesPlayed: number; // Total matches for scaling\n}\n\n/**\n * Difficulty tier levels (5 tiers for adaptive system)\n */\nexport enum DifficultyTier {\n BEGINNER = 1,\n NOVICE = 2,\n INTERMEDIATE = 3,\n ADVANCED = 4,\n EXPERT = 5,\n}\n\n/**\n * Difficulty parameters that control AI behavior\n * Applied dynamically based on player skill level\n * \n * @korean 난이도 매개변수 - 플레이어 실력에 따라 AI 행동 제어\n */\nexport interface DifficultyParameters {\n /** AI reaction time range in milliseconds */\n readonly reactionTimeMs: { readonly min: number; readonly max: number };\n /** Accuracy for vital point strikes (0.0-1.0) */\n readonly vitalPointAccuracy: number;\n /** Accuracy for basic attacks (0.0-1.0) */\n readonly basicAttackAccuracy: number;\n /** Block timing window in milliseconds (smaller = harder to block) */\n readonly blockTimingWindow: number;\n /** Decision quality affects technique selection optimality (0.0-1.0) */\n readonly decisionQuality: number;\n /** Aggression modifier multiplier (0.5-2.0) */\n readonly aggressionModifier: number;\n /** Chance to attempt combo sequences (0.0-1.0) */\n readonly comboChance: number;\n}\n\n/**\n * Difficulty parameter sets for each skill tier\n * Defines AI behavior characteristics at each difficulty level\n * \n * @korean 각 난이도 단계별 매개변수 설정\n */\nexport const DIFFICULTY_PARAMETERS: Record<DifficultyTier, DifficultyParameters> = {\n [DifficultyTier.BEGINNER]: {\n reactionTimeMs: { min: 800, max: 1200 },\n vitalPointAccuracy: 0.40,\n basicAttackAccuracy: 0.70,\n blockTimingWindow: 150,\n decisionQuality: 0.50,\n aggressionModifier: 0.7,\n comboChance: 0.20,\n },\n [DifficultyTier.NOVICE]: {\n reactionTimeMs: { min: 500, max: 800 },\n vitalPointAccuracy: 0.55,\n basicAttackAccuracy: 0.78,\n blockTimingWindow: 120,\n decisionQuality: 0.65,\n aggressionModifier: 0.9,\n comboChance: 0.35,\n },\n [DifficultyTier.INTERMEDIATE]: {\n reactionTimeMs: { min: 300, max: 500 },\n vitalPointAccuracy: 0.65,\n basicAttackAccuracy: 0.85,\n blockTimingWindow: 90,\n decisionQuality: 0.75,\n aggressionModifier: 1.1,\n comboChance: 0.50,\n },\n [DifficultyTier.ADVANCED]: {\n reactionTimeMs: { min: 150, max: 300 },\n vitalPointAccuracy: 0.75,\n basicAttackAccuracy: 0.90,\n blockTimingWindow: 70,\n decisionQuality: 0.85,\n aggressionModifier: 1.3,\n comboChance: 0.60,\n },\n [DifficultyTier.EXPERT]: {\n reactionTimeMs: { min: 50, max: 150 },\n vitalPointAccuracy: 0.85,\n basicAttackAccuracy: 0.95,\n blockTimingWindow: 50,\n decisionQuality: 0.95,\n aggressionModifier: 1.5,\n comboChance: 0.70,\n },\n};\n\n/**\n * Map skill score (0.0-1.0) to difficulty tier\n * Uses fixed thresholds for consistent tier assignment\n * \n * @korean 실력 점수를 난이도 단계로 변환\n * \n * @param score - Player skill score (0.0-1.0)\n * @returns Corresponding difficulty tier\n */\nexport function skillScoreToTier(score: number): DifficultyTier {\n if (score < 0.2) return DifficultyTier.BEGINNER;\n if (score < 0.4) return DifficultyTier.NOVICE;\n if (score < 0.6) return DifficultyTier.INTERMEDIATE;\n if (score < 0.8) return DifficultyTier.ADVANCED;\n return DifficultyTier.EXPERT;\n}\n\n/**\n * Linear interpolation helper\n * @param a - Start value\n * @param b - End value\n * @param t - Interpolation factor (0.0-1.0)\n * @returns Interpolated value\n */\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * Math.max(0, Math.min(1, t));\n}\n\n/**\n * Interpolate between two difficulty parameter sets\n * Used for smooth difficulty transitions over time\n * \n * @korean 난이도 매개변수 간 부드러운 전환\n * \n * @param from - Starting difficulty parameters\n * @param to - Target difficulty parameters\n * @param progress - Interpolation progress (0.0-1.0)\n * @returns Interpolated difficulty parameters\n */\nexport function interpolateDifficultyParameters(\n from: DifficultyParameters,\n to: DifficultyParameters,\n progress: number\n): DifficultyParameters {\n const t = Math.max(0, Math.min(1, progress));\n \n return {\n reactionTimeMs: {\n min: lerp(from.reactionTimeMs.min, to.reactionTimeMs.min, t),\n max: lerp(from.reactionTimeMs.max, to.reactionTimeMs.max, t),\n },\n vitalPointAccuracy: lerp(from.vitalPointAccuracy, to.vitalPointAccuracy, t),\n basicAttackAccuracy: lerp(from.basicAttackAccuracy, to.basicAttackAccuracy, t),\n blockTimingWindow: lerp(from.blockTimingWindow, to.blockTimingWindow, t),\n decisionQuality: lerp(from.decisionQuality, to.decisionQuality, t),\n aggressionModifier: lerp(from.aggressionModifier, to.aggressionModifier, t),\n comboChance: lerp(from.comboChance, to.comboChance, t),\n };\n}\n\n/**\n * Adaptive Difficulty System\n */\nexport class AdaptiveDifficulty {\n private playerSkillMetrics: PlayerSkillMetrics;\n private readonly skillDecay = 0.95; // Gradual skill decay between matches\n private readonly learningRate = 0.1; // How quickly to adapt\n\n constructor() {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Update player skill metrics based on match performance\n */\n updateSkillMetrics(matchData: {\n hitsLanded: number;\n totalAttacks: number;\n combosExecuted: number;\n perfectBlockCount: number;\n avgReactionTimeMs: number;\n vitalPointsHit: number;\n effectiveStanceChanges: number;\n damageDealt: number;\n damageTaken: number;\n }): void {\n const { metrics } = this;\n\n // Update accuracy with learning rate\n const matchAccuracy =\n matchData.totalAttacks > 0\n ? matchData.hitsLanded / matchData.totalAttacks\n : 0.5;\n metrics.averageAccuracy =\n metrics.averageAccuracy * (1 - this.learningRate) +\n matchAccuracy * this.learningRate;\n\n // Update combo count\n metrics.comboCount += matchData.combosExecuted;\n\n // Update perfect blocks\n metrics.perfectBlocks += matchData.perfectBlockCount;\n\n // Update reaction time\n if (matchData.avgReactionTimeMs > 0) {\n metrics.reactionTime =\n metrics.reactionTime * (1 - this.learningRate) +\n matchData.avgReactionTimeMs * this.learningRate;\n }\n\n // Update vital point hits\n metrics.vitalPointHits += matchData.vitalPointsHit;\n\n // Update stance transitions\n metrics.stanceTransitions += matchData.effectiveStanceChanges;\n\n // Update damage efficiency\n const matchEfficiency =\n matchData.damageTaken > 0\n ? Math.min(1, matchData.damageDealt / matchData.damageTaken)\n : matchData.damageDealt > 0\n ? 1.0 // Perfect defense with damage dealt\n : 0.5; // No damage on either side\n metrics.damageEfficiency =\n metrics.damageEfficiency * (1 - this.learningRate) +\n matchEfficiency * this.learningRate;\n\n // Increment matches played\n metrics.matchesPlayed += 1;\n\n // Apply skill decay to prevent over-adjustment\n this.applySkillDecay();\n }\n\n /**\n * Apply gradual skill decay to prevent over-adjustment\n */\n private applySkillDecay(): void {\n const { metrics } = this;\n metrics.averageAccuracy =\n metrics.averageAccuracy * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n metrics.damageEfficiency =\n metrics.damageEfficiency * this.skillDecay +\n 0.5 * (1 - this.skillDecay);\n }\n\n /**\n * Calculate overall player skill level (0.0 - 1.0)\n */\n calculatePlayerSkill(): number {\n const { metrics } = this;\n\n // Weight different skill components\n const accuracyScore = metrics.averageAccuracy * 0.3;\n const comboScore = Math.min(1, metrics.comboCount / 20) * 0.2;\n const blockScore = Math.min(1, metrics.perfectBlocks / 10) * 0.2;\n const reactionScore = Math.max(0, 1 - metrics.reactionTime / 1000) * 0.15;\n const vitalScore = Math.min(1, metrics.vitalPointHits / 15) * 0.15;\n\n return accuracyScore + comboScore + blockScore + reactionScore + vitalScore;\n }\n\n /**\n * Get current difficulty tier based on skill level\n */\n getDifficultyTier(): DifficultyTier {\n const skillLevel = this.calculatePlayerSkill();\n return skillScoreToTier(skillLevel);\n }\n\n /**\n * Get difficulty parameters for current skill tier\n * Returns the appropriate DifficultyParameters based on player skill\n * \n * @korean 현재 실력 단계에 맞는 난이도 매개변수 반환\n */\n getDifficultyParameters(): DifficultyParameters {\n const tier = this.getDifficultyTier();\n return DIFFICULTY_PARAMETERS[tier];\n }\n\n /**\n * Adjust AI personality based on player skill\n */\n adjustAIPersonality(personality: AIPersonality): AIPersonality {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n\n // Scale factors based on difficulty tier\n const aggressionScale = 1 + tier * 0.1; // +10% per tier\n const feintScale = 1 + tier * 0.15; // +15% per tier\n const comboScale = 1 + tier * 0.12; // +12% per tier\n const stanceScale = 1 + tier * 0.08; // +8% per tier\n\n return {\n ...personality,\n aggressionLevel: Math.min(\n 0.95,\n personality.aggressionLevel * aggressionScale\n ),\n feintChance: Math.min(0.6, personality.feintChance * feintScale),\n comboTendency: Math.min(0.85, personality.comboTendency * comboScale),\n stanceSwitchFrequency: Math.min(\n 0.9,\n personality.stanceSwitchFrequency * stanceScale\n ),\n // Adjust retreat threshold - better players face more aggressive AI\n tacticalRetreatThreshold: Math.max(\n 0.1,\n personality.tacticalRetreatThreshold * (1 - skillLevel * 0.3)\n ),\n };\n }\n\n /**\n * Get skill metrics\n */\n getMetrics(): Readonly<PlayerSkillMetrics> {\n return { ...this.playerSkillMetrics };\n }\n\n /**\n * Reset skill metrics\n */\n reset(): void {\n this.playerSkillMetrics = {\n averageAccuracy: 0.5,\n comboCount: 0,\n perfectBlocks: 0,\n reactionTime: 800,\n vitalPointHits: 0,\n stanceTransitions: 0,\n damageEfficiency: 0.5,\n matchesPlayed: 0,\n };\n }\n\n /**\n * Get difficulty adjustment recommendation\n */\n getDifficultyRecommendation(): {\n tier: DifficultyTier;\n tierName: string;\n skillLevel: number;\n shouldIncrease: boolean;\n message: string;\n } {\n const skillLevel = this.calculatePlayerSkill();\n const tier = this.getDifficultyTier();\n const shouldIncrease = skillLevel > 0.7 && tier < DifficultyTier.EXPERT;\n\n const tierNames: Record<DifficultyTier, string> = {\n [DifficultyTier.BEGINNER]: \"Beginner (초보)\",\n [DifficultyTier.NOVICE]: \"Novice (입문)\",\n [DifficultyTier.INTERMEDIATE]: \"Intermediate (중급)\",\n [DifficultyTier.ADVANCED]: \"Advanced (고급)\",\n [DifficultyTier.EXPERT]: \"Expert (전문)\",\n };\n\n let message: string;\n if (shouldIncrease) {\n message = \"Player shows mastery - increasing difficulty\";\n } else if (skillLevel < 0.3) {\n message = \"Player struggling - maintaining current difficulty\";\n } else {\n message = \"Player performing well - difficulty appropriate\";\n }\n\n return {\n tier,\n tierName: tierNames[tier],\n skillLevel,\n shouldIncrease,\n message,\n };\n }\n\n /**\n * Export metrics for persistence\n */\n exportMetrics(): string {\n return JSON.stringify(this.playerSkillMetrics);\n }\n\n /**\n * Import metrics from persistence\n */\n importMetrics(data: string): boolean {\n try {\n const metrics = JSON.parse(data) as PlayerSkillMetrics;\n this.playerSkillMetrics = metrics;\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get reference to metrics (for internal use)\n */\n private get metrics(): PlayerSkillMetrics {\n return this.playerSkillMetrics;\n }\n}\n"],"mappings":"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"}
|