Files
text-adventure-game/utils/combatSystem.js
Claude ccfd6a5e75 fix: 修复武器经验获取并完善义体系统
- 修复战斗胜利后未获得武器技能经验的问题 (initCombat未传递skillExpReward)
- 每级武器技能提供5%武器伤害加成(已实现,无需修改)
- 实现义体安装/卸载功能,支持NPC对话交互
- StatusPanel添加义体装备槽显示
- MapPanel修复NPC对话import问题
- 新增成就系统框架
- 添加项目文档CLAUDE.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 15:52:32 +08:00

593 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 战斗系统 - 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'
}