Files
pixelheros/assets/script/game/hero/HeroAttrsComp.ts
walkpan f63f5c6656 feat(战斗): 优化自动施法目标选择逻辑
- 在 HeroAttrsComp 中新增 combat_target_eid 和 enemy_in_cast_range 字段,用于跟踪当前战斗目标
- 修改 MoveSystem 在移动时同步更新战斗目标状态,并清理无效目标
- 重构 SCastSystem 的自动施法逻辑,优先使用已锁定的战斗目标而非重新搜索
- 调整技能 6005 和 6006 的 hit_count 参数,分别改为 2 和 3 次打击
- 为友方技能施法添加事件派发机制,通知其他系统技能释放
2026-03-16 20:30:28 +08:00

619 lines
21 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 { Attrs, BType } from "../common/config/HeroAttrs";
import { BuffConf, BuffRunType } from "../common/config/SkillSet";
import { HeroDisVal, HeroInfo, HType } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { smc } from "../common/SingletonModuleComp";
import { HeroViewComp } from "./HeroViewComp";
import { _decorator } from "cc";
/**
* 可撤销的属性型 Buff/Debuff 实例
* - 仅用于 Timed 类型
* - 进入 BUFFS/DEBUFFS 后会在到期时自动反向回滚
*/
interface ActiveBuffState {
id: number
attr: Attrs
sourceUuid: number
value: number
BType: BType
time: number
}
/**
* 间隔触发型效果实例
* - 不直接进入 BUFFS/DEBUFFS
* - 由 HeroBuffSystem 每帧推进 tick/remain 并触发执行
*/
interface IntervalBuffState {
id: number
attr: Attrs
sourceUuid: number
value: number
BType: BType
interval: number
remain: number
tick: number
}
@ecs.register('HeroAttrs')
export class HeroAttrsComp extends ecs.Comp {
public debugMode: boolean = false;
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
// ==================== 基础属性(有初始值) ====================
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, ActiveBuffState[]> = {};
DEBUFFS: Record<number, ActiveBuffState[]> = {};
INTERVAL_EFFECTS: IntervalBuffState[] = [];
private buffInstanceId = 0;
// ==================== 标记状态 ====================
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
combat_target_eid: number = -1;
enemy_in_cast_range: boolean = false;
start(){
}
// ==================== BUFF 系统初始化 ====================
/**
* 初始化角色的 buff debuff
* 从 HeroInfo 读取初始配置,建立属性系统
*/
initAttrs() {
// 清空现有 buff/debuff
this.BUFFS = {};
this.DEBUFFS = {};
this.INTERVAL_EFFECTS = [];
this.buffInstanceId = 0;
// 获取英雄配置
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; // ✅ 仅标记需要更新
if (this.debugMode) {
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.hp_max / 100;
}
this.shield += addValue;
this.shield_max += addValue;
if (this.shield < 0) this.shield = 0;
if (this.shield_max < 0) this.shield_max = 0;
this.dirty_shield = true; // 标记护盾需要更新
if (this.debugMode) {
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) {
const normalized = this.normalizeBuffValue(buffConf);
const runType = this.resolveRunType(buffConf);
// Interval按间隔触发写入 INTERVAL_EFFECTS由系统统一执行
if (runType === BuffRunType.Interval) {
const interval = buffConf.interval && buffConf.interval > 0 ? buffConf.interval : 1;
const remain = buffConf.time > 0 ? buffConf.time : interval;
this.INTERVAL_EFFECTS.push({
id: ++this.buffInstanceId,
attr: buffConf.buff,
sourceUuid: buffConf.uuid,
value: normalized.value,
BType: normalized.BType,
interval,
remain,
tick: interval
});
return;
}
// Permanent一次性生效不写入 BUFFS/DEBUFFS不存在自动撤销
if (runType === BuffRunType.Permanent) {
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
return;
}
// Timed先应用再记录后续 updateList 到期后自动反向移除
const duration = buffConf.time > 0 ? buffConf.time : 1;
const targetList = buffConf.isDebuff ? this.DEBUFFS : this.BUFFS;
const attrKey = buffConf.buff as unknown as number; // 强制转换 key 类型以适配 Record
if (!targetList[attrKey]) {
targetList[attrKey] = [];
}
const currentBuffs = targetList[attrKey];
currentBuffs.push({
id: ++this.buffInstanceId,
attr: buffConf.buff,
sourceUuid: buffConf.uuid,
value: normalized.value,
BType: normalized.BType,
time: duration
});
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
if (this.debugMode) {
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${normalized.value}, 时间:${duration}`);
}
}
/**
* runType 解析优先级:
* 1) 显式配置 runType
* 2) 兼容旧配置:存在 interval 视为 Interval
* 3) time>0 视为 Timed否则 Permanent
*/
private resolveRunType(buffConf: BuffConf): BuffRunType {
if (buffConf.runType !== undefined) return buffConf.runType;
if (buffConf.interval && buffConf.interval > 0) return BuffRunType.Interval;
return buffConf.time > 0 ? BuffRunType.Timed : BuffRunType.Permanent;
}
/**
* 把配置值统一转换为“可直接写入容器和结算”的数值
* - RATIO 会在写入前转换为 VALUE
* - BOOLEAN 保持原类型
*/
private normalizeBuffValue(buffConf: BuffConf): { value: number; BType: BType } {
if (buffConf.BType === BType.BOOLEAN) {
return { value: buffConf.value, BType: BType.BOOLEAN };
}
return {
value: this.resolveBuffValue(buffConf.buff, buffConf.value, buffConf.BType),
BType: BType.VALUE
};
}
private resolveBuffValue(attr: Attrs, value: number, type: BType): number {
if (type !== BType.RATIO) return value;
if (attr === Attrs.hp || attr === Attrs.shield) {
return this.hp_max * value / 100;
}
if (attr === Attrs.hp_max || attr === Attrs.shield_max) {
return this[attr] * value / 100;
}
if (typeof this[attr] === "number") {
return (this[attr] as number) * value / 100;
}
return value;
}
/**
* 通用属性修改应用
* @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;
}
// 护盾特殊处理:同时修改当前值和最大值
if (attr === Attrs.shield && type === BType.VALUE) {
this.shield += finalValue;
this.shield_max += finalValue;
this.shield = Math.max(0, this.shield);
this.shield_max = Math.max(0, this.shield_max);
this.dirty_shield = true;
return;
}
if (type === BType.RATIO) {
finalValue = this.resolveBuffValue(attr, finalValue, BType.RATIO);
if (typeof this[attr] === 'number') {
this[attr] = (this[attr] as number) + finalValue;
}
} 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
}
}
isStun(): boolean {
return this.in_stun;
}
isFrost(): boolean {
return this.in_frost;
}
triggerAtkCD() {
this.a_cd = 0;
this.can_atk = false;
}
triggerSkillCD() {
this.s_cd = 0;
this.can_skill = false;
}
// ==================== 临时 BUFF/DEBUFF 更新 ====================
/**
* 更新临时 buff/debuff 的剩余时间
* @param dt 时间增量
*/
updateBuffsDebuffs(dt: number) {
this.updateList(this.BUFFS, dt);
this.updateList(this.DEBUFFS, dt);
// 更新状态标记 (根据 DEBUFFS 列表是否存在有效项)
this.updateStatusFlags();
}
/**
* 仅做“采集触发事件”,不做数值执行
* 实际执行由 HeroBuffSystem.applyIntervalEffect 统一处理
*/
collectIntervalEffectsBySystem(dt: number): IntervalBuffState[] {
const triggered: IntervalBuffState[] = [];
for (let i = this.INTERVAL_EFFECTS.length - 1; i >= 0; i--) {
const state = this.INTERVAL_EFFECTS[i];
state.remain -= dt;
state.tick -= dt;
while (state.tick <= 0 && state.remain > 0) {
triggered.push({
id: state.id,
attr: state.attr,
sourceUuid: state.sourceUuid,
value: state.value,
BType: state.BType,
interval: state.interval,
remain: state.remain,
tick: state.tick
});
state.tick += state.interval;
}
if (state.remain <= 0) {
this.INTERVAL_EFFECTS.splice(i, 1);
}
}
return triggered;
}
applyStoredEffect(attr: Attrs, value: number, type: BType) {
this.applyAttrChange(attr, value, type);
}
/**
* Timed Buff/Debuff 到期更新
* - 到期时调用 applyAttrChange(reverse=true) 回滚
* - 该函数只处理 BUFFS/DEBUFFS不处理 INTERVAL_EFFECTS
*/
private updateList(list: Record<number, ActiveBuffState[]>, 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) {
this.applyAttrChange(buff.attr, buff.value, buff.BType, true);
buffs.splice(i, 1);
if (this.debugMode) {
mLogger.log(this.debugMode, 'HeroAttrs', `Buff过期: 属性:${buff.attr}, 恢复值:${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 {
void skill_id;
const rangeType = this.type as HType.Melee | HType.Mid | HType.Long;
const maxRange = HeroDisVal[rangeType];
let minRange = 0;
if (rangeType === HType.Mid) {
minRange = HeroDisVal[HType.Melee];
} else if (rangeType === HType.Long) {
minRange = HeroDisVal[HType.Mid];
}
this.maxSkillDistance = maxRange;
this.minSkillDistance = minRange;
}
/**
* 获取缓存的最远技能攻击距离
* @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.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.INTERVAL_EFFECTS = [];
this.buffInstanceId = 0;
// 重置技能距离缓存
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_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.combat_target_eid = -1;
this.enemy_in_cast_range = false;
// 重置脏标签
this.dirty_hp = false;
this.dirty_shield = false;
}
}
@ecs.register('HeroBuffSystem')
export class HeroBuffSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroAttrsComp);
}
update(e: ecs.Entity): void {
if (!smc.mission.play || smc.mission.pause) return;
const attrs = e.get(HeroAttrsComp);
if (!attrs || attrs.is_dead) return;
// 1) 推进 Timed Buff/Debuff 生命周期
attrs.updateBuffsDebuffs(this.dt);
// 2) 收集本帧到点的 Interval 触发
const triggered = attrs.collectIntervalEffectsBySystem(this.dt);
if (triggered.length === 0) return;
const view = e.get(HeroViewComp);
// 3) 统一执行数值与表现
for (const effect of triggered) {
this.applyIntervalEffect(attrs, view, effect);
}
}
/**
* 间隔效果统一执行入口
* - 先做数值结算
* - 再按实际变化触发表现,避免无效动画
*/
private applyIntervalEffect(attrs: HeroAttrsComp, view: HeroViewComp | null, effect: IntervalBuffState) {
if (effect.attr === Attrs.hp) {
const oldHp = attrs.hp;
attrs.add_hp(effect.value, true);
const delta = attrs.hp - oldHp;
if (view && delta !== 0) {
view.playIntervalEffect(effect.attr, delta, effect.sourceUuid);
}
return;
}
if (effect.attr === Attrs.shield) {
const oldShield = attrs.shield;
attrs.add_shield(effect.value, true);
const delta = attrs.shield - oldShield;
if (view && delta !== 0) {
view.playIntervalEffect(effect.attr, delta, effect.sourceUuid);
}
return;
}
attrs.applyStoredEffect(effect.attr, effect.value, effect.BType);
if (view) {
view.playIntervalEffect(effect.attr, effect.value, effect.sourceUuid);
}
}
}