Files
pixelheros/assets/script/game/hero/HeroSkills.ts
walkpan f43e0a75e5 refactor(HeroSkills): 优化技能冷却时间计算逻辑
使用技能配置中的hset属性代替数组索引判断攻击类型
添加速度加成下限保护防止除零错误
简化条件判断并移除冗余代码
2025-12-30 22:41:27 +08:00

273 lines
8.1 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 } from "../common/config/HeroAttrs";
import { HeroInfo } from "../common/config/heroSet";
import { HSSet, SkillSet } from "../common/config/SkillSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
/**
* ==================== 技能槽位数据 ====================
* 单个技能的运行时数据
*/
export interface SkillSlot {
s_uuid: number; // 技能配置ID
cd: number; // 当前CD时间递减
cd_max: number; // 最大CD时间
cost: number; // MP消耗
level: number; // 技能等级(预留)
dis: number; // 攻击距离
hset: HSSet; // 技能设定, 0:普通攻击, 1:一般技能, 2:必杀技
}
/**
* ==================== 英雄技能数据组件 ====================
*
* 职责:
* 1. 存储角色拥有的技能列表
* 2. 管理技能CD状态
* 3. 提供技能查询接口
*
* 设计理念:
* - 只存数据,不含施法逻辑
* - CD 更新由 HSkillSystem 负责
* - 施法判定由 HSkillSystem 负责
*/
@ecs.register('HeroSkills')
export class HeroSkillsComp extends ecs.Comp {
// ==================== 技能槽位列表 ====================
/** 技能槽位数组最多4个技能 */
skills: Record<number, SkillSlot> = {};
max_auto: boolean = true;
// ==================== 辅助方法 ====================
/**
* 初始化技能列表
* @param sUuids 技能配置ID数组
* @param uuid 英雄UUID
* @param entity 实体对象(用于更新技能距离缓存)
*/
initSkills(sUuids: number[], uuid: number, entity?: ecs.Entity) {
this.skills = [];
for (let i = 0; i < sUuids.length; i++) {
const s_uuid = sUuids[i];
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
continue;
}
// 第0个技能的 cd_max 取 herosinfo[uuid].as
const cdMax = i === 0 ? HeroInfo[uuid].as : config.cd;
let hset = HSSet.atk;
if(i ===1) hset = HSSet.skill;
if(i ===2) hset = HSSet.max;
this.skills[s_uuid] = {
s_uuid: config.uuid,
cd: 0,
cd_max: cdMax,
cost: config.cost,
level: 1,
dis: Number(config.dis),
hset: hset,
};
}
// 更新技能距离缓存
if (entity) {
const attrsComp = entity.get(HeroAttrsComp);
if (attrsComp) {
attrsComp.updateSkillDistanceCache(this);
}
}
}
/**
* 添加单个技能
* @param s_uuid 技能配置ID
* @param entity 实体对象(用于更新技能距离缓存)
*/
addSkill(s_uuid: number, entity?: ecs.Entity, hset: HSSet=HSSet.skill) {
const config = SkillSet[s_uuid];
if (!config) {
console.warn(`[HeroSkills] 技能配置不存在: ${s_uuid}`);
return;
}
this.skills[s_uuid] = {
s_uuid: config.uuid,
cd: 0,
cd_max: config.cd,
cost: config.cost,
level: 1,
dis: Number(config.dis),
hset: hset,
};
// 更新技能距离缓存
if (entity) {
const attrsComp = entity.get(HeroAttrsComp);
if (attrsComp) {
attrsComp.updateSkillDistanceCache(this);
}
}
}
/**
* 获取指定s_uuid的技能
*/
getSkill(s_uuid: number): SkillSlot | null {
return this.skills[s_uuid] ?? null;
}
/**
* 检查技能是否可施放通过s_uuid
* @param s_uuid 技能配置ID
* @param currentMp 当前MP值
*/
canCast(s_uuid: number, currentMp: number): boolean {
const skill = this.getSkill(s_uuid);
if (!skill) return false;
// 检查CD和MP
return skill.cd <= 0 && currentMp >= skill.cost;
}
/**
* 重置技能CD开始冷却通过索引
*/
resetCD(s_uuid: number) {
let attrsCom = this.ent.get(HeroAttrsComp);
if (!attrsCom) return;
const skill = this.getSkill(s_uuid);
if (!skill) return;
const speedAttr = skill.hset === HSSet.atk ? Attrs.AS : Attrs.SS;
const rawSpeed = attrsCom.Attrs?.[speedAttr] ?? 0;
const speedBonus = Math.max(-0.9, rawSpeed / 100);
const speedMultiplier = 1 / (1 + speedBonus);
skill.cd = Math.max(0, skill.cd_max * speedMultiplier);
}
/**
* 更新所有技能CD每帧调用
* @param dt 时间增量
*/
updateCDs(dt: number) {
for (const s_uuid in this.skills) {
const skill = this.skills[Number(s_uuid)];
if (skill.cd > 0) {
skill.cd -= dt;
if (skill.cd < 0) {
skill.cd = 0;
}
}
}
}
/**
* 获取所有可施放的技能索引
*/
getReadySkills(currentMp: number): number[] {
const ready: number[] = [];
for (const s_uuid in this.skills) {
if (this.canCast(Number(s_uuid), currentMp)) {
ready.push(Number(s_uuid));
}
}
return ready;
}
/**
* 检查技能攻击距离是否足够
* @param s_uuid 技能配置ID
* @param distance 目标距离
* @returns 是否在攻击范围内
*/
canReachTarget(s_uuid: number, distance: number): boolean {
const skill = this.getSkill(s_uuid);
if (!skill) {
return false;
}
return distance <= skill.dis;
}
/**
* 获取技能的攻击距离
* @param s_uuid 技能配置ID
* @returns 攻击距离如果技能不存在返回0
*/
getSkillDistance(s_uuid: number): number {
const skill = this.getSkill(s_uuid);
return skill ? skill.dis : 0;
}
/**
* 获取可施放技能中的最远攻击距离
* @param mp 当前MP值
* @returns 最远攻击距离如果没有可用技能返回0
*/
getMaxSkillDistance(mp: number): number {
const readySkills = this.getReadySkills(mp);
if (readySkills.length === 0) return 0;
let maxDistance = 0;
for (const s_uuid of readySkills) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis > maxDistance) {
maxDistance = skill.dis;
}
}
return maxDistance;
}
/**
* 获取可施放技能中的最近攻击距离
* @param mp 当前MP值
* @returns 最近攻击距离如果没有可用技能返回0
*/
getMinSkillDistance(mp: number): number {
const readySkills = this.getReadySkills(mp);
if (readySkills.length === 0) return 0;
let minDistance = Number.MAX_VALUE;
for (const s_uuid of readySkills) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis < minDistance) {
minDistance = skill.dis;
}
}
return minDistance === Number.MAX_VALUE ? 0 : minDistance;
}
/**
* 获取所有技能中的最小攻击距离不考虑MP限制
* 用于移动停止判断,让英雄在合适位置等待回蓝
* @returns 最小攻击距离如果没有技能返回0
*/
getAbsoluteMinSkillDistance(): number {
const skillIds = Object.keys(this.skills).map(Number);
if (skillIds.length === 0) return 0;
let minDistance = Number.MAX_VALUE;
for (const s_uuid of skillIds) {
const skill = this.getSkill(s_uuid);
if (skill && skill.dis < minDistance) {
minDistance = skill.dis;
}
}
return minDistance === Number.MAX_VALUE ? 0 : minDistance;
}
reset() {
this.skills = {};
}
setMaxAuto(on: boolean) {
this.max_auto = on;
}
}