410 lines
15 KiB
TypeScript
410 lines
15 KiB
TypeScript
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType } from "../common/config/heroSet";
|
||
import { mLogger } from "../common/Logger";
|
||
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { TalentConfig, TalentType } from "../common/config/TalentSet";
|
||
import { Attrs } from "../common/config/HeroAttrs";
|
||
import { FieldSkillHelper } from "./FieldSkillHelper";
|
||
@ecs.register('HeroAttrs')
|
||
export class HeroAttrsComp extends ecs.Comp {
|
||
public debugMode: boolean = false;
|
||
private static readonly percentRateThreshold = 1;
|
||
private static readonly minAttackCd = 0.05;
|
||
|
||
Ebus:any=null!
|
||
// ==================== 角色基础信息 ====================
|
||
hero_uuid: number = 1001;
|
||
hero_name: string = "hero";
|
||
lv: number = 1;
|
||
pool_lv: number = 1;
|
||
type: number = 0; // 0近战 1远程 2辅助
|
||
fac: number = 0; // 0:hero 1:monster
|
||
// ==================== 基础属性(有初始值) ====================
|
||
base_ap: number = 0; // 原始基础攻击(无任何加成)
|
||
base_hp: number = 0; // 原始基础血量(无任何加成)
|
||
ap: number = 0; // 基础攻击
|
||
hp: number = 100; // 基础血量
|
||
hp_max: number = 100; // 最大血量
|
||
speed: number = 100; // 基础移动速度
|
||
dis: number = 100; // 基础距离
|
||
shield: number = 0; // 当前护盾
|
||
|
||
// ==================== 攻击属性 (补充) ====================
|
||
skills: Record<number, HSkillInfo> = {};
|
||
|
||
// ==================== 触发类技能 ====================
|
||
[SkillTriggerType.Call]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.Dead]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.FStart]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.FEnd]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.Atking]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.Atked]?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||
[SkillTriggerType.Revive]?: {s_uuid: number, r_num: number, upr: number};
|
||
|
||
// ==================== 特殊属性 ====================
|
||
critical: number = 0; // 暴击率
|
||
critical_res: number = 0; // 暴击抗性
|
||
freeze_chance: number = 0; // 冰冻概率
|
||
freeze_res: number = 0; // 冰冻抗性
|
||
knockback_chance: number = 0; // 击退概率
|
||
knockback_distance: number = 0; // 击退距离强化
|
||
knockback_res: number = 0; // 击退抗性
|
||
crit_damage: number = 0; // 额外暴击伤害
|
||
puncture_chance: number = 0; // 穿透概率
|
||
wfuny: number = 0; // 风怒
|
||
|
||
revived_count: number = 0; // 已复活次数
|
||
invincible_time: number = 0;// 无敌时间
|
||
|
||
|
||
frost_end_time: number = 0;
|
||
|
||
boom: boolean = false; // 自爆怪
|
||
|
||
// ==================== 脏标签标记 ====================
|
||
dirty_hp: boolean = false; // 血量变更标记
|
||
dirty_shield: boolean = false; // 护盾变更标记
|
||
|
||
// ==================== 技能距离缓存 ====================
|
||
maxSkillDistance: number = 0; // 最远技能攻击距离(缓存,受MP影响)
|
||
minSkillDistance: number = 0; // 最近技能攻击距离(缓存,不受MP影响,用于停止位置判断)
|
||
|
||
// ==================== 阵型位置 ====================
|
||
|
||
// ==================== 标记状态 ====================
|
||
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;
|
||
combat_target_eid: number = -1;
|
||
enemy_in_cast_range: boolean = false;
|
||
start(){
|
||
}
|
||
// ==================== BUFF 系统初始化 ====================
|
||
/**
|
||
* 初始化角色的 buff debuff
|
||
* 从 HeroInfo 读取初始配置,建立属性系统
|
||
*/
|
||
initAttrs() {
|
||
this.frost_end_time = 0;
|
||
}
|
||
/*******************基础属性管理********************/
|
||
|
||
add_hp(value:number){
|
||
const oldHp = this.hp;
|
||
let addValue = value;
|
||
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)}`);
|
||
}
|
||
return addValue;
|
||
}
|
||
add_shield(value:number){
|
||
const oldShield = this.shield;
|
||
const addValue = Math.max(0, Math.floor(value));
|
||
if (addValue <= 0) return;
|
||
this.shield += addValue;
|
||
this.shield = Math.min(this.shield, FightSet.SHIELD_MAX); // 限制护盾最大层数
|
||
if (this.shield < 0) this.shield = 0;
|
||
this.dirty_shield = true; // 标记护盾需要更新
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾次数变更: ${this.hero_name}, 变化=${addValue}, ${Math.floor(oldShield)} -> ${Math.floor(this.shield)}`);
|
||
}
|
||
}
|
||
add_hp_max(value:number){
|
||
this.hp_max+=value
|
||
this.hp+=value
|
||
this.dirty_hp = true; // ✅ 仅标记需要更新
|
||
return value
|
||
}
|
||
|
||
add_ap(value:number){
|
||
this.ap +=value
|
||
return value
|
||
}
|
||
|
||
/**
|
||
* 统一的特殊/固定属性数值增加方法
|
||
* @param attr_type 属性类型枚举
|
||
* @param value 增加的数值
|
||
*/
|
||
add_special_attr(attr_type: Attrs, value: number) {
|
||
// 利用枚举值(字符串)与类属性名一致的特性,动态访问并累加属性
|
||
const key = attr_type as keyof this;
|
||
|
||
// 确保目标属性存在且类型为数字,避免运行时错误
|
||
if (typeof this[key] === 'number') {
|
||
(this as any)[key] += value;
|
||
} else {
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', `未找到对应数字属性或无法累加: attr_type=${attr_type}, value=${value}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
toFrost(time: number=1) {
|
||
const frostTime = FightSet.FROST_TIME * time;
|
||
this.frost_end_time = Math.max(this.frost_end_time, frostTime);
|
||
}
|
||
|
||
updateCD(dt: number){
|
||
// 如果处于冰冻状态,则技能 CD 暂停刷新
|
||
if (this.isFrost()) return;
|
||
|
||
for (const key in this.skills) {
|
||
const skill = this.skills[key];
|
||
if (!skill) continue;
|
||
const actualCd = this.getEffectiveSkillCd(skill.uuid);
|
||
if (actualCd <= 0) {
|
||
skill.ccd = 0;
|
||
continue;
|
||
}
|
||
if (skill.ccd >= actualCd) {
|
||
skill.ccd = actualCd;
|
||
continue;
|
||
}
|
||
skill.ccd = Math.min(actualCd, skill.ccd + dt);
|
||
}
|
||
}
|
||
isFrost(): boolean {
|
||
return this.frost_end_time > 0
|
||
}
|
||
getSkillLevel(skillId: number): number {
|
||
if (!skillId) return 0;
|
||
return this.skills[skillId]?.lv ?? 0;
|
||
}
|
||
|
||
getSkillIds(): number[] {
|
||
return Object.values(this.skills).map(skill => skill.uuid);
|
||
}
|
||
|
||
isSkillReady(skillId: number): boolean {
|
||
if (!skillId) return false;
|
||
const skill = this.skills[skillId];
|
||
if (!skill) return false;
|
||
const actualCd = this.getEffectiveSkillCd(skillId);
|
||
if (actualCd <= 0) return true;
|
||
return skill.ccd >= actualCd;
|
||
}
|
||
|
||
triggerSkillCD(skillId: number) {
|
||
if (!skillId) return;
|
||
const skill = this.skills[skillId];
|
||
if (!skill) return;
|
||
skill.ccd = 0;
|
||
}
|
||
|
||
getSkillCdProgress(skillId: number): number {
|
||
if (!skillId) return 1;
|
||
const skill = this.skills[skillId];
|
||
const actualCd = this.getEffectiveSkillCd(skillId);
|
||
if (!skill || actualCd <= 0) return 1;
|
||
return Math.max(0, Math.min(1, skill.ccd / actualCd));
|
||
}
|
||
|
||
getDisplaySkillCdProgress(): number {
|
||
const skillIds = this.getSkillIds();
|
||
const displaySkillId = skillIds[1] ?? skillIds[0] ?? 0;
|
||
return this.getSkillCdProgress(displaySkillId);
|
||
}
|
||
|
||
/** 将驻场配置值统一换算成百分比数值,兼容 0.2 和 20 两种写法。 */
|
||
private getFieldPercentValue(type: FieldSkillType): number {
|
||
const rawValue = FieldSkillHelper.getFieldSkillTotalValue(type);
|
||
if (Math.abs(rawValue) <= HeroAttrsComp.percentRateThreshold) {
|
||
return rawValue * 100;
|
||
}
|
||
return rawValue;
|
||
}
|
||
|
||
/** 英雄实时暴击率 = 基础暴击率 + 驻场暴击率。 */
|
||
public getRuntimeCritical(): number {
|
||
if (this.fac !== FacSet.HERO) return this.critical;
|
||
return this.critical + this.getFieldPercentValue(FieldSkillType.HeroCrit);
|
||
}
|
||
|
||
/** 英雄实时冰冻率 = 基础冰冻率 + 驻场冰冻率。 */
|
||
public getRuntimeFreezeChance(): number {
|
||
if (this.fac !== FacSet.HERO) return this.freeze_chance;
|
||
return this.freeze_chance + this.getFieldPercentValue(FieldSkillType.HeroFrost);
|
||
}
|
||
|
||
/** 英雄实时穿透概率 = 基础穿透概率。 */
|
||
public getRuntimePunctureChance(): number {
|
||
return this.puncture_chance;
|
||
}
|
||
|
||
/** 英雄实时暴击伤害 = 基础额外暴伤 + 驻场暴伤。 */
|
||
public getRuntimeCritDamageBonus(): number {
|
||
if (this.fac !== FacSet.HERO) return this.crit_damage;
|
||
return this.crit_damage + this.getFieldPercentValue(FieldSkillType.HeroCritDamage);
|
||
}
|
||
|
||
/** 攻速加成通过缩短普通攻击技能 CD 生效,正值越高,攻击越快。 */
|
||
public getRuntimeAttackSpeedBonus(): number {
|
||
if (this.fac !== FacSet.HERO) return 0;
|
||
return this.getFieldPercentValue(FieldSkillType.HeroSpeed);
|
||
}
|
||
|
||
/** 根据攻速加成换算实际攻击间隔,避免直接改写配置里的基础 CD。 */
|
||
public getEffectiveSkillCd(skillId: number): number {
|
||
const skill = this.skills[skillId];
|
||
if (!skill) return 0;
|
||
if (skill.cd <= 0) return 0;
|
||
const speedBonus = this.getRuntimeAttackSpeedBonus();
|
||
if (speedBonus <= 0) return skill.cd;
|
||
const speedRate = 1 + speedBonus / 100;
|
||
return Math.max(HeroAttrsComp.minAttackCd, skill.cd / speedRate);
|
||
}
|
||
|
||
|
||
|
||
// ==================== 技能距离缓存管理 ====================
|
||
/**
|
||
* 更新技能距离缓存
|
||
* 在技能初始化、新增技能、MP变化时调用
|
||
* @param skillsComp 技能组件
|
||
*/
|
||
public updateSkillDistanceCache(): void {
|
||
const maxRange = this.dis;
|
||
let minRange = 0;
|
||
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.base_ap = 0;
|
||
this.base_hp = 0;
|
||
this.ap = 0;
|
||
this.hp = 100;
|
||
this.hp_max = 100;
|
||
this.speed = 100;
|
||
this.dis = 100;
|
||
this.shield = 0;
|
||
|
||
// 重置新增属性
|
||
this.skills = {};
|
||
this.call = undefined;
|
||
this.dead = undefined;
|
||
this.fstart = undefined;
|
||
this.fend = undefined;
|
||
this.atking = undefined;
|
||
this.atked = undefined;
|
||
this.revive = undefined;
|
||
this.critical = 0;
|
||
this.critical_res = 0;
|
||
this.freeze_chance = 0;
|
||
this.freeze_res = 0;
|
||
this.knockback_chance = 0;
|
||
this.knockback_distance = 0;
|
||
this.knockback_res = 0;
|
||
this.crit_damage = 0;
|
||
this.revived_count = 0;
|
||
this.invincible_time = 0;
|
||
this.puncture_chance = 0;
|
||
this.wfuny = 0;
|
||
this.boom = false;
|
||
|
||
this.frost_end_time = 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.combat_target_eid = -1;
|
||
this.enemy_in_cast_range = false;
|
||
// 重置脏标签
|
||
this.dirty_hp = false;
|
||
this.dirty_shield = false;
|
||
}
|
||
|
||
|
||
|
||
/** 获取指定天赋的加成数值 */
|
||
public static getTalentValue(talentId: TalentType): number {
|
||
if (!smc || !smc.collection || !smc.collection.talents) return 0;
|
||
let level = smc.collection.talents[talentId] || 0;
|
||
if (level <= 0) return 0;
|
||
let talentInfo = TalentConfig.talents.find(t => t.id === talentId);
|
||
if (!talentInfo || !talentInfo.values || level > talentInfo.values.length) return 0;
|
||
return talentInfo.values[level - 1];
|
||
}
|
||
}
|
||
|
||
@ecs.register('HeroBuffSystem')
|
||
export class HeroBuffSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||
private timer =new Timer(0.1)
|
||
filter(): ecs.IMatcher {
|
||
return ecs.allOf(HeroAttrsComp);
|
||
}
|
||
|
||
update(e: ecs.Entity): void {
|
||
if(this.timer.update(this.dt)){
|
||
const attrsComp = e.get(HeroAttrsComp);
|
||
if(attrsComp.frost_end_time > 0){
|
||
attrsComp.frost_end_time -= 0.1;
|
||
if(attrsComp.frost_end_time <= 0){
|
||
attrsComp.frost_end_time = 0;
|
||
}
|
||
}
|
||
|
||
}
|
||
void e;
|
||
}
|
||
}
|
||
|
||
|
||
|