- 在SkillSet.ts中新增HSSet枚举区分普通攻击、技能和必杀技 - 重构TalSet.ts中的天赋效果枚举,移除N_ATK和N_SKILL类型 - 在HeroSkillsComp中增加hset字段标识技能类型 - 修改SACastSystem以支持根据技能类型触发不同天赋 - 完全重写TalComp组件,实现更完善的天赋触发和效果管理
273 lines
8.2 KiB
TypeScript
273 lines
8.2 KiB
TypeScript
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> = {};
|
||
|
||
// ==================== 辅助方法 ====================
|
||
|
||
/**
|
||
* 初始化技能列表
|
||
* @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;
|
||
|
||
// 普通攻击(skills[0])受 AS 影响,其余技能受 SS 影响
|
||
const isNormalAttack = s_uuid === this.skills[0]?.s_uuid;
|
||
const speedAttr = isNormalAttack ? Attrs.AS : Attrs.SS;
|
||
const speedBonus = attrsCom.Attrs[speedAttr] / 100; // 100 表示 100% 提速
|
||
const speedMultiplier = 1 / (1 + speedBonus); // 提速 100% => cd 减半
|
||
|
||
skill.cd = skill.cd_max * speedMultiplier;
|
||
if (skill) {
|
||
skill.cd = skill.cd_max;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 更新所有技能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 = {};
|
||
}
|
||
} |