将原有的伤害计算逻辑拆分为更清晰的步骤,引入applyPR方法统一处理伤害加成和抗性计算 根据技能类型(DType)应用对应的元素伤害计算 使用防御和魔防的百分比减免公式替代原有的固定值减免
466 lines
17 KiB
TypeScript
466 lines
17 KiB
TypeScript
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||
import { FacSet } from "../common/config/GameSet";
|
||
import { Attrs } from "../common/config/HeroAttrs";
|
||
import { FightSet } from "../common/config/GameSet";
|
||
import { SkillSet, DType } from "../common/config/SkillSet";
|
||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||
import { HeroViewComp } from "./HeroViewComp";
|
||
import { DamageQueueComp, DamageEvent, DamageQueueHelper } from "./DamageQueueComp";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
|
||
|
||
/** 最终伤害数据接口
|
||
* 用于封装一次攻击计算的所有结果数据
|
||
* @property damage - 最终造成的伤害值(已考虑所有加成和减免)
|
||
* @property isCrit - 是否为暴击攻击
|
||
* @property isDodge - 是否被闪避(闪避时damage为0)
|
||
*/
|
||
interface FinalData {
|
||
damage: number;
|
||
isCrit: boolean;
|
||
isDodge: boolean;
|
||
}
|
||
/**
|
||
* 英雄攻击系统 - 伤害处理核心系统
|
||
*
|
||
* 职责:
|
||
* 1. 处理所有伤害事件的计算和分发
|
||
* 2. 管理伤害队列的处理流程
|
||
* 3. 协调视图层的表现更新
|
||
*
|
||
* 重要概念:
|
||
* - damageEvent.Attrs: 施法者属性快照(创建技能时保存)
|
||
* - targetAttrs: 被攻击者实时属性
|
||
* - 属性来源规范:攻击判定用施法者,防御判定用被攻击者
|
||
*/
|
||
@ecs.register('HeroAtkSystem')
|
||
export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||
|
||
private debugMode: boolean = false; // 是否启用调试模式
|
||
|
||
/**
|
||
* 过滤器:处理拥有伤害队列的实体
|
||
*/
|
||
filter(): ecs.IMatcher {
|
||
return ecs.allOf(HeroAttrsComp, DamageQueueComp);
|
||
}
|
||
|
||
/**
|
||
* 系统更新(每帧调用)
|
||
* 处理伤害队列中的所有伤害事件
|
||
*/
|
||
update(e: ecs.Entity): void {
|
||
if(!smc.mission.play || smc.mission.pause) return;
|
||
const model = e.get(HeroAttrsComp);
|
||
const damageQueue = e.get(DamageQueueComp);
|
||
|
||
if (!model || !damageQueue || damageQueue.isEmpty()) return;
|
||
|
||
// 标记正在处理
|
||
damageQueue.isProcessing = true;
|
||
|
||
// 处理队列中的所有伤害事件
|
||
let processedCount = 0;
|
||
while (!damageQueue.isEmpty()) {
|
||
const damageEvent = damageQueue.getNextDamageEvent();
|
||
if (!damageEvent) break;
|
||
|
||
// 处理单个伤害事件
|
||
const FDData = this.doAttack(e, damageEvent);
|
||
processedCount++;
|
||
damageQueue.processedCount++;
|
||
|
||
if (this.debugMode) {
|
||
const casterName = damageEvent.caster?.ent?.get(HeroAttrsComp)?.hero_name || "未知";
|
||
const casterUuid = damageEvent.caster?.ent?.get(HeroAttrsComp)?.hero_uuid || 0;
|
||
console.log(`[HeroAtkSystem] 英雄${model.hero_name} (uuid: ${model.hero_uuid}) 受到 ${casterName}(uuid: ${casterUuid})的 伤害 ${FDData.damage},${FDData.isCrit?"暴击":"普通"}攻击,技能ID ${damageEvent.s_uuid}`);
|
||
}
|
||
|
||
// 如果目标已死亡,停止处理后续伤害
|
||
if (model.is_dead) {
|
||
if (this.debugMode) {
|
||
console.log(`[HeroAtkSystem] ${model.hero_name} 已死亡,停止处理剩余伤害`);
|
||
}
|
||
damageQueue.clear(); // 清空剩余伤害
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 如果队列已空,移除伤害队列组件
|
||
if (damageQueue.isEmpty()) {
|
||
e.remove(DamageQueueComp);
|
||
|
||
if (this.debugMode && processedCount > 0) {
|
||
console.log(`[HeroAtkSystem] ${model.hero_name} 伤害队列处理完成,共处理 ${processedCount} 个伤害事件`);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 执行攻击计算 - 核心伤害计算逻辑
|
||
*
|
||
* 属性使用规范(重要!):
|
||
*
|
||
* ✅ 正确使用施法者属性(damageEvent.Attrs - 快照):
|
||
* - CRITICAL: 暴击率判定
|
||
* - CRITICAL_DMG: 暴击伤害加成
|
||
* - BACK_CHANCE: 击退概率
|
||
* - HIT: 命中率(用于闪避计算)
|
||
* - AP/MAP: 攻击力(基础伤害计算)
|
||
*
|
||
* ✅ 正确使用被攻击者属性(targetAttrs - 实时):
|
||
* - DODGE: 闪避率(用于闪避计算)
|
||
* - SHIELD_MAX: 护盾最大值(护盾吸收)
|
||
* - hp: 当前生命值(伤害应用)
|
||
* - 各种抗性属性(预留扩展)
|
||
*
|
||
* ❌ 错误使用案例(已修复):
|
||
* - 不要混用施法者和被攻击者的属性进行同一计算
|
||
* - 暴击伤害应该基于施法者的暴击伤害属性
|
||
*
|
||
* @param target 目标实体(被攻击者)
|
||
* @param damageEvent 伤害事件数据(包含施法者信息和属性快照)
|
||
* @returns 最终伤害数据(包含伤害值、暴击标记、闪避标记)
|
||
*/
|
||
private doAttack(target: ecs.Entity, damageEvent: DamageEvent): FinalData {
|
||
const targetAttrs = target.get(HeroAttrsComp);
|
||
const targetView = target.get(HeroViewComp);
|
||
let reDate:FinalData={
|
||
damage:0,
|
||
isCrit:false,
|
||
isDodge:false,
|
||
}
|
||
if (!targetAttrs || targetAttrs.is_dead) return reDate;
|
||
|
||
const caster = damageEvent.caster;
|
||
const attackerModel = caster?.ent?.get(HeroAttrsComp);
|
||
|
||
// 获取技能配置
|
||
const skillConf = SkillSet[damageEvent.s_uuid];
|
||
if (!skillConf) return reDate;
|
||
|
||
// 触发被攻击事件
|
||
this.onAttacked(target);
|
||
|
||
// 闪避判定
|
||
// 闪避成功概率 = 被攻击者闪避率 - 施法者命中率
|
||
// targetAttrs.Attrs[Attrs.DODGE]: 被攻击者的实时闪避属性
|
||
// damageEvent.Attrs[Attrs.HIT]: 施法者在技能创建时的命中属性快照
|
||
const isDodge =this.checkChance((targetAttrs.Attrs[Attrs.DODGE]-damageEvent.Attrs[Attrs.HIT]) || 0);
|
||
if (isDodge) {
|
||
// TODO: 触发闪避视图表现
|
||
reDate.isDodge=true;
|
||
return reDate;
|
||
}
|
||
// 暴击判定
|
||
// 使用施法者的暴击率属性(damageEvent.Attrs 快照)
|
||
const isCrit = this.checkChance(damageEvent.Attrs[Attrs.CRITICAL]);
|
||
if (isCrit) attackerModel?.clearTalBuffByAttr(Attrs.CRITICAL);
|
||
// 计算伤害
|
||
let damage = this.dmgCount(damageEvent.Attrs,targetAttrs.Attrs,damageEvent.s_uuid);
|
||
if (isCrit) {
|
||
// 暴击伤害计算
|
||
// 使用施法者的暴击伤害加成属性(damageEvent.Attrs 快照)
|
||
// 公式:最终伤害 = 基础伤害 * (1 + 系统暴击倍率 + 施法者暴击伤害加成)
|
||
const casterCritDmg = damageEvent.Attrs[Attrs.CRITICAL_DMG] || 0;
|
||
damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + casterCritDmg) / 100));
|
||
reDate.isCrit=true;
|
||
}
|
||
// 伤害计算(考虑易伤等debuff)
|
||
damage = this.calculateDamage(targetAttrs, damage);
|
||
// 护盾吸收
|
||
damage =Math.floor(this.absorbShield(targetAttrs, damage))
|
||
if (damage <= 0) return reDate;
|
||
// 应用伤害到数据层
|
||
targetAttrs.hp -= damage;
|
||
targetAttrs.atked_count++;
|
||
// 击退判定
|
||
// 使用施法者的击退概率属性(damageEvent.Attrs 快照)
|
||
// 击退成功后需要清理施法者的相关天赋buff
|
||
const isBack = this.checkChance(damageEvent.Attrs[Attrs.BACK_CHANCE] || 0);
|
||
if (isBack) attackerModel?.clearTalBuffByAttr(Attrs.BACK_CHANCE);
|
||
|
||
|
||
// ✅ 触发视图层表现(伤害数字、受击动画、后退)
|
||
if (targetView) targetView.do_atked(damage, isCrit, damageEvent.s_uuid, isBack);
|
||
|
||
|
||
// 检查死亡
|
||
if (targetAttrs.hp <= 0) {
|
||
this.doDead(target);
|
||
// ✅ 触发死亡视图表现
|
||
if (targetView) {
|
||
targetView.do_dead();
|
||
}
|
||
}
|
||
|
||
if (this.debugMode) {
|
||
console.log(`[HeroAtkSystem] ${targetAttrs.hero_name} 受到 ${damage} 点伤害 (暴击: ${isCrit})`);
|
||
}
|
||
|
||
reDate.damage=damage;
|
||
return reDate;
|
||
}
|
||
/**
|
||
* 基础伤害计算
|
||
* 根据技能配置和施法者属性计算基础伤害值
|
||
*
|
||
* @param CAttrs 施法者属性快照(包含攻击力等关键属性)
|
||
* @param s_uuid 技能ID,用于获取技能配置
|
||
* @returns 基础伤害值(未考虑暴击、抗性等因素)
|
||
*
|
||
* @todo 后续可以扩展更多伤害计算公式:
|
||
* - 技能倍率加成
|
||
* - 元素伤害计算
|
||
* - 真实伤害/魔法伤害/物理伤害区分
|
||
*/
|
||
private dmgCount(CAttrs:any,TAttrs:any,s_uuid:number){
|
||
let sConf = SkillSet[s_uuid];
|
||
if (!sConf) return 0;
|
||
let apBase = (sConf.ap||0)*CAttrs[Attrs.AP]/100;
|
||
let mapBase = (sConf.map||0)*CAttrs[Attrs.MAP]/100;
|
||
const def = (TAttrs[Attrs.DEF]||0);
|
||
const mdef = (TAttrs[Attrs.MDEF]||0);
|
||
const apRed = def / (def + FightSet.DEF_C);
|
||
const mapRed = mdef / (mdef + FightSet.MDEF_C);
|
||
let apAfter = Math.floor(apBase * (1 - apRed));
|
||
let mapAfter = Math.floor(mapBase * (1 - mapRed));
|
||
apAfter = this.applyPR(apAfter, CAttrs[Attrs.PHYS_POWER]||0, TAttrs[Attrs.PHYS_RES]||0);
|
||
mapAfter = this.applyPR(mapAfter, CAttrs[Attrs.MAGIC_POWER]||0, TAttrs[Attrs.MAGIC_RES]||0);
|
||
switch (sConf.DType) {
|
||
case DType.ICE:
|
||
mapAfter = this.applyPR(mapAfter, CAttrs[Attrs.ICE_POWER]||0, TAttrs[Attrs.ICE_RES]||0);
|
||
break;
|
||
case DType.FIRE:
|
||
mapAfter = this.applyPR(mapAfter, CAttrs[Attrs.FIRE_POWER]||0, TAttrs[Attrs.FIRE_RES]||0);
|
||
break;
|
||
case DType.WIND:
|
||
mapAfter = this.applyPR(mapAfter, CAttrs[Attrs.WIND_POWER]||0, TAttrs[Attrs.WIND_RES]||0);
|
||
break;
|
||
}
|
||
let total = apAfter + mapAfter;
|
||
total = Math.floor(total * (1 - ((TAttrs[Attrs.DAMAGE_REDUCTION]||0)/100)));
|
||
return Math.max(0,total);
|
||
}
|
||
|
||
private applyPR(base: number, power: number, res: number): number {
|
||
return Math.floor(base * (1 + (power/100)) * (1 - (res/100)));
|
||
}
|
||
/**
|
||
* 处理角色死亡
|
||
*
|
||
* 死亡处理流程:
|
||
* 1. 标记死亡状态(is_dead = true)
|
||
* 2. 触发死亡事件(onDeath)
|
||
* 3. 记录调试信息(如启用调试模式)
|
||
*
|
||
* @param entity 死亡的实体
|
||
*
|
||
* @important 死亡状态一旦设置,该实体将不再处理新的伤害事件
|
||
* 这确保了死亡逻辑的单一性和一致性
|
||
*/
|
||
private doDead(entity: ecs.Entity): void {
|
||
const model = entity.get(HeroAttrsComp);
|
||
if (!model || model.is_dead) return;
|
||
|
||
model.is_dead = true;
|
||
|
||
// 触发死亡事件
|
||
this.onDeath(entity);
|
||
|
||
if (this.debugMode) {
|
||
console.log(`[HeroAtkSystem] ${model.hero_name} 死亡`);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 统一概率判定方法
|
||
*
|
||
* 用于所有概率相关的判定:
|
||
* - 暴击率判定
|
||
* - 闪避率判定
|
||
* - 击退概率判定
|
||
* - 其他特殊效果概率
|
||
*
|
||
* @param rate 概率值(0-100的百分比)
|
||
* @returns true-判定成功,false-判定失败
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* // 10%概率触发
|
||
* if (this.checkChance(10)) {
|
||
* // 触发特殊效果
|
||
* }
|
||
* ```
|
||
*
|
||
* @important 概率为0或负数时直接返回false,避免不必要的随机数计算
|
||
*/
|
||
private checkChance(rate: number): boolean {
|
||
if (rate <= 0) return false;
|
||
const r = Math.random() * 100;
|
||
return r < rate;
|
||
}
|
||
|
||
/**
|
||
* 伤害计算(考虑易伤等debuff)
|
||
*
|
||
* 预留的伤害计算扩展点,用于处理:
|
||
* - 被攻击者的易伤状态(增加受到伤害)
|
||
* - 被攻击者的伤害减免状态(减少受到伤害)
|
||
* - 元素抗性计算
|
||
* - 真实伤害/魔法伤害/物理伤害的类型区分
|
||
*
|
||
* @param model 被攻击者的属性组件(包含抗性、易伤等状态)
|
||
* @param baseDamage 基础伤害值
|
||
* @returns 最终伤害值(经过各种加成和减免后的结果)
|
||
*/
|
||
private calculateDamage(model: HeroAttrsComp, baseDamage: number): number {
|
||
// 这里可以添加易伤等debuff的计算逻辑
|
||
// 例如:如果目标有易伤buff,增加受到的伤害
|
||
return baseDamage;
|
||
}
|
||
|
||
/**
|
||
* 护盾吸收伤害
|
||
*
|
||
* 护盾吸收逻辑:
|
||
* 1. 如果护盾值 >= 伤害值:完全吸收,剩余伤害为0
|
||
* 2. 如果护盾值 < 伤害值:部分吸收,剩余伤害 = 原伤害 - 护盾值
|
||
* 3. 护盾被击破时,重置护盾最大值属性
|
||
*
|
||
* @param model 被攻击者的属性组件(包含当前护盾值)
|
||
* @param damage 原始伤害值
|
||
* @returns 剩余伤害值(护盾吸收后的结果)
|
||
*/
|
||
private absorbShield(model: HeroAttrsComp, damage: number): number {
|
||
if (model.shield <= 0) return damage;
|
||
|
||
if (model.shield >= damage) {
|
||
model.shield -= damage;
|
||
if (model.shield <= 0) {
|
||
model.shield = 0;
|
||
model.Attrs[Attrs.SHIELD_MAX] = 0;
|
||
}
|
||
return 0;
|
||
} else {
|
||
const remainingDamage = damage - model.shield;
|
||
model.shield = 0;
|
||
model.Attrs[Attrs.SHIELD_MAX] = 0;
|
||
return remainingDamage;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 被攻击时触发的事件
|
||
*
|
||
* 预留的扩展点,用于处理被攻击时的特殊逻辑:
|
||
* - 触发反伤效果(荆棘光环等)
|
||
* - 触发被攻击天赋(如受击回血、受击反击等)
|
||
* - 触发特殊状态(如受伤狂暴、受伤护盾等)
|
||
*
|
||
* @param entity 被攻击的实体
|
||
*
|
||
* @todo 当前对怪物实体直接返回,后续可以根据需求扩展怪物的被攻击逻辑
|
||
*/
|
||
private onAttacked(entity: ecs.Entity): void {
|
||
const model = entity.get(HeroAttrsComp);
|
||
if (!model || model.is_dead) return;
|
||
|
||
// 这里可以添加被攻击时的特殊处理逻辑
|
||
if (model.fac === FacSet.MON) return;
|
||
|
||
// 例如:触发某些天赋效果、反击逻辑等
|
||
}
|
||
|
||
/**
|
||
* 死亡时触发的事件
|
||
*
|
||
* 根据实体阵营类型处理不同的死亡逻辑:
|
||
*
|
||
* - FacSet.MON(怪物):触发掉落逻辑
|
||
* - 延迟执行掉落,避免阻塞主逻辑
|
||
* - 可以扩展:经验值计算、任务进度等
|
||
*
|
||
* - FacSet.HERO(英雄):触发英雄死亡特殊处理
|
||
* - 游戏结束判定
|
||
* - 复活机制检查
|
||
* - 死亡惩罚/奖励
|
||
*
|
||
* @param entity 死亡的实体
|
||
*
|
||
* @important 死亡事件应该幂等,避免重复触发
|
||
*/
|
||
private onDeath(entity: ecs.Entity): void {
|
||
const model = entity.get(HeroAttrsComp);
|
||
if (!model) return;
|
||
|
||
if (model.fac === FacSet.MON) {
|
||
// 怪物死亡处理
|
||
this.scheduleDrop(entity);
|
||
} else if (model.fac === FacSet.HERO) {
|
||
// 英雄死亡处理
|
||
this.scheduleHeroDeath(entity);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 延迟执行掉落逻辑
|
||
*
|
||
* 采用延迟执行的原因:
|
||
* 1. 避免在伤害计算过程中阻塞主线程
|
||
* 2. 给死亡动画播放留出时间
|
||
* 3. 可以批量处理多个掉落,优化性能
|
||
*
|
||
* @param entity 死亡的怪物实体
|
||
*
|
||
* @todo 具体实现可以包括:
|
||
* - 根据怪物等级计算基础掉落
|
||
* - 幸运值影响掉落品质
|
||
* - 特殊事件(双倍掉落、稀有掉落等)
|
||
* - 掉落物在场景中的生成位置计算
|
||
*/
|
||
private scheduleDrop(entity: ecs.Entity): void {
|
||
// 这里可以添加掉落逻辑
|
||
// 例如:延迟一段时间后生成掉落物品
|
||
}
|
||
|
||
/**
|
||
* 延迟执行英雄死亡逻辑
|
||
*
|
||
* 英雄死亡的特殊处理,比普通怪物复杂:
|
||
*
|
||
* 处理内容包括:
|
||
* - 检查复活次数和复活条件
|
||
* - 触发游戏结束界面(如适用)
|
||
* - 记录死亡统计信息
|
||
* - 处理死亡惩罚(经验损失、装备损坏等)
|
||
*
|
||
* @param entity 死亡的英雄实体
|
||
*
|
||
* @important 英雄死亡通常需要玩家交互,所以必须延迟处理
|
||
* 给玩家足够的反馈时间和操作空间
|
||
*/
|
||
private scheduleHeroDeath(entity: ecs.Entity): void {
|
||
// 这里可以添加英雄死亡的特殊处理
|
||
// 例如:触发游戏结束、复活机制等
|
||
}
|
||
|
||
/**
|
||
* 启用调试模式
|
||
*/
|
||
enableDebug() {
|
||
this.debugMode = true;
|
||
}
|
||
|
||
/**
|
||
* 禁用调试模式
|
||
*/
|
||
disableDebug() {
|
||
this.debugMode = false;
|
||
}
|
||
|
||
|
||
} |