blacktrigram 0.7.47 → 0.7.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +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/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +29 -25
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.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/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.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/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.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/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +3 -11
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.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/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.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/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.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/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.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/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.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/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.d.ts.map +1 -1
- package/lib/hooks/useHUDLayout.js +3 -2
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.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/KickPhaseApplicator.d.ts +6 -0
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +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 +21 -0
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -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.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +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/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
- package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +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.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js +15 -15
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
- package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +74 -12
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js +34 -0
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/index.d.ts +1 -1
- package/lib/systems/animation/core/index.d.ts.map +1 -1
- package/lib/systems/animation/core/types.d.ts +24 -0
- package/lib/systems/animation/core/types.d.ts.map +1 -1
- package/lib/systems/animation/core/types.js +27 -11
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.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/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js +19 -19
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js +17 -17
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js +24 -24
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js +21 -21
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.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/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.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 +6 -6
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.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/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.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/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.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/systems/vitalpoint/VitalPointsData.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/animations.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/index.js.map +1 -1
- package/lib/types/constants/layout.d.ts +21 -0
- package/lib/types/constants/layout.d.ts.map +1 -1
- package/lib/types/constants/layout.js +22 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/constants/ui.js.map +1 -1
- package/lib/types/facial.js +19 -19
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/muscle.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/types/player-visual.d.ts +1 -1
- package/lib/types/player-visual.d.ts.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js +6 -7
- 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/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.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/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.d.ts +7 -0
- package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js +16 -2
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointMarker3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/VitalPointMarker3D.tsx"],"sourcesContent":["/**\n * VitalPointMarker3D - Individual vital point marker with hover labels\n *\n * Provides interactive 3D markers for vital points with Korean-English bilingual labels\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { VitalPoint } from \"../../../../systems/vitalpoint/types\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n UI_DIMENSIONS,\n} from \"../../../../types/constants\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../../utils/htmlOverlayHelpers\";\n\n/**\n * Props for VitalPointMarker3D component\n */\nexport interface VitalPointMarker3DProps {\n /** The vital point data to visualize */\n readonly vitalPoint: VitalPoint;\n /** Whether this vital point is currently selected */\n readonly isSelected: boolean;\n /** Whether training mode is active */\n readonly isTraining: boolean;\n /** Whether on mobile device (larger hit targets) */\n readonly isMobile?: boolean;\n /** Callback when vital point is clicked/hit */\n readonly onHit?: (vitalPointId: string) => void;\n /** Base size multiplier (for difficulty modes) */\n readonly sizeMultiplier?: number;\n /** Pulse frequency in Hz (default: 6Hz for selected, 4Hz for training mode) */\n readonly pulseFrequency?: number;\n /** Pulse amplitude (default: 0.25 for selected, 0.15 for training mode) */\n readonly pulseAmplitude?: number;\n /** Maximum emissive intensity for selected/hovered state (default: 3.5) */\n readonly maxEmissiveIntensity?: number;\n}\n\n/**\n * Get color based on vital point severity\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n switch (severity) {\n case VitalPointSeverity.MINOR:\n return KOREAN_COLORS.POSITIVE_GREEN;\n case VitalPointSeverity.MODERATE:\n return KOREAN_COLORS.WARNING_YELLOW;\n case VitalPointSeverity.MAJOR:\n return KOREAN_COLORS.ACCENT_GOLD;\n case VitalPointSeverity.CRITICAL:\n return KOREAN_COLORS.ACCENT_RED;\n case VitalPointSeverity.LETHAL:\n return KOREAN_COLORS.NEGATIVE_RED; // Most severe - red for lethal vital points\n default:\n return KOREAN_COLORS.TEXT_SECONDARY;\n }\n};\n\n/**\n * Base scale offset for pulsing animation\n * This value (1.15) ensures the marker pulses around a slightly enlarged baseline,\n * making the pulsing effect more visible without excessive size variation.\n * Used in useFrame hook for scale calculation (see line ~180).\n */\nconst PULSE_BASE_SCALE = 1.15;\n\n/**\n * VitalPointMarker3D Component\n * Individual 3D marker with hover tooltip\n */\nexport const VitalPointMarker3D: React.FC<VitalPointMarker3DProps> = ({\n vitalPoint,\n isSelected,\n isTraining,\n isMobile = false,\n onHit,\n sizeMultiplier = 1.0,\n pulseFrequency,\n pulseAmplitude,\n maxEmissiveIntensity = 3.5,\n}) => {\n const defaultPulseFrequency = isTraining && !isSelected ? 4 : 6;\n const defaultPulseAmplitude = isTraining && !isSelected ? 0.15 : 0.25;\n \n const activePulseFrequency = pulseFrequency ?? defaultPulseFrequency;\n const activePulseAmplitude = pulseAmplitude ?? defaultPulseAmplitude;\n const meshRef = useRef<THREE.Mesh>(null);\n const [hovered, setHovered] = useState(false);\n\n const baseSize = isMobile ? 0.15 : 0.1;\n const markerSize = baseSize * sizeMultiplier;\n\n const targetScale = useMemo(() => new THREE.Vector3(1, 1, 1), []);\n\n useFrame((state) => {\n if (!meshRef.current) return;\n\n if (isSelected || hovered) {\n const pulse = Math.sin(state.clock.elapsedTime * activePulseFrequency) * activePulseAmplitude + PULSE_BASE_SCALE;\n meshRef.current.scale.setScalar(pulse);\n } else {\n targetScale.set(1, 1, 1);\n meshRef.current.scale.lerp(targetScale, 0.1);\n }\n });\n\n const color = useMemo(\n () => getSeverityColor(vitalPoint.severity),\n [vitalPoint.severity]\n );\n\n const markerMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: isSelected ? KOREAN_COLORS.ACCENT_GOLD : color,\n emissive: isSelected ? KOREAN_COLORS.ACCENT_GOLD : color,\n emissiveIntensity: isSelected || hovered ? maxEmissiveIntensity : 2.0,\n metalness: 0.9, // Increased metalness for more reflective appearance (was 0.8)\n roughness: 0.1, // Reduced roughness for stronger reflections (was 0.2)\n clearcoat: 1.0,\n clearcoatRoughness: 0.05, // Reduced for sharper clearcoat (was 0.1)\n transparent: true,\n opacity: isTraining ? 0.9 : 0.6,\n transmission: 0.1, // Slight transmission for glass-like effect\n thickness: 0.2,\n ior: 2.4, // High IOR for gem-like appearance\n sheen: 0.3, // Increased sheen\n sheenRoughness: 0.1,\n }),\n [isSelected, hovered, color, maxEmissiveIntensity, isTraining]\n );\n\n useEffect(() => {\n return () => {\n markerMaterial.dispose();\n };\n }, [markerMaterial]);\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const tooltipDistanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"text\", isMobile);\n }, [screenWidth, isMobile]);\n\n const tooltipOverlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(\n \"tooltip\",\n false,\n tooltipDistanceFactor,\n true,\n false\n );\n }, [tooltipDistanceFactor]);\n\n const handleClick = useCallback(() => {\n if (isTraining && onHit) {\n onHit(vitalPoint.id);\n }\n }, [isTraining, onHit, vitalPoint.id]);\n\n return (\n <group>\n {/* Hit target sphere\n Note: Three.js 3D objects lack standard DOM accessibility (aria-label, role, etc.).\n For accessible alternatives, see keyboard shortcuts documented in the UI and\n consider future enhancements for screen reader support via Html overlays. */}\n <mesh\n ref={meshRef}\n onClick={handleClick}\n onPointerOver={() => setHovered(true)}\n onPointerOut={() => setHovered(false)}\n name={`vital-point-marker-${vitalPoint.id}`}\n >\n <sphereGeometry args={[markerSize, 16, 16]} />\n <primitive object={markerMaterial} attach=\"material\" />\n </mesh>\n\n {/* Ring indicator for selected marker */}\n {isSelected && (\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[markerSize * 1.2, markerSize * 1.5, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n\n {/* Hover tooltip with Korean-English labels */}\n {hovered && (\n <Html\n position={[0, markerSize + 0.2, 0]}\n center={tooltipOverlayStyle.center}\n distanceFactor={tooltipOverlayStyle.distanceFactor}\n occlude={tooltipOverlayStyle.occlude}\n style={{ pointerEvents: tooltipOverlayStyle.pointerEvents }}\n >\n <div\n style={{\n background: \"rgba(0, 0, 0, 0.9)\",\n border: `2px solid ${isSelected ? \"#ffd700\" : \"#00ffff\"}`,\n borderRadius: \"8px\",\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n whiteSpace: \"nowrap\",\n boxShadow: \"0 0 15px rgba(0, 255, 255, 0.5)\",\n transform: tooltipOverlayStyle.transform,\n zIndex: tooltipOverlayStyle.zIndex,\n }}\n data-testid={`vital-point-tooltip-${vitalPoint.id}`}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n color: \"#ffd700\",\n marginBottom: \"4px\",\n }}\n >\n {vitalPoint.names.korean}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: \"#00ffff\",\n marginBottom: \"4px\",\n }}\n >\n {vitalPoint.names.english}\n </div>\n\n {/* Romanized name */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: \"#999999\",\n fontStyle: \"italic\",\n }}\n >\n {vitalPoint.names.romanized}\n </div>\n\n {/* Severity indicator */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: `#${new THREE.Color(color).getHexString()}`,\n marginTop: \"6px\",\n borderTop: \"1px solid rgba(255, 255, 255, 0.2)\",\n paddingTop: \"4px\",\n }}\n >\n 심각도 | {vitalPoint.severity}\n </div>\n </div>\n </Html>\n )}\n </group>\n );\n};\n\nexport default VitalPointMarker3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VitalPointMarker3D.js","names":[],"sources":["../../../../../src/components/screens/training/components/VitalPointMarker3D.tsx"],"sourcesContent":["/**\n * VitalPointMarker3D - Individual vital point marker with hover labels\n *\n * Provides interactive 3D markers for vital points with Korean-English bilingual labels\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport * as THREE from \"three\";\nimport { VitalPoint } from \"../../../../systems/vitalpoint/types\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport {\n FONT_FAMILY,\n KOREAN_COLORS,\n UI_DIMENSIONS,\n} from \"../../../../types/constants\";\nimport {\n applyHtmlOverlayStyles,\n calculateDistanceFactor,\n} from \"../../../../utils/htmlOverlayHelpers\";\n\n/**\n * Props for VitalPointMarker3D component\n */\nexport interface VitalPointMarker3DProps {\n /** The vital point data to visualize */\n readonly vitalPoint: VitalPoint;\n /** Whether this vital point is currently selected */\n readonly isSelected: boolean;\n /** Whether training mode is active */\n readonly isTraining: boolean;\n /** Whether on mobile device (larger hit targets) */\n readonly isMobile?: boolean;\n /** Callback when vital point is clicked/hit */\n readonly onHit?: (vitalPointId: string) => void;\n /** Base size multiplier (for difficulty modes) */\n readonly sizeMultiplier?: number;\n /** Pulse frequency in Hz (default: 6Hz for selected, 4Hz for training mode) */\n readonly pulseFrequency?: number;\n /** Pulse amplitude (default: 0.25 for selected, 0.15 for training mode) */\n readonly pulseAmplitude?: number;\n /** Maximum emissive intensity for selected/hovered state (default: 3.5) */\n readonly maxEmissiveIntensity?: number;\n}\n\n/**\n * Get color based on vital point severity\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n switch (severity) {\n case VitalPointSeverity.MINOR:\n return KOREAN_COLORS.POSITIVE_GREEN;\n case VitalPointSeverity.MODERATE:\n return KOREAN_COLORS.WARNING_YELLOW;\n case VitalPointSeverity.MAJOR:\n return KOREAN_COLORS.ACCENT_GOLD;\n case VitalPointSeverity.CRITICAL:\n return KOREAN_COLORS.ACCENT_RED;\n case VitalPointSeverity.LETHAL:\n return KOREAN_COLORS.NEGATIVE_RED; // Most severe - red for lethal vital points\n default:\n return KOREAN_COLORS.TEXT_SECONDARY;\n }\n};\n\n/**\n * Base scale offset for pulsing animation\n * This value (1.15) ensures the marker pulses around a slightly enlarged baseline,\n * making the pulsing effect more visible without excessive size variation.\n * Used in useFrame hook for scale calculation (see line ~180).\n */\nconst PULSE_BASE_SCALE = 1.15;\n\n/**\n * VitalPointMarker3D Component\n * Individual 3D marker with hover tooltip\n */\nexport const VitalPointMarker3D: React.FC<VitalPointMarker3DProps> = ({\n vitalPoint,\n isSelected,\n isTraining,\n isMobile = false,\n onHit,\n sizeMultiplier = 1.0,\n pulseFrequency,\n pulseAmplitude,\n maxEmissiveIntensity = 3.5,\n}) => {\n const defaultPulseFrequency = isTraining && !isSelected ? 4 : 6;\n const defaultPulseAmplitude = isTraining && !isSelected ? 0.15 : 0.25;\n \n const activePulseFrequency = pulseFrequency ?? defaultPulseFrequency;\n const activePulseAmplitude = pulseAmplitude ?? defaultPulseAmplitude;\n const meshRef = useRef<THREE.Mesh>(null);\n const [hovered, setHovered] = useState(false);\n\n const baseSize = isMobile ? 0.15 : 0.1;\n const markerSize = baseSize * sizeMultiplier;\n\n const targetScale = useMemo(() => new THREE.Vector3(1, 1, 1), []);\n\n useFrame((state) => {\n if (!meshRef.current) return;\n\n if (isSelected || hovered) {\n const pulse = Math.sin(state.clock.elapsedTime * activePulseFrequency) * activePulseAmplitude + PULSE_BASE_SCALE;\n meshRef.current.scale.setScalar(pulse);\n } else {\n targetScale.set(1, 1, 1);\n meshRef.current.scale.lerp(targetScale, 0.1);\n }\n });\n\n const color = useMemo(\n () => getSeverityColor(vitalPoint.severity),\n [vitalPoint.severity]\n );\n\n const markerMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: isSelected ? KOREAN_COLORS.ACCENT_GOLD : color,\n emissive: isSelected ? KOREAN_COLORS.ACCENT_GOLD : color,\n emissiveIntensity: isSelected || hovered ? maxEmissiveIntensity : 2.0,\n metalness: 0.9, // Increased metalness for more reflective appearance (was 0.8)\n roughness: 0.1, // Reduced roughness for stronger reflections (was 0.2)\n clearcoat: 1.0,\n clearcoatRoughness: 0.05, // Reduced for sharper clearcoat (was 0.1)\n transparent: true,\n opacity: isTraining ? 0.9 : 0.6,\n transmission: 0.1, // Slight transmission for glass-like effect\n thickness: 0.2,\n ior: 2.4, // High IOR for gem-like appearance\n sheen: 0.3, // Increased sheen\n sheenRoughness: 0.1,\n }),\n [isSelected, hovered, color, maxEmissiveIntensity, isTraining]\n );\n\n useEffect(() => {\n return () => {\n markerMaterial.dispose();\n };\n }, [markerMaterial]);\n\n const [screenWidth, setScreenWidth] = useState(() =>\n typeof window !== \"undefined\"\n ? window.innerWidth\n : UI_DIMENSIONS.DEFAULT_SCREEN_WIDTH\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const handleResize = () => {\n setScreenWidth(window.innerWidth);\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n const tooltipDistanceFactor = useMemo(() => {\n return calculateDistanceFactor(screenWidth, \"text\", isMobile);\n }, [screenWidth, isMobile]);\n\n const tooltipOverlayStyle = useMemo(() => {\n return applyHtmlOverlayStyles(\n \"tooltip\",\n false,\n tooltipDistanceFactor,\n true,\n false\n );\n }, [tooltipDistanceFactor]);\n\n const handleClick = useCallback(() => {\n if (isTraining && onHit) {\n onHit(vitalPoint.id);\n }\n }, [isTraining, onHit, vitalPoint.id]);\n\n return (\n <group>\n {/* Hit target sphere\n Note: Three.js 3D objects lack standard DOM accessibility (aria-label, role, etc.).\n For accessible alternatives, see keyboard shortcuts documented in the UI and\n consider future enhancements for screen reader support via Html overlays. */}\n <mesh\n ref={meshRef}\n onClick={handleClick}\n onPointerOver={() => setHovered(true)}\n onPointerOut={() => setHovered(false)}\n name={`vital-point-marker-${vitalPoint.id}`}\n >\n <sphereGeometry args={[markerSize, 16, 16]} />\n <primitive object={markerMaterial} attach=\"material\" />\n </mesh>\n\n {/* Ring indicator for selected marker */}\n {isSelected && (\n <mesh rotation={[Math.PI / 2, 0, 0]}>\n <ringGeometry args={[markerSize * 1.2, markerSize * 1.5, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.6}\n side={THREE.DoubleSide}\n />\n </mesh>\n )}\n\n {/* Hover tooltip with Korean-English labels */}\n {hovered && (\n <Html\n position={[0, markerSize + 0.2, 0]}\n center={tooltipOverlayStyle.center}\n distanceFactor={tooltipOverlayStyle.distanceFactor}\n occlude={tooltipOverlayStyle.occlude}\n style={{ pointerEvents: tooltipOverlayStyle.pointerEvents }}\n >\n <div\n style={{\n background: \"rgba(0, 0, 0, 0.9)\",\n border: `2px solid ${isSelected ? \"#ffd700\" : \"#00ffff\"}`,\n borderRadius: \"8px\",\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n fontFamily: FONT_FAMILY.KOREAN,\n whiteSpace: \"nowrap\",\n boxShadow: \"0 0 15px rgba(0, 255, 255, 0.5)\",\n transform: tooltipOverlayStyle.transform,\n zIndex: tooltipOverlayStyle.zIndex,\n }}\n data-testid={`vital-point-tooltip-${vitalPoint.id}`}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"14px\",\n fontWeight: \"bold\",\n color: \"#ffd700\",\n marginBottom: \"4px\",\n }}\n >\n {vitalPoint.names.korean}\n </div>\n\n {/* English name */}\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: \"#00ffff\",\n marginBottom: \"4px\",\n }}\n >\n {vitalPoint.names.english}\n </div>\n\n {/* Romanized name */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: \"#999999\",\n fontStyle: \"italic\",\n }}\n >\n {vitalPoint.names.romanized}\n </div>\n\n {/* Severity indicator */}\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: `#${new THREE.Color(color).getHexString()}`,\n marginTop: \"6px\",\n borderTop: \"1px solid rgba(255, 255, 255, 0.2)\",\n paddingTop: \"4px\",\n }}\n >\n 심각도 | {vitalPoint.severity}\n </div>\n </div>\n </Html>\n )}\n </group>\n );\n};\n\nexport default VitalPointMarker3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalPointTrainingOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/VitalPointTrainingOverlayHtml.tsx"],"sourcesContent":["/**\n * VitalPointTrainingOverlayHtml - Html overlay for vital point selection\n *\n * Provides vital point selection interface with consistent Korean martial arts theming.\n *\n * @module components/screens/training/components/VitalPointTrainingOverlayHtml\n * @category Training UI\n * @korean 급소훈련오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonGlowEffect,\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for VitalPointTrainingOverlayHtml component\n */\nexport interface VitalPointTrainingOverlayHtmlProps {\n /** Currently selected vital point ID */\n readonly selectedVitalPoint: string | null;\n /** Callback when vital point is selected */\n readonly onVitalPointSelect: (vitalPointId: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Get Korean color from severity\n * Maps vital point severity to KOREAN_COLORS constants\n *\n * @korean 심각도별색상\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n const colorMap: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: KOREAN_COLORS.POSITIVE_GREEN,\n [VitalPointSeverity.MODERATE]: KOREAN_COLORS.WARNING_ORANGE,\n [VitalPointSeverity.MAJOR]: KOREAN_COLORS.ACCENT_GOLD,\n [VitalPointSeverity.CRITICAL]: KOREAN_COLORS.ACCENT_RED,\n [VitalPointSeverity.LETHAL]: KOREAN_COLORS.PRIMARY_RED,\n };\n return colorMap[severity] ?? KOREAN_COLORS.TEXT_TERTIARY;\n};\n\n/**\n * Get difficulty stars\n */\nconst getDifficultyStars = (difficulty: number): string => {\n const stars = Math.ceil(difficulty * 5);\n return \"★\".repeat(Math.min(stars, 5)) + \"☆\".repeat(5 - Math.min(stars, 5));\n};\n\n/**\n * VitalPointTrainingOverlayHtml Component\n * Html overlay for vital point selection and information\n */\nexport const VitalPointTrainingOverlayHtml =\n React.memo<VitalPointTrainingOverlayHtmlProps>(\n ({ selectedVitalPoint, onVitalPointSelect, isMobile }) => {\n const availableVitalPoints = useMemo(\n () => KOREAN_VITAL_POINTS.slice(0, isMobile ? 4 : 6),\n [isMobile],\n );\n\n const selectedPoint = useMemo(\n () => availableVitalPoints.find((p) => p.id === selectedVitalPoint),\n [availableVitalPoints, selectedVitalPoint],\n );\n\n const selectedVitalPointGlow = React.useMemo(\n () => getNeonGlowEffect(KOREAN_COLORS.ACCENT_GOLD, \"strong\", true),\n [],\n );\n\n const panelWidth = isMobile ? 200 : 240;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.7)}`,\n };\n\n return (\n <div style={panelStyle} data-testid=\"vital-point-training-html\">\n {/* Compact header */}\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"13px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA),\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.SECONDARY_MAGENTA,\n \"subtle\",\n ),\n }}\n >\n {formatBilingualText(\"급소 선택\", \"Vital Point\", \"pipe\")}\n </div>\n </div>\n\n {/* Vital Points List */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${SPACING.SM}px`,\n }}\n >\n {availableVitalPoints.map((point) => {\n const isSelected = point.id === selectedVitalPoint;\n const severityColor = getSeverityColor(point.severity);\n const severityColorRgba = hexToRgbaString(severityColor);\n\n const glowEffect = isSelected\n ? selectedVitalPointGlow\n : undefined;\n\n return (\n <button\n key={point.id}\n onClick={() => onVitalPointSelect(point.id)}\n className={`vital-point-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n borderColor: isSelected\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)\n : severityColorRgba,\n fontFamily: FONT_FAMILY.KOREAN,\n boxShadow: glowEffect,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid={`vital-point-${point.id}`}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: severityColorRgba,\n }}\n >\n {point.names.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n {point.names.english}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: `${SPACING.XS}px`,\n }}\n >\n {getDifficultyStars(point.targetingDifficulty ?? 0.5)}\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Selected Point Details */}\n {selectedPoint && (\n <div\n style={{\n marginTop: `${SPACING.MD}px`,\n paddingTop: `${SPACING.MD}px`,\n borderTop: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2)}`,\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n marginBottom: `${SPACING.SM}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {formatBilingualText(\"선택된 급소\", \"Selected Point\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n lineHeight: \"1.4\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <span\n style={{\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {formatBilingualText(\"위치\", \"Location\", \"parentheses\")}:\n </span>{\" \"}\n {selectedPoint.category}\n </div>\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <span\n style={{\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {formatBilingualText(\"심각도\", \"Severity\", \"parentheses\")}:\n </span>{\" \"}\n {selectedPoint.severity}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: `${SPACING.SM}px`,\n }}\n >\n {formatBilingualText(\n selectedPoint.description.korean,\n selectedPoint.description.english,\n \"pipe\",\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.selectedVitalPoint === nextProps.selectedVitalPoint &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onVitalPointSelect === nextProps.onVitalPointSelect\n );\n },\n );\n\nVitalPointTrainingOverlayHtml.displayName = \"VitalPointTrainingOverlayHtml\";\n\nexport default VitalPointTrainingOverlayHtml;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VitalPointTrainingOverlayHtml.js","names":[],"sources":["../../../../../src/components/screens/training/components/VitalPointTrainingOverlayHtml.tsx"],"sourcesContent":["/**\n * VitalPointTrainingOverlayHtml - Html overlay for vital point selection\n *\n * Provides vital point selection interface with consistent Korean martial arts theming.\n *\n * @module components/screens/training/components/VitalPointTrainingOverlayHtml\n * @category Training UI\n * @korean 급소훈련오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { KOREAN_VITAL_POINTS } from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { SPACING } from \"../../../../types/constants/ui\";\nimport { hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport {\n formatBilingualText,\n getEnhancedKoreanOverlayStyles,\n getResponsiveSpacing,\n} from \"../../../../utils/koreanThemeHelpers\";\nimport {\n getNeonGlowEffect,\n getNeonTextShadow,\n getSmoothTransition,\n} from \"../../../../utils/visualEffects\";\nimport \"../training.css\";\n\n/**\n * Props for VitalPointTrainingOverlayHtml component\n */\nexport interface VitalPointTrainingOverlayHtmlProps {\n /** Currently selected vital point ID */\n readonly selectedVitalPoint: string | null;\n /** Callback when vital point is selected */\n readonly onVitalPointSelect: (vitalPointId: string) => void;\n /** Whether on mobile device */\n readonly isMobile: boolean;\n}\n\n/**\n * Get Korean color from severity\n * Maps vital point severity to KOREAN_COLORS constants\n *\n * @korean 심각도별색상\n */\nconst getSeverityColor = (severity: VitalPointSeverity): number => {\n const colorMap: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: KOREAN_COLORS.POSITIVE_GREEN,\n [VitalPointSeverity.MODERATE]: KOREAN_COLORS.WARNING_ORANGE,\n [VitalPointSeverity.MAJOR]: KOREAN_COLORS.ACCENT_GOLD,\n [VitalPointSeverity.CRITICAL]: KOREAN_COLORS.ACCENT_RED,\n [VitalPointSeverity.LETHAL]: KOREAN_COLORS.PRIMARY_RED,\n };\n return colorMap[severity] ?? KOREAN_COLORS.TEXT_TERTIARY;\n};\n\n/**\n * Get difficulty stars\n */\nconst getDifficultyStars = (difficulty: number): string => {\n const stars = Math.ceil(difficulty * 5);\n return \"★\".repeat(Math.min(stars, 5)) + \"☆\".repeat(5 - Math.min(stars, 5));\n};\n\n/**\n * VitalPointTrainingOverlayHtml Component\n * Html overlay for vital point selection and information\n */\nexport const VitalPointTrainingOverlayHtml =\n React.memo<VitalPointTrainingOverlayHtmlProps>(\n ({ selectedVitalPoint, onVitalPointSelect, isMobile }) => {\n const availableVitalPoints = useMemo(\n () => KOREAN_VITAL_POINTS.slice(0, isMobile ? 4 : 6),\n [isMobile],\n );\n\n const selectedPoint = useMemo(\n () => availableVitalPoints.find((p) => p.id === selectedVitalPoint),\n [availableVitalPoints, selectedVitalPoint],\n );\n\n const selectedVitalPointGlow = React.useMemo(\n () => getNeonGlowEffect(KOREAN_COLORS.ACCENT_GOLD, \"strong\", true),\n [],\n );\n\n const panelWidth = isMobile ? 200 : 240;\n const padding = getResponsiveSpacing(\"sm\", isMobile);\n\n const panelStyle: React.CSSProperties = {\n ...getEnhancedKoreanOverlayStyles({\n opacity: 0.88,\n glowIntensity: \"medium\",\n includeGradient: false,\n includeBackdropBlur: true,\n depthLayers: 3,\n }),\n width: `${panelWidth}px`,\n padding: `${padding}px`,\n border: `1px solid ${hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.7)}`,\n };\n\n return (\n <div style={panelStyle} data-testid=\"vital-point-training-html\">\n {/* Compact header */}\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <div\n style={{\n fontSize: isMobile ? \"12px\" : \"13px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA),\n textShadow: getNeonTextShadow(\n KOREAN_COLORS.SECONDARY_MAGENTA,\n \"subtle\",\n ),\n }}\n >\n {formatBilingualText(\"급소 선택\", \"Vital Point\", \"pipe\")}\n </div>\n </div>\n\n {/* Vital Points List */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${SPACING.SM}px`,\n }}\n >\n {availableVitalPoints.map((point) => {\n const isSelected = point.id === selectedVitalPoint;\n const severityColor = getSeverityColor(point.severity);\n const severityColorRgba = hexToRgbaString(severityColor);\n\n const glowEffect = isSelected\n ? selectedVitalPointGlow\n : undefined;\n\n return (\n <button\n key={point.id}\n onClick={() => onVitalPointSelect(point.id)}\n className={`vital-point-button ${isSelected ? \"selected\" : \"\"}`}\n style={{\n borderColor: isSelected\n ? hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD)\n : severityColorRgba,\n fontFamily: FONT_FAMILY.KOREAN,\n boxShadow: glowEffect,\n transition: getSmoothTransition(\"all\", \"normal\"),\n }}\n data-testid={`vital-point-${point.id}`}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: severityColorRgba,\n }}\n >\n {point.names.korean}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n }}\n >\n {point.names.english}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"8px\" : \"9px\",\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n marginTop: `${SPACING.XS}px`,\n }}\n >\n {getDifficultyStars(point.targetingDifficulty ?? 0.5)}\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Selected Point Details */}\n {selectedPoint && (\n <div\n style={{\n marginTop: `${SPACING.MD}px`,\n paddingTop: `${SPACING.MD}px`,\n borderTop: `1px solid ${hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY, 0.2)}`,\n }}\n >\n <div\n style={{\n fontSize: isMobile ? \"11px\" : \"12px\",\n fontWeight: \"bold\",\n color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN),\n marginBottom: `${SPACING.SM}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n {formatBilingualText(\"선택된 급소\", \"Selected Point\", \"pipe\")}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"10px\" : \"11px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY),\n lineHeight: \"1.4\",\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n >\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <span\n style={{\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {formatBilingualText(\"위치\", \"Location\", \"parentheses\")}:\n </span>{\" \"}\n {selectedPoint.category}\n </div>\n <div style={{ marginBottom: `${SPACING.XS}px` }}>\n <span\n style={{\n color: hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD),\n }}\n >\n {formatBilingualText(\"심각도\", \"Severity\", \"parentheses\")}:\n </span>{\" \"}\n {selectedPoint.severity}\n </div>\n <div\n style={{\n fontSize: isMobile ? \"9px\" : \"10px\",\n color: hexToRgbaString(KOREAN_COLORS.TEXT_TERTIARY),\n marginTop: `${SPACING.SM}px`,\n }}\n >\n {formatBilingualText(\n selectedPoint.description.korean,\n selectedPoint.description.english,\n \"pipe\",\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.selectedVitalPoint === nextProps.selectedVitalPoint &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.onVitalPointSelect === nextProps.onVitalPointSelect\n );\n },\n );\n\nVitalPointTrainingOverlayHtml.displayName = \"VitalPointTrainingOverlayHtml\";\n\nexport default VitalPointTrainingOverlayHtml;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingBottomHUD.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/training/components/hud/TrainingBottomHUD.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"TrainingBottomHUD.d.ts","sourceRoot":"","sources":["../../../../../../src/components/screens/training/components/hud/TrainingBottomHUD.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAmB9D,MAAM,WAAW,sBAAsB;IACrC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,sCAAsC;IACtC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,uCAAuC;IACvC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,kCAAkC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,gDAAgD;IAChD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,eAAe,CAAC;IAC7C,mDAAmD;IACnD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IAClE,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAsK9D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BORDERS, GRADIENTS, HUD_STYLE, SPACING } from "../../../../../types/constants/designSystem.js";
|
|
2
2
|
import { Z_INDEX } from "../../../../../types/LayoutTypes.js";
|
|
3
|
-
import { HUD_SIDE_CONTROL_RESERVES } from "../../../../../types/constants/layout.js";
|
|
3
|
+
import { HUD_SIDE_CONTROL_RESERVES, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT } from "../../../../../types/constants/layout.js";
|
|
4
4
|
import { getHUDHeight, getResponsivePadding, shouldShowMobileControls } from "../../../../../utils/responsiveLayout.js";
|
|
5
5
|
import TechniqueBar from "../../../../shared/three/ui/TechniqueBar.js";
|
|
6
6
|
import { VolumeControl } from "../../../../shared/ui/VolumeControl.js";
|
|
@@ -35,7 +35,7 @@ var TrainingBottomHUD = ({ width, height, isMobile = false, positionScale, techn
|
|
|
35
35
|
const showMobileControls = shouldShowMobileControls(width, isMobile);
|
|
36
36
|
const showArchetypeSelector = showMobileControls && onArchetypeSelect !== void 0 && selectedArchetype !== void 0;
|
|
37
37
|
const layout = React.useMemo(() => {
|
|
38
|
-
const hudHeight = getHUDHeight(height,
|
|
38
|
+
const hudHeight = getHUDHeight(height, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT) * positionScale;
|
|
39
39
|
const padding = getResponsivePadding(width) * positionScale;
|
|
40
40
|
const volumeReserve = showMobileControls ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE : HUD_SIDE_CONTROL_RESERVES.VOLUME_CONTROL;
|
|
41
41
|
const archetypeReserve = showArchetypeSelector ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingBottomHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingBottomHUD.tsx"],"sourcesContent":["/**\n * TrainingBottomHUD - Bottom bar for training screen\n *\n * Contains:\n * - Technique Bar (centered)\n * - Volume Control (bottom-right, compact)\n * - Feedback Message (centered overlay)\n * - Archetype Selector (mobile only - bottom-left)\n *\n * Gaming Layout Best Practice:\n * - Width: 100% of screen\n * - Height: Resolution-based ~11% of screen height (40-120px range)\n * - On mobile, consolidates controls from TopHUD\n *\n * @korean 훈련화면 하단 바 - 기술 바, 음량, 피드백, 모바일 원형선택\n */\n\nimport React from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { Technique } from \"../../../../../types\";\nimport { PlayerArchetype } from \"../../../../../types/common\";\nimport { HUD_SIDE_CONTROL_RESERVES } from \"../../../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { SPACING, BORDERS, GRADIENTS, HUD_STYLE } from \"../../../../../types/constants/designSystem\";\nimport {\n getHUDHeight,\n getResponsivePadding,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { TechniqueBar } from \"../../../../shared/three/ui/TechniqueBar\";\nimport { VolumeControl } from \"../../../../shared/ui/VolumeControl\";\nimport { ArchetypeSelectionButtons } from \"../TrainingButtonsOverlayHtml\";\nimport TrainingFeedbackOverlayHtml from \"../TrainingFeedbackOverlayHtml\";\n\n\n\nexport interface TrainingBottomHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Available techniques for the technique bar */\n readonly techniques: readonly Technique[];\n /** Player state for technique availability checks */\n readonly player: PlayerState;\n /** Currently selected technique index */\n readonly selectedIndex: number;\n /** Active technique cooldowns */\n readonly cooldowns: Map<string, number>;\n /** Handler for technique selection */\n readonly onTechniqueSelect: (index: number) => void;\n /** Whether to show feedback message */\n readonly showFeedback: boolean;\n /** Feedback message to display */\n readonly feedbackMessage: string;\n /** Currently selected archetype (for mobile) */\n readonly selectedArchetype?: PlayerArchetype;\n /** Handler for archetype selection (for mobile) */\n readonly onArchetypeSelect?: (archetype: PlayerArchetype) => void;\n /** Handler for playing sound effects (for mobile) */\n readonly onPlaySFX?: (sound: string) => void;\n}\n\n/**\n * TrainingBottomHUD Component\n *\n * Compact bottom bar with centered technique bar, volume control,\n * and archetype selector on mobile. Uses resolution-based sizing.\n */\nexport const TrainingBottomHUD: React.FC<TrainingBottomHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n techniques,\n player,\n selectedIndex,\n cooldowns,\n onTechniqueSelect,\n showFeedback,\n feedbackMessage,\n selectedArchetype,\n onArchetypeSelect,\n onPlaySFX,\n}) => {\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n const showArchetypeSelector =\n showMobileControls &&\n onArchetypeSelect !== undefined &&\n selectedArchetype !== undefined;\n\n const layout = React.useMemo(() => {\n const hudHeight = getHUDHeight(height, 0.11) * positionScale;\n \n const padding = getResponsivePadding(width) * positionScale;\n\n const volumeReserve = showMobileControls\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : HUD_SIDE_CONTROL_RESERVES.VOLUME_CONTROL;\n const archetypeReserve = showArchetypeSelector\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : 0;\n\n const techniqueBarContainerWidth = Math.max(\n 0,\n width -\n padding * 2 -\n volumeReserve -\n archetypeReserve,\n );\n\n return {\n hudHeight,\n padding,\n volumeReserve,\n archetypeReserve,\n techniqueBarContainerWidth,\n };\n }, [width, height, positionScale, showArchetypeSelector, showMobileControls]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n padding: `${layout.padding}px`,\n boxSizing: \"border-box\",\n borderTop: BORDERS.default,\n background: GRADIENTS.verticalReverse(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"training-bottom-hud\"\n >\n {/* Feedback Message (centered in screen, above technique bar) */}\n {showFeedback && (\n <div\n style={{\n position: \"fixed\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n zIndex: Z_INDEX.MODAL,\n pointerEvents: \"none\",\n }}\n >\n <TrainingFeedbackOverlayHtml\n message={feedbackMessage}\n isMobile={showMobileControls}\n />\n </div>\n )}\n\n {/* Technique Bar - centered, embedded mode for proper containment.\n Reserve right space for the absolute Volume Control and (on mobile)\n left space for the Archetype Selector so technique cards never sit\n under the side controls. */}\n <div\n style={{\n pointerEvents: \"all\",\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n overflow: \"visible\",\n height: \"100%\",\n marginRight: layout.volumeReserve,\n marginLeft: showArchetypeSelector ? layout.archetypeReserve : 0,\n }}\n data-testid=\"training-bottom-hud-technique-section\"\n >\n <TechniqueBar\n techniques={techniques as Technique[]}\n player={player}\n selectedIndex={selectedIndex}\n cooldowns={cooldowns}\n onTechniqueSelect={onTechniqueSelect}\n onTechniqueHover={(_tech) => {}}\n isMobile={showMobileControls}\n screenWidth={width}\n screenHeight={height}\n embedded={true}\n containerWidth={layout.techniqueBarContainerWidth}\n />\n </div>\n\n {/* Volume Control - bottom right corner */}\n <div\n style={{\n position: \"absolute\",\n right: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n }}\n data-testid=\"training-bottom-hud-volume-section\"\n >\n <VolumeControl position=\"custom\" compact={true} />\n </div>\n\n {/* Mobile Archetype Selector - bottom left corner */}\n {showArchetypeSelector && (\n <div\n style={{\n position: \"absolute\",\n left: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n display: \"flex\",\n alignItems: \"center\",\n gap: SPACING.xxs,\n padding: `${SPACING.xxs} ${SPACING.xs}`,\n background: GRADIENTS.vertical(0.9),\n border: BORDERS.accent,\n borderRadius: SPACING.xxs,\n }}\n data-testid=\"training-bottom-hud-archetype-section\"\n >\n <ArchetypeSelectionButtons\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={onArchetypeSelect}\n onPlaySFX={onPlaySFX ?? (() => {})}\n isMobile={showMobileControls}\n />\n </div>\n )}\n </div>\n );\n};\n\nexport default TrainingBottomHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA,IAAa,qBAAuD,EAClE,OACA,QACA,WAAW,OACX,eACA,YACA,QACA,eACA,WACA,mBACA,cACA,iBACA,mBACA,mBACA,gBACI;CACJ,MAAM,qBAAqB,yBAAyB,OAAO,SAAS;CACpE,MAAM,wBACJ,sBACA,sBAAsB,KAAA,KACtB,sBAAsB,KAAA;CAExB,MAAM,SAAS,MAAM,cAAc;EACjC,MAAM,YAAY,aAAa,QAAQ,IAAK,GAAG;EAE/C,MAAM,UAAU,qBAAqB,MAAM,GAAG;EAE9C,MAAM,gBAAgB,qBAClB,0BAA0B,uBAC1B,0BAA0B;EAC9B,MAAM,mBAAmB,wBACrB,0BAA0B,uBAC1B;EAUJ,OAAO;GACL;GACA;GACA;GACA;GACA,4BAbiC,KAAK,IACtC,GACA,QACE,UAAU,IACV,gBACA,iBAQF;GACD;IACA;EAAC;EAAO;EAAQ;EAAe;EAAuB;EAAmB,CAAC;CAE7E,OACE,qBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,QAAQ;GACR,MAAM;GACN,OAAO;GACP,QAAQ,GAAG,OAAO,UAAU;GAC5B,SAAS;GACT,eAAe;GACf,gBAAgB;GAChB,YAAY;GACZ,eAAe;GACf,SAAS,GAAG,OAAO,QAAQ;GAC3B,WAAW;GACX,WAAW,QAAQ;GACnB,YAAY,UAAU,gBAAgB,GAAI;GAC1C,gBAAgB,UAAU;GAC3B;EACD,eAAY;YAlBd;GAqBG,gBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,WAAW;KACX,QAAQ,QAAQ;KAChB,eAAe;KAChB;cAED,oBAAC,6BAAD;KACE,SAAS;KACT,UAAU;KACV,CAAA;IACE,CAAA;GAOR,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,MAAM;KACN,SAAS;KACT,eAAe;KACf,gBAAgB;KAChB,YAAY;KACZ,UAAU;KACV,QAAQ;KACR,aAAa,OAAO;KACpB,YAAY,wBAAwB,OAAO,mBAAmB;KAC/D;IACD,eAAY;cAEZ,oBAAC,cAAD;KACc;KACJ;KACO;KACJ;KACQ;KACnB,mBAAmB,UAAU;KAC7B,UAAU;KACV,aAAa;KACb,cAAc;KACd,UAAU;KACV,gBAAgB,OAAO;KACvB,CAAA;IACE,CAAA;GAGN,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO,GAAG,OAAO,UAAU,IAAI;KAC/B,QAAQ,GAAG,OAAO,QAAQ;KAC1B,eAAe;KAChB;IACD,eAAY;cAEZ,oBAAC,eAAD;KAAe,UAAS;KAAS,SAAS;KAAQ,CAAA;IAC9C,CAAA;GAGL,yBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,MAAM,GAAG,OAAO,UAAU,IAAI;KAC9B,QAAQ,GAAG,OAAO,QAAQ;KAC1B,eAAe;KACf,SAAS;KACT,YAAY;KACZ,KAAK,QAAQ;KACb,SAAS,GAAG,QAAQ,IAAI,GAAG,QAAQ;KACnC,YAAY,UAAU,SAAS,GAAI;KACnC,QAAQ,QAAQ;KAChB,cAAc,QAAQ;KACvB;IACD,eAAY;cAEZ,oBAAC,2BAAD;KACqB;KACA;KACnB,WAAW,oBAAoB;KAC/B,UAAU;KACV,CAAA;IACE,CAAA;GAEJ"}
|
|
1
|
+
{"version":3,"file":"TrainingBottomHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingBottomHUD.tsx"],"sourcesContent":["/**\n * TrainingBottomHUD - Bottom bar for training screen\n *\n * Contains:\n * - Technique Bar (centered)\n * - Volume Control (bottom-right, compact)\n * - Feedback Message (centered overlay)\n * - Archetype Selector (mobile only - bottom-left)\n *\n * Gaming Layout Best Practice:\n * - Width: 100% of screen\n * - Height: Resolution-based ~11% of screen height (40-120px range)\n * - On mobile, consolidates controls from TopHUD\n *\n * @korean 훈련화면 하단 바 - 기술 바, 음량, 피드백, 모바일 원형선택\n */\n\nimport React from \"react\";\nimport { PlayerState } from \"../../../../../systems\";\nimport { Technique } from \"../../../../../types\";\nimport { PlayerArchetype } from \"../../../../../types/common\";\nimport {\n HUD_SIDE_CONTROL_RESERVES,\n TRAINING_BOTTOM_HUD_HEIGHT_PERCENT,\n} from \"../../../../../types/constants/layout\";\nimport { Z_INDEX } from \"../../../../../types/LayoutTypes\";\nimport { SPACING, BORDERS, GRADIENTS, HUD_STYLE } from \"../../../../../types/constants/designSystem\";\nimport {\n getHUDHeight,\n getResponsivePadding,\n shouldShowMobileControls,\n} from \"../../../../../utils/responsiveLayout\";\nimport { TechniqueBar } from \"../../../../shared/three/ui/TechniqueBar\";\nimport { VolumeControl } from \"../../../../shared/ui/VolumeControl\";\nimport { ArchetypeSelectionButtons } from \"../TrainingButtonsOverlayHtml\";\nimport TrainingFeedbackOverlayHtml from \"../TrainingFeedbackOverlayHtml\";\n\n\n\nexport interface TrainingBottomHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Available techniques for the technique bar */\n readonly techniques: readonly Technique[];\n /** Player state for technique availability checks */\n readonly player: PlayerState;\n /** Currently selected technique index */\n readonly selectedIndex: number;\n /** Active technique cooldowns */\n readonly cooldowns: Map<string, number>;\n /** Handler for technique selection */\n readonly onTechniqueSelect: (index: number) => void;\n /** Whether to show feedback message */\n readonly showFeedback: boolean;\n /** Feedback message to display */\n readonly feedbackMessage: string;\n /** Currently selected archetype (for mobile) */\n readonly selectedArchetype?: PlayerArchetype;\n /** Handler for archetype selection (for mobile) */\n readonly onArchetypeSelect?: (archetype: PlayerArchetype) => void;\n /** Handler for playing sound effects (for mobile) */\n readonly onPlaySFX?: (sound: string) => void;\n}\n\n/**\n * TrainingBottomHUD Component\n *\n * Compact bottom bar with centered technique bar, volume control,\n * and archetype selector on mobile. Uses resolution-based sizing.\n */\nexport const TrainingBottomHUD: React.FC<TrainingBottomHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n techniques,\n player,\n selectedIndex,\n cooldowns,\n onTechniqueSelect,\n showFeedback,\n feedbackMessage,\n selectedArchetype,\n onArchetypeSelect,\n onPlaySFX,\n}) => {\n const showMobileControls = shouldShowMobileControls(width, isMobile);\n const showArchetypeSelector =\n showMobileControls &&\n onArchetypeSelect !== undefined &&\n selectedArchetype !== undefined;\n\n const layout = React.useMemo(() => {\n const hudHeight = getHUDHeight(height, TRAINING_BOTTOM_HUD_HEIGHT_PERCENT) * positionScale;\n \n const padding = getResponsivePadding(width) * positionScale;\n\n const volumeReserve = showMobileControls\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : HUD_SIDE_CONTROL_RESERVES.VOLUME_CONTROL;\n const archetypeReserve = showArchetypeSelector\n ? HUD_SIDE_CONTROL_RESERVES.TECHNIQUE_BAR_MOBILE\n : 0;\n\n const techniqueBarContainerWidth = Math.max(\n 0,\n width -\n padding * 2 -\n volumeReserve -\n archetypeReserve,\n );\n\n return {\n hudHeight,\n padding,\n volumeReserve,\n archetypeReserve,\n techniqueBarContainerWidth,\n };\n }, [width, height, positionScale, showArchetypeSelector, showMobileControls]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"center\",\n alignItems: \"center\",\n pointerEvents: \"none\",\n padding: `${layout.padding}px`,\n boxSizing: \"border-box\",\n borderTop: BORDERS.default,\n background: GRADIENTS.verticalReverse(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"training-bottom-hud\"\n >\n {/* Feedback Message (centered in screen, above technique bar) */}\n {showFeedback && (\n <div\n style={{\n position: \"fixed\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n zIndex: Z_INDEX.MODAL,\n pointerEvents: \"none\",\n }}\n >\n <TrainingFeedbackOverlayHtml\n message={feedbackMessage}\n isMobile={showMobileControls}\n />\n </div>\n )}\n\n {/* Technique Bar - centered, embedded mode for proper containment.\n Reserve right space for the absolute Volume Control and (on mobile)\n left space for the Archetype Selector so technique cards never sit\n under the side controls. */}\n <div\n style={{\n pointerEvents: \"all\",\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"center\",\n alignItems: \"center\",\n overflow: \"visible\",\n height: \"100%\",\n marginRight: layout.volumeReserve,\n marginLeft: showArchetypeSelector ? layout.archetypeReserve : 0,\n }}\n data-testid=\"training-bottom-hud-technique-section\"\n >\n <TechniqueBar\n techniques={techniques as Technique[]}\n player={player}\n selectedIndex={selectedIndex}\n cooldowns={cooldowns}\n onTechniqueSelect={onTechniqueSelect}\n onTechniqueHover={(_tech) => {}}\n isMobile={showMobileControls}\n screenWidth={width}\n screenHeight={height}\n embedded={true}\n containerWidth={layout.techniqueBarContainerWidth}\n />\n </div>\n\n {/* Volume Control - bottom right corner */}\n <div\n style={{\n position: \"absolute\",\n right: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n }}\n data-testid=\"training-bottom-hud-volume-section\"\n >\n <VolumeControl position=\"custom\" compact={true} />\n </div>\n\n {/* Mobile Archetype Selector - bottom left corner */}\n {showArchetypeSelector && (\n <div\n style={{\n position: \"absolute\",\n left: `${layout.padding * 1.5}px`,\n bottom: `${layout.padding}px`,\n pointerEvents: \"all\",\n display: \"flex\",\n alignItems: \"center\",\n gap: SPACING.xxs,\n padding: `${SPACING.xxs} ${SPACING.xs}`,\n background: GRADIENTS.vertical(0.9),\n border: BORDERS.accent,\n borderRadius: SPACING.xxs,\n }}\n data-testid=\"training-bottom-hud-archetype-section\"\n >\n <ArchetypeSelectionButtons\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={onArchetypeSelect}\n onPlaySFX={onPlaySFX ?? (() => {})}\n isMobile={showMobileControls}\n />\n </div>\n )}\n </div>\n );\n};\n\nexport default TrainingBottomHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,IAAa,qBAAuD,EAClE,OACA,QACA,WAAW,OACX,eACA,YACA,QACA,eACA,WACA,mBACA,cACA,iBACA,mBACA,mBACA,gBACI;CACJ,MAAM,qBAAqB,yBAAyB,OAAO,QAAQ;CACnE,MAAM,wBACJ,sBACA,sBAAsB,KAAA,KACtB,sBAAsB,KAAA;CAExB,MAAM,SAAS,MAAM,cAAc;EACjC,MAAM,YAAY,aAAa,QAAQ,kCAAkC,IAAI;EAE7E,MAAM,UAAU,qBAAqB,KAAK,IAAI;EAE9C,MAAM,gBAAgB,qBAClB,0BAA0B,uBAC1B,0BAA0B;EAC9B,MAAM,mBAAmB,wBACrB,0BAA0B,uBAC1B;EAUJ,OAAO;GACL;GACA;GACA;GACA;GACA,4BAbiC,KAAK,IACtC,GACA,QACE,UAAU,IACV,gBACA,gBAQF;EACF;CACF,GAAG;EAAC;EAAO;EAAQ;EAAe;EAAuB;CAAkB,CAAC;CAE5E,OACE,qBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,QAAQ;GACR,MAAM;GACN,OAAO;GACP,QAAQ,GAAG,OAAO,UAAU;GAC5B,SAAS;GACT,eAAe;GACf,gBAAgB;GAChB,YAAY;GACZ,eAAe;GACf,SAAS,GAAG,OAAO,QAAQ;GAC3B,WAAW;GACX,WAAW,QAAQ;GACnB,YAAY,UAAU,gBAAgB,EAAG;GACzC,gBAAgB,UAAU;EAC5B;EACA,eAAY;YAlBd;GAqBG,gBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,KAAK;KACL,MAAM;KACN,WAAW;KACX,QAAQ,QAAQ;KAChB,eAAe;IACjB;cAEA,oBAAC,6BAAD;KACE,SAAS;KACT,UAAU;IACX,CAAA;GACE,CAAA;GAOP,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,MAAM;KACN,SAAS;KACT,eAAe;KACf,gBAAgB;KAChB,YAAY;KACZ,UAAU;KACV,QAAQ;KACR,aAAa,OAAO;KACpB,YAAY,wBAAwB,OAAO,mBAAmB;IAChE;IACA,eAAY;cAEZ,oBAAC,cAAD;KACc;KACJ;KACO;KACJ;KACQ;KACnB,mBAAmB,UAAU,CAAC;KAC9B,UAAU;KACV,aAAa;KACb,cAAc;KACd,UAAU;KACV,gBAAgB,OAAO;IACxB,CAAA;GACE,CAAA;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO,GAAG,OAAO,UAAU,IAAI;KAC/B,QAAQ,GAAG,OAAO,QAAQ;KAC1B,eAAe;IACjB;IACA,eAAY;cAEZ,oBAAC,eAAD;KAAe,UAAS;KAAS,SAAS;IAAO,CAAA;GAC9C,CAAA;GAGJ,yBACC,oBAAC,OAAD;IACE,OAAO;KACL,UAAU;KACV,MAAM,GAAG,OAAO,UAAU,IAAI;KAC9B,QAAQ,GAAG,OAAO,QAAQ;KAC1B,eAAe;KACf,SAAS;KACT,YAAY;KACZ,KAAK,QAAQ;KACb,SAAS,GAAG,QAAQ,IAAI,GAAG,QAAQ;KACnC,YAAY,UAAU,SAAS,EAAG;KAClC,QAAQ,QAAQ;KAChB,cAAc,QAAQ;IACxB;IACA,eAAY;cAEZ,oBAAC,2BAAD;KACqB;KACA;KACnB,WAAW,oBAAoB,CAAC;KAChC,UAAU;IACX,CAAA;GACE,CAAA;EAEJ;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingLeftHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingLeftHUD.tsx"],"sourcesContent":["/**\n * TrainingLeftHUD - Left side HUD for training screen\n *\n * Contains:\n * - Anatomy Display controls\n * - Guard Indicator\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - When combined with the right HUD, leaves roughly 64–72% center width for the arena depending on resolution\n *\n * Responsible for sizing and positioning all left-side UI elements.\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 훈련화면 왼쪽 HUD - 해부학 표시 및 가드 표시기\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../../types/common\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { GuardIndicator } from \"../../../../shared/three/indicators/GuardIndicator\";\nimport AnatomyControlsOverlayHtml from \"../AnatomyControlsOverlayHtml\";\nimport type { AnatomyLayer } from \"../AnatomyOverlay3D\";\n\nexport interface TrainingLeftHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Currently visible anatomy layers */\n readonly visibleAnatomyLayers: readonly AnatomyLayer[];\n /** Handler for toggling anatomy layers */\n readonly onAnatomyLayerToggle: (layer: AnatomyLayer) => void;\n /** Current stance index (0-7) */\n readonly currentStanceIndex: number;\n /** Whether player is in guard stance */\n readonly isInGuard: boolean;\n}\n\n/**\n * TrainingLeftHUD Component\n *\n * Left side of the training screen containing anatomy controls and guard indicator.\n * Uses resolution-based sizing for smooth scaling across all screen sizes.\n * Uses shared HUD utilities for consistent layout and styling.\n */\nexport const TrainingLeftHUD: React.FC<TrainingLeftHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n visibleAnatomyLayers,\n onAnatomyLayerToggle,\n currentStanceIndex,\n isInGuard,\n}) => {\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'left',\n 'training'\n );\n\n const currentStance: TrigramStance =\n TRIGRAM_STANCES_ORDER[currentStanceIndex];\n const anatomyControlsWidth =\n layout.hudWidth > layout.padding * 2\n ? layout.hudWidth - layout.padding * 2\n : undefined;\n\n return (\n <BaseHUDContainer\n position=\"left\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n dataTestId=\"training-left-hud\"\n >\n {/* Anatomy Controls */}\n <div\n style={{\n pointerEvents: \"all\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${layout.gap}px`,\n maxWidth: \"100%\",\n }}\n data-testid=\"training-left-hud-anatomy-section\"\n >\n <AnatomyControlsOverlayHtml\n visibleLayers={visibleAnatomyLayers as AnatomyLayer[]}\n onLayerToggle={onAnatomyLayerToggle}\n isMobile={isMobile}\n width={anatomyControlsWidth}\n />\n </div>\n\n {/* Guard Indicator */}\n <div\n style={{ pointerEvents: \"none\", maxWidth: \"100%\" }}\n data-testid=\"training-left-hud-guard-section\"\n >\n <GuardIndicator\n currentStance={currentStance}\n isInGuard={isInGuard}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default TrainingLeftHUD;\n"],"mappings":";;;;;;;;;;;;;;;AAqDA,IAAa,mBAAmD,EAC9D,OACA,QACA,WAAW,OACX,eACA,sBACA,sBACA,oBACA,gBACI;CACJ,MAAM,SAAS,aACb,OACA,QACA,eACA,QACA,
|
|
1
|
+
{"version":3,"file":"TrainingLeftHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingLeftHUD.tsx"],"sourcesContent":["/**\n * TrainingLeftHUD - Left side HUD for training screen\n *\n * Contains:\n * - Anatomy Display controls\n * - Guard Indicator\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: 100% minus top/bottom HUD heights\n * - When combined with the right HUD, leaves roughly 64–72% center width for the arena depending on resolution\n *\n * Responsible for sizing and positioning all left-side UI elements.\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 훈련화면 왼쪽 HUD - 해부학 표시 및 가드 표시기\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../../systems/trigram/types\";\nimport { TrigramStance } from \"../../../../../types/common\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport { GuardIndicator } from \"../../../../shared/three/indicators/GuardIndicator\";\nimport AnatomyControlsOverlayHtml from \"../AnatomyControlsOverlayHtml\";\nimport type { AnatomyLayer } from \"../AnatomyOverlay3D\";\n\nexport interface TrainingLeftHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Currently visible anatomy layers */\n readonly visibleAnatomyLayers: readonly AnatomyLayer[];\n /** Handler for toggling anatomy layers */\n readonly onAnatomyLayerToggle: (layer: AnatomyLayer) => void;\n /** Current stance index (0-7) */\n readonly currentStanceIndex: number;\n /** Whether player is in guard stance */\n readonly isInGuard: boolean;\n}\n\n/**\n * TrainingLeftHUD Component\n *\n * Left side of the training screen containing anatomy controls and guard indicator.\n * Uses resolution-based sizing for smooth scaling across all screen sizes.\n * Uses shared HUD utilities for consistent layout and styling.\n */\nexport const TrainingLeftHUD: React.FC<TrainingLeftHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n visibleAnatomyLayers,\n onAnatomyLayerToggle,\n currentStanceIndex,\n isInGuard,\n}) => {\n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'left',\n 'training'\n );\n\n const currentStance: TrigramStance =\n TRIGRAM_STANCES_ORDER[currentStanceIndex];\n const anatomyControlsWidth =\n layout.hudWidth > layout.padding * 2\n ? layout.hudWidth - layout.padding * 2\n : undefined;\n\n return (\n <BaseHUDContainer\n position=\"left\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n dataTestId=\"training-left-hud\"\n >\n {/* Anatomy Controls */}\n <div\n style={{\n pointerEvents: \"all\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: `${layout.gap}px`,\n maxWidth: \"100%\",\n }}\n data-testid=\"training-left-hud-anatomy-section\"\n >\n <AnatomyControlsOverlayHtml\n visibleLayers={visibleAnatomyLayers as AnatomyLayer[]}\n onLayerToggle={onAnatomyLayerToggle}\n isMobile={isMobile}\n width={anatomyControlsWidth}\n />\n </div>\n\n {/* Guard Indicator */}\n <div\n style={{ pointerEvents: \"none\", maxWidth: \"100%\" }}\n data-testid=\"training-left-hud-guard-section\"\n >\n <GuardIndicator\n currentStance={currentStance}\n isInGuard={isInGuard}\n position=\"left\"\n isMobile={isMobile}\n />\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default TrainingLeftHUD;\n"],"mappings":";;;;;;;;;;;;;;;AAqDA,IAAa,mBAAmD,EAC9D,OACA,QACA,WAAW,OACX,eACA,sBACA,sBACA,oBACA,gBACI;CACJ,MAAM,SAAS,aACb,OACA,QACA,eACA,QACA,UACF;CAEA,MAAM,gBACJ,sBAAsB;CACxB,MAAM,uBACJ,OAAO,WAAW,OAAO,UAAU,IAC/B,OAAO,WAAW,OAAO,UAAU,IACnC,KAAA;CAEN,OACE,qBAAC,kBAAD;EACE,UAAS;EACT,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,KAAK,OAAO;EACZ,YAAW;YAPb,CAUE,oBAAC,OAAD;GACE,OAAO;IACL,eAAe;IACf,SAAS;IACT,eAAe;IACf,KAAK,GAAG,OAAO,IAAI;IACnB,UAAU;GACZ;GACA,eAAY;aAEZ,oBAAC,4BAAD;IACE,eAAe;IACf,eAAe;IACL;IACV,OAAO;GACR,CAAA;EACE,CAAA,GAGL,oBAAC,OAAD;GACE,OAAO;IAAE,eAAe;IAAQ,UAAU;GAAO;GACjD,eAAY;aAEZ,oBAAC,gBAAD;IACiB;IACJ;IACX,UAAS;IACC;GACX,CAAA;EACE,CAAA,CACW;;AAEtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingRightHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingRightHUD.tsx"],"sourcesContent":["/**\n * TrainingRightHUD - Right side panel for training screen\n *\n * Layout Order (gaming best practice - most referenced at top):\n * 1. Training Statistics (top) - Always visible, most checked\n * 2. Training Mode Selector (middle) - Changed occasionally\n * 3. Vital Point / Footwork Panel (bottom) - Contextual\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: Between top/bottom bars\n * - Scrollable vital point section for long lists\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 훈련화면 오른쪽 패널 - 통계(상), 모드(중), 급소(하)\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { getResponsiveSize } from \"../../../../../utils/responsiveLayout\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport type {\n FootworkDrill,\n TrainingMode,\n TrainingStats,\n} from \"../../hooks/useTrainingState\";\nimport FootworkDrillsOverlayHtml from \"../FootworkDrillsOverlayHtml\";\nimport TrainingModeSelectorOverlayHtml from \"../TrainingModeSelectorOverlayHtml\";\nimport TrainingStatsOverlayHtml from \"../TrainingStatsOverlayHtml\";\nimport VitalPointTrainingOverlayHtml from \"../VitalPointTrainingOverlayHtml\";\n\nexport interface TrainingRightHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Current training mode */\n readonly trainingMode: TrainingMode;\n /** Handler for training mode change */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Training statistics */\n readonly stats: TrainingStats & {\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n };\n /** Distance to training dummy */\n readonly distanceToDummy: number;\n /** Effective reach for current technique */\n readonly effectiveReach: number;\n /** Selected vital point ID */\n readonly selectedVitalPoint: string | null;\n /** Handler for vital point selection */\n readonly onVitalPointSelect: (point: string | null) => void;\n /** Current footwork drill type */\n readonly footworkDrillType: FootworkDrill;\n /** Current footwork drill step */\n readonly footworkDrillStep: number;\n /** Whether footwork drill is active */\n readonly footworkDrillActive: boolean;\n /** Handler to start footwork drill */\n readonly onStartFootworkDrill: (drill: FootworkDrill) => void;\n /** Handler to stop footwork drill */\n readonly onStopFootworkDrill: () => void;\n /** Handler to advance footwork step */\n readonly onAdvanceFootworkStep: () => void;\n}\n\n/**\n * TrainingRightHUD Component\n *\n * Right panel with Stats (top), Mode selector (middle), Vital points (bottom).\n * Uses shared HUD utilities for consistent layout and styling.\n */\nexport const TrainingRightHUD: React.FC<TrainingRightHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n trainingMode,\n onModeChange,\n stats,\n distanceToDummy,\n effectiveReach,\n selectedVitalPoint,\n onVitalPointSelect,\n footworkDrillType,\n footworkDrillStep,\n footworkDrillActive,\n onStartFootworkDrill,\n onStopFootworkDrill,\n onAdvanceFootworkStep,\n}) => {\n const gapOverride = getResponsiveSize(width, {\n mobile: 6,\n tablet: 7,\n desktop: 8,\n }) * positionScale;\n \n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'right',\n 'training',\n undefined, // Use default padding from hook\n gapOverride // Override gap for tighter spacing\n );\n\n return (\n <BaseHUDContainer\n position=\"right\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"training-right-hud\"\n >\n {/* TOP: Training Stats - most referenced, always visible */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flexShrink: 0,\n }}\n data-testid=\"training-right-hud-stats-section\"\n >\n <TrainingStatsOverlayHtml\n stats={stats}\n isMobile={isMobile}\n width={layout.hudWidth - layout.padding * 2}\n distanceToDummy={distanceToDummy}\n effectiveReach={effectiveReach}\n />\n </div>\n\n {/* MIDDLE: Mode Selector - compact */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flexShrink: 0,\n }}\n data-testid=\"training-right-hud-mode-section\"\n >\n <TrainingModeSelectorOverlayHtml\n currentMode={trainingMode}\n onModeChange={onModeChange}\n isMobile={isMobile}\n />\n </div>\n\n {/* BOTTOM: Vital Point / Footwork - scrollable for long lists */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flex: 1,\n overflow: \"auto\",\n minHeight: 0,\n }}\n data-testid=\"training-right-hud-bottom-section\"\n >\n {trainingMode === \"footwork\" || trainingMode === \"combo_practice\" ? (\n <FootworkDrillsOverlayHtml\n currentDrill={footworkDrillType}\n onDrillChange={onStartFootworkDrill}\n currentStep={footworkDrillStep}\n onStepComplete={onAdvanceFootworkStep}\n isActive={footworkDrillActive}\n onToggleActive={() => {\n if (footworkDrillActive) {\n onStopFootworkDrill();\n } else {\n onStartFootworkDrill(footworkDrillType);\n }\n }}\n isMobile={isMobile}\n />\n ) : (\n <VitalPointTrainingOverlayHtml\n selectedVitalPoint={selectedVitalPoint}\n onVitalPointSelect={onVitalPointSelect}\n isMobile={isMobile}\n />\n )}\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default TrainingRightHUD;\n"],"mappings":";;;;;;;;;;;;;;;;AA+EA,IAAa,oBAAqD,EAChE,OACA,QACA,WAAW,OACX,eACA,cACA,cACA,OACA,iBACA,gBACA,oBACA,oBACA,mBACA,mBACA,qBACA,sBACA,qBACA,4BACI;CAOJ,MAAM,SAAS,aACb,OACA,QACA,eACA,SACA,YACA,KAAA,GAZkB,kBAAkB,OAAO;EAC3C,QAAQ;EACR,QAAQ;EACR,SAAS;
|
|
1
|
+
{"version":3,"file":"TrainingRightHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingRightHUD.tsx"],"sourcesContent":["/**\n * TrainingRightHUD - Right side panel for training screen\n *\n * Layout Order (gaming best practice - most referenced at top):\n * 1. Training Statistics (top) - Always visible, most checked\n * 2. Training Mode Selector (middle) - Changed occasionally\n * 3. Vital Point / Footwork Panel (bottom) - Contextual\n *\n * Gaming Layout Best Practice:\n * - Width: Resolution-based 14-18% of screen\n * - Height: Between top/bottom bars\n * - Scrollable vital point section for long lists\n *\n * Now uses shared HUD utilities with resolution-based sizing.\n *\n * @korean 훈련화면 오른쪽 패널 - 통계(상), 모드(중), 급소(하)\n */\n\nimport React from \"react\";\nimport { useHUDLayout } from \"../../../../../hooks/useHUDLayout\";\nimport { getResponsiveSize } from \"../../../../../utils/responsiveLayout\";\nimport { BaseHUDContainer } from \"../../../../shared/ui/BaseHUDContainer\";\nimport type {\n FootworkDrill,\n TrainingMode,\n TrainingStats,\n} from \"../../hooks/useTrainingState\";\nimport FootworkDrillsOverlayHtml from \"../FootworkDrillsOverlayHtml\";\nimport TrainingModeSelectorOverlayHtml from \"../TrainingModeSelectorOverlayHtml\";\nimport TrainingStatsOverlayHtml from \"../TrainingStatsOverlayHtml\";\nimport VitalPointTrainingOverlayHtml from \"../VitalPointTrainingOverlayHtml\";\n\nexport interface TrainingRightHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile controls should be shown (NOT for sizing) */\n readonly isMobile?: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Current training mode */\n readonly trainingMode: TrainingMode;\n /** Handler for training mode change */\n readonly onModeChange: (mode: TrainingMode) => void;\n /** Training statistics */\n readonly stats: TrainingStats & {\n readonly sessionDuration?: number;\n readonly bestCombo?: number;\n readonly perfectStrikes?: number;\n };\n /** Distance to training dummy */\n readonly distanceToDummy: number;\n /** Effective reach for current technique */\n readonly effectiveReach: number;\n /** Selected vital point ID */\n readonly selectedVitalPoint: string | null;\n /** Handler for vital point selection */\n readonly onVitalPointSelect: (point: string | null) => void;\n /** Current footwork drill type */\n readonly footworkDrillType: FootworkDrill;\n /** Current footwork drill step */\n readonly footworkDrillStep: number;\n /** Whether footwork drill is active */\n readonly footworkDrillActive: boolean;\n /** Handler to start footwork drill */\n readonly onStartFootworkDrill: (drill: FootworkDrill) => void;\n /** Handler to stop footwork drill */\n readonly onStopFootworkDrill: () => void;\n /** Handler to advance footwork step */\n readonly onAdvanceFootworkStep: () => void;\n}\n\n/**\n * TrainingRightHUD Component\n *\n * Right panel with Stats (top), Mode selector (middle), Vital points (bottom).\n * Uses shared HUD utilities for consistent layout and styling.\n */\nexport const TrainingRightHUD: React.FC<TrainingRightHUDProps> = ({\n width,\n height,\n isMobile = false,\n positionScale,\n trainingMode,\n onModeChange,\n stats,\n distanceToDummy,\n effectiveReach,\n selectedVitalPoint,\n onVitalPointSelect,\n footworkDrillType,\n footworkDrillStep,\n footworkDrillActive,\n onStartFootworkDrill,\n onStopFootworkDrill,\n onAdvanceFootworkStep,\n}) => {\n const gapOverride = getResponsiveSize(width, {\n mobile: 6,\n tablet: 7,\n desktop: 8,\n }) * positionScale;\n \n const layout = useHUDLayout(\n width,\n height,\n positionScale,\n 'right',\n 'training',\n undefined, // Use default padding from hook\n gapOverride // Override gap for tighter spacing\n );\n\n return (\n <BaseHUDContainer\n position=\"right\"\n width={layout.hudWidth}\n height={layout.availableHeight}\n topOffset={layout.topOffset}\n padding={layout.padding}\n gap={layout.gap}\n style={{ overflow: \"hidden\" }}\n dataTestId=\"training-right-hud\"\n >\n {/* TOP: Training Stats - most referenced, always visible */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flexShrink: 0,\n }}\n data-testid=\"training-right-hud-stats-section\"\n >\n <TrainingStatsOverlayHtml\n stats={stats}\n isMobile={isMobile}\n width={layout.hudWidth - layout.padding * 2}\n distanceToDummy={distanceToDummy}\n effectiveReach={effectiveReach}\n />\n </div>\n\n {/* MIDDLE: Mode Selector - compact */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flexShrink: 0,\n }}\n data-testid=\"training-right-hud-mode-section\"\n >\n <TrainingModeSelectorOverlayHtml\n currentMode={trainingMode}\n onModeChange={onModeChange}\n isMobile={isMobile}\n />\n </div>\n\n {/* BOTTOM: Vital Point / Footwork - scrollable for long lists */}\n <div\n style={{\n pointerEvents: \"all\",\n width: \"100%\",\n flex: 1,\n overflow: \"auto\",\n minHeight: 0,\n }}\n data-testid=\"training-right-hud-bottom-section\"\n >\n {trainingMode === \"footwork\" || trainingMode === \"combo_practice\" ? (\n <FootworkDrillsOverlayHtml\n currentDrill={footworkDrillType}\n onDrillChange={onStartFootworkDrill}\n currentStep={footworkDrillStep}\n onStepComplete={onAdvanceFootworkStep}\n isActive={footworkDrillActive}\n onToggleActive={() => {\n if (footworkDrillActive) {\n onStopFootworkDrill();\n } else {\n onStartFootworkDrill(footworkDrillType);\n }\n }}\n isMobile={isMobile}\n />\n ) : (\n <VitalPointTrainingOverlayHtml\n selectedVitalPoint={selectedVitalPoint}\n onVitalPointSelect={onVitalPointSelect}\n isMobile={isMobile}\n />\n )}\n </div>\n </BaseHUDContainer>\n );\n};\n\nexport default TrainingRightHUD;\n"],"mappings":";;;;;;;;;;;;;;;;AA+EA,IAAa,oBAAqD,EAChE,OACA,QACA,WAAW,OACX,eACA,cACA,cACA,OACA,iBACA,gBACA,oBACA,oBACA,mBACA,mBACA,qBACA,sBACA,qBACA,4BACI;CAOJ,MAAM,SAAS,aACb,OACA,QACA,eACA,SACA,YACA,KAAA,GAZkB,kBAAkB,OAAO;EAC3C,QAAQ;EACR,QAAQ;EACR,SAAS;CACX,CAAC,IAAI,aAUL;CAEA,OACE,qBAAC,kBAAD;EACE,UAAS;EACT,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,KAAK,OAAO;EACZ,OAAO,EAAE,UAAU,SAAS;EAC5B,YAAW;YARb;GAWE,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,OAAO;KACP,YAAY;IACd;IACA,eAAY;cAEZ,oBAAC,0BAAD;KACS;KACG;KACV,OAAO,OAAO,WAAW,OAAO,UAAU;KACzB;KACD;IACjB,CAAA;GACE,CAAA;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,OAAO;KACP,YAAY;IACd;IACA,eAAY;cAEZ,oBAAC,iCAAD;KACE,aAAa;KACC;KACJ;IACX,CAAA;GACE,CAAA;GAGL,oBAAC,OAAD;IACE,OAAO;KACL,eAAe;KACf,OAAO;KACP,MAAM;KACN,UAAU;KACV,WAAW;IACb;IACA,eAAY;cAEX,iBAAiB,cAAc,iBAAiB,mBAC/C,oBAAC,2BAAD;KACE,cAAc;KACd,eAAe;KACf,aAAa;KACb,gBAAgB;KAChB,UAAU;KACV,sBAAsB;MACpB,IAAI,qBACF,oBAAoB;WAEpB,qBAAqB,iBAAiB;KAE1C;KACU;IACX,CAAA,IAED,oBAAC,+BAAD;KACsB;KACA;KACV;IACX,CAAA;GAEA,CAAA;EACW;;AAEtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrainingTopHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingTopHUD.tsx"],"sourcesContent":["/**\n * TrainingTopHUD - Slim top bar for training screen\n *\n * Gaming Best Practice - Minimal Top Bar:\n * - Training Active/Stop indicator (left)\n * - Vital Point hint + Archetype Selector (center) - desktop only\n * - Return to Menu button (right) - Standard gaming pattern\n *\n * Mobile:\n * - Only shows Training status (left) and Return button (right)\n * - Other controls consolidated in BottomHUD\n *\n * Layout:\n * - Width: 100% of screen\n * - Height: Compact 50-70px (minimal obstruction)\n *\n * @korean 훈련화면 상단 바 - 훈련 상태, 급소 힌트, 원형 선택, 메뉴 복귀\n */\n\nimport React from \"react\";\nimport { PlayerArchetype } from \"../../../../../types/common\";\nimport { TRAINING_TOP_HUD_HEIGHT_PERCENT } from \"../../../../../types/constants/layout\";\nimport { SPACING, SPACING_NUMERIC, SPACING_ADJUSTMENTS, BORDER_RADIUS, TYPOGRAPHY, TYPOGRAPHY_NUMERIC, HIERARCHY, BORDERS, GRADIENTS, HUD_STYLE } from \"../../../../../types/constants/designSystem\";\nimport { getHUDHeight } from \"../../../../../utils/responsiveLayout\";\nimport {\n ArchetypeSelectionButtons,\n ReturnToMenuButton,\n} from \"../TrainingButtonsOverlayHtml\";\nimport TrainingControlsOverlayHtml from \"../TrainingControlsOverlayHtml\";\n\nexport interface TrainingTopHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Handler to start training */\n readonly onStartTraining: () => void;\n /** Handler to stop training */\n readonly onStopTraining: () => void;\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Handler for archetype selection */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Whether vital point overlay is visible */\n readonly overlayVisible: boolean;\n /** Handler for returning to menu */\n readonly onReturnToMenu: () => void;\n /** Handler for playing sound effects */\n readonly onPlaySFX: (sound: string) => void;\n}\n\n/**\n * TrainingTopHUD Component\n *\n * Slim top bar containing training controls, vital point hint, archetype selector,\n * and return to menu button. On mobile, only essential controls shown.\n */\nexport const TrainingTopHUD: React.FC<TrainingTopHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n isTraining,\n onStartTraining,\n onStopTraining,\n selectedArchetype,\n onArchetypeSelect,\n overlayVisible,\n onReturnToMenu,\n onPlaySFX,\n}) => {\n const layout = React.useMemo(() => {\n const hudHeight =\n getHUDHeight(height, TRAINING_TOP_HUD_HEIGHT_PERCENT) * positionScale;\n\n const padding = isMobile ? SPACING_NUMERIC.xs : SPACING_NUMERIC.sm * positionScale;\n const gap = isMobile ? SPACING_NUMERIC.xs : SPACING_NUMERIC.sm * positionScale;\n const fontSize = isMobile ? TYPOGRAPHY_NUMERIC.bodySmall : TYPOGRAPHY_NUMERIC.bodySmall * positionScale;\n\n return {\n hudHeight,\n padding,\n gap,\n fontSize,\n hudWidth: width,\n };\n }, [width, height, isMobile, positionScale]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: `${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.default,\n background: GRADIENTS.vertical(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"training-top-hud\"\n >\n {/* Left Section - Training Controls (compact) */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: `${layout.gap}px`,\n pointerEvents: \"all\",\n alignItems: \"center\",\n }}\n data-testid=\"training-top-hud-left-section\"\n >\n <TrainingControlsOverlayHtml\n isTraining={isTraining}\n onStartTraining={onStartTraining}\n onStopTraining={onStopTraining}\n isMobile={isMobile}\n variant=\"compact\"\n />\n </div>\n\n {/* Center Section - Vital Point Hint + Archetype Selector (desktop) */}\n {!isMobile && (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${layout.gap * 2}px`,\n }}\n data-testid=\"training-top-hud-center-section\"\n >\n {/* Vital Point Hint */}\n {!overlayVisible && (\n <div\n style={{\n padding: `${SPACING.xxs} ${SPACING_ADJUSTMENTS.xsPlus}`,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.sm,\n fontSize: `${layout.fontSize}px`,\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n color: HIERARCHY.accent.color,\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"vital-point-hint\"\n >\n 💡 Press{\" \"}\n <span\n style={{\n color: HIERARCHY.gold.color,\n fontWeight: \"bold\",\n }}\n >\n V\n </span>{\" \"}\n for vital points\n </div>\n )}\n\n {/* Archetype Selector */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${layout.gap}px`,\n padding: `${SPACING_ADJUSTMENTS.compact} ${SPACING.sm}`,\n background: HUD_STYLE.background,\n border: BORDERS.accent,\n borderRadius: BORDER_RADIUS.md,\n pointerEvents: \"all\",\n }}\n >\n <span\n style={{\n fontSize: `${layout.fontSize}px`,\n color: HIERARCHY.gold.color,\n fontWeight: \"bold\",\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n whiteSpace: \"nowrap\",\n }}\n >\n 원형 | Archetype:\n </span>\n <ArchetypeSelectionButtons\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={onArchetypeSelect}\n onPlaySFX={onPlaySFX}\n isMobile={isMobile}\n />\n </div>\n </div>\n )}\n\n {/* Mobile Center - Just vital point hint */}\n {isMobile && !overlayVisible && (\n <div\n style={{\n padding: `${SPACING.xxs} ${SPACING.xs}`,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.sm,\n fontSize: TYPOGRAPHY.caption.fontSize,\n fontFamily: TYPOGRAPHY.caption.fontFamily,\n color: HIERARCHY.accent.color,\n }}\n data-testid=\"training-top-hud-center-section\"\n >\n <span style={{ color: HIERARCHY.gold.color }}>\n V\n </span>{\" \"}\n = 급소\n </div>\n )}\n\n {/* Right Section - Return to Menu */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n pointerEvents: \"all\",\n }}\n data-testid=\"training-top-hud-right-section\"\n >\n <ReturnToMenuButton\n onClick={onReturnToMenu}\n onMouseEnter={() => onPlaySFX(\"menu_hover\")}\n isMobile={isMobile}\n />\n </div>\n </div>\n );\n};\n\nexport default TrainingTopHUD;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrainingTopHUD.js","names":[],"sources":["../../../../../../src/components/screens/training/components/hud/TrainingTopHUD.tsx"],"sourcesContent":["/**\n * TrainingTopHUD - Slim top bar for training screen\n *\n * Gaming Best Practice - Minimal Top Bar:\n * - Training Active/Stop indicator (left)\n * - Vital Point hint + Archetype Selector (center) - desktop only\n * - Return to Menu button (right) - Standard gaming pattern\n *\n * Mobile:\n * - Only shows Training status (left) and Return button (right)\n * - Other controls consolidated in BottomHUD\n *\n * Layout:\n * - Width: 100% of screen\n * - Height: Compact 50-70px (minimal obstruction)\n *\n * @korean 훈련화면 상단 바 - 훈련 상태, 급소 힌트, 원형 선택, 메뉴 복귀\n */\n\nimport React from \"react\";\nimport { PlayerArchetype } from \"../../../../../types/common\";\nimport { TRAINING_TOP_HUD_HEIGHT_PERCENT } from \"../../../../../types/constants/layout\";\nimport { SPACING, SPACING_NUMERIC, SPACING_ADJUSTMENTS, BORDER_RADIUS, TYPOGRAPHY, TYPOGRAPHY_NUMERIC, HIERARCHY, BORDERS, GRADIENTS, HUD_STYLE } from \"../../../../../types/constants/designSystem\";\nimport { getHUDHeight } from \"../../../../../utils/responsiveLayout\";\nimport {\n ArchetypeSelectionButtons,\n ReturnToMenuButton,\n} from \"../TrainingButtonsOverlayHtml\";\nimport TrainingControlsOverlayHtml from \"../TrainingControlsOverlayHtml\";\n\nexport interface TrainingTopHUDProps {\n /** Screen width for layout calculations */\n readonly width: number;\n /** Screen height for layout calculations */\n readonly height: number;\n /** Whether mobile layout is active */\n readonly isMobile: boolean;\n /** Position scale multiplier for large displays */\n readonly positionScale: number;\n /** Whether training is currently active */\n readonly isTraining: boolean;\n /** Handler to start training */\n readonly onStartTraining: () => void;\n /** Handler to stop training */\n readonly onStopTraining: () => void;\n /** Currently selected archetype */\n readonly selectedArchetype: PlayerArchetype;\n /** Handler for archetype selection */\n readonly onArchetypeSelect: (archetype: PlayerArchetype) => void;\n /** Whether vital point overlay is visible */\n readonly overlayVisible: boolean;\n /** Handler for returning to menu */\n readonly onReturnToMenu: () => void;\n /** Handler for playing sound effects */\n readonly onPlaySFX: (sound: string) => void;\n}\n\n/**\n * TrainingTopHUD Component\n *\n * Slim top bar containing training controls, vital point hint, archetype selector,\n * and return to menu button. On mobile, only essential controls shown.\n */\nexport const TrainingTopHUD: React.FC<TrainingTopHUDProps> = ({\n width,\n height,\n isMobile,\n positionScale,\n isTraining,\n onStartTraining,\n onStopTraining,\n selectedArchetype,\n onArchetypeSelect,\n overlayVisible,\n onReturnToMenu,\n onPlaySFX,\n}) => {\n const layout = React.useMemo(() => {\n const hudHeight =\n getHUDHeight(height, TRAINING_TOP_HUD_HEIGHT_PERCENT) * positionScale;\n\n const padding = isMobile ? SPACING_NUMERIC.xs : SPACING_NUMERIC.sm * positionScale;\n const gap = isMobile ? SPACING_NUMERIC.xs : SPACING_NUMERIC.sm * positionScale;\n const fontSize = isMobile ? TYPOGRAPHY_NUMERIC.bodySmall : TYPOGRAPHY_NUMERIC.bodySmall * positionScale;\n\n return {\n hudHeight,\n padding,\n gap,\n fontSize,\n hudWidth: width,\n };\n }, [width, height, isMobile, positionScale]);\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n height: `${layout.hudHeight}px`,\n display: \"flex\",\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: `${layout.padding}px`,\n pointerEvents: \"none\",\n boxSizing: \"border-box\",\n borderBottom: BORDERS.default,\n background: GRADIENTS.vertical(0.9),\n backdropFilter: HUD_STYLE.backdropFilter,\n }}\n data-testid=\"training-top-hud\"\n >\n {/* Left Section - Training Controls (compact) */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n gap: `${layout.gap}px`,\n pointerEvents: \"all\",\n alignItems: \"center\",\n }}\n data-testid=\"training-top-hud-left-section\"\n >\n <TrainingControlsOverlayHtml\n isTraining={isTraining}\n onStartTraining={onStartTraining}\n onStopTraining={onStopTraining}\n isMobile={isMobile}\n variant=\"compact\"\n />\n </div>\n\n {/* Center Section - Vital Point Hint + Archetype Selector (desktop) */}\n {!isMobile && (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${layout.gap * 2}px`,\n }}\n data-testid=\"training-top-hud-center-section\"\n >\n {/* Vital Point Hint */}\n {!overlayVisible && (\n <div\n style={{\n padding: `${SPACING.xxs} ${SPACING_ADJUSTMENTS.xsPlus}`,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.sm,\n fontSize: `${layout.fontSize}px`,\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n color: HIERARCHY.accent.color,\n whiteSpace: \"nowrap\",\n }}\n data-testid=\"vital-point-hint\"\n >\n 💡 Press{\" \"}\n <span\n style={{\n color: HIERARCHY.gold.color,\n fontWeight: \"bold\",\n }}\n >\n V\n </span>{\" \"}\n for vital points\n </div>\n )}\n\n {/* Archetype Selector */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: `${layout.gap}px`,\n padding: `${SPACING_ADJUSTMENTS.compact} ${SPACING.sm}`,\n background: HUD_STYLE.background,\n border: BORDERS.accent,\n borderRadius: BORDER_RADIUS.md,\n pointerEvents: \"all\",\n }}\n >\n <span\n style={{\n fontSize: `${layout.fontSize}px`,\n color: HIERARCHY.gold.color,\n fontWeight: \"bold\",\n fontFamily: TYPOGRAPHY.bodySmall.fontFamily,\n whiteSpace: \"nowrap\",\n }}\n >\n 원형 | Archetype:\n </span>\n <ArchetypeSelectionButtons\n selectedArchetype={selectedArchetype}\n onArchetypeSelect={onArchetypeSelect}\n onPlaySFX={onPlaySFX}\n isMobile={isMobile}\n />\n </div>\n </div>\n )}\n\n {/* Mobile Center - Just vital point hint */}\n {isMobile && !overlayVisible && (\n <div\n style={{\n padding: `${SPACING.xxs} ${SPACING.xs}`,\n background: HUD_STYLE.background,\n border: BORDERS.muted,\n borderRadius: BORDER_RADIUS.sm,\n fontSize: TYPOGRAPHY.caption.fontSize,\n fontFamily: TYPOGRAPHY.caption.fontFamily,\n color: HIERARCHY.accent.color,\n }}\n data-testid=\"training-top-hud-center-section\"\n >\n <span style={{ color: HIERARCHY.gold.color }}>\n V\n </span>{\" \"}\n = 급소\n </div>\n )}\n\n {/* Right Section - Return to Menu */}\n <div\n style={{\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n pointerEvents: \"all\",\n }}\n data-testid=\"training-top-hud-right-section\"\n >\n <ReturnToMenuButton\n onClick={onReturnToMenu}\n onMouseEnter={() => onPlaySFX(\"menu_hover\")}\n isMobile={isMobile}\n />\n </div>\n </div>\n );\n};\n\nexport default TrainingTopHUD;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,IAAa,kBAAiD,EAC5D,OACA,QACA,UACA,eACA,YACA,iBACA,gBACA,mBACA,mBACA,gBACA,gBACA,gBACI;CACJ,MAAM,SAAS,MAAM,cAAc;EAQjC,OAAO;GACL,WAPA,aAAa,QAAQ,+BAA+B,IAAI;GAQxD,SANc,WAAW,gBAAgB,KAAK,gBAAgB,KAAK;GAOnE,KANU,WAAW,gBAAgB,KAAK,gBAAgB,KAAK;GAO/D,UANe,WAAW,mBAAmB,YAAY,mBAAmB,YAAY;GAOxF,UAAU;EACZ;CACF,GAAG;EAAC;EAAO;EAAQ;EAAU;CAAa,CAAC;CAE3C,OACE,qBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ,GAAG,OAAO,UAAU;GAC5B,SAAS;GACT,eAAe;GACf,gBAAgB;GAChB,YAAY;GACZ,SAAS,GAAG,OAAO,QAAQ;GAC3B,eAAe;GACf,WAAW;GACX,cAAc,QAAQ;GACtB,YAAY,UAAU,SAAS,EAAG;GAClC,gBAAgB,UAAU;EAC5B;EACA,eAAY;YAlBd;GAqBE,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,KAAK,GAAG,OAAO,IAAI;KACnB,eAAe;KACf,YAAY;IACd;IACA,eAAY;cAEZ,oBAAC,6BAAD;KACc;KACK;KACD;KACN;KACV,SAAQ;IACT,CAAA;GACE,CAAA;GAGJ,CAAC,YACA,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,KAAK,GAAG,OAAO,MAAM,EAAE;IACzB;IACA,eAAY;cAPd,CAUG,CAAC,kBACA,qBAAC,OAAD;KACE,OAAO;MACL,SAAS,GAAG,QAAQ,IAAI,GAAG,oBAAoB;MAC/C,YAAY,UAAU;MACtB,QAAQ,QAAQ;MAChB,cAAc,cAAc;MAC5B,UAAU,GAAG,OAAO,SAAS;MAC7B,YAAY,WAAW,UAAU;MACjC,OAAO,UAAU,OAAO;MACxB,YAAY;KACd;KACA,eAAY;eAXd;MAYC;MACU;MACT,oBAAC,QAAD;OACE,OAAO;QACL,OAAO,UAAU,KAAK;QACtB,YAAY;OACd;iBACD;MAEK,CAAA;MAAE;MAAI;KAET;QAIP,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,eAAe;MACf,YAAY;MACZ,KAAK,GAAG,OAAO,IAAI;MACnB,SAAS,GAAG,oBAAoB,QAAQ,GAAG,QAAQ;MACnD,YAAY,UAAU;MACtB,QAAQ,QAAQ;MAChB,cAAc,cAAc;MAC5B,eAAe;KACjB;eAXF,CAaE,oBAAC,QAAD;MACE,OAAO;OACL,UAAU,GAAG,OAAO,SAAS;OAC7B,OAAO,UAAU,KAAK;OACtB,YAAY;OACZ,YAAY,WAAW,UAAU;OACjC,YAAY;MACd;gBACD;KAEK,CAAA,GACN,oBAAC,2BAAD;MACqB;MACA;MACR;MACD;KACX,CAAA,CACE;MACF;;GAIN,YAAY,CAAC,kBACZ,qBAAC,OAAD;IACE,OAAO;KACL,SAAS,GAAG,QAAQ,IAAI,GAAG,QAAQ;KACnC,YAAY,UAAU;KACtB,QAAQ,QAAQ;KAChB,cAAc,cAAc;KAC5B,UAAU,WAAW,QAAQ;KAC7B,YAAY,WAAW,QAAQ;KAC/B,OAAO,UAAU,OAAO;IAC1B;IACA,eAAY;cAVd;KAYE,oBAAC,QAAD;MAAM,OAAO,EAAE,OAAO,UAAU,KAAK,MAAM;gBAAG;KAExC,CAAA;KAAE;KAAI;IAET;;GAIP,oBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,eAAe;IACjB;IACA,eAAY;cAEZ,oBAAC,oBAAD;KACE,SAAS;KACT,oBAAoB,UAAU,YAAY;KAChC;IACX,CAAA;GACE,CAAA;EACF;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAttackMovement.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useAttackMovement.ts"],"sourcesContent":["/**\n * useAttackMovement Hook - Track Attack Movement for 3D Characters\n *\n * Custom hook for managing attack movement physics during animations.\n * Tracks lunge and recovery phases with smooth easing curves.\n *\n * @korean 공격이동훅 - 공격 이동 추적\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { AnimationType } from \"@/systems/animation\";\nimport { AttackMovementPhysics } from \"@/systems/physics\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Configuration for useAttackMovement hook\n */\nexport interface UseAttackMovementConfig {\n /** Whether character is currently attacking */\n readonly isAttacking: boolean;\n /** Animation type for current attack */\n readonly animationType?: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Base position of character (when not attacking) */\n readonly basePosition: [number, number, number];\n /** Direction vector for attack (normalized) */\n readonly attackDirection?: THREE.Vector3;\n /** Animation duration in seconds (default: 0.4) */\n readonly animationDuration?: number;\n}\n\n/**\n * Return value from useAttackMovement hook\n */\nexport interface UseAttackMovementResult {\n /** Current 3D position including attack movement */\n readonly currentPosition: [number, number, number];\n /** Whether in forward lunge phase */\n readonly isLunging: boolean;\n /** Whether in recovery return phase */\n readonly isRecovering: boolean;\n /** Progress through attack movement (0-1) */\n readonly progress: number;\n}\n\n/**\n * useAttackMovement hook\n *\n * Tracks attack movement physics and returns current position.\n * Automatically handles lunge and recovery phases with smooth easing.\n *\n * @param config - Attack movement configuration\n * @returns Current position and movement state\n *\n * @example\n * ```typescript\n * const { currentPosition, isLunging } = useAttackMovement({\n * isAttacking: playerIsAttacking,\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI,\n * basePosition: [0, 0, 0],\n * attackDirection: new THREE.Vector3(1, 0, 0),\n * });\n *\n * // Use currentPosition for rendering\n * <CharacterModel position={currentPosition} />\n * ```\n *\n * @korean 공격이동사용\n */\nexport function useAttackMovement(\n config: UseAttackMovementConfig\n): UseAttackMovementResult {\n const {\n isAttacking,\n animationType,\n currentStance,\n basePosition,\n attackDirection,\n animationDuration = 0.4,\n } = config;\n\n const physicsRef = useRef(new AttackMovementPhysics());\n\n const attackStartTimeRef = useRef<number | null>(null);\n const attackMovementResultRef = useRef<ReturnType<\n typeof physicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const [currentPosition, setCurrentPosition] = useState<\n [number, number, number]\n >(basePosition);\n const [isLunging, setIsLunging] = useState(false);\n const [isRecovering, setIsRecovering] = useState(false);\n const [progress, setProgress] = useState(0);\n\n const wasAttackingRef = useRef(false);\n\n useEffect(() => {\n if (!isAttacking) {\n setCurrentPosition(basePosition);\n }\n }, [isAttacking, basePosition]);\n\n useEffect(() => {\n if (isAttacking && !wasAttackingRef.current) {\n attackStartTimeRef.current = performance.now() / 1000;\n\n if (animationType && attackDirection) {\n attackMovementResultRef.current =\n physicsRef.current.calculateAttackMovement({\n animationType,\n currentStance,\n direction: attackDirection.clone().normalize(),\n animationDuration,\n });\n }\n } else if (!isAttacking && wasAttackingRef.current) {\n attackStartTimeRef.current = null;\n attackMovementResultRef.current = null;\n setIsLunging(false);\n setIsRecovering(false);\n setProgress(0);\n }\n\n wasAttackingRef.current = isAttacking;\n\n if (!isAttacking || !attackStartTimeRef.current || !attackMovementResultRef.current) {\n return;\n }\n\n const result = attackMovementResultRef.current;\n let animationFrameId: number;\n\n const updatePosition = () => {\n if (attackStartTimeRef.current === null) return;\n\n const currentTime = performance.now() / 1000;\n const elapsedTime = currentTime - attackStartTimeRef.current;\n\n const lunging = physicsRef.current.isInLungePhase(\n elapsedTime,\n result.lungeDuration\n );\n const recovering = physicsRef.current.isInRecoveryPhase(\n elapsedTime,\n result\n );\n\n setIsLunging(lunging);\n setIsRecovering(recovering);\n\n const totalProgress = Math.min(1.0, elapsedTime / result.totalDuration);\n setProgress(totalProgress);\n\n if (lunging || recovering) {\n const basePos = new THREE.Vector3(...basePosition);\n const newPosition = physicsRef.current.applyAttackMovement(\n basePos,\n result,\n elapsedTime,\n recovering\n );\n\n setCurrentPosition([newPosition.x, newPosition.y, newPosition.z]);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n } else {\n setCurrentPosition(basePosition);\n setIsLunging(false);\n setIsRecovering(false);\n setProgress(1.0);\n }\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n }, [\n isAttacking,\n animationType,\n currentStance,\n attackDirection,\n animationDuration,\n basePosition,\n ]);\n\n return {\n currentPosition,\n isLunging,\n isRecovering,\n progress,\n };\n}\n\nexport default useAttackMovement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,kBACd,QACyB;CACzB,MAAM,EACJ,aACA,eACA,eACA,cACA,iBACA,oBAAoB,OAClB;CAEJ,MAAM,aAAa,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"useAttackMovement.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useAttackMovement.ts"],"sourcesContent":["/**\n * useAttackMovement Hook - Track Attack Movement for 3D Characters\n *\n * Custom hook for managing attack movement physics during animations.\n * Tracks lunge and recovery phases with smooth easing curves.\n *\n * @korean 공격이동훅 - 공격 이동 추적\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { AnimationType } from \"@/systems/animation\";\nimport { AttackMovementPhysics } from \"@/systems/physics\";\nimport { TrigramStance } from \"@/types/common\";\n\n/**\n * Configuration for useAttackMovement hook\n */\nexport interface UseAttackMovementConfig {\n /** Whether character is currently attacking */\n readonly isAttacking: boolean;\n /** Animation type for current attack */\n readonly animationType?: AnimationType;\n /** Current trigram stance */\n readonly currentStance: TrigramStance;\n /** Base position of character (when not attacking) */\n readonly basePosition: [number, number, number];\n /** Direction vector for attack (normalized) */\n readonly attackDirection?: THREE.Vector3;\n /** Animation duration in seconds (default: 0.4) */\n readonly animationDuration?: number;\n}\n\n/**\n * Return value from useAttackMovement hook\n */\nexport interface UseAttackMovementResult {\n /** Current 3D position including attack movement */\n readonly currentPosition: [number, number, number];\n /** Whether in forward lunge phase */\n readonly isLunging: boolean;\n /** Whether in recovery return phase */\n readonly isRecovering: boolean;\n /** Progress through attack movement (0-1) */\n readonly progress: number;\n}\n\n/**\n * useAttackMovement hook\n *\n * Tracks attack movement physics and returns current position.\n * Automatically handles lunge and recovery phases with smooth easing.\n *\n * @param config - Attack movement configuration\n * @returns Current position and movement state\n *\n * @example\n * ```typescript\n * const { currentPosition, isLunging } = useAttackMovement({\n * isAttacking: playerIsAttacking,\n * animationType: AnimationType.ROUNDHOUSE_KICK,\n * currentStance: TrigramStance.LI,\n * basePosition: [0, 0, 0],\n * attackDirection: new THREE.Vector3(1, 0, 0),\n * });\n *\n * // Use currentPosition for rendering\n * <CharacterModel position={currentPosition} />\n * ```\n *\n * @korean 공격이동사용\n */\nexport function useAttackMovement(\n config: UseAttackMovementConfig\n): UseAttackMovementResult {\n const {\n isAttacking,\n animationType,\n currentStance,\n basePosition,\n attackDirection,\n animationDuration = 0.4,\n } = config;\n\n const physicsRef = useRef(new AttackMovementPhysics());\n\n const attackStartTimeRef = useRef<number | null>(null);\n const attackMovementResultRef = useRef<ReturnType<\n typeof physicsRef.current.calculateAttackMovement\n > | null>(null);\n\n const [currentPosition, setCurrentPosition] = useState<\n [number, number, number]\n >(basePosition);\n const [isLunging, setIsLunging] = useState(false);\n const [isRecovering, setIsRecovering] = useState(false);\n const [progress, setProgress] = useState(0);\n\n const wasAttackingRef = useRef(false);\n\n useEffect(() => {\n if (!isAttacking) {\n setCurrentPosition(basePosition);\n }\n }, [isAttacking, basePosition]);\n\n useEffect(() => {\n if (isAttacking && !wasAttackingRef.current) {\n attackStartTimeRef.current = performance.now() / 1000;\n\n if (animationType && attackDirection) {\n attackMovementResultRef.current =\n physicsRef.current.calculateAttackMovement({\n animationType,\n currentStance,\n direction: attackDirection.clone().normalize(),\n animationDuration,\n });\n }\n } else if (!isAttacking && wasAttackingRef.current) {\n attackStartTimeRef.current = null;\n attackMovementResultRef.current = null;\n setIsLunging(false);\n setIsRecovering(false);\n setProgress(0);\n }\n\n wasAttackingRef.current = isAttacking;\n\n if (!isAttacking || !attackStartTimeRef.current || !attackMovementResultRef.current) {\n return;\n }\n\n const result = attackMovementResultRef.current;\n let animationFrameId: number;\n\n const updatePosition = () => {\n if (attackStartTimeRef.current === null) return;\n\n const currentTime = performance.now() / 1000;\n const elapsedTime = currentTime - attackStartTimeRef.current;\n\n const lunging = physicsRef.current.isInLungePhase(\n elapsedTime,\n result.lungeDuration\n );\n const recovering = physicsRef.current.isInRecoveryPhase(\n elapsedTime,\n result\n );\n\n setIsLunging(lunging);\n setIsRecovering(recovering);\n\n const totalProgress = Math.min(1.0, elapsedTime / result.totalDuration);\n setProgress(totalProgress);\n\n if (lunging || recovering) {\n const basePos = new THREE.Vector3(...basePosition);\n const newPosition = physicsRef.current.applyAttackMovement(\n basePos,\n result,\n elapsedTime,\n recovering\n );\n\n setCurrentPosition([newPosition.x, newPosition.y, newPosition.z]);\n\n animationFrameId = requestAnimationFrame(updatePosition);\n } else {\n setCurrentPosition(basePosition);\n setIsLunging(false);\n setIsRecovering(false);\n setProgress(1.0);\n }\n };\n\n animationFrameId = requestAnimationFrame(updatePosition);\n\n return () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n };\n }, [\n isAttacking,\n animationType,\n currentStance,\n attackDirection,\n animationDuration,\n basePosition,\n ]);\n\n return {\n currentPosition,\n isLunging,\n isRecovering,\n progress,\n };\n}\n\nexport default useAttackMovement;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,kBACd,QACyB;CACzB,MAAM,EACJ,aACA,eACA,eACA,cACA,iBACA,oBAAoB,OAClB;CAEJ,MAAM,aAAa,OAAO,IAAI,sBAAsB,CAAC;CAErD,MAAM,qBAAqB,OAAsB,IAAI;CACrD,MAAM,0BAA0B,OAEtB,IAAI;CAEd,MAAM,CAAC,iBAAiB,sBAAsB,SAE5C,YAAY;CACd,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,CAAC,UAAU,eAAe,SAAS,CAAC;CAE1C,MAAM,kBAAkB,OAAO,KAAK;CAEpC,gBAAgB;EACd,IAAI,CAAC,aACH,mBAAmB,YAAY;CAEnC,GAAG,CAAC,aAAa,YAAY,CAAC;CAE9B,gBAAgB;EACd,IAAI,eAAe,CAAC,gBAAgB,SAAS;GAC3C,mBAAmB,UAAU,YAAY,IAAI,IAAI;GAEjD,IAAI,iBAAiB,iBACnB,wBAAwB,UACtB,WAAW,QAAQ,wBAAwB;IACzC;IACA;IACA,WAAW,gBAAgB,MAAM,EAAE,UAAU;IAC7C;GACF,CAAC;EAEP,OAAO,IAAI,CAAC,eAAe,gBAAgB,SAAS;GAClD,mBAAmB,UAAU;GAC7B,wBAAwB,UAAU;GAClC,aAAa,KAAK;GAClB,gBAAgB,KAAK;GACrB,YAAY,CAAC;EACf;EAEA,gBAAgB,UAAU;EAE1B,IAAI,CAAC,eAAe,CAAC,mBAAmB,WAAW,CAAC,wBAAwB,SAC1E;EAGF,MAAM,SAAS,wBAAwB;EACvC,IAAI;EAEJ,MAAM,uBAAuB;GAC3B,IAAI,mBAAmB,YAAY,MAAM;GAGzC,MAAM,cADc,YAAY,IAAI,IAAI,MACN,mBAAmB;GAErD,MAAM,UAAU,WAAW,QAAQ,eACjC,aACA,OAAO,aACT;GACA,MAAM,aAAa,WAAW,QAAQ,kBACpC,aACA,MACF;GAEA,aAAa,OAAO;GACpB,gBAAgB,UAAU;GAG1B,YADsB,KAAK,IAAI,GAAK,cAAc,OAAO,aAC7C,CAAa;GAEzB,IAAI,WAAW,YAAY;IACzB,MAAM,UAAU,IAAI,MAAM,QAAQ,GAAG,YAAY;IACjD,MAAM,cAAc,WAAW,QAAQ,oBACrC,SACA,QACA,aACA,UACF;IAEA,mBAAmB;KAAC,YAAY;KAAG,YAAY;KAAG,YAAY;IAAC,CAAC;IAEhE,mBAAmB,sBAAsB,cAAc;GACzD,OAAO;IACL,mBAAmB,YAAY;IAC/B,aAAa,KAAK;IAClB,gBAAgB,KAAK;IACrB,YAAY,CAAG;GACjB;EACF;EAEA,mBAAmB,sBAAsB,cAAc;EAEvD,aAAa;GACX,IAAI,kBACF,qBAAqB,gBAAgB;EAEzC;CACF,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;CACF;AACF"}
|
|
@@ -46,6 +46,7 @@ export interface UseTrainingActionsConfig {
|
|
|
46
46
|
}) => void;
|
|
47
47
|
readonly playerAnimation: {
|
|
48
48
|
readonly transitionTo: (state: AnimationState) => boolean;
|
|
49
|
+
readonly transitionToAttack: (durationSeconds: number) => boolean;
|
|
49
50
|
readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;
|
|
50
51
|
readonly currentState: string;
|
|
51
52
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTrainingActions.d.ts","sourceRoot":"","sources":["../../../../../src/components/screens/training/hooks/useTrainingActions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,eAAe,EACf,eAAe,EAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8CAA8C,CAAC;AAEpF,OAAO,EACL,cAAc,EACd,aAAa,
|
|
1
|
+
{"version":3,"file":"useTrainingActions.d.ts","sourceRoot":"","sources":["../../../../../src/components/screens/training/hooks/useTrainingActions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { AnimationState } from "../../../../systems/animation/core/types.js";
|
|
2
1
|
import { CombatAttackType, DamageType, PlayerArchetype } from "../../../../types/common.js";
|
|
3
2
|
import { DEFAULT_BODY_RADIUS_METERS } from "../../../../types/physicsConstants.js";
|
|
4
3
|
import { TRIGRAM_STANCES_ORDER } from "../../../../systems/trigram/types.js";
|
|
5
4
|
import { calculateDistance3D } from "../../../../utils/math.js";
|
|
6
|
-
import { getAnimationForTechnique } from "../../../../systems/animation/core/AnimationRegistry.js";
|
|
5
|
+
import { getAnimationDurationOrFallback, getAnimationForTechnique } from "../../../../systems/animation/core/AnimationRegistry.js";
|
|
7
6
|
import { getTechniquesByStance } from "../../../../systems/trigram/techniques/index.js";
|
|
8
7
|
import { KoreanTechniquesSystem } from "../../../../systems/trigram/KoreanTechniques.js";
|
|
9
8
|
import { physicalReachCalculator } from "../../../../systems/physics/PhysicalReachCalculator.js";
|
|
@@ -253,8 +252,11 @@ function useTrainingActions(config) {
|
|
|
253
252
|
startTime,
|
|
254
253
|
techniqueId
|
|
255
254
|
};
|
|
256
|
-
if (setAttackAnimation && techniqueId)
|
|
257
|
-
|
|
255
|
+
if (setAttackAnimation && techniqueId) {
|
|
256
|
+
const animationName = getAnimationForTechnique(techniqueId);
|
|
257
|
+
setAttackAnimation(animationName);
|
|
258
|
+
playerAnimation.transitionToAttack(getAnimationDurationOrFallback(animationName));
|
|
259
|
+
} else playerAnimation.transitionToAttack(getAnimationDurationOrFallback());
|
|
258
260
|
if (!techniqueToUse && selectedTechniqueId) techniqueToUse = KoreanTechniquesSystem.getTechniqueById(selectedTechniqueId);
|
|
259
261
|
if (playAttackSound) {
|
|
260
262
|
const damage = techniqueToUse?.damage ?? 10;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTrainingActions.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingActions.ts"],"sourcesContent":["/**\n * useTrainingActions Hook - Training Action Handlers\n *\n * Custom hook for managing training action handlers.\n * Mirrors useCombatActions pattern for consistency.\n *\n * @korean 훈련액션훅 - 훈련 액션 핸들러 관리\n */\n\nimport { useCallback, useRef } from \"react\";\nimport {\n AudioBodyRegion,\n ImpactIntensity,\n} from \"../../../../audio/types\";\nimport type { AttackIntensity } from \"../../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../../data/archetypePhysicalAttributes\";\nimport {\n AnimationState,\n AnimationType,\n getAnimationForTechnique,\n} from \"../../../../systems/animation\";\nimport { physicalReachCalculator } from \"../../../../systems/physics\";\nimport { getTechniquesByStance } from \"../../../../systems/trigram/techniques\";\nimport { KoreanTechniquesSystem } from \"../../../../systems/trigram/KoreanTechniques\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../systems/trigram/types\";\nimport type { KoreanTechnique } from \"../../../../systems/vitalpoint/types\";\nimport {\n CombatAttackType,\n DamageType,\n PlayerArchetype,\n Position,\n TrigramStance,\n} from \"../../../../types/common\";\nimport {\n DEFAULT_BODY_RADIUS_METERS,\n METERS_TO_TRAINING_UNITS,\n} from \"../../../../types/physicsConstants\";\nimport { calculateDistance3D } from \"../../../../utils/math\";\nimport { TrainingActions, TrainingScreenState } from \"./useTrainingState\";\n\nexport interface UseTrainingActionsConfig {\n readonly state: TrainingScreenState;\n readonly actions: TrainingActions;\n readonly playerPosition: Position;\n readonly player3DPosition: [number, number, number];\n readonly dummyPosition: [number, number, number];\n readonly playerArchetype: import(\"../../../../types/common\").PlayerArchetype;\n readonly playerStance: TrigramStance;\n /** Ref to current selected technique's animation type for distance-based hit detection */\n readonly currentTechniqueAnimationTypeRef: React.MutableRefObject<AnimationType>;\n readonly audio: {\n readonly playSFX: (sound: string, volume?: number) => Promise<void>;\n };\n /** Attack sound function from useCombatAudio for playing attack whoosh sounds */\n readonly playAttackSound?: (intensity: AttackIntensity) => Promise<void>;\n /** Bone impact audio function from useCombatAudio or similar hook */\n readonly playBoneImpactSound?: (options: {\n region?: AudioBodyRegion;\n intensity?: ImpactIntensity;\n damage?: number;\n remainingHealth?: number;\n vitalPoint?: boolean;\n hitPosition?: { x: number; y: number; z?: number };\n }) => Promise<void>;\n readonly onPlayerUpdate: (updates: {\n currentStance?: TrigramStance;\n lastActionTime?: number;\n position?: Position;\n }) => void;\n readonly playerAnimation: {\n readonly transitionTo: (state: AnimationState) => boolean;\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n readonly currentState: string;\n };\n /** External ref to store pending attack data - shared with animation events */\n readonly pendingAttackRef: React.MutableRefObject<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>;\n /** Currently selected technique ID (from technique bar) */\n readonly selectedTechniqueId?: string;\n /** Callback to set the visual attack animation */\n readonly setAttackAnimation?: (animationName: string | undefined) => void;\n}\n\nexport interface UseTrainingActionsReturn {\n readonly handleStartTraining: () => void;\n readonly handleStopTraining: () => void;\n readonly handleDummyHit: (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean;\n readonly handleDummyDefeated: () => void;\n readonly handleStanceChange: (stanceIndex: number) => void;\n readonly handleAttack: () => void;\n}\n\n/**\n * Get the best default technique for an archetype based on current stance.\n *\n * Each archetype has preferred combat styles:\n * - MUSA: Direct strikes, joint techniques (BLUNT/JOINT damage)\n * - AMSALJA: Nerve strikes, pressure points (NERVE/PRESSURE damage)\n * - HACKER: Precise calculated strikes (high accuracy)\n * - JEONGBO_YOWON: Psychological pressure, submissions (PRESSURE/JOINT)\n * - JOJIK_POKRYEOKBAE: High damage brutal strikes\n *\n * @korean 원형별 기본 기술 선택 - 8개 자세에 따른 최적 기술\n */\nfunction getDefaultTechniqueForArchetype(\n archetype: PlayerArchetype,\n stance: TrigramStance,\n): KoreanTechnique | undefined {\n const techniques = getTechniquesByStance(stance);\n if (techniques.length === 0) return undefined;\n\n const scoredTechniques = techniques.map((tech) => {\n let score = 0;\n const damageType = tech.damageType;\n const attackType = tech.type;\n const isAdvanced =\n (tech.kiCost || 0) >= 10 || (tech.staminaCost || 0) >= 15;\n\n switch (archetype) {\n case PlayerArchetype.MUSA:\n if (damageType === DamageType.JOINT) score += 30;\n if (damageType === DamageType.CRUSHING) score += 25;\n if (damageType === DamageType.BLUNT) score += 20;\n if (attackType === CombatAttackType.STRIKE) score += 15;\n if (attackType === CombatAttackType.PUNCH) score += 10;\n if (isAdvanced) score += 10;\n break;\n\n case PlayerArchetype.AMSALJA:\n if (damageType === DamageType.NERVE) score += 35;\n if (damageType === DamageType.PRESSURE) score += 30;\n if (attackType === CombatAttackType.NERVE_STRIKE) score += 25;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 25;\n if (attackType === CombatAttackType.THRUST) score += 15;\n if ((tech.accuracy || 0) >= 0.9) score += 10;\n break;\n\n case PlayerArchetype.HACKER:\n if ((tech.accuracy || 0) >= 0.9) score += 30;\n if ((tech.accuracy || 0) >= 0.8) score += 15;\n if (damageType === DamageType.NERVE) score += 20;\n if (damageType === DamageType.INTERNAL) score += 20;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 15;\n break;\n\n case PlayerArchetype.JEONGBO_YOWON:\n if (damageType === DamageType.PRESSURE) score += 30;\n if (damageType === DamageType.JOINT) score += 25;\n if (attackType === CombatAttackType.GRAPPLE) score += 20;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 15;\n break;\n\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n if ((tech.damage || 0) >= 35) score += 35;\n if ((tech.damage || 0) >= 30) score += 20;\n if (damageType === DamageType.SLASHING) score += 20;\n if (damageType === DamageType.PIERCING) score += 15;\n if (damageType === DamageType.CRUSHING) score += 15;\n if (attackType === CombatAttackType.KICK) score += 10;\n break;\n }\n\n return { technique: tech, score };\n });\n\n scoredTechniques.sort((a, b) => b.score - a.score);\n return scoredTechniques[0]?.technique;\n}\n\n/**\n * Calculate hit accuracy based on distance and effective reach.\n * Uses PhysicalReachCalculator for animation-aware reach calculation.\n *\n * Distance logic matches CombatSystem: if out of reach, guaranteed miss (accuracy 0).\n * Training scene coordinates are in meters, using METERS_TO_TRAINING_UNITS = 1.0.\n *\n * **IMPORTANT**: Distance calculation accounts for body radius.\n * Center-to-center distance is adjusted by subtracting target body radius\n * because attacks hit the body surface, not the center point.\n * The target body radius is calculated from physical attributes for archetypes,\n * or uses DEFAULT_BODY_RADIUS_METERS for training dummies.\n *\n * Example: If player center is 1.5m from dummy center, but dummy body\n * extends 0.23m from center, the effective distance to hit is 1.27m.\n *\n * @korean 거리 기반 명중률 계산 - 사정거리 밖이면 빗나감\n */\nfunction calculateHitAccuracy(\n playerPos: [number, number, number],\n dummyPos: [number, number, number],\n archetype: import(\"../../../../types/common\").PlayerArchetype,\n stance: TrigramStance,\n animationType?: AnimationType,\n reachConfig?: import(\"../../../../types/physics\").PhysicalReachConfig,\n): number {\n const centerToCenterDistance = calculateDistance3D(playerPos, dummyPos);\n\n const playerPhysicalAttributes = getArchetypePhysicalAttributes(archetype);\n\n const targetBodyRadius = DEFAULT_BODY_RADIUS_METERS;\n\n const effectiveDistance = Math.max(\n 0,\n centerToCenterDistance - targetBodyRadius,\n );\n\n if (animationType !== undefined) {\n const maxReachMeters = physicalReachCalculator.calculateMaxReach(\n playerPhysicalAttributes,\n animationType,\n stance,\n reachConfig, // Use technique's designed reach if provided\n );\n\n const reachInUnits = maxReachMeters * METERS_TO_TRAINING_UNITS;\n\n if (effectiveDistance > reachInUnits) {\n return 0;\n }\n\n return Math.max(0.7, 1.0 - (effectiveDistance / reachInUnits) * 0.3);\n }\n\n const defaultReach = 0.7 * METERS_TO_TRAINING_UNITS;\n if (effectiveDistance > defaultReach) {\n return 0; // Out of reach = miss\n }\n return Math.max(0.5, 1.0 - (effectiveDistance / defaultReach) * 0.5);\n}\n\n/**\n * useTrainingActions hook\n * Provides training action handlers with proper memoization\n */\nexport function useTrainingActions(\n config: UseTrainingActionsConfig,\n): UseTrainingActionsReturn {\n const {\n state,\n actions,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n currentTechniqueAnimationTypeRef,\n audio,\n playBoneImpactSound,\n playAttackSound,\n onPlayerUpdate,\n playerAnimation,\n pendingAttackRef,\n selectedTechniqueId,\n setAttackAnimation,\n } = config;\n\n const dummyResetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const handleStartTraining = useCallback(() => {\n actions.startTraining();\n audio.playSFX(\"menu_select\");\n }, [actions, audio]);\n\n const handleStopTraining = useCallback(() => {\n if (dummyResetTimeoutRef.current) {\n clearTimeout(dummyResetTimeoutRef.current);\n dummyResetTimeoutRef.current = null;\n }\n actions.stopTraining();\n audio.playSFX(\"menu_back\");\n }, [actions, audio]);\n\n const handleDummyDefeated = useCallback(() => {\n actions.setFeedback(\"훈련 더미 무력화! | Dummy Defeated!\");\n audio.playSFX(\"ki_release\");\n\n if (dummyResetTimeoutRef.current) {\n clearTimeout(dummyResetTimeoutRef.current);\n }\n\n dummyResetTimeoutRef.current = setTimeout(() => {\n actions.resetDummy();\n }, 2000);\n }, [actions, audio]);\n\n const handleDummyHit = useCallback(\n (\n _vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ): boolean => {\n const animationType = attackContext?.animationType;\n\n let reachConfig: import(\"../../../../types/physics\").PhysicalReachConfig | undefined;\n const resolvedTechniqueId =\n attackContext?.techniqueId ?? selectedTechniqueId;\n if (resolvedTechniqueId) {\n const technique = KoreanTechniquesSystem.getTechniqueById(resolvedTechniqueId);\n reachConfig = technique?.reachConfig;\n }\n\n const accuracy = calculateHitAccuracy(\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n animationType,\n reachConfig,\n );\n\n const hitPosition: [number, number, number] = [\n dummyPosition[0],\n 1.5,\n dummyPosition[2],\n ];\n\n if (accuracy > 0.5) {\n const points = Math.round(accuracy * 100);\n const damage = Math.round(accuracy * 15); // 0-15 damage based on accuracy\n const isPerfect = accuracy > 0.9;\n\n if (state.isTraining) {\n actions.registerHit(points, damage, isPerfect);\n }\n\n if (playBoneImpactSound) {\n void playBoneImpactSound({\n damage,\n remainingHealth: 100, // Dummy has 100 health\n vitalPoint: false,\n hitPosition: { x: hitPosition[0], y: hitPosition[1], z: hitPosition[2] },\n });\n }\n\n let effectType: \"success\" | \"perfect\";\n if (isPerfect) {\n actions.setFeedback(\"완벽한 타격! | Perfect Strike!\");\n audio.playSFX(\"ki_release\");\n effectType = \"perfect\";\n } else if (accuracy > 0.7) {\n actions.setFeedback(\"좋은 타격! | Good Strike!\");\n audio.playSFX(\"ki_charge\");\n effectType = \"success\";\n } else {\n actions.setFeedback(\"타격 성공 | Strike Success\");\n audio.playSFX(\"menu_click\");\n effectType = \"success\";\n }\n\n actions.addHitEffect({\n position: hitPosition,\n type: effectType,\n visible: true,\n damage,\n });\n\n return true;\n } else {\n if (state.isTraining) {\n actions.registerMiss();\n }\n actions.setFeedback(\"빗나감 | Miss - Out of reach!\");\n audio.playSFX(\"menu_navigate\");\n\n actions.addHitEffect({\n position: hitPosition,\n type: \"miss\",\n visible: true,\n });\n\n return false;\n }\n },\n [\n state.isTraining,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n actions,\n audio,\n playBoneImpactSound,\n selectedTechniqueId,\n ],\n );\n\n const handleStanceChange = useCallback(\n (stanceIndex: number) => {\n actions.setStanceIndex(stanceIndex);\n const stance = TRIGRAM_STANCES_ORDER[stanceIndex];\n if (stance) {\n playerAnimation.transitionToStanceGuard(stance);\n onPlayerUpdate({ currentStance: stance });\n audio.playSFX(\"stance_change\");\n }\n },\n [actions, onPlayerUpdate, audio, playerAnimation],\n );\n\n const handleAttack = useCallback(() => {\n let techniqueToUse: KoreanTechnique | undefined;\n let techniqueId = selectedTechniqueId;\n\n if (!techniqueId) {\n const defaultTechnique = getDefaultTechniqueForArchetype(\n playerArchetype,\n playerStance,\n );\n if (defaultTechnique) {\n techniqueToUse = defaultTechnique;\n techniqueId = defaultTechnique.id;\n }\n }\n\n let animationType = currentTechniqueAnimationTypeRef.current;\n if (techniqueToUse?.animationType) {\n animationType = techniqueToUse.animationType;\n currentTechniqueAnimationTypeRef.current = animationType;\n }\n\n const startTime = performance.now() / 1000; // Current time in seconds\n\n const accuracy = calculateHitAccuracy(\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n animationType,\n techniqueToUse?.reachConfig, // Pass technique's reachConfig for accurate reach\n );\n\n pendingAttackRef.current = {\n accuracy,\n vitalPoint: state.selectedVitalPoint ?? \"generic\",\n animationType,\n startTime,\n techniqueId, // Store resolved technique ID for handleDummyHit\n };\n\n if (setAttackAnimation && techniqueId) {\n const animationName = getAnimationForTechnique(techniqueId);\n setAttackAnimation(animationName);\n }\n\n playerAnimation.transitionTo(AnimationState.ATTACK);\n\n if (!techniqueToUse && selectedTechniqueId) {\n techniqueToUse = KoreanTechniquesSystem.getTechniqueById(selectedTechniqueId);\n }\n\n if (playAttackSound) {\n const damage = techniqueToUse?.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n void playAttackSound(intensity);\n } else {\n audio.playSFX(\"whoosh\");\n }\n }, [\n state.selectedVitalPoint,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n currentTechniqueAnimationTypeRef,\n playerAnimation,\n audio,\n playAttackSound,\n pendingAttackRef,\n selectedTechniqueId,\n setAttackAnimation,\n ]);\n\n return {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n };\n}\n\nexport default useTrainingActions;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useTrainingActions.js","names":[],"sources":["../../../../../src/components/screens/training/hooks/useTrainingActions.ts"],"sourcesContent":["/**\n * useTrainingActions Hook - Training Action Handlers\n *\n * Custom hook for managing training action handlers.\n * Mirrors useCombatActions pattern for consistency.\n *\n * @korean 훈련액션훅 - 훈련 액션 핸들러 관리\n */\n\nimport { useCallback, useRef } from \"react\";\nimport {\n AudioBodyRegion,\n ImpactIntensity,\n} from \"../../../../audio/types\";\nimport type { AttackIntensity } from \"../../../screens/combat/hooks/useCombatAudio\";\nimport { getArchetypePhysicalAttributes } from \"../../../../data/archetypePhysicalAttributes\";\nimport {\n AnimationState,\n AnimationType,\n getAnimationDurationOrFallback,\n getAnimationForTechnique,\n} from \"../../../../systems/animation\";\nimport { physicalReachCalculator } from \"../../../../systems/physics\";\nimport { getTechniquesByStance } from \"../../../../systems/trigram/techniques\";\nimport { KoreanTechniquesSystem } from \"../../../../systems/trigram/KoreanTechniques\";\nimport { TRIGRAM_STANCES_ORDER } from \"../../../../systems/trigram/types\";\nimport type { KoreanTechnique } from \"../../../../systems/vitalpoint/types\";\nimport {\n CombatAttackType,\n DamageType,\n PlayerArchetype,\n Position,\n TrigramStance,\n} from \"../../../../types/common\";\nimport {\n DEFAULT_BODY_RADIUS_METERS,\n METERS_TO_TRAINING_UNITS,\n} from \"../../../../types/physicsConstants\";\nimport { calculateDistance3D } from \"../../../../utils/math\";\nimport { TrainingActions, TrainingScreenState } from \"./useTrainingState\";\n\nexport interface UseTrainingActionsConfig {\n readonly state: TrainingScreenState;\n readonly actions: TrainingActions;\n readonly playerPosition: Position;\n readonly player3DPosition: [number, number, number];\n readonly dummyPosition: [number, number, number];\n readonly playerArchetype: import(\"../../../../types/common\").PlayerArchetype;\n readonly playerStance: TrigramStance;\n /** Ref to current selected technique's animation type for distance-based hit detection */\n readonly currentTechniqueAnimationTypeRef: React.MutableRefObject<AnimationType>;\n readonly audio: {\n readonly playSFX: (sound: string, volume?: number) => Promise<void>;\n };\n /** Attack sound function from useCombatAudio for playing attack whoosh sounds */\n readonly playAttackSound?: (intensity: AttackIntensity) => Promise<void>;\n /** Bone impact audio function from useCombatAudio or similar hook */\n readonly playBoneImpactSound?: (options: {\n region?: AudioBodyRegion;\n intensity?: ImpactIntensity;\n damage?: number;\n remainingHealth?: number;\n vitalPoint?: boolean;\n hitPosition?: { x: number; y: number; z?: number };\n }) => Promise<void>;\n readonly onPlayerUpdate: (updates: {\n currentStance?: TrigramStance;\n lastActionTime?: number;\n position?: Position;\n }) => void;\n readonly playerAnimation: {\n readonly transitionTo: (state: AnimationState) => boolean;\n readonly transitionToAttack: (durationSeconds: number) => boolean;\n readonly transitionToStanceGuard: (stance: TrigramStance) => boolean;\n readonly currentState: string;\n };\n /** External ref to store pending attack data - shared with animation events */\n readonly pendingAttackRef: React.MutableRefObject<{\n accuracy: number;\n vitalPoint: string;\n animationType?: AnimationType;\n startTime?: number;\n techniqueId?: string;\n } | null>;\n /** Currently selected technique ID (from technique bar) */\n readonly selectedTechniqueId?: string;\n /** Callback to set the visual attack animation */\n readonly setAttackAnimation?: (animationName: string | undefined) => void;\n}\n\nexport interface UseTrainingActionsReturn {\n readonly handleStartTraining: () => void;\n readonly handleStopTraining: () => void;\n readonly handleDummyHit: (\n vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ) => boolean;\n readonly handleDummyDefeated: () => void;\n readonly handleStanceChange: (stanceIndex: number) => void;\n readonly handleAttack: () => void;\n}\n\n/**\n * Get the best default technique for an archetype based on current stance.\n *\n * Each archetype has preferred combat styles:\n * - MUSA: Direct strikes, joint techniques (BLUNT/JOINT damage)\n * - AMSALJA: Nerve strikes, pressure points (NERVE/PRESSURE damage)\n * - HACKER: Precise calculated strikes (high accuracy)\n * - JEONGBO_YOWON: Psychological pressure, submissions (PRESSURE/JOINT)\n * - JOJIK_POKRYEOKBAE: High damage brutal strikes\n *\n * @korean 원형별 기본 기술 선택 - 8개 자세에 따른 최적 기술\n */\nfunction getDefaultTechniqueForArchetype(\n archetype: PlayerArchetype,\n stance: TrigramStance,\n): KoreanTechnique | undefined {\n const techniques = getTechniquesByStance(stance);\n if (techniques.length === 0) return undefined;\n\n const scoredTechniques = techniques.map((tech) => {\n let score = 0;\n const damageType = tech.damageType;\n const attackType = tech.type;\n const isAdvanced =\n (tech.kiCost || 0) >= 10 || (tech.staminaCost || 0) >= 15;\n\n switch (archetype) {\n case PlayerArchetype.MUSA:\n if (damageType === DamageType.JOINT) score += 30;\n if (damageType === DamageType.CRUSHING) score += 25;\n if (damageType === DamageType.BLUNT) score += 20;\n if (attackType === CombatAttackType.STRIKE) score += 15;\n if (attackType === CombatAttackType.PUNCH) score += 10;\n if (isAdvanced) score += 10;\n break;\n\n case PlayerArchetype.AMSALJA:\n if (damageType === DamageType.NERVE) score += 35;\n if (damageType === DamageType.PRESSURE) score += 30;\n if (attackType === CombatAttackType.NERVE_STRIKE) score += 25;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 25;\n if (attackType === CombatAttackType.THRUST) score += 15;\n if ((tech.accuracy || 0) >= 0.9) score += 10;\n break;\n\n case PlayerArchetype.HACKER:\n if ((tech.accuracy || 0) >= 0.9) score += 30;\n if ((tech.accuracy || 0) >= 0.8) score += 15;\n if (damageType === DamageType.NERVE) score += 20;\n if (damageType === DamageType.INTERNAL) score += 20;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 15;\n break;\n\n case PlayerArchetype.JEONGBO_YOWON:\n if (damageType === DamageType.PRESSURE) score += 30;\n if (damageType === DamageType.JOINT) score += 25;\n if (attackType === CombatAttackType.GRAPPLE) score += 20;\n if (attackType === CombatAttackType.PRESSURE_POINT) score += 15;\n break;\n\n case PlayerArchetype.JOJIK_POKRYEOKBAE:\n if ((tech.damage || 0) >= 35) score += 35;\n if ((tech.damage || 0) >= 30) score += 20;\n if (damageType === DamageType.SLASHING) score += 20;\n if (damageType === DamageType.PIERCING) score += 15;\n if (damageType === DamageType.CRUSHING) score += 15;\n if (attackType === CombatAttackType.KICK) score += 10;\n break;\n }\n\n return { technique: tech, score };\n });\n\n scoredTechniques.sort((a, b) => b.score - a.score);\n return scoredTechniques[0]?.technique;\n}\n\n/**\n * Calculate hit accuracy based on distance and effective reach.\n * Uses PhysicalReachCalculator for animation-aware reach calculation.\n *\n * Distance logic matches CombatSystem: if out of reach, guaranteed miss (accuracy 0).\n * Training scene coordinates are in meters, using METERS_TO_TRAINING_UNITS = 1.0.\n *\n * **IMPORTANT**: Distance calculation accounts for body radius.\n * Center-to-center distance is adjusted by subtracting target body radius\n * because attacks hit the body surface, not the center point.\n * The target body radius is calculated from physical attributes for archetypes,\n * or uses DEFAULT_BODY_RADIUS_METERS for training dummies.\n *\n * Example: If player center is 1.5m from dummy center, but dummy body\n * extends 0.23m from center, the effective distance to hit is 1.27m.\n *\n * @korean 거리 기반 명중률 계산 - 사정거리 밖이면 빗나감\n */\nfunction calculateHitAccuracy(\n playerPos: [number, number, number],\n dummyPos: [number, number, number],\n archetype: import(\"../../../../types/common\").PlayerArchetype,\n stance: TrigramStance,\n animationType?: AnimationType,\n reachConfig?: import(\"../../../../types/physics\").PhysicalReachConfig,\n): number {\n const centerToCenterDistance = calculateDistance3D(playerPos, dummyPos);\n\n const playerPhysicalAttributes = getArchetypePhysicalAttributes(archetype);\n\n const targetBodyRadius = DEFAULT_BODY_RADIUS_METERS;\n\n const effectiveDistance = Math.max(\n 0,\n centerToCenterDistance - targetBodyRadius,\n );\n\n if (animationType !== undefined) {\n const maxReachMeters = physicalReachCalculator.calculateMaxReach(\n playerPhysicalAttributes,\n animationType,\n stance,\n reachConfig, // Use technique's designed reach if provided\n );\n\n const reachInUnits = maxReachMeters * METERS_TO_TRAINING_UNITS;\n\n if (effectiveDistance > reachInUnits) {\n return 0;\n }\n\n return Math.max(0.7, 1.0 - (effectiveDistance / reachInUnits) * 0.3);\n }\n\n const defaultReach = 0.7 * METERS_TO_TRAINING_UNITS;\n if (effectiveDistance > defaultReach) {\n return 0; // Out of reach = miss\n }\n return Math.max(0.5, 1.0 - (effectiveDistance / defaultReach) * 0.5);\n}\n\n/**\n * useTrainingActions hook\n * Provides training action handlers with proper memoization\n */\nexport function useTrainingActions(\n config: UseTrainingActionsConfig,\n): UseTrainingActionsReturn {\n const {\n state,\n actions,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n currentTechniqueAnimationTypeRef,\n audio,\n playBoneImpactSound,\n playAttackSound,\n onPlayerUpdate,\n playerAnimation,\n pendingAttackRef,\n selectedTechniqueId,\n setAttackAnimation,\n } = config;\n\n const dummyResetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const handleStartTraining = useCallback(() => {\n actions.startTraining();\n audio.playSFX(\"menu_select\");\n }, [actions, audio]);\n\n const handleStopTraining = useCallback(() => {\n if (dummyResetTimeoutRef.current) {\n clearTimeout(dummyResetTimeoutRef.current);\n dummyResetTimeoutRef.current = null;\n }\n actions.stopTraining();\n audio.playSFX(\"menu_back\");\n }, [actions, audio]);\n\n const handleDummyDefeated = useCallback(() => {\n actions.setFeedback(\"훈련 더미 무력화! | Dummy Defeated!\");\n audio.playSFX(\"ki_release\");\n\n if (dummyResetTimeoutRef.current) {\n clearTimeout(dummyResetTimeoutRef.current);\n }\n\n dummyResetTimeoutRef.current = setTimeout(() => {\n actions.resetDummy();\n }, 2000);\n }, [actions, audio]);\n\n const handleDummyHit = useCallback(\n (\n _vitalPointId: string,\n attackContext?: {\n animationType?: AnimationType;\n techniqueId?: string;\n },\n ): boolean => {\n const animationType = attackContext?.animationType;\n\n let reachConfig: import(\"../../../../types/physics\").PhysicalReachConfig | undefined;\n const resolvedTechniqueId =\n attackContext?.techniqueId ?? selectedTechniqueId;\n if (resolvedTechniqueId) {\n const technique = KoreanTechniquesSystem.getTechniqueById(resolvedTechniqueId);\n reachConfig = technique?.reachConfig;\n }\n\n const accuracy = calculateHitAccuracy(\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n animationType,\n reachConfig,\n );\n\n const hitPosition: [number, number, number] = [\n dummyPosition[0],\n 1.5,\n dummyPosition[2],\n ];\n\n if (accuracy > 0.5) {\n const points = Math.round(accuracy * 100);\n const damage = Math.round(accuracy * 15); // 0-15 damage based on accuracy\n const isPerfect = accuracy > 0.9;\n\n if (state.isTraining) {\n actions.registerHit(points, damage, isPerfect);\n }\n\n if (playBoneImpactSound) {\n void playBoneImpactSound({\n damage,\n remainingHealth: 100, // Dummy has 100 health\n vitalPoint: false,\n hitPosition: { x: hitPosition[0], y: hitPosition[1], z: hitPosition[2] },\n });\n }\n\n let effectType: \"success\" | \"perfect\";\n if (isPerfect) {\n actions.setFeedback(\"완벽한 타격! | Perfect Strike!\");\n audio.playSFX(\"ki_release\");\n effectType = \"perfect\";\n } else if (accuracy > 0.7) {\n actions.setFeedback(\"좋은 타격! | Good Strike!\");\n audio.playSFX(\"ki_charge\");\n effectType = \"success\";\n } else {\n actions.setFeedback(\"타격 성공 | Strike Success\");\n audio.playSFX(\"menu_click\");\n effectType = \"success\";\n }\n\n actions.addHitEffect({\n position: hitPosition,\n type: effectType,\n visible: true,\n damage,\n });\n\n return true;\n } else {\n if (state.isTraining) {\n actions.registerMiss();\n }\n actions.setFeedback(\"빗나감 | Miss - Out of reach!\");\n audio.playSFX(\"menu_navigate\");\n\n actions.addHitEffect({\n position: hitPosition,\n type: \"miss\",\n visible: true,\n });\n\n return false;\n }\n },\n [\n state.isTraining,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n actions,\n audio,\n playBoneImpactSound,\n selectedTechniqueId,\n ],\n );\n\n const handleStanceChange = useCallback(\n (stanceIndex: number) => {\n actions.setStanceIndex(stanceIndex);\n const stance = TRIGRAM_STANCES_ORDER[stanceIndex];\n if (stance) {\n playerAnimation.transitionToStanceGuard(stance);\n onPlayerUpdate({ currentStance: stance });\n audio.playSFX(\"stance_change\");\n }\n },\n [actions, onPlayerUpdate, audio, playerAnimation],\n );\n\n const handleAttack = useCallback(() => {\n let techniqueToUse: KoreanTechnique | undefined;\n let techniqueId = selectedTechniqueId;\n\n if (!techniqueId) {\n const defaultTechnique = getDefaultTechniqueForArchetype(\n playerArchetype,\n playerStance,\n );\n if (defaultTechnique) {\n techniqueToUse = defaultTechnique;\n techniqueId = defaultTechnique.id;\n }\n }\n\n let animationType = currentTechniqueAnimationTypeRef.current;\n if (techniqueToUse?.animationType) {\n animationType = techniqueToUse.animationType;\n currentTechniqueAnimationTypeRef.current = animationType;\n }\n\n const startTime = performance.now() / 1000; // Current time in seconds\n\n const accuracy = calculateHitAccuracy(\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n animationType,\n techniqueToUse?.reachConfig, // Pass technique's reachConfig for accurate reach\n );\n\n pendingAttackRef.current = {\n accuracy,\n vitalPoint: state.selectedVitalPoint ?? \"generic\",\n animationType,\n startTime,\n techniqueId, // Store resolved technique ID for handleDummyHit\n };\n\n if (setAttackAnimation && techniqueId) {\n const animationName = getAnimationForTechnique(techniqueId);\n setAttackAnimation(animationName);\n playerAnimation.transitionToAttack(\n getAnimationDurationOrFallback(animationName),\n );\n } else {\n playerAnimation.transitionToAttack(getAnimationDurationOrFallback());\n }\n\n if (!techniqueToUse && selectedTechniqueId) {\n techniqueToUse = KoreanTechniquesSystem.getTechniqueById(selectedTechniqueId);\n }\n\n if (playAttackSound) {\n const damage = techniqueToUse?.damage ?? 10;\n const intensity: AttackIntensity =\n damage >= 40\n ? \"critical\"\n : damage >= 25\n ? \"heavy\"\n : damage >= 10\n ? \"medium\"\n : \"light\";\n void playAttackSound(intensity);\n } else {\n audio.playSFX(\"whoosh\");\n }\n }, [\n state.selectedVitalPoint,\n player3DPosition,\n dummyPosition,\n playerArchetype,\n playerStance,\n currentTechniqueAnimationTypeRef,\n playerAnimation,\n audio,\n playAttackSound,\n pendingAttackRef,\n selectedTechniqueId,\n setAttackAnimation,\n ]);\n\n return {\n handleStartTraining,\n handleStopTraining,\n handleDummyHit,\n handleDummyDefeated,\n handleStanceChange,\n handleAttack,\n };\n}\n\nexport default useTrainingActions;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTrainingLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/screens/training/hooks/useTrainingLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"useTrainingLayout.d.ts","sourceRoot":"","sources":["../../../../../src/components/screens/training/hooks/useTrainingLayout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAsBH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,eAAe,EAAE,uBAAuB,CAAC;IAClD,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,cAAc,CAmGhB"}
|