blacktrigram 0.7.98 → 0.7.100
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 +14 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatStateSystem.js","names":[],"sources":["../../../src/systems/combat/CombatStateSystem.ts"],"sourcesContent":["/**\n * Combat State System for managing Ready/Shaken/Vulnerable/Helpless states.\n * \n * **Korean**: 전투 상태 시스템 (Combat State System)\n * \n * Implements the combat readiness state machine based on cumulative damage,\n * pain, consciousness, and balance. Each state affects player capabilities\n * with specific multipliers.\n * \n * ## Combat States\n * \n * - **🟢 READY**: 100% capability - perfect combat condition\n * - **🟡 SHAKEN**: 80% capability - slightly compromised, -20% accuracy\n * - **🟠 VULNERABLE**: 60% capability - significantly exposed, -40% defense\n * - **🔴 HELPLESS**: 20% capability - near incapacitation, cannot block\n * \n * @module systems/combat/CombatStateSystem\n * @category Combat System\n * @korean 전투상태시스템\n */\n\nimport { PlayerState } from \"../player\";\n\n/**\n * Combat readiness states representing player combat effectiveness.\n * \n * **Korean**: 전투 준비 상태\n */\nexport enum CombatReadinessState {\n /** 🟢 Ready - Full combat capability (100%) */\n READY = \"ready\",\n /** 🟡 Shaken - Reduced effectiveness (80%) */\n SHAKEN = \"shaken\",\n /** 🟠 Vulnerable - Significantly impaired (60%) */\n VULNERABLE = \"vulnerable\",\n /** 🔴 Helpless - Near incapacitation (20%) */\n HELPLESS = \"helpless\",\n}\n\n/**\n * State capability modifiers for each combat state.\n * \n * **Korean**: 상태 능력 배율\n */\ninterface StateCapability {\n /** Overall capability percentage */\n readonly capability: number;\n /** Accuracy modifier (affects hit chance and damage output) */\n readonly accuracyModifier: number;\n /** Defense modifier (affects damage reduction) */\n readonly defenseModifier: number;\n /** Damage modifier (affects outgoing damage) */\n readonly damageModifier: number;\n /** Damage taken multiplier (affects incoming damage) */\n readonly damageTakenMultiplier: number;\n /** Movement speed modifier */\n readonly speedModifier: number;\n /** Can perform blocking actions */\n readonly canBlock: boolean;\n /** Can execute techniques */\n readonly canExecuteTechniques: boolean;\n}\n\n/**\n * Combat State System managing readiness state transitions.\n * \n * Evaluates player condition and determines current combat readiness state.\n * States degrade based on:\n * - Cumulative damage (health loss)\n * - Pain overload\n * - Consciousness reduction\n * - Balance disruption\n * \n * @example\n * ```typescript\n * const combatStateSystem = new CombatStateSystem();\n * \n * // Determine current state\n * const state = combatStateSystem.determineState(player);\n * \n * // Get capability modifiers\n * const capability = combatStateSystem.getCapability(state);\n * console.log(`Capability: ${capability.capability}%`);\n * \n * // Apply modifiers to damage calculation\n * const modifiedDamage = baseDamage * capability.accuracyModifier;\n * ```\n * \n * @korean 전투상태시스템\n */\nexport class CombatStateSystem {\n /**\n * State capability definitions with modifiers per state.\n * \n * Based on acceptance criteria:\n * - READY: 100% capability (baseline)\n * - SHAKEN: -15% accuracy, -10% damage\n * - VULNERABLE: -30% accuracy, -25% damage, +50% damage taken\n * - HELPLESS: Cannot attack, cannot block, +100% damage taken\n */\n private readonly stateCapabilities: Record<CombatReadinessState, StateCapability> = {\n [CombatReadinessState.READY]: {\n capability: 1.0, // 100%\n accuracyModifier: 1.0,\n defenseModifier: 1.0,\n damageModifier: 1.0,\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 1.0,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.SHAKEN]: {\n capability: 0.85, // 85%\n accuracyModifier: 0.85, // -15% accuracy\n defenseModifier: 1.0,\n damageModifier: 0.9, // -10% damage\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 0.95,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.VULNERABLE]: {\n capability: 0.7, // 70%\n accuracyModifier: 0.7, // -30% accuracy\n defenseModifier: 0.75,\n damageModifier: 0.75, // -25% damage\n damageTakenMultiplier: 1.5, // +50% damage taken\n speedModifier: 0.8,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.HELPLESS]: {\n capability: 0.0, // 0%\n accuracyModifier: 0.0, // Cannot attack\n defenseModifier: 0.0,\n damageModifier: 0.0, // Cannot attack\n damageTakenMultiplier: 2.0, // +100% damage taken (2x)\n speedModifier: 0.3,\n canBlock: false, // Cannot block\n canExecuteTechniques: false,\n },\n };\n\n /**\n * Determines the current combat readiness state based on player condition.\n * \n * Evaluates health, pain, consciousness, balance, and recent hits to determine\n * the most appropriate combat state. State transitions follow acceptance criteria:\n * - READY → SHAKEN: After taking 2-3 hits or significant vital point strike\n * - SHAKEN → VULNERABLE: After additional 2 hits or loss of 30% body part health\n * - VULNERABLE → HELPLESS: After head trauma, pain >80, or knock-down\n * - Recovery: HELPLESS → READY over 5 seconds if no additional hits\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp in milliseconds\n * @returns Current combat readiness state\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player, Date.now());\n * if (state === CombatReadinessState.HELPLESS) {\n * console.log(\"Player is near incapacitation!\");\n * }\n * ```\n * \n * @korean 상태결정\n */\n determineState(player: PlayerState, currentTime?: number): CombatReadinessState {\n const healthPercent = player.health / player.maxHealth;\n const pain = player.pain;\n const consciousness = player.consciousness;\n const balance = player.balance;\n \n // Count recent hits (within last 10 seconds)\n const recentHits = this.countRecentHits(player, currentTime, 10000);\n \n // Check for recovery from HELPLESS state\n // If player was helpless and recovery conditions are met, allow normal state determination\n // Recovery overrides low stats if enough time has passed without hits\n if (player.lastHelplessStateTime && currentTime) {\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n \n // Recovery: HELPLESS → normal state after 5 seconds if no additional hits\n if (timeSinceHelpless >= 5000 && noRecentHits) {\n // Player has recovered - skip HELPLESS check and determine state normally\n // This allows recovery even if stats are still low\n // Continue to normal state determination below\n } else if (timeSinceHelpless < 5000) {\n // Still in recovery period, check if should remain HELPLESS\n const stillCritical = healthPercent <= 0.3 || pain > 80 || consciousness <= 20 || balance <= 20;\n if (stillCritical) {\n return CombatReadinessState.HELPLESS;\n }\n }\n }\n\n // Check for HELPLESS state (worst condition) - only if not in recovery\n // Triggers: health <= 30%, pain > 80, consciousness <= 20, balance <= 20, or head trauma\n const hasHeadTrauma = player.bodyPartHealth \n ? (player.bodyPartHealth.head / (player.bodyPartMaxHealth?.head ?? 100)) < 0.5\n : false;\n \n // Only enter HELPLESS if not already recovering\n const isRecovering = player.lastHelplessStateTime && currentTime \n && (currentTime - player.lastHelplessStateTime) >= 5000\n && this.countRecentHits(player, currentTime, 5000) === 0;\n \n if (!isRecovering && (\n healthPercent <= 0.3 ||\n pain > 80 ||\n consciousness <= 20 ||\n balance <= 20 ||\n hasHeadTrauma\n )) {\n return CombatReadinessState.HELPLESS;\n }\n\n // Check for VULNERABLE state\n // Triggers: health <= 50%, pain > 60, consciousness <= 40, balance <= 40,\n // or 30% body part health loss\n const hasBodyPartDamage = this.checkBodyPartHealthLoss(player, 0.3);\n \n if (\n healthPercent <= 0.5 ||\n pain > 60 ||\n consciousness <= 40 ||\n balance <= 40 ||\n hasBodyPartDamage ||\n recentHits >= 4 // Additional 2 hits from SHAKEN (total 4-5 hits)\n ) {\n return CombatReadinessState.VULNERABLE;\n }\n\n // Check for SHAKEN state\n // Triggers: health <= 70%, pain > 30, consciousness <= 60, balance <= 60,\n // or 2-3 recent hits\n if (\n healthPercent <= 0.7 ||\n pain > 30 ||\n consciousness <= 60 ||\n balance <= 60 ||\n recentHits >= 2 // 2-3 hits triggers SHAKEN\n ) {\n return CombatReadinessState.SHAKEN;\n }\n\n // Default to READY state\n return CombatReadinessState.READY;\n }\n\n /**\n * Gets the capability modifiers for a specific combat state.\n * \n * Returns all modifiers that should be applied to player actions\n * based on their current combat readiness.\n * \n * @param state - Combat readiness state\n * @returns Capability modifiers for the state\n * \n * @example\n * ```typescript\n * const capability = combatStateSystem.getCapability(\n * CombatReadinessState.SHAKEN\n * );\n * \n * // Apply to damage calculation\n * const finalDamage = baseDamage * capability.accuracyModifier;\n * \n * // Check if can block\n * if (!capability.canBlock) {\n * console.log(\"Cannot block in this state!\");\n * }\n * ```\n * \n * @korean 능력조회\n */\n getCapability(state: CombatReadinessState): StateCapability {\n return this.stateCapabilities[state];\n }\n\n /**\n * Applies combat state modifiers to a player state.\n * \n * Creates a modified player state with capability reductions\n * based on current combat readiness.\n * \n * @param player - Current player state\n * @param state - Combat readiness state to apply\n * @returns Modified player state with applied modifiers\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player);\n * const modifiedPlayer = combatStateSystem.applyStateModifiers(\n * player,\n * state\n * );\n * ```\n * \n * @korean 상태적용\n */\n applyStateModifiers(\n player: PlayerState,\n state: CombatReadinessState\n ): PlayerState {\n const capability = this.getCapability(state);\n\n return {\n ...player,\n attackPower: Math.floor(player.attackPower * capability.damageModifier),\n defense: Math.floor(player.defense * capability.defenseModifier),\n speed: Math.floor(player.speed * capability.speedModifier),\n isBlocking: player.isBlocking && capability.canBlock,\n isStunned: player.isStunned || !capability.canExecuteTechniques,\n };\n }\n\n /**\n * Gets bilingual name for combat state.\n * \n * @param state - Combat readiness state\n * @returns Korean and English state names\n * \n * @korean 상태이름\n */\n getStateName(state: CombatReadinessState): { korean: string; english: string } {\n const names: Record<CombatReadinessState, { korean: string; english: string }> = {\n [CombatReadinessState.READY]: {\n korean: \"준비완료\",\n english: \"Ready\",\n },\n [CombatReadinessState.SHAKEN]: {\n korean: \"동요상태\",\n english: \"Shaken\",\n },\n [CombatReadinessState.VULNERABLE]: {\n korean: \"취약상태\",\n english: \"Vulnerable\",\n },\n [CombatReadinessState.HELPLESS]: {\n korean: \"무력상태\",\n english: \"Helpless\",\n },\n };\n\n return names[state];\n }\n\n /**\n * Gets emoji indicator for combat state.\n * \n * @param state - Combat readiness state\n * @returns Emoji representing the state\n * \n * @korean 상태아이콘\n */\n getStateEmoji(state: CombatReadinessState): string {\n const emojis: Record<CombatReadinessState, string> = {\n [CombatReadinessState.READY]: \"🟢\",\n [CombatReadinessState.SHAKEN]: \"🟡\",\n [CombatReadinessState.VULNERABLE]: \"🟠\",\n [CombatReadinessState.HELPLESS]: \"🔴\",\n };\n\n return emojis[state];\n }\n\n /**\n * Counts recent hits within a time window.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (milliseconds)\n * @param timeWindow - Time window to check in milliseconds\n * @returns Number of hits within the time window\n * \n * @private\n * @korean 최근타격횟수\n */\n private countRecentHits(\n player: PlayerState,\n currentTime: number | undefined,\n timeWindow: number\n ): number {\n if (!currentTime || !player.recentHitTimestamps) {\n return 0;\n }\n\n const cutoffTime = currentTime - timeWindow;\n return player.recentHitTimestamps.filter(\n (timestamp) => timestamp >= cutoffTime\n ).length;\n }\n\n /**\n * Checks if any body part has lost more than the specified percentage of health.\n * \n * @param player - Current player state\n * @param lossThreshold - Health loss threshold (0.0 to 1.0)\n * @returns True if any body part has lost more than the threshold\n * \n * @private\n * @korean 신체부위손상확인\n */\n private checkBodyPartHealthLoss(\n player: PlayerState,\n lossThreshold: number\n ): boolean {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return false;\n }\n\n const bodyParts: Array<keyof typeof player.bodyPartHealth> = [\n \"head\",\n \"neck\",\n \"torsoUpper\",\n \"torsoLower\",\n \"armLeft\",\n \"armRight\",\n \"legLeft\",\n \"legRight\",\n ];\n\n for (const part of bodyParts) {\n const current = player.bodyPartHealth[part];\n const max = player.bodyPartMaxHealth[part];\n const healthPercent = current / max;\n const lossPercent = 1 - healthPercent;\n\n if (lossPercent >= lossThreshold) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Records a hit on the player and updates recent hit tracking.\n * \n * Should be called by combat system when player takes a hit.\n * Maintains a rolling window of the last 10 hit timestamps.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp of the hit\n * @returns Updated player state with recorded hit\n * \n * @korean 타격기록\n */\n recordHit(player: PlayerState, currentTime: number): PlayerState {\n const recentHits = player.recentHitTimestamps ?? [];\n \n // Add new hit timestamp and keep only last 10\n const updatedHits = [...recentHits, currentTime].slice(-10);\n\n return {\n ...player,\n recentHitTimestamps: updatedHits,\n hitsTaken: player.hitsTaken + 1,\n };\n }\n\n /**\n * Updates player when entering HELPLESS state.\n * \n * Records the timestamp for recovery tracking.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp when entering helpless state\n * @returns Updated player state\n * \n * @korean 무력상태기록\n */\n enterHelplessState(player: PlayerState, currentTime: number): PlayerState {\n return {\n ...player,\n lastHelplessStateTime: currentTime,\n };\n }\n\n /**\n * Checks if player can recover from HELPLESS state.\n * \n * Recovery occurs after 5 seconds with no additional hits.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp\n * @returns True if recovery is possible\n * \n * @korean 회복가능확인\n */\n canRecoverFromHelpless(player: PlayerState, currentTime: number): boolean {\n if (!player.lastHelplessStateTime) {\n return false;\n }\n\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n\n return timeSinceHelpless >= 5000 && noRecentHits;\n }\n}\n\nexport default CombatStateSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatStateSystem.js","names":[],"sources":["../../../src/systems/combat/CombatStateSystem.ts"],"sourcesContent":["/**\n * Combat State System for managing Ready/Shaken/Vulnerable/Helpless states.\n * \n * **Korean**: 전투 상태 시스템 (Combat State System)\n * \n * Implements the combat readiness state machine based on cumulative damage,\n * pain, consciousness, and balance. Each state affects player capabilities\n * with specific multipliers.\n * \n * ## Combat States\n * \n * - **🟢 READY**: 100% capability - perfect combat condition\n * - **🟡 SHAKEN**: 80% capability - slightly compromised, -20% accuracy\n * - **🟠 VULNERABLE**: 60% capability - significantly exposed, -40% defense\n * - **🔴 HELPLESS**: 20% capability - near incapacitation, cannot block\n * \n * @module systems/combat/CombatStateSystem\n * @category Combat System\n * @korean 전투상태시스템\n */\n\nimport { PlayerState } from \"../player\";\n\n/**\n * Combat readiness states representing player combat effectiveness.\n * \n * **Korean**: 전투 준비 상태\n */\nexport enum CombatReadinessState {\n /** 🟢 Ready - Full combat capability (100%) */\n READY = \"ready\",\n /** 🟡 Shaken - Reduced effectiveness (80%) */\n SHAKEN = \"shaken\",\n /** 🟠 Vulnerable - Significantly impaired (60%) */\n VULNERABLE = \"vulnerable\",\n /** 🔴 Helpless - Near incapacitation (20%) */\n HELPLESS = \"helpless\",\n}\n\n/**\n * State capability modifiers for each combat state.\n * \n * **Korean**: 상태 능력 배율\n */\ninterface StateCapability {\n /** Overall capability percentage */\n readonly capability: number;\n /** Accuracy modifier (affects hit chance and damage output) */\n readonly accuracyModifier: number;\n /** Defense modifier (affects damage reduction) */\n readonly defenseModifier: number;\n /** Damage modifier (affects outgoing damage) */\n readonly damageModifier: number;\n /** Damage taken multiplier (affects incoming damage) */\n readonly damageTakenMultiplier: number;\n /** Movement speed modifier */\n readonly speedModifier: number;\n /** Can perform blocking actions */\n readonly canBlock: boolean;\n /** Can execute techniques */\n readonly canExecuteTechniques: boolean;\n}\n\n/**\n * Combat State System managing readiness state transitions.\n * \n * Evaluates player condition and determines current combat readiness state.\n * States degrade based on:\n * - Cumulative damage (health loss)\n * - Pain overload\n * - Consciousness reduction\n * - Balance disruption\n * \n * @example\n * ```typescript\n * const combatStateSystem = new CombatStateSystem();\n * \n * // Determine current state\n * const state = combatStateSystem.determineState(player);\n * \n * // Get capability modifiers\n * const capability = combatStateSystem.getCapability(state);\n * console.log(`Capability: ${capability.capability}%`);\n * \n * // Apply modifiers to damage calculation\n * const modifiedDamage = baseDamage * capability.accuracyModifier;\n * ```\n * \n * @korean 전투상태시스템\n */\nexport class CombatStateSystem {\n /**\n * State capability definitions with modifiers per state.\n * \n * Based on acceptance criteria:\n * - READY: 100% capability (baseline)\n * - SHAKEN: -15% accuracy, -10% damage\n * - VULNERABLE: -30% accuracy, -25% damage, +50% damage taken\n * - HELPLESS: Cannot attack, cannot block, +100% damage taken\n */\n private readonly stateCapabilities: Record<CombatReadinessState, StateCapability> = {\n [CombatReadinessState.READY]: {\n capability: 1.0, // 100%\n accuracyModifier: 1.0,\n defenseModifier: 1.0,\n damageModifier: 1.0,\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 1.0,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.SHAKEN]: {\n capability: 0.85, // 85%\n accuracyModifier: 0.85, // -15% accuracy\n defenseModifier: 1.0,\n damageModifier: 0.9, // -10% damage\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 0.95,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.VULNERABLE]: {\n capability: 0.7, // 70%\n accuracyModifier: 0.7, // -30% accuracy\n defenseModifier: 0.75,\n damageModifier: 0.75, // -25% damage\n damageTakenMultiplier: 1.5, // +50% damage taken\n speedModifier: 0.8,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.HELPLESS]: {\n capability: 0.0, // 0%\n accuracyModifier: 0.0, // Cannot attack\n defenseModifier: 0.0,\n damageModifier: 0.0, // Cannot attack\n damageTakenMultiplier: 2.0, // +100% damage taken (2x)\n speedModifier: 0.3,\n canBlock: false, // Cannot block\n canExecuteTechniques: false,\n },\n };\n\n /**\n * Determines the current combat readiness state based on player condition.\n * \n * Evaluates health, pain, consciousness, balance, and recent hits to determine\n * the most appropriate combat state. State transitions follow acceptance criteria:\n * - READY → SHAKEN: After taking 2-3 hits or significant vital point strike\n * - SHAKEN → VULNERABLE: After additional 2 hits or loss of 30% body part health\n * - VULNERABLE → HELPLESS: After head trauma, pain >80, or knock-down\n * - Recovery: HELPLESS → READY over 5 seconds if no additional hits\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp in milliseconds\n * @returns Current combat readiness state\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player, Date.now());\n * if (state === CombatReadinessState.HELPLESS) {\n * console.log(\"Player is near incapacitation!\");\n * }\n * ```\n * \n * @korean 상태결정\n */\n determineState(player: PlayerState, currentTime?: number): CombatReadinessState {\n const healthPercent = player.health / player.maxHealth;\n const pain = player.pain;\n const consciousness = player.consciousness;\n const balance = player.balance;\n \n // Count recent hits (within last 10 seconds)\n const recentHits = this.countRecentHits(player, currentTime, 10000);\n \n // Check for recovery from HELPLESS state\n // If player was helpless and recovery conditions are met, allow normal state determination\n // Recovery overrides low stats if enough time has passed without hits\n if (player.lastHelplessStateTime && currentTime) {\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n \n // Recovery: HELPLESS → normal state after 5 seconds if no additional hits\n if (timeSinceHelpless >= 5000 && noRecentHits) {\n // Player has recovered - skip HELPLESS check and determine state normally\n // This allows recovery even if stats are still low\n // Continue to normal state determination below\n } else if (timeSinceHelpless < 5000) {\n // Still in recovery period, check if should remain HELPLESS\n const stillCritical = healthPercent <= 0.3 || pain > 80 || consciousness <= 20 || balance <= 20;\n if (stillCritical) {\n return CombatReadinessState.HELPLESS;\n }\n }\n }\n\n // Check for HELPLESS state (worst condition) - only if not in recovery\n // Triggers: health <= 30%, pain > 80, consciousness <= 20, balance <= 20, or head trauma\n const hasHeadTrauma = player.bodyPartHealth \n ? (player.bodyPartHealth.head / (player.bodyPartMaxHealth?.head ?? 100)) < 0.5\n : false;\n \n // Only enter HELPLESS if not already recovering\n const isRecovering = player.lastHelplessStateTime && currentTime \n && (currentTime - player.lastHelplessStateTime) >= 5000\n && this.countRecentHits(player, currentTime, 5000) === 0;\n \n if (!isRecovering && (\n healthPercent <= 0.3 ||\n pain > 80 ||\n consciousness <= 20 ||\n balance <= 20 ||\n hasHeadTrauma\n )) {\n return CombatReadinessState.HELPLESS;\n }\n\n // Check for VULNERABLE state\n // Triggers: health <= 50%, pain > 60, consciousness <= 40, balance <= 40,\n // or 30% body part health loss\n const hasBodyPartDamage = this.checkBodyPartHealthLoss(player, 0.3);\n \n if (\n healthPercent <= 0.5 ||\n pain > 60 ||\n consciousness <= 40 ||\n balance <= 40 ||\n hasBodyPartDamage ||\n recentHits >= 4 // Additional 2 hits from SHAKEN (total 4-5 hits)\n ) {\n return CombatReadinessState.VULNERABLE;\n }\n\n // Check for SHAKEN state\n // Triggers: health <= 70%, pain > 30, consciousness <= 60, balance <= 60,\n // or 2-3 recent hits\n if (\n healthPercent <= 0.7 ||\n pain > 30 ||\n consciousness <= 60 ||\n balance <= 60 ||\n recentHits >= 2 // 2-3 hits triggers SHAKEN\n ) {\n return CombatReadinessState.SHAKEN;\n }\n\n // Default to READY state\n return CombatReadinessState.READY;\n }\n\n /**\n * Gets the capability modifiers for a specific combat state.\n * \n * Returns all modifiers that should be applied to player actions\n * based on their current combat readiness.\n * \n * @param state - Combat readiness state\n * @returns Capability modifiers for the state\n * \n * @example\n * ```typescript\n * const capability = combatStateSystem.getCapability(\n * CombatReadinessState.SHAKEN\n * );\n * \n * // Apply to damage calculation\n * const finalDamage = baseDamage * capability.accuracyModifier;\n * \n * // Check if can block\n * if (!capability.canBlock) {\n * console.log(\"Cannot block in this state!\");\n * }\n * ```\n * \n * @korean 능력조회\n */\n getCapability(state: CombatReadinessState): StateCapability {\n return this.stateCapabilities[state];\n }\n\n /**\n * Applies combat state modifiers to a player state.\n * \n * Creates a modified player state with capability reductions\n * based on current combat readiness.\n * \n * @param player - Current player state\n * @param state - Combat readiness state to apply\n * @returns Modified player state with applied modifiers\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player);\n * const modifiedPlayer = combatStateSystem.applyStateModifiers(\n * player,\n * state\n * );\n * ```\n * \n * @korean 상태적용\n */\n applyStateModifiers(\n player: PlayerState,\n state: CombatReadinessState\n ): PlayerState {\n const capability = this.getCapability(state);\n\n return {\n ...player,\n attackPower: Math.floor(player.attackPower * capability.damageModifier),\n defense: Math.floor(player.defense * capability.defenseModifier),\n speed: Math.floor(player.speed * capability.speedModifier),\n isBlocking: player.isBlocking && capability.canBlock,\n isStunned: player.isStunned || !capability.canExecuteTechniques,\n };\n }\n\n /**\n * Gets bilingual name for combat state.\n * \n * @param state - Combat readiness state\n * @returns Korean and English state names\n * \n * @korean 상태이름\n */\n getStateName(state: CombatReadinessState): { korean: string; english: string } {\n const names: Record<CombatReadinessState, { korean: string; english: string }> = {\n [CombatReadinessState.READY]: {\n korean: \"준비완료\",\n english: \"Ready\",\n },\n [CombatReadinessState.SHAKEN]: {\n korean: \"동요상태\",\n english: \"Shaken\",\n },\n [CombatReadinessState.VULNERABLE]: {\n korean: \"취약상태\",\n english: \"Vulnerable\",\n },\n [CombatReadinessState.HELPLESS]: {\n korean: \"무력상태\",\n english: \"Helpless\",\n },\n };\n\n return names[state];\n }\n\n /**\n * Gets emoji indicator for combat state.\n * \n * @param state - Combat readiness state\n * @returns Emoji representing the state\n * \n * @korean 상태아이콘\n */\n getStateEmoji(state: CombatReadinessState): string {\n const emojis: Record<CombatReadinessState, string> = {\n [CombatReadinessState.READY]: \"🟢\",\n [CombatReadinessState.SHAKEN]: \"🟡\",\n [CombatReadinessState.VULNERABLE]: \"🟠\",\n [CombatReadinessState.HELPLESS]: \"🔴\",\n };\n\n return emojis[state];\n }\n\n /**\n * Counts recent hits within a time window.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (milliseconds)\n * @param timeWindow - Time window to check in milliseconds\n * @returns Number of hits within the time window\n * \n * @private\n * @korean 최근타격횟수\n */\n private countRecentHits(\n player: PlayerState,\n currentTime: number | undefined,\n timeWindow: number\n ): number {\n if (!currentTime || !player.recentHitTimestamps) {\n return 0;\n }\n\n const cutoffTime = currentTime - timeWindow;\n return player.recentHitTimestamps.filter(\n (timestamp) => timestamp >= cutoffTime\n ).length;\n }\n\n /**\n * Checks if any body part has lost more than the specified percentage of health.\n * \n * @param player - Current player state\n * @param lossThreshold - Health loss threshold (0.0 to 1.0)\n * @returns True if any body part has lost more than the threshold\n * \n * @private\n * @korean 신체부위손상확인\n */\n private checkBodyPartHealthLoss(\n player: PlayerState,\n lossThreshold: number\n ): boolean {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return false;\n }\n\n const bodyParts: Array<keyof typeof player.bodyPartHealth> = [\n \"head\",\n \"neck\",\n \"torsoUpper\",\n \"torsoLower\",\n \"armLeft\",\n \"armRight\",\n \"legLeft\",\n \"legRight\",\n ];\n\n for (const part of bodyParts) {\n const current = player.bodyPartHealth[part];\n const max = player.bodyPartMaxHealth[part];\n const healthPercent = current / max;\n const lossPercent = 1 - healthPercent;\n\n if (lossPercent >= lossThreshold) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Records a hit on the player and updates recent hit tracking.\n * \n * Should be called by combat system when player takes a hit.\n * Maintains a rolling window of the last 10 hit timestamps.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp of the hit\n * @returns Updated player state with recorded hit\n * \n * @korean 타격기록\n */\n recordHit(player: PlayerState, currentTime: number): PlayerState {\n const recentHits = player.recentHitTimestamps ?? [];\n \n // Add new hit timestamp and keep only last 10\n const updatedHits = [...recentHits, currentTime].slice(-10);\n\n return {\n ...player,\n recentHitTimestamps: updatedHits,\n hitsTaken: player.hitsTaken + 1,\n };\n }\n\n /**\n * Updates player when entering HELPLESS state.\n * \n * Records the timestamp for recovery tracking.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp when entering helpless state\n * @returns Updated player state\n * \n * @korean 무력상태기록\n */\n enterHelplessState(player: PlayerState, currentTime: number): PlayerState {\n return {\n ...player,\n lastHelplessStateTime: currentTime,\n };\n }\n\n /**\n * Checks if player can recover from HELPLESS state.\n * \n * Recovery occurs after 5 seconds with no additional hits.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp\n * @returns True if recovery is possible\n * \n * @korean 회복가능확인\n */\n canRecoverFromHelpless(player: PlayerState, currentTime: number): boolean {\n if (!player.lastHelplessStateTime) {\n return false;\n }\n\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n\n return timeSinceHelpless >= 5000 && noRecentHits;\n }\n}\n\nexport default CombatStateSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":"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"}
|
|
@@ -179,9 +179,7 @@ var GrappleSystem = class {
|
|
|
179
179
|
case GrappleTarget.NECK:
|
|
180
180
|
strength *= 1.2;
|
|
181
181
|
break;
|
|
182
|
-
case GrappleTarget.BOTH_ARMS:
|
|
183
|
-
strength *= .7;
|
|
184
|
-
break;
|
|
182
|
+
case GrappleTarget.BOTH_ARMS: strength *= .7;
|
|
185
183
|
}
|
|
186
184
|
return Math.min(this.config.maxGripStrength, strength);
|
|
187
185
|
}
|
|
@@ -205,9 +203,7 @@ var GrappleSystem = class {
|
|
|
205
203
|
case GrappleTarget.BOTH_ARMS:
|
|
206
204
|
baseChance -= .2;
|
|
207
205
|
break;
|
|
208
|
-
case GrappleTarget.NECK:
|
|
209
|
-
baseChance -= .1;
|
|
210
|
-
break;
|
|
206
|
+
case GrappleTarget.NECK: baseChance -= .1;
|
|
211
207
|
}
|
|
212
208
|
return Math.max(.05, Math.min(.95, baseChance));
|
|
213
209
|
}
|
|
@@ -256,9 +252,7 @@ var GrappleSystem = class {
|
|
|
256
252
|
case GrappleTarget.NECK:
|
|
257
253
|
cost *= 1.5;
|
|
258
254
|
break;
|
|
259
|
-
case GrappleTarget.BOTH_ARMS:
|
|
260
|
-
cost *= 1.8;
|
|
261
|
-
break;
|
|
255
|
+
case GrappleTarget.BOTH_ARMS: cost *= 1.8;
|
|
262
256
|
}
|
|
263
257
|
const gripFactor = gripStrength / this.config.maxGripStrength;
|
|
264
258
|
cost *= .7 + gripFactor * .6;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrappleSystem.js","names":[],"sources":["../../../src/systems/combat/GrappleSystem.ts"],"sourcesContent":["/**\n * Grappling System for Korean Martial Arts Combat\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Implements realistic grappling and takedown mechanics based on:\n * - **Hapkido (합기도)**: Joint locks and control techniques\n * - **Ssireum (씨름)**: Traditional Korean wrestling\n * - **GON Stance (곤괘)**: Earth stance grounding and control\n *\n * ## Core Mechanics\n *\n * 1. **Grab Initiation**: Establish grip on opponent's limb/body\n * 2. **Control Duration**: Maintain control with stamina cost\n * 3. **Grip Strength**: Affects escape difficulty and follow-up options\n * 4. **Escape Mechanics**: Based on strength, technique, and stamina\n * 5. **Follow-up Techniques**: Throws, takedowns, joint locks from control\n *\n * ## Phase 2 Enhancement\n *\n * Enhanced with Gon (Earth) trigram metadata integration:\n * - Uses `controlDuration` from ExtendedGonTechnique\n * - Supports authentic Ssireum/Hapkido control durations\n * - Maintains backward compatibility with non-Gon techniques\n *\n * @module systems/combat/GrappleSystem\n * @category Combat System\n * @korean 잡기시스템\n */\n\nimport type { PlayerState } from \"@/systems/player\";\nimport {\n CombatState,\n GrappleControl,\n GrappleState,\n GrappleTarget,\n TrigramStance,\n} from \"@/types\";\nimport { asExtendedGonTechnique } from \"../trigram/types/GonTechniqueExtensions\";\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\n\n/**\n * Configuration for grappling mechanics.\n *\n * **Korean**: 잡기 설정 (Grapple Configuration)\n */\nexport interface GrappleConfig {\n /** Base stamina cost per second to maintain control */\n readonly baseStaminaCostPerSecond: number;\n /** Minimum grip strength to maintain control */\n readonly minGripStrength: number;\n /** Maximum grip strength value */\n readonly maxGripStrength: number;\n /** Base escape difficulty multiplier */\n readonly baseEscapeDifficulty: number;\n /** Stamina cost to attempt escape */\n readonly escapeStaminaCost: number;\n /** Minimum duration before escape is possible (ms) */\n readonly minControlDuration: number;\n}\n\n/**\n * Default grappling configuration values.\n */\nexport const DEFAULT_GRAPPLE_CONFIG: GrappleConfig = {\n baseStaminaCostPerSecond: 5,\n minGripStrength: 20,\n maxGripStrength: 100,\n baseEscapeDifficulty: 1.5,\n escapeStaminaCost: 15,\n minControlDuration: 500, // 0.5 seconds\n};\n\n/**\n * Grapple attempt result.\n *\n * **Korean**: 잡기 시도 결과 (Grapple Attempt Result)\n */\nexport interface GrappleAttemptResult {\n /** Whether the grapple was successful */\n readonly success: boolean;\n /** New grapple control state if successful */\n readonly grappleControl?: GrappleControl;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Escape attempt result.\n *\n * **Korean**: 탈출 시도 결과 (Escape Attempt Result)\n */\nexport interface EscapeAttemptResult {\n /** Whether the escape was successful */\n readonly success: boolean;\n /** Updated grapple control state */\n readonly grappleControl: GrappleControl | null;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Grappling System for control and hold mechanics.\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Manages grappling state between combatants, including grip strength,\n * control duration, escape mechanics, and follow-up techniques.\n */\nexport class GrappleSystem {\n private readonly config: GrappleConfig;\n\n constructor(config: Partial<GrappleConfig> = {}) {\n this.config = { ...DEFAULT_GRAPPLE_CONFIG, ...config };\n }\n\n /**\n * Attempt to initiate a grapple on target.\n *\n * **Korean**: 잡기 시도 (Attempt Grapple)\n *\n * @param attacker - Player attempting the grapple\n * @param defender - Target player\n * @param target - Body part to grapple\n * @param currentTime - Current game time in milliseconds\n * @returns Result of the grapple attempt\n */\n attemptGrapple(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget,\n currentTime: number\n ): GrappleAttemptResult {\n // Check if attacker can grapple\n if (attacker.combatState === CombatState.STUNNED) {\n return {\n success: false,\n reason: \"Attacker is stunned\",\n staminaCost: 0,\n };\n }\n\n // Check if attacker is already grappling someone else\n if (attacker.combatState === CombatState.GRAPPLING) {\n return {\n success: false,\n reason: \"Attacker is already grappling another opponent\",\n staminaCost: 0,\n };\n }\n\n if (attacker.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Check if defender is already being grappled\n if (defender.combatState === CombatState.GRAPPLED) {\n return {\n success: false,\n reason: \"Target is already being grappled\",\n staminaCost: 0,\n };\n }\n\n // Calculate grip strength based on attacker attributes and stance\n const gripStrength = this.calculateGripStrength(attacker, target);\n\n // Calculate success chance based on attributes\n const successChance = this.calculateGrappleSuccessChance(\n attacker,\n defender,\n target\n );\n\n const roll = Math.random();\n const success = roll < successChance;\n\n if (success) {\n // Create grapple control - start with GRABBING state during initiation\n const grappleControl: GrappleControl = {\n state: GrappleState.GRABBING,\n target,\n controllerId: attacker.id,\n targetId: defender.id,\n gripStrength,\n duration: 0,\n startTime: currentTime,\n canEscape: false, // Initially cannot escape\n staminaCostPerSecond: this.calculateStaminaCost(target, gripStrength),\n };\n\n return {\n success: true,\n grappleControl,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n return {\n success: false,\n reason: `Grapple attempt failed (${Math.round(successChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost * 0.5, // Half cost on failure\n };\n }\n\n /**\n * Update grapple control state over time.\n *\n * **Korean**: 잡기 업데이트 (Update Grapple)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed since last update (seconds)\n * @param currentTime - Current game time in milliseconds\n * @returns Updated grapple control or null if broken\n */\n updateGrapple(\n grappleControl: GrappleControl,\n controller: PlayerState,\n _target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): GrappleControl | null {\n // Check if controller can maintain control\n if (\n controller.stamina < grappleControl.staminaCostPerSecond * deltaTime ||\n controller.combatState === CombatState.STUNNED\n ) {\n // Control broken due to stamina or state\n return null;\n }\n\n // Update duration\n const newDuration = currentTime - grappleControl.startTime;\n\n // Transition from GRABBING to CONTROLLING after initial establishment\n let newState = grappleControl.state;\n if (grappleControl.state === GrappleState.GRABBING && newDuration > 0) {\n newState = GrappleState.CONTROLLING;\n }\n\n // Decay grip strength slightly over time\n const gripDecayRate = 2; // points per second\n const rawGripStrength = grappleControl.gripStrength - gripDecayRate * deltaTime;\n\n // Check if grip strength has dropped too low\n if (rawGripStrength < this.config.minGripStrength) {\n return null;\n }\n\n const newGripStrength = rawGripStrength;\n\n // Allow escape attempts after minimum duration\n const canEscape = newDuration >= this.config.minControlDuration;\n\n return {\n ...grappleControl,\n state: newState,\n gripStrength: newGripStrength,\n duration: newDuration,\n canEscape,\n };\n }\n\n /**\n * Attempt to escape from a grapple.\n *\n * **Korean**: 탈출 시도 (Attempt Escape)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player attempting to escape\n * @returns Result of the escape attempt\n */\n attemptEscape(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): EscapeAttemptResult {\n // Check if escape is possible\n if (!grappleControl.canEscape) {\n return {\n success: false,\n grappleControl,\n reason: \"Cannot escape yet\",\n staminaCost: 0,\n };\n }\n\n // Check stamina\n if (target.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n grappleControl,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Calculate escape chance\n const escapeChance = this.calculateEscapeChance(\n grappleControl,\n controller,\n target\n );\n\n const roll = Math.random();\n const success = roll < escapeChance;\n\n if (success) {\n return {\n success: true,\n grappleControl: null, // Escape successful, control broken\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n // Escape failed, weaken grip slightly\n const weakenedGripStrength = Math.max(\n this.config.minGripStrength,\n grappleControl.gripStrength - 10\n );\n\n return {\n success: false,\n grappleControl: {\n ...grappleControl,\n gripStrength: weakenedGripStrength,\n },\n reason: `Escape failed (${Math.round(escapeChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n /**\n * Calculate grip strength based on attacker attributes and target.\n *\n * **Korean**: 악력 계산 (Calculate Grip Strength)\n *\n * @private\n */\n private calculateGripStrength(\n attacker: PlayerState,\n target: GrappleTarget\n ): number {\n // Base strength from attack power\n let strength = attacker.attackPower * 3;\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n strength *= 1.3; // GON stance excels at grappling\n } else if (attacker.currentStance === TrigramStance.GAN) {\n strength *= 1.15; // GAN stance is also good for control\n }\n\n // Target modifiers\n switch (target) {\n case GrappleTarget.HAND:\n strength *= 1.1; // Easier to control hand\n break;\n case GrappleTarget.ARM:\n strength *= 1.0; // Standard difficulty\n break;\n case GrappleTarget.LEG:\n strength *= 0.9; // Harder to control leg\n break;\n case GrappleTarget.TORSO:\n strength *= 0.85; // Harder to control torso\n break;\n case GrappleTarget.NECK:\n strength *= 1.2; // High control but risky\n break;\n case GrappleTarget.BOTH_ARMS:\n strength *= 0.7; // Very hard to control both arms\n break;\n }\n\n return Math.min(this.config.maxGripStrength, strength);\n }\n\n /**\n * Calculate grapple success chance.\n *\n * **Korean**: 잡기 성공률 계산 (Calculate Grapple Success Chance)\n *\n * @private\n */\n private calculateGrappleSuccessChance(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget\n ): number {\n // Base chance from technique vs defense\n const attributeDiff = attacker.technique - defender.defense;\n let baseChance = 0.5 + attributeDiff * 0.02; // ±2% per point difference\n\n // Speed advantage\n if (attacker.speed > defender.speed) {\n baseChance += (attacker.speed - defender.speed) * 0.01;\n }\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n baseChance += 0.15; // GON stance excels at grappling\n }\n\n if (defender.combatState === CombatState.ATTACKING) {\n baseChance += 0.2; // Easier to grapple during attack\n } else if (defender.combatState === CombatState.DEFENDING) {\n baseChance -= 0.15; // Harder to grapple defensive opponent\n }\n\n // Target difficulty\n switch (target) {\n case GrappleTarget.HAND:\n baseChance += 0.1; // Easier to grab hand\n break;\n case GrappleTarget.BOTH_ARMS:\n baseChance -= 0.2; // Much harder to grab both arms\n break;\n case GrappleTarget.NECK:\n baseChance -= 0.1; // Risky target\n break;\n }\n\n // Clamp between 0.05 and 0.95\n return Math.max(0.05, Math.min(0.95, baseChance));\n }\n\n /**\n * Calculate escape chance from grapple.\n *\n * **Korean**: 탈출 성공률 계산 (Calculate Escape Chance)\n *\n * @private\n */\n private calculateEscapeChance(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): number {\n // Base chance from strength vs grip\n const gripFactor = grappleControl.gripStrength / this.config.maxGripStrength;\n let baseChance = 0.5 - gripFactor * 0.3; // Stronger grip = harder escape\n\n // Attribute comparison\n const strengthDiff = target.attackPower - controller.attackPower;\n baseChance += strengthDiff * 0.02;\n\n // Speed advantage helps escape\n if (target.speed > controller.speed) {\n baseChance += (target.speed - controller.speed) * 0.015;\n }\n\n // Technique helps find weaknesses in grip\n baseChance += target.technique * 0.005;\n\n // Stance modifiers - certain stances are better at escaping grapples\n // **Korean**: 곤(坤) 자세는 탈출에 유리 (+30%), 간(艮) 자세는 방어적인 탈출 (+15%)\n if (target.currentStance === TrigramStance.GON) {\n baseChance += 0.3; // GON stance excels at grounding and escape\n } else if (target.currentStance === TrigramStance.GAN) {\n baseChance += 0.15; // GAN stance has defensive escape advantage\n }\n\n // Time factor - longer control makes escape harder\n const durationSeconds = grappleControl.duration / 1000;\n const timePenalty = Math.min(0.2, durationSeconds * 0.02);\n baseChance -= timePenalty;\n\n // Clamp between 0.05 and 0.85\n return Math.max(0.05, Math.min(0.85, baseChance));\n }\n\n /**\n * Calculate stamina cost to maintain control.\n *\n * **Korean**: 체력 소모 계산 (Calculate Stamina Cost)\n *\n * @private\n */\n private calculateStaminaCost(\n target: GrappleTarget,\n gripStrength: number\n ): number {\n let cost = this.config.baseStaminaCostPerSecond;\n\n // Target affects stamina cost\n switch (target) {\n case GrappleTarget.HAND:\n cost *= 0.8; // Easier to maintain\n break;\n case GrappleTarget.ARM:\n cost *= 1.0; // Standard\n break;\n case GrappleTarget.LEG:\n cost *= 1.2; // Harder to maintain\n break;\n case GrappleTarget.TORSO:\n cost *= 1.3; // Much harder to maintain\n break;\n case GrappleTarget.NECK:\n cost *= 1.5; // Very demanding\n break;\n case GrappleTarget.BOTH_ARMS:\n cost *= 1.8; // Extremely demanding\n break;\n }\n\n // Higher grip strength costs more stamina\n const gripFactor = gripStrength / this.config.maxGripStrength;\n cost *= 0.7 + gripFactor * 0.6; // 70-130% based on grip\n\n return cost;\n }\n\n /**\n * Check if a grapple can be transitioned to a throw.\n *\n * **Korean**: 던지기 전환 확인 (Check Throw Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting throw\n * @returns Whether throw transition is possible\n */\n canTransitionToThrow(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 1000) {\n return false; // Need at least 1 second of control\n }\n\n // Need sufficient grip strength\n if (grappleControl.gripStrength < 60) {\n return false;\n }\n\n // Need sufficient stamina for throw\n if (controller.stamina < 20) {\n return false;\n }\n\n // Certain targets are better for throws\n const goodThrowTargets = [\n GrappleTarget.ARM,\n GrappleTarget.TORSO,\n GrappleTarget.BOTH_ARMS,\n ];\n\n return goodThrowTargets.includes(grappleControl.target);\n }\n\n /**\n * Check if a grapple can be transitioned to a joint lock.\n *\n * **Korean**: 관절기 전환 확인 (Check Joint Lock Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting lock\n * @returns Whether joint lock transition is possible\n */\n canTransitionToJointLock(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 800) {\n return false;\n }\n\n // Need high technique for joint locks\n if (controller.technique < 10) {\n return false;\n }\n\n // Joint locks work on limbs\n const lockTargets = [\n GrappleTarget.HAND,\n GrappleTarget.ARM,\n GrappleTarget.LEG,\n ];\n\n return lockTargets.includes(grappleControl.target);\n }\n\n /**\n * Get technique-specific control duration from Gon metadata.\n *\n * **Korean**: 기술별 제어 시간 조회 (Get Technique Control Duration)\n *\n * Retrieves the post-throw positional advantage duration from\n * ExtendedGonTechnique metadata. Falls back to default duration\n * if technique doesn't have Gon-specific enhancements.\n *\n * **Control Duration Philosophy**:\n * - Traditional Ssireum techniques (800-2000ms) provide longer control\n * - Aggressive slam techniques (800-1200ms) provide brief control\n * - Sacrifice throws (1500-2000ms) provide extended ground control\n *\n * **Use Cases**:\n * - Determining follow-up attack windows after throws\n * - Calculating defender recovery time before counterattack\n * - Applying post-throw positional advantage in game state\n *\n * @param technique - Korean martial arts technique (checks for ExtendedGonTechnique)\n * @param defaultDuration - Fallback duration if technique has no metadata (default: 1000ms)\n * @returns Control duration in milliseconds\n *\n * @example\n * ```typescript\n * const controlTime = grappleSystem.getTechniqueControlDuration(\n * ssireumThrowTechnique, // controlDuration: 1800\n * 1000 // default fallback\n * );\n * // Result: 1800ms (uses technique metadata)\n * ```\n *\n * @korean 기술별제어시간조회\n */\n getTechniqueControlDuration(\n technique: KoreanTechnique,\n defaultDuration: number = 1000,\n ): number {\n // Use helper function for safe type casting\n const gonTechnique = asExtendedGonTechnique(technique);\n if (!gonTechnique) {\n // Non-Gon technique: use default duration\n return defaultDuration;\n }\n\n // Extract control duration from validated Gon technique\n return gonTechnique.controlDuration;\n }\n\n /**\n * Apply post-throw control advantage window.\n *\n * **Korean**: 던지기 후 우세 적용 (Apply Post-Throw Advantage)\n *\n * Creates a control advantage state after a successful throw,\n * using the technique's `controlDuration` to determine how long\n * the attacker maintains positional dominance.\n *\n * **Advantage Effects**:\n * - Attacker gains priority for follow-up attacks\n * - Defender must wait for control duration to expire\n * - Applies to throw transitions from grapple control\n *\n * @param technique - Throw technique executed\n * @param attackerId - Player who executed throw\n * @param defenderId - Player who was thrown\n * @param currentTime - Current game time in milliseconds\n * @returns Control advantage state with duration from technique metadata\n *\n * @example\n * ```typescript\n * const advantage = grappleSystem.applyPostThrowAdvantage(\n * ssireumThrowTechnique,\n * \"player1\",\n * \"player2\",\n * Date.now()\n * );\n * // advantage.duration = 1800ms (from technique metadata)\n * ```\n *\n * @korean 던지기후우세적용\n */\n applyPostThrowAdvantage(\n technique: KoreanTechnique,\n attackerId: string,\n defenderId: string,\n currentTime: number,\n ): {\n controllerId: string;\n targetId: string;\n duration: number;\n startTime: number;\n endTime: number;\n } {\n // Get technique-specific control duration with 1200ms fallback\n const controlDuration = this.getTechniqueControlDuration(technique, 1200);\n\n return {\n controllerId: attackerId,\n targetId: defenderId,\n duration: controlDuration,\n startTime: currentTime,\n endTime: currentTime + controlDuration,\n };\n }\n}\n\nexport default GrappleSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrappleSystem.js","names":[],"sources":["../../../src/systems/combat/GrappleSystem.ts"],"sourcesContent":["/**\n * Grappling System for Korean Martial Arts Combat\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Implements realistic grappling and takedown mechanics based on:\n * - **Hapkido (합기도)**: Joint locks and control techniques\n * - **Ssireum (씨름)**: Traditional Korean wrestling\n * - **GON Stance (곤괘)**: Earth stance grounding and control\n *\n * ## Core Mechanics\n *\n * 1. **Grab Initiation**: Establish grip on opponent's limb/body\n * 2. **Control Duration**: Maintain control with stamina cost\n * 3. **Grip Strength**: Affects escape difficulty and follow-up options\n * 4. **Escape Mechanics**: Based on strength, technique, and stamina\n * 5. **Follow-up Techniques**: Throws, takedowns, joint locks from control\n *\n * ## Phase 2 Enhancement\n *\n * Enhanced with Gon (Earth) trigram metadata integration:\n * - Uses `controlDuration` from ExtendedGonTechnique\n * - Supports authentic Ssireum/Hapkido control durations\n * - Maintains backward compatibility with non-Gon techniques\n *\n * @module systems/combat/GrappleSystem\n * @category Combat System\n * @korean 잡기시스템\n */\n\nimport type { PlayerState } from \"@/systems/player\";\nimport {\n CombatState,\n GrappleControl,\n GrappleState,\n GrappleTarget,\n TrigramStance,\n} from \"@/types\";\nimport { asExtendedGonTechnique } from \"../trigram/types/GonTechniqueExtensions\";\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\n\n/**\n * Configuration for grappling mechanics.\n *\n * **Korean**: 잡기 설정 (Grapple Configuration)\n */\nexport interface GrappleConfig {\n /** Base stamina cost per second to maintain control */\n readonly baseStaminaCostPerSecond: number;\n /** Minimum grip strength to maintain control */\n readonly minGripStrength: number;\n /** Maximum grip strength value */\n readonly maxGripStrength: number;\n /** Base escape difficulty multiplier */\n readonly baseEscapeDifficulty: number;\n /** Stamina cost to attempt escape */\n readonly escapeStaminaCost: number;\n /** Minimum duration before escape is possible (ms) */\n readonly minControlDuration: number;\n}\n\n/**\n * Default grappling configuration values.\n */\nexport const DEFAULT_GRAPPLE_CONFIG: GrappleConfig = {\n baseStaminaCostPerSecond: 5,\n minGripStrength: 20,\n maxGripStrength: 100,\n baseEscapeDifficulty: 1.5,\n escapeStaminaCost: 15,\n minControlDuration: 500, // 0.5 seconds\n};\n\n/**\n * Grapple attempt result.\n *\n * **Korean**: 잡기 시도 결과 (Grapple Attempt Result)\n */\nexport interface GrappleAttemptResult {\n /** Whether the grapple was successful */\n readonly success: boolean;\n /** New grapple control state if successful */\n readonly grappleControl?: GrappleControl;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Escape attempt result.\n *\n * **Korean**: 탈출 시도 결과 (Escape Attempt Result)\n */\nexport interface EscapeAttemptResult {\n /** Whether the escape was successful */\n readonly success: boolean;\n /** Updated grapple control state */\n readonly grappleControl: GrappleControl | null;\n /** Reason for failure if unsuccessful */\n readonly reason?: string;\n /** Stamina cost for the attempt */\n readonly staminaCost: number;\n}\n\n/**\n * Grappling System for control and hold mechanics.\n *\n * **Korean**: 잡기 시스템 (Grapple System)\n *\n * Manages grappling state between combatants, including grip strength,\n * control duration, escape mechanics, and follow-up techniques.\n */\nexport class GrappleSystem {\n private readonly config: GrappleConfig;\n\n constructor(config: Partial<GrappleConfig> = {}) {\n this.config = { ...DEFAULT_GRAPPLE_CONFIG, ...config };\n }\n\n /**\n * Attempt to initiate a grapple on target.\n *\n * **Korean**: 잡기 시도 (Attempt Grapple)\n *\n * @param attacker - Player attempting the grapple\n * @param defender - Target player\n * @param target - Body part to grapple\n * @param currentTime - Current game time in milliseconds\n * @returns Result of the grapple attempt\n */\n attemptGrapple(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget,\n currentTime: number\n ): GrappleAttemptResult {\n // Check if attacker can grapple\n if (attacker.combatState === CombatState.STUNNED) {\n return {\n success: false,\n reason: \"Attacker is stunned\",\n staminaCost: 0,\n };\n }\n\n // Check if attacker is already grappling someone else\n if (attacker.combatState === CombatState.GRAPPLING) {\n return {\n success: false,\n reason: \"Attacker is already grappling another opponent\",\n staminaCost: 0,\n };\n }\n\n if (attacker.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Check if defender is already being grappled\n if (defender.combatState === CombatState.GRAPPLED) {\n return {\n success: false,\n reason: \"Target is already being grappled\",\n staminaCost: 0,\n };\n }\n\n // Calculate grip strength based on attacker attributes and stance\n const gripStrength = this.calculateGripStrength(attacker, target);\n\n // Calculate success chance based on attributes\n const successChance = this.calculateGrappleSuccessChance(\n attacker,\n defender,\n target\n );\n\n const roll = Math.random();\n const success = roll < successChance;\n\n if (success) {\n // Create grapple control - start with GRABBING state during initiation\n const grappleControl: GrappleControl = {\n state: GrappleState.GRABBING,\n target,\n controllerId: attacker.id,\n targetId: defender.id,\n gripStrength,\n duration: 0,\n startTime: currentTime,\n canEscape: false, // Initially cannot escape\n staminaCostPerSecond: this.calculateStaminaCost(target, gripStrength),\n };\n\n return {\n success: true,\n grappleControl,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n return {\n success: false,\n reason: `Grapple attempt failed (${Math.round(successChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost * 0.5, // Half cost on failure\n };\n }\n\n /**\n * Update grapple control state over time.\n *\n * **Korean**: 잡기 업데이트 (Update Grapple)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player being controlled\n * @param deltaTime - Time elapsed since last update (seconds)\n * @param currentTime - Current game time in milliseconds\n * @returns Updated grapple control or null if broken\n */\n updateGrapple(\n grappleControl: GrappleControl,\n controller: PlayerState,\n _target: PlayerState,\n deltaTime: number,\n currentTime: number\n ): GrappleControl | null {\n // Check if controller can maintain control\n if (\n controller.stamina < grappleControl.staminaCostPerSecond * deltaTime ||\n controller.combatState === CombatState.STUNNED\n ) {\n // Control broken due to stamina or state\n return null;\n }\n\n // Update duration\n const newDuration = currentTime - grappleControl.startTime;\n\n // Transition from GRABBING to CONTROLLING after initial establishment\n let newState = grappleControl.state;\n if (grappleControl.state === GrappleState.GRABBING && newDuration > 0) {\n newState = GrappleState.CONTROLLING;\n }\n\n // Decay grip strength slightly over time\n const gripDecayRate = 2; // points per second\n const rawGripStrength = grappleControl.gripStrength - gripDecayRate * deltaTime;\n\n // Check if grip strength has dropped too low\n if (rawGripStrength < this.config.minGripStrength) {\n return null;\n }\n\n const newGripStrength = rawGripStrength;\n\n // Allow escape attempts after minimum duration\n const canEscape = newDuration >= this.config.minControlDuration;\n\n return {\n ...grappleControl,\n state: newState,\n gripStrength: newGripStrength,\n duration: newDuration,\n canEscape,\n };\n }\n\n /**\n * Attempt to escape from a grapple.\n *\n * **Korean**: 탈출 시도 (Attempt Escape)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player maintaining control\n * @param target - Player attempting to escape\n * @returns Result of the escape attempt\n */\n attemptEscape(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): EscapeAttemptResult {\n // Check if escape is possible\n if (!grappleControl.canEscape) {\n return {\n success: false,\n grappleControl,\n reason: \"Cannot escape yet\",\n staminaCost: 0,\n };\n }\n\n // Check stamina\n if (target.stamina < this.config.escapeStaminaCost) {\n return {\n success: false,\n grappleControl,\n reason: \"Insufficient stamina\",\n staminaCost: 0,\n };\n }\n\n // Calculate escape chance\n const escapeChance = this.calculateEscapeChance(\n grappleControl,\n controller,\n target\n );\n\n const roll = Math.random();\n const success = roll < escapeChance;\n\n if (success) {\n return {\n success: true,\n grappleControl: null, // Escape successful, control broken\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n // Escape failed, weaken grip slightly\n const weakenedGripStrength = Math.max(\n this.config.minGripStrength,\n grappleControl.gripStrength - 10\n );\n\n return {\n success: false,\n grappleControl: {\n ...grappleControl,\n gripStrength: weakenedGripStrength,\n },\n reason: `Escape failed (${Math.round(escapeChance * 100)}% chance)`,\n staminaCost: this.config.escapeStaminaCost,\n };\n }\n\n /**\n * Calculate grip strength based on attacker attributes and target.\n *\n * **Korean**: 악력 계산 (Calculate Grip Strength)\n *\n * @private\n */\n private calculateGripStrength(\n attacker: PlayerState,\n target: GrappleTarget\n ): number {\n // Base strength from attack power\n let strength = attacker.attackPower * 3;\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n strength *= 1.3; // GON stance excels at grappling\n } else if (attacker.currentStance === TrigramStance.GAN) {\n strength *= 1.15; // GAN stance is also good for control\n }\n\n // Target modifiers\n switch (target) {\n case GrappleTarget.HAND:\n strength *= 1.1; // Easier to control hand\n break;\n case GrappleTarget.ARM:\n strength *= 1.0; // Standard difficulty\n break;\n case GrappleTarget.LEG:\n strength *= 0.9; // Harder to control leg\n break;\n case GrappleTarget.TORSO:\n strength *= 0.85; // Harder to control torso\n break;\n case GrappleTarget.NECK:\n strength *= 1.2; // High control but risky\n break;\n case GrappleTarget.BOTH_ARMS:\n strength *= 0.7; // Very hard to control both arms\n break;\n }\n\n return Math.min(this.config.maxGripStrength, strength);\n }\n\n /**\n * Calculate grapple success chance.\n *\n * **Korean**: 잡기 성공률 계산 (Calculate Grapple Success Chance)\n *\n * @private\n */\n private calculateGrappleSuccessChance(\n attacker: PlayerState,\n defender: PlayerState,\n target: GrappleTarget\n ): number {\n // Base chance from technique vs defense\n const attributeDiff = attacker.technique - defender.defense;\n let baseChance = 0.5 + attributeDiff * 0.02; // ±2% per point difference\n\n // Speed advantage\n if (attacker.speed > defender.speed) {\n baseChance += (attacker.speed - defender.speed) * 0.01;\n }\n\n // Stance modifiers\n if (attacker.currentStance === TrigramStance.GON) {\n baseChance += 0.15; // GON stance excels at grappling\n }\n\n if (defender.combatState === CombatState.ATTACKING) {\n baseChance += 0.2; // Easier to grapple during attack\n } else if (defender.combatState === CombatState.DEFENDING) {\n baseChance -= 0.15; // Harder to grapple defensive opponent\n }\n\n // Target difficulty\n switch (target) {\n case GrappleTarget.HAND:\n baseChance += 0.1; // Easier to grab hand\n break;\n case GrappleTarget.BOTH_ARMS:\n baseChance -= 0.2; // Much harder to grab both arms\n break;\n case GrappleTarget.NECK:\n baseChance -= 0.1; // Risky target\n break;\n }\n\n // Clamp between 0.05 and 0.95\n return Math.max(0.05, Math.min(0.95, baseChance));\n }\n\n /**\n * Calculate escape chance from grapple.\n *\n * **Korean**: 탈출 성공률 계산 (Calculate Escape Chance)\n *\n * @private\n */\n private calculateEscapeChance(\n grappleControl: GrappleControl,\n controller: PlayerState,\n target: PlayerState\n ): number {\n // Base chance from strength vs grip\n const gripFactor = grappleControl.gripStrength / this.config.maxGripStrength;\n let baseChance = 0.5 - gripFactor * 0.3; // Stronger grip = harder escape\n\n // Attribute comparison\n const strengthDiff = target.attackPower - controller.attackPower;\n baseChance += strengthDiff * 0.02;\n\n // Speed advantage helps escape\n if (target.speed > controller.speed) {\n baseChance += (target.speed - controller.speed) * 0.015;\n }\n\n // Technique helps find weaknesses in grip\n baseChance += target.technique * 0.005;\n\n // Stance modifiers - certain stances are better at escaping grapples\n // **Korean**: 곤(坤) 자세는 탈출에 유리 (+30%), 간(艮) 자세는 방어적인 탈출 (+15%)\n if (target.currentStance === TrigramStance.GON) {\n baseChance += 0.3; // GON stance excels at grounding and escape\n } else if (target.currentStance === TrigramStance.GAN) {\n baseChance += 0.15; // GAN stance has defensive escape advantage\n }\n\n // Time factor - longer control makes escape harder\n const durationSeconds = grappleControl.duration / 1000;\n const timePenalty = Math.min(0.2, durationSeconds * 0.02);\n baseChance -= timePenalty;\n\n // Clamp between 0.05 and 0.85\n return Math.max(0.05, Math.min(0.85, baseChance));\n }\n\n /**\n * Calculate stamina cost to maintain control.\n *\n * **Korean**: 체력 소모 계산 (Calculate Stamina Cost)\n *\n * @private\n */\n private calculateStaminaCost(\n target: GrappleTarget,\n gripStrength: number\n ): number {\n let cost = this.config.baseStaminaCostPerSecond;\n\n // Target affects stamina cost\n switch (target) {\n case GrappleTarget.HAND:\n cost *= 0.8; // Easier to maintain\n break;\n case GrappleTarget.ARM:\n cost *= 1.0; // Standard\n break;\n case GrappleTarget.LEG:\n cost *= 1.2; // Harder to maintain\n break;\n case GrappleTarget.TORSO:\n cost *= 1.3; // Much harder to maintain\n break;\n case GrappleTarget.NECK:\n cost *= 1.5; // Very demanding\n break;\n case GrappleTarget.BOTH_ARMS:\n cost *= 1.8; // Extremely demanding\n break;\n }\n\n // Higher grip strength costs more stamina\n const gripFactor = gripStrength / this.config.maxGripStrength;\n cost *= 0.7 + gripFactor * 0.6; // 70-130% based on grip\n\n return cost;\n }\n\n /**\n * Check if a grapple can be transitioned to a throw.\n *\n * **Korean**: 던지기 전환 확인 (Check Throw Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting throw\n * @returns Whether throw transition is possible\n */\n canTransitionToThrow(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 1000) {\n return false; // Need at least 1 second of control\n }\n\n // Need sufficient grip strength\n if (grappleControl.gripStrength < 60) {\n return false;\n }\n\n // Need sufficient stamina for throw\n if (controller.stamina < 20) {\n return false;\n }\n\n // Certain targets are better for throws\n const goodThrowTargets = [\n GrappleTarget.ARM,\n GrappleTarget.TORSO,\n GrappleTarget.BOTH_ARMS,\n ];\n\n return goodThrowTargets.includes(grappleControl.target);\n }\n\n /**\n * Check if a grapple can be transitioned to a joint lock.\n *\n * **Korean**: 관절기 전환 확인 (Check Joint Lock Transition)\n *\n * @param grappleControl - Current grapple control state\n * @param controller - Player attempting lock\n * @returns Whether joint lock transition is possible\n */\n canTransitionToJointLock(\n grappleControl: GrappleControl,\n controller: PlayerState\n ): boolean {\n // Need sufficient control duration\n if (grappleControl.duration < 800) {\n return false;\n }\n\n // Need high technique for joint locks\n if (controller.technique < 10) {\n return false;\n }\n\n // Joint locks work on limbs\n const lockTargets = [\n GrappleTarget.HAND,\n GrappleTarget.ARM,\n GrappleTarget.LEG,\n ];\n\n return lockTargets.includes(grappleControl.target);\n }\n\n /**\n * Get technique-specific control duration from Gon metadata.\n *\n * **Korean**: 기술별 제어 시간 조회 (Get Technique Control Duration)\n *\n * Retrieves the post-throw positional advantage duration from\n * ExtendedGonTechnique metadata. Falls back to default duration\n * if technique doesn't have Gon-specific enhancements.\n *\n * **Control Duration Philosophy**:\n * - Traditional Ssireum techniques (800-2000ms) provide longer control\n * - Aggressive slam techniques (800-1200ms) provide brief control\n * - Sacrifice throws (1500-2000ms) provide extended ground control\n *\n * **Use Cases**:\n * - Determining follow-up attack windows after throws\n * - Calculating defender recovery time before counterattack\n * - Applying post-throw positional advantage in game state\n *\n * @param technique - Korean martial arts technique (checks for ExtendedGonTechnique)\n * @param defaultDuration - Fallback duration if technique has no metadata (default: 1000ms)\n * @returns Control duration in milliseconds\n *\n * @example\n * ```typescript\n * const controlTime = grappleSystem.getTechniqueControlDuration(\n * ssireumThrowTechnique, // controlDuration: 1800\n * 1000 // default fallback\n * );\n * // Result: 1800ms (uses technique metadata)\n * ```\n *\n * @korean 기술별제어시간조회\n */\n getTechniqueControlDuration(\n technique: KoreanTechnique,\n defaultDuration: number = 1000,\n ): number {\n // Use helper function for safe type casting\n const gonTechnique = asExtendedGonTechnique(technique);\n if (!gonTechnique) {\n // Non-Gon technique: use default duration\n return defaultDuration;\n }\n\n // Extract control duration from validated Gon technique\n return gonTechnique.controlDuration;\n }\n\n /**\n * Apply post-throw control advantage window.\n *\n * **Korean**: 던지기 후 우세 적용 (Apply Post-Throw Advantage)\n *\n * Creates a control advantage state after a successful throw,\n * using the technique's `controlDuration` to determine how long\n * the attacker maintains positional dominance.\n *\n * **Advantage Effects**:\n * - Attacker gains priority for follow-up attacks\n * - Defender must wait for control duration to expire\n * - Applies to throw transitions from grapple control\n *\n * @param technique - Throw technique executed\n * @param attackerId - Player who executed throw\n * @param defenderId - Player who was thrown\n * @param currentTime - Current game time in milliseconds\n * @returns Control advantage state with duration from technique metadata\n *\n * @example\n * ```typescript\n * const advantage = grappleSystem.applyPostThrowAdvantage(\n * ssireumThrowTechnique,\n * \"player1\",\n * \"player2\",\n * Date.now()\n * );\n * // advantage.duration = 1800ms (from technique metadata)\n * ```\n *\n * @korean 던지기후우세적용\n */\n applyPostThrowAdvantage(\n technique: KoreanTechnique,\n attackerId: string,\n defenderId: string,\n currentTime: number,\n ): {\n controllerId: string;\n targetId: string;\n duration: number;\n startTime: number;\n endTime: number;\n } {\n // Get technique-specific control duration with 1200ms fallback\n const controlDuration = this.getTechniqueControlDuration(technique, 1200);\n\n return {\n controllerId: attackerId,\n targetId: defenderId,\n duration: controlDuration,\n startTime: currentTime,\n endTime: currentTime + controlDuration,\n };\n }\n}\n\nexport default GrappleSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LimbExposureSystem.js","names":[],"sources":["../../../src/systems/combat/LimbExposureSystem.ts"],"sourcesContent":["/**\n * Limb Exposure System for Counter-Attack Detection\n *\n * **Korean**: 사지 노출 시스템 (Saji Nochul System)\n *\n * Analyzes technique animations to determine when limbs become exposed and vulnerable\n * during attack execution. This enables:\n * - Defensive counter-attacks (반격 - banggyeok)\n * - Breaking techniques (파쇄기 - paswaegi)\n * - Exploiting overextension (과다신장 약점 - gwada sinjang yakjeom)\n *\n * **Martial Arts Philosophy**:\n * - **허점공격** (Heojeom Gonggyeok) - Attacking openings/weaknesses\n * - **후수필승** (Husu Pilseung) - Victory through counter-attack timing\n * - **이순응변** (Isun Eungbyeon) - Adapt and exploit opponent's mistakes\n *\n * @module systems/combat/LimbExposureSystem\n * @korean 사지노출시스템\n */\n\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\nimport type {\n BreakingResult,\n BreakingTarget,\n CounterOpportunity,\n ExposedLimbType,\n LimbExposureWindow,\n PhysicalReachConfig,\n} from \"../../types/physics\";\nimport { BREAKING_STATUS_EFFECT_IDS } from \"./BreakingStatusEffects\";\n\n/**\n * Breaking technique constants\n * **Korean**: 파쇄기술 상수\n */\n\n/**\n * Minimum effective force required to successfully execute a breaking technique.\n * Force below this threshold will not break joints/bones.\n * Typical damage values range from 20-80, with 40 being the minimum for effective breaks.\n * @korean 파쇄최소힘\n */\nconst BREAKING_FORCE_THRESHOLD = 40;\n\n/**\n * Maximum effective force for calculating break severity on 0-1 scale.\n * Force values above 80 are clamped to 1.0 severity (complete break).\n * @korean 파쇄최대힘\n */\nconst BREAKING_MAX_FORCE = 80;\n\n/**\n * Maximum range in meters for executing counter-attacks.\n * Counter-attacks are close-range defensive techniques requiring proximity.\n * @korean 반격최대거리\n */\nconst MAX_COUNTER_RANGE_METERS = 1.0;\n\n/**\n * Calculate counter-attack opportunities from a technique's reach configuration.\n *\n * **Korean**: 반격 기회 계산\n *\n * Analyzes the technique's execution timeline and limb exposure to identify\n * windows where the opponent is vulnerable to counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Counter opportunity if one exists, undefined otherwise\n *\n * @korean 반격기회계산\n */\nexport function calculateCounterOpportunity(\n technique: KoreanTechnique,\n currentTime: number\n): CounterOpportunity | undefined {\n const { reachConfig, executionTime } = technique;\n\n // No exposure window defined, no counter opportunity\n if (!reachConfig.exposureWindow) {\n return undefined;\n }\n\n const exposure = reachConfig.exposureWindow;\n\n // Calculate absolute timing of exposure window\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Check if current time is within the exposure window\n if (currentTime < windowStart || currentTime > windowEnd) {\n return undefined;\n }\n\n // Determine recommended counter techniques based on exposed limb\n const recommendedCounters = getRecommendedCounters(\n exposure.exposedLimb,\n reachConfig,\n exposure.allowsBreaking\n );\n\n return {\n exposedLimb: exposure.exposedLimb,\n windowStart,\n windowDuration: exposure.duration,\n vulnerabilityMultiplier: exposure.vulnerabilityMultiplier,\n allowsBreaking: exposure.allowsBreaking,\n recommendedCounters,\n };\n}\n\n/**\n * Get recommended counter-technique IDs for an exposed limb.\n *\n * **Korean**: 추천 반격 기술 조회\n *\n * Returns technique IDs that would be effective against the exposed limb.\n * These IDs are string-based and should match techniques in the game's\n * technique library. When implementing, validate these IDs exist.\n *\n * **Note**: These are example/placeholder IDs. Actual implementation should\n * reference real technique definitions or use a typed enum of technique IDs.\n *\n * @param exposedLimb - The limb that is exposed\n * @param reachConfig - Reach configuration of the attacking technique\n * @param allowsBreaking - Whether breaking techniques are viable\n * @returns Array of recommended counter-technique IDs (unvalidated strings)\n *\n * @internal\n * @korean 추천반격기술조회\n */\nfunction getRecommendedCounters(\n exposedLimb: ExposedLimbType,\n reachConfig: PhysicalReachConfig,\n allowsBreaking: boolean\n): readonly string[] {\n const counters: string[] = [];\n\n // Breaking techniques for overextended limbs\n if (allowsBreaking) {\n if (exposedLimb.includes(\"leg\") || exposedLimb.includes(\"ankle\")) {\n counters.push(\"leg_sweep\", \"ankle_break\", \"knee_stomp\");\n } else if (exposedLimb.includes(\"knee\")) {\n counters.push(\"knee_break\", \"hyperextension_kick\");\n } else if (exposedLimb.includes(\"arm\") || exposedLimb.includes(\"elbow\")) {\n counters.push(\"arm_bar\", \"elbow_break\", \"joint_lock\");\n } else if (exposedLimb.includes(\"wrist\")) {\n counters.push(\"wrist_lock\", \"wrist_break\");\n }\n }\n\n // Vital point strikes on exposed limbs\n if (exposedLimb.includes(\"arm\")) {\n counters.push(\"ulnar_nerve_strike\", \"radial_nerve_pressure\");\n } else if (exposedLimb.includes(\"leg\")) {\n counters.push(\"peroneal_nerve_strike\", \"thigh_pressure_point\");\n }\n\n // Counter-strikes based on body part type\n if (reachConfig.bodyPart === \"leg\") {\n counters.push(\"leg_parry_counter\", \"inside_leg_kick\");\n } else if (reachConfig.bodyPart === \"arm\") {\n counters.push(\"arm_trap_counter\", \"redirect_punch\");\n }\n\n return counters;\n}\n\n/**\n * Calculate vulnerability multiplier for a technique at a specific time.\n *\n * **Korean**: 취약성 배수 계산\n *\n * Determines how vulnerable the attacker is at a given point in their\n * technique execution. Useful for damage calculation during counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Vulnerability multiplier (1.0 = normal, >1.0 = more vulnerable)\n *\n * @korean 취약성배수계산\n */\nexport function calculateVulnerabilityMultiplier(\n technique: KoreanTechnique,\n currentTime: number\n): number {\n const { reachConfig, executionTime, recoveryTime } = technique;\n\n // No exposure window, return baseline vulnerability\n if (!reachConfig.exposureWindow) {\n return 1.0;\n }\n\n const exposure = reachConfig.exposureWindow;\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Before exposure window: slight vulnerability during wind-up\n if (currentTime < windowStart) {\n return 1.1;\n }\n\n // Within exposure window: maximum vulnerability\n if (currentTime <= windowEnd) {\n return exposure.vulnerabilityMultiplier;\n }\n\n // Recovery phase: moderate vulnerability\n const recoveryStart = executionTime;\n const recoveryEnd = executionTime + recoveryTime;\n if (currentTime > recoveryStart && currentTime <= recoveryEnd) {\n const recoveryProgress = (currentTime - recoveryStart) / recoveryTime;\n // Linearly decrease from max vulnerability to baseline\n return (\n exposure.vulnerabilityMultiplier -\n (exposure.vulnerabilityMultiplier - 1.0) * recoveryProgress\n );\n }\n\n // After recovery: baseline vulnerability\n return 1.0;\n}\n\n/**\n * Determine which limb is exposed based on technique and animation state.\n *\n * **Korean**: 노출된 사지 결정\n *\n * Analyzes the technique's body part and animation to determine which\n * specific limb (left/right) is currently exposed.\n *\n * @param technique - The technique being executed\n * @param isLeftSided - Whether the technique uses the left side\n * @returns The exposed limb type\n *\n * @korean 노출사지결정\n */\nexport function determineExposedLimb(\n technique: KoreanTechnique,\n isLeftSided: boolean = false\n): ExposedLimbType {\n const { reachConfig } = technique;\n\n // If exposure window explicitly defines the limb, use it\n if (reachConfig.exposureWindow?.exposedLimb) {\n return reachConfig.exposureWindow.exposedLimb;\n }\n\n // Otherwise, infer from body part and sidedness\n const side = isLeftSided ? \"left\" : \"right\";\n\n switch (reachConfig.bodyPart) {\n case \"arm\":\n // Determine joint based on reach extension\n if (reachConfig.baseExtension > 0.8) {\n return `${side}_arm` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.5) {\n return `${side}_elbow` as ExposedLimbType;\n } else {\n return `${side}_wrist` as ExposedLimbType;\n }\n\n case \"leg\":\n // Kicks expose different parts based on extension\n if (reachConfig.baseExtension > 1.0) {\n return `${side}_leg` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.7) {\n return `${side}_knee` as ExposedLimbType;\n } else {\n return `${side}_ankle` as ExposedLimbType;\n }\n\n case \"torso\":\n // Torso techniques typically expose arms\n return `${side}_arm` as ExposedLimbType;\n\n default:\n return \"right_arm\";\n }\n}\n\n/**\n * Map exposed limb to breaking technique target.\n *\n * **Korean**: 노출 사지를 파쇄 목표로 매핑\n *\n * Converts an exposed limb type to the appropriate breaking target\n * for joint and limb breaking techniques.\n *\n * @param exposedLimb - The exposed limb\n * @returns The breaking target joint/bone\n *\n * @korean 파쇄목표매핑\n */\nexport function mapLimbToBreakingTarget(\n exposedLimb: ExposedLimbType\n): BreakingTarget {\n if (exposedLimb.includes(\"ankle\")) return \"ankle\";\n if (exposedLimb.includes(\"knee\")) return \"knee\";\n if (exposedLimb.includes(\"elbow\")) return \"elbow\";\n if (exposedLimb.includes(\"wrist\")) return \"wrist\";\n\n // Default mapping for general limbs\n if (exposedLimb.includes(\"leg\")) return \"knee\";\n if (exposedLimb.includes(\"arm\")) return \"elbow\";\n\n return \"elbow\"; // Default fallback\n}\n\n/**\n * Calculate breaking technique effectiveness.\n *\n * **Korean**: 파쇄 기술 효과 계산\n *\n * Determines the result of a breaking technique applied to an exposed limb,\n * considering the vulnerability window, force applied, and target joint.\n *\n * @param breakingTechnique - The breaking technique being applied (reserved for future use)\n * @param counterOpportunity - The counter opportunity being exploited\n * @param force - Force/damage of the breaking technique (typically 20-80 range)\n * @returns Result of the breaking attempt\n *\n * @remarks\n * The breakingTechnique parameter is currently unused but reserved for future\n * extensibility. It may be used to apply technique-specific modifiers or\n * validate technique compatibility with the breaking target.\n *\n * @korean 파쇄기술효과계산\n */\nexport function calculateBreakingResult(\n breakingTechnique: KoreanTechnique,\n counterOpportunity: CounterOpportunity,\n force: number\n): BreakingResult {\n // Note: breakingTechnique parameter reserved for future use\n // (e.g., technique-specific breaking modifiers)\n void breakingTechnique;\n const target = mapLimbToBreakingTarget(counterOpportunity.exposedLimb);\n\n // Breaking only works during valid windows\n if (!counterOpportunity.allowsBreaking) {\n return {\n success: false,\n target,\n severity: 0,\n damage: force * 0.5, // Reduced damage without breaking\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Calculate breaking severity based on force and vulnerability\n const effectiveForce = force * counterOpportunity.vulnerabilityMultiplier;\n const baseSuccess = effectiveForce > BREAKING_FORCE_THRESHOLD;\n const severity = Math.min(effectiveForce / BREAKING_MAX_FORCE, 1.0);\n\n if (!baseSuccess) {\n return {\n success: false,\n target,\n severity: 0,\n damage: effectiveForce * 0.7,\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Successful break - apply appropriate effects\n // Status effect IDs from BREAKING_STATUS_EFFECT_IDS constants\n // See src/systems/combat/BreakingStatusEffects.ts for all available IDs\n const statusEffects: string[] = [BREAKING_STATUS_EFFECT_IDS.PAIN];\n let mobilityReduction: number;\n\n // Severity-based effects\n if (severity > 0.8) {\n // Severe break - dislocation or fracture\n statusEffects.push(\n BREAKING_STATUS_EFFECT_IDS.SEVERE_INJURY,\n BREAKING_STATUS_EFFECT_IDS.DISABLED_LIMB\n );\n mobilityReduction = 0.6;\n\n if (target === \"ankle\" || target === \"knee\") {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.IMPAIRED_MOBILITY);\n mobilityReduction = 0.8; // Leg breaks severely impact movement\n }\n } else if (severity > 0.5) {\n // Moderate break\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.INJURED_LIMB);\n mobilityReduction = 0.4;\n } else {\n // Minor break - sprain or strain\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.SPRAINED_JOINT);\n mobilityReduction = 0.2;\n }\n\n // Bleeding for bone breaks\n if (severity > 0.6) {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.BLEEDING);\n }\n\n return {\n success: true,\n target,\n severity,\n damage: effectiveForce,\n mobilityReduction,\n statusEffects,\n };\n}\n\n/**\n * Check if a defender can execute a counter-attack.\n *\n * **Korean**: 반격 실행 가능 여부 확인\n *\n * Validates whether a defender is in position and has the resources\n * to execute a counter-attack against an exposed limb.\n *\n * @param defenderStamina - Defender's current stamina\n * @param counterTechnique - The counter technique to execute\n * @param distance - Distance between attacker and defender (meters)\n * @returns Whether the counter can be executed\n *\n * @korean 반격실행가능확인\n */\nexport function canExecuteCounter(\n defenderStamina: number,\n counterTechnique: KoreanTechnique,\n distance: number\n): boolean {\n // Check stamina requirement\n if (defenderStamina < counterTechnique.staminaCost) {\n return false;\n }\n\n // Counter must be executable at current distance\n if (distance > MAX_COUNTER_RANGE_METERS) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Generate limb exposure window for a technique.\n *\n * **Korean**: 사지 노출 시간 생성\n *\n * Creates a default limb exposure window for techniques that don't\n * explicitly define one, based on heuristics from the technique type\n * and reach configuration.\n *\n * @param technique - The technique to generate exposure for\n * @returns Generated limb exposure window\n *\n * @korean 사지노출시간생성\n */\nexport function generateLimbExposureWindow(\n technique: KoreanTechnique\n): LimbExposureWindow {\n const { reachConfig, executionTime } = technique;\n const exposedLimb = determineExposedLimb(technique);\n\n // High extension = high vulnerability\n const isHighExtension = reachConfig.baseExtension > 1.0;\n const isMediumExtension = reachConfig.baseExtension > 0.8;\n\n // Determine vulnerability based on extension\n let vulnerabilityMultiplier: number;\n let allowsBreaking: boolean;\n\n if (isHighExtension) {\n vulnerabilityMultiplier = 2.2; // Very vulnerable\n allowsBreaking = true;\n } else if (isMediumExtension) {\n vulnerabilityMultiplier = 1.6;\n allowsBreaking = false;\n } else {\n vulnerabilityMultiplier = 1.2; // Low extension, low vulnerability\n allowsBreaking = false;\n }\n\n // Exposure starts during peak extension (typically 40-60% of execution)\n const startTime = 0.5;\n\n // Duration scales with execution time (20-40% of execution)\n const durationRatio = isHighExtension ? 0.4 : 0.25;\n const duration = Math.floor(executionTime * durationRatio);\n\n return {\n exposedLimb,\n startTime,\n duration,\n vulnerabilityMultiplier,\n allowsBreaking,\n };\n}\n\nexport default {\n calculateCounterOpportunity,\n calculateVulnerabilityMultiplier,\n determineExposedLimb,\n mapLimbToBreakingTarget,\n calculateBreakingResult,\n canExecuteCounter,\n generateLimbExposureWindow,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LimbExposureSystem.js","names":[],"sources":["../../../src/systems/combat/LimbExposureSystem.ts"],"sourcesContent":["/**\n * Limb Exposure System for Counter-Attack Detection\n *\n * **Korean**: 사지 노출 시스템 (Saji Nochul System)\n *\n * Analyzes technique animations to determine when limbs become exposed and vulnerable\n * during attack execution. This enables:\n * - Defensive counter-attacks (반격 - banggyeok)\n * - Breaking techniques (파쇄기 - paswaegi)\n * - Exploiting overextension (과다신장 약점 - gwada sinjang yakjeom)\n *\n * **Martial Arts Philosophy**:\n * - **허점공격** (Heojeom Gonggyeok) - Attacking openings/weaknesses\n * - **후수필승** (Husu Pilseung) - Victory through counter-attack timing\n * - **이순응변** (Isun Eungbyeon) - Adapt and exploit opponent's mistakes\n *\n * @module systems/combat/LimbExposureSystem\n * @korean 사지노출시스템\n */\n\nimport type { KoreanTechnique } from \"../vitalpoint/types\";\nimport type {\n BreakingResult,\n BreakingTarget,\n CounterOpportunity,\n ExposedLimbType,\n LimbExposureWindow,\n PhysicalReachConfig,\n} from \"../../types/physics\";\nimport { BREAKING_STATUS_EFFECT_IDS } from \"./BreakingStatusEffects\";\n\n/**\n * Breaking technique constants\n * **Korean**: 파쇄기술 상수\n */\n\n/**\n * Minimum effective force required to successfully execute a breaking technique.\n * Force below this threshold will not break joints/bones.\n * Typical damage values range from 20-80, with 40 being the minimum for effective breaks.\n * @korean 파쇄최소힘\n */\nconst BREAKING_FORCE_THRESHOLD = 40;\n\n/**\n * Maximum effective force for calculating break severity on 0-1 scale.\n * Force values above 80 are clamped to 1.0 severity (complete break).\n * @korean 파쇄최대힘\n */\nconst BREAKING_MAX_FORCE = 80;\n\n/**\n * Maximum range in meters for executing counter-attacks.\n * Counter-attacks are close-range defensive techniques requiring proximity.\n * @korean 반격최대거리\n */\nconst MAX_COUNTER_RANGE_METERS = 1.0;\n\n/**\n * Calculate counter-attack opportunities from a technique's reach configuration.\n *\n * **Korean**: 반격 기회 계산\n *\n * Analyzes the technique's execution timeline and limb exposure to identify\n * windows where the opponent is vulnerable to counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Counter opportunity if one exists, undefined otherwise\n *\n * @korean 반격기회계산\n */\nexport function calculateCounterOpportunity(\n technique: KoreanTechnique,\n currentTime: number\n): CounterOpportunity | undefined {\n const { reachConfig, executionTime } = technique;\n\n // No exposure window defined, no counter opportunity\n if (!reachConfig.exposureWindow) {\n return undefined;\n }\n\n const exposure = reachConfig.exposureWindow;\n\n // Calculate absolute timing of exposure window\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Check if current time is within the exposure window\n if (currentTime < windowStart || currentTime > windowEnd) {\n return undefined;\n }\n\n // Determine recommended counter techniques based on exposed limb\n const recommendedCounters = getRecommendedCounters(\n exposure.exposedLimb,\n reachConfig,\n exposure.allowsBreaking\n );\n\n return {\n exposedLimb: exposure.exposedLimb,\n windowStart,\n windowDuration: exposure.duration,\n vulnerabilityMultiplier: exposure.vulnerabilityMultiplier,\n allowsBreaking: exposure.allowsBreaking,\n recommendedCounters,\n };\n}\n\n/**\n * Get recommended counter-technique IDs for an exposed limb.\n *\n * **Korean**: 추천 반격 기술 조회\n *\n * Returns technique IDs that would be effective against the exposed limb.\n * These IDs are string-based and should match techniques in the game's\n * technique library. When implementing, validate these IDs exist.\n *\n * **Note**: These are example/placeholder IDs. Actual implementation should\n * reference real technique definitions or use a typed enum of technique IDs.\n *\n * @param exposedLimb - The limb that is exposed\n * @param reachConfig - Reach configuration of the attacking technique\n * @param allowsBreaking - Whether breaking techniques are viable\n * @returns Array of recommended counter-technique IDs (unvalidated strings)\n *\n * @internal\n * @korean 추천반격기술조회\n */\nfunction getRecommendedCounters(\n exposedLimb: ExposedLimbType,\n reachConfig: PhysicalReachConfig,\n allowsBreaking: boolean\n): readonly string[] {\n const counters: string[] = [];\n\n // Breaking techniques for overextended limbs\n if (allowsBreaking) {\n if (exposedLimb.includes(\"leg\") || exposedLimb.includes(\"ankle\")) {\n counters.push(\"leg_sweep\", \"ankle_break\", \"knee_stomp\");\n } else if (exposedLimb.includes(\"knee\")) {\n counters.push(\"knee_break\", \"hyperextension_kick\");\n } else if (exposedLimb.includes(\"arm\") || exposedLimb.includes(\"elbow\")) {\n counters.push(\"arm_bar\", \"elbow_break\", \"joint_lock\");\n } else if (exposedLimb.includes(\"wrist\")) {\n counters.push(\"wrist_lock\", \"wrist_break\");\n }\n }\n\n // Vital point strikes on exposed limbs\n if (exposedLimb.includes(\"arm\")) {\n counters.push(\"ulnar_nerve_strike\", \"radial_nerve_pressure\");\n } else if (exposedLimb.includes(\"leg\")) {\n counters.push(\"peroneal_nerve_strike\", \"thigh_pressure_point\");\n }\n\n // Counter-strikes based on body part type\n if (reachConfig.bodyPart === \"leg\") {\n counters.push(\"leg_parry_counter\", \"inside_leg_kick\");\n } else if (reachConfig.bodyPart === \"arm\") {\n counters.push(\"arm_trap_counter\", \"redirect_punch\");\n }\n\n return counters;\n}\n\n/**\n * Calculate vulnerability multiplier for a technique at a specific time.\n *\n * **Korean**: 취약성 배수 계산\n *\n * Determines how vulnerable the attacker is at a given point in their\n * technique execution. Useful for damage calculation during counter-attacks.\n *\n * @param technique - The technique being executed\n * @param currentTime - Current time in technique execution (ms)\n * @returns Vulnerability multiplier (1.0 = normal, >1.0 = more vulnerable)\n *\n * @korean 취약성배수계산\n */\nexport function calculateVulnerabilityMultiplier(\n technique: KoreanTechnique,\n currentTime: number\n): number {\n const { reachConfig, executionTime, recoveryTime } = technique;\n\n // No exposure window, return baseline vulnerability\n if (!reachConfig.exposureWindow) {\n return 1.0;\n }\n\n const exposure = reachConfig.exposureWindow;\n const windowStart = executionTime * exposure.startTime;\n const windowEnd = windowStart + exposure.duration;\n\n // Before exposure window: slight vulnerability during wind-up\n if (currentTime < windowStart) {\n return 1.1;\n }\n\n // Within exposure window: maximum vulnerability\n if (currentTime <= windowEnd) {\n return exposure.vulnerabilityMultiplier;\n }\n\n // Recovery phase: moderate vulnerability\n const recoveryStart = executionTime;\n const recoveryEnd = executionTime + recoveryTime;\n if (currentTime > recoveryStart && currentTime <= recoveryEnd) {\n const recoveryProgress = (currentTime - recoveryStart) / recoveryTime;\n // Linearly decrease from max vulnerability to baseline\n return (\n exposure.vulnerabilityMultiplier -\n (exposure.vulnerabilityMultiplier - 1.0) * recoveryProgress\n );\n }\n\n // After recovery: baseline vulnerability\n return 1.0;\n}\n\n/**\n * Determine which limb is exposed based on technique and animation state.\n *\n * **Korean**: 노출된 사지 결정\n *\n * Analyzes the technique's body part and animation to determine which\n * specific limb (left/right) is currently exposed.\n *\n * @param technique - The technique being executed\n * @param isLeftSided - Whether the technique uses the left side\n * @returns The exposed limb type\n *\n * @korean 노출사지결정\n */\nexport function determineExposedLimb(\n technique: KoreanTechnique,\n isLeftSided: boolean = false\n): ExposedLimbType {\n const { reachConfig } = technique;\n\n // If exposure window explicitly defines the limb, use it\n if (reachConfig.exposureWindow?.exposedLimb) {\n return reachConfig.exposureWindow.exposedLimb;\n }\n\n // Otherwise, infer from body part and sidedness\n const side = isLeftSided ? \"left\" : \"right\";\n\n switch (reachConfig.bodyPart) {\n case \"arm\":\n // Determine joint based on reach extension\n if (reachConfig.baseExtension > 0.8) {\n return `${side}_arm` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.5) {\n return `${side}_elbow` as ExposedLimbType;\n } else {\n return `${side}_wrist` as ExposedLimbType;\n }\n\n case \"leg\":\n // Kicks expose different parts based on extension\n if (reachConfig.baseExtension > 1.0) {\n return `${side}_leg` as ExposedLimbType;\n } else if (reachConfig.baseExtension > 0.7) {\n return `${side}_knee` as ExposedLimbType;\n } else {\n return `${side}_ankle` as ExposedLimbType;\n }\n\n case \"torso\":\n // Torso techniques typically expose arms\n return `${side}_arm` as ExposedLimbType;\n\n default:\n return \"right_arm\";\n }\n}\n\n/**\n * Map exposed limb to breaking technique target.\n *\n * **Korean**: 노출 사지를 파쇄 목표로 매핑\n *\n * Converts an exposed limb type to the appropriate breaking target\n * for joint and limb breaking techniques.\n *\n * @param exposedLimb - The exposed limb\n * @returns The breaking target joint/bone\n *\n * @korean 파쇄목표매핑\n */\nexport function mapLimbToBreakingTarget(\n exposedLimb: ExposedLimbType\n): BreakingTarget {\n if (exposedLimb.includes(\"ankle\")) return \"ankle\";\n if (exposedLimb.includes(\"knee\")) return \"knee\";\n if (exposedLimb.includes(\"elbow\")) return \"elbow\";\n if (exposedLimb.includes(\"wrist\")) return \"wrist\";\n\n // Default mapping for general limbs\n if (exposedLimb.includes(\"leg\")) return \"knee\";\n if (exposedLimb.includes(\"arm\")) return \"elbow\";\n\n return \"elbow\"; // Default fallback\n}\n\n/**\n * Calculate breaking technique effectiveness.\n *\n * **Korean**: 파쇄 기술 효과 계산\n *\n * Determines the result of a breaking technique applied to an exposed limb,\n * considering the vulnerability window, force applied, and target joint.\n *\n * @param breakingTechnique - The breaking technique being applied (reserved for future use)\n * @param counterOpportunity - The counter opportunity being exploited\n * @param force - Force/damage of the breaking technique (typically 20-80 range)\n * @returns Result of the breaking attempt\n *\n * @remarks\n * The breakingTechnique parameter is currently unused but reserved for future\n * extensibility. It may be used to apply technique-specific modifiers or\n * validate technique compatibility with the breaking target.\n *\n * @korean 파쇄기술효과계산\n */\nexport function calculateBreakingResult(\n breakingTechnique: KoreanTechnique,\n counterOpportunity: CounterOpportunity,\n force: number\n): BreakingResult {\n // Note: breakingTechnique parameter reserved for future use\n // (e.g., technique-specific breaking modifiers)\n void breakingTechnique;\n const target = mapLimbToBreakingTarget(counterOpportunity.exposedLimb);\n\n // Breaking only works during valid windows\n if (!counterOpportunity.allowsBreaking) {\n return {\n success: false,\n target,\n severity: 0,\n damage: force * 0.5, // Reduced damage without breaking\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Calculate breaking severity based on force and vulnerability\n const effectiveForce = force * counterOpportunity.vulnerabilityMultiplier;\n const baseSuccess = effectiveForce > BREAKING_FORCE_THRESHOLD;\n const severity = Math.min(effectiveForce / BREAKING_MAX_FORCE, 1.0);\n\n if (!baseSuccess) {\n return {\n success: false,\n target,\n severity: 0,\n damage: effectiveForce * 0.7,\n mobilityReduction: 0,\n statusEffects: [],\n };\n }\n\n // Successful break - apply appropriate effects\n // Status effect IDs from BREAKING_STATUS_EFFECT_IDS constants\n // See src/systems/combat/BreakingStatusEffects.ts for all available IDs\n const statusEffects: string[] = [BREAKING_STATUS_EFFECT_IDS.PAIN];\n let mobilityReduction: number;\n\n // Severity-based effects\n if (severity > 0.8) {\n // Severe break - dislocation or fracture\n statusEffects.push(\n BREAKING_STATUS_EFFECT_IDS.SEVERE_INJURY,\n BREAKING_STATUS_EFFECT_IDS.DISABLED_LIMB\n );\n mobilityReduction = 0.6;\n\n if (target === \"ankle\" || target === \"knee\") {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.IMPAIRED_MOBILITY);\n mobilityReduction = 0.8; // Leg breaks severely impact movement\n }\n } else if (severity > 0.5) {\n // Moderate break\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.INJURED_LIMB);\n mobilityReduction = 0.4;\n } else {\n // Minor break - sprain or strain\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.SPRAINED_JOINT);\n mobilityReduction = 0.2;\n }\n\n // Bleeding for bone breaks\n if (severity > 0.6) {\n statusEffects.push(BREAKING_STATUS_EFFECT_IDS.BLEEDING);\n }\n\n return {\n success: true,\n target,\n severity,\n damage: effectiveForce,\n mobilityReduction,\n statusEffects,\n };\n}\n\n/**\n * Check if a defender can execute a counter-attack.\n *\n * **Korean**: 반격 실행 가능 여부 확인\n *\n * Validates whether a defender is in position and has the resources\n * to execute a counter-attack against an exposed limb.\n *\n * @param defenderStamina - Defender's current stamina\n * @param counterTechnique - The counter technique to execute\n * @param distance - Distance between attacker and defender (meters)\n * @returns Whether the counter can be executed\n *\n * @korean 반격실행가능확인\n */\nexport function canExecuteCounter(\n defenderStamina: number,\n counterTechnique: KoreanTechnique,\n distance: number\n): boolean {\n // Check stamina requirement\n if (defenderStamina < counterTechnique.staminaCost) {\n return false;\n }\n\n // Counter must be executable at current distance\n if (distance > MAX_COUNTER_RANGE_METERS) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Generate limb exposure window for a technique.\n *\n * **Korean**: 사지 노출 시간 생성\n *\n * Creates a default limb exposure window for techniques that don't\n * explicitly define one, based on heuristics from the technique type\n * and reach configuration.\n *\n * @param technique - The technique to generate exposure for\n * @returns Generated limb exposure window\n *\n * @korean 사지노출시간생성\n */\nexport function generateLimbExposureWindow(\n technique: KoreanTechnique\n): LimbExposureWindow {\n const { reachConfig, executionTime } = technique;\n const exposedLimb = determineExposedLimb(technique);\n\n // High extension = high vulnerability\n const isHighExtension = reachConfig.baseExtension > 1.0;\n const isMediumExtension = reachConfig.baseExtension > 0.8;\n\n // Determine vulnerability based on extension\n let vulnerabilityMultiplier: number;\n let allowsBreaking: boolean;\n\n if (isHighExtension) {\n vulnerabilityMultiplier = 2.2; // Very vulnerable\n allowsBreaking = true;\n } else if (isMediumExtension) {\n vulnerabilityMultiplier = 1.6;\n allowsBreaking = false;\n } else {\n vulnerabilityMultiplier = 1.2; // Low extension, low vulnerability\n allowsBreaking = false;\n }\n\n // Exposure starts during peak extension (typically 40-60% of execution)\n const startTime = 0.5;\n\n // Duration scales with execution time (20-40% of execution)\n const durationRatio = isHighExtension ? 0.4 : 0.25;\n const duration = Math.floor(executionTime * durationRatio);\n\n return {\n exposedLimb,\n startTime,\n duration,\n vulnerabilityMultiplier,\n allowsBreaking,\n };\n}\n\nexport default {\n calculateCounterOpportunity,\n calculateVulnerabilityMultiplier,\n determineExposedLimb,\n mapLimbToBreakingTarget,\n calculateBreakingResult,\n canExecuteCounter,\n generateLimbExposureWindow,\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
|