blacktrigram 0.7.67 → 0.7.69
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/assets/index.css +987 -987
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js +2 -2
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +3 -3
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/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 +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.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/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.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/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +2 -2
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/index.js +2 -2
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +11 -209
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.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/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/index.js +2 -2
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +9 -9
|
@@ -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":";;;;;;AAgEA,IAAa,yBAAwC;CACnD,0BAA0B;CAC1B,iBAAiB;CACjB,iBAAiB;CACjB,sBAAsB;CACtB,mBAAmB;CACnB,oBAAoB;AACtB;;;;;;;;;AA0CA,IAAa,gBAAb,MAA2B;CACzB;CAEA,YAAY,SAAiC,CAAC,GAAG;EAC/C,KAAK,SAAS;GAAE,GAAG;GAAwB,GAAG;EAAO;CACvD;;;;;;;;;;;;CAaA,eACE,UACA,UACA,QACA,aACsB;EAEtB,IAAI,SAAS,gBAAgB,YAAY,SACvC,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAIF,IAAI,SAAS,gBAAgB,YAAY,WACvC,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,IAAI,SAAS,UAAU,KAAK,OAAO,mBACjC,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAIF,IAAI,SAAS,gBAAgB,YAAY,UACvC,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAIF,MAAM,eAAe,KAAK,sBAAsB,UAAU,MAAM;EAGhE,MAAM,gBAAgB,KAAK,8BACzB,UACA,UACA,MACF;EAKA,IAHa,KAAK,OACF,IAAO,eAgBrB,OAAO;GACL,SAAS;GACT,gBAAA;IAbA,OAAO,aAAa;IACpB;IACA,cAAc,SAAS;IACvB,UAAU,SAAS;IACnB;IACA,UAAU;IACV,WAAW;IACX,WAAW;IACX,sBAAsB,KAAK,qBAAqB,QAAQ,YAAY;GAKpE;GACA,aAAa,KAAK,OAAO;EAC3B;EAGF,OAAO;GACL,SAAS;GACT,QAAQ,2BAA2B,KAAK,MAAM,gBAAgB,GAAG,EAAE;GACnE,aAAa,KAAK,OAAO,oBAAoB;EAC/C;CACF;;;;;;;;;;;;;CAcA,cACE,gBACA,YACA,SACA,WACA,aACuB;EAEvB,IACE,WAAW,UAAU,eAAe,uBAAuB,aAC3D,WAAW,gBAAgB,YAAY,SAGvC,OAAO;EAIT,MAAM,cAAc,cAAc,eAAe;EAGjD,IAAI,WAAW,eAAe;EAC9B,IAAI,eAAe,UAAU,aAAa,YAAY,cAAc,GAClE,WAAW,aAAa;EAK1B,MAAM,kBAAkB,eAAe,eAAe,IAAgB;EAGtE,IAAI,kBAAkB,KAAK,OAAO,iBAChC,OAAO;EAGT,MAAM,kBAAkB;EAGxB,MAAM,YAAY,eAAe,KAAK,OAAO;EAE7C,OAAO;GACL,GAAG;GACH,OAAO;GACP,cAAc;GACd,UAAU;GACV;EACF;CACF;;;;;;;;;;;CAYA,cACE,gBACA,YACA,QACqB;EAErB,IAAI,CAAC,eAAe,WAClB,OAAO;GACL,SAAS;GACT;GACA,QAAQ;GACR,aAAa;EACf;EAIF,IAAI,OAAO,UAAU,KAAK,OAAO,mBAC/B,OAAO;GACL,SAAS;GACT;GACA,QAAQ;GACR,aAAa;EACf;EAIF,MAAM,eAAe,KAAK,sBACxB,gBACA,YACA,MACF;EAKA,IAHa,KAAK,OACF,IAAO,cAGrB,OAAO;GACL,SAAS;GACT,gBAAgB;GAChB,aAAa,KAAK,OAAO;EAC3B;EAIF,MAAM,uBAAuB,KAAK,IAChC,KAAK,OAAO,iBACZ,eAAe,eAAe,EAChC;EAEA,OAAO;GACL,SAAS;GACT,gBAAgB;IACd,GAAG;IACH,cAAc;GAChB;GACA,QAAQ,kBAAkB,KAAK,MAAM,eAAe,GAAG,EAAE;GACzD,aAAa,KAAK,OAAO;EAC3B;CACF;;;;;;;;CASA,sBACE,UACA,QACQ;EAER,IAAI,WAAW,SAAS,cAAc;EAGtC,IAAI,SAAS,kBAAkB,cAAc,KAC3C,YAAY;OACP,IAAI,SAAS,kBAAkB,cAAc,KAClD,YAAY;EAId,QAAQ,QAAR;GACE,KAAK,cAAc;IACjB,YAAY;IACZ;GACF,KAAK,cAAc;IACjB,YAAY;IACZ;GACF,KAAK,cAAc;IACjB,YAAY;IACZ;GACF,KAAK,cAAc;IACjB,YAAY;IACZ;GACF,KAAK,cAAc;IACjB,YAAY;IACZ;GACF,KAAK,cAAc;IACjB,YAAY;IACZ;EACJ;EAEA,OAAO,KAAK,IAAI,KAAK,OAAO,iBAAiB,QAAQ;CACvD;;;;;;;;CASA,8BACE,UACA,UACA,QACQ;EAGR,IAAI,aAAa,MADK,SAAS,YAAY,SAAS,WACb;EAGvC,IAAI,SAAS,QAAQ,SAAS,OAC5B,eAAe,SAAS,QAAQ,SAAS,SAAS;EAIpD,IAAI,SAAS,kBAAkB,cAAc,KAC3C,cAAc;EAGhB,IAAI,SAAS,gBAAgB,YAAY,WACvC,cAAc;OACT,IAAI,SAAS,gBAAgB,YAAY,WAC9C,cAAc;EAIhB,QAAQ,QAAR;GACE,KAAK,cAAc;IACjB,cAAc;IACd;GACF,KAAK,cAAc;IACjB,cAAc;IACd;GACF,KAAK,cAAc;IACjB,cAAc;IACd;EACJ;EAGA,OAAO,KAAK,IAAI,KAAM,KAAK,IAAI,KAAM,UAAU,CAAC;CAClD;;;;;;;;CASA,sBACE,gBACA,YACA,QACQ;EAGR,IAAI,aAAa,KADE,eAAe,eAAe,KAAK,OAAO,kBACzB;EAGpC,MAAM,eAAe,OAAO,cAAc,WAAW;EACrD,cAAc,eAAe;EAG7B,IAAI,OAAO,QAAQ,WAAW,OAC5B,eAAe,OAAO,QAAQ,WAAW,SAAS;EAIpD,cAAc,OAAO,YAAY;EAIjC,IAAI,OAAO,kBAAkB,cAAc,KACzC,cAAc;OACT,IAAI,OAAO,kBAAkB,cAAc,KAChD,cAAc;EAIhB,MAAM,kBAAkB,eAAe,WAAW;EAClD,MAAM,cAAc,KAAK,IAAI,IAAK,kBAAkB,GAAI;EACxD,cAAc;EAGd,OAAO,KAAK,IAAI,KAAM,KAAK,IAAI,KAAM,UAAU,CAAC;CAClD;;;;;;;;CASA,qBACE,QACA,cACQ;EACR,IAAI,OAAO,KAAK,OAAO;EAGvB,QAAQ,QAAR;GACE,KAAK,cAAc;IACjB,QAAQ;IACR;GACF,KAAK,cAAc;IACjB,QAAQ;IACR;GACF,KAAK,cAAc;IACjB,QAAQ;IACR;GACF,KAAK,cAAc;IACjB,QAAQ;IACR;GACF,KAAK,cAAc;IACjB,QAAQ;IACR;GACF,KAAK,cAAc;IACjB,QAAQ;IACR;EACJ;EAGA,MAAM,aAAa,eAAe,KAAK,OAAO;EAC9C,QAAQ,KAAM,aAAa;EAE3B,OAAO;CACT;;;;;;;;;;CAWA,qBACE,gBACA,YACS;EAET,IAAI,eAAe,WAAW,KAC5B,OAAO;EAIT,IAAI,eAAe,eAAe,IAChC,OAAO;EAIT,IAAI,WAAW,UAAU,IACvB,OAAO;EAUT,OAAO;GALL,cAAc;GACd,cAAc;GACd,cAAc;EAGT,EAAiB,SAAS,eAAe,MAAM;CACxD;;;;;;;;;;CAWA,yBACE,gBACA,YACS;EAET,IAAI,eAAe,WAAW,KAC5B,OAAO;EAIT,IAAI,WAAW,YAAY,IACzB,OAAO;EAUT,OAAO;GALL,cAAc;GACd,cAAc;GACd,cAAc;EAGT,EAAY,SAAS,eAAe,MAAM;CACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCA,4BACE,WACA,kBAA0B,KAClB;EAER,MAAM,eAAe,uBAAuB,SAAS;EACrD,IAAI,CAAC,cAEH,OAAO;EAIT,OAAO,aAAa;CACtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCA,wBACE,WACA,YACA,YACA,aAOA;EAEA,MAAM,kBAAkB,KAAK,4BAA4B,WAAW,IAAI;EAExE,OAAO;GACL,cAAc;GACd,UAAU;GACV,UAAU;GACV,WAAW;GACX,SAAS,cAAc;EACzB;CACF;AACF"}
|
|
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":"painConsciousnessUtils.js","names":[],"sources":["../../../src/systems/combat/painConsciousnessUtils.ts"],"sourcesContent":["/**\n * Utility functions for Pain and Consciousness management in combat.\n * \n * **Korean**: 고통 의식 관리 유틸리티\n * \n * Provides helper functions for managing pain and consciousness effects\n * in combat scenarios, including status checking, effect application,\n * and recovery management.\n * \n * @module systems/combat/painConsciousnessUtils\n * @category Combat System\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointCategory } from \"@/types\";\nimport { CombatResult } from \"./types\";\nimport PainResponseSystem, { PainLevel, ShockPainEffect } from \"./PainResponseSystem\";\nimport ConsciousnessSystem, { ConsciousnessLevel } from \"./ConsciousnessSystem\";\n\n/**\n * Pain and consciousness status information.\n */\nexport interface PainConsciousnessStatus {\n /** Current pain level */\n readonly painLevel: PainLevel;\n /** Current consciousness level */\n readonly consciousnessLevel: ConsciousnessLevel;\n /** Whether player is in pain overload */\n readonly isInPainOverload: boolean;\n /** Whether player is at incapacitation threshold */\n readonly isIncapacitated: boolean;\n /** Whether player is combat effective */\n readonly isCombatEffective: boolean;\n /** Overall combat effectiveness percentage (0-1) */\n readonly combatEffectiveness: number;\n /** Bilingual status description */\n readonly statusDescription: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Gets comprehensive pain and consciousness status for a player.\n * \n * @param player - Player to check status for\n * @param painSystem - Pain response system instance\n * @param consciousnessSystem - Consciousness system instance\n * @returns Comprehensive status information\n * \n * @example\n * ```typescript\n * const status = getPainConsciousnessStatus(player, painSystem, consciousnessSystem);\n * console.log(`Combat effectiveness: ${status.combatEffectiveness * 100}%`);\n * console.log(`Status: ${status.statusDescription.english}`);\n * ```\n * \n * @korean 상태확인\n */\nexport function getPainConsciousnessStatus(\n player: PlayerState,\n painSystem: PainResponseSystem,\n consciousnessSystem: ConsciousnessSystem\n): PainConsciousnessStatus {\n const painLevel = painSystem.getPainLevel(player.pain);\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n const isInPainOverload = painSystem.isInPainOverload(player);\n const isIncapacitated = consciousnessSystem.isAtIncapacitationThreshold(player);\n \n // Calculate overall combat effectiveness\n const painPenalty = painSystem.getEffects(painLevel).performancePenalty;\n const consciousnessPenalty = consciousnessSystem.getEffects(consciousnessLevel).accuracyPenalty;\n const combatEffectiveness = Math.max(0, 1 - Math.max(painPenalty, consciousnessPenalty));\n \n const isCombatEffective = combatEffectiveness > 0.5;\n \n // Generate status description\n const painName = painSystem.getLevelName(painLevel);\n const consciousnessName = consciousnessSystem.getLevelName(consciousnessLevel);\n \n let statusKorean = `고통: ${painName.korean}, 의식: ${consciousnessName.korean}`;\n let statusEnglish = `Pain: ${painName.english}, Consciousness: ${consciousnessName.english}`;\n \n if (isIncapacitated) {\n statusKorean = \"무력화 상태\";\n statusEnglish = \"Incapacitated\";\n } else if (isInPainOverload) {\n statusKorean = \"고통 과부하\";\n statusEnglish = \"Pain Overload\";\n }\n \n return {\n painLevel,\n consciousnessLevel,\n isInPainOverload,\n isIncapacitated,\n isCombatEffective,\n combatEffectiveness,\n statusDescription: {\n korean: statusKorean,\n english: statusEnglish,\n },\n };\n}\n\n/**\n * Determines if a combat result should trigger head trauma.\n * \n * Head trauma occurs for:\n * - Neurological vital point hits\n * - Vascular hits to head region\n * - High damage hits (>25) that could cause concussion\n * - Critical hits to upper body\n * \n * @param result - Combat result to check\n * @param category - Vital point category if known\n * @returns True if result should cause head trauma\n * \n * @korean 두부외상확인\n */\nexport function isHeadTraumaHit(\n result: CombatResult,\n category?: VitalPointCategory\n): boolean {\n // Neurological hits often target head\n if (category === VitalPointCategory.NEUROLOGICAL) {\n return true;\n }\n \n // Vascular hits to head region\n if (category === VitalPointCategory.VASCULAR && result.damage > 15) {\n return true;\n }\n \n // High damage hits that could concuss\n if (result.damage > 25) {\n return true;\n }\n \n // Critical hits to vital points\n if (result.isCritical && result.vitalPointHit) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Extracts vital point category from combat result.\n * \n * Uses heuristics to determine category from effect sources and hit data.\n * \n * @param result - Combat result\n * @returns Vital point category if determinable\n * \n * @korean 급소분류추출\n */\nexport function extractVitalPointCategory(result: CombatResult): VitalPointCategory | undefined {\n if (!result.vitalPointHit || !result.effects || result.effects.length === 0) {\n return undefined;\n }\n \n const effect = result.effects[0];\n if (!effect.source) {\n return undefined;\n }\n \n const source = effect.source.toLowerCase();\n \n // Map source strings to categories\n if (source.includes('neuro') || source.includes('nerve')) {\n return VitalPointCategory.NEUROLOGICAL;\n }\n if (source.includes('vascular') || source.includes('blood') || source.includes('artery')) {\n return VitalPointCategory.VASCULAR;\n }\n if (source.includes('respiratory') || source.includes('breath') || source.includes('lung')) {\n return VitalPointCategory.RESPIRATORY;\n }\n if (source.includes('skeletal') || source.includes('bone')) {\n return VitalPointCategory.SKELETAL;\n }\n if (source.includes('muscular') || source.includes('muscle')) {\n return VitalPointCategory.MUSCULAR;\n }\n if (source.includes('organ') || source.includes('visceral')) {\n return VitalPointCategory.ORGAN;\n }\n if (source.includes('joint')) {\n return VitalPointCategory.JOINT;\n }\n \n return undefined;\n}\n\n/**\n * Calculates recommended recovery time based on player condition.\n * \n * @param player - Player state\n * @param consciousnessSystem - Consciousness system\n * @returns Estimated recovery time in seconds\n * \n * @example\n * ```typescript\n * const recoveryTime = getRecommendedRecoveryTime(player, consciousnessSystem);\n * console.log(`Recovery needed: ${recoveryTime}s`);\n * ```\n * \n * @korean 회복시간계산\n */\nexport function getRecommendedRecoveryTime(\n player: PlayerState,\n consciousnessSystem: ConsciousnessSystem\n): number {\n let painRecoveryTime = 0;\n let consciousnessRecoveryTime = 0;\n \n // Pain recovery time (-5 pain/second)\n if (player.pain > 0) {\n painRecoveryTime = player.pain / 5;\n }\n \n // Consciousness recovery time (5 points/second after 5s delay)\n if (player.consciousness < 100) {\n const consciousnessToRecover = 100 - player.consciousness;\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n // Account for slower recovery at low consciousness\n let recoveryRate = 5; // Base rate\n if (consciousnessLevel === ConsciousnessLevel.STUNNED) {\n recoveryRate = 2.5; // 50% slower\n } else if (consciousnessLevel === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryRate = 1; // 20% of base\n }\n \n consciousnessRecoveryTime = 5 + (consciousnessToRecover / recoveryRate);\n }\n \n // Since pain and consciousness recover in parallel (concurrently in game loop),\n // return the maximum of the two recovery times, not the sum\n return Math.ceil(Math.max(painRecoveryTime, consciousnessRecoveryTime));\n}\n\n/**\n * Checks if shock pain effect is still active.\n * \n * @param shockEffect - Shock pain effect to check\n * @returns True if effect is still active\n * \n * @korean 충격통활성확인\n */\nexport function isShockPainActive(shockEffect: ShockPainEffect): boolean {\n const elapsed = Date.now() - shockEffect.startTime;\n return elapsed < shockEffect.duration;\n}\n\n/**\n * Gets remaining shock pain duration.\n * \n * @param shockEffect - Shock pain effect\n * @returns Remaining duration in milliseconds, or 0 if expired\n * \n * @korean 충격통잔여시간\n */\nexport function getShockPainRemainingDuration(shockEffect: ShockPainEffect): number {\n const elapsed = Date.now() - shockEffect.startTime;\n return Math.max(0, shockEffect.duration - elapsed);\n}\n\n/**\n * Formats pain and consciousness values for display.\n * \n * @param player - Player state\n * @returns Formatted display strings\n * \n * @example\n * ```typescript\n * const display = formatPainConsciousnessDisplay(player);\n * console.log(display.pain); // \"Pain: 45/100\"\n * console.log(display.consciousness); // \"Consciousness: 85/100\"\n * ```\n * \n * @korean 표시형식\n */\nexport function formatPainConsciousnessDisplay(player: PlayerState): {\n readonly pain: string;\n readonly consciousness: string;\n readonly painKorean: string;\n readonly consciousnessKorean: string;\n} {\n return {\n pain: `Pain: ${Math.floor(player.pain)}/100`,\n consciousness: `Consciousness: ${Math.floor(player.consciousness)}/100`,\n painKorean: `고통: ${Math.floor(player.pain)}/100`,\n consciousnessKorean: `의식: ${Math.floor(player.consciousness)}/100`,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"painConsciousnessUtils.js","names":[],"sources":["../../../src/systems/combat/painConsciousnessUtils.ts"],"sourcesContent":["/**\n * Utility functions for Pain and Consciousness management in combat.\n * \n * **Korean**: 고통 의식 관리 유틸리티\n * \n * Provides helper functions for managing pain and consciousness effects\n * in combat scenarios, including status checking, effect application,\n * and recovery management.\n * \n * @module systems/combat/painConsciousnessUtils\n * @category Combat System\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointCategory } from \"@/types\";\nimport { CombatResult } from \"./types\";\nimport PainResponseSystem, { PainLevel, ShockPainEffect } from \"./PainResponseSystem\";\nimport ConsciousnessSystem, { ConsciousnessLevel } from \"./ConsciousnessSystem\";\n\n/**\n * Pain and consciousness status information.\n */\nexport interface PainConsciousnessStatus {\n /** Current pain level */\n readonly painLevel: PainLevel;\n /** Current consciousness level */\n readonly consciousnessLevel: ConsciousnessLevel;\n /** Whether player is in pain overload */\n readonly isInPainOverload: boolean;\n /** Whether player is at incapacitation threshold */\n readonly isIncapacitated: boolean;\n /** Whether player is combat effective */\n readonly isCombatEffective: boolean;\n /** Overall combat effectiveness percentage (0-1) */\n readonly combatEffectiveness: number;\n /** Bilingual status description */\n readonly statusDescription: {\n readonly korean: string;\n readonly english: string;\n };\n}\n\n/**\n * Gets comprehensive pain and consciousness status for a player.\n * \n * @param player - Player to check status for\n * @param painSystem - Pain response system instance\n * @param consciousnessSystem - Consciousness system instance\n * @returns Comprehensive status information\n * \n * @example\n * ```typescript\n * const status = getPainConsciousnessStatus(player, painSystem, consciousnessSystem);\n * console.log(`Combat effectiveness: ${status.combatEffectiveness * 100}%`);\n * console.log(`Status: ${status.statusDescription.english}`);\n * ```\n * \n * @korean 상태확인\n */\nexport function getPainConsciousnessStatus(\n player: PlayerState,\n painSystem: PainResponseSystem,\n consciousnessSystem: ConsciousnessSystem\n): PainConsciousnessStatus {\n const painLevel = painSystem.getPainLevel(player.pain);\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n const isInPainOverload = painSystem.isInPainOverload(player);\n const isIncapacitated = consciousnessSystem.isAtIncapacitationThreshold(player);\n \n // Calculate overall combat effectiveness\n const painPenalty = painSystem.getEffects(painLevel).performancePenalty;\n const consciousnessPenalty = consciousnessSystem.getEffects(consciousnessLevel).accuracyPenalty;\n const combatEffectiveness = Math.max(0, 1 - Math.max(painPenalty, consciousnessPenalty));\n \n const isCombatEffective = combatEffectiveness > 0.5;\n \n // Generate status description\n const painName = painSystem.getLevelName(painLevel);\n const consciousnessName = consciousnessSystem.getLevelName(consciousnessLevel);\n \n let statusKorean = `고통: ${painName.korean}, 의식: ${consciousnessName.korean}`;\n let statusEnglish = `Pain: ${painName.english}, Consciousness: ${consciousnessName.english}`;\n \n if (isIncapacitated) {\n statusKorean = \"무력화 상태\";\n statusEnglish = \"Incapacitated\";\n } else if (isInPainOverload) {\n statusKorean = \"고통 과부하\";\n statusEnglish = \"Pain Overload\";\n }\n \n return {\n painLevel,\n consciousnessLevel,\n isInPainOverload,\n isIncapacitated,\n isCombatEffective,\n combatEffectiveness,\n statusDescription: {\n korean: statusKorean,\n english: statusEnglish,\n },\n };\n}\n\n/**\n * Determines if a combat result should trigger head trauma.\n * \n * Head trauma occurs for:\n * - Neurological vital point hits\n * - Vascular hits to head region\n * - High damage hits (>25) that could cause concussion\n * - Critical hits to upper body\n * \n * @param result - Combat result to check\n * @param category - Vital point category if known\n * @returns True if result should cause head trauma\n * \n * @korean 두부외상확인\n */\nexport function isHeadTraumaHit(\n result: CombatResult,\n category?: VitalPointCategory\n): boolean {\n // Neurological hits often target head\n if (category === VitalPointCategory.NEUROLOGICAL) {\n return true;\n }\n \n // Vascular hits to head region\n if (category === VitalPointCategory.VASCULAR && result.damage > 15) {\n return true;\n }\n \n // High damage hits that could concuss\n if (result.damage > 25) {\n return true;\n }\n \n // Critical hits to vital points\n if (result.isCritical && result.vitalPointHit) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Extracts vital point category from combat result.\n * \n * Uses heuristics to determine category from effect sources and hit data.\n * \n * @param result - Combat result\n * @returns Vital point category if determinable\n * \n * @korean 급소분류추출\n */\nexport function extractVitalPointCategory(result: CombatResult): VitalPointCategory | undefined {\n if (!result.vitalPointHit || !result.effects || result.effects.length === 0) {\n return undefined;\n }\n \n const effect = result.effects[0];\n if (!effect.source) {\n return undefined;\n }\n \n const source = effect.source.toLowerCase();\n \n // Map source strings to categories\n if (source.includes('neuro') || source.includes('nerve')) {\n return VitalPointCategory.NEUROLOGICAL;\n }\n if (source.includes('vascular') || source.includes('blood') || source.includes('artery')) {\n return VitalPointCategory.VASCULAR;\n }\n if (source.includes('respiratory') || source.includes('breath') || source.includes('lung')) {\n return VitalPointCategory.RESPIRATORY;\n }\n if (source.includes('skeletal') || source.includes('bone')) {\n return VitalPointCategory.SKELETAL;\n }\n if (source.includes('muscular') || source.includes('muscle')) {\n return VitalPointCategory.MUSCULAR;\n }\n if (source.includes('organ') || source.includes('visceral')) {\n return VitalPointCategory.ORGAN;\n }\n if (source.includes('joint')) {\n return VitalPointCategory.JOINT;\n }\n \n return undefined;\n}\n\n/**\n * Calculates recommended recovery time based on player condition.\n * \n * @param player - Player state\n * @param consciousnessSystem - Consciousness system\n * @returns Estimated recovery time in seconds\n * \n * @example\n * ```typescript\n * const recoveryTime = getRecommendedRecoveryTime(player, consciousnessSystem);\n * console.log(`Recovery needed: ${recoveryTime}s`);\n * ```\n * \n * @korean 회복시간계산\n */\nexport function getRecommendedRecoveryTime(\n player: PlayerState,\n consciousnessSystem: ConsciousnessSystem\n): number {\n let painRecoveryTime = 0;\n let consciousnessRecoveryTime = 0;\n \n // Pain recovery time (-5 pain/second)\n if (player.pain > 0) {\n painRecoveryTime = player.pain / 5;\n }\n \n // Consciousness recovery time (5 points/second after 5s delay)\n if (player.consciousness < 100) {\n const consciousnessToRecover = 100 - player.consciousness;\n const consciousnessLevel = consciousnessSystem.getLevel(player.consciousness);\n \n // Account for slower recovery at low consciousness\n let recoveryRate = 5; // Base rate\n if (consciousnessLevel === ConsciousnessLevel.STUNNED) {\n recoveryRate = 2.5; // 50% slower\n } else if (consciousnessLevel === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryRate = 1; // 20% of base\n }\n \n consciousnessRecoveryTime = 5 + (consciousnessToRecover / recoveryRate);\n }\n \n // Since pain and consciousness recover in parallel (concurrently in game loop),\n // return the maximum of the two recovery times, not the sum\n return Math.ceil(Math.max(painRecoveryTime, consciousnessRecoveryTime));\n}\n\n/**\n * Checks if shock pain effect is still active.\n * \n * @param shockEffect - Shock pain effect to check\n * @returns True if effect is still active\n * \n * @korean 충격통활성확인\n */\nexport function isShockPainActive(shockEffect: ShockPainEffect): boolean {\n const elapsed = Date.now() - shockEffect.startTime;\n return elapsed < shockEffect.duration;\n}\n\n/**\n * Gets remaining shock pain duration.\n * \n * @param shockEffect - Shock pain effect\n * @returns Remaining duration in milliseconds, or 0 if expired\n * \n * @korean 충격통잔여시간\n */\nexport function getShockPainRemainingDuration(shockEffect: ShockPainEffect): number {\n const elapsed = Date.now() - shockEffect.startTime;\n return Math.max(0, shockEffect.duration - elapsed);\n}\n\n/**\n * Formats pain and consciousness values for display.\n * \n * @param player - Player state\n * @returns Formatted display strings\n * \n * @example\n * ```typescript\n * const display = formatPainConsciousnessDisplay(player);\n * console.log(display.pain); // \"Pain: 45/100\"\n * console.log(display.consciousness); // \"Consciousness: 85/100\"\n * ```\n * \n * @korean 표시형식\n */\nexport function formatPainConsciousnessDisplay(player: PlayerState): {\n readonly pain: string;\n readonly consciousness: string;\n readonly painKorean: string;\n readonly consciousnessKorean: string;\n} {\n return {\n pain: `Pain: ${Math.floor(player.pain)}/100`,\n consciousness: `Consciousness: ${Math.floor(player.consciousness)}/100`,\n painKorean: `고통: ${Math.floor(player.pain)}/100`,\n consciousnessKorean: `의식: ${Math.floor(player.consciousness)}/100`,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeGuards.js","names":[],"sources":["../../../src/systems/combat/typeGuards.ts"],"sourcesContent":["/**\n * Type guard functions for combat system\n * Provides runtime type checking for better type safety\n */\n\nimport { PlayerArchetype, VitalPointCategory, VitalPointSeverity } from \"../../types/common\";\nimport { VitalPoint } from \"../vitalpoint/types\";\n\n/**\n * Type guard to check if a value is a valid PlayerArchetype\n * @param value - Value to check\n * @returns True if value is a valid PlayerArchetype\n */\nexport function isValidArchetype(value: unknown): value is PlayerArchetype {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(PlayerArchetype).includes(value as PlayerArchetype);\n}\n\n/**\n * Type guard to check if a value is a valid VitalPoint\n * @param value - Value to check\n * @returns True if value is a valid VitalPoint\n */\nexport function isVitalPoint(value: unknown): value is VitalPoint {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n \n const obj = value as Record<string, unknown>;\n \n // Validate position structure\n const hasValidPosition = \n obj.position !== undefined &&\n typeof obj.position === \"object\" &&\n obj.position !== null &&\n typeof (obj.position as Record<string, unknown>).x === \"number\" &&\n typeof (obj.position as Record<string, unknown>).y === \"number\";\n \n return (\n typeof obj.id === \"string\" &&\n typeof obj.category === \"string\" &&\n Object.values(VitalPointCategory).includes(obj.category as VitalPointCategory) &&\n typeof obj.severity === \"string\" &&\n Object.values(VitalPointSeverity).includes(obj.severity as VitalPointSeverity) &&\n hasValidPosition &&\n Array.isArray(obj.effects) &&\n typeof obj.names === \"object\" &&\n obj.names !== null &&\n typeof obj.description === \"object\" &&\n obj.description !== null &&\n typeof obj.targetingDifficulty === \"number\" &&\n Array.isArray(obj.effectiveStances)\n );\n}\n\n/**\n * Type guard to check if a value is a valid VitalPointCategory\n * @param value - Value to check\n * @returns True if value is a valid VitalPointCategory\n */\nexport function isVitalPointCategory(value: unknown): value is VitalPointCategory {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(VitalPointCategory).includes(value as VitalPointCategory);\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,iBAAiB,OAA0C;CACzE,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO,OAAO,OAAO,eAAe,
|
|
1
|
+
{"version":3,"file":"typeGuards.js","names":[],"sources":["../../../src/systems/combat/typeGuards.ts"],"sourcesContent":["/**\n * Type guard functions for combat system\n * Provides runtime type checking for better type safety\n */\n\nimport { PlayerArchetype, VitalPointCategory, VitalPointSeverity } from \"../../types/common\";\nimport { VitalPoint } from \"../vitalpoint/types\";\n\n/**\n * Type guard to check if a value is a valid PlayerArchetype\n * @param value - Value to check\n * @returns True if value is a valid PlayerArchetype\n */\nexport function isValidArchetype(value: unknown): value is PlayerArchetype {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(PlayerArchetype).includes(value as PlayerArchetype);\n}\n\n/**\n * Type guard to check if a value is a valid VitalPoint\n * @param value - Value to check\n * @returns True if value is a valid VitalPoint\n */\nexport function isVitalPoint(value: unknown): value is VitalPoint {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n \n const obj = value as Record<string, unknown>;\n \n // Validate position structure\n const hasValidPosition = \n obj.position !== undefined &&\n typeof obj.position === \"object\" &&\n obj.position !== null &&\n typeof (obj.position as Record<string, unknown>).x === \"number\" &&\n typeof (obj.position as Record<string, unknown>).y === \"number\";\n \n return (\n typeof obj.id === \"string\" &&\n typeof obj.category === \"string\" &&\n Object.values(VitalPointCategory).includes(obj.category as VitalPointCategory) &&\n typeof obj.severity === \"string\" &&\n Object.values(VitalPointSeverity).includes(obj.severity as VitalPointSeverity) &&\n hasValidPosition &&\n Array.isArray(obj.effects) &&\n typeof obj.names === \"object\" &&\n obj.names !== null &&\n typeof obj.description === \"object\" &&\n obj.description !== null &&\n typeof obj.targetingDifficulty === \"number\" &&\n Array.isArray(obj.effectiveStances)\n );\n}\n\n/**\n * Type guard to check if a value is a valid VitalPointCategory\n * @param value - Value to check\n * @returns True if value is a valid VitalPointCategory\n */\nexport function isVitalPointCategory(value: unknown): value is VitalPointCategory {\n if (typeof value !== \"string\") {\n return false;\n }\n \n return Object.values(VitalPointCategory).includes(value as VitalPointCategory);\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,iBAAiB,OAA0C;CACzE,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO,OAAO,OAAO,eAAe,CAAC,CAAC,SAAS,KAAwB;AACzE;;;;;;AAOA,SAAgB,aAAa,OAAqC;CAChE,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAGT,MAAM,MAAM;CAGZ,MAAM,mBACJ,IAAI,aAAa,KAAA,KACjB,OAAO,IAAI,aAAa,YACxB,IAAI,aAAa,QACjB,OAAQ,IAAI,SAAqC,MAAM,YACvD,OAAQ,IAAI,SAAqC,MAAM;CAEzD,OACE,OAAO,IAAI,OAAO,YAClB,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,kBAAkB,CAAC,CAAC,SAAS,IAAI,QAA8B,KAC7E,OAAO,IAAI,aAAa,YACxB,OAAO,OAAO,kBAAkB,CAAC,CAAC,SAAS,IAAI,QAA8B,KAC7E,oBACA,MAAM,QAAQ,IAAI,OAAO,KACzB,OAAO,IAAI,UAAU,YACrB,IAAI,UAAU,QACd,OAAO,IAAI,gBAAgB,YAC3B,IAAI,gBAAgB,QACpB,OAAO,IAAI,wBAAwB,YACnC,MAAM,QAAQ,IAAI,gBAAgB;AAEtC;;;;;;AAOA,SAAgB,qBAAqB,OAA6C;CAChF,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,OAAO,OAAO,OAAO,kBAAkB,CAAC,CAAC,SAAS,KAA2B;AAC/E"}
|
package/lib/systems/index.js
CHANGED
|
@@ -14,9 +14,7 @@ import { DARK_OPS_ARCHETYPE_BONUSES, DARK_OPS_NIGHT_BONUS, DARK_OPS_SPECIAL_EFFE
|
|
|
14
14
|
import { DAMAGE_TYPE_EFFECTIVENESS, KOREAN_TECHNIQUE_CATEGORIES, TECHNIQUE_DIFFICULTY_LEVELS, TECHNIQUE_MODIFIERS, TECHNIQUE_NAMING, TECHNIQUE_PROPERTIES, TRIGRAM_TECHNIQUE_PROPERTIES, calculateDamageEffectiveness, getTrigramKey, getTrigramProperties } from "./trigram/techniques/TechniqueConfig.js";
|
|
15
15
|
import { TRIGRAM_TECHNIQUES, getAllTechniques, getTechniqueById, getTechniqueCountByStance, getTotalTechniqueCount } from "./trigram/techniques/index.js";
|
|
16
16
|
import { KoreanTechniquesSystem, TECHNIQUE_EFFECTIVENESS_MATRIX, getTechniquesByStance } from "./trigram/KoreanTechniques.js";
|
|
17
|
-
import { STANCE_SPEED_MODIFIERS } from "./physics/MovementPhysics.js";
|
|
18
17
|
import { EffectIntensity, HitEffectEnum, HitEffectType } from "./effects.js";
|
|
19
|
-
import { DEFAULT_INJURY_MOVEMENT_CONFIG, InjuryMovementModifier, injuryMovementModifier } from "./movement/InjuryMovementModifier.js";
|
|
20
18
|
import { BodyPartHealthSystem, bodyPartHealthSystem } from "./bodypart/BodyPartHealthSystem.js";
|
|
21
19
|
import { applyDamageToBodyParts, applyVitalPointDamageToBodyParts, getBodyPartCombatEffects, getBodyRegionFromVitalPoint, healBodyPartsProportionally, initializeBodyPartHealthForPlayer, isPlayerIncapacitatedByBodyDamage } from "./bodypart/BodyPartDamageIntegration.js";
|
|
22
20
|
import { MovementPenaltySystem, movementPenaltySystem } from "./bodypart/MovementPenaltySystem.js";
|
|
@@ -50,6 +48,8 @@ import { AIActionType } from "./ai/types.js";
|
|
|
50
48
|
import { ARCHETYPE_ENFORCEMENT, enforceArchetypeBehavior, getActionFrequency, getSignatureMove, isActionProhibited, shouldExecuteSignatureMove } from "./ai/ArchetypeEnforcer.js";
|
|
51
49
|
import { AIDecisionTree } from "./ai/DecisionTree.js";
|
|
52
50
|
import TrainingAI from "./ai/TrainingAI.js";
|
|
51
|
+
import { STANCE_SPEED_MODIFIERS } from "./physics/MovementPhysics.js";
|
|
52
|
+
import { DEFAULT_INJURY_MOVEMENT_CONFIG, InjuryMovementModifier, injuryMovementModifier } from "./movement/InjuryMovementModifier.js";
|
|
53
53
|
import TrainingCombatSystem from "./combat/TrainingCombatSystem.js";
|
|
54
54
|
import { isValidArchetype, isVitalPoint, isVitalPointCategory } from "./combat/typeGuards.js";
|
|
55
55
|
import CombatStateSystem, { CombatReadinessState } from "./combat/CombatStateSystem.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":[],"sources":["../../../src/systems/movement/integration.ts"],"sourcesContent":["/**\n * Integration module for InjuryMovementModifier with MovementPhysics\n * \n * **Korean**: 손상 이동 통합 (Injury Movement Integration)\n * \n * Provides helper functions to integrate the InjuryMovementModifier system\n * with the existing MovementPhysics system.\n * \n * @module systems/movement/integration\n * @category Movement System\n * @korean 손상이동통합\n */\n\nimport { injuryMovementModifier } from \"./InjuryMovementModifier\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate leg injury factor for MovementPhysics from body part health.\n * \n * **Korean**: 다리 손상 요소 계산\n * \n * Converts detailed body part health into a simple 0-1 injury factor\n * that MovementPhysics.MovementState expects. This allows gradual\n * migration from the old simple system to the new detailed system.\n * \n * Uses the **worst** (minimum) leg health to match the main injury system's behavior,\n * ensuring asymmetric injuries (e.g., one leg at 0%, other at 100%) are properly represented.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Leg injury factor (0 = healthy, 1 = fully injured), clamped to 0-1 range\n * \n * @example\n * ```typescript\n * const legInjuryFactor = calculateLegInjuryFactor(bodyPartHealth);\n * movementState.legInjuryFactor = legInjuryFactor;\n * ```\n * \n * @korean 다리손상요소계산\n */\nexport function calculateLegInjuryFactor(\n bodyPartHealth: BodyPartHealth\n): number {\n // Use worst (minimum) leg health to match main injury system behavior\n // This ensures asymmetric injuries are properly represented\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n \n // Clamp worst leg health to valid 0-100 range to avoid invalid injury factors\n const clampedLegHealth = Math.min(100, Math.max(0, worstLegHealth));\n\n // Normalize to 0-1 health ratio (1 = fully healthy, 0 = no health)\n const healthRatio = clampedLegHealth / 100;\n\n // Convert to 0-1 injury factor (0 = healthy, 1 = fully injured)\n // Use inverse of health ratio and clamp defensively to 0-1\n const injuryFactor = 1.0 - healthRatio;\n return Math.min(1, Math.max(0, injuryFactor));\n}\n\n/**\n * Calculate comprehensive movement speed with all injury modifiers.\n * \n * **Korean**: 종합 이동 속도 계산\n * \n * This is the recommended function for full integration with the new\n * injury system. It applies all modifiers including leg injuries,\n * torso damage, stance bonuses, and pain penalties.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Final calculated speed in m/s\n * \n * @example\n * ```typescript\n * const finalSpeed = calculateMovementSpeed(\n * 5.0,\n * playerBodyHealth,\n * TrigramStance.GEON,\n * 65\n * );\n * \n * // Use this speed to override MovementPhysics\n * movementPhysics.setMaxSpeed(finalSpeed);\n * ```\n * \n * @korean 종합이동속도계산\n */\nexport function calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n): number {\n const result = injuryMovementModifier.calculateMovementSpeed(\n baseSpeed,\n bodyPartHealth,\n stance,\n painLevel\n );\n \n return result.finalSpeed;\n}\n\n/**\n * Calculate speed multiplier from injuries (without stance or pain).\n * \n * **Korean**: 손상 속도 배수 계산\n * \n * Useful when you want to apply injury penalties separately from\n * stance and pain modifiers.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Speed multiplier (0.1-1.0)\n * \n * @korean 손상속도배수계산\n */\nexport function calculateInjuryMultiplier(\n bodyPartHealth: BodyPartHealth\n): number {\n // Calculate without stance or pain modifiers\n const result = injuryMovementModifier.calculateMovementSpeed(\n 1.0, // Base speed of 1.0 to get pure multiplier\n bodyPartHealth,\n TrigramStance.GEON, // Neutral stance (1.0x)\n 0 // No pain\n );\n \n // The result will be the pure injury multiplier\n return result.speedMultiplier;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,yBACd,gBACQ;CAGR,MAAM,iBAAiB,KAAK,IAAI,eAAe,SAAS,eAAe,QAAQ;CAU/E,MAAM,eAAe,IAPI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,cAAc,CAG7C,IAAmB;CAKvC,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC;AAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,uBACd,WACA,gBACA,QACA,WACQ;CAQR,OAPe,uBAAuB,uBACpC,WACA,gBACA,QACA,SAGK,
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../../../src/systems/movement/integration.ts"],"sourcesContent":["/**\n * Integration module for InjuryMovementModifier with MovementPhysics\n * \n * **Korean**: 손상 이동 통합 (Injury Movement Integration)\n * \n * Provides helper functions to integrate the InjuryMovementModifier system\n * with the existing MovementPhysics system.\n * \n * @module systems/movement/integration\n * @category Movement System\n * @korean 손상이동통합\n */\n\nimport { injuryMovementModifier } from \"./InjuryMovementModifier\";\nimport type { BodyPartHealth } from \"../bodypart/types\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Calculate leg injury factor for MovementPhysics from body part health.\n * \n * **Korean**: 다리 손상 요소 계산\n * \n * Converts detailed body part health into a simple 0-1 injury factor\n * that MovementPhysics.MovementState expects. This allows gradual\n * migration from the old simple system to the new detailed system.\n * \n * Uses the **worst** (minimum) leg health to match the main injury system's behavior,\n * ensuring asymmetric injuries (e.g., one leg at 0%, other at 100%) are properly represented.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Leg injury factor (0 = healthy, 1 = fully injured), clamped to 0-1 range\n * \n * @example\n * ```typescript\n * const legInjuryFactor = calculateLegInjuryFactor(bodyPartHealth);\n * movementState.legInjuryFactor = legInjuryFactor;\n * ```\n * \n * @korean 다리손상요소계산\n */\nexport function calculateLegInjuryFactor(\n bodyPartHealth: BodyPartHealth\n): number {\n // Use worst (minimum) leg health to match main injury system behavior\n // This ensures asymmetric injuries are properly represented\n const worstLegHealth = Math.min(bodyPartHealth.legLeft, bodyPartHealth.legRight);\n \n // Clamp worst leg health to valid 0-100 range to avoid invalid injury factors\n const clampedLegHealth = Math.min(100, Math.max(0, worstLegHealth));\n\n // Normalize to 0-1 health ratio (1 = fully healthy, 0 = no health)\n const healthRatio = clampedLegHealth / 100;\n\n // Convert to 0-1 injury factor (0 = healthy, 1 = fully injured)\n // Use inverse of health ratio and clamp defensively to 0-1\n const injuryFactor = 1.0 - healthRatio;\n return Math.min(1, Math.max(0, injuryFactor));\n}\n\n/**\n * Calculate comprehensive movement speed with all injury modifiers.\n * \n * **Korean**: 종합 이동 속도 계산\n * \n * This is the recommended function for full integration with the new\n * injury system. It applies all modifiers including leg injuries,\n * torso damage, stance bonuses, and pain penalties.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param baseSpeed - Base movement speed (m/s)\n * @param bodyPartHealth - Current body part health\n * @param stance - Current trigram stance\n * @param painLevel - Current pain level (0-100)\n * @returns Final calculated speed in m/s\n * \n * @example\n * ```typescript\n * const finalSpeed = calculateMovementSpeed(\n * 5.0,\n * playerBodyHealth,\n * TrigramStance.GEON,\n * 65\n * );\n * \n * // Use this speed to override MovementPhysics\n * movementPhysics.setMaxSpeed(finalSpeed);\n * ```\n * \n * @korean 종합이동속도계산\n */\nexport function calculateMovementSpeed(\n baseSpeed: number,\n bodyPartHealth: BodyPartHealth,\n stance: TrigramStance,\n painLevel: number\n): number {\n const result = injuryMovementModifier.calculateMovementSpeed(\n baseSpeed,\n bodyPartHealth,\n stance,\n painLevel\n );\n \n return result.finalSpeed;\n}\n\n/**\n * Calculate speed multiplier from injuries (without stance or pain).\n * \n * **Korean**: 손상 속도 배수 계산\n * \n * Useful when you want to apply injury penalties separately from\n * stance and pain modifiers.\n * \n * Uses the shared singleton instance to avoid allocations in per-frame loops.\n * \n * @param bodyPartHealth - Current body part health\n * @returns Speed multiplier (0.1-1.0)\n * \n * @korean 손상속도배수계산\n */\nexport function calculateInjuryMultiplier(\n bodyPartHealth: BodyPartHealth\n): number {\n // Calculate without stance or pain modifiers\n const result = injuryMovementModifier.calculateMovementSpeed(\n 1.0, // Base speed of 1.0 to get pure multiplier\n bodyPartHealth,\n TrigramStance.GEON, // Neutral stance (1.0x)\n 0 // No pain\n );\n \n // The result will be the pure injury multiplier\n return result.speedMultiplier;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,yBACd,gBACQ;CAGR,MAAM,iBAAiB,KAAK,IAAI,eAAe,SAAS,eAAe,QAAQ;CAU/E,MAAM,eAAe,IAPI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,cAAc,CAG7C,IAAmB;CAKvC,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC;AAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,uBACd,WACA,gBACA,QACA,WACQ;CAQR,OAPe,uBAAuB,uBACpC,WACA,gBACA,QACA,SAGK,CAAA,CAAO;AAChB;;;;;;;;;;;;;;;;AAiBA,SAAgB,0BACd,gBACQ;CAUR,OARe,uBAAuB,uBACpC,GACA,gBACA,cAAc,MACd,CAIK,CAAA,CAAO;AAChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttackMovementPhysics.js","names":[],"sources":["../../../src/systems/physics/AttackMovementPhysics.ts"],"sourcesContent":["/**\n * Attack Movement Physics System for realistic forward momentum during attacks.\n *\n * **Korean**: 공격 이동 물리 시스템 (Attack Movement Physics System)\n *\n * Implements realistic forward movement physics when fighters execute attacks.\n * Kicks naturally extend the body forward, punches lunge with body weight,\n * and spinning techniques carry rotational momentum.\n *\n * ## Attack Movement Mechanics\n *\n * Forward movement distance is determined by:\n * - Animation type (kicks > punches > elbows/knees)\n * - Stance modifiers (8 trigram stances affect aggression)\n * - Animation phase timing (extension → peak → recovery)\n *\n * ## Movement Integration\n *\n * - Extension phase (frames 3-6): Forward lunge with ease-out\n * - Peak phase (frame 6): Maximum extension reached\n * - Recovery phase (frames 7-10): Return to original stance\n * - Arena bounds: Automatically clamp movement to valid zone\n *\n * ## Performance\n *\n * Optimized for 60fps with efficient vector calculations and\n * smooth easing curves for realistic attack momentum feel.\n *\n * @module systems/physics/AttackMovementPhysics\n * @category Physics System\n * @korean 공격이동물리\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"@/types/common\";\nimport { AnimationType } from \"@/systems/animation\";\n\n/**\n * Configuration for attack movement calculation.\n *\n * **Korean**: 공격 이동 설정 (Attack Movement Configuration)\n *\n * @korean 공격이동설정\n */\nexport interface AttackMovementConfig {\n /** Animation type being executed */\n readonly animationType: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Normalized attack direction vector (attacker → target) */\n readonly direction: THREE.Vector3;\n /** Total attack animation duration in seconds */\n readonly animationDuration: number;\n}\n\n/**\n * Result of attack movement calculation.\n *\n * **Korean**: 공격 이동 결과 (Attack Movement Result)\n *\n * Contains displacement vector and timing for attack lunge.\n *\n * @korean 공격이동결과\n */\nexport interface AttackMovementResult {\n /** Total forward displacement vector in world space */\n readonly displacement: THREE.Vector3;\n /** Duration of forward lunge phase in seconds */\n readonly lungeDuration: number;\n /** Duration of recovery return phase in seconds */\n readonly recoveryDuration: number;\n /** Total movement cycle duration in seconds */\n readonly totalDuration: number;\n}\n\n/**\n * Attack Movement Physics Engine.\n *\n * **Korean**: 공격 이동 물리 엔진\n *\n * Calculates realistic forward movement during attack animations\n * based on technique type, stance modifiers, and animation timing.\n * Provides smooth lunge and recovery phases for authentic martial arts feel.\n *\n * @example\n * ```typescript\n * const physics = new AttackMovementPhysics();\n *\n * // Calculate movement for roundhouse kick\n * const config: AttackMovementConfig = {\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI, // Fire stance (aggressive)\n * direction: new THREE.Vector3(1, 0, 0).normalize(),\n * animationDuration: 0.48, // 480ms kick animation\n * };\n *\n * const result = physics.calculateAttackMovement(config);\n * // Result: 1.0m base * 1.3 (Fire bonus) = ~1.3m forward lunge\n * // lungeDuration: 0.24s (50% of animation)\n * // recoveryDuration: 0.24s (50% of animation)\n * ```\n *\n * @korean 공격이동물리\n */\nexport class AttackMovementPhysics {\n /**\n * Calculates attack movement displacement and timing.\n *\n * **Korean**: 공격 이동 계산 (Calculate Attack Movement)\n *\n * Determines forward lunge distance and recovery timing based on:\n * 1. Base movement from animation type\n * 2. Stance movement modifier (8 trigram effects)\n * 3. Animation phase durations (lunge vs recovery)\n *\n * @param config - Attack movement configuration\n * @returns Attack movement result with displacement and timing\n *\n * @example\n * ```typescript\n * // Front kick with Heaven stance\n * const kick = physics.calculateAttackMovement({\n * animationType: AnimationType.FRONT_KICK,\n * currentStance: TrigramStance.GEON,\n * direction: attackVector,\n * animationDuration: 0.4,\n * });\n * // Result: ~0.88m forward (0.8m * 1.1 Geon modifier)\n *\n * // Jab with Mountain stance\n * const jab = physics.calculateAttackMovement({\n * animationType: AnimationType.JAB,\n * currentStance: TrigramStance.GAN,\n * direction: attackVector,\n * animationDuration: 0.25,\n * });\n * // Result: ~0.24m forward (0.3m * 0.8 Gan modifier)\n * ```\n *\n * @korean 공격이동계산\n */\n calculateAttackMovement(\n config: AttackMovementConfig\n ): AttackMovementResult {\n // 1. Get base movement distance for animation type\n const baseDistance = this.getBaseMovementDistance(config.animationType);\n\n // 2. Apply stance movement modifier\n const stanceModifier = this.getStanceMovementModifier(config.currentStance);\n const finalDistance = baseDistance * stanceModifier;\n\n // 3. Calculate displacement vector\n const displacement = config.direction.clone().multiplyScalar(finalDistance);\n\n // 4. Calculate phase durations (lunge + recovery)\n const lungeDuration = config.animationDuration * 0.5; // First 50% = forward\n const recoveryDuration = config.animationDuration * 0.5; // Last 50% = return\n\n return {\n displacement,\n lungeDuration,\n recoveryDuration,\n totalDuration: config.animationDuration,\n };\n }\n\n /**\n * Gets base forward movement distance for animation type.\n *\n * **Korean**: 기본 이동 거리 (Base Movement Distance)\n *\n * Movement distances by technique category:\n * - Kicks: 0.8-1.2m (leg extension, longest reach)\n * - Punches: 0.3-0.5m (arm extension, body lunge)\n * - Elbows/Knees: 0.2-0.3m (close range techniques)\n * - Spinning: 0.5-0.8m (rotation carries momentum)\n *\n * @param animationType - Type of attack animation\n * @returns Base movement distance in meters\n *\n * @private\n * @korean 기본이동거리\n */\n private getBaseMovementDistance(animationType: AnimationType): number {\n // Kicks - highest forward movement (leg extension)\n if (\n animationType === AnimationType.ROUNDHOUSE_KICK ||\n animationType === AnimationType.SIDE_KICK ||\n animationType === AnimationType.BACK_KICK\n ) {\n return 1.0; // 1.0m forward lunge\n }\n\n if (\n animationType === AnimationType.FRONT_KICK ||\n animationType === AnimationType.PUSH_KICK\n ) {\n return 0.8; // 0.8m forward lunge\n }\n\n if (\n animationType === AnimationType.AXE_KICK ||\n animationType === AnimationType.CRESCENT_KICK ||\n animationType === AnimationType.LOW_KICK\n ) {\n return 0.9; // 0.9m forward lunge\n }\n\n // Spinning kicks - momentum carries body forward\n if (\n animationType === AnimationType.SPINNING_HOOK ||\n animationType === AnimationType.SPINNING_HEEL_KICK ||\n animationType === AnimationType.TORNADO_KICK\n ) {\n return 0.8; // 0.8m with rotation\n }\n\n // Jumping/flying kicks - aerial momentum\n if (\n animationType === AnimationType.FLYING_KICK ||\n animationType === AnimationType.JUMPING_KICK\n ) {\n return 1.2; // 1.2m maximum extension\n }\n\n // Punches - moderate forward movement (arm + body lunge)\n if (\n animationType === AnimationType.CROSS ||\n animationType === AnimationType.OVERHAND ||\n animationType === AnimationType.HOOK\n ) {\n return 0.5; // 0.5m forward lunge\n }\n\n if (\n animationType === AnimationType.JAB ||\n animationType === AnimationType.BACKFIST\n ) {\n return 0.3; // 0.3m forward lunge\n }\n\n if (\n animationType === AnimationType.UPPERCUT ||\n animationType === AnimationType.HAMMER_FIST ||\n animationType === AnimationType.PALM_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge\n }\n\n // Elbow/Knee - minimal movement (close range)\n if (\n animationType === AnimationType.ELBOW_STRIKE ||\n animationType === AnimationType.KNEE_STRIKE ||\n animationType === AnimationType.KNEE_KICK ||\n animationType === AnimationType.ELBOW_UPPERCUT ||\n animationType === AnimationType.FLYING_KNEE ||\n animationType === AnimationType.CLINCH_KNEE ||\n animationType === AnimationType.SPINNING_ELBOW ||\n animationType === AnimationType.TEMPLE_ELBOW ||\n animationType === AnimationType.SPINNING_BACK_ELBOW ||\n animationType === AnimationType.SPINAL_ELBOW ||\n animationType === AnimationType.BRACHIAL_ELBOW ||\n animationType === AnimationType.KIDNEY_KNEE ||\n animationType === AnimationType.FEMORAL_KNEE\n ) {\n return 0.2; // 0.2m forward lunge\n }\n\n // Specialized jab variants - similar to jab (quick extension)\n if (\n animationType === AnimationType.SPEAR_HAND_STRIKE ||\n animationType === AnimationType.NERVE_STRIKE ||\n animationType === AnimationType.PRESSURE_POINT_STRIKE ||\n animationType === AnimationType.LIGHTNING_STRIKE ||\n animationType === AnimationType.RAPID_BARRAGE ||\n animationType === AnimationType.RHYTHMIC_STRIKES ||\n animationType === AnimationType.NERVE_PARALYSIS ||\n animationType === AnimationType.THROAT_STRIKE ||\n animationType === AnimationType.EYE_GOUGE\n ) {\n return 0.3; // 0.3m forward lunge (jab-like)\n }\n\n // Specialized cross variants - similar to cross (body weight transfer)\n if (\n animationType === AnimationType.HEAVEN_STRIKE ||\n animationType === AnimationType.FLOWING_CROSS\n ) {\n return 0.5; // 0.5m forward lunge (cross-like)\n }\n\n // Specialized palm strikes - similar to palm strike\n if (\n animationType === AnimationType.SOLAR_PLEXUS_STRIKE ||\n animationType === AnimationType.FLOWING_PUSH ||\n animationType === AnimationType.LIVER_DISRUPTION ||\n animationType === AnimationType.EAR_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge (palm-like)\n }\n\n // Grappling - minimal forward movement (rotation/off-balancing in place)\n if (\n animationType === AnimationType.THROW ||\n animationType === AnimationType.JOINT_LOCK ||\n animationType === AnimationType.TAKEDOWN ||\n animationType === AnimationType.SWEEP ||\n animationType === AnimationType.CLINCH ||\n animationType === AnimationType.GRAPPLE ||\n animationType === AnimationType.SLAM ||\n animationType === AnimationType.WRIST_LOCK ||\n animationType === AnimationType.ARM_BAR ||\n animationType === AnimationType.SHOULDER_LOCK ||\n animationType === AnimationType.HIP_THROW ||\n animationType === AnimationType.LEG_REAP ||\n animationType === AnimationType.SMALL_CIRCLE_LOCK ||\n animationType === AnimationType.FINGER_LOCK ||\n animationType === AnimationType.ELBOW_LOCK ||\n animationType === AnimationType.SHOULDER_MANIPULATION ||\n animationType === AnimationType.MOUNTAIN_LOCK ||\n animationType === AnimationType.EARTH_EMBRACE ||\n animationType === AnimationType.CAROTID_CHOKE\n ) {\n return 0.05; // 0.05m forward movement for grappling entries\n }\n\n // Default - conservative minimal movement for any unmapped types\n // Additional AnimationType values can be grouped with the closest category above\n // to fine-tune their forward displacement without changing overall behavior\n return 0.2;\n }\n\n /**\n * Gets stance movement modifier for attack lunge.\n *\n * **Korean**: 자세 이동 배율 (Stance Movement Modifier)\n *\n * Trigram stance movement modifiers based on combat philosophy:\n * - ☰ 건 (Geon/Heaven): +30% movement (aggressive, drives forward with pure yang)\n * - ☳ 진 (Jin/Thunder): +20% movement (explosive power, shocking force)\n * - ☴ 손 (Son/Wind): +15% movement (continuous flow, mobile)\n * - ☲ 리 (Li/Fire): +10% movement (precision strikes, controlled aggression)\n * - ☱ 태 (Tae/Lake): 0% (neutral, fluid and adaptive)\n * - ☵ 감 (Gam/Water): 0% (neutral, flowing and flexible)\n * - ☷ 곤 (Gon/Earth): -10% movement (grounded, stable techniques)\n * - ☶ 간 (Gan/Mountain): -20% movement (defensive mastery, minimal advance)\n *\n * @param stance - Current trigram stance\n * @returns Movement multiplier (0.8 to 1.3)\n *\n * @private\n * @korean 자세이동배율\n */\n private getStanceMovementModifier(stance: TrigramStance): number {\n const modifiers: Record<TrigramStance, number> = {\n [TrigramStance.GEON]: 1.3, // Heaven: +30% aggressive forward pressure (pure yang drives forward)\n [TrigramStance.JIN]: 1.2, // Thunder: +20% explosive movement\n [TrigramStance.SON]: 1.15, // Wind: +15% flowing movement\n [TrigramStance.LI]: 1.1, // Fire: +10% precision strikes with controlled forward movement\n [TrigramStance.TAE]: 1.0, // Lake: neutral movement\n [TrigramStance.GAM]: 1.0, // Water: neutral movement\n [TrigramStance.GON]: 0.9, // Earth: -10% grounded movement\n [TrigramStance.GAN]: 0.8, // Mountain: -20% defensive movement\n };\n return modifiers[stance];\n }\n\n /**\n * Applies attack movement force to attacker position over time.\n *\n * **Korean**: 공격 이동 적용 (Apply Attack Movement)\n *\n * Uses smooth ease-out cubic curve for realistic lunge feel:\n * - Fast initial forward movement (attack commitment)\n * - Gradual deceleration at peak extension\n * - Smooth return during recovery phase\n *\n * @param basePosition - Original stance position (does not change during attack)\n * @param result - Attack movement result with displacement\n * @param elapsedTime - Time elapsed since attack started\n * @param isRecoveryPhase - Whether in recovery (return) phase\n * @returns New position with attack displacement applied\n *\n * @example\n * ```typescript\n * // In animation update loop (60fps)\n * const basePos = new THREE.Vector3(0, 0, 0); // Original stance position\n * \n * if (elapsedTime < result.lungeDuration) {\n * // Lunge forward phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * false\n * );\n * } else if (elapsedTime < result.totalDuration) {\n * // Recovery return phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * true\n * );\n * }\n * ```\n *\n * @korean 공격이동적용\n */\n applyAttackMovement(\n basePosition: THREE.Vector3,\n result: AttackMovementResult,\n elapsedTime: number,\n isRecoveryPhase: boolean\n ): THREE.Vector3 {\n let progress: number;\n\n if (!isRecoveryPhase) {\n // Lunge forward phase\n progress = Math.min(1.0, elapsedTime / result.lungeDuration);\n\n // Smooth forward lunge curve (ease-out cubic)\n // Fast initial commitment, gradual deceleration at peak\n const easedProgress = 1 - Math.pow(1 - progress, 3);\n\n // Calculate position along forward path\n const currentDisplacement = result.displacement\n .clone()\n .multiplyScalar(easedProgress);\n\n return basePosition.clone().add(currentDisplacement);\n } else {\n // Recovery return phase\n const recoveryTime = elapsedTime - result.lungeDuration;\n progress = Math.min(1.0, recoveryTime / result.recoveryDuration);\n\n // Smooth return curve (ease-in cubic)\n // Gradual start, faster finish back to stance\n const easedProgress = Math.pow(progress, 3);\n\n // Calculate return position (from peak back to origin)\n const returnDisplacement = result.displacement\n .clone()\n .multiplyScalar(1.0 - easedProgress);\n\n return basePosition.clone().add(returnDisplacement);\n }\n }\n\n /**\n * Checks if attacker is currently in lunge phase.\n *\n * **Korean**: 돌진 상태 확인 (Check Lunge State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param lungeDuration - Total lunge phase duration\n * @returns True if in forward lunge phase\n *\n * @korean 돌진상태확인\n */\n isInLungePhase(elapsedTime: number, lungeDuration: number): boolean {\n return elapsedTime < lungeDuration;\n }\n\n /**\n * Checks if attacker is in recovery return phase.\n *\n * **Korean**: 회복 상태 확인 (Check Recovery State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param result - Attack movement result with timing\n * @returns True if in recovery return phase\n *\n * @korean 회복상태확인\n */\n isInRecoveryPhase(\n elapsedTime: number,\n result: AttackMovementResult\n ): boolean {\n return (\n elapsedTime >= result.lungeDuration &&\n elapsedTime < result.totalDuration\n );\n }\n\n /**\n * Gets bilingual Korean-English name for lunge phase.\n *\n * **Korean**: 돌진 단계 이름 (Lunge Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @korean 돌진단계이름\n */\n getLungePhaseName(): { korean: string; english: string } {\n return {\n korean: \"돌진\",\n english: \"Lunge\",\n };\n }\n\n /**\n * Gets bilingual Korean-English name for recovery phase.\n *\n * **Korean**: 복귀 단계 이름 (Recovery Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @korean 복귀단계이름\n */\n getRecoveryPhaseName(): { korean: string; english: string } {\n return {\n korean: \"복귀\",\n english: \"Recovery\",\n };\n }\n}\n\nexport default AttackMovementPhysics;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttackMovementPhysics.js","names":[],"sources":["../../../src/systems/physics/AttackMovementPhysics.ts"],"sourcesContent":["/**\n * Attack Movement Physics System for realistic forward momentum during attacks.\n *\n * **Korean**: 공격 이동 물리 시스템 (Attack Movement Physics System)\n *\n * Implements realistic forward movement physics when fighters execute attacks.\n * Kicks naturally extend the body forward, punches lunge with body weight,\n * and spinning techniques carry rotational momentum.\n *\n * ## Attack Movement Mechanics\n *\n * Forward movement distance is determined by:\n * - Animation type (kicks > punches > elbows/knees)\n * - Stance modifiers (8 trigram stances affect aggression)\n * - Animation phase timing (extension → peak → recovery)\n *\n * ## Movement Integration\n *\n * - Extension phase (frames 3-6): Forward lunge with ease-out\n * - Peak phase (frame 6): Maximum extension reached\n * - Recovery phase (frames 7-10): Return to original stance\n * - Arena bounds: Automatically clamp movement to valid zone\n *\n * ## Performance\n *\n * Optimized for 60fps with efficient vector calculations and\n * smooth easing curves for realistic attack momentum feel.\n *\n * @module systems/physics/AttackMovementPhysics\n * @category Physics System\n * @korean 공격이동물리\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"@/types/common\";\nimport { AnimationType } from \"@/systems/animation\";\n\n/**\n * Configuration for attack movement calculation.\n *\n * **Korean**: 공격 이동 설정 (Attack Movement Configuration)\n *\n * @korean 공격이동설정\n */\nexport interface AttackMovementConfig {\n /** Animation type being executed */\n readonly animationType: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Normalized attack direction vector (attacker → target) */\n readonly direction: THREE.Vector3;\n /** Total attack animation duration in seconds */\n readonly animationDuration: number;\n}\n\n/**\n * Result of attack movement calculation.\n *\n * **Korean**: 공격 이동 결과 (Attack Movement Result)\n *\n * Contains displacement vector and timing for attack lunge.\n *\n * @korean 공격이동결과\n */\nexport interface AttackMovementResult {\n /** Total forward displacement vector in world space */\n readonly displacement: THREE.Vector3;\n /** Duration of forward lunge phase in seconds */\n readonly lungeDuration: number;\n /** Duration of recovery return phase in seconds */\n readonly recoveryDuration: number;\n /** Total movement cycle duration in seconds */\n readonly totalDuration: number;\n}\n\n/**\n * Attack Movement Physics Engine.\n *\n * **Korean**: 공격 이동 물리 엔진\n *\n * Calculates realistic forward movement during attack animations\n * based on technique type, stance modifiers, and animation timing.\n * Provides smooth lunge and recovery phases for authentic martial arts feel.\n *\n * @example\n * ```typescript\n * const physics = new AttackMovementPhysics();\n *\n * // Calculate movement for roundhouse kick\n * const config: AttackMovementConfig = {\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI, // Fire stance (aggressive)\n * direction: new THREE.Vector3(1, 0, 0).normalize(),\n * animationDuration: 0.48, // 480ms kick animation\n * };\n *\n * const result = physics.calculateAttackMovement(config);\n * // Result: 1.0m base * 1.3 (Fire bonus) = ~1.3m forward lunge\n * // lungeDuration: 0.24s (50% of animation)\n * // recoveryDuration: 0.24s (50% of animation)\n * ```\n *\n * @korean 공격이동물리\n */\nexport class AttackMovementPhysics {\n /**\n * Calculates attack movement displacement and timing.\n *\n * **Korean**: 공격 이동 계산 (Calculate Attack Movement)\n *\n * Determines forward lunge distance and recovery timing based on:\n * 1. Base movement from animation type\n * 2. Stance movement modifier (8 trigram effects)\n * 3. Animation phase durations (lunge vs recovery)\n *\n * @param config - Attack movement configuration\n * @returns Attack movement result with displacement and timing\n *\n * @example\n * ```typescript\n * // Front kick with Heaven stance\n * const kick = physics.calculateAttackMovement({\n * animationType: AnimationType.FRONT_KICK,\n * currentStance: TrigramStance.GEON,\n * direction: attackVector,\n * animationDuration: 0.4,\n * });\n * // Result: ~0.88m forward (0.8m * 1.1 Geon modifier)\n *\n * // Jab with Mountain stance\n * const jab = physics.calculateAttackMovement({\n * animationType: AnimationType.JAB,\n * currentStance: TrigramStance.GAN,\n * direction: attackVector,\n * animationDuration: 0.25,\n * });\n * // Result: ~0.24m forward (0.3m * 0.8 Gan modifier)\n * ```\n *\n * @korean 공격이동계산\n */\n calculateAttackMovement(\n config: AttackMovementConfig\n ): AttackMovementResult {\n // 1. Get base movement distance for animation type\n const baseDistance = this.getBaseMovementDistance(config.animationType);\n\n // 2. Apply stance movement modifier\n const stanceModifier = this.getStanceMovementModifier(config.currentStance);\n const finalDistance = baseDistance * stanceModifier;\n\n // 3. Calculate displacement vector\n const displacement = config.direction.clone().multiplyScalar(finalDistance);\n\n // 4. Calculate phase durations (lunge + recovery)\n const lungeDuration = config.animationDuration * 0.5; // First 50% = forward\n const recoveryDuration = config.animationDuration * 0.5; // Last 50% = return\n\n return {\n displacement,\n lungeDuration,\n recoveryDuration,\n totalDuration: config.animationDuration,\n };\n }\n\n /**\n * Gets base forward movement distance for animation type.\n *\n * **Korean**: 기본 이동 거리 (Base Movement Distance)\n *\n * Movement distances by technique category:\n * - Kicks: 0.8-1.2m (leg extension, longest reach)\n * - Punches: 0.3-0.5m (arm extension, body lunge)\n * - Elbows/Knees: 0.2-0.3m (close range techniques)\n * - Spinning: 0.5-0.8m (rotation carries momentum)\n *\n * @param animationType - Type of attack animation\n * @returns Base movement distance in meters\n *\n * @private\n * @korean 기본이동거리\n */\n private getBaseMovementDistance(animationType: AnimationType): number {\n // Kicks - highest forward movement (leg extension)\n if (\n animationType === AnimationType.ROUNDHOUSE_KICK ||\n animationType === AnimationType.SIDE_KICK ||\n animationType === AnimationType.BACK_KICK\n ) {\n return 1.0; // 1.0m forward lunge\n }\n\n if (\n animationType === AnimationType.FRONT_KICK ||\n animationType === AnimationType.PUSH_KICK\n ) {\n return 0.8; // 0.8m forward lunge\n }\n\n if (\n animationType === AnimationType.AXE_KICK ||\n animationType === AnimationType.CRESCENT_KICK ||\n animationType === AnimationType.LOW_KICK\n ) {\n return 0.9; // 0.9m forward lunge\n }\n\n // Spinning kicks - momentum carries body forward\n if (\n animationType === AnimationType.SPINNING_HOOK ||\n animationType === AnimationType.SPINNING_HEEL_KICK ||\n animationType === AnimationType.TORNADO_KICK\n ) {\n return 0.8; // 0.8m with rotation\n }\n\n // Jumping/flying kicks - aerial momentum\n if (\n animationType === AnimationType.FLYING_KICK ||\n animationType === AnimationType.JUMPING_KICK\n ) {\n return 1.2; // 1.2m maximum extension\n }\n\n // Punches - moderate forward movement (arm + body lunge)\n if (\n animationType === AnimationType.CROSS ||\n animationType === AnimationType.OVERHAND ||\n animationType === AnimationType.HOOK\n ) {\n return 0.5; // 0.5m forward lunge\n }\n\n if (\n animationType === AnimationType.JAB ||\n animationType === AnimationType.BACKFIST\n ) {\n return 0.3; // 0.3m forward lunge\n }\n\n if (\n animationType === AnimationType.UPPERCUT ||\n animationType === AnimationType.HAMMER_FIST ||\n animationType === AnimationType.PALM_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge\n }\n\n // Elbow/Knee - minimal movement (close range)\n if (\n animationType === AnimationType.ELBOW_STRIKE ||\n animationType === AnimationType.KNEE_STRIKE ||\n animationType === AnimationType.KNEE_KICK ||\n animationType === AnimationType.ELBOW_UPPERCUT ||\n animationType === AnimationType.FLYING_KNEE ||\n animationType === AnimationType.CLINCH_KNEE ||\n animationType === AnimationType.SPINNING_ELBOW ||\n animationType === AnimationType.TEMPLE_ELBOW ||\n animationType === AnimationType.SPINNING_BACK_ELBOW ||\n animationType === AnimationType.SPINAL_ELBOW ||\n animationType === AnimationType.BRACHIAL_ELBOW ||\n animationType === AnimationType.KIDNEY_KNEE ||\n animationType === AnimationType.FEMORAL_KNEE\n ) {\n return 0.2; // 0.2m forward lunge\n }\n\n // Specialized jab variants - similar to jab (quick extension)\n if (\n animationType === AnimationType.SPEAR_HAND_STRIKE ||\n animationType === AnimationType.NERVE_STRIKE ||\n animationType === AnimationType.PRESSURE_POINT_STRIKE ||\n animationType === AnimationType.LIGHTNING_STRIKE ||\n animationType === AnimationType.RAPID_BARRAGE ||\n animationType === AnimationType.RHYTHMIC_STRIKES ||\n animationType === AnimationType.NERVE_PARALYSIS ||\n animationType === AnimationType.THROAT_STRIKE ||\n animationType === AnimationType.EYE_GOUGE\n ) {\n return 0.3; // 0.3m forward lunge (jab-like)\n }\n\n // Specialized cross variants - similar to cross (body weight transfer)\n if (\n animationType === AnimationType.HEAVEN_STRIKE ||\n animationType === AnimationType.FLOWING_CROSS\n ) {\n return 0.5; // 0.5m forward lunge (cross-like)\n }\n\n // Specialized palm strikes - similar to palm strike\n if (\n animationType === AnimationType.SOLAR_PLEXUS_STRIKE ||\n animationType === AnimationType.FLOWING_PUSH ||\n animationType === AnimationType.LIVER_DISRUPTION ||\n animationType === AnimationType.EAR_STRIKE\n ) {\n return 0.4; // 0.4m forward lunge (palm-like)\n }\n\n // Grappling - minimal forward movement (rotation/off-balancing in place)\n if (\n animationType === AnimationType.THROW ||\n animationType === AnimationType.JOINT_LOCK ||\n animationType === AnimationType.TAKEDOWN ||\n animationType === AnimationType.SWEEP ||\n animationType === AnimationType.CLINCH ||\n animationType === AnimationType.GRAPPLE ||\n animationType === AnimationType.SLAM ||\n animationType === AnimationType.WRIST_LOCK ||\n animationType === AnimationType.ARM_BAR ||\n animationType === AnimationType.SHOULDER_LOCK ||\n animationType === AnimationType.HIP_THROW ||\n animationType === AnimationType.LEG_REAP ||\n animationType === AnimationType.SMALL_CIRCLE_LOCK ||\n animationType === AnimationType.FINGER_LOCK ||\n animationType === AnimationType.ELBOW_LOCK ||\n animationType === AnimationType.SHOULDER_MANIPULATION ||\n animationType === AnimationType.MOUNTAIN_LOCK ||\n animationType === AnimationType.EARTH_EMBRACE ||\n animationType === AnimationType.CAROTID_CHOKE\n ) {\n return 0.05; // 0.05m forward movement for grappling entries\n }\n\n // Default - conservative minimal movement for any unmapped types\n // Additional AnimationType values can be grouped with the closest category above\n // to fine-tune their forward displacement without changing overall behavior\n return 0.2;\n }\n\n /**\n * Gets stance movement modifier for attack lunge.\n *\n * **Korean**: 자세 이동 배율 (Stance Movement Modifier)\n *\n * Trigram stance movement modifiers based on combat philosophy:\n * - ☰ 건 (Geon/Heaven): +30% movement (aggressive, drives forward with pure yang)\n * - ☳ 진 (Jin/Thunder): +20% movement (explosive power, shocking force)\n * - ☴ 손 (Son/Wind): +15% movement (continuous flow, mobile)\n * - ☲ 리 (Li/Fire): +10% movement (precision strikes, controlled aggression)\n * - ☱ 태 (Tae/Lake): 0% (neutral, fluid and adaptive)\n * - ☵ 감 (Gam/Water): 0% (neutral, flowing and flexible)\n * - ☷ 곤 (Gon/Earth): -10% movement (grounded, stable techniques)\n * - ☶ 간 (Gan/Mountain): -20% movement (defensive mastery, minimal advance)\n *\n * @param stance - Current trigram stance\n * @returns Movement multiplier (0.8 to 1.3)\n *\n * @private\n * @korean 자세이동배율\n */\n private getStanceMovementModifier(stance: TrigramStance): number {\n const modifiers: Record<TrigramStance, number> = {\n [TrigramStance.GEON]: 1.3, // Heaven: +30% aggressive forward pressure (pure yang drives forward)\n [TrigramStance.JIN]: 1.2, // Thunder: +20% explosive movement\n [TrigramStance.SON]: 1.15, // Wind: +15% flowing movement\n [TrigramStance.LI]: 1.1, // Fire: +10% precision strikes with controlled forward movement\n [TrigramStance.TAE]: 1.0, // Lake: neutral movement\n [TrigramStance.GAM]: 1.0, // Water: neutral movement\n [TrigramStance.GON]: 0.9, // Earth: -10% grounded movement\n [TrigramStance.GAN]: 0.8, // Mountain: -20% defensive movement\n };\n return modifiers[stance];\n }\n\n /**\n * Applies attack movement force to attacker position over time.\n *\n * **Korean**: 공격 이동 적용 (Apply Attack Movement)\n *\n * Uses smooth ease-out cubic curve for realistic lunge feel:\n * - Fast initial forward movement (attack commitment)\n * - Gradual deceleration at peak extension\n * - Smooth return during recovery phase\n *\n * @param basePosition - Original stance position (does not change during attack)\n * @param result - Attack movement result with displacement\n * @param elapsedTime - Time elapsed since attack started\n * @param isRecoveryPhase - Whether in recovery (return) phase\n * @returns New position with attack displacement applied\n *\n * @example\n * ```typescript\n * // In animation update loop (60fps)\n * const basePos = new THREE.Vector3(0, 0, 0); // Original stance position\n * \n * if (elapsedTime < result.lungeDuration) {\n * // Lunge forward phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * false\n * );\n * } else if (elapsedTime < result.totalDuration) {\n * // Recovery return phase\n * const newPosition = physics.applyAttackMovement(\n * basePos,\n * result,\n * elapsedTime,\n * true\n * );\n * }\n * ```\n *\n * @korean 공격이동적용\n */\n applyAttackMovement(\n basePosition: THREE.Vector3,\n result: AttackMovementResult,\n elapsedTime: number,\n isRecoveryPhase: boolean\n ): THREE.Vector3 {\n let progress: number;\n\n if (!isRecoveryPhase) {\n // Lunge forward phase\n progress = Math.min(1.0, elapsedTime / result.lungeDuration);\n\n // Smooth forward lunge curve (ease-out cubic)\n // Fast initial commitment, gradual deceleration at peak\n const easedProgress = 1 - Math.pow(1 - progress, 3);\n\n // Calculate position along forward path\n const currentDisplacement = result.displacement\n .clone()\n .multiplyScalar(easedProgress);\n\n return basePosition.clone().add(currentDisplacement);\n } else {\n // Recovery return phase\n const recoveryTime = elapsedTime - result.lungeDuration;\n progress = Math.min(1.0, recoveryTime / result.recoveryDuration);\n\n // Smooth return curve (ease-in cubic)\n // Gradual start, faster finish back to stance\n const easedProgress = Math.pow(progress, 3);\n\n // Calculate return position (from peak back to origin)\n const returnDisplacement = result.displacement\n .clone()\n .multiplyScalar(1.0 - easedProgress);\n\n return basePosition.clone().add(returnDisplacement);\n }\n }\n\n /**\n * Checks if attacker is currently in lunge phase.\n *\n * **Korean**: 돌진 상태 확인 (Check Lunge State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param lungeDuration - Total lunge phase duration\n * @returns True if in forward lunge phase\n *\n * @korean 돌진상태확인\n */\n isInLungePhase(elapsedTime: number, lungeDuration: number): boolean {\n return elapsedTime < lungeDuration;\n }\n\n /**\n * Checks if attacker is in recovery return phase.\n *\n * **Korean**: 회복 상태 확인 (Check Recovery State)\n *\n * @param elapsedTime - Time elapsed since attack started\n * @param result - Attack movement result with timing\n * @returns True if in recovery return phase\n *\n * @korean 회복상태확인\n */\n isInRecoveryPhase(\n elapsedTime: number,\n result: AttackMovementResult\n ): boolean {\n return (\n elapsedTime >= result.lungeDuration &&\n elapsedTime < result.totalDuration\n );\n }\n\n /**\n * Gets bilingual Korean-English name for lunge phase.\n *\n * **Korean**: 돌진 단계 이름 (Lunge Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @korean 돌진단계이름\n */\n getLungePhaseName(): { korean: string; english: string } {\n return {\n korean: \"돌진\",\n english: \"Lunge\",\n };\n }\n\n /**\n * Gets bilingual Korean-English name for recovery phase.\n *\n * **Korean**: 복귀 단계 이름 (Recovery Phase Name)\n *\n * @returns Korean and English phase names\n *\n * @korean 복귀단계이름\n */\n getRecoveryPhaseName(): { korean: string; english: string } {\n return {\n korean: \"복귀\",\n english: \"Recovery\",\n };\n }\n}\n\nexport default AttackMovementPhysics;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollisionDetection.js","names":[],"sources":["../../../src/systems/physics/CollisionDetection.ts"],"sourcesContent":["/**\n * Collision Detection System for Black Trigram combat physics.\n *\n * **Korean**: 충돌 감지 시스템\n *\n * Implements precise collision detection for the 70 vital points combat system using:\n * - Broad-phase AABB (Axis-Aligned Bounding Box) checks for performance\n * - Narrow-phase raycasting for precise vital point detection\n * - Attack reach validation based on technique and stance\n * - Bounding boxes for 5 anatomical regions\n *\n * ## Architecture\n *\n * The system uses a two-phase collision detection approach:\n *\n * 1. **Broad-phase**: Fast AABB checks to eliminate impossible collisions\n * 2. **Narrow-phase**: Precise raycasting to identify specific vital points\n *\n * This ensures 60fps performance even with complex collision queries.\n *\n * ## Performance\n *\n * - Broad-phase: O(1) per check (simple distance comparison)\n * - Narrow-phase: O(n) where n = vital points in region (typically 8-20)\n * - Target: <100 collision checks per frame for 60fps\n *\n * @example\n * ```typescript\n * const collision = new CollisionDetection();\n *\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 }, // Attacker position\n * { x: 0, y: 0, z: 6.5 }, // Defender position\n * { id: \"kick\", type: \"kick\" }, // Technique\n * TrigramStance.LI, // Attacker stance\n * \"torso\" // Target region\n * );\n *\n * if (result.hit) {\n * console.log(`Hit ${result.vitalPoint?.names.english}!`);\n * console.log(`Accuracy: ${(result.accuracy * 100).toFixed(1)}%`);\n * }\n * ```\n *\n * @module systems/physics/CollisionDetection\n * @category Combat Systems\n * @korean 충돌감지시스템\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../types/common\";\nimport type {\n AnatomicalRegionPhysics,\n BoundingBox,\n CollisionResult,\n Position3D,\n RaycastQuery,\n TechniqueType,\n} from \"../../types/physics\";\nimport {\n ANATOMICAL_DIMENSIONS,\n BASE_REACH,\n STANCE_REACH_MODIFIERS,\n} from \"../../types/physics\";\nimport type { VitalPoint } from \"../vitalpoint/types\";\nimport { VITAL_POINTS_DATA } from \"../vitalpoint/VitalPointsData\";\nimport { CoordinateMapper } from \"./CoordinateMapper\";\nimport { ThreeObjectPools } from \"../../utils/threeObjectPool\";\n\n/**\n * Collision Detection Engine for combat physics.\n *\n * **Korean**: 충돌 감지 엔진\n *\n * Provides efficient collision detection for combat using bounding boxes and raycasting.\n * Optimized for 60fps performance with multiple simultaneous collision checks.\n *\n * @category Combat Systems\n * @korean 충돌감지엔진\n */\nexport class CollisionDetection {\n private readonly boundingBoxes: Map<AnatomicalRegionPhysics, BoundingBox> =\n new Map();\n private readonly raycaster: THREE.Raycaster = new THREE.Raycaster();\n private vitalPointsByRegion: Map<AnatomicalRegionPhysics, VitalPoint[]> =\n new Map();\n\n // Geometry cache for object pooling to avoid repeated allocations during combat\n private readonly geometryCache: Map<string, THREE.BufferGeometry> = new Map();\n\n // Coordinate mapper for 2D→3D conversion\n private readonly coordinateMapper: CoordinateMapper;\n\n /**\n * Creates a new CollisionDetection instance.\n *\n * Initializes bounding boxes for all anatomical regions and organizes\n * vital points by region for efficient lookup.\n */\n constructor() {\n this.coordinateMapper = new CoordinateMapper();\n this.initializeBoundingBoxes();\n this.organizeVitalPointsByRegion();\n this.initializeGeometryCache();\n }\n\n /**\n * Cleans up Three.js resources.\n *\n * **Korean**: 자원 정리\n *\n * Disposes of cached geometries and releases memory to prevent leaks.\n * Should be called when the CollisionDetection instance is no longer needed.\n *\n * @korean 자원정리\n */\n public dispose(): void {\n // Dispose all cached geometries\n for (const geometry of this.geometryCache.values()) {\n geometry.dispose();\n }\n this.geometryCache.clear();\n }\n\n /**\n * Checks if an attack hits the defender.\n *\n * **Korean**: 공격 타격 확인\n *\n * Performs two-phase collision detection:\n * 1. Calculate effective attack reach based on technique and stance\n * 2. Broad-phase: Check if defender is within reach\n * 3. Broad-phase: Check if target region's AABB is within reach\n * 4. Narrow-phase: Raycast to find precise vital point hit\n *\n * @param attackerPosition - 3D position of the attacker\n * @param defenderPosition - 3D position of the defender\n * @param technique - Technique being used with type information\n * @param attackerStance - Attacker's current trigram stance\n * @param targetRegion - Anatomical region being targeted\n * @returns Collision result with hit status, vital point, distance, and accuracy\n *\n * @example\n * ```typescript\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 },\n * { x: 0, y: 0, z: 6 },\n * { type: \"punch\" },\n * TrigramStance.GEON,\n * \"head\"\n * );\n * ```\n *\n * @korean 공격타격확인\n */\n checkAttackHit(\n attackerPosition: Position3D,\n defenderPosition: Position3D,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Technique object has dynamic properties from combat system\n technique: { type?: string; [key: string]: any },\n attackerStance: TrigramStance,\n targetRegion: AnatomicalRegionPhysics,\n ): CollisionResult {\n // Calculate effective attack reach\n const techniqueType = this.parseTechniqueType(technique.type);\n const attackReach = this.calculateAttackReach(\n techniqueType,\n attackerStance,\n );\n\n // Calculate distance between attacker and defender\n const distance = this.calculateDistance3D(\n attackerPosition,\n defenderPosition,\n );\n\n // Broad-phase: Check if defender is within reach\n if (distance > attackReach.effectiveReach) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Get target region bounding box\n const targetBox = this.boundingBoxes.get(targetRegion);\n if (!targetBox) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Narrow-phase: Raycast from attacker to target region\n const attackDirection = this.normalizeVector3D(\n this.subtractVectors3D(defenderPosition, attackerPosition),\n );\n\n const raycastQuery: RaycastQuery = {\n origin: attackerPosition,\n direction: attackDirection,\n maxDistance: attackReach.effectiveReach,\n targetRegion,\n };\n\n // Check intersection with target bounding box\n const intersection = this.raycastBoundingBox(\n raycastQuery,\n targetBox,\n defenderPosition,\n );\n\n if (!intersection) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Determine specific vital point within region\n const vitalPoint = this.identifyVitalPoint(\n targetRegion,\n intersection.point,\n defenderPosition,\n );\n\n if (!vitalPoint) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Calculate accuracy (how close to vital point center)\n const accuracy = this.calculateHitAccuracy(\n intersection.point,\n vitalPoint,\n defenderPosition,\n );\n\n return {\n hit: true,\n region: targetRegion,\n vitalPoint,\n distance,\n accuracy,\n hitPoint: intersection.point,\n };\n }\n\n /**\n * Calculates effective attack reach for a technique.\n *\n * **Korean**: 공격 범위 계산\n *\n * Applies stance modifiers to base technique reach.\n *\n * @param techniqueType - Type of technique\n * @param stance - Current trigram stance\n * @returns Attack reach with all modifiers applied\n *\n * @private\n * @korean 공격범위계산\n */\n private calculateAttackReach(\n techniqueType: TechniqueType,\n stance: TrigramStance,\n ): {\n technique: TechniqueType;\n baseReach: number;\n stance: TrigramStance;\n stanceModifier: number;\n effectiveReach: number;\n } {\n // Use BASE_REACH constant from physics types\n const baseReach = BASE_REACH[techniqueType];\n\n // Use STANCE_REACH_MODIFIERS constant from physics types\n const stanceModifier = STANCE_REACH_MODIFIERS[stance];\n const effectiveReach = baseReach * stanceModifier;\n\n return {\n technique: techniqueType,\n baseReach,\n stance,\n stanceModifier,\n effectiveReach,\n };\n }\n\n /**\n * Performs raycasting against a bounding box.\n *\n * **Korean**: 경계 상자 광선 투사\n *\n * Uses cached geometries from object pool to avoid repeated allocations\n * during combat. Creates a Three.js mesh for the bounding box and performs\n * raycasting to detect intersection points.\n *\n * Uses object pooling for Vector3 allocations to reduce GC pressure.\n *\n * @param query - Raycast query parameters\n * @param box - Bounding box to test\n * @param defenderPosition - Position of the defender\n * @returns Intersection point or null if no hit\n *\n * @private\n * @korean 경계상자광선투사\n */\n private raycastBoundingBox(\n query: RaycastQuery,\n box: BoundingBox,\n defenderPosition: Position3D,\n ): { point: Position3D } | null {\n // Get cached geometry from pool to avoid repeated allocations\n const cacheKey = `${box.type}-${box.region}`;\n let geometry = this.geometryCache.get(cacheKey);\n\n // If not cached (shouldn't happen after initialization), create it\n if (!geometry) {\n geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n\n // Create temporary mesh for raycasting (mesh is lightweight, geometry is cached)\n const mesh = new THREE.Mesh(geometry);\n mesh.position.set(\n defenderPosition.x + box.center.x,\n defenderPosition.y + box.center.y,\n defenderPosition.z + box.center.z,\n );\n\n // Setup raycaster using pooled Vector3 objects to reduce GC pressure\n // Wrap in try-finally to ensure pooled objects are always released\n const origin = ThreeObjectPools.vector3.acquire();\n const direction = ThreeObjectPools.vector3.acquire();\n\n try {\n origin.set(query.origin.x, query.origin.y, query.origin.z);\n direction.set(query.direction.x, query.direction.y, query.direction.z);\n\n this.raycaster.set(origin, direction);\n this.raycaster.far = query.maxDistance;\n\n // Perform raycast\n const intersections = this.raycaster.intersectObject(mesh);\n\n // Clean up temporary mesh (geometry remains cached)\n // Note: mesh.material is undefined, no need to dispose\n\n if (intersections.length > 0) {\n const point = intersections[0].point;\n return {\n point: { x: point.x, y: point.y, z: point.z },\n };\n }\n\n return null;\n } finally {\n // Release pooled objects back to pool (guaranteed even if exception occurs)\n ThreeObjectPools.vector3.release(origin);\n ThreeObjectPools.vector3.release(direction);\n }\n }\n\n /**\n * Creates Three.js geometry for a bounding box.\n *\n * Helper method for geometry cache initialization.\n *\n * @param box - Bounding box specification\n * @returns Three.js geometry\n *\n * @private\n * @korean 경계상자지오메트리생성\n */\n private createGeometryForBox(box: BoundingBox): THREE.BufferGeometry {\n switch (box.type) {\n case \"sphere\":\n return new THREE.SphereGeometry(box.dimensions.x, 8, 8);\n case \"box\":\n return new THREE.BoxGeometry(\n box.dimensions.x,\n box.dimensions.y,\n box.dimensions.z,\n );\n case \"capsule\":\n return new THREE.CapsuleGeometry(\n box.dimensions.x,\n box.dimensions.y,\n 4,\n 8,\n );\n }\n }\n\n /**\n * Identifies the specific vital point hit within a region.\n *\n * **Korean**: 급소 식별\n *\n * Finds the closest vital point to the hit location within the targeted region.\n *\n * @param region - Anatomical region hit\n * @param hitPoint - 3D point where attack intersected\n * @param defenderPosition - Position of the defender\n * @returns Closest vital point or null if none found\n *\n * @private\n * @korean 급소식별\n */\n private identifyVitalPoint(\n region: AnatomicalRegionPhysics,\n hitPoint: Position3D,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): VitalPoint | null {\n // Get all vital points for this region\n const vitalPoints = this.vitalPointsByRegion.get(region);\n if (!vitalPoints || vitalPoints.length === 0) {\n return null;\n }\n\n // Use CoordinateMapper to find the closest vital point in 3D space\n const result = this.coordinateMapper.findClosestVitalPoint(\n hitPoint,\n vitalPoints,\n region,\n );\n\n // For collision detection, we accept any vital point in the region\n // even if it's not very close, as long as the region was hit\n // This provides more forgiving collision detection while still\n // using accurate 3D positioning for scoring/accuracy\n return result\n ? result.vitalPoint\n : vitalPoints.length > 0\n ? vitalPoints[0]\n : null;\n }\n\n /**\n * Calculates hit accuracy based on distance to vital point center.\n *\n * **Korean**: 타격 정확도 계산\n *\n * Accuracy decreases linearly with distance from vital point center.\n * Perfect accuracy (1.0) at center, decreasing to 0 at 5cm radius.\n *\n * @param hitPoint - Point where attack landed\n * @param vitalPoint - Target vital point\n * @param defenderPosition - Position of the defender\n * @returns Accuracy value from 0 to 1\n *\n * @private\n * @korean 타격정확도계산\n */\n private calculateHitAccuracy(\n hitPoint: Position3D,\n vitalPoint: VitalPoint,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): number {\n // Use CoordinateMapper to convert vital point to 3D and calculate distance\n const distance = this.coordinateMapper.distanceToVitalPoint(\n hitPoint,\n vitalPoint,\n );\n\n // Accuracy calculation: Perfect (1.0) at center, decreasing to 0 at 5cm radius\n const maxDistance = 0.05; // 5cm radius for vital points\n const accuracy = Math.max(0, 1 - distance / maxDistance);\n\n return accuracy;\n }\n\n /**\n * Calculates Euclidean distance between two 3D points.\n *\n * @param pos1 - First position\n * @param pos2 - Second position\n * @returns Distance in meters\n *\n * @private\n * @korean 3D거리계산\n */\n private calculateDistance3D(pos1: Position3D, pos2: Position3D): number {\n const dx = pos1.x - pos2.x;\n const dy = pos1.y - pos2.y;\n const dz = pos1.z - pos2.z;\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n\n /**\n * Subtracts one 3D vector from another.\n *\n * @param v1 - First vector\n * @param v2 - Second vector\n * @returns Resulting vector\n *\n * @private\n * @korean 벡터빼기\n */\n private subtractVectors3D(v1: Position3D, v2: Position3D): Position3D {\n return {\n x: v1.x - v2.x,\n y: v1.y - v2.y,\n z: v1.z - v2.z,\n };\n }\n\n /**\n * Normalizes a 3D vector to unit length.\n *\n * @param vec - Vector to normalize\n * @returns Normalized vector\n *\n * @private\n * @korean 벡터정규화\n */\n private normalizeVector3D(vec: Position3D): Position3D {\n const length = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);\n if (length === 0) {\n return { x: 0, y: 0, z: 1 }; // Default forward direction\n }\n return {\n x: vec.x / length,\n y: vec.y / length,\n z: vec.z / length,\n };\n }\n\n /**\n * Parses technique type from string.\n *\n * @param techniqueTypeStr - Technique type as string\n * @returns Parsed technique type\n *\n * @private\n * @korean 기술유형파싱\n */\n private parseTechniqueType(techniqueTypeStr?: string): TechniqueType {\n const typeMap: Record<string, TechniqueType> = {\n punch: \"punch\",\n kick: \"kick\",\n elbow: \"elbow\",\n knee: \"knee\",\n pressure_point: \"pressure_point\",\n strike: \"punch\", // Default strike to punch\n };\n\n return typeMap[techniqueTypeStr ?? \"punch\"] ?? \"punch\";\n }\n\n /**\n * Initializes bounding boxes for all anatomical regions.\n *\n * Creates collision volumes for the 5 anatomical regions using the\n * ANATOMICAL_DIMENSIONS constants from the physics types module.\n *\n * @private\n * @korean 경계상자초기화\n */\n private initializeBoundingBoxes(): void {\n // Head: Sphere (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"head\", {\n type: ANATOMICAL_DIMENSIONS.head.type,\n center: ANATOMICAL_DIMENSIONS.head.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.head.radius,\n y: 0,\n z: 0,\n }, // radius only\n region: \"head\",\n });\n\n // Neck: Capsule (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"neck\", {\n type: ANATOMICAL_DIMENSIONS.neck.type,\n center: ANATOMICAL_DIMENSIONS.neck.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.neck.radius,\n y: ANATOMICAL_DIMENSIONS.neck.height,\n z: 0,\n }, // radius and height\n region: \"neck\",\n });\n\n // Torso: Box (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"torso\", {\n type: ANATOMICAL_DIMENSIONS.torso.type,\n center: ANATOMICAL_DIMENSIONS.torso.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.torso.width,\n y: ANATOMICAL_DIMENSIONS.torso.height,\n z: ANATOMICAL_DIMENSIONS.torso.depth,\n }, // width, height, depth\n region: \"torso\",\n });\n\n // Arms: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"arms\", {\n type: ANATOMICAL_DIMENSIONS.arms.type,\n center: ANATOMICAL_DIMENSIONS.arms.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.arms.radius,\n y: ANATOMICAL_DIMENSIONS.arms.height,\n z: 0,\n }, // radius and length\n region: \"arms\",\n });\n\n // Legs: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"legs\", {\n type: ANATOMICAL_DIMENSIONS.legs.type,\n center: ANATOMICAL_DIMENSIONS.legs.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.legs.radius,\n y: ANATOMICAL_DIMENSIONS.legs.height,\n z: 0,\n }, // radius and length\n region: \"legs\",\n });\n }\n\n /**\n * Initializes geometry cache for object pooling.\n *\n * Pre-creates all geometries needed for raycasting to avoid repeated\n * allocations during combat. Critical for maintaining 60fps with up to\n * 100 collision checks per frame.\n *\n * @private\n * @korean 지오메트리캐시초기화\n */\n private initializeGeometryCache(): void {\n // Pre-create and cache geometries for all bounding boxes\n for (const box of this.boundingBoxes.values()) {\n const cacheKey = `${box.type}-${box.region}`;\n const geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n }\n\n /**\n * Organizes vital points by anatomical region for efficient lookup.\n *\n * NOTE: This categorization currently uses y-coordinate thresholds that assume\n * positions are in meters. However, VITAL_POINTS_DATA uses pixel coordinates\n * (e.g., y: 50), which will cause incorrect categorization. Most vital points\n * will end up in the \"legs\" region since pixel y-coordinates are typically less\n * than 0.8.\n *\n * TODO: After implementing 2D→3D coordinate mapping, update this method to use\n * the converted 3D positions, or use the anatomical region data that may already\n * exist in the vital points data structure.\n *\n * Categorizes the 70 vital points into their respective regions:\n * - Head: 10 vital points\n * - Neck: 8 vital points\n * - Torso: 20 vital points\n * - Arms: 16 vital points\n * - Legs: 16 vital points\n *\n * @private\n * @korean 급소영역별정리\n */\n private organizeVitalPointsByRegion(): void {\n const regionMap: Map<AnatomicalRegionPhysics, VitalPoint[]> = new Map([\n [\"head\", []],\n [\"neck\", []],\n [\"torso\", []],\n [\"arms\", []],\n [\"legs\", []],\n ]);\n\n // Use existing VitalPointsData categorization by ID prefix\n // This is more reliable than coordinate-based heuristics\n for (const vp of VITAL_POINTS_DATA) {\n let region: AnatomicalRegionPhysics;\n\n // Categorize by ID prefix (existing VitalPointsData convention)\n // Check neck-related patterns first (more specific) before head (broader)\n if (\n vp.id.includes(\"_neck\") ||\n vp.id.includes(\"_throat\") ||\n vp.id === \"head_side_neck\" ||\n vp.id === \"head_throat\"\n ) {\n region = \"neck\";\n } else if (vp.id.startsWith(\"head_\")) {\n region = \"head\";\n } else if (vp.id.startsWith(\"torso_\")) {\n region = \"torso\";\n } else if (\n vp.id.startsWith(\"arm_left_\") ||\n vp.id.startsWith(\"arm_right_\")\n ) {\n region = \"arms\";\n } else if (\n vp.id.startsWith(\"leg_left_\") ||\n vp.id.startsWith(\"leg_right_\")\n ) {\n region = \"legs\";\n } else {\n // Default to torso for uncategorized points (core region)\n region = \"torso\";\n }\n\n const list = regionMap.get(region);\n if (list) {\n list.push(vp);\n }\n }\n\n this.vitalPointsByRegion = regionMap;\n }\n\n /**\n * Gets the bounding box for an anatomical region.\n *\n * @param region - Anatomical region\n * @returns Bounding box or undefined if not found\n *\n * @korean 경계상자조회\n */\n getBoundingBox(region: AnatomicalRegionPhysics): BoundingBox | undefined {\n return this.boundingBoxes.get(region);\n }\n\n /**\n * Gets all bounding boxes.\n *\n * @returns Map of all bounding boxes by region\n *\n * @korean 모든경계상자조회\n */\n getAllBoundingBoxes(): ReadonlyMap<AnatomicalRegionPhysics, BoundingBox> {\n return this.boundingBoxes;\n }\n\n /**\n * Gets vital points for a specific region.\n *\n * @param region - Anatomical region\n * @returns Array of vital points in that region\n *\n * @korean 영역별급소조회\n */\n getVitalPointsInRegion(\n region: AnatomicalRegionPhysics,\n ): readonly VitalPoint[] {\n return this.vitalPointsByRegion.get(region) ?? [];\n }\n}\n\nexport default CollisionDetection;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollisionDetection.js","names":[],"sources":["../../../src/systems/physics/CollisionDetection.ts"],"sourcesContent":["/**\n * Collision Detection System for Black Trigram combat physics.\n *\n * **Korean**: 충돌 감지 시스템\n *\n * Implements precise collision detection for the 70 vital points combat system using:\n * - Broad-phase AABB (Axis-Aligned Bounding Box) checks for performance\n * - Narrow-phase raycasting for precise vital point detection\n * - Attack reach validation based on technique and stance\n * - Bounding boxes for 5 anatomical regions\n *\n * ## Architecture\n *\n * The system uses a two-phase collision detection approach:\n *\n * 1. **Broad-phase**: Fast AABB checks to eliminate impossible collisions\n * 2. **Narrow-phase**: Precise raycasting to identify specific vital points\n *\n * This ensures 60fps performance even with complex collision queries.\n *\n * ## Performance\n *\n * - Broad-phase: O(1) per check (simple distance comparison)\n * - Narrow-phase: O(n) where n = vital points in region (typically 8-20)\n * - Target: <100 collision checks per frame for 60fps\n *\n * @example\n * ```typescript\n * const collision = new CollisionDetection();\n *\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 }, // Attacker position\n * { x: 0, y: 0, z: 6.5 }, // Defender position\n * { id: \"kick\", type: \"kick\" }, // Technique\n * TrigramStance.LI, // Attacker stance\n * \"torso\" // Target region\n * );\n *\n * if (result.hit) {\n * console.log(`Hit ${result.vitalPoint?.names.english}!`);\n * console.log(`Accuracy: ${(result.accuracy * 100).toFixed(1)}%`);\n * }\n * ```\n *\n * @module systems/physics/CollisionDetection\n * @category Combat Systems\n * @korean 충돌감지시스템\n */\n\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../types/common\";\nimport type {\n AnatomicalRegionPhysics,\n BoundingBox,\n CollisionResult,\n Position3D,\n RaycastQuery,\n TechniqueType,\n} from \"../../types/physics\";\nimport {\n ANATOMICAL_DIMENSIONS,\n BASE_REACH,\n STANCE_REACH_MODIFIERS,\n} from \"../../types/physics\";\nimport type { VitalPoint } from \"../vitalpoint/types\";\nimport { VITAL_POINTS_DATA } from \"../vitalpoint/VitalPointsData\";\nimport { CoordinateMapper } from \"./CoordinateMapper\";\nimport { ThreeObjectPools } from \"../../utils/threeObjectPool\";\n\n/**\n * Collision Detection Engine for combat physics.\n *\n * **Korean**: 충돌 감지 엔진\n *\n * Provides efficient collision detection for combat using bounding boxes and raycasting.\n * Optimized for 60fps performance with multiple simultaneous collision checks.\n *\n * @category Combat Systems\n * @korean 충돌감지엔진\n */\nexport class CollisionDetection {\n private readonly boundingBoxes: Map<AnatomicalRegionPhysics, BoundingBox> =\n new Map();\n private readonly raycaster: THREE.Raycaster = new THREE.Raycaster();\n private vitalPointsByRegion: Map<AnatomicalRegionPhysics, VitalPoint[]> =\n new Map();\n\n // Geometry cache for object pooling to avoid repeated allocations during combat\n private readonly geometryCache: Map<string, THREE.BufferGeometry> = new Map();\n\n // Coordinate mapper for 2D→3D conversion\n private readonly coordinateMapper: CoordinateMapper;\n\n /**\n * Creates a new CollisionDetection instance.\n *\n * Initializes bounding boxes for all anatomical regions and organizes\n * vital points by region for efficient lookup.\n */\n constructor() {\n this.coordinateMapper = new CoordinateMapper();\n this.initializeBoundingBoxes();\n this.organizeVitalPointsByRegion();\n this.initializeGeometryCache();\n }\n\n /**\n * Cleans up Three.js resources.\n *\n * **Korean**: 자원 정리\n *\n * Disposes of cached geometries and releases memory to prevent leaks.\n * Should be called when the CollisionDetection instance is no longer needed.\n *\n * @korean 자원정리\n */\n public dispose(): void {\n // Dispose all cached geometries\n for (const geometry of this.geometryCache.values()) {\n geometry.dispose();\n }\n this.geometryCache.clear();\n }\n\n /**\n * Checks if an attack hits the defender.\n *\n * **Korean**: 공격 타격 확인\n *\n * Performs two-phase collision detection:\n * 1. Calculate effective attack reach based on technique and stance\n * 2. Broad-phase: Check if defender is within reach\n * 3. Broad-phase: Check if target region's AABB is within reach\n * 4. Narrow-phase: Raycast to find precise vital point hit\n *\n * @param attackerPosition - 3D position of the attacker\n * @param defenderPosition - 3D position of the defender\n * @param technique - Technique being used with type information\n * @param attackerStance - Attacker's current trigram stance\n * @param targetRegion - Anatomical region being targeted\n * @returns Collision result with hit status, vital point, distance, and accuracy\n *\n * @example\n * ```typescript\n * const result = collision.checkAttackHit(\n * { x: 0, y: 0, z: 5 },\n * { x: 0, y: 0, z: 6 },\n * { type: \"punch\" },\n * TrigramStance.GEON,\n * \"head\"\n * );\n * ```\n *\n * @korean 공격타격확인\n */\n checkAttackHit(\n attackerPosition: Position3D,\n defenderPosition: Position3D,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Technique object has dynamic properties from combat system\n technique: { type?: string; [key: string]: any },\n attackerStance: TrigramStance,\n targetRegion: AnatomicalRegionPhysics,\n ): CollisionResult {\n // Calculate effective attack reach\n const techniqueType = this.parseTechniqueType(technique.type);\n const attackReach = this.calculateAttackReach(\n techniqueType,\n attackerStance,\n );\n\n // Calculate distance between attacker and defender\n const distance = this.calculateDistance3D(\n attackerPosition,\n defenderPosition,\n );\n\n // Broad-phase: Check if defender is within reach\n if (distance > attackReach.effectiveReach) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Get target region bounding box\n const targetBox = this.boundingBoxes.get(targetRegion);\n if (!targetBox) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Narrow-phase: Raycast from attacker to target region\n const attackDirection = this.normalizeVector3D(\n this.subtractVectors3D(defenderPosition, attackerPosition),\n );\n\n const raycastQuery: RaycastQuery = {\n origin: attackerPosition,\n direction: attackDirection,\n maxDistance: attackReach.effectiveReach,\n targetRegion,\n };\n\n // Check intersection with target bounding box\n const intersection = this.raycastBoundingBox(\n raycastQuery,\n targetBox,\n defenderPosition,\n );\n\n if (!intersection) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Determine specific vital point within region\n const vitalPoint = this.identifyVitalPoint(\n targetRegion,\n intersection.point,\n defenderPosition,\n );\n\n if (!vitalPoint) {\n return {\n hit: false,\n distance,\n accuracy: 0,\n };\n }\n\n // Calculate accuracy (how close to vital point center)\n const accuracy = this.calculateHitAccuracy(\n intersection.point,\n vitalPoint,\n defenderPosition,\n );\n\n return {\n hit: true,\n region: targetRegion,\n vitalPoint,\n distance,\n accuracy,\n hitPoint: intersection.point,\n };\n }\n\n /**\n * Calculates effective attack reach for a technique.\n *\n * **Korean**: 공격 범위 계산\n *\n * Applies stance modifiers to base technique reach.\n *\n * @param techniqueType - Type of technique\n * @param stance - Current trigram stance\n * @returns Attack reach with all modifiers applied\n *\n * @private\n * @korean 공격범위계산\n */\n private calculateAttackReach(\n techniqueType: TechniqueType,\n stance: TrigramStance,\n ): {\n technique: TechniqueType;\n baseReach: number;\n stance: TrigramStance;\n stanceModifier: number;\n effectiveReach: number;\n } {\n // Use BASE_REACH constant from physics types\n const baseReach = BASE_REACH[techniqueType];\n\n // Use STANCE_REACH_MODIFIERS constant from physics types\n const stanceModifier = STANCE_REACH_MODIFIERS[stance];\n const effectiveReach = baseReach * stanceModifier;\n\n return {\n technique: techniqueType,\n baseReach,\n stance,\n stanceModifier,\n effectiveReach,\n };\n }\n\n /**\n * Performs raycasting against a bounding box.\n *\n * **Korean**: 경계 상자 광선 투사\n *\n * Uses cached geometries from object pool to avoid repeated allocations\n * during combat. Creates a Three.js mesh for the bounding box and performs\n * raycasting to detect intersection points.\n *\n * Uses object pooling for Vector3 allocations to reduce GC pressure.\n *\n * @param query - Raycast query parameters\n * @param box - Bounding box to test\n * @param defenderPosition - Position of the defender\n * @returns Intersection point or null if no hit\n *\n * @private\n * @korean 경계상자광선투사\n */\n private raycastBoundingBox(\n query: RaycastQuery,\n box: BoundingBox,\n defenderPosition: Position3D,\n ): { point: Position3D } | null {\n // Get cached geometry from pool to avoid repeated allocations\n const cacheKey = `${box.type}-${box.region}`;\n let geometry = this.geometryCache.get(cacheKey);\n\n // If not cached (shouldn't happen after initialization), create it\n if (!geometry) {\n geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n\n // Create temporary mesh for raycasting (mesh is lightweight, geometry is cached)\n const mesh = new THREE.Mesh(geometry);\n mesh.position.set(\n defenderPosition.x + box.center.x,\n defenderPosition.y + box.center.y,\n defenderPosition.z + box.center.z,\n );\n\n // Setup raycaster using pooled Vector3 objects to reduce GC pressure\n // Wrap in try-finally to ensure pooled objects are always released\n const origin = ThreeObjectPools.vector3.acquire();\n const direction = ThreeObjectPools.vector3.acquire();\n\n try {\n origin.set(query.origin.x, query.origin.y, query.origin.z);\n direction.set(query.direction.x, query.direction.y, query.direction.z);\n\n this.raycaster.set(origin, direction);\n this.raycaster.far = query.maxDistance;\n\n // Perform raycast\n const intersections = this.raycaster.intersectObject(mesh);\n\n // Clean up temporary mesh (geometry remains cached)\n // Note: mesh.material is undefined, no need to dispose\n\n if (intersections.length > 0) {\n const point = intersections[0].point;\n return {\n point: { x: point.x, y: point.y, z: point.z },\n };\n }\n\n return null;\n } finally {\n // Release pooled objects back to pool (guaranteed even if exception occurs)\n ThreeObjectPools.vector3.release(origin);\n ThreeObjectPools.vector3.release(direction);\n }\n }\n\n /**\n * Creates Three.js geometry for a bounding box.\n *\n * Helper method for geometry cache initialization.\n *\n * @param box - Bounding box specification\n * @returns Three.js geometry\n *\n * @private\n * @korean 경계상자지오메트리생성\n */\n private createGeometryForBox(box: BoundingBox): THREE.BufferGeometry {\n switch (box.type) {\n case \"sphere\":\n return new THREE.SphereGeometry(box.dimensions.x, 8, 8);\n case \"box\":\n return new THREE.BoxGeometry(\n box.dimensions.x,\n box.dimensions.y,\n box.dimensions.z,\n );\n case \"capsule\":\n return new THREE.CapsuleGeometry(\n box.dimensions.x,\n box.dimensions.y,\n 4,\n 8,\n );\n }\n }\n\n /**\n * Identifies the specific vital point hit within a region.\n *\n * **Korean**: 급소 식별\n *\n * Finds the closest vital point to the hit location within the targeted region.\n *\n * @param region - Anatomical region hit\n * @param hitPoint - 3D point where attack intersected\n * @param defenderPosition - Position of the defender\n * @returns Closest vital point or null if none found\n *\n * @private\n * @korean 급소식별\n */\n private identifyVitalPoint(\n region: AnatomicalRegionPhysics,\n hitPoint: Position3D,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): VitalPoint | null {\n // Get all vital points for this region\n const vitalPoints = this.vitalPointsByRegion.get(region);\n if (!vitalPoints || vitalPoints.length === 0) {\n return null;\n }\n\n // Use CoordinateMapper to find the closest vital point in 3D space\n const result = this.coordinateMapper.findClosestVitalPoint(\n hitPoint,\n vitalPoints,\n region,\n );\n\n // For collision detection, we accept any vital point in the region\n // even if it's not very close, as long as the region was hit\n // This provides more forgiving collision detection while still\n // using accurate 3D positioning for scoring/accuracy\n return result\n ? result.vitalPoint\n : vitalPoints.length > 0\n ? vitalPoints[0]\n : null;\n }\n\n /**\n * Calculates hit accuracy based on distance to vital point center.\n *\n * **Korean**: 타격 정확도 계산\n *\n * Accuracy decreases linearly with distance from vital point center.\n * Perfect accuracy (1.0) at center, decreasing to 0 at 5cm radius.\n *\n * @param hitPoint - Point where attack landed\n * @param vitalPoint - Target vital point\n * @param defenderPosition - Position of the defender\n * @returns Accuracy value from 0 to 1\n *\n * @private\n * @korean 타격정확도계산\n */\n private calculateHitAccuracy(\n hitPoint: Position3D,\n vitalPoint: VitalPoint,\n _defenderPosition: Position3D, // Prefixed with _ to indicate intentionally unused\n ): number {\n // Use CoordinateMapper to convert vital point to 3D and calculate distance\n const distance = this.coordinateMapper.distanceToVitalPoint(\n hitPoint,\n vitalPoint,\n );\n\n // Accuracy calculation: Perfect (1.0) at center, decreasing to 0 at 5cm radius\n const maxDistance = 0.05; // 5cm radius for vital points\n const accuracy = Math.max(0, 1 - distance / maxDistance);\n\n return accuracy;\n }\n\n /**\n * Calculates Euclidean distance between two 3D points.\n *\n * @param pos1 - First position\n * @param pos2 - Second position\n * @returns Distance in meters\n *\n * @private\n * @korean 3D거리계산\n */\n private calculateDistance3D(pos1: Position3D, pos2: Position3D): number {\n const dx = pos1.x - pos2.x;\n const dy = pos1.y - pos2.y;\n const dz = pos1.z - pos2.z;\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n\n /**\n * Subtracts one 3D vector from another.\n *\n * @param v1 - First vector\n * @param v2 - Second vector\n * @returns Resulting vector\n *\n * @private\n * @korean 벡터빼기\n */\n private subtractVectors3D(v1: Position3D, v2: Position3D): Position3D {\n return {\n x: v1.x - v2.x,\n y: v1.y - v2.y,\n z: v1.z - v2.z,\n };\n }\n\n /**\n * Normalizes a 3D vector to unit length.\n *\n * @param vec - Vector to normalize\n * @returns Normalized vector\n *\n * @private\n * @korean 벡터정규화\n */\n private normalizeVector3D(vec: Position3D): Position3D {\n const length = Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);\n if (length === 0) {\n return { x: 0, y: 0, z: 1 }; // Default forward direction\n }\n return {\n x: vec.x / length,\n y: vec.y / length,\n z: vec.z / length,\n };\n }\n\n /**\n * Parses technique type from string.\n *\n * @param techniqueTypeStr - Technique type as string\n * @returns Parsed technique type\n *\n * @private\n * @korean 기술유형파싱\n */\n private parseTechniqueType(techniqueTypeStr?: string): TechniqueType {\n const typeMap: Record<string, TechniqueType> = {\n punch: \"punch\",\n kick: \"kick\",\n elbow: \"elbow\",\n knee: \"knee\",\n pressure_point: \"pressure_point\",\n strike: \"punch\", // Default strike to punch\n };\n\n return typeMap[techniqueTypeStr ?? \"punch\"] ?? \"punch\";\n }\n\n /**\n * Initializes bounding boxes for all anatomical regions.\n *\n * Creates collision volumes for the 5 anatomical regions using the\n * ANATOMICAL_DIMENSIONS constants from the physics types module.\n *\n * @private\n * @korean 경계상자초기화\n */\n private initializeBoundingBoxes(): void {\n // Head: Sphere (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"head\", {\n type: ANATOMICAL_DIMENSIONS.head.type,\n center: ANATOMICAL_DIMENSIONS.head.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.head.radius,\n y: 0,\n z: 0,\n }, // radius only\n region: \"head\",\n });\n\n // Neck: Capsule (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"neck\", {\n type: ANATOMICAL_DIMENSIONS.neck.type,\n center: ANATOMICAL_DIMENSIONS.neck.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.neck.radius,\n y: ANATOMICAL_DIMENSIONS.neck.height,\n z: 0,\n }, // radius and height\n region: \"neck\",\n });\n\n // Torso: Box (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"torso\", {\n type: ANATOMICAL_DIMENSIONS.torso.type,\n center: ANATOMICAL_DIMENSIONS.torso.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.torso.width,\n y: ANATOMICAL_DIMENSIONS.torso.height,\n z: ANATOMICAL_DIMENSIONS.torso.depth,\n }, // width, height, depth\n region: \"torso\",\n });\n\n // Arms: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"arms\", {\n type: ANATOMICAL_DIMENSIONS.arms.type,\n center: ANATOMICAL_DIMENSIONS.arms.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.arms.radius,\n y: ANATOMICAL_DIMENSIONS.arms.height,\n z: 0,\n }, // radius and length\n region: \"arms\",\n });\n\n // Legs: Capsules (from ANATOMICAL_DIMENSIONS)\n this.boundingBoxes.set(\"legs\", {\n type: ANATOMICAL_DIMENSIONS.legs.type,\n center: ANATOMICAL_DIMENSIONS.legs.center,\n dimensions: {\n x: ANATOMICAL_DIMENSIONS.legs.radius,\n y: ANATOMICAL_DIMENSIONS.legs.height,\n z: 0,\n }, // radius and length\n region: \"legs\",\n });\n }\n\n /**\n * Initializes geometry cache for object pooling.\n *\n * Pre-creates all geometries needed for raycasting to avoid repeated\n * allocations during combat. Critical for maintaining 60fps with up to\n * 100 collision checks per frame.\n *\n * @private\n * @korean 지오메트리캐시초기화\n */\n private initializeGeometryCache(): void {\n // Pre-create and cache geometries for all bounding boxes\n for (const box of this.boundingBoxes.values()) {\n const cacheKey = `${box.type}-${box.region}`;\n const geometry = this.createGeometryForBox(box);\n this.geometryCache.set(cacheKey, geometry);\n }\n }\n\n /**\n * Organizes vital points by anatomical region for efficient lookup.\n *\n * NOTE: This categorization currently uses y-coordinate thresholds that assume\n * positions are in meters. However, VITAL_POINTS_DATA uses pixel coordinates\n * (e.g., y: 50), which will cause incorrect categorization. Most vital points\n * will end up in the \"legs\" region since pixel y-coordinates are typically less\n * than 0.8.\n *\n * TODO: After implementing 2D→3D coordinate mapping, update this method to use\n * the converted 3D positions, or use the anatomical region data that may already\n * exist in the vital points data structure.\n *\n * Categorizes the 70 vital points into their respective regions:\n * - Head: 10 vital points\n * - Neck: 8 vital points\n * - Torso: 20 vital points\n * - Arms: 16 vital points\n * - Legs: 16 vital points\n *\n * @private\n * @korean 급소영역별정리\n */\n private organizeVitalPointsByRegion(): void {\n const regionMap: Map<AnatomicalRegionPhysics, VitalPoint[]> = new Map([\n [\"head\", []],\n [\"neck\", []],\n [\"torso\", []],\n [\"arms\", []],\n [\"legs\", []],\n ]);\n\n // Use existing VitalPointsData categorization by ID prefix\n // This is more reliable than coordinate-based heuristics\n for (const vp of VITAL_POINTS_DATA) {\n let region: AnatomicalRegionPhysics;\n\n // Categorize by ID prefix (existing VitalPointsData convention)\n // Check neck-related patterns first (more specific) before head (broader)\n if (\n vp.id.includes(\"_neck\") ||\n vp.id.includes(\"_throat\") ||\n vp.id === \"head_side_neck\" ||\n vp.id === \"head_throat\"\n ) {\n region = \"neck\";\n } else if (vp.id.startsWith(\"head_\")) {\n region = \"head\";\n } else if (vp.id.startsWith(\"torso_\")) {\n region = \"torso\";\n } else if (\n vp.id.startsWith(\"arm_left_\") ||\n vp.id.startsWith(\"arm_right_\")\n ) {\n region = \"arms\";\n } else if (\n vp.id.startsWith(\"leg_left_\") ||\n vp.id.startsWith(\"leg_right_\")\n ) {\n region = \"legs\";\n } else {\n // Default to torso for uncategorized points (core region)\n region = \"torso\";\n }\n\n const list = regionMap.get(region);\n if (list) {\n list.push(vp);\n }\n }\n\n this.vitalPointsByRegion = regionMap;\n }\n\n /**\n * Gets the bounding box for an anatomical region.\n *\n * @param region - Anatomical region\n * @returns Bounding box or undefined if not found\n *\n * @korean 경계상자조회\n */\n getBoundingBox(region: AnatomicalRegionPhysics): BoundingBox | undefined {\n return this.boundingBoxes.get(region);\n }\n\n /**\n * Gets all bounding boxes.\n *\n * @returns Map of all bounding boxes by region\n *\n * @korean 모든경계상자조회\n */\n getAllBoundingBoxes(): ReadonlyMap<AnatomicalRegionPhysics, BoundingBox> {\n return this.boundingBoxes;\n }\n\n /**\n * Gets vital points for a specific region.\n *\n * @param region - Anatomical region\n * @returns Array of vital points in that region\n *\n * @korean 영역별급소조회\n */\n getVitalPointsInRegion(\n region: AnatomicalRegionPhysics,\n ): readonly VitalPoint[] {\n return this.vitalPointsByRegion.get(region) ?? [];\n }\n}\n\nexport default CollisionDetection;\n"],"mappings":"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"}
|