Files
pixelheros/assets/script/game/hero/HeroAttrsComp.ts
walkpan a544f65d73 refactor(战斗): 重构英雄与怪物属性系统,简化数据结构
- 移除 HeroSkillsComp 组件,将技能逻辑合并到 HeroAttrsComp
- 将属性从 Attrs 枚举映射改为 HeroAttrsComp 中的独立字段
- 为 HeroAttrsComp 添加攻击和技能冷却时间管理功能
- 统一英雄和怪物的属性初始化方式,简化配置数据
- 在 GameSet 中添加击退概率配置项
- 修复 SkillView 中属性名大小写错误
2026-03-11 23:13:21 +08:00

446 lines
18 KiB
TypeScript
Raw 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.
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { Attrs, BType } from "../common/config/HeroAttrs";
import { BuffConf, SkillDisVal, SkillRange, SkillSet } from "../common/config/SkillSet";
import { HeroInfo } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { _decorator } from "cc";
const { property } = _decorator;
interface talTrigger{
value:number
count:number
}
@ecs.register('HeroAttrs')
export class HeroAttrsComp extends ecs.Comp {
public debugMode: boolean = true;
Ebus:any=null!
// ==================== 角色基础信息 ====================
hero_uuid: number = 1001;
hero_name: string = "hero";
lv: number = 1;
type: number = 0; // 0近战 1远程 2辅助
fac: number = 0; // 0:hero 1:monster
rangeType:SkillRange = SkillRange.Melee;
// ==================== 基础属性(有初始值) ====================
ap: number = 0; // 基础攻击
hp: number = 100; // 基础血量
hp_max: number = 100; // 最大血量
speed: number = 100; // 基础移动速度
dis: number = 100; // 基础距离
shield: number = 0; // 当前护盾
shield_max: number = 0; // 最大护盾值
// ==================== 攻击属性 (补充) ====================
a_cd: number = 0; // 攻击计时
s_cd: number = 0; // 技能计时
a_cd_max: number = 0; // 攻击CD
s_cd_max: number = 0; // 技能CD
// ==================== 暴击与命中属性 ====================
critical: number = 0; // 暴击率
critical_dmg: number = 0; // 暴击伤害
// ==================== 特殊效果属性 ====================
freeze_chance: number = 0; // 冰冻概率
stun_chance: number = 0; // 眩晕概率
back_chance: number = 0; // 击退概率
slow_chance: number = 0; // 减速概率
// ==================== 武器进化相关 ====================
puncture: number = 0; // 穿刺次数
puncture_dmg: number = 0; // 穿刺伤害
wfuny: number = 0; // 风怒
// ==================== 增益效果属性 ====================
revive_count: number = 0; // 复活次数
revive_time: number = 0; // 复活时间
invincible_time: number = 0;// 无敌时间
in_stun=false
in_frost=false
boom: boolean = false; // 自爆怪
// ==================== 脏标签标记 ====================
dirty_hp: boolean = false; // 血量变更标记
dirty_shield: boolean = false; // 护盾变更标记
// ==================== 技能距离缓存 ====================
maxSkillDistance: number = 0; // 最远技能攻击距离缓存受MP影响
minSkillDistance: number = 0; // 最近技能攻击距离缓存不受MP影响用于停止位置判断
// ==================== Buff/Debuff 系统 ====================
/** 持久型buff数组 - 不会自动过期 */
BUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
DEBUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
// ==================== 标记状态 ====================
is_dead: boolean = false;
is_count_dead: boolean = false;
is_atking: boolean = false; // 是否正在攻击
is_stop: boolean = false; // 是否正在停止
is_boss: boolean = false;
is_big_boss: boolean = false;
is_master: boolean = false;
is_friend: boolean = false;
is_kalami: boolean = false;
is_reviving: boolean = false; // 是否正在复活中
// ==================== 计数统计 ====================
atk_count: number = 0; // 攻击次数
atked_count: number = 0; // 被攻击次数
killed_count:number=0;
atk_id:number=0; //普通攻击技能id
skill_id:number=0; //技能攻击技能id
can_atk=false
can_skill=false
start(){
}
// ==================== BUFF 系统初始化 ====================
/**
* 初始化角色的 buff debuff
* 从 HeroInfo 读取初始配置,建立属性系统
*/
initAttrs() {
// 清空现有 buff/debuff
this.BUFFS = {};
this.DEBUFFS = {};
// 获取英雄配置
const heroInfo = HeroInfo[this.hero_uuid];
if (!heroInfo) return;
}
/*******************基础属性管理********************/
add_hp(value:number,isValue:boolean){
const oldHp = this.hp;
let addValue = value;
if(!isValue){
addValue = value * this.hp_max / 100;
}
// ✅ 数据层只负责数据修改,不调用视图层
// let heroView = this.ent.get(HeroViewComp);
// if(heroView && addValue > 0){
// heroView.health(addValue);
// }
this.hp += addValue;
this.hp = Math.max(0, Math.min(this.hp, this.hp_max));
this.dirty_hp = true; // ✅ 仅标记需要更新
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
}
add_shield(value:number,isValue:boolean){
const oldShield = this.shield;
let addValue = value;
if(!isValue){
addValue = value * this.shield_max / 100;
}
this.shield += addValue;
this.dirty_shield = true; // 标记护盾需要更新
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldShield.toFixed(1)} -> ${this.shield.toFixed(1)}`);
}
// ==================== BUFF 管理 ====================
/**
* 添加 buff 效果
* @param buffConf buff 配置
*/
addBuff(buffConf: BuffConf) {
// 1. 如果是永久性增益time=0直接修改基础属性
if (buffConf.time <= 0) {
this.applyAttrChange(buffConf.buff, buffConf.value, buffConf.BType);
return;
}
// 2. 临时性 Buff/Debuff 处理
// 区分存储列表
const targetList = buffConf.isDebuff ? this.DEBUFFS : this.BUFFS;
// 确保列表初始化
// 注意:这里我们用 buffConf.buff (属性名) 作为 key而不是 buffConf.uuid
// 这样同一种属性的 buff 可以叠加或覆盖
const attrKey = buffConf.buff as unknown as number; // 强制转换 key 类型以适配 Record
if (!targetList[attrKey]) {
targetList[attrKey] = [];
}
const currentBuffs = targetList[attrKey];
// 查找是否已有同源的 buff (这里假设 uuid 相同为同源,或者简单点直接追加)
// 策略直接追加update 时统一计算
// 记录添加时的数值,方便后续移除(虽然 update 是重新计算总值的)
currentBuffs.push({
value: buffConf.value,
BType: buffConf.BType,
time: buffConf.time
});
// 立即应用一次属性变更(增量)
this.applyAttrChange(buffConf.buff, buffConf.value, buffConf.BType);
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${buffConf.value}, 时间:${buffConf.time}`);
}
/**
* 通用属性修改应用
* @param attr 属性名
* @param value 变化值
* @param type 数值类型 (0:固定值, 1:百分比)
* @param reverse 是否反向应用 (用于移除 buff)
*/
private applyAttrChange(attr: Attrs, value: number, type: BType, reverse: boolean = false) {
let finalValue = value;
// 如果是移除 buff取反
if (reverse) {
finalValue = -value;
}
// 处理百分比
// 注意:百分比通常是基于基础值计算,这里简化为直接修改当前值
// 如果需要严格的基础值+百分比,需要维护 baseAttrs 和 bonusAttrs
// 当前架构直接修改属性,百分比暂时按 (当前值 * 百分比) 或 (基础值 * 百分比) 处理
// 鉴于属性系统中 hp/hp_max 等已经是数值,这里百分比暂定为:属性 = 属性 + (属性 * 百分比)
// 但对于 addBuff 这种临时增益,百分比通常是基于"基础值"的。
// 由于没有显式的 base_ap这里简化处理
// VALUE: 直接加减
// RATIO: 简单处理为 value 就是最终加成值(需要配置表里填好的数值),或者基于当前值
// 通常配置表填 10 表示 10%,即 0.1。
// 但这里的 value 已经是 number。假设配置 10 就是 10 点,或者 10%。
// 修正:根据 BType 处理
if (type === BType.RATIO) {
// 假设 value 是百分比整数,如 10 代表 10%
// 必须基于某个基数。这里暂时基于当前值(不严谨,但在没有 base 属性的情况下只能这样,或者基于 100?
// 更合理的做法:如果是 RATIOvalue 应该在配置时就转为实际数值,或者这里基于当前属性计算
// 考虑到 Attrs 定义里有很多非数值属性(如状态),需要特殊处理
// 针对状态类属性IN_FROST 等),通常是 BOOLEAN不走 RATIO
// 针对数值类ap, hpRATIO 应该是 value% * current
// 简化:目前只支持 VALUE 型直接加减。RATIO 型需要更复杂的属性系统支持Base + Add + Mul
// 这里的实现先按 VALUE 处理,如果以后需要 RATIO建议在 addBuff 前计算好 value
// 或者:约定 RATIO 时value 是基于当前值的百分比增量
const ratio = finalValue / 100;
// 注意:这里直接修改 this[attr] 会导致多次叠加问题。
// 临时 Buff 的正确做法是Update 中每一帧重置属性 -> 应用所有 Buff。
// 但当前架构似乎是“增量修改”式的。
// “增量修改”式在移除时很麻烦(尤其是百分比)。
// 妥协方案:只支持 VALUE 加减。配置表里的 RATIO 需要在逻辑层转为 VALUE。
// 但为了兼容 BType.RATIO这里暂时按 (当前值 * ratio) 计算增量
// 这会导致 recursive 问题,所以严谨的项目里属性必须分层。
// 鉴于“少可用原则”,这里仅处理 VALUE 和 BOOLEAN
if (typeof this[attr] === 'number') {
// 警告:直接改写数值可能不准确
this[attr] = (this[attr] as number) * (1 + ratio);
}
} else if (type === BType.BOOLEAN) {
// 布尔型/状态型value > 0 为 true/计数+1移除时 -1
// 这里使用计数器方式来支持多个同类状态叠加
// 例如 IN_FROST 是一个状态,不是属性。
// 需要在 HeroAttrsComp 中定义对应的状态计数器,或者利用 DEBUFFS 列表的存在性判断状态
// 暂时直接修改属性(如果属性是 boolean
if (typeof this[attr] === 'boolean') {
this[attr] = !reverse; // 添加设为 true, 移除设为 false (不仅确,多个冰冻会出问题)
// 正确做法updateBuffsDebuffs 中根据列表是否为空来设置状态
}
} else {
// VALUE 固定值
if (typeof this[attr] === 'number') {
this[attr] = (this[attr] as number) + finalValue;
}
}
// 标记脏数据(如果有对应的 dirty 标记)
if (attr === Attrs.hp) this.dirty_hp = true;
if (attr === Attrs.shield) this.dirty_shield = true;
}
//======更新cd========//
updateCD(dt: number){
if(this.atk_id !=0&&!this.can_atk){
this.a_cd+=dt
if(this.a_cd >= this.a_cd_max) this.can_atk = true
}
if(this.skill_id !=0&&this.can_skill){
this.s_cd+=dt
if(this.s_cd >= this.s_cd_max) this.can_skill = true
}
}
// ==================== 临时 BUFF/DEBUFF 更新 ====================
/**
* 更新临时 buff/debuff 的剩余时间
* @param dt 时间增量
*/
updateBuffsDebuffs(dt: number) {
this.updateList(this.BUFFS, dt);
this.updateList(this.DEBUFFS, dt);
// 更新状态标记 (根据 DEBUFFS 列表是否存在有效项)
this.updateStatusFlags();
}
private updateList(list: Record<number, Array<{value: number, BType: BType, time: number}>>, dt: number) {
for (const attrKey in list) {
const buffs = list[attrKey];
if (!buffs || buffs.length === 0) continue;
// 倒序遍历以便移除
for (let i = buffs.length - 1; i >= 0; i--) {
const buff = buffs[i];
buff.time -= dt;
if (buff.time <= 0) {
// Buff 过期,移除属性加成
// 注意key 是 string (from record key),需要转为 Attrs
const attrName = attrKey as unknown as Attrs;
this.applyAttrChange(attrName, buff.value, buff.BType, true); // reverse = true
// 从列表中移除
buffs.splice(i, 1);
mLogger.log(this.debugMode, 'HeroAttrs', `Buff过期: 属性:${attrName}, 恢复值:${buff.value}`);
}
}
// 如果该属性的 buff 列表空了,可以清理 key (可选)
if (buffs.length === 0) {
delete list[attrKey];
}
}
}
private updateStatusFlags() {
// 检查特定状态的 Debuff 列表是否为空,来更新 boolean 标志
this.checkStatus(Attrs.IN_FROST, 'in_frost');
this.checkStatus(Attrs.IN_STUN, 'in_stun');
}
private checkStatus(attr: Attrs, flagName: string) {
const key = attr as unknown as number;
const hasBuff = this.DEBUFFS[key] && this.DEBUFFS[key].length > 0;
// 只有当状态改变时才赋值,避免每帧赋值
if (this[flagName] !== hasBuff) {
this[flagName] = hasBuff;
// 状态变化日志
if (this.debugMode) {
mLogger.log(this.debugMode, 'HeroAttrs', `状态变更: ${this.hero_name}, ${flagName} = ${hasBuff}`);
}
}
}
// ==================== 技能距离缓存管理 ====================
/**
* 更新技能距离缓存
* 在技能初始化、新增技能、MP变化时调用
* @param skillsComp 技能组件
*/
public updateSkillDistanceCache(skill_id:number): void {
let skillConf=SkillSet[skill_id];
if (!skillConf) {
this.maxSkillDistance = 0;
this.minSkillDistance = 0;
return;
}
// 最远距离使用当前MP可施放的技能
this.maxSkillDistance = SkillDisVal[skillConf.dis];
// 最近距离使用所有技能中的最小距离不考虑MP限制用于停止位置判断
this.minSkillDistance = SkillDisVal[skillConf.dis];
}
/**
* 获取缓存的最远技能攻击距离
* @returns 最远攻击距离
*/
public getCachedMaxSkillDistance(): number {
return this.maxSkillDistance;
}
/**
* 获取缓存的最近技能攻击距离
* @returns 最近攻击距离
*/
public getCachedMinSkillDistance(): number {
return this.minSkillDistance;
}
reset() {
// 重置为初始状态
this.hero_uuid = 1001;
this.hero_name = "hero";
this.lv = 1;
this.type = 0;
this.fac = 0;
this.rangeType = SkillRange.Melee;
this.ap = 0;
this.hp = 100;
this.hp_max = 100;
this.speed = 100;
this.dis = 100;
this.shield = 0;
this.shield_max = 0;
// 重置新增属性
this.a_cd = 0;
this.s_cd = 0;
this.a_cd_max = 0;
this.s_cd_max = 0;
this.critical = 0;
this.critical_dmg = 0;
this.freeze_chance = 0;
this.stun_chance = 0;
this.back_chance = 0;
this.slow_chance = 0;
this.revive_count = 0;
this.revive_time = 0;
this.invincible_time = 0;
this.puncture = 0;
this.puncture_dmg = 0;
this.wfuny = 0;
this.boom = false;
this.in_frost = false;
this.in_stun = false;
this.BUFFS = {};
this.DEBUFFS = {};
// 重置技能距离缓存
this.maxSkillDistance = 0;
this.minSkillDistance = 0;
this.is_dead = false;
this.is_count_dead = false;
this.is_atking = false;
this.is_stop = false;
this.is_boss = false;
this.is_big_boss = false;
this.is_master = false;
this.is_friend = false;
this.is_kalami = false;
this.is_reviving = false;
this.atk_count = 0;
this.atked_count = 0;
this.killed_count =0;
this.atk_id = 0;
this.skill_id = 0;
this.can_atk=false
this.can_skill=false
// 重置脏标签
this.dirty_hp = false;
this.dirty_shield = false;
}
}