Files
pixelheros/assets/script/game/hero/HeroAttrsComp.ts
walkpan 4305a4461e refactor(hero&mission): 调整英雄站位逻辑与配置
1.  修改游戏地平线Y轴偏移至100,适配新的UI布局
2.  为英雄属性组件添加分路与排位字段并初始化
3.  重构英雄站位分配逻辑,使用新增字段记录英雄位置
4.  更新地图与UI预制体的布局偏移适配新的游戏地平线
2026-05-13 00:15:38 +08:00

392 lines
14 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 { HeroDisVal, HeroInfo, HSkillInfo, HType } 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 } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp";
import { TalentConfig, TalentType } from "../common/config/TalentSet";
@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
// ==================== 基础属性(有初始值) ====================
ap: number = 0; // 基础攻击
hp: number = 100; // 基础血量
hp_max: number = 100; // 最大血量
speed: number = 100; // 基础移动速度
dis: number = 100; // 基础距离
shield: number = 0; // 当前护盾
// ==================== 攻击属性 (补充) ====================
skills: Record<number, HSkillInfo> = {};
// ==================== 触发类技能 ====================
call?: number[];
dead?: number[];
fstart?: number[];
fend?: number[];
atking?: {s_uuid: number, t_num: number}[];
atked?: {s_uuid: number, t_num: number}[];
revive?: {s_uuid: number, r_num: number, upr: number};
// ==================== 特殊属性 ====================
critical: number = 0; // 暴击率
freeze_chance: number = 0; // 冰冻概率
crit_damage: number = 0; // 额外暴击伤害
puncture: 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影响用于停止位置判断
// ==================== 阵型位置 ====================
lane: number = -1; // 所在分路0上路, 1中路, 2下路
lane_index: number = -1; // 所在路中的排位0前排, 1后排
// ==================== 标记状态 ====================
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
}
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 = HeroAttrsComp.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 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 rangeType = this.type as HType.Melee | HType.Mid | HType.Long;
const maxRange = HeroDisVal[rangeType];
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.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.freeze_chance = 0;
this.crit_damage = 0;
this.revived_count = 0;
this.invincible_time = 0;
this.puncture = 0;
this.wfuny = 0;
this.boom = false;
this.frost_end_time = 0;
// 重置技能距离缓存
this.maxSkillDistance = 0;
this.minSkillDistance = 0;
this.lane = -1;
this.lane_index = -1;
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 getFieldSkillTotalValue(type: FieldSkillType): number {
let total = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (!model || model.is_dead || model.fac !== FacSet.HERO) return;
const heroConfig = HeroInfo[model.hero_uuid];
if (heroConfig && heroConfig.field) {
for (const skillUuid of heroConfig.field) {
const skillConfig = FieldSkillSet[skillUuid];
if (skillConfig && skillConfig.type === type) {
total += skillConfig.value;
}
}
}
});
return total;
}
/** 获取指定天赋的加成数值 */
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;
}
}