blacktrigram 0.7.23 → 0.7.25
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/ARCHITECTURE.md +51 -47
- package/CRA-ASSESSMENT.md +13 -13
- package/DATA_MODEL.md +6 -2
- package/SECURITY_ARCHITECTURE.md +6 -6
- package/THREAT_MODEL.md +6 -6
- package/lib/audio/AudioManager.js +3 -2
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/systems/ai/AdaptiveDifficulty.js +2 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- 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/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceSystem.js","names":[],"sources":["../../../src/systems/combat/BalanceSystem.ts"],"sourcesContent":["/**\n * Balance System for managing stability and vulnerability windows.\n *\n * **Korean**: 균형 시스템 (Balance System)\n *\n * Implements physical balance and stability mechanics. Loss of balance creates\n * vulnerability windows where damage is increased and defensive options limited.\n * Balance is affected by:\n * - Leg strikes\n * - Throws and sweeps\n * - Heavy impacts\n * - Fatigue (low stamina)\n * - Stance transitions (0.5s vulnerability window)\n * - Body part damage (legs reduce balance)\n * - Rapid stance changes (penalty system)\n *\n * ## Balance States\n *\n * - **Stable**: 80-100 balance - Full mobility and defense\n * - **Unsteady**: 50-79 balance - Reduced evasion, slower movement\n * - **Off-Balance**: 20-49 balance - Vulnerability window, defense penalty\n * - **Falling**: 0-19 balance - Severe vulnerability, possible knockdown\n *\n * ## Fall System Integration\n *\n * When balance falls below 20%, the system can trigger fall animations (낙법).\n * Fall direction is determined by attack vector and player stance.\n *\n * ## Stance Transition Vulnerability\n *\n * Changing stances creates a 0.5s vulnerability window with 1.5x damage multiplier.\n * Rapid stance changes (>2 in 3s) apply additional 20% balance penalty for 2s.\n *\n * @module systems/combat/BalanceSystem\n * @category Combat System\n * @korean 균형시스템\n */\n\nimport { BodyRegion } from \"@/types\";\nimport { TrigramStance } from \"@/types/common\";\nimport {\n determineFallDirection,\n determineFallFromStance,\n getRecoveryConfig,\n isVulnerableFrame,\n} from \"../animation\";\nimport type {\n FallType,\n GroundState,\n RecoveryAnimationType,\n} from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Stance transition state tracking.\n *\n * **Korean**: 자세 전환 상태 (Stance Transition State)\n *\n * Tracks when a player is transitioning between stances, creating a\n * 0.5s vulnerability window with increased damage multiplier.\n */\nexport interface TransitionState {\n /** Whether player is currently transitioning */\n readonly isTransitioning: boolean;\n /** Timestamp when transition started (ms) */\n readonly transitionStartTime: number;\n /** Damage multiplier during transition (1.5x) */\n readonly vulnerabilityMultiplier: number;\n /** Stance transitioning from */\n readonly fromStance: TrigramStance | null;\n /** Stance transitioning to */\n readonly toStance: TrigramStance | null;\n}\n\n/**\n * Stance change history entry.\n *\n * **Korean**: 자세 변경 기록 (Stance Change Record)\n */\ninterface StanceChangeRecord {\n /** Timestamp of stance change */\n readonly timestamp: number;\n /** Stance changed from */\n readonly fromStance: TrigramStance;\n /** Stance changed to */\n readonly toStance: TrigramStance;\n}\n\n/**\n * Extended player state with balance system data.\n *\n * Extends PlayerState with additional balance-specific tracking.\n */\nexport interface BalancePlayerState extends PlayerState {\n /** Stance transition state */\n readonly transitionState?: TransitionState;\n /** Stance change history (last 5 changes) */\n readonly stanceChangeHistory?: readonly StanceChangeRecord[];\n /** Rapid change penalty end timestamp */\n readonly rapidChangePenaltyEnd?: number;\n}\n\n/**\n * Balance levels representing physical stability.\n *\n * **Korean**: 균형 수준\n */\nexport enum BalanceLevel {\n /** Stable footing (80-100) */\n STABLE = \"stable\",\n /** Unsteady but controlled (50-79) */\n UNSTEADY = \"unsteady\",\n /** Off-balance, vulnerable (20-49) */\n OFF_BALANCE = \"off_balance\",\n /** Falling or knocked down (0-19) */\n FALLING = \"falling\",\n}\n\n/**\n * Effects of balance on combat performance.\n */\ninterface BalanceEffects {\n /** Balance value range */\n readonly range: readonly [number, number];\n /** Defense multiplier */\n readonly defenseMultiplier: number;\n /** Evasion success chance modifier */\n readonly evasionModifier: number;\n /** Movement speed multiplier */\n readonly movementSpeedMultiplier: number;\n /** Vulnerability to attacks multiplier */\n readonly vulnerabilityMultiplier: number;\n /** Can perform evasive actions */\n readonly canEvade: boolean;\n /** Risk of knockdown */\n readonly knockdownRisk: number;\n}\n\n/**\n * Balance System managing stability and vulnerability.\n *\n * Balance represents physical stability and center of gravity control.\n * Low balance creates vulnerability windows where opponents can capitalize\n * with increased damage and reduced defensive options.\n *\n * @example\n * ```typescript\n * const balanceSystem = new BalanceSystem();\n *\n * // Apply balance disruption from leg strike\n * const newPlayer = balanceSystem.disruptBalance(\n * player,\n * 15,\n * BodyRegion.LEFT_LEG\n * );\n *\n * // Check if vulnerable\n * const isVulnerable = balanceSystem.isVulnerable(newPlayer);\n *\n * // Apply recovery\n * const recovered = balanceSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 균형시스템\n */\nexport class BalanceSystem {\n /**\n * Balance level effects and thresholds.\n */\n private readonly balanceEffects: Record<BalanceLevel, BalanceEffects> = {\n [BalanceLevel.STABLE]: {\n range: [80, 100],\n defenseMultiplier: 1.0,\n evasionModifier: 1.0,\n movementSpeedMultiplier: 1.0,\n vulnerabilityMultiplier: 1.0,\n canEvade: true,\n knockdownRisk: 0.0,\n },\n [BalanceLevel.UNSTEADY]: {\n range: [50, 79],\n defenseMultiplier: 0.85,\n evasionModifier: 0.7,\n movementSpeedMultiplier: 0.85,\n vulnerabilityMultiplier: 1.15,\n canEvade: true,\n knockdownRisk: 0.1,\n },\n [BalanceLevel.OFF_BALANCE]: {\n range: [20, 49],\n defenseMultiplier: 0.6,\n evasionModifier: 0.4,\n movementSpeedMultiplier: 0.6,\n vulnerabilityMultiplier: 1.5, // 50% more damage taken\n canEvade: false,\n knockdownRisk: 0.3,\n },\n [BalanceLevel.FALLING]: {\n range: [0, 19],\n defenseMultiplier: 0.3,\n evasionModifier: 0.0,\n movementSpeedMultiplier: 0.3,\n vulnerabilityMultiplier: 2.0, // 100% more damage taken\n canEvade: false,\n knockdownRisk: 0.8,\n },\n };\n\n /**\n * Balance disruption multipliers by body region.\n */\n private readonly regionMultipliers: Record<string, number> = {\n [BodyRegion.LEFT_LEG]: 2.5,\n [BodyRegion.RIGHT_LEG]: 2.5,\n [BodyRegion.CORE]: 2.0,\n [BodyRegion.TORSO]: 1.5,\n [BodyRegion.HEAD]: 1.0,\n default: 0.5,\n };\n\n /**\n * Base balance recovery rate per second.\n */\n private readonly baseRecoveryRate = 8.0; // 8 points per second\n\n /**\n * Maximum balance value.\n */\n private readonly maxBalance = 100;\n\n /**\n * Stance transition constants.\n */\n private readonly transitionDuration = 500; // 0.5s in milliseconds\n private readonly transitionVulnerabilityMultiplier = 1.5; // 1.5x damage during transition\n\n /**\n * Rapid stance change penalty constants.\n */\n private readonly rapidChangeWindow = 3000; // 3 seconds in milliseconds\n private readonly rapidChangeThreshold = 2; // >2 changes trigger penalty\n private readonly rapidChangePenalty = 0.2; // 20% balance reduction\n private readonly rapidChangePenaltyDuration = 2000; // 2 seconds penalty\n private readonly stanceChangeHistoryLimit = 5; // Keep last 5 changes\n\n /**\n * Disrupts balance from combat impact.\n *\n * Calculates balance loss based on damage amount and body region hit.\n * Leg strikes cause maximum balance disruption.\n *\n * Now also considers:\n * - Body part damage modifier (damaged legs reduce balance more)\n * - Rapid stance change penalty (20% additional reduction)\n *\n * @param player - Current player state\n * @param impact - Impact force amount\n * @param region - Body region affected\n * @param currentTime - Current game time for penalty checks (optional)\n * @returns Updated player state with reduced balance\n *\n * @example\n * ```typescript\n * // Leg sweep causes major balance disruption\n * player = system.disruptBalance(\n * player,\n * 20,\n * BodyRegion.RIGHT_LEG,\n * Date.now()\n * );\n * ```\n *\n * @public\n * @korean 균형파괴\n */\n disruptBalance(\n player: BalancePlayerState,\n impact: number,\n region?: BodyRegion,\n currentTime?: number\n ): BalancePlayerState {\n // Get multiplier based on region\n const multiplier = region\n ? this.regionMultipliers[region] ?? this.regionMultipliers.default\n : this.regionMultipliers.default;\n\n // Calculate base balance loss\n let balanceLoss = impact * multiplier * 0.6; // 0.6 = balance sensitivity\n\n // Apply body part damage modifier\n const bodyModifier = this.calculateBalanceModifier(player);\n balanceLoss *= 1.0 / bodyModifier; // More damage = more balance loss\n\n // Apply rapid stance change penalty if time is provided\n if (currentTime !== undefined && this.isRapidChangePenaltyActive(player, currentTime)) {\n balanceLoss *= 1.0 + this.rapidChangePenalty; // +20% balance loss\n }\n\n // Apply balance loss, clamped to 0\n const newBalance = Math.max(0, player.balance - balanceLoss);\n\n return {\n ...player,\n balance: newBalance,\n };\n }\n\n /**\n * Applies balance recovery over time.\n *\n * Balance recovers quickly when not being disrupted, allowing\n * fighters to regain stable footing between exchanges.\n *\n * Recovery is affected by:\n * - Balance level (harder to recover when off-balance)\n * - Stamina (low stamina = slow recovery)\n * - Body part damage (leg damage reduces recovery rate)\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with recovered balance\n *\n * @example\n * ```typescript\n * // In game loop\n * player = system.applyRecovery(player, 16); // ~60fps\n * ```\n *\n * @public\n * @korean 균형회복\n */\n applyRecovery(player: BalancePlayerState, deltaTime: number): BalancePlayerState {\n // Already at maximum balance\n if (player.balance >= this.maxBalance) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n const level = this.getBalanceLevel(player.balance);\n\n // Recovery modifier based on balance level\n let recoveryModifier = 1.0;\n if (level === BalanceLevel.OFF_BALANCE) {\n recoveryModifier = 0.7; // Harder to recover when off-balance\n } else if (level === BalanceLevel.FALLING) {\n recoveryModifier = 0.4; // Very hard to recover when falling\n }\n\n // Stamina affects recovery rate\n const staminaFactor = player.stamina / player.maxStamina;\n \n // Body part damage affects recovery rate\n const bodyModifier = this.calculateBalanceModifier(player);\n \n const finalModifier = recoveryModifier * (0.5 + staminaFactor * 0.5) * bodyModifier;\n\n const recovery = this.baseRecoveryRate * deltaSeconds * finalModifier;\n const newBalance = Math.min(this.maxBalance, player.balance + recovery);\n\n return {\n ...player,\n balance: newBalance,\n };\n }\n\n /**\n * Determines balance level from balance value.\n *\n * @param balance - Balance value (0-100)\n * @returns Current balance level\n *\n * @public\n * @korean 균형수준확인\n */\n getBalanceLevel(balance: number): BalanceLevel {\n if (balance >= 80) return BalanceLevel.STABLE;\n if (balance >= 50) return BalanceLevel.UNSTEADY;\n if (balance >= 20) return BalanceLevel.OFF_BALANCE;\n return BalanceLevel.FALLING;\n }\n\n /**\n * Gets effects for a specific balance level.\n *\n * @param level - Balance level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 균형효과\n */\n getEffects(level: BalanceLevel): BalanceEffects {\n return this.balanceEffects[level];\n }\n\n /**\n * Applies balance effects to player state.\n *\n * Modifies player stats based on current balance level.\n *\n * @param player - Current player state\n * @returns Modified player state with balance effects\n *\n * @public\n * @korean 균형효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n defense: Math.floor(player.defense * effects.defenseMultiplier),\n speed: Math.floor(player.speed * effects.movementSpeedMultiplier),\n };\n }\n\n /**\n * Checks if player is vulnerable due to balance.\n *\n * @param player - Current player state\n * @returns True if off-balance or falling\n *\n * @public\n * @korean 균형취약확인\n */\n isVulnerable(player: PlayerState): boolean {\n const level = this.getBalanceLevel(player.balance);\n return level === BalanceLevel.OFF_BALANCE || level === BalanceLevel.FALLING;\n }\n\n /**\n * Calculates damage multiplier for vulnerable balance state.\n *\n * @param player - Current player state\n * @returns Damage multiplier (1.0 = normal, >1.0 = increased damage)\n *\n * @public\n * @korean 취약성배율\n */\n getVulnerabilityMultiplier(player: PlayerState): number {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n return effects.vulnerabilityMultiplier;\n }\n\n /**\n * Checks if knockdown should occur, using a provided random function for determinism.\n *\n * @param player - Current player state\n * @param randomFn - Optional random number generator (returns number in [0,1)), defaults to Math.random\n * @returns True if knockdown should occur\n *\n * @public\n * @korean 넘어짐확인\n */\n shouldKnockdown(\n player: PlayerState,\n randomFn: () => number = Math.random\n ): boolean {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n return randomFn() < effects.knockdownRisk;\n }\n\n /**\n * Gets bilingual name for balance level.\n *\n * @param level - Balance level\n * @returns Korean and English level names\n *\n * @public\n * @korean 균형이름\n */\n getLevelName(level: BalanceLevel): { korean: string; english: string } {\n const names: Record<BalanceLevel, { korean: string; english: string }> = {\n [BalanceLevel.STABLE]: {\n korean: \"안정\",\n english: \"Stable\",\n },\n [BalanceLevel.UNSTEADY]: {\n korean: \"불안정\",\n english: \"Unsteady\",\n },\n [BalanceLevel.OFF_BALANCE]: {\n korean: \"균형상실\",\n english: \"Off-Balance\",\n },\n [BalanceLevel.FALLING]: {\n korean: \"낙하중\",\n english: \"Falling\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets color indicator for balance level (for UI).\n *\n * @param level - Balance level\n * @returns Hex color code\n *\n * @public\n * @korean 균형색상\n */\n getLevelColor(level: BalanceLevel): number {\n const colors: Record<BalanceLevel, number> = {\n [BalanceLevel.STABLE]: 0x00ff00, // Green\n [BalanceLevel.UNSTEADY]: 0xffff00, // Yellow\n [BalanceLevel.OFF_BALANCE]: 0xff8800, // Orange\n [BalanceLevel.FALLING]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if balance is low enough to trigger fall animation.\n *\n * Falls occur when balance drops below 20% (FALLING state).\n * This creates realistic knockdown conditions from balance loss.\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (balanceSystem.shouldTriggerFall(player)) {\n * const fallType = balanceSystem.determineFallType(\n * player,\n * attackAngle,\n * attackHeight\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 낙법발동확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.balance < 20; // FALLING threshold\n }\n\n /**\n * Determines which fall animation to play based on attack and stance.\n *\n * Calculates fall direction using:\n * - Attack angle relative to player\n * - Attack height (high/mid/low)\n * - Player's current stance bias\n *\n * Korean falling techniques (낙법):\n * - 전방낙법 (Jeonbang Nakbeop): Forward fall\n * - 후방낙법 (Hubang Nakbeop): Backward fall\n * - 측방낙법 (Cheukbang Nakbeop): Side fall\n *\n * @param player - Current player state\n * @param attackAngle - Angle of attack in radians (0 = from front)\n * @param attackHeight - Attack height: 'high', 'mid', or 'low'\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player facing forward (0°), attacked from behind (π)\n * const fallType = balanceSystem.determineFallType(\n * player,\n * Math.PI,\n * 'mid'\n * );\n * // Returns: 'forward' (pushed forward by rear attack)\n *\n * // Low sweep from right side\n * const fallType = balanceSystem.determineFallType(\n * player,\n * Math.PI/2,\n * 'low'\n * );\n * // Returns: 'side_right' (swept to the side)\n * ```\n *\n * @public\n * @korean 낙법유형결정\n */\n determineFallType(\n _player: PlayerState,\n attackAngle: number,\n attackHeight: \"high\" | \"mid\" | \"low\" = \"mid\"\n ): FallType {\n // Get player facing angle from position or default to 0\n const playerFacing = 0; // Default facing forward\n\n // Use attack direction to determine fall\n return determineFallDirection(attackAngle, playerFacing, attackHeight);\n }\n\n /**\n * Determines fall type based on player stance when no attack direction available.\n *\n * Uses stance bias to determine likely fall direction when balance is lost\n * without a specific attack (e.g., from fatigue, leg damage accumulation).\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Heaven stance (aggressive forward)\n * const fallType = balanceSystem.determineFallTypeFromStance(\n * TrigramStance.GEON\n * );\n * // Returns: 'forward' (Heaven stance has forward bias)\n * ```\n *\n * @public\n * @korean 자세낙법결정\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance);\n }\n\n /**\n * Check if player is in a grounded state based on animation state.\n *\n * Player is considered grounded when in any ground_* animation state\n * (ground_prone, ground_supine, ground_side_left, ground_side_right).\n *\n * @param animationState - Current animation state from AnimationStateMachine\n * @returns True if player is on the ground\n *\n * @example\n * ```typescript\n * const isGrounded = balanceSystem.isGrounded(\"ground_prone\");\n * // Returns: true\n *\n * const notGrounded = balanceSystem.isGrounded(\"idle\");\n * // Returns: false\n * ```\n *\n * @public\n * @korean 지면상태확인\n */\n isGrounded(animationState: string): boolean {\n return animationState.startsWith(\"ground_\");\n }\n\n /**\n * Get ground state from animation state.\n *\n * Extracts the ground position type from animation state name.\n * Returns null if not in a ground state.\n *\n * @param animationState - Current animation state from AnimationStateMachine\n * @returns Ground state or null if not grounded\n *\n * @example\n * ```typescript\n * const state = balanceSystem.getGroundState(\"ground_prone\");\n * // Returns: \"prone\"\n *\n * const none = balanceSystem.getGroundState(\"idle\");\n * // Returns: null\n * ```\n *\n * @public\n * @korean 지면자세가져오기\n */\n getGroundState(animationState: string): GroundState | null {\n if (!this.isGrounded(animationState)) {\n return null;\n }\n\n // Extract ground state from animation state name\n // \"ground_prone\" -> \"prone\"\n const groundType = animationState.replace(\"ground_\", \"\");\n\n // Validate that it's a valid ground state\n if (\n groundType === \"prone\" ||\n groundType === \"supine\" ||\n groundType === \"side_left\" ||\n groundType === \"side_right\"\n ) {\n return groundType as GroundState;\n }\n\n return null;\n }\n\n /**\n * Check if player can execute recovery based on stamina.\n *\n * Some recovery animations (like roll recovery) require stamina.\n * This checks if player has sufficient stamina for the recovery type.\n *\n * @param player - Current player state\n * @param recoveryType - Type of recovery animation\n * @returns True if player has enough stamina\n *\n * @example\n * ```typescript\n * // Roll recovery costs 20 stamina\n * const canRoll = balanceSystem.canRecoverWithType(player, \"roll_recovery\");\n * // Returns: true if player.stamina >= 20\n *\n * // Normal recoveries have no cost\n * const canStand = balanceSystem.canRecoverWithType(player, \"prone_standup\");\n * // Returns: true (no stamina requirement)\n * ```\n *\n * @public\n * @korean 회복가능확인\n */\n canRecoverWithType(\n player: PlayerState,\n recoveryType: RecoveryAnimationType\n ): boolean {\n const config = getRecoveryConfig(recoveryType);\n\n // Check if player has enough stamina\n return player.stamina >= config.staminaCost;\n }\n\n /**\n * Apply stamina cost for recovery animation.\n *\n * Deducts stamina cost from player state for recovery animations\n * that require stamina (like roll recovery).\n *\n * @param player - Current player state\n * @param recoveryType - Type of recovery animation\n * @returns Updated player state with stamina deducted\n *\n * @example\n * ```typescript\n * // Roll recovery costs 20 stamina\n * const recovered = balanceSystem.applyRecoveryCost(player, \"roll_recovery\");\n * // recovered.stamina = player.stamina - 20\n * ```\n *\n * @public\n * @korean 회복비용적용\n */\n applyRecoveryCost(\n player: PlayerState,\n recoveryType: RecoveryAnimationType\n ): PlayerState {\n const config = getRecoveryConfig(recoveryType);\n\n if (config.staminaCost === 0) {\n return player;\n }\n\n return {\n ...player,\n stamina: Math.max(0, player.stamina - config.staminaCost),\n };\n }\n\n /**\n * Get damage multiplier during recovery animation.\n *\n * Some recovery animations (like defensive getup) provide damage reduction.\n * This returns the multiplier to apply to incoming damage.\n *\n * @param recoveryType - Type of recovery animation\n * @param currentFrame - Current frame in the animation\n * @returns Damage multiplier (1.0 = normal, 0.5 = 50% reduction)\n *\n * @example\n * ```typescript\n * // Defensive getup has 50% damage reduction\n * const multiplier = balanceSystem.getRecoveryDamageMultiplier(\"defensive_getup\", 20);\n * // Returns: 0.5 (50% damage reduction)\n *\n * // Normal recoveries have no reduction\n * const normal = balanceSystem.getRecoveryDamageMultiplier(\"prone_standup\", 15);\n * // Returns: 1.0 (full damage)\n * ```\n *\n * @public\n * @korean 회복피해배율\n */\n getRecoveryDamageMultiplier(\n recoveryType: RecoveryAnimationType,\n currentFrame: number\n ): number {\n const config = getRecoveryConfig(recoveryType);\n\n // Only apply damage reduction during vulnerable frames\n if (!isVulnerableFrame(recoveryType, currentFrame)) {\n return 1.0; // No vulnerability = full damage (or no damage if invulnerable)\n }\n\n // Apply damage reduction\n // damageReduction of 0.5 means 50% reduction, so multiplier is 0.5\n return 1.0 - config.damageReduction;\n }\n\n /**\n * Start a stance transition, creating vulnerability window.\n *\n * When a player changes stance, they become vulnerable for 0.5s with\n * a 1.5x damage multiplier. This creates tactical depth around stance changes.\n *\n * @param player - Current player state\n * @param newStance - Target stance to transition to\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player state with transition tracking\n *\n * @example\n * ```typescript\n * // Player switches from Heaven to Water stance\n * const transitioning = balanceSystem.startStanceTransition(\n * player,\n * TrigramStance.GAM,\n * Date.now()\n * );\n * // transitioning.transitionState.isTransitioning = true\n * // transitioning.transitionState.vulnerabilityMultiplier = 1.5\n * ```\n *\n * @public\n * @korean 자세전환시작\n */\n startStanceTransition(\n player: BalancePlayerState,\n newStance: TrigramStance,\n currentTime: number\n ): BalancePlayerState {\n // No-op if changing to the same stance (avoid unnecessary transitions)\n if (newStance === player.currentStance) {\n return player;\n }\n\n // Initialize transition state\n const transitionState: TransitionState = {\n isTransitioning: true,\n transitionStartTime: currentTime,\n vulnerabilityMultiplier: this.transitionVulnerabilityMultiplier,\n fromStance: player.currentStance,\n toStance: newStance,\n };\n\n // Add to stance change history\n const historyEntry: StanceChangeRecord = {\n timestamp: currentTime,\n fromStance: player.currentStance,\n toStance: newStance,\n };\n\n const history = player.stanceChangeHistory ?? [];\n const newHistory = [...history, historyEntry].slice(-this.stanceChangeHistoryLimit);\n\n // Check for rapid stance changes (inclusive boundary)\n const recentChanges = newHistory.filter(\n (entry) => currentTime - entry.timestamp <= this.rapidChangeWindow\n );\n\n let rapidChangePenaltyEnd = player.rapidChangePenaltyEnd;\n\n if (recentChanges.length > this.rapidChangeThreshold) {\n // Apply rapid change penalty\n rapidChangePenaltyEnd = currentTime + this.rapidChangePenaltyDuration;\n }\n\n return {\n ...player,\n currentStance: newStance,\n lastStanceChangeTime: currentTime,\n transitionState,\n stanceChangeHistory: newHistory,\n rapidChangePenaltyEnd,\n };\n }\n\n /**\n * Update stance transition state based on time elapsed.\n *\n * Transitions last 0.5s. After that, vulnerability window closes.\n * Called each frame to manage transition timing.\n *\n * @param player - Current player state\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player state\n *\n * @example\n * ```typescript\n * // In game loop\n * player = balanceSystem.updateTransition(player, Date.now());\n * ```\n *\n * @public\n * @korean 전환상태갱신\n */\n updateTransition(\n player: BalancePlayerState,\n currentTime: number\n ): BalancePlayerState {\n if (!player.transitionState?.isTransitioning) {\n return player;\n }\n\n const elapsed = currentTime - player.transitionState.transitionStartTime;\n\n // Check if transition window has ended\n if (elapsed >= this.transitionDuration) {\n return {\n ...player,\n transitionState: {\n ...player.transitionState,\n isTransitioning: false,\n vulnerabilityMultiplier: 1.0,\n },\n };\n }\n\n return player;\n }\n\n /**\n * Calculate balance modifier based on body part damage.\n *\n * Leg damage significantly reduces balance. The leg-specific modifier\n * scales linearly from 0.7x at 0% leg health to 1.0x at 100% leg health:\n * - 100% leg health: 1.0x balance (no reduction from legs)\n * - 70% leg health: ≈0.91x balance (≈9% reduction from legs)\n * - 30% leg health: ≈0.79x balance (≈21% reduction from legs)\n * - 0% leg health: 0.7x balance (30% reduction from legs)\n *\n * Torso damage also affects balance but to a lesser degree, and the\n * combined leg/torso modifier is clamped so the final balance modifier\n * stays within the range 0.5x to 1.0x.\n *\n * @param player - Current player state with body part health\n * @returns Balance modifier (0.5 to 1.0)\n *\n * @example\n * ```typescript\n * const modifier = balanceSystem.calculateBalanceModifier(player);\n * const effectiveBalance = player.balance * modifier;\n * ```\n *\n * @public\n * @korean 균형조정계수계산\n */\n calculateBalanceModifier(player: BalancePlayerState): number {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return 1.0; // No body part tracking, no modifier\n }\n\n // Read leg and torso health ratios from body part health tracking\n const leftLegHealthRaw =\n (player.bodyPartHealth.legLeft ?? 0) /\n (player.bodyPartMaxHealth.legLeft ?? 1);\n const rightLegHealthRaw =\n (player.bodyPartHealth.legRight ?? 0) /\n (player.bodyPartMaxHealth.legRight ?? 1);\n \n // Use torsoLower for core balance (lower body)\n const torsoHealthRaw =\n (player.bodyPartHealth.torsoLower ?? 0) /\n (player.bodyPartMaxHealth.torsoLower ?? 1);\n\n // Clamp health ratios to [0, 1] to prevent out-of-range values\n const leftLegHealth = Math.max(0, Math.min(1, leftLegHealthRaw));\n const rightLegHealth = Math.max(0, Math.min(1, rightLegHealthRaw));\n const torsoHealth = Math.max(0, Math.min(1, torsoHealthRaw));\n\n // Average leg health (both legs affect balance)\n const avgLegHealth = (leftLegHealth + rightLegHealth) / 2;\n\n // Leg damage: 0-30% reduction based on damage\n const legModifier = 0.7 + avgLegHealth * 0.3; // Range: 0.7 to 1.0\n\n // Torso damage: 0-10% reduction based on damage\n const torsoModifier = 0.9 + torsoHealth * 0.1; // Range: 0.9 to 1.0\n\n // Combine modifiers (multiplicative)\n const combinedModifier = legModifier * torsoModifier;\n\n // Clamp to [0.5, 1.0] range as documented\n return Math.max(0.5, Math.min(1.0, combinedModifier));\n }\n\n /**\n * Calculate knockback resistance based on current stance.\n *\n * Different stances provide varying levels of knockback resistance:\n * - Defensive stances (Mountain, Earth): +50% resistance\n * - Balanced stances (Water, Wind): normal resistance\n * - Offensive stances (Heaven, Fire, Thunder): -30% resistance\n * - Fluid stance (Lake): normal resistance\n *\n * @param stance - Current trigram stance\n * @returns Knockback resistance multiplier (0.7 to 1.5)\n *\n * @example\n * ```typescript\n * const resistance = balanceSystem.getKnockbackResistance(TrigramStance.GAN);\n * const effectiveKnockback = baseKnockback * (1.0 / resistance);\n * ```\n *\n * @public\n * @korean 넉백저항계산\n */\n getKnockbackResistance(stance: TrigramStance): number {\n // Defensive stances: +50% resistance\n if (stance === TrigramStance.GAN || stance === TrigramStance.GON) {\n // Mountain, Earth\n return 1.5;\n }\n\n // Offensive stances: -30% resistance\n if (stance === TrigramStance.GEON || stance === TrigramStance.LI || stance === TrigramStance.JIN) {\n // Heaven, Fire, Thunder\n return 0.7;\n }\n\n // Balanced/adaptive stances: normal resistance\n return 1.0; // Water, Wind, Lake\n }\n\n /**\n * Check if rapid stance change penalty is active.\n *\n * Penalty applies when player changes stances >2 times in 3 seconds.\n * Lasts for 2 seconds after the last rapid change.\n * The penalty increases balance loss by 20% in `disruptBalance()`.\n *\n * @param player - Current player state\n * @param currentTime - Current game time in milliseconds\n * @returns True if penalty is active\n *\n * @example\n * ```typescript\n * // The penalty is applied automatically in disruptBalance()\n * if (balanceSystem.isRapidChangePenaltyActive(player, Date.now())) {\n * // Penalty active: balance loss will be increased by 20%\n * cy.log(\"Rapid change penalty active\");\n * }\n * ```\n *\n * @public\n * @korean 급속변경벌칙확인\n */\n isRapidChangePenaltyActive(\n player: BalancePlayerState,\n currentTime: number\n ): boolean {\n if (!player.rapidChangePenaltyEnd) {\n return false;\n }\n return currentTime < player.rapidChangePenaltyEnd;\n }\n\n /**\n * Get total vulnerability multiplier considering all factors.\n *\n * Combines:\n * - Base balance state vulnerability (1.0 to 2.0)\n * - Stance transition vulnerability (1.5x during 0.5s window)\n *\n * @param player - Current player state\n * @returns Combined vulnerability multiplier\n *\n * @example\n * ```typescript\n * const multiplier = balanceSystem.getTotalVulnerabilityMultiplier(player);\n * const finalDamage = baseDamage * multiplier;\n * ```\n *\n * @public\n * @korean 총취약성배율\n */\n getTotalVulnerabilityMultiplier(player: BalancePlayerState): number {\n // Get base balance vulnerability\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n let multiplier = effects.vulnerabilityMultiplier;\n\n // Apply transition vulnerability if active\n if (player.transitionState?.isTransitioning) {\n multiplier *= player.transitionState.vulnerabilityMultiplier;\n }\n\n return multiplier;\n }\n}\n\nexport default BalanceSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BalanceSystem.js","names":[],"sources":["../../../src/systems/combat/BalanceSystem.ts"],"sourcesContent":["/**\n * Balance System for managing stability and vulnerability windows.\n *\n * **Korean**: 균형 시스템 (Balance System)\n *\n * Implements physical balance and stability mechanics. Loss of balance creates\n * vulnerability windows where damage is increased and defensive options limited.\n * Balance is affected by:\n * - Leg strikes\n * - Throws and sweeps\n * - Heavy impacts\n * - Fatigue (low stamina)\n * - Stance transitions (0.5s vulnerability window)\n * - Body part damage (legs reduce balance)\n * - Rapid stance changes (penalty system)\n *\n * ## Balance States\n *\n * - **Stable**: 80-100 balance - Full mobility and defense\n * - **Unsteady**: 50-79 balance - Reduced evasion, slower movement\n * - **Off-Balance**: 20-49 balance - Vulnerability window, defense penalty\n * - **Falling**: 0-19 balance - Severe vulnerability, possible knockdown\n *\n * ## Fall System Integration\n *\n * When balance falls below 20%, the system can trigger fall animations (낙법).\n * Fall direction is determined by attack vector and player stance.\n *\n * ## Stance Transition Vulnerability\n *\n * Changing stances creates a 0.5s vulnerability window with 1.5x damage multiplier.\n * Rapid stance changes (>2 in 3s) apply additional 20% balance penalty for 2s.\n *\n * @module systems/combat/BalanceSystem\n * @category Combat System\n * @korean 균형시스템\n */\n\nimport { BodyRegion } from \"@/types\";\nimport { TrigramStance } from \"@/types/common\";\nimport {\n determineFallDirection,\n determineFallFromStance,\n getRecoveryConfig,\n isVulnerableFrame,\n} from \"../animation\";\nimport type {\n FallType,\n GroundState,\n RecoveryAnimationType,\n} from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Stance transition state tracking.\n *\n * **Korean**: 자세 전환 상태 (Stance Transition State)\n *\n * Tracks when a player is transitioning between stances, creating a\n * 0.5s vulnerability window with increased damage multiplier.\n */\nexport interface TransitionState {\n /** Whether player is currently transitioning */\n readonly isTransitioning: boolean;\n /** Timestamp when transition started (ms) */\n readonly transitionStartTime: number;\n /** Damage multiplier during transition (1.5x) */\n readonly vulnerabilityMultiplier: number;\n /** Stance transitioning from */\n readonly fromStance: TrigramStance | null;\n /** Stance transitioning to */\n readonly toStance: TrigramStance | null;\n}\n\n/**\n * Stance change history entry.\n *\n * **Korean**: 자세 변경 기록 (Stance Change Record)\n */\ninterface StanceChangeRecord {\n /** Timestamp of stance change */\n readonly timestamp: number;\n /** Stance changed from */\n readonly fromStance: TrigramStance;\n /** Stance changed to */\n readonly toStance: TrigramStance;\n}\n\n/**\n * Extended player state with balance system data.\n *\n * Extends PlayerState with additional balance-specific tracking.\n */\nexport interface BalancePlayerState extends PlayerState {\n /** Stance transition state */\n readonly transitionState?: TransitionState;\n /** Stance change history (last 5 changes) */\n readonly stanceChangeHistory?: readonly StanceChangeRecord[];\n /** Rapid change penalty end timestamp */\n readonly rapidChangePenaltyEnd?: number;\n}\n\n/**\n * Balance levels representing physical stability.\n *\n * **Korean**: 균형 수준\n */\nexport enum BalanceLevel {\n /** Stable footing (80-100) */\n STABLE = \"stable\",\n /** Unsteady but controlled (50-79) */\n UNSTEADY = \"unsteady\",\n /** Off-balance, vulnerable (20-49) */\n OFF_BALANCE = \"off_balance\",\n /** Falling or knocked down (0-19) */\n FALLING = \"falling\",\n}\n\n/**\n * Effects of balance on combat performance.\n */\ninterface BalanceEffects {\n /** Balance value range */\n readonly range: readonly [number, number];\n /** Defense multiplier */\n readonly defenseMultiplier: number;\n /** Evasion success chance modifier */\n readonly evasionModifier: number;\n /** Movement speed multiplier */\n readonly movementSpeedMultiplier: number;\n /** Vulnerability to attacks multiplier */\n readonly vulnerabilityMultiplier: number;\n /** Can perform evasive actions */\n readonly canEvade: boolean;\n /** Risk of knockdown */\n readonly knockdownRisk: number;\n}\n\n/**\n * Balance System managing stability and vulnerability.\n *\n * Balance represents physical stability and center of gravity control.\n * Low balance creates vulnerability windows where opponents can capitalize\n * with increased damage and reduced defensive options.\n *\n * @example\n * ```typescript\n * const balanceSystem = new BalanceSystem();\n *\n * // Apply balance disruption from leg strike\n * const newPlayer = balanceSystem.disruptBalance(\n * player,\n * 15,\n * BodyRegion.LEFT_LEG\n * );\n *\n * // Check if vulnerable\n * const isVulnerable = balanceSystem.isVulnerable(newPlayer);\n *\n * // Apply recovery\n * const recovered = balanceSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 균형시스템\n */\nexport class BalanceSystem {\n /**\n * Balance level effects and thresholds.\n */\n private readonly balanceEffects: Record<BalanceLevel, BalanceEffects> = {\n [BalanceLevel.STABLE]: {\n range: [80, 100],\n defenseMultiplier: 1.0,\n evasionModifier: 1.0,\n movementSpeedMultiplier: 1.0,\n vulnerabilityMultiplier: 1.0,\n canEvade: true,\n knockdownRisk: 0.0,\n },\n [BalanceLevel.UNSTEADY]: {\n range: [50, 79],\n defenseMultiplier: 0.85,\n evasionModifier: 0.7,\n movementSpeedMultiplier: 0.85,\n vulnerabilityMultiplier: 1.15,\n canEvade: true,\n knockdownRisk: 0.1,\n },\n [BalanceLevel.OFF_BALANCE]: {\n range: [20, 49],\n defenseMultiplier: 0.6,\n evasionModifier: 0.4,\n movementSpeedMultiplier: 0.6,\n vulnerabilityMultiplier: 1.5, // 50% more damage taken\n canEvade: false,\n knockdownRisk: 0.3,\n },\n [BalanceLevel.FALLING]: {\n range: [0, 19],\n defenseMultiplier: 0.3,\n evasionModifier: 0.0,\n movementSpeedMultiplier: 0.3,\n vulnerabilityMultiplier: 2.0, // 100% more damage taken\n canEvade: false,\n knockdownRisk: 0.8,\n },\n };\n\n /**\n * Balance disruption multipliers by body region.\n */\n private readonly regionMultipliers: Record<string, number> = {\n [BodyRegion.LEFT_LEG]: 2.5,\n [BodyRegion.RIGHT_LEG]: 2.5,\n [BodyRegion.CORE]: 2.0,\n [BodyRegion.TORSO]: 1.5,\n [BodyRegion.HEAD]: 1.0,\n default: 0.5,\n };\n\n /**\n * Base balance recovery rate per second.\n */\n private readonly baseRecoveryRate = 8.0; // 8 points per second\n\n /**\n * Maximum balance value.\n */\n private readonly maxBalance = 100;\n\n /**\n * Stance transition constants.\n */\n private readonly transitionDuration = 500; // 0.5s in milliseconds\n private readonly transitionVulnerabilityMultiplier = 1.5; // 1.5x damage during transition\n\n /**\n * Rapid stance change penalty constants.\n */\n private readonly rapidChangeWindow = 3000; // 3 seconds in milliseconds\n private readonly rapidChangeThreshold = 2; // >2 changes trigger penalty\n private readonly rapidChangePenalty = 0.2; // 20% balance reduction\n private readonly rapidChangePenaltyDuration = 2000; // 2 seconds penalty\n private readonly stanceChangeHistoryLimit = 5; // Keep last 5 changes\n\n /**\n * Disrupts balance from combat impact.\n *\n * Calculates balance loss based on damage amount and body region hit.\n * Leg strikes cause maximum balance disruption.\n *\n * Now also considers:\n * - Body part damage modifier (damaged legs reduce balance more)\n * - Rapid stance change penalty (20% additional reduction)\n *\n * @param player - Current player state\n * @param impact - Impact force amount\n * @param region - Body region affected\n * @param currentTime - Current game time for penalty checks (optional)\n * @returns Updated player state with reduced balance\n *\n * @example\n * ```typescript\n * // Leg sweep causes major balance disruption\n * player = system.disruptBalance(\n * player,\n * 20,\n * BodyRegion.RIGHT_LEG,\n * Date.now()\n * );\n * ```\n *\n * @public\n * @korean 균형파괴\n */\n disruptBalance(\n player: BalancePlayerState,\n impact: number,\n region?: BodyRegion,\n currentTime?: number\n ): BalancePlayerState {\n // Get multiplier based on region\n const multiplier = region\n ? this.regionMultipliers[region] ?? this.regionMultipliers.default\n : this.regionMultipliers.default;\n\n // Calculate base balance loss\n let balanceLoss = impact * multiplier * 0.6; // 0.6 = balance sensitivity\n\n // Apply body part damage modifier\n const bodyModifier = this.calculateBalanceModifier(player);\n balanceLoss *= 1.0 / bodyModifier; // More damage = more balance loss\n\n // Apply rapid stance change penalty if time is provided\n if (currentTime !== undefined && this.isRapidChangePenaltyActive(player, currentTime)) {\n balanceLoss *= 1.0 + this.rapidChangePenalty; // +20% balance loss\n }\n\n // Apply balance loss, clamped to 0\n const newBalance = Math.max(0, player.balance - balanceLoss);\n\n return {\n ...player,\n balance: newBalance,\n };\n }\n\n /**\n * Applies balance recovery over time.\n *\n * Balance recovers quickly when not being disrupted, allowing\n * fighters to regain stable footing between exchanges.\n *\n * Recovery is affected by:\n * - Balance level (harder to recover when off-balance)\n * - Stamina (low stamina = slow recovery)\n * - Body part damage (leg damage reduces recovery rate)\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with recovered balance\n *\n * @example\n * ```typescript\n * // In game loop\n * player = system.applyRecovery(player, 16); // ~60fps\n * ```\n *\n * @public\n * @korean 균형회복\n */\n applyRecovery(player: BalancePlayerState, deltaTime: number): BalancePlayerState {\n // Already at maximum balance\n if (player.balance >= this.maxBalance) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n const level = this.getBalanceLevel(player.balance);\n\n // Recovery modifier based on balance level\n let recoveryModifier = 1.0;\n if (level === BalanceLevel.OFF_BALANCE) {\n recoveryModifier = 0.7; // Harder to recover when off-balance\n } else if (level === BalanceLevel.FALLING) {\n recoveryModifier = 0.4; // Very hard to recover when falling\n }\n\n // Stamina affects recovery rate\n const staminaFactor = player.stamina / player.maxStamina;\n \n // Body part damage affects recovery rate\n const bodyModifier = this.calculateBalanceModifier(player);\n \n const finalModifier = recoveryModifier * (0.5 + staminaFactor * 0.5) * bodyModifier;\n\n const recovery = this.baseRecoveryRate * deltaSeconds * finalModifier;\n const newBalance = Math.min(this.maxBalance, player.balance + recovery);\n\n return {\n ...player,\n balance: newBalance,\n };\n }\n\n /**\n * Determines balance level from balance value.\n *\n * @param balance - Balance value (0-100)\n * @returns Current balance level\n *\n * @public\n * @korean 균형수준확인\n */\n getBalanceLevel(balance: number): BalanceLevel {\n if (balance >= 80) return BalanceLevel.STABLE;\n if (balance >= 50) return BalanceLevel.UNSTEADY;\n if (balance >= 20) return BalanceLevel.OFF_BALANCE;\n return BalanceLevel.FALLING;\n }\n\n /**\n * Gets effects for a specific balance level.\n *\n * @param level - Balance level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 균형효과\n */\n getEffects(level: BalanceLevel): BalanceEffects {\n return this.balanceEffects[level];\n }\n\n /**\n * Applies balance effects to player state.\n *\n * Modifies player stats based on current balance level.\n *\n * @param player - Current player state\n * @returns Modified player state with balance effects\n *\n * @public\n * @korean 균형효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n defense: Math.floor(player.defense * effects.defenseMultiplier),\n speed: Math.floor(player.speed * effects.movementSpeedMultiplier),\n };\n }\n\n /**\n * Checks if player is vulnerable due to balance.\n *\n * @param player - Current player state\n * @returns True if off-balance or falling\n *\n * @public\n * @korean 균형취약확인\n */\n isVulnerable(player: PlayerState): boolean {\n const level = this.getBalanceLevel(player.balance);\n return level === BalanceLevel.OFF_BALANCE || level === BalanceLevel.FALLING;\n }\n\n /**\n * Calculates damage multiplier for vulnerable balance state.\n *\n * @param player - Current player state\n * @returns Damage multiplier (1.0 = normal, >1.0 = increased damage)\n *\n * @public\n * @korean 취약성배율\n */\n getVulnerabilityMultiplier(player: PlayerState): number {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n return effects.vulnerabilityMultiplier;\n }\n\n /**\n * Checks if knockdown should occur, using a provided random function for determinism.\n *\n * @param player - Current player state\n * @param randomFn - Optional random number generator (returns number in [0,1)), defaults to Math.random\n * @returns True if knockdown should occur\n *\n * @public\n * @korean 넘어짐확인\n */\n shouldKnockdown(\n player: PlayerState,\n randomFn: () => number = Math.random\n ): boolean {\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n return randomFn() < effects.knockdownRisk;\n }\n\n /**\n * Gets bilingual name for balance level.\n *\n * @param level - Balance level\n * @returns Korean and English level names\n *\n * @public\n * @korean 균형이름\n */\n getLevelName(level: BalanceLevel): { korean: string; english: string } {\n const names: Record<BalanceLevel, { korean: string; english: string }> = {\n [BalanceLevel.STABLE]: {\n korean: \"안정\",\n english: \"Stable\",\n },\n [BalanceLevel.UNSTEADY]: {\n korean: \"불안정\",\n english: \"Unsteady\",\n },\n [BalanceLevel.OFF_BALANCE]: {\n korean: \"균형상실\",\n english: \"Off-Balance\",\n },\n [BalanceLevel.FALLING]: {\n korean: \"낙하중\",\n english: \"Falling\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets color indicator for balance level (for UI).\n *\n * @param level - Balance level\n * @returns Hex color code\n *\n * @public\n * @korean 균형색상\n */\n getLevelColor(level: BalanceLevel): number {\n const colors: Record<BalanceLevel, number> = {\n [BalanceLevel.STABLE]: 0x00ff00, // Green\n [BalanceLevel.UNSTEADY]: 0xffff00, // Yellow\n [BalanceLevel.OFF_BALANCE]: 0xff8800, // Orange\n [BalanceLevel.FALLING]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if balance is low enough to trigger fall animation.\n *\n * Falls occur when balance drops below 20% (FALLING state).\n * This creates realistic knockdown conditions from balance loss.\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (balanceSystem.shouldTriggerFall(player)) {\n * const fallType = balanceSystem.determineFallType(\n * player,\n * attackAngle,\n * attackHeight\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 낙법발동확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.balance < 20; // FALLING threshold\n }\n\n /**\n * Determines which fall animation to play based on attack and stance.\n *\n * Calculates fall direction using:\n * - Attack angle relative to player\n * - Attack height (high/mid/low)\n * - Player's current stance bias\n *\n * Korean falling techniques (낙법):\n * - 전방낙법 (Jeonbang Nakbeop): Forward fall\n * - 후방낙법 (Hubang Nakbeop): Backward fall\n * - 측방낙법 (Cheukbang Nakbeop): Side fall\n *\n * @param player - Current player state\n * @param attackAngle - Angle of attack in radians (0 = from front)\n * @param attackHeight - Attack height: 'high', 'mid', or 'low'\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player facing forward (0°), attacked from behind (π)\n * const fallType = balanceSystem.determineFallType(\n * player,\n * Math.PI,\n * 'mid'\n * );\n * // Returns: 'forward' (pushed forward by rear attack)\n *\n * // Low sweep from right side\n * const fallType = balanceSystem.determineFallType(\n * player,\n * Math.PI/2,\n * 'low'\n * );\n * // Returns: 'side_right' (swept to the side)\n * ```\n *\n * @public\n * @korean 낙법유형결정\n */\n determineFallType(\n _player: PlayerState,\n attackAngle: number,\n attackHeight: \"high\" | \"mid\" | \"low\" = \"mid\"\n ): FallType {\n // Get player facing angle from position or default to 0\n const playerFacing = 0; // Default facing forward\n\n // Use attack direction to determine fall\n return determineFallDirection(attackAngle, playerFacing, attackHeight);\n }\n\n /**\n * Determines fall type based on player stance when no attack direction available.\n *\n * Uses stance bias to determine likely fall direction when balance is lost\n * without a specific attack (e.g., from fatigue, leg damage accumulation).\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Heaven stance (aggressive forward)\n * const fallType = balanceSystem.determineFallTypeFromStance(\n * TrigramStance.GEON\n * );\n * // Returns: 'forward' (Heaven stance has forward bias)\n * ```\n *\n * @public\n * @korean 자세낙법결정\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance);\n }\n\n /**\n * Check if player is in a grounded state based on animation state.\n *\n * Player is considered grounded when in any ground_* animation state\n * (ground_prone, ground_supine, ground_side_left, ground_side_right).\n *\n * @param animationState - Current animation state from AnimationStateMachine\n * @returns True if player is on the ground\n *\n * @example\n * ```typescript\n * const isGrounded = balanceSystem.isGrounded(\"ground_prone\");\n * // Returns: true\n *\n * const notGrounded = balanceSystem.isGrounded(\"idle\");\n * // Returns: false\n * ```\n *\n * @public\n * @korean 지면상태확인\n */\n isGrounded(animationState: string): boolean {\n return animationState.startsWith(\"ground_\");\n }\n\n /**\n * Get ground state from animation state.\n *\n * Extracts the ground position type from animation state name.\n * Returns null if not in a ground state.\n *\n * @param animationState - Current animation state from AnimationStateMachine\n * @returns Ground state or null if not grounded\n *\n * @example\n * ```typescript\n * const state = balanceSystem.getGroundState(\"ground_prone\");\n * // Returns: \"prone\"\n *\n * const none = balanceSystem.getGroundState(\"idle\");\n * // Returns: null\n * ```\n *\n * @public\n * @korean 지면자세가져오기\n */\n getGroundState(animationState: string): GroundState | null {\n if (!this.isGrounded(animationState)) {\n return null;\n }\n\n // Extract ground state from animation state name\n // \"ground_prone\" -> \"prone\"\n const groundType = animationState.replace(\"ground_\", \"\");\n\n // Validate that it's a valid ground state\n if (\n groundType === \"prone\" ||\n groundType === \"supine\" ||\n groundType === \"side_left\" ||\n groundType === \"side_right\"\n ) {\n return groundType as GroundState;\n }\n\n return null;\n }\n\n /**\n * Check if player can execute recovery based on stamina.\n *\n * Some recovery animations (like roll recovery) require stamina.\n * This checks if player has sufficient stamina for the recovery type.\n *\n * @param player - Current player state\n * @param recoveryType - Type of recovery animation\n * @returns True if player has enough stamina\n *\n * @example\n * ```typescript\n * // Roll recovery costs 20 stamina\n * const canRoll = balanceSystem.canRecoverWithType(player, \"roll_recovery\");\n * // Returns: true if player.stamina >= 20\n *\n * // Normal recoveries have no cost\n * const canStand = balanceSystem.canRecoverWithType(player, \"prone_standup\");\n * // Returns: true (no stamina requirement)\n * ```\n *\n * @public\n * @korean 회복가능확인\n */\n canRecoverWithType(\n player: PlayerState,\n recoveryType: RecoveryAnimationType\n ): boolean {\n const config = getRecoveryConfig(recoveryType);\n\n // Check if player has enough stamina\n return player.stamina >= config.staminaCost;\n }\n\n /**\n * Apply stamina cost for recovery animation.\n *\n * Deducts stamina cost from player state for recovery animations\n * that require stamina (like roll recovery).\n *\n * @param player - Current player state\n * @param recoveryType - Type of recovery animation\n * @returns Updated player state with stamina deducted\n *\n * @example\n * ```typescript\n * // Roll recovery costs 20 stamina\n * const recovered = balanceSystem.applyRecoveryCost(player, \"roll_recovery\");\n * // recovered.stamina = player.stamina - 20\n * ```\n *\n * @public\n * @korean 회복비용적용\n */\n applyRecoveryCost(\n player: PlayerState,\n recoveryType: RecoveryAnimationType\n ): PlayerState {\n const config = getRecoveryConfig(recoveryType);\n\n if (config.staminaCost === 0) {\n return player;\n }\n\n return {\n ...player,\n stamina: Math.max(0, player.stamina - config.staminaCost),\n };\n }\n\n /**\n * Get damage multiplier during recovery animation.\n *\n * Some recovery animations (like defensive getup) provide damage reduction.\n * This returns the multiplier to apply to incoming damage.\n *\n * @param recoveryType - Type of recovery animation\n * @param currentFrame - Current frame in the animation\n * @returns Damage multiplier (1.0 = normal, 0.5 = 50% reduction)\n *\n * @example\n * ```typescript\n * // Defensive getup has 50% damage reduction\n * const multiplier = balanceSystem.getRecoveryDamageMultiplier(\"defensive_getup\", 20);\n * // Returns: 0.5 (50% damage reduction)\n *\n * // Normal recoveries have no reduction\n * const normal = balanceSystem.getRecoveryDamageMultiplier(\"prone_standup\", 15);\n * // Returns: 1.0 (full damage)\n * ```\n *\n * @public\n * @korean 회복피해배율\n */\n getRecoveryDamageMultiplier(\n recoveryType: RecoveryAnimationType,\n currentFrame: number\n ): number {\n const config = getRecoveryConfig(recoveryType);\n\n // Only apply damage reduction during vulnerable frames\n if (!isVulnerableFrame(recoveryType, currentFrame)) {\n return 1.0; // No vulnerability = full damage (or no damage if invulnerable)\n }\n\n // Apply damage reduction\n // damageReduction of 0.5 means 50% reduction, so multiplier is 0.5\n return 1.0 - config.damageReduction;\n }\n\n /**\n * Start a stance transition, creating vulnerability window.\n *\n * When a player changes stance, they become vulnerable for 0.5s with\n * a 1.5x damage multiplier. This creates tactical depth around stance changes.\n *\n * @param player - Current player state\n * @param newStance - Target stance to transition to\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player state with transition tracking\n *\n * @example\n * ```typescript\n * // Player switches from Heaven to Water stance\n * const transitioning = balanceSystem.startStanceTransition(\n * player,\n * TrigramStance.GAM,\n * Date.now()\n * );\n * // transitioning.transitionState.isTransitioning = true\n * // transitioning.transitionState.vulnerabilityMultiplier = 1.5\n * ```\n *\n * @public\n * @korean 자세전환시작\n */\n startStanceTransition(\n player: BalancePlayerState,\n newStance: TrigramStance,\n currentTime: number\n ): BalancePlayerState {\n // No-op if changing to the same stance (avoid unnecessary transitions)\n if (newStance === player.currentStance) {\n return player;\n }\n\n // Initialize transition state\n const transitionState: TransitionState = {\n isTransitioning: true,\n transitionStartTime: currentTime,\n vulnerabilityMultiplier: this.transitionVulnerabilityMultiplier,\n fromStance: player.currentStance,\n toStance: newStance,\n };\n\n // Add to stance change history\n const historyEntry: StanceChangeRecord = {\n timestamp: currentTime,\n fromStance: player.currentStance,\n toStance: newStance,\n };\n\n const history = player.stanceChangeHistory ?? [];\n const newHistory = [...history, historyEntry].slice(-this.stanceChangeHistoryLimit);\n\n // Check for rapid stance changes (inclusive boundary)\n const recentChanges = newHistory.filter(\n (entry) => currentTime - entry.timestamp <= this.rapidChangeWindow\n );\n\n let rapidChangePenaltyEnd = player.rapidChangePenaltyEnd;\n\n if (recentChanges.length > this.rapidChangeThreshold) {\n // Apply rapid change penalty\n rapidChangePenaltyEnd = currentTime + this.rapidChangePenaltyDuration;\n }\n\n return {\n ...player,\n currentStance: newStance,\n lastStanceChangeTime: currentTime,\n transitionState,\n stanceChangeHistory: newHistory,\n rapidChangePenaltyEnd,\n };\n }\n\n /**\n * Update stance transition state based on time elapsed.\n *\n * Transitions last 0.5s. After that, vulnerability window closes.\n * Called each frame to manage transition timing.\n *\n * @param player - Current player state\n * @param currentTime - Current game time in milliseconds\n * @returns Updated player state\n *\n * @example\n * ```typescript\n * // In game loop\n * player = balanceSystem.updateTransition(player, Date.now());\n * ```\n *\n * @public\n * @korean 전환상태갱신\n */\n updateTransition(\n player: BalancePlayerState,\n currentTime: number\n ): BalancePlayerState {\n if (!player.transitionState?.isTransitioning) {\n return player;\n }\n\n const elapsed = currentTime - player.transitionState.transitionStartTime;\n\n // Check if transition window has ended\n if (elapsed >= this.transitionDuration) {\n return {\n ...player,\n transitionState: {\n ...player.transitionState,\n isTransitioning: false,\n vulnerabilityMultiplier: 1.0,\n },\n };\n }\n\n return player;\n }\n\n /**\n * Calculate balance modifier based on body part damage.\n *\n * Leg damage significantly reduces balance. The leg-specific modifier\n * scales linearly from 0.7x at 0% leg health to 1.0x at 100% leg health:\n * - 100% leg health: 1.0x balance (no reduction from legs)\n * - 70% leg health: ≈0.91x balance (≈9% reduction from legs)\n * - 30% leg health: ≈0.79x balance (≈21% reduction from legs)\n * - 0% leg health: 0.7x balance (30% reduction from legs)\n *\n * Torso damage also affects balance but to a lesser degree, and the\n * combined leg/torso modifier is clamped so the final balance modifier\n * stays within the range 0.5x to 1.0x.\n *\n * @param player - Current player state with body part health\n * @returns Balance modifier (0.5 to 1.0)\n *\n * @example\n * ```typescript\n * const modifier = balanceSystem.calculateBalanceModifier(player);\n * const effectiveBalance = player.balance * modifier;\n * ```\n *\n * @public\n * @korean 균형조정계수계산\n */\n calculateBalanceModifier(player: BalancePlayerState): number {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return 1.0; // No body part tracking, no modifier\n }\n\n // Read leg and torso health ratios from body part health tracking\n const leftLegHealthRaw =\n (player.bodyPartHealth.legLeft ?? 0) /\n (player.bodyPartMaxHealth.legLeft ?? 1);\n const rightLegHealthRaw =\n (player.bodyPartHealth.legRight ?? 0) /\n (player.bodyPartMaxHealth.legRight ?? 1);\n \n // Use torsoLower for core balance (lower body)\n const torsoHealthRaw =\n (player.bodyPartHealth.torsoLower ?? 0) /\n (player.bodyPartMaxHealth.torsoLower ?? 1);\n\n // Clamp health ratios to [0, 1] to prevent out-of-range values\n const leftLegHealth = Math.max(0, Math.min(1, leftLegHealthRaw));\n const rightLegHealth = Math.max(0, Math.min(1, rightLegHealthRaw));\n const torsoHealth = Math.max(0, Math.min(1, torsoHealthRaw));\n\n // Average leg health (both legs affect balance)\n const avgLegHealth = (leftLegHealth + rightLegHealth) / 2;\n\n // Leg damage: 0-30% reduction based on damage\n const legModifier = 0.7 + avgLegHealth * 0.3; // Range: 0.7 to 1.0\n\n // Torso damage: 0-10% reduction based on damage\n const torsoModifier = 0.9 + torsoHealth * 0.1; // Range: 0.9 to 1.0\n\n // Combine modifiers (multiplicative)\n const combinedModifier = legModifier * torsoModifier;\n\n // Clamp to [0.5, 1.0] range as documented\n return Math.max(0.5, Math.min(1.0, combinedModifier));\n }\n\n /**\n * Calculate knockback resistance based on current stance.\n *\n * Different stances provide varying levels of knockback resistance:\n * - Defensive stances (Mountain, Earth): +50% resistance\n * - Balanced stances (Water, Wind): normal resistance\n * - Offensive stances (Heaven, Fire, Thunder): -30% resistance\n * - Fluid stance (Lake): normal resistance\n *\n * @param stance - Current trigram stance\n * @returns Knockback resistance multiplier (0.7 to 1.5)\n *\n * @example\n * ```typescript\n * const resistance = balanceSystem.getKnockbackResistance(TrigramStance.GAN);\n * const effectiveKnockback = baseKnockback * (1.0 / resistance);\n * ```\n *\n * @public\n * @korean 넉백저항계산\n */\n getKnockbackResistance(stance: TrigramStance): number {\n // Defensive stances: +50% resistance\n if (stance === TrigramStance.GAN || stance === TrigramStance.GON) {\n // Mountain, Earth\n return 1.5;\n }\n\n // Offensive stances: -30% resistance\n if (stance === TrigramStance.GEON || stance === TrigramStance.LI || stance === TrigramStance.JIN) {\n // Heaven, Fire, Thunder\n return 0.7;\n }\n\n // Balanced/adaptive stances: normal resistance\n return 1.0; // Water, Wind, Lake\n }\n\n /**\n * Check if rapid stance change penalty is active.\n *\n * Penalty applies when player changes stances >2 times in 3 seconds.\n * Lasts for 2 seconds after the last rapid change.\n * The penalty increases balance loss by 20% in `disruptBalance()`.\n *\n * @param player - Current player state\n * @param currentTime - Current game time in milliseconds\n * @returns True if penalty is active\n *\n * @example\n * ```typescript\n * // The penalty is applied automatically in disruptBalance()\n * if (balanceSystem.isRapidChangePenaltyActive(player, Date.now())) {\n * // Penalty active: balance loss will be increased by 20%\n * cy.log(\"Rapid change penalty active\");\n * }\n * ```\n *\n * @public\n * @korean 급속변경벌칙확인\n */\n isRapidChangePenaltyActive(\n player: BalancePlayerState,\n currentTime: number\n ): boolean {\n if (!player.rapidChangePenaltyEnd) {\n return false;\n }\n return currentTime < player.rapidChangePenaltyEnd;\n }\n\n /**\n * Get total vulnerability multiplier considering all factors.\n *\n * Combines:\n * - Base balance state vulnerability (1.0 to 2.0)\n * - Stance transition vulnerability (1.5x during 0.5s window)\n *\n * @param player - Current player state\n * @returns Combined vulnerability multiplier\n *\n * @example\n * ```typescript\n * const multiplier = balanceSystem.getTotalVulnerabilityMultiplier(player);\n * const finalDamage = baseDamage * multiplier;\n * ```\n *\n * @public\n * @korean 총취약성배율\n */\n getTotalVulnerabilityMultiplier(player: BalancePlayerState): number {\n // Get base balance vulnerability\n const level = this.getBalanceLevel(player.balance);\n const effects = this.getEffects(level);\n let multiplier = effects.vulnerabilityMultiplier;\n\n // Apply transition vulnerability if active\n if (player.transitionState?.isTransitioning) {\n multiplier *= player.transitionState.vulnerabilityMultiplier;\n }\n\n return multiplier;\n }\n}\n\nexport default BalanceSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CombatStateSystem.js","names":[],"sources":["../../../src/systems/combat/CombatStateSystem.ts"],"sourcesContent":["/**\n * Combat State System for managing Ready/Shaken/Vulnerable/Helpless states.\n * \n * **Korean**: 전투 상태 시스템 (Combat State System)\n * \n * Implements the combat readiness state machine based on cumulative damage,\n * pain, consciousness, and balance. Each state affects player capabilities\n * with specific multipliers.\n * \n * ## Combat States\n * \n * - **🟢 READY**: 100% capability - perfect combat condition\n * - **🟡 SHAKEN**: 80% capability - slightly compromised, -20% accuracy\n * - **🟠 VULNERABLE**: 60% capability - significantly exposed, -40% defense\n * - **🔴 HELPLESS**: 20% capability - near incapacitation, cannot block\n * \n * @module systems/combat/CombatStateSystem\n * @category Combat System\n * @korean 전투상태시스템\n */\n\nimport { PlayerState } from \"../player\";\n\n/**\n * Combat readiness states representing player combat effectiveness.\n * \n * **Korean**: 전투 준비 상태\n */\nexport enum CombatReadinessState {\n /** 🟢 Ready - Full combat capability (100%) */\n READY = \"ready\",\n /** 🟡 Shaken - Reduced effectiveness (80%) */\n SHAKEN = \"shaken\",\n /** 🟠 Vulnerable - Significantly impaired (60%) */\n VULNERABLE = \"vulnerable\",\n /** 🔴 Helpless - Near incapacitation (20%) */\n HELPLESS = \"helpless\",\n}\n\n/**\n * State capability modifiers for each combat state.\n * \n * **Korean**: 상태 능력 배율\n */\ninterface StateCapability {\n /** Overall capability percentage */\n readonly capability: number;\n /** Accuracy modifier (affects hit chance and damage output) */\n readonly accuracyModifier: number;\n /** Defense modifier (affects damage reduction) */\n readonly defenseModifier: number;\n /** Damage modifier (affects outgoing damage) */\n readonly damageModifier: number;\n /** Damage taken multiplier (affects incoming damage) */\n readonly damageTakenMultiplier: number;\n /** Movement speed modifier */\n readonly speedModifier: number;\n /** Can perform blocking actions */\n readonly canBlock: boolean;\n /** Can execute techniques */\n readonly canExecuteTechniques: boolean;\n}\n\n/**\n * Combat State System managing readiness state transitions.\n * \n * Evaluates player condition and determines current combat readiness state.\n * States degrade based on:\n * - Cumulative damage (health loss)\n * - Pain overload\n * - Consciousness reduction\n * - Balance disruption\n * \n * @example\n * ```typescript\n * const combatStateSystem = new CombatStateSystem();\n * \n * // Determine current state\n * const state = combatStateSystem.determineState(player);\n * \n * // Get capability modifiers\n * const capability = combatStateSystem.getCapability(state);\n * console.log(`Capability: ${capability.capability}%`);\n * \n * // Apply modifiers to damage calculation\n * const modifiedDamage = baseDamage * capability.accuracyModifier;\n * ```\n * \n * @public\n * @korean 전투상태시스템\n */\nexport class CombatStateSystem {\n /**\n * State capability definitions with modifiers per state.\n * \n * Based on acceptance criteria:\n * - READY: 100% capability (baseline)\n * - SHAKEN: -15% accuracy, -10% damage\n * - VULNERABLE: -30% accuracy, -25% damage, +50% damage taken\n * - HELPLESS: Cannot attack, cannot block, +100% damage taken\n */\n private readonly stateCapabilities: Record<CombatReadinessState, StateCapability> = {\n [CombatReadinessState.READY]: {\n capability: 1.0, // 100%\n accuracyModifier: 1.0,\n defenseModifier: 1.0,\n damageModifier: 1.0,\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 1.0,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.SHAKEN]: {\n capability: 0.85, // 85%\n accuracyModifier: 0.85, // -15% accuracy\n defenseModifier: 1.0,\n damageModifier: 0.9, // -10% damage\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 0.95,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.VULNERABLE]: {\n capability: 0.7, // 70%\n accuracyModifier: 0.7, // -30% accuracy\n defenseModifier: 0.75,\n damageModifier: 0.75, // -25% damage\n damageTakenMultiplier: 1.5, // +50% damage taken\n speedModifier: 0.8,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.HELPLESS]: {\n capability: 0.0, // 0%\n accuracyModifier: 0.0, // Cannot attack\n defenseModifier: 0.0,\n damageModifier: 0.0, // Cannot attack\n damageTakenMultiplier: 2.0, // +100% damage taken (2x)\n speedModifier: 0.3,\n canBlock: false, // Cannot block\n canExecuteTechniques: false,\n },\n };\n\n /**\n * Determines the current combat readiness state based on player condition.\n * \n * Evaluates health, pain, consciousness, balance, and recent hits to determine\n * the most appropriate combat state. State transitions follow acceptance criteria:\n * - READY → SHAKEN: After taking 2-3 hits or significant vital point strike\n * - SHAKEN → VULNERABLE: After additional 2 hits or loss of 30% body part health\n * - VULNERABLE → HELPLESS: After head trauma, pain >80, or knock-down\n * - Recovery: HELPLESS → READY over 5 seconds if no additional hits\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp in milliseconds\n * @returns Current combat readiness state\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player, Date.now());\n * if (state === CombatReadinessState.HELPLESS) {\n * console.log(\"Player is near incapacitation!\");\n * }\n * ```\n * \n * @public\n * @korean 상태결정\n */\n determineState(player: PlayerState, currentTime?: number): CombatReadinessState {\n const healthPercent = player.health / player.maxHealth;\n const pain = player.pain;\n const consciousness = player.consciousness;\n const balance = player.balance;\n \n // Count recent hits (within last 10 seconds)\n const recentHits = this.countRecentHits(player, currentTime, 10000);\n \n // Check for recovery from HELPLESS state\n // If player was helpless and recovery conditions are met, allow normal state determination\n // Recovery overrides low stats if enough time has passed without hits\n if (player.lastHelplessStateTime && currentTime) {\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n \n // Recovery: HELPLESS → normal state after 5 seconds if no additional hits\n if (timeSinceHelpless >= 5000 && noRecentHits) {\n // Player has recovered - skip HELPLESS check and determine state normally\n // This allows recovery even if stats are still low\n // Continue to normal state determination below\n } else if (timeSinceHelpless < 5000) {\n // Still in recovery period, check if should remain HELPLESS\n const stillCritical = healthPercent <= 0.3 || pain > 80 || consciousness <= 20 || balance <= 20;\n if (stillCritical) {\n return CombatReadinessState.HELPLESS;\n }\n }\n }\n\n // Check for HELPLESS state (worst condition) - only if not in recovery\n // Triggers: health <= 30%, pain > 80, consciousness <= 20, balance <= 20, or head trauma\n const hasHeadTrauma = player.bodyPartHealth \n ? (player.bodyPartHealth.head / (player.bodyPartMaxHealth?.head ?? 100)) < 0.5\n : false;\n \n // Only enter HELPLESS if not already recovering\n const isRecovering = player.lastHelplessStateTime && currentTime \n && (currentTime - player.lastHelplessStateTime) >= 5000\n && this.countRecentHits(player, currentTime, 5000) === 0;\n \n if (!isRecovering && (\n healthPercent <= 0.3 ||\n pain > 80 ||\n consciousness <= 20 ||\n balance <= 20 ||\n hasHeadTrauma\n )) {\n return CombatReadinessState.HELPLESS;\n }\n\n // Check for VULNERABLE state\n // Triggers: health <= 50%, pain > 60, consciousness <= 40, balance <= 40,\n // or 30% body part health loss\n const hasBodyPartDamage = this.checkBodyPartHealthLoss(player, 0.3);\n \n if (\n healthPercent <= 0.5 ||\n pain > 60 ||\n consciousness <= 40 ||\n balance <= 40 ||\n hasBodyPartDamage ||\n recentHits >= 4 // Additional 2 hits from SHAKEN (total 4-5 hits)\n ) {\n return CombatReadinessState.VULNERABLE;\n }\n\n // Check for SHAKEN state\n // Triggers: health <= 70%, pain > 30, consciousness <= 60, balance <= 60,\n // or 2-3 recent hits\n if (\n healthPercent <= 0.7 ||\n pain > 30 ||\n consciousness <= 60 ||\n balance <= 60 ||\n recentHits >= 2 // 2-3 hits triggers SHAKEN\n ) {\n return CombatReadinessState.SHAKEN;\n }\n\n // Default to READY state\n return CombatReadinessState.READY;\n }\n\n /**\n * Gets the capability modifiers for a specific combat state.\n * \n * Returns all modifiers that should be applied to player actions\n * based on their current combat readiness.\n * \n * @param state - Combat readiness state\n * @returns Capability modifiers for the state\n * \n * @example\n * ```typescript\n * const capability = combatStateSystem.getCapability(\n * CombatReadinessState.SHAKEN\n * );\n * \n * // Apply to damage calculation\n * const finalDamage = baseDamage * capability.accuracyModifier;\n * \n * // Check if can block\n * if (!capability.canBlock) {\n * console.log(\"Cannot block in this state!\");\n * }\n * ```\n * \n * @public\n * @korean 능력조회\n */\n getCapability(state: CombatReadinessState): StateCapability {\n return this.stateCapabilities[state];\n }\n\n /**\n * Applies combat state modifiers to a player state.\n * \n * Creates a modified player state with capability reductions\n * based on current combat readiness.\n * \n * @param player - Current player state\n * @param state - Combat readiness state to apply\n * @returns Modified player state with applied modifiers\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player);\n * const modifiedPlayer = combatStateSystem.applyStateModifiers(\n * player,\n * state\n * );\n * ```\n * \n * @public\n * @korean 상태적용\n */\n applyStateModifiers(\n player: PlayerState,\n state: CombatReadinessState\n ): PlayerState {\n const capability = this.getCapability(state);\n\n return {\n ...player,\n attackPower: Math.floor(player.attackPower * capability.damageModifier),\n defense: Math.floor(player.defense * capability.defenseModifier),\n speed: Math.floor(player.speed * capability.speedModifier),\n isBlocking: player.isBlocking && capability.canBlock,\n isStunned: player.isStunned || !capability.canExecuteTechniques,\n };\n }\n\n /**\n * Gets bilingual name for combat state.\n * \n * @param state - Combat readiness state\n * @returns Korean and English state names\n * \n * @public\n * @korean 상태이름\n */\n getStateName(state: CombatReadinessState): { korean: string; english: string } {\n const names: Record<CombatReadinessState, { korean: string; english: string }> = {\n [CombatReadinessState.READY]: {\n korean: \"준비완료\",\n english: \"Ready\",\n },\n [CombatReadinessState.SHAKEN]: {\n korean: \"동요상태\",\n english: \"Shaken\",\n },\n [CombatReadinessState.VULNERABLE]: {\n korean: \"취약상태\",\n english: \"Vulnerable\",\n },\n [CombatReadinessState.HELPLESS]: {\n korean: \"무력상태\",\n english: \"Helpless\",\n },\n };\n\n return names[state];\n }\n\n /**\n * Gets emoji indicator for combat state.\n * \n * @param state - Combat readiness state\n * @returns Emoji representing the state\n * \n * @public\n * @korean 상태아이콘\n */\n getStateEmoji(state: CombatReadinessState): string {\n const emojis: Record<CombatReadinessState, string> = {\n [CombatReadinessState.READY]: \"🟢\",\n [CombatReadinessState.SHAKEN]: \"🟡\",\n [CombatReadinessState.VULNERABLE]: \"🟠\",\n [CombatReadinessState.HELPLESS]: \"🔴\",\n };\n\n return emojis[state];\n }\n\n /**\n * Counts recent hits within a time window.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (milliseconds)\n * @param timeWindow - Time window to check in milliseconds\n * @returns Number of hits within the time window\n * \n * @private\n * @korean 최근타격횟수\n */\n private countRecentHits(\n player: PlayerState,\n currentTime: number | undefined,\n timeWindow: number\n ): number {\n if (!currentTime || !player.recentHitTimestamps) {\n return 0;\n }\n\n const cutoffTime = currentTime - timeWindow;\n return player.recentHitTimestamps.filter(\n (timestamp) => timestamp >= cutoffTime\n ).length;\n }\n\n /**\n * Checks if any body part has lost more than the specified percentage of health.\n * \n * @param player - Current player state\n * @param lossThreshold - Health loss threshold (0.0 to 1.0)\n * @returns True if any body part has lost more than the threshold\n * \n * @private\n * @korean 신체부위손상확인\n */\n private checkBodyPartHealthLoss(\n player: PlayerState,\n lossThreshold: number\n ): boolean {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return false;\n }\n\n const bodyParts: Array<keyof typeof player.bodyPartHealth> = [\n \"head\",\n \"neck\",\n \"torsoUpper\",\n \"torsoLower\",\n \"armLeft\",\n \"armRight\",\n \"legLeft\",\n \"legRight\",\n ];\n\n for (const part of bodyParts) {\n const current = player.bodyPartHealth[part];\n const max = player.bodyPartMaxHealth[part];\n const healthPercent = current / max;\n const lossPercent = 1 - healthPercent;\n\n if (lossPercent >= lossThreshold) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Records a hit on the player and updates recent hit tracking.\n * \n * Should be called by combat system when player takes a hit.\n * Maintains a rolling window of the last 10 hit timestamps.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp of the hit\n * @returns Updated player state with recorded hit\n * \n * @public\n * @korean 타격기록\n */\n recordHit(player: PlayerState, currentTime: number): PlayerState {\n const recentHits = player.recentHitTimestamps ?? [];\n \n // Add new hit timestamp and keep only last 10\n const updatedHits = [...recentHits, currentTime].slice(-10);\n\n return {\n ...player,\n recentHitTimestamps: updatedHits,\n hitsTaken: player.hitsTaken + 1,\n };\n }\n\n /**\n * Updates player when entering HELPLESS state.\n * \n * Records the timestamp for recovery tracking.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp when entering helpless state\n * @returns Updated player state\n * \n * @public\n * @korean 무력상태기록\n */\n enterHelplessState(player: PlayerState, currentTime: number): PlayerState {\n return {\n ...player,\n lastHelplessStateTime: currentTime,\n };\n }\n\n /**\n * Checks if player can recover from HELPLESS state.\n * \n * Recovery occurs after 5 seconds with no additional hits.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp\n * @returns True if recovery is possible\n * \n * @public\n * @korean 회복가능확인\n */\n canRecoverFromHelpless(player: PlayerState, currentTime: number): boolean {\n if (!player.lastHelplessStateTime) {\n return false;\n }\n\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n\n return timeSinceHelpless >= 5000 && noRecentHits;\n }\n}\n\nexport default CombatStateSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CombatStateSystem.js","names":[],"sources":["../../../src/systems/combat/CombatStateSystem.ts"],"sourcesContent":["/**\n * Combat State System for managing Ready/Shaken/Vulnerable/Helpless states.\n * \n * **Korean**: 전투 상태 시스템 (Combat State System)\n * \n * Implements the combat readiness state machine based on cumulative damage,\n * pain, consciousness, and balance. Each state affects player capabilities\n * with specific multipliers.\n * \n * ## Combat States\n * \n * - **🟢 READY**: 100% capability - perfect combat condition\n * - **🟡 SHAKEN**: 80% capability - slightly compromised, -20% accuracy\n * - **🟠 VULNERABLE**: 60% capability - significantly exposed, -40% defense\n * - **🔴 HELPLESS**: 20% capability - near incapacitation, cannot block\n * \n * @module systems/combat/CombatStateSystem\n * @category Combat System\n * @korean 전투상태시스템\n */\n\nimport { PlayerState } from \"../player\";\n\n/**\n * Combat readiness states representing player combat effectiveness.\n * \n * **Korean**: 전투 준비 상태\n */\nexport enum CombatReadinessState {\n /** 🟢 Ready - Full combat capability (100%) */\n READY = \"ready\",\n /** 🟡 Shaken - Reduced effectiveness (80%) */\n SHAKEN = \"shaken\",\n /** 🟠 Vulnerable - Significantly impaired (60%) */\n VULNERABLE = \"vulnerable\",\n /** 🔴 Helpless - Near incapacitation (20%) */\n HELPLESS = \"helpless\",\n}\n\n/**\n * State capability modifiers for each combat state.\n * \n * **Korean**: 상태 능력 배율\n */\ninterface StateCapability {\n /** Overall capability percentage */\n readonly capability: number;\n /** Accuracy modifier (affects hit chance and damage output) */\n readonly accuracyModifier: number;\n /** Defense modifier (affects damage reduction) */\n readonly defenseModifier: number;\n /** Damage modifier (affects outgoing damage) */\n readonly damageModifier: number;\n /** Damage taken multiplier (affects incoming damage) */\n readonly damageTakenMultiplier: number;\n /** Movement speed modifier */\n readonly speedModifier: number;\n /** Can perform blocking actions */\n readonly canBlock: boolean;\n /** Can execute techniques */\n readonly canExecuteTechniques: boolean;\n}\n\n/**\n * Combat State System managing readiness state transitions.\n * \n * Evaluates player condition and determines current combat readiness state.\n * States degrade based on:\n * - Cumulative damage (health loss)\n * - Pain overload\n * - Consciousness reduction\n * - Balance disruption\n * \n * @example\n * ```typescript\n * const combatStateSystem = new CombatStateSystem();\n * \n * // Determine current state\n * const state = combatStateSystem.determineState(player);\n * \n * // Get capability modifiers\n * const capability = combatStateSystem.getCapability(state);\n * console.log(`Capability: ${capability.capability}%`);\n * \n * // Apply modifiers to damage calculation\n * const modifiedDamage = baseDamage * capability.accuracyModifier;\n * ```\n * \n * @public\n * @korean 전투상태시스템\n */\nexport class CombatStateSystem {\n /**\n * State capability definitions with modifiers per state.\n * \n * Based on acceptance criteria:\n * - READY: 100% capability (baseline)\n * - SHAKEN: -15% accuracy, -10% damage\n * - VULNERABLE: -30% accuracy, -25% damage, +50% damage taken\n * - HELPLESS: Cannot attack, cannot block, +100% damage taken\n */\n private readonly stateCapabilities: Record<CombatReadinessState, StateCapability> = {\n [CombatReadinessState.READY]: {\n capability: 1.0, // 100%\n accuracyModifier: 1.0,\n defenseModifier: 1.0,\n damageModifier: 1.0,\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 1.0,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.SHAKEN]: {\n capability: 0.85, // 85%\n accuracyModifier: 0.85, // -15% accuracy\n defenseModifier: 1.0,\n damageModifier: 0.9, // -10% damage\n damageTakenMultiplier: 1.0, // Normal damage taken\n speedModifier: 0.95,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.VULNERABLE]: {\n capability: 0.7, // 70%\n accuracyModifier: 0.7, // -30% accuracy\n defenseModifier: 0.75,\n damageModifier: 0.75, // -25% damage\n damageTakenMultiplier: 1.5, // +50% damage taken\n speedModifier: 0.8,\n canBlock: true,\n canExecuteTechniques: true,\n },\n [CombatReadinessState.HELPLESS]: {\n capability: 0.0, // 0%\n accuracyModifier: 0.0, // Cannot attack\n defenseModifier: 0.0,\n damageModifier: 0.0, // Cannot attack\n damageTakenMultiplier: 2.0, // +100% damage taken (2x)\n speedModifier: 0.3,\n canBlock: false, // Cannot block\n canExecuteTechniques: false,\n },\n };\n\n /**\n * Determines the current combat readiness state based on player condition.\n * \n * Evaluates health, pain, consciousness, balance, and recent hits to determine\n * the most appropriate combat state. State transitions follow acceptance criteria:\n * - READY → SHAKEN: After taking 2-3 hits or significant vital point strike\n * - SHAKEN → VULNERABLE: After additional 2 hits or loss of 30% body part health\n * - VULNERABLE → HELPLESS: After head trauma, pain >80, or knock-down\n * - Recovery: HELPLESS → READY over 5 seconds if no additional hits\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp in milliseconds\n * @returns Current combat readiness state\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player, Date.now());\n * if (state === CombatReadinessState.HELPLESS) {\n * console.log(\"Player is near incapacitation!\");\n * }\n * ```\n * \n * @public\n * @korean 상태결정\n */\n determineState(player: PlayerState, currentTime?: number): CombatReadinessState {\n const healthPercent = player.health / player.maxHealth;\n const pain = player.pain;\n const consciousness = player.consciousness;\n const balance = player.balance;\n \n // Count recent hits (within last 10 seconds)\n const recentHits = this.countRecentHits(player, currentTime, 10000);\n \n // Check for recovery from HELPLESS state\n // If player was helpless and recovery conditions are met, allow normal state determination\n // Recovery overrides low stats if enough time has passed without hits\n if (player.lastHelplessStateTime && currentTime) {\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n \n // Recovery: HELPLESS → normal state after 5 seconds if no additional hits\n if (timeSinceHelpless >= 5000 && noRecentHits) {\n // Player has recovered - skip HELPLESS check and determine state normally\n // This allows recovery even if stats are still low\n // Continue to normal state determination below\n } else if (timeSinceHelpless < 5000) {\n // Still in recovery period, check if should remain HELPLESS\n const stillCritical = healthPercent <= 0.3 || pain > 80 || consciousness <= 20 || balance <= 20;\n if (stillCritical) {\n return CombatReadinessState.HELPLESS;\n }\n }\n }\n\n // Check for HELPLESS state (worst condition) - only if not in recovery\n // Triggers: health <= 30%, pain > 80, consciousness <= 20, balance <= 20, or head trauma\n const hasHeadTrauma = player.bodyPartHealth \n ? (player.bodyPartHealth.head / (player.bodyPartMaxHealth?.head ?? 100)) < 0.5\n : false;\n \n // Only enter HELPLESS if not already recovering\n const isRecovering = player.lastHelplessStateTime && currentTime \n && (currentTime - player.lastHelplessStateTime) >= 5000\n && this.countRecentHits(player, currentTime, 5000) === 0;\n \n if (!isRecovering && (\n healthPercent <= 0.3 ||\n pain > 80 ||\n consciousness <= 20 ||\n balance <= 20 ||\n hasHeadTrauma\n )) {\n return CombatReadinessState.HELPLESS;\n }\n\n // Check for VULNERABLE state\n // Triggers: health <= 50%, pain > 60, consciousness <= 40, balance <= 40,\n // or 30% body part health loss\n const hasBodyPartDamage = this.checkBodyPartHealthLoss(player, 0.3);\n \n if (\n healthPercent <= 0.5 ||\n pain > 60 ||\n consciousness <= 40 ||\n balance <= 40 ||\n hasBodyPartDamage ||\n recentHits >= 4 // Additional 2 hits from SHAKEN (total 4-5 hits)\n ) {\n return CombatReadinessState.VULNERABLE;\n }\n\n // Check for SHAKEN state\n // Triggers: health <= 70%, pain > 30, consciousness <= 60, balance <= 60,\n // or 2-3 recent hits\n if (\n healthPercent <= 0.7 ||\n pain > 30 ||\n consciousness <= 60 ||\n balance <= 60 ||\n recentHits >= 2 // 2-3 hits triggers SHAKEN\n ) {\n return CombatReadinessState.SHAKEN;\n }\n\n // Default to READY state\n return CombatReadinessState.READY;\n }\n\n /**\n * Gets the capability modifiers for a specific combat state.\n * \n * Returns all modifiers that should be applied to player actions\n * based on their current combat readiness.\n * \n * @param state - Combat readiness state\n * @returns Capability modifiers for the state\n * \n * @example\n * ```typescript\n * const capability = combatStateSystem.getCapability(\n * CombatReadinessState.SHAKEN\n * );\n * \n * // Apply to damage calculation\n * const finalDamage = baseDamage * capability.accuracyModifier;\n * \n * // Check if can block\n * if (!capability.canBlock) {\n * console.log(\"Cannot block in this state!\");\n * }\n * ```\n * \n * @public\n * @korean 능력조회\n */\n getCapability(state: CombatReadinessState): StateCapability {\n return this.stateCapabilities[state];\n }\n\n /**\n * Applies combat state modifiers to a player state.\n * \n * Creates a modified player state with capability reductions\n * based on current combat readiness.\n * \n * @param player - Current player state\n * @param state - Combat readiness state to apply\n * @returns Modified player state with applied modifiers\n * \n * @example\n * ```typescript\n * const state = combatStateSystem.determineState(player);\n * const modifiedPlayer = combatStateSystem.applyStateModifiers(\n * player,\n * state\n * );\n * ```\n * \n * @public\n * @korean 상태적용\n */\n applyStateModifiers(\n player: PlayerState,\n state: CombatReadinessState\n ): PlayerState {\n const capability = this.getCapability(state);\n\n return {\n ...player,\n attackPower: Math.floor(player.attackPower * capability.damageModifier),\n defense: Math.floor(player.defense * capability.defenseModifier),\n speed: Math.floor(player.speed * capability.speedModifier),\n isBlocking: player.isBlocking && capability.canBlock,\n isStunned: player.isStunned || !capability.canExecuteTechniques,\n };\n }\n\n /**\n * Gets bilingual name for combat state.\n * \n * @param state - Combat readiness state\n * @returns Korean and English state names\n * \n * @public\n * @korean 상태이름\n */\n getStateName(state: CombatReadinessState): { korean: string; english: string } {\n const names: Record<CombatReadinessState, { korean: string; english: string }> = {\n [CombatReadinessState.READY]: {\n korean: \"준비완료\",\n english: \"Ready\",\n },\n [CombatReadinessState.SHAKEN]: {\n korean: \"동요상태\",\n english: \"Shaken\",\n },\n [CombatReadinessState.VULNERABLE]: {\n korean: \"취약상태\",\n english: \"Vulnerable\",\n },\n [CombatReadinessState.HELPLESS]: {\n korean: \"무력상태\",\n english: \"Helpless\",\n },\n };\n\n return names[state];\n }\n\n /**\n * Gets emoji indicator for combat state.\n * \n * @param state - Combat readiness state\n * @returns Emoji representing the state\n * \n * @public\n * @korean 상태아이콘\n */\n getStateEmoji(state: CombatReadinessState): string {\n const emojis: Record<CombatReadinessState, string> = {\n [CombatReadinessState.READY]: \"🟢\",\n [CombatReadinessState.SHAKEN]: \"🟡\",\n [CombatReadinessState.VULNERABLE]: \"🟠\",\n [CombatReadinessState.HELPLESS]: \"🔴\",\n };\n\n return emojis[state];\n }\n\n /**\n * Counts recent hits within a time window.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp (milliseconds)\n * @param timeWindow - Time window to check in milliseconds\n * @returns Number of hits within the time window\n * \n * @private\n * @korean 최근타격횟수\n */\n private countRecentHits(\n player: PlayerState,\n currentTime: number | undefined,\n timeWindow: number\n ): number {\n if (!currentTime || !player.recentHitTimestamps) {\n return 0;\n }\n\n const cutoffTime = currentTime - timeWindow;\n return player.recentHitTimestamps.filter(\n (timestamp) => timestamp >= cutoffTime\n ).length;\n }\n\n /**\n * Checks if any body part has lost more than the specified percentage of health.\n * \n * @param player - Current player state\n * @param lossThreshold - Health loss threshold (0.0 to 1.0)\n * @returns True if any body part has lost more than the threshold\n * \n * @private\n * @korean 신체부위손상확인\n */\n private checkBodyPartHealthLoss(\n player: PlayerState,\n lossThreshold: number\n ): boolean {\n if (!player.bodyPartHealth || !player.bodyPartMaxHealth) {\n return false;\n }\n\n const bodyParts: Array<keyof typeof player.bodyPartHealth> = [\n \"head\",\n \"neck\",\n \"torsoUpper\",\n \"torsoLower\",\n \"armLeft\",\n \"armRight\",\n \"legLeft\",\n \"legRight\",\n ];\n\n for (const part of bodyParts) {\n const current = player.bodyPartHealth[part];\n const max = player.bodyPartMaxHealth[part];\n const healthPercent = current / max;\n const lossPercent = 1 - healthPercent;\n\n if (lossPercent >= lossThreshold) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Records a hit on the player and updates recent hit tracking.\n * \n * Should be called by combat system when player takes a hit.\n * Maintains a rolling window of the last 10 hit timestamps.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp of the hit\n * @returns Updated player state with recorded hit\n * \n * @public\n * @korean 타격기록\n */\n recordHit(player: PlayerState, currentTime: number): PlayerState {\n const recentHits = player.recentHitTimestamps ?? [];\n \n // Add new hit timestamp and keep only last 10\n const updatedHits = [...recentHits, currentTime].slice(-10);\n\n return {\n ...player,\n recentHitTimestamps: updatedHits,\n hitsTaken: player.hitsTaken + 1,\n };\n }\n\n /**\n * Updates player when entering HELPLESS state.\n * \n * Records the timestamp for recovery tracking.\n * \n * @param player - Current player state\n * @param currentTime - Timestamp when entering helpless state\n * @returns Updated player state\n * \n * @public\n * @korean 무력상태기록\n */\n enterHelplessState(player: PlayerState, currentTime: number): PlayerState {\n return {\n ...player,\n lastHelplessStateTime: currentTime,\n };\n }\n\n /**\n * Checks if player can recover from HELPLESS state.\n * \n * Recovery occurs after 5 seconds with no additional hits.\n * \n * @param player - Current player state\n * @param currentTime - Current timestamp\n * @returns True if recovery is possible\n * \n * @public\n * @korean 회복가능확인\n */\n canRecoverFromHelpless(player: PlayerState, currentTime: number): boolean {\n if (!player.lastHelplessStateTime) {\n return false;\n }\n\n const timeSinceHelpless = currentTime - player.lastHelplessStateTime;\n const noRecentHits = this.countRecentHits(player, currentTime, 5000) === 0;\n\n return timeSinceHelpless >= 5000 && noRecentHits;\n }\n}\n\nexport default CombatStateSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @public\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @public\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @public\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @public\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @public\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @public\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @public\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @public\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @public\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @public\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @public\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @public\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @public\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConsciousnessSystem.js","names":[],"sources":["../../../src/systems/combat/ConsciousnessSystem.ts"],"sourcesContent":["/**\n * Consciousness System for managing awareness and cognitive function levels.\n *\n * **Korean**: 의식 시스템 (Consciousness System)\n *\n * Implements consciousness levels based on head trauma, blood flow restriction,\n * and neural damage. Consciousness affects reaction time, targeting precision,\n * and overall combat effectiveness.\n *\n * ## Consciousness Levels\n *\n * - **전투각성 (Combat Alert)**: 100% - Full awareness, optimal combat ability\n * - **혼란상태 (Disoriented)**: 70% - Reduced reaction, vulnerability window\n * - **기절직전 (Stunned)**: 40% - Severe impairment, incapacitation opportunity\n * - **무의식 (Unconscious)**: 0% - Complete incapacitation\n *\n * ## Fall System Integration\n *\n * When consciousness drops below 10%, the system triggers fall animations (낙법).\n * The character loses the ability to stand and collapses to the ground.\n * Fall direction is based on last impact location.\n *\n * @module systems/combat/ConsciousnessSystem\n * @category Combat System\n * @korean 의식시스템\n */\n\nimport { VitalPointCategory } from \"@/types\";\nimport type { TrigramStance } from \"@/types/common\";\nimport { determineFallFromStance } from \"../animation\";\nimport type { FallType } from \"../animation/core/types\";\nimport { PlayerState } from \"../player\";\n\n/**\n * Consciousness levels representing cognitive function and awareness.\n *\n * **Korean**: 의식 수준\n */\nexport enum ConsciousnessLevel {\n /** Full combat awareness (90-100%) */\n COMBAT_ALERT = \"combat_alert\",\n /** Disoriented and vulnerable (50-89%) */\n DISORIENTED = \"disoriented\",\n /** Severely stunned (10-49%) */\n STUNNED = \"stunned\",\n /** Completely unconscious (0-9%) */\n UNCONSCIOUS = \"unconscious\",\n}\n\n/**\n * Effects of consciousness levels on combat performance.\n */\ninterface ConsciousnessEffects {\n /** Consciousness value range */\n readonly range: readonly [number, number];\n /** Reaction time multiplier */\n readonly reactionTimeMultiplier: number;\n /** Accuracy penalty */\n readonly accuracyPenalty: number;\n /** Defense penalty */\n readonly defensePenalty: number;\n /** Can perform actions */\n readonly canAct: boolean;\n /** Vision clarity multiplier */\n readonly visionClarity: number;\n}\n\n/**\n * Consciousness System managing awareness and cognitive function.\n *\n * Tracks consciousness damage from:\n * - Head trauma (strikes to head region)\n * - Neurological vital point hits\n * - Blood flow restriction (vascular hits)\n * - Respiratory disruption\n *\n * Consciousness naturally recovers over time when not taking damage.\n *\n * @example\n * ```typescript\n * const consciousnessSystem = new ConsciousnessSystem();\n *\n * // Apply damage from head strike\n * const newPlayer = consciousnessSystem.applyDamage(\n * player,\n * 15,\n * VitalPointCategory.NEUROLOGICAL\n * );\n *\n * // Check consciousness level\n * const level = consciousnessSystem.getLevel(newPlayer.consciousness);\n * console.log(`Consciousness: ${level}`);\n *\n * // Apply recovery over time\n * const recovered = consciousnessSystem.applyRecovery(newPlayer, 1000);\n * ```\n *\n * @public\n * @korean 의식시스템\n */\nexport class ConsciousnessSystem {\n /**\n * Consciousness level effects and thresholds.\n */\n private readonly levelEffects: Record<\n ConsciousnessLevel,\n ConsciousnessEffects\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n range: [90, 100],\n reactionTimeMultiplier: 1.0,\n accuracyPenalty: 0.0,\n defensePenalty: 0.0,\n canAct: true,\n visionClarity: 1.0,\n },\n [ConsciousnessLevel.DISORIENTED]: {\n range: [50, 89],\n reactionTimeMultiplier: 1.3, // 30% slower reactions\n accuracyPenalty: 0.2, // -20% accuracy\n defensePenalty: 0.15, // -15% defense\n canAct: true,\n visionClarity: 0.7,\n },\n [ConsciousnessLevel.STUNNED]: {\n range: [20, 49], // Updated: Changed from [10, 49] to [20, 49]\n reactionTimeMultiplier: 2.0, // 2x slower reactions\n accuracyPenalty: 0.5, // -50% accuracy\n defensePenalty: 0.4, // -40% defense\n canAct: true,\n visionClarity: 0.4,\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n range: [0, 19], // Updated: Changed from [0, 9] to [0, 19] - incapacitation threshold <20%\n reactionTimeMultiplier: Infinity,\n accuracyPenalty: 1.0, // Complete inability\n defensePenalty: 1.0, // No defense\n canAct: false,\n visionClarity: 0.0,\n },\n };\n\n /**\n * Damage multipliers by vital point category affecting consciousness.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 3.0, // Nerve strikes heavily affect consciousness\n [VitalPointCategory.VASCULAR]: 2.0, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 1.5, // Breathing difficulty\n default: 0.5, // Other damage has minor effect\n };\n\n /**\n * Base recovery rate per second.\n */\n private readonly baseRecoveryRate = 5.0; // 5 points per second\n\n /**\n * Incapacitation threshold - consciousness <20% renders player helpless.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n */\n private readonly incapacitationThreshold = 20;\n\n /**\n * Duration of helplessness when consciousness drops below threshold (3-5 seconds).\n */\n private readonly helplessDuration = {\n min: 3000, // 3 seconds\n max: 5000, // 5 seconds\n };\n\n /**\n * Time without head trauma required for consciousness recovery (5 seconds).\n */\n private readonly noTraumaRecoveryTime = 5000; // 5 seconds\n\n /**\n * Applies consciousness damage from combat.\n *\n * Calculates consciousness reduction based on damage amount and\n * vital point category. Neurological and vascular damage have\n * the highest impact on consciousness.\n *\n * @param player - Current player state\n * @param damage - Base damage amount\n * @param category - Vital point category hit\n * @returns Updated player state with reduced consciousness\n *\n * @example\n * ```typescript\n * // Head strike causes significant consciousness damage\n * const newPlayer = system.applyDamage(\n * player,\n * 20,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * console.log(`Consciousness: ${newPlayer.consciousness}`);\n * ```\n *\n * @public\n * @korean 의식피해적용\n */\n applyDamage(\n player: PlayerState,\n damage: number,\n category?: VitalPointCategory\n ): PlayerState {\n // Get multiplier based on category\n const multiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate consciousness damage\n const consciousnessDamage = damage * multiplier;\n\n // Apply damage, clamped to 0-100 range\n const newConsciousness = Math.max(\n 0,\n Math.min(100, player.consciousness - consciousnessDamage)\n );\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Applies consciousness recovery over time.\n *\n * Consciousness naturally recovers when not taking damage.\n * Recovery rate depends on current level - severely stunned\n * players recover more slowly.\n *\n * **Note**: Full recovery only occurs after 5 seconds without head trauma.\n *\n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @param lastHeadTraumaTime - Timestamp of last head trauma (optional)\n * @returns Updated player state with recovered consciousness\n *\n * @example\n * ```typescript\n * // In game loop (60fps, ~16ms per frame)\n * player = system.applyRecovery(player, 16, player.lastActionTime);\n * ```\n *\n * @public\n * @korean 의식회복\n */\n applyRecovery(\n player: PlayerState,\n deltaTime: number,\n lastHeadTraumaTime?: number\n ): PlayerState {\n // Already at full consciousness\n if (player.consciousness >= 100) {\n return player;\n }\n\n // Check if enough time has passed since last head trauma\n const timeSinceTrauma = lastHeadTraumaTime\n ? Date.now() - lastHeadTraumaTime\n : Infinity;\n\n // Don't recover if head trauma is recent (within 5 seconds)\n if (timeSinceTrauma < this.noTraumaRecoveryTime) {\n return player;\n }\n\n // Calculate recovery based on time\n const deltaSeconds = deltaTime / 1000;\n const level = this.getLevel(player.consciousness);\n\n // Recovery rate modifier based on level\n let recoveryModifier = 1.0;\n if (level === ConsciousnessLevel.STUNNED) {\n recoveryModifier = 0.5; // Slower recovery when stunned\n } else if (level === ConsciousnessLevel.UNCONSCIOUS) {\n recoveryModifier = 0.2; // Very slow recovery when unconscious\n }\n\n const recovery = this.baseRecoveryRate * deltaSeconds * recoveryModifier;\n const newConsciousness = Math.min(100, player.consciousness + recovery);\n\n return {\n ...player,\n consciousness: newConsciousness,\n };\n }\n\n /**\n * Determines consciousness level from consciousness value.\n *\n * Updated thresholds:\n * - 90-100: Combat Alert\n * - 50-89: Disoriented\n * - 20-49: Stunned\n * - 0-19: Unconscious (incapacitation threshold <20%)\n *\n * @param consciousness - Consciousness value (0-100)\n * @returns Current consciousness level\n *\n * @example\n * ```typescript\n * const level = system.getLevel(75);\n * console.log(level); // DISORIENTED\n *\n * const level2 = system.getLevel(15);\n * console.log(level2); // UNCONSCIOUS (below incapacitation threshold)\n * ```\n *\n * @public\n * @korean 의식수준확인\n */\n getLevel(consciousness: number): ConsciousnessLevel {\n if (consciousness >= 90) return ConsciousnessLevel.COMBAT_ALERT;\n if (consciousness >= 50) return ConsciousnessLevel.DISORIENTED;\n if (consciousness >= 20) return ConsciousnessLevel.STUNNED;\n return ConsciousnessLevel.UNCONSCIOUS;\n }\n\n /**\n * Gets effects for a specific consciousness level.\n *\n * @param level - Consciousness level\n * @returns Effects applied at that level\n *\n * @public\n * @korean 의식효과\n */\n getEffects(level: ConsciousnessLevel): ConsciousnessEffects {\n return this.levelEffects[level];\n }\n\n /**\n * Applies consciousness effects to player state.\n *\n * Modifies player stats based on current consciousness level.\n *\n * @param player - Current player state\n * @returns Modified player state with consciousness effects\n *\n * @public\n * @korean 의식효과적용\n */\n applyEffects(player: PlayerState): PlayerState {\n const level = this.getLevel(player.consciousness);\n const effects = this.getEffects(level);\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - effects.accuracyPenalty)\n ),\n defense: Math.floor(player.defense * (1 - effects.defensePenalty)),\n technique: Math.floor(player.technique * (1 - effects.accuracyPenalty)),\n isStunned:\n player.isStunned ||\n level === ConsciousnessLevel.UNCONSCIOUS ||\n level === ConsciousnessLevel.STUNNED,\n };\n }\n\n /**\n * Checks if player is at incapacitation threshold.\n *\n * **Korean**: 무력화한계 (Incapacitation Threshold)\n *\n * When consciousness drops below 20%, player becomes helpless for 3-5 seconds.\n *\n * @param player - Current player state\n * @returns True if consciousness is below incapacitation threshold (<20%)\n *\n * @example\n * ```typescript\n * if (system.isAtIncapacitationThreshold(player)) {\n * // Player is helpless, trigger helpless state\n * const helplessDuration = system.getHelplessDuration();\n * console.log(`Player helpless for ${helplessDuration}ms`);\n * }\n * ```\n *\n * @public\n * @korean 무력화한계확인\n */\n isAtIncapacitationThreshold(player: PlayerState): boolean {\n return player.consciousness < this.incapacitationThreshold;\n }\n\n /**\n * Gets random helpless duration when consciousness drops below threshold.\n *\n * Returns a random duration between 3-5 seconds (3000-5000ms).\n *\n * @returns Helpless duration in milliseconds\n *\n * @public\n * @korean 무력화지속시간\n */\n getHelplessDuration(): number {\n return (\n this.helplessDuration.min +\n Math.random() * (this.helplessDuration.max - this.helplessDuration.min)\n );\n }\n\n /**\n * Checks if enough time has passed for consciousness recovery.\n *\n * Consciousness only recovers after 5 seconds without head trauma.\n *\n * @param lastHeadTraumaTime - Timestamp of last head trauma\n * @returns True if recovery is allowed\n *\n * @public\n * @korean 회복가능확인\n */\n canRecover(lastHeadTraumaTime: number): boolean {\n const timeSinceTrauma = Date.now() - lastHeadTraumaTime;\n return timeSinceTrauma >= this.noTraumaRecoveryTime;\n }\n\n /**\n * Checks if player is incapacitated due to consciousness.\n *\n * @param player - Current player state\n * @returns True if player is unconscious\n *\n * @public\n * @korean 무의식확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return (\n this.getLevel(player.consciousness) === ConsciousnessLevel.UNCONSCIOUS\n );\n }\n\n /**\n * Gets bilingual name for consciousness level.\n *\n * @param level - Consciousness level\n * @returns Korean and English level names\n *\n * @public\n * @korean 의식이름\n */\n getLevelName(level: ConsciousnessLevel): { korean: string; english: string } {\n const names: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"전투각성\",\n english: \"Combat Alert\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"혼란상태\",\n english: \"Disoriented\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"기절직전\",\n english: \"Stunned\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"무의식\",\n english: \"Unconscious\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of consciousness level effects.\n *\n * @param level - Consciousness level\n * @returns Bilingual description\n *\n * @public\n * @korean 의식설명\n */\n getLevelDescription(level: ConsciousnessLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n ConsciousnessLevel,\n { korean: string; english: string }\n > = {\n [ConsciousnessLevel.COMBAT_ALERT]: {\n korean: \"완전한 의식과 전투 능력\",\n english: \"Full awareness and combat ability\",\n },\n [ConsciousnessLevel.DISORIENTED]: {\n korean: \"반응 시간 저하 및 혼란\",\n english: \"Reduced reaction time and confusion\",\n },\n [ConsciousnessLevel.STUNNED]: {\n korean: \"심각한 손상, 무력화 위험\",\n english: \"Severe impairment, incapacitation risk\",\n },\n [ConsciousnessLevel.UNCONSCIOUS]: {\n korean: \"완전한 무의식 상태\",\n english: \"Complete unconsciousness\",\n },\n };\n\n return descriptions[level];\n }\n\n /**\n * Gets color indicator for consciousness level (for UI).\n *\n * @param level - Consciousness level\n * @returns Hex color code\n *\n * @public\n * @korean 의식색상\n */\n getLevelColor(level: ConsciousnessLevel): number {\n const colors: Record<ConsciousnessLevel, number> = {\n [ConsciousnessLevel.COMBAT_ALERT]: 0x00ff00, // Green\n [ConsciousnessLevel.DISORIENTED]: 0xffaa00, // Orange\n [ConsciousnessLevel.STUNNED]: 0xff4400, // Red-orange\n [ConsciousnessLevel.UNCONSCIOUS]: 0xff0000, // Red\n };\n\n return colors[level];\n }\n\n /**\n * Checks if consciousness is low enough to trigger fall animation.\n *\n * Falls occur when consciousness drops below 10% (UNCONSCIOUS threshold).\n * This represents complete loss of ability to maintain standing position.\n *\n * Korean terminology:\n * - 의식상실낙법 (Uisik Sangsil Nakbeop): Consciousness loss fall\n * - 기절낙하 (Gijeol Nakha): Knockout collapse\n *\n * @param player - Current player state\n * @returns True if fall animation should trigger\n *\n * @example\n * ```typescript\n * if (consciousnessSystem.shouldTriggerFall(player)) {\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * lastImpactAngle\n * );\n * animationMachine.transitionTo(FALL_TYPE_TO_ANIMATION[fallType]);\n * }\n * ```\n *\n * @public\n * @korean 의식상실낙법확인\n */\n shouldTriggerFall(player: PlayerState): boolean {\n return player.consciousness < 10; // UNCONSCIOUS threshold\n }\n\n /**\n * Determines fall direction based on last impact and stance.\n *\n * When consciousness is lost, fall direction is determined by:\n * - Last attack angle (if available)\n * - Current stance bias (if no attack data)\n * - Default to backward fall (natural collapse)\n *\n * Korean falling from consciousness loss:\n * - 후방의식상실 (Hubang Uisik Sangsil): Backward consciousness loss fall\n * - 전방기절 (Jeonbang Gijeol): Forward knockout\n *\n * @param player - Current player state\n * @param lastImpactAngle - Angle of last hit (optional)\n * @returns Fall type to use for animation\n *\n * @example\n * ```typescript\n * // Player loses consciousness from frontal head strike\n * const fallType = consciousnessSystem.determineFallType(\n * player,\n * 0 // Front angle\n * );\n * // Returns: 'backward' (knocked back by frontal strike)\n *\n * // Player loses consciousness without specific impact\n * const fallType = consciousnessSystem.determineFallType(player);\n * // Returns: Stance-based fall or 'backward' default\n * ```\n *\n * @public\n * @korean 의식상실낙법결정\n */\n determineFallType(_player: PlayerState, lastImpactAngle?: number): FallType {\n // If we have impact angle, use simple direction logic\n if (lastImpactAngle !== undefined) {\n const normalizedAngle = Math.abs(lastImpactAngle);\n\n // Front impact (0° ±45°) causes backward fall\n if (normalizedAngle < Math.PI / 4) {\n return \"backward\";\n }\n\n // Rear impact (180° ±45°) causes forward fall\n // Since normalizedAngle is in [0, π], any angle > 3π/4 is a rear impact\n if (normalizedAngle > (3 * Math.PI) / 4) {\n return \"forward\";\n }\n\n // Side impacts: at this point normalizedAngle is in (π/4, 3π/4),\n // so lastImpactAngle cannot be 0 or ±π.\n if (lastImpactAngle < 0) {\n return \"side_left\";\n }\n if (lastImpactAngle > 0) {\n return \"side_right\";\n }\n\n // Fallback for exact 0 (or extremely small) angles in case future\n // changes alter the front/rear checks above. We treat this as a\n // natural backward collapse rather than a side fall.\n return \"backward\";\n }\n\n // Default to backward fall (natural collapse from consciousness loss)\n return \"backward\";\n }\n\n /**\n * Determines fall type based on stance when consciousness is lost.\n *\n * Uses stance bias to determine fall direction when consciousness\n * is lost without specific impact data.\n *\n * Korean terminology:\n * - 자세의식상실 (Jase Uisik Sangsil): Stance-based consciousness loss\n *\n * @param stance - Current trigram stance\n * @returns Fall type based on stance characteristics\n *\n * @example\n * ```typescript\n * // Player in Mountain stance (defensive back)\n * const fallType = consciousnessSystem.determineFallTypeFromStance(\n * TrigramStance.GAN\n * );\n * // Returns: 'backward' (Mountain has defensive backward bias)\n * ```\n *\n * @public\n * @korean 자세의식상실낙법\n */\n determineFallTypeFromStance(stance: TrigramStance): FallType {\n return determineFallFromStance(stance, \"backward\");\n }\n}\n\nexport default ConsciousnessSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @public\n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @public\n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @public\n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @public\n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @public\n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @public\n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @public\n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @public\n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @public\n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @public\n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @public\n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PainResponseSystem.js","names":[],"sources":["../../../src/systems/combat/PainResponseSystem.ts"],"sourcesContent":["/**\n * Pain Response System for managing cumulative pain effects in combat.\n * \n * **Korean**: 고통 반응 시스템 (Pain Response System)\n * \n * Implements realistic pain accumulation that progressively reduces combat\n * effectiveness. Unlike health, pain represents the body's protective response\n * to damage, reducing capabilities before critical injury occurs.\n * \n * ## Pain Mechanics\n * \n * - **충격통 (Shock Pain)**: Instant reaction affecting all abilities (10-30% reduction for 2-3 seconds)\n * - **누적외상 (Cumulative Trauma)**: Progressive damage impairment\n * - **통증과부하 (Pain Overload)**: Complete incapacitation from overwhelming pain (>80 pain)\n * - **무력화한계 (Incapacitation Threshold)**: Point of complete combat inability\n * \n * ## Pain Levels\n * \n * - **0-20**: Minimal pain - no significant effects\n * - **20-40**: Moderate pain - slight performance reduction (-10%)\n * - **40-60**: Significant pain - noticeable impairment (-20%)\n * - **60-80**: Severe pain - major limitations (-35%)\n * - **80-100**: Pain overload - chance of stun/unconsciousness, extreme impairment (-50%)\n * \n * @module systems/combat/PainResponseSystem\n * @category Combat System\n * @korean 고통반응시스템\n */\n\nimport { PlayerState } from \"../player\";\nimport { VitalPointSeverity, VitalPointCategory } from \"@/types\";\n\n/**\n * Pain level categories for status indication.\n */\nexport enum PainLevel {\n /** Minimal pain (0-20) - no significant effects */\n MINIMAL = \"minimal\",\n /** Moderate pain (20-40) - slight performance reduction (-10%) */\n MODERATE = \"moderate\",\n /** Significant pain (40-60) - noticeable impairment (-20%) */\n SIGNIFICANT = \"significant\",\n /** Severe pain (60-80) - major limitations (-35%) */\n SEVERE = \"severe\",\n /** Pain overload (80-100) - chance of stun/unconsciousness, extreme impairment (-50%) */\n OVERLOAD = \"overload\",\n}\n\n/**\n * Shock pain effect from sudden trauma.\n * \n * **Korean**: 충격통 효과 (Shock Pain Effect)\n * \n * Represents instant reaction to damage affecting all abilities for 2-3 seconds.\n */\nexport interface ShockPainEffect {\n /** Intensity of shock pain (0.1-0.3 for 10-30% reduction) */\n readonly intensity: number;\n /** Duration remaining in milliseconds (2000-3000ms) */\n readonly duration: number;\n /** Timestamp when shock pain was applied */\n readonly startTime: number;\n /** Damage value that caused the shock */\n readonly causedByDamage: number;\n}\n\n/**\n * Effects of pain on combat performance.\n */\ninterface PainEffects {\n /** Pain value range */\n readonly range: readonly [number, number];\n /** Overall performance penalty multiplier */\n readonly performancePenalty: number;\n /** Accuracy reduction */\n readonly accuracyReduction: number;\n /** Damage output reduction */\n readonly damageReduction: number;\n /** Movement speed reduction */\n readonly speedReduction: number;\n /** Chance of involuntary stun (0-1) */\n readonly stunChance: number;\n}\n\n/**\n * Pain Response System managing cumulative pain effects.\n * \n * Pain accumulates from all damage sources and reduces combat effectiveness\n * progressively. High pain can incapacitate even if health remains adequate.\n * Pain dissipates slowly over time.\n * \n * @example\n * ```typescript\n * const painSystem = new PainResponseSystem();\n * \n * // Apply pain from damage\n * const newPlayer = painSystem.applyPain(player, 15, VitalPointSeverity.MAJOR);\n * \n * // Check pain level\n * const level = painSystem.getPainLevel(newPlayer.pain);\n * \n * // Apply dissipation over time\n * const recovered = painSystem.applyDissipation(newPlayer, 1000);\n * ```\n * \n * @public\n * @korean 고통반응시스템\n */\nexport class PainResponseSystem {\n /**\n * Pain level effects and thresholds matching requirements.\n */\n private readonly painEffects: Record<PainLevel, PainEffects> = {\n [PainLevel.MINIMAL]: {\n range: [0, 20],\n performancePenalty: 0.0,\n accuracyReduction: 0.0,\n damageReduction: 0.0,\n speedReduction: 0.0,\n stunChance: 0.0,\n },\n [PainLevel.MODERATE]: {\n range: [20, 40],\n performancePenalty: 0.1, // -10% overall\n accuracyReduction: 0.1,\n damageReduction: 0.1,\n speedReduction: 0.05,\n stunChance: 0.0,\n },\n [PainLevel.SIGNIFICANT]: {\n range: [40, 60],\n performancePenalty: 0.2, // -20% overall\n accuracyReduction: 0.2,\n damageReduction: 0.2,\n speedReduction: 0.15,\n stunChance: 0.05, // 5% chance\n },\n [PainLevel.SEVERE]: {\n range: [60, 80],\n performancePenalty: 0.35, // -35% overall\n accuracyReduction: 0.35,\n damageReduction: 0.35,\n speedReduction: 0.25,\n stunChance: 0.15, // 15% chance\n },\n [PainLevel.OVERLOAD]: {\n range: [80, 100],\n performancePenalty: 0.5, // -50% overall\n accuracyReduction: 0.5,\n damageReduction: 0.5,\n speedReduction: 0.4,\n stunChance: 0.3, // 30% chance per hit at pain >80\n },\n };\n\n /**\n * Pain multipliers by vital point severity.\n */\n private readonly severityMultipliers: Record<VitalPointSeverity, number> = {\n [VitalPointSeverity.MINOR]: 0.5,\n [VitalPointSeverity.MODERATE]: 1.0,\n [VitalPointSeverity.MAJOR]: 1.5,\n [VitalPointSeverity.CRITICAL]: 2.0,\n [VitalPointSeverity.LETHAL]: 3.0,\n };\n\n /**\n * Pain multipliers by vital point category.\n */\n private readonly categoryMultipliers: Record<string, number> = {\n [VitalPointCategory.NEUROLOGICAL]: 2.5, // Nerve strikes cause intense pain\n [VitalPointCategory.VASCULAR]: 1.5, // Blood flow restriction\n [VitalPointCategory.RESPIRATORY]: 2.0, // Breathing difficulty causes panic\n [VitalPointCategory.SKELETAL]: 1.8, // Bone/structural damage\n [VitalPointCategory.MUSCULAR]: 1.4, // Muscle trauma\n [VitalPointCategory.ORGAN]: 2.2, // Internal organ trauma\n [VitalPointCategory.JOINT]: 1.9, // Joint damage\n [VitalPointCategory.CIRCULATORY]: 1.6, // Circulatory disruption\n default: 1.0, // Standard damage\n };\n\n /**\n * Base pain dissipation rate per second (requirement: -5 pain/second).\n */\n private readonly BASE_DISSIPATION_RATE = 5.0; // -5 pain per second\n\n /**\n * Shock pain duration range in milliseconds (requirement: 2-3 seconds).\n */\n private readonly SHOCK_PAIN_DURATION = {\n min: 2000, // 2 seconds\n max: 3000, // 3 seconds\n };\n\n /**\n * Minimum damage to trigger shock pain.\n */\n private readonly SHOCK_PAIN_THRESHOLD = 10;\n\n /**\n * Pain overload threshold for stun chance (requirement: >80).\n */\n private readonly PAIN_OVERLOAD_THRESHOLD = 80;\n\n /**\n * Shock pain intensity constants.\n */\n private readonly MAX_SHOCK_INTENSITY = 0.3; // 30% max reduction\n private readonly MIN_SHOCK_INTENSITY = 0.1; // 10% min reduction\n private readonly DAMAGE_SCALE_FACTOR = 100; // for normalizing damage to 0-1 range\n private readonly INTENSITY_RANGE = 0.2; // 20% variable range\n\n /**\n * Pain to damage ratio for cumulative trauma calculation.\n */\n private readonly PAIN_TO_DAMAGE_RATIO = 0.5;\n\n /**\n * Applies pain from combat damage with shock pain effects.\n * \n * Calculates pain increase based on damage amount, vital point severity,\n * and vital point category. Also determines if shock pain effect should be triggered.\n * \n * **Shock Pain**: Instant 10-30% reduction for 2-3 seconds on significant hits (>=10 damage)\n * **Cumulative Trauma**: Progressive pain accumulation that reduces performance\n * **Pain Overload**: At >80 pain, chance of stun/unconsciousness\n * \n * @param player - Current player state\n * @param damage - Base damage amount\n * @param severity - Optional vital point severity\n * @param category - Optional vital point category for more accurate pain calculation\n * @returns Updated player state with increased pain and optional shock effect\n * \n * @example\n * ```typescript\n * // Normal hit\n * const result = system.applyPain(player, 10);\n * \n * // Vital point neurological hit with shock pain\n * const result = system.applyPain(\n * player, \n * 20, \n * VitalPointSeverity.MAJOR,\n * VitalPointCategory.NEUROLOGICAL\n * );\n * if (result.shockEffect) {\n * console.log(`Shock pain active for ${result.shockEffect.duration}ms`);\n * }\n * ```\n * \n * @public\n * @korean 고통적용\n */\n applyPain(\n player: PlayerState,\n damage: number,\n severity?: VitalPointSeverity,\n category?: VitalPointCategory\n ): { player: PlayerState; shockEffect?: ShockPainEffect } {\n // Get multipliers based on severity and category\n const severityMultiplier = severity\n ? this.severityMultipliers[severity]\n : 1.0;\n \n const categoryMultiplier = category\n ? this.categoryMultipliers[category] ?? this.categoryMultipliers.default\n : this.categoryMultipliers.default;\n\n // Calculate pain increase (cumulative trauma)\n const painIncrease = damage * severityMultiplier * categoryMultiplier * this.PAIN_TO_DAMAGE_RATIO;\n\n // Apply pain, clamped to 0-100\n const newPain = Math.max(0, Math.min(100, player.pain + painIncrease));\n\n let shockEffect: ShockPainEffect | undefined;\n\n // Trigger shock pain for significant hits (>=10 damage)\n if (damage >= this.SHOCK_PAIN_THRESHOLD) {\n // Calculate shock intensity: 10-30% reduction based on damage\n const shockIntensity = Math.min(\n this.MAX_SHOCK_INTENSITY,\n this.MIN_SHOCK_INTENSITY + (damage / this.DAMAGE_SCALE_FACTOR) * this.INTENSITY_RANGE\n );\n \n // Random duration between 2-3 seconds\n const shockDuration =\n this.SHOCK_PAIN_DURATION.min +\n Math.random() *\n (this.SHOCK_PAIN_DURATION.max - this.SHOCK_PAIN_DURATION.min);\n\n shockEffect = {\n intensity: shockIntensity,\n duration: shockDuration,\n startTime: Date.now(),\n causedByDamage: damage,\n };\n }\n\n const updatedPlayer: PlayerState = {\n ...player,\n pain: newPain,\n };\n\n return { player: updatedPlayer, shockEffect };\n }\n\n /**\n * Applies pain dissipation over time (requirement: -5 pain/second).\n * \n * Pain naturally dissipates when not taking damage at a constant rate\n * of -5 pain per second.\n * \n * @param player - Current player state\n * @param deltaTime - Time elapsed in milliseconds\n * @returns Updated player state with reduced pain\n * \n * @example\n * ```typescript\n * // In game loop (~60fps, 16ms per frame)\n * player = system.applyDissipation(player, 16);\n * ```\n * \n * @public\n * @korean 고통감소\n */\n applyDissipation(player: PlayerState, deltaTime: number): PlayerState {\n // Already at zero pain\n if (player.pain <= 0) {\n return player;\n }\n\n const deltaSeconds = deltaTime / 1000;\n \n // Constant dissipation rate of -5 pain/second\n const dissipation = this.BASE_DISSIPATION_RATE * deltaSeconds;\n const newPain = Math.max(0, player.pain - dissipation);\n\n return {\n ...player,\n pain: newPain,\n };\n }\n\n /**\n * Determines pain level from pain value.\n * \n * @param pain - Pain value (0-100)\n * @returns Current pain level\n * \n * @public\n * @korean 고통수준확인\n */\n getPainLevel(pain: number): PainLevel {\n if (pain >= 80) return PainLevel.OVERLOAD;\n if (pain >= 60) return PainLevel.SEVERE;\n if (pain >= 40) return PainLevel.SIGNIFICANT;\n if (pain >= 20) return PainLevel.MODERATE;\n return PainLevel.MINIMAL;\n }\n\n /**\n * Gets effects for a specific pain level.\n * \n * @param level - Pain level\n * @returns Effects applied at that level\n * \n * @public\n * @korean 고통효과\n */\n getEffects(level: PainLevel): PainEffects {\n return this.painEffects[level];\n }\n\n /**\n * Applies pain effects to player state, including shock pain.\n * \n * Modifies player stats based on current pain level. When shock pain\n * is active, additional penalties are applied temporarily.\n * \n * @param player - Current player state\n * @param shockEffect - Optional active shock pain effect\n * @returns Modified player state with pain effects\n * \n * @public\n * @korean 고통효과적용\n */\n applyEffects(\n player: PlayerState,\n shockEffect?: ShockPainEffect\n ): PlayerState {\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Calculate total penalties (base pain + shock pain if active)\n let totalAccuracyPenalty = effects.accuracyReduction;\n let totalDamagePenalty = effects.damageReduction;\n\n // Apply shock pain if still active\n if (shockEffect) {\n const elapsed = Date.now() - shockEffect.startTime;\n if (elapsed < shockEffect.duration) {\n // Add shock pain intensity to penalties\n totalAccuracyPenalty = Math.min(\n 1.0,\n totalAccuracyPenalty + shockEffect.intensity\n );\n totalDamagePenalty = Math.min(\n 1.0,\n totalDamagePenalty + shockEffect.intensity\n );\n }\n }\n\n return {\n ...player,\n attackPower: Math.floor(\n player.attackPower * (1 - totalDamagePenalty)\n ),\n defense: Math.floor(\n player.defense * (1 - effects.performancePenalty)\n ),\n speed: Math.floor(\n player.speed * (1 - effects.speedReduction)\n ),\n technique: Math.floor(\n player.technique * (1 - totalAccuracyPenalty)\n ),\n };\n }\n\n /**\n * Checks if pain overload should trigger stun.\n * \n * At pain >80, there's a chance per hit to trigger stun based on pain level.\n * Pain Overload level has 30% chance to trigger stun.\n * \n * @param player - Current player state\n * @returns True if player should be stunned from pain\n * \n * @public\n * @korean 고통기절확인\n */\n shouldTriggerStun(player: PlayerState): boolean {\n if (player.pain < this.PAIN_OVERLOAD_THRESHOLD) {\n return false;\n }\n\n const level = this.getPainLevel(player.pain);\n const effects = this.getEffects(level);\n\n // Roll for stun chance\n return Math.random() < effects.stunChance;\n }\n\n /**\n * Checks if player is in pain overload state (>80 pain).\n * \n * @param player - Current player state\n * @returns True if pain is above overload threshold\n * \n * @public\n * @korean 고통과부하확인\n */\n isInPainOverload(player: PlayerState): boolean {\n return player.pain >= this.PAIN_OVERLOAD_THRESHOLD;\n }\n\n /**\n * Checks if player is incapacitated by pain.\n * \n * @param player - Current player state\n * @returns True if pain is at overload level\n * \n * @public\n * @korean 고통무력화확인\n */\n isIncapacitated(player: PlayerState): boolean {\n return this.getPainLevel(player.pain) === PainLevel.OVERLOAD;\n }\n\n /**\n * Gets bilingual name for pain level.\n * \n * @param level - Pain level\n * @returns Korean and English level names\n * \n * @public\n * @korean 고통이름\n */\n getLevelName(level: PainLevel): { korean: string; english: string } {\n const names: Record<PainLevel, { korean: string; english: string }> = {\n [PainLevel.MINIMAL]: {\n korean: \"최소\",\n english: \"Minimal\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통\",\n english: \"Moderate\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당\",\n english: \"Significant\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각\",\n english: \"Severe\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"과부하\",\n english: \"Overload\",\n },\n };\n\n return names[level];\n }\n\n /**\n * Gets description of pain level effects.\n * \n * @param level - Pain level\n * @returns Bilingual description\n * \n * @public\n * @korean 고통설명\n */\n getLevelDescription(level: PainLevel): {\n korean: string;\n english: string;\n } {\n const descriptions: Record<\n PainLevel,\n { korean: string; english: string }\n > = {\n [PainLevel.MINIMAL]: {\n korean: \"최소한의 고통, 전투 능력에 영향 없음\",\n english: \"Minimal pain, no significant effects on combat\",\n },\n [PainLevel.MODERATE]: {\n korean: \"보통 고통, 약간의 능력 저하 (-10%)\",\n english: \"Moderate pain, slight performance reduction (-10%)\",\n },\n [PainLevel.SIGNIFICANT]: {\n korean: \"상당한 고통, 명확한 손상 (-20%)\",\n english: \"Significant pain, noticeable impairment (-20%)\",\n },\n [PainLevel.SEVERE]: {\n korean: \"심각한 고통, 주요 제한 (-35%)\",\n english: \"Severe pain, major limitations (-35%)\",\n },\n [PainLevel.OVERLOAD]: {\n korean: \"고통 과부하, 기절 위험 (-50%, 30% 기절 확률)\",\n english: \"Pain overload, stun risk (-50%, 30% stun chance)\",\n },\n };\n\n return descriptions[level];\n }\n}\n\nexport default PainResponseSystem;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effects.js","names":[],"sources":["../../src/systems/effects.ts"],"sourcesContent":["// Import enums from enums.ts\n\n// Combat effects and status system for Korean martial arts\n\n// Hit effect for visual feedback\n// Hit effect types\n\nexport enum HitEffectType {\n GENERAL_DAMAGE = \"general_damage\",\n CRITICAL_HIT = \"critical_hit\",\n VITAL_POINT_STRIKE = \"vital_point_strike\",\n STATUS_EFFECT = \"status_effect\",\n MISS = \"miss\",\n BLOCK = \"block\",\n PARRY = \"parry\",\n COUNTER = \"counter\",\n HIT = \"hit\",\n}\n\n// Effect types for status effects\nexport enum HitEffectEnum {\n STUN = \"stun\",\n WEAKNESS = \"weakness\",\n STAMINA_DRAIN = \"stamina_drain\",\n VULNERABILITY = \"vulnerability\",\n BLEEDING = \"bleeding\",\n BUFF = \"buff\",\n DEBUFF = \"debuff\",\n PARALYSIS = \"paralysis\",\n POISON = \"poison\",\n BURN = \"burn\",\n FREEZE = \"freeze\",\n CONFUSION = \"confusion\",\n}\n\n// Effect intensity levels\nexport enum EffectIntensity {\n WEAK = \"weak\",\n MINOR = \"minor\",\n LOW = \"low\",\n MEDIUM = \"medium\",\n MODERATE = \"moderate\",\n HIGH = \"high\",\n SEVERE = \"severe\",\n CRITICAL = \"critical\",\n EXTREME = \"extreme\",\n}\n\n// Status effects that can be applied to players\n// Effect types\nexport type EffectType =\n | \"stun\"\n | \"poison\"\n | \"burn\"\n | \"bleed\"\n | \"exhausted\"\n | \"focused\"\n | \"rage\"\n | \"defensive\"\n | \"weakened\"\n | \"strengthened\"\n | \"paralysis\" // Add missing paralysis\n | \"confusion\" // Add missing confusion\n | \"vulnerability\" // Add missing vulnerability\n | \"stamina_drain\"; // Add missing stamina_drain\n\n// Particle effect for visual feedback\n// Particle effect types\nexport type ParticleType =\n | \"spark\"\n | \"blood\"\n | \"energy\"\n | \"dust\"\n | \"flash\"\n | \"smoke\"\n | \"lightning\"\n | \"wind\";\n\n// Environmental effect\n// Environmental effect types\nexport type EnvironmentalEffectType =\n | \"smoke\"\n | \"fire\"\n | \"ice\"\n | \"wind\"\n | \"lightning\"\n | \"darkness\"\n | \"light\"\n | \"pressure\";\n"],"mappings":";AAOA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,
|
|
1
|
+
{"version":3,"file":"effects.js","names":[],"sources":["../../src/systems/effects.ts"],"sourcesContent":["// Import enums from enums.ts\n\n// Combat effects and status system for Korean martial arts\n\n// Hit effect for visual feedback\n// Hit effect types\n\nexport enum HitEffectType {\n GENERAL_DAMAGE = \"general_damage\",\n CRITICAL_HIT = \"critical_hit\",\n VITAL_POINT_STRIKE = \"vital_point_strike\",\n STATUS_EFFECT = \"status_effect\",\n MISS = \"miss\",\n BLOCK = \"block\",\n PARRY = \"parry\",\n COUNTER = \"counter\",\n HIT = \"hit\",\n}\n\n// Effect types for status effects\nexport enum HitEffectEnum {\n STUN = \"stun\",\n WEAKNESS = \"weakness\",\n STAMINA_DRAIN = \"stamina_drain\",\n VULNERABILITY = \"vulnerability\",\n BLEEDING = \"bleeding\",\n BUFF = \"buff\",\n DEBUFF = \"debuff\",\n PARALYSIS = \"paralysis\",\n POISON = \"poison\",\n BURN = \"burn\",\n FREEZE = \"freeze\",\n CONFUSION = \"confusion\",\n}\n\n// Effect intensity levels\nexport enum EffectIntensity {\n WEAK = \"weak\",\n MINOR = \"minor\",\n LOW = \"low\",\n MEDIUM = \"medium\",\n MODERATE = \"moderate\",\n HIGH = \"high\",\n SEVERE = \"severe\",\n CRITICAL = \"critical\",\n EXTREME = \"extreme\",\n}\n\n// Status effects that can be applied to players\n// Effect types\nexport type EffectType =\n | \"stun\"\n | \"poison\"\n | \"burn\"\n | \"bleed\"\n | \"exhausted\"\n | \"focused\"\n | \"rage\"\n | \"defensive\"\n | \"weakened\"\n | \"strengthened\"\n | \"paralysis\" // Add missing paralysis\n | \"confusion\" // Add missing confusion\n | \"vulnerability\" // Add missing vulnerability\n | \"stamina_drain\"; // Add missing stamina_drain\n\n// Particle effect for visual feedback\n// Particle effect types\nexport type ParticleType =\n | \"spark\"\n | \"blood\"\n | \"energy\"\n | \"dust\"\n | \"flash\"\n | \"smoke\"\n | \"lightning\"\n | \"wind\";\n\n// Environmental effect\n// Environmental effect types\nexport type EnvironmentalEffectType =\n | \"smoke\"\n | \"fire\"\n | \"ice\"\n | \"wind\"\n | \"lightning\"\n | \"darkness\"\n | \"light\"\n | \"pressure\";\n"],"mappings":";AAOA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,oBAAiB;AACjB,eAAA,kBAAe;AACf,eAAA,wBAAqB;AACrB,eAAA,mBAAgB;AAChB,eAAA,UAAO;AACP,eAAA,WAAQ;AACR,eAAA,WAAQ;AACR,eAAA,aAAU;AACV,eAAA,SAAM;;KACP;AAGD,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,UAAO;AACP,eAAA,cAAW;AACX,eAAA,mBAAgB;AAChB,eAAA,mBAAgB;AAChB,eAAA,cAAW;AACX,eAAA,UAAO;AACP,eAAA,YAAS;AACT,eAAA,eAAY;AACZ,eAAA,YAAS;AACT,eAAA,UAAO;AACP,eAAA,YAAS;AACT,eAAA,eAAY;;KACb;AAGD,IAAY,kBAAL,yBAAA,iBAAA;AACL,iBAAA,UAAO;AACP,iBAAA,WAAQ;AACR,iBAAA,SAAM;AACN,iBAAA,YAAS;AACT,iBAAA,cAAW;AACX,iBAAA,UAAO;AACP,iBAAA,YAAS;AACT,iBAAA,cAAW;AACX,iBAAA,aAAU;;KACX"}
|
package/lib/systems/game.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game.js","names":[],"sources":["../../src/systems/game.ts"],"sourcesContent":["// Core game state and flow management\n\nimport { GameMode, KoreanText } from \"@/types\";\nimport type { PlayerState } from \"./player\";\n\n// Match configuration\nexport interface MatchConfig {\n readonly mode: GameMode;\n readonly rounds: number;\n readonly roundDuration: number; // seconds\n readonly player1Archetype: string;\n readonly player2Archetype: string;\n readonly stage: string;\n readonly difficulty?: \"easy\" | \"medium\" | \"hard\" | \"expert\";\n}\n\n// Game event system\nexport interface GameEvent {\n readonly id: string;\n readonly type: GameEventType;\n readonly timestamp: number;\n readonly playerId?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Event data can contain various types depending on event type\n readonly data: Record<string, any>;\n readonly message?: KoreanText;\n}\n\n// Game event types\nexport enum GameEventType {\n GAME_START = \"game_start\",\n ROUND_START = \"round_start\",\n ROUND_END = \"round_end\",\n MATCH_END = \"match_end\",\n PLAYER_ATTACK = \"player_attack\",\n PLAYER_HIT = \"player_hit\",\n STANCE_CHANGE = \"stance_change\",\n TECHNIQUE_EXECUTE = \"technique_execute\",\n VITAL_POINT_HIT = \"vital_point_hit\",\n STATUS_EFFECT = \"status_effect\",\n PAUSE_TOGGLE = \"pause_toggle\",\n ERROR = \"error\",\n}\n\n// Game session interface\nexport interface GameSession {\n readonly id: string;\n readonly gameMode: GameMode;\n readonly players: readonly [PlayerState, PlayerState];\n readonly currentRound: number;\n readonly maxRounds: number;\n readonly roundTimeLimit: number;\n readonly timeRemaining: number;\n readonly isPaused: boolean;\n readonly isGameOver: boolean;\n readonly winner: PlayerState | null;\n}\n\n// Game configuration\nexport interface GameConfig {\n readonly maxHealth: number;\n readonly maxKi: number;\n readonly maxStamina: number;\n readonly roundDuration: number;\n readonly maxRounds: number;\n readonly difficulty: \"beginner\" | \"intermediate\" | \"expert\" | \"master\";\n readonly enableVitalPoints: boolean;\n readonly enableStatusEffects: boolean;\n readonly allowArchetypeSwitching: boolean;\n}\n\n// Game save data\nexport interface GameSaveData {\n readonly version: string;\n readonly playerId: string;\n readonly playerProgress: {\n readonly archetypeExperience: Record<string, number>;\n readonly unlockedTechniques: readonly string[];\n readonly achievements: readonly string[];\n };\n readonly settings: {\n readonly volume: number;\n readonly difficulty: string;\n readonly controls: Record<string, string>;\n };\n readonly statistics: {\n readonly totalMatches: number;\n readonly wins: number;\n readonly losses: number;\n readonly favoriteArchetype: string;\n };\n}\n"],"mappings":";AA4BA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,
|
|
1
|
+
{"version":3,"file":"game.js","names":[],"sources":["../../src/systems/game.ts"],"sourcesContent":["// Core game state and flow management\n\nimport { GameMode, KoreanText } from \"@/types\";\nimport type { PlayerState } from \"./player\";\n\n// Match configuration\nexport interface MatchConfig {\n readonly mode: GameMode;\n readonly rounds: number;\n readonly roundDuration: number; // seconds\n readonly player1Archetype: string;\n readonly player2Archetype: string;\n readonly stage: string;\n readonly difficulty?: \"easy\" | \"medium\" | \"hard\" | \"expert\";\n}\n\n// Game event system\nexport interface GameEvent {\n readonly id: string;\n readonly type: GameEventType;\n readonly timestamp: number;\n readonly playerId?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Event data can contain various types depending on event type\n readonly data: Record<string, any>;\n readonly message?: KoreanText;\n}\n\n// Game event types\nexport enum GameEventType {\n GAME_START = \"game_start\",\n ROUND_START = \"round_start\",\n ROUND_END = \"round_end\",\n MATCH_END = \"match_end\",\n PLAYER_ATTACK = \"player_attack\",\n PLAYER_HIT = \"player_hit\",\n STANCE_CHANGE = \"stance_change\",\n TECHNIQUE_EXECUTE = \"technique_execute\",\n VITAL_POINT_HIT = \"vital_point_hit\",\n STATUS_EFFECT = \"status_effect\",\n PAUSE_TOGGLE = \"pause_toggle\",\n ERROR = \"error\",\n}\n\n// Game session interface\nexport interface GameSession {\n readonly id: string;\n readonly gameMode: GameMode;\n readonly players: readonly [PlayerState, PlayerState];\n readonly currentRound: number;\n readonly maxRounds: number;\n readonly roundTimeLimit: number;\n readonly timeRemaining: number;\n readonly isPaused: boolean;\n readonly isGameOver: boolean;\n readonly winner: PlayerState | null;\n}\n\n// Game configuration\nexport interface GameConfig {\n readonly maxHealth: number;\n readonly maxKi: number;\n readonly maxStamina: number;\n readonly roundDuration: number;\n readonly maxRounds: number;\n readonly difficulty: \"beginner\" | \"intermediate\" | \"expert\" | \"master\";\n readonly enableVitalPoints: boolean;\n readonly enableStatusEffects: boolean;\n readonly allowArchetypeSwitching: boolean;\n}\n\n// Game save data\nexport interface GameSaveData {\n readonly version: string;\n readonly playerId: string;\n readonly playerProgress: {\n readonly archetypeExperience: Record<string, number>;\n readonly unlockedTechniques: readonly string[];\n readonly achievements: readonly string[];\n };\n readonly settings: {\n readonly volume: number;\n readonly difficulty: string;\n readonly controls: Record<string, string>;\n };\n readonly statistics: {\n readonly totalMatches: number;\n readonly wins: number;\n readonly losses: number;\n readonly favoriteArchetype: string;\n };\n}\n"],"mappings":";AA4BA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,gBAAa;AACb,eAAA,iBAAc;AACd,eAAA,eAAY;AACZ,eAAA,eAAY;AACZ,eAAA,mBAAgB;AAChB,eAAA,gBAAa;AACb,eAAA,mBAAgB;AAChB,eAAA,uBAAoB;AACpB,eAAA,qBAAkB;AAClB,eAAA,mBAAgB;AAChB,eAAA,kBAAe;AACf,eAAA,WAAQ;;KACT"}
|