2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 战斗系统 - 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 - 闪避点数
|
2026-02-02 15:52:32 +08:00
|
|
|
|
* @param {Boolean} isPlayer - 是否是玩家攻击(玩家享受最低命中率加成)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
* @returns {Number} 命中概率 (0-1)
|
|
|
|
|
|
*/
|
2026-02-02 15:52:32 +08:00
|
|
|
|
export function calculateHitRate(ap, ep, isPlayer = false) {
|
2026-01-21 17:13:51 +08:00
|
|
|
|
const ratio = ap / (ep + 1)
|
|
|
|
|
|
|
|
|
|
|
|
// 非线性命中概率表
|
2026-02-02 15:52:32 +08:00
|
|
|
|
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)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算暴击概率
|
|
|
|
|
|
* @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 }
|
|
|
|
|
|
*/
|
2026-02-02 15:52:32 +08:00
|
|
|
|
export function processAttack(attacker, defender, stance = 'balance', environment = 'normal', attackerBonuses = {}, defenderBonuses = {}, isPlayerAttacker = false) {
|
2026-01-21 17:13:51 +08:00
|
|
|
|
// 计算AP和EP
|
|
|
|
|
|
const ap = calculateAP(attacker, stance, 1, environment, attackerBonuses)
|
|
|
|
|
|
const ep = calculateEP(defender, 'balance', environment, defenderBonuses)
|
|
|
|
|
|
|
2026-02-02 15:52:32 +08:00
|
|
|
|
// 计算命中概率(玩家攻击时isPlayerAttacker=true,敌人攻击时isPlayerAttacker=false)
|
|
|
|
|
|
const hitRate = calculateHitRate(ap, ep, isPlayerAttacker)
|
2026-01-21 17:13:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 第一步:闪避判定
|
|
|
|
|
|
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,
|
2026-02-02 15:52:32 +08:00
|
|
|
|
{},
|
|
|
|
|
|
true // 玩家攻击
|
2026-01-21 17:13:51 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
{},
|
2026-02-02 15:52:32 +08:00
|
|
|
|
playerBonuses,
|
|
|
|
|
|
false // 敌人攻击
|
2026-01-21 17:13:51 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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 - 环境类型
|
2026-02-02 15:52:32 +08:00
|
|
|
|
* @param {String} preferredStance - 玩家偏好的战斗姿态
|
2026-01-21 17:13:51 +08:00
|
|
|
|
* @returns {Object} 战斗状态
|
|
|
|
|
|
*/
|
2026-02-02 15:52:32 +08:00
|
|
|
|
export function initCombat(enemyId, enemyConfig, environment = 'normal', preferredStance = 'balance') {
|
2026-01-21 17:13:51 +08:00
|
|
|
|
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,
|
2026-02-02 15:52:32 +08:00
|
|
|
|
skillExpReward: enemyConfig.skillExpReward || 0,
|
2026-01-21 17:13:51 +08:00
|
|
|
|
drops: enemyConfig.drops || []
|
|
|
|
|
|
},
|
2026-02-02 15:52:32 +08:00
|
|
|
|
stance: preferredStance, // 使用玩家偏好的战斗姿态
|
2026-01-21 17:13:51 +08:00
|
|
|
|
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] || '平衡'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:20:10 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取攻击速度修正
|
|
|
|
|
|
* @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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 17:13:51 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取环境类型
|
|
|
|
|
|
* @param {String} locationId - 位置ID
|
|
|
|
|
|
* @returns {String} 环境类型
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function getEnvironmentType(locationId) {
|
|
|
|
|
|
const location = LOCATION_CONFIG[locationId]
|
|
|
|
|
|
return location?.environment || 'normal'
|
|
|
|
|
|
}
|