/** * 战斗系统 - AP/EP命中计算、三层防御、战斗tick处理 * Phase 6 核心系统实现 */ import { LOCATION_CONFIG } from '@/config/locations.js' /** * 计算AP(攻击点数) * AP = 灵巧 + 智力*0.2 + 技能加成 + 装备加成 + 姿态加成 - 敌人数惩罚 * * @param {Object} attacker - 攻击者对象(玩家或敌人) * @param {String} stance - 战斗姿态 'attack' | 'defense' | 'balance' | 'rapid' | 'heavy' * @param {Number} enemyCount - 敌人数量(用于计算多敌人惩罚) * @param {String} environment - 环境类型 'normal' | 'dark' | 'narrow' | 'harsh' * @param {Object} bonuses - 额外加成 { skillBonus: number, equipBonus: number, darkPenaltyReduce: number } * @returns {Number} AP值 */ export function calculateAP(attacker, stance = 'balance', enemyCount = 1, environment = 'normal', bonuses = {}) { const { skillBonus = 0, equipBonus = 0, darkPenaltyReduce = 0 } = bonuses // 基础AP = 灵巧 + 智力 * 0.2 let ap = (attacker.baseStats?.dexterity || 0) + (attacker.baseStats?.intuition || 0) * 0.2 // 技能加成 ap += skillBonus // 装备加成 ap += equipBonus // 姿态加成 const stanceModifiers = { attack: 1.1, // 攻击姿态: AP +10% defense: 0.9, // 防御姿态: AP -10% balance: 1.0, // 平衡姿态: 无加成 rapid: 1.05, // 快速打击: AP +5% heavy: 1.15 // 重击: AP +15% } ap *= stanceModifiers[stance] || 1.0 // 环境惩罚 if (environment === 'dark') { // 黑暗环境 AP -20%,可被夜视技能减少 const darkPenalty = 0.2 * (1 - darkPenaltyReduce / 100) ap *= (1 - darkPenalty) } else if (environment === 'narrow') { // 狭窄空间对AP无直接影响 ap *= 1.0 } else if (environment === 'harsh') { // 恶劣环境 AP -10% ap *= 0.9 } // 多敌人惩罚(每多1个敌人,AP -5%,最多-30%) if (enemyCount > 1) { const penalty = Math.min(0.3, (enemyCount - 1) * 0.05) ap *= (1 - penalty) } // 耐力惩罚 if (attacker.currentStats && attacker.currentStats.stamina !== undefined) { const staminaRatio = attacker.currentStats.stamina / (attacker.currentStats.maxStamina || 100) if (staminaRatio === 0) { ap *= 0.5 // 耐力耗尽,AP减半 } else if (staminaRatio < 0.5) { // 耐力低于50%时逐渐降低 ap *= (0.5 + staminaRatio * 0.5) } } return Math.max(1, Math.floor(ap)) } /** * 计算EP(闪避点数) * EP = 敏捷 + 智力*0.2 + 技能加成 + 装备加成 + 姿态加成 - 环境惩罚 * * @param {Object} defender - 防御者对象(玩家或敌人) * @param {String} stance - 战斗姿态 'attack' | 'defense' | 'balance' | 'rapid' | 'heavy' * @param {String} environment - 环境类型 * @param {Object} bonuses - 额外加成 * @returns {Number} EP值 */ export function calculateEP(defender, stance = 'balance', environment = 'normal', bonuses = {}) { const { skillBonus = 0, equipBonus = 0, darkPenaltyReduce = 0 } = bonuses // 基础EP = 敏捷 + 智力 * 0.2 let ep = (defender.baseStats?.agility || 0) + (defender.baseStats?.intuition || 0) * 0.2 // 技能加成 ep += skillBonus // 装备加成 ep += equipBonus // 姿态加成 const stanceModifiers = { attack: 0.8, // 攻击姿态: EP -20% defense: 1.2, // 防御姿态: EP +20% balance: 1.0, // 平衡姿态: 无加成 rapid: 0.7, // 快速打击: EP -30% heavy: 0.85 // 重击: EP -15% } ep *= stanceModifiers[stance] || 1.0 // 环境惩罚 if (environment === 'dark') { // 黑暗环境 EP -20%,可被夜视技能减少 const darkPenalty = 0.2 * (1 - darkPenaltyReduce / 100) ep *= (1 - darkPenalty) } else if (environment === 'narrow') { // 狭窄空间 EP -30% ep *= 0.7 } else if (environment === 'harsh') { // 恶劣环境 EP -10% ep *= 0.9 } // 耐力惩罚 if (defender.currentStats && defender.currentStats.stamina !== undefined) { const staminaRatio = defender.currentStats.stamina / (defender.currentStats.maxStamina || 100) if (staminaRatio === 0) { ep *= 0.5 } else if (staminaRatio < 0.5) { ep *= (0.5 + staminaRatio * 0.5) } } return Math.max(1, Math.floor(ep)) } /** * 计算命中概率(非线性公式) * 基于Yet Another Idle RPG的设计 * * @param {Number} ap - 攻击点数 * @param {Number} ep - 闪避点数 * @param {Boolean} isPlayer - 是否是玩家攻击(玩家享受最低命中率加成) * @returns {Number} 命中概率 (0-1) */ export function calculateHitRate(ap, ep, isPlayer = false) { const ratio = ap / (ep + 1) // 非线性命中概率表 let hitRate = 0.05 if (ratio >= 5) hitRate = 0.98 else if (ratio >= 3) hitRate = 0.90 else if (ratio >= 2) hitRate = 0.80 else if (ratio >= 1.5) hitRate = 0.65 else if (ratio >= 1) hitRate = 0.50 else if (ratio >= 0.75) hitRate = 0.38 else if (ratio >= 0.5) hitRate = 0.24 else if (ratio >= 0.33) hitRate = 0.15 else if (ratio >= 0.2) hitRate = 0.08 // 最低命中率:敌人攻击玩家时至少40%命中率,玩家攻击敌人时至少15% const minHitRate = isPlayer ? 0.15 : 0.40 return Math.max(minHitRate, hitRate) } /** * 计算暴击概率 * @param {Object} attacker - 攻击者 * @param {Object} bonuses - 额外加成 * @returns {Number} 暴击概率 (0-1) */ export function calculateCritRate(attacker, bonuses = {}) { const { critRateBonus = 0 } = bonuses // 基础暴击率 = 灵巧 / 100 + 全局加成 const baseCritRate = (attacker.baseStats?.dexterity || 0) / 100 const globalBonus = attacker.globalBonus?.critRate || 0 return Math.min(0.8, baseCritRate + (globalBonus + critRateBonus) / 100) } /** * 计算暴击伤害倍数 * @param {Object} attacker - 攻击者 * @returns {Number} 暴击倍数 */ export function calculateCritMultiplier(attacker) { const baseMult = 1.5 const bonusMult = attacker.globalBonus?.critMult || 0 return baseMult + bonusMult } /** * 处理单次攻击(三层防御机制) * 1. 闪避判定 * 2. 护盾吸收 * 3. 防御力减少 * * @param {Object} attacker - 攻击者对象 * @param {Object} defender - 防御者对象 * @param {String} stance - 攻击者姿态 * @param {String} environment - 环境类型 * @param {Object} attackerBonuses - 攻击者加成 * @param {Object} defenderBonuses - 防御者加成 * @returns {Object} { hit: boolean, damage: number, crit: boolean, evaded: boolean, shieldAbsorbed: number } */ export function processAttack(attacker, defender, stance = 'balance', environment = 'normal', attackerBonuses = {}, defenderBonuses = {}, isPlayerAttacker = false) { // 计算AP和EP const ap = calculateAP(attacker, stance, 1, environment, attackerBonuses) const ep = calculateEP(defender, 'balance', environment, defenderBonuses) // 计算命中概率(玩家攻击时isPlayerAttacker=true,敌人攻击时isPlayerAttacker=false) const hitRate = calculateHitRate(ap, ep, isPlayerAttacker) // 第一步:闪避判定 const roll = Math.random() if (roll > hitRate) { return { hit: false, damage: 0, crit: false, evaded: true, shieldAbsorbed: 0, blocked: false } } // 命中成功,计算伤害 let damage = attacker.baseStats?.attack || attacker.attack || 0 // 武器伤害加成(如果装备了武器) if (attacker.weaponDamage) { damage += attacker.weaponDamage } // 姿态对伤害的影响 const stanceDamageMod = { attack: 1.5, // 攻击姿态: 伤害 +50% defense: 0.7, // 防御姿态: 伤害 -30% balance: 1.0, rapid: 0.6, // 快速打击: 伤害 -40% heavy: 2.0 // 重击: 伤害 +100% } damage *= stanceDamageMod[stance] || 1.0 // 暴击判定 const critRate = calculateCritRate(attacker, attackerBonuses) const crit = Math.random() < critRate if (crit) { const critMult = calculateCritMultiplier(attacker) damage *= critMult } // 第二步:护盾吸收 let shieldAbsorbed = 0 const shield = defender.shield || defender.currentStats?.shield || 0 if (shield > 0) { const blockRate = defender.blockRate || 0 // 格挡率 const shouldBlock = Math.random() < blockRate if (shouldBlock) { // 护盾完全吸收伤害(可能还有剩余) shieldAbsorbed = Math.min(shield, damage) damage -= shieldAbsorbed // 更新护盾值 if (defender.currentStats) { defender.currentStats.shield = Math.max(0, shield - shieldAbsorbed) } if (damage <= 0) { return { hit: true, damage: 0, crit, evaded: false, shieldAbsorbed, blocked: true } } } } // 第三步:防御力减少 const defense = defender.baseStats?.defense || defender.defense || 0 // 计算减免后伤害 let reducedDamage = damage - defense // 最小伤害为原始伤害的10%(防御力最多减少90%) const minDamage = Math.max(1, damage * 0.1) reducedDamage = Math.max(minDamage, reducedDamage) return { hit: true, damage: Math.max(1, Math.floor(reducedDamage)), crit, evaded: false, shieldAbsorbed, blocked: false } } /** * 构建玩家战斗者对象(包含武器伤害) * @param {Object} playerStore - 玩家Store * @returns {Object} 战斗者对象 */ function buildPlayerAttacker(playerStore) { // 基础攻击力 = 力量 const baseAttack = playerStore.baseStats?.strength || 10 // 武器伤害 let weaponDamage = 0 const weapon = playerStore.equipment?.weapon if (weapon && weapon.baseDamage) { weaponDamage = weapon.baseDamage } // 技能加成(木棍精通等) const skillId = weapon?.type === 'weapon' ? getWeaponSkillId(weapon) : null let skillBonus = 0 if (skillId && playerStore.skills?.[skillId]?.level > 0) { // 每级增加5%武器伤害 skillBonus = Math.floor(weaponDamage * playerStore.skills[skillId].level * 0.05) } // 全局加成 const attackBonus = playerStore.globalBonus?.attackBonus || 0 return { ...playerStore, baseStats: { ...playerStore.baseStats, attack: baseAttack + attackBonus, dexterity: playerStore.baseStats?.dexterity || 8, intuition: playerStore.baseStats?.intuition || 10 }, weaponDamage: weaponDamage + skillBonus, attack: baseAttack + attackBonus, critRate: playerStore.globalBonus?.critRate || 0 } } /** * 根据武器获取对应的技能ID * @param {Object} weapon - 武器对象 * @returns {String|null} 技能ID */ function getWeaponSkillId(weapon) { if (weapon.id === 'wooden_stick') return 'stick_mastery' if (weapon.subtype === 'sword') return 'sword_mastery' if (weapon.subtype === 'axe') return 'axe_mastery' return null } /** * 处理战斗tick(每秒执行一次) * @param {Object} gameStore - 游戏Store * @param {Object} playerStore - 玩家Store * @param {Object} combatState - 战斗状态 * @returns {Object} { victory: boolean, defeat: boolean, continue: boolean, logs: Array } */ export function combatTick(gameStore, playerStore, combatState) { const logs = [] if (!combatState || !combatState.enemy) { return { victory: false, defeat: false, continue: false, logs: ['战斗状态异常'] } } const enemy = combatState.enemy const stance = combatState.stance || 'balance' const environment = combatState.environment || 'normal' // 获取环境惩罚减少(夜视技能) const darkPenaltyReduce = playerStore.globalBonus?.darkPenaltyReduce || 0 // 构建玩家战斗者对象(包含武器伤害) const playerAttacker = buildPlayerAttacker(playerStore) // 玩家攻击 const playerBonuses = { skillBonus: 0, // 已在 buildPlayerAttacker 中计算 equipBonus: 0, // 已在 buildPlayerAttacker 中计算 darkPenaltyReduce } const playerAttack = processAttack( playerAttacker, enemy, stance, environment, playerBonuses, {}, true // 玩家攻击 ) if (playerAttack.hit) { enemy.hp = Math.max(0, enemy.hp - playerAttack.damage) if (playerAttack.crit) { logs.push({ type: 'combat', message: `你暴击了!对${enemy.name}造成${playerAttack.damage}点伤害!`, highlight: true }) } else { logs.push({ type: 'combat', message: `你攻击${enemy.name},造成${playerAttack.damage}点伤害` }) } if (playerAttack.shieldAbsorbed > 0) { logs.push({ type: 'combat', message: `${enemy.name}的护盾吸收了${playerAttack.shieldAbsorbed}点伤害` }) } } else if (playerAttack.evaded) { logs.push({ type: 'combat', message: `你攻击${enemy.name},未命中!` }) } // 检查敌人是否死亡 if (enemy.hp <= 0) { return { victory: true, defeat: false, continue: false, logs, enemy } } // 敌人攻击 const enemyAttack = processAttack( enemy, playerStore, 'balance', environment, {}, playerBonuses, false // 敌人攻击 ) if (enemyAttack.hit) { playerStore.currentStats.health = Math.max( 0, playerStore.currentStats.health - enemyAttack.damage ) logs.push({ type: 'combat', message: `${enemy.name}攻击你,造成${enemyAttack.damage}点伤害` }) if (enemyAttack.crit) { logs.push({ type: 'combat', message: `${enemy.name}的攻击是暴击!`, highlight: true }) } } else if (enemyAttack.evaded) { logs.push({ type: 'combat', message: `${enemy.name}攻击你,你闪避了!` }) } // 检查玩家是否死亡 if (playerStore.currentStats.health <= 0) { return { victory: false, defeat: true, continue: false, logs } } // 消耗耐力 const staminaCost = getStaminaCost(stance) playerStore.currentStats.stamina = Math.max( 0, playerStore.currentStats.stamina - staminaCost ) return { victory: false, defeat: false, continue: true, logs, enemy } } /** * 获取姿态的耐力消耗 * @param {String} stance - 姿态 * @returns {Number} 每秒耐力消耗 */ export function getStaminaCost(stance) { const costs = { attack: 2, defense: 1, balance: 0.5, rapid: 3, heavy: 2.5 } return costs[stance] || 0.5 } /** * 初始化战斗状态 * @param {String} enemyId - 敌人ID * @param {Object} enemyConfig - 敌人配置 * @param {String} environment - 环境类型 * @param {String} preferredStance - 玩家偏好的战斗姿态 * @returns {Object} 战斗状态 */ export function initCombat(enemyId, enemyConfig, environment = 'normal', preferredStance = 'balance') { return { enemyId, enemy: { id: enemyId, name: enemyConfig.name, hp: enemyConfig.baseStats.health, maxHp: enemyConfig.baseStats.health, attack: enemyConfig.baseStats.attack, defense: enemyConfig.baseStats.defense, baseStats: enemyConfig.baseStats, expReward: enemyConfig.expReward, skillExpReward: enemyConfig.skillExpReward || 0, drops: enemyConfig.drops || [] }, stance: preferredStance, // 使用玩家偏好的战斗姿态 environment, startTime: Date.now(), ticks: 0 } } /** * 获取姿态显示名称 * @param {String} stance - 姿态 * @returns {String} 显示名称 */ export function getStanceDisplayName(stance) { const names = { attack: '攻击', defense: '防御', balance: '平衡', rapid: '快速', heavy: '重击' } return names[stance] || '平衡' } /** * 获取攻击速度修正 * @param {String} stance - 姿态 * @param {Number} baseSpeed - 基础攻击速度 * @returns {Number} 修正后的攻击速度 */ export function getAttackSpeed(stance, baseSpeed = 1.0) { const speedModifiers = { attack: 1.3, // 攻击姿态: 速度 +30% defense: 0.9, // 防御姿态: 速度 -10% balance: 1.0, rapid: 2.0, // 快速打击: 速度 +100% heavy: 0.5 // 重击: 速度 -50% } return baseSpeed * (speedModifiers[stance] || 1.0) } /** * 获取环境类型 * @param {String} locationId - 位置ID * @returns {String} 环境类型 */ export function getEnvironmentType(locationId) { const location = LOCATION_CONFIG[locationId] return location?.environment || 'normal' }