Files
heros/assets/script/game/hero/HeroModelComp.ts

725 lines
22 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, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs";
import { BuffConf, SkillSet } from "../common/config/SkillSet";
import { HeroInfo, AttrSet, HeroUpSet } from "../common/config/heroSet";
import { MonModelComp } from "./MonModelComp";
import { FacSet } from "../common/config/BoxSet";
import { FightSet } from "../common/config/Mission";
@ecs.register('HeroModel')
export class HeroModelComp extends ecs.Comp {
// ==================== 角色基础信息 ====================
hero_uuid: number = 1001;
hero_name: string = "hero";
lv: number = 1;
type: number = 0; // 0近战 1远程 2辅助
fac: number = 0; // 0:hero 1:monster
// ==================== 基础属性(有初始值) ====================
base_ap: number = 0; // 基础攻击
base_map: number = 0; // 基础魔法攻击
base_def: number = 5; // 基础防御
base_hp: number = 100; // 基础血量
base_mp: number = 100; // 基础魔法值
base_speed: number = 100; // 基础移动速度
base_dis: number = 100; // 基础距离
// ==================== 动态属性值 ====================
hp: number = 100; // 当前血量
mp: number = 100; // 当前魔法值
shield: number = 0; // 当前护盾
Attrs: any = []; // 最终属性数组经过Buff计算后
NeAttrs: any = []; // 负面状态数组
// ==================== Buff/Debuff 系统 ====================
/** 持久型buff数组 - 不会自动过期 */
BUFFS: Record<number, Array<{value: number, BType: BType}>> = {};
/** 临时型buff数组 - 按时间自动过期 */
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {};
// ==================== 标记状态 ====================
is_dead: boolean = false;
is_count_dead: boolean = false;
is_boss: boolean = false;
is_big_boss: boolean = false;
is_master: boolean = false;
is_friend: boolean = false;
is_kalami: boolean = false;
// ==================== 计数统计 ====================
atk_count: number = 0; // 攻击次数
atked_count: number = 0; // 被攻击次数
// ==================== 技能配置 ====================
skills: any = [];
// ==================== BUFF 系统初始化 ====================
/**
* 初始化角色的 buff debuff
* 从 HeroInfo 读取初始配置,建立属性系统
*/
initAttrs() {
// 清空现有 buff/debuff
this.BUFFS = {};
this.BUFFS_TEMP = {};
// 获取英雄配置
const heroInfo = HeroInfo[this.hero_uuid];
if (!heroInfo) return;
// 1. 重置为基础值
this.Attrs[Attrs.HP_MAX] = this.base_hp;
this.Attrs[Attrs.MP_MAX] = this.base_mp;
this.Attrs[Attrs.DEF] = this.base_def;
this.Attrs[Attrs.AP] = this.base_ap;
this.Attrs[Attrs.MAP] = this.base_map;
this.Attrs[Attrs.SPEED] = this.base_speed;
this.Attrs[Attrs.DIS] = this.base_dis;
// 2. 初始化其他属性(无初始值的)
for (const attrKey in this.Attrs) {
const attrIndex = parseInt(attrKey);
if (
attrIndex !== Attrs.HP_MAX &&
attrIndex !== Attrs.MP_MAX &&
attrIndex !== Attrs.DEF &&
attrIndex !== Attrs.AP &&
attrIndex !== Attrs.MAP &&
attrIndex !== Attrs.SPEED &&
attrIndex !== Attrs.DIS
) {
this.Attrs[attrIndex] = 0;
}
}
// 加载初始 buff
if (heroInfo.buff && heroInfo.buff.length > 0) {
for (const buffConf of heroInfo.buff) {
this.addBuff(buffConf);
}
}
}
// ==================== BUFF 管理 ====================
/**
* 添加 buff 效果(支持多次叠加)
* @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff)
*/
addBuff(buffConf: BuffConf) {
const isPermanent = buffConf.time === 0;
const attrIndex = buffConf.buff;
if (isPermanent) {
// 添加持久buff到BUFFS - 直接追加到数组
if (!this.BUFFS[attrIndex]) {
this.BUFFS[attrIndex] = [];
}
this.BUFFS[attrIndex].push({ value: buffConf.value, BType: buffConf.BType });
} else {
// 添加临时buff到BUFFS_TEMP - 直接追加到数组
if (!this.BUFFS_TEMP[attrIndex]) {
this.BUFFS_TEMP[attrIndex] = [];
}
this.BUFFS_TEMP[attrIndex].push({
value: buffConf.value,
BType: buffConf.BType,
remainTime: buffConf.time
});
}
// 重新计算受影响的属性
this.recalculateSingleAttr(attrIndex);
}
// ==================== 属性计算系统 ====================
/**
* 重新计算单个属性
* @param attrIndex 属性索引
*/
recalculateSingleAttr(attrIndex: number) {
// 1. 获取基础值
const baseValues: Record<number, number> = {
[Attrs.HP_MAX]: this.base_hp,
[Attrs.MP_MAX]: this.base_mp,
[Attrs.DEF]: this.base_def,
[Attrs.AP]: this.base_ap,
[Attrs.MAP]: this.base_map,
[Attrs.SPEED]: this.base_speed,
[Attrs.SHIELD_MAX]: 0
};
const baseVal = baseValues[attrIndex] !== undefined ? baseValues[attrIndex] : 0;
// 2. 收集所有数值型 buff/debuff
let totalValue = baseVal;
// 遍历持久buff数组
if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
for (const buff of this.BUFFS[attrIndex]) {
if (buff.BType === BType.VALUE) {
totalValue += buff.value;
}
}
}
// 遍历临时buff数组
if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) {
for (const buff of this.BUFFS_TEMP[attrIndex]) {
if (buff.BType === BType.VALUE) {
totalValue += buff.value;
}
}
}
// 3. 收集所有百分比型 buff/debuff
let totalRatio = 0;
// 遍历持久buff数组
if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
for (const buff of this.BUFFS[attrIndex]) {
if (buff.BType === BType.RATIO) {
totalRatio += buff.value;
}
}
}
// 遍历临时buff数组
if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) {
for (const buff of this.BUFFS_TEMP[attrIndex]) {
if (buff.BType === BType.RATIO) {
totalRatio += buff.value;
}
}
}
// 4. 根据属性类型计算最终值
const attrType = AttrsType[attrIndex];
const isRatioAttr = attrType === BType.RATIO;
if (isRatioAttr) {
// 百分比型属性:直接加减
this.Attrs[attrIndex] = totalValue + totalRatio;
} else {
// 数值型属性:(基础值+数值) × (1 + 百分比/100)
this.Attrs[attrIndex] = Math.floor(totalValue * (1 + totalRatio / 100));
}
// 5. 确保属性值合理
this.clampSingleAttr(attrIndex);
}
/**
* 确保单个属性值合理
*/
private clampSingleAttr(attrIndex: number) {
switch(attrIndex) {
case Attrs.HP_MAX:
case Attrs.MP_MAX:
this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]);
break;
case Attrs.DEF:
case Attrs.AP:
case Attrs.MAP:
this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]);
break;
case Attrs.CRITICAL:
case Attrs.DODGE:
case Attrs.HIT:
this.Attrs[attrIndex] = Math.max(0, Math.min(AttrSet.ATTR_MAX, this.Attrs[attrIndex]));
break;
}
}
// ==================== 临时 BUFF/DEBUFF 更新 ====================
/**
* 更新临时 buff/debuff 的剩余时间
* @param dt 时间增量
*/
updateTemporaryBuffsDebuffs(dt: number) {
const affectedAttrs = new Set<number>();
// 更新临时型buff
for (const attrIndex in this.BUFFS_TEMP) {
const buffs = this.BUFFS_TEMP[attrIndex];
buffs.forEach(buff => {
buff.remainTime -= dt;
if (buff.remainTime <= 0) {
const index = buffs.indexOf(buff);
if (index > -1) {
buffs.splice(index, 1);
}
}
});
if (buffs.length === 0) {
delete this.BUFFS_TEMP[attrIndex];
affectedAttrs.add(parseInt(attrIndex));
}
}
// 负面状态更新
for (const key in this.NeAttrs) {
const debuff = this.NeAttrs[key];
debuff.remainTime -= dt;
if (debuff.remainTime <= 0) {
debuff.remainTime = 0;
}
}
// 只重新计算受影响的属性
affectedAttrs.forEach(attrIndex => {
this.recalculateSingleAttr(attrIndex);
});
}
// ==================== BUFF 辅助方法 ====================
/**
* 清空buff
* @param attrIndex 属性索引如果为空则清理所有buff
* @param isBuff true时清理value>0的增益bufffalse时清理value<0的减益buff
*/
clearBuffs(attrIndex?: number, isBuff: boolean = true): void {
if (attrIndex === undefined) {
for (const attrIndex in this.BUFFS_TEMP) {
this.clearBuffsForAttr(parseInt(attrIndex), isBuff);
}
} else {
this.clearBuffsForAttr(attrIndex, isBuff);
}
}
/**
* 清理指定属性的buff
* @param attrIndex 属性索引
* @param isBuff true清理增益bufffalse清理减益buff
*/
private clearBuffsForAttr(attrIndex: number, isBuff: boolean): void {
const buffContainer = this.BUFFS_TEMP;
if (!buffContainer[attrIndex]) return;
buffContainer[attrIndex] = buffContainer[attrIndex].filter(buff => {
const shouldClear = isBuff ? buff.value > 0 : buff.value < 0;
return !shouldClear;
});
if (buffContainer[attrIndex].length === 0) {
delete buffContainer[attrIndex];
}
this.recalculateSingleAttr(attrIndex);
}
// ==================== NeAttrs负面状态管理 ====================
/**
* 清理单个NeAttr负面状态
*/
clearNeAttr(neAttrIndex: number): void {
if (this.NeAttrs[neAttrIndex]) {
this.NeAttrs[neAttrIndex].value = 0;
this.NeAttrs[neAttrIndex].time = 0;
}
}
/**
* 清理所有NeAttrs负面状态
*/
clearAllNeAttrs(): void {
for (const key in this.NeAttrs) {
this.NeAttrs[key].value = 0;
this.NeAttrs[key].time = 0;
}
}
/**
* 检查是否处于眩晕状态
*/
public isStun(): boolean {
return this.NeAttrs[NeAttrs.IN_STUN]?.time > 0;
}
/**
* 检查是否处于冰冻状态
*/
public isFrost(): boolean {
return this.NeAttrs[NeAttrs.IN_FROST]?.time > 0;
}
reset() {
// 重置为初始状态
this.hero_uuid = 1001;
this.hero_name = "hero";
this.lv = 1;
this.type = 0;
this.fac = 0;
this.base_ap = 0;
this.base_map = 0;
this.base_def = 5;
this.base_hp = 100;
this.base_mp = 100;
this.base_speed = 100;
this.base_dis = 100;
this.hp = 100;
this.mp = 100;
this.shield = 0;
this.Attrs = [];
this.NeAttrs = [];
this.BUFFS = {};
this.BUFFS_TEMP = {};
this.is_dead = false;
this.is_count_dead = false;
this.is_boss = false;
this.is_big_boss = false;
this.is_master = false;
this.is_friend = false;
this.is_kalami = false;
this.atk_count = 0;
this.atked_count = 0;
this.skills = [];
}
}
/**
* ==================== 英雄属性更新系统 ====================
*
* 按照 ECS 设计理念:
* - ComponentHeroModelComp存储数据
* - SystemHeroAttrSystem处理业务逻辑
*
* 系统职责:
* 1. 每帧更新临时 Buff时间递减过期移除
* 2. 每帧更新 HP/MP 自然回复
* 3. 限制属性值在合理范围内
*
* 使用方式:
* 在 RootSystem 中注册此系统,它会自动每帧更新所有拥有 HeroModelComp 的实体
*/
export class HeroAttrSystem extends ecs.ComblockSystem
implements ecs.ISystemUpdate, ecs.IEntityEnterSystem, ecs.ISystemFirstUpdate {
// ==================== 调试统计(可选)====================
private entityCount: number = 0; // 本帧处理的实体数
private frameCount: number = 0; // 总帧数
private debugMode: boolean = false; // 是否启用调试模式
/**
* 过滤器:只处理拥有 HeroModelComp 的实体
*/
filter(): ecs.IMatcher {
return ecs.allOf(HeroModelComp);
}
/**
* 实体首次进入系统时调用(每个实体只调用一次)
*/
entityEnter(e: ecs.Entity): void {
const model = e.get(HeroModelComp);
if (!model) return;
console.log(`[HeroAttrSystem] 英雄进入系统: ${model.hero_name} (uuid: ${model.hero_uuid})`);
}
/**
* 系统首次更新前调用(整个系统只调用一次)
*/
firstUpdate(): void {
console.log("[HeroAttrSystem] 系统首次更新");
}
/**
* 每帧更新(为每个英雄调用一次)
*
* ⭐ 关键理解:
* - 如果有 3 个英雄,这个方法每帧会被调用 3 次
* - 每次调用处理不同的实体 e
* - 这是正确的设计,不是 bug
*/
update(e: ecs.Entity): void {
const model = e.get(HeroModelComp);
if (!model || model.is_dead) return;
// 统计:记录本帧处理的实体数
this.entityCount++;
// 调试日志(可选,调试时启用)
if (this.debugMode) {
console.log(` [${this.entityCount}] 更新英雄: ${model.hero_name}, HP: ${model.hp.toFixed(2)}`);
}
// 1. 更新临时 Buff/Debuff时间递减过期自动移除
model.updateTemporaryBuffsDebuffs(this.dt);
// 2. HP/MP 自然回复(业务规则)
model.mp += HeroUpSet.MP * this.dt;
model.hp += HeroUpSet.HP * this.dt;
// 3. 限制属性值在合理范围内
if (model.mp > model.Attrs[Attrs.MP_MAX]) {
model.mp = model.Attrs[Attrs.MP_MAX];
}
if (model.hp > model.Attrs[Attrs.HP_MAX]) {
model.hp = model.Attrs[Attrs.HP_MAX];
}
// 每 60 帧输出一次统计
this.frameCount++;
if (this.frameCount % 60 === 0 && this.entityCount === 1) {
console.log(`[HeroAttrSystem] 第 ${this.frameCount} 帧,处理 ${this.entityCount} 个英雄`);
}
// 注意:显示更新由 HeroViewComp 负责,这里只处理数据
}
/**
* 启用调试模式(调试时使用)
*/
enableDebug() {
this.debugMode = true;
}
/**
* 禁用调试模式(正式运行)
*/
disableDebug() {
this.debugMode = false;
}
}
/**
* ==================== 英雄战斗系统 ====================
*
* 按照 ECS 设计理念:
* - ComponentHeroModelComp存储数据
* - SystemHeroBattleSystem处理战斗业务逻辑
*
* 系统职责:
* 1. 处理角色被攻击逻辑(伤害计算、暴击判定、闪避判定)
* 2. 处理护盾吸收逻辑
* 3. 处理角色死亡逻辑
* 4. 处理战斗状态管理
*
* 使用方式:
* 在 RootSystem 中注册此系统,它会自动处理所有拥有 HeroModelComp 的实体的战斗逻辑
*/
export class HeroBattleSystem extends ecs.ComblockSystem
implements ecs.ISystemUpdate, ecs.IEntityEnterSystem {
private debugMode: boolean = false; // 是否启用调试模式
/**
* 过滤器:只处理拥有 HeroModelComp 的实体
*/
filter(): ecs.IMatcher {
return ecs.allOf(HeroModelComp);
}
/**
* 实体首次进入系统时调用(每个实体只调用一次)
*/
entityEnter(e: ecs.Entity): void {
const model = e.get(HeroModelComp);
if (!model) return;
console.log(`[HeroBattleSystem] 英雄进入战斗系统: ${model.hero_name} (uuid: ${model.hero_uuid})`);
}
/**
* 处理角色被攻击
* @param target 被攻击的目标实体
* @param remainingDamage 基础伤害值
* @param attackerAttrs 攻击者的属性
* @param skillId 技能ID
* @returns 实际造成的伤害
*/
public doAttack(target: ecs.Entity, remainingDamage: number, attackerAttrs: any, skillId: number): number {
const targetModel = target.get(HeroModelComp);
if (!targetModel || targetModel.is_dead) return 0;
// 获取技能配置
const skillConf = SkillSet[skillId];
// 触发被攻击事件
this.onAttacked(target);
// 闪避判定
if (this.checkDodge(targetModel)) {
return 0;
}
// 暴击判定
const isCrit = this.checkCrit(attackerAttrs[Attrs.CRITICAL]);
let damage = remainingDamage;
if (isCrit) {
damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + attackerAttrs[Attrs.CRITICAL_DMG]) / 100));
}
// 伤害计算考虑易伤等debuff
damage = this.calculateDamage(targetModel, damage);
// 护盾吸收
damage = this.absorbShield(targetModel, damage);
if (damage <= 0) return 0;
// 应用伤害
targetModel.hp -= damage;
targetModel.atked_count++;
// 检查死亡
if (targetModel.hp <= 0) {
this.doDead(target);
}
if (this.debugMode) {
console.log(`[HeroBattleSystem] ${targetModel.hero_name} 受到 ${damage} 点伤害 (暴击: ${isCrit})`);
}
return damage;
}
/**
* 处理角色死亡
*/
private doDead(entity: ecs.Entity): void {
const model = entity.get(HeroModelComp);
if (!model || model.is_dead) return;
model.is_dead = true;
// 触发死亡事件
this.onDeath(entity);
if (this.debugMode) {
console.log(`[HeroBattleSystem] ${model.hero_name} 死亡`);
}
}
/**
* 闪避判定
*/
private checkDodge(model: HeroModelComp): boolean {
if (model.Attrs[Attrs.DODGE] > 0) {
const random = Math.random() * 100;
if (random < model.Attrs[Attrs.DODGE]) {
if (this.debugMode) {
console.log(`[HeroBattleSystem] ${model.hero_name} 闪避了攻击`);
}
return true;
}
}
return false;
}
/**
* 暴击判定
*/
private checkCrit(critRate: number): boolean {
if (critRate > 0) {
const random = Math.random() * 100;
return random < critRate;
}
return false;
}
/**
* 伤害计算考虑易伤等debuff
*/
private calculateDamage(model: HeroModelComp, baseDamage: number): number {
// 这里可以添加易伤等debuff的计算逻辑
// 例如如果目标有易伤buff增加受到的伤害
return baseDamage;
}
/**
* 护盾吸收伤害
*/
private absorbShield(model: HeroModelComp, damage: number): number {
if (model.shield <= 0) return damage;
if (model.shield >= damage) {
model.shield -= damage;
if (model.shield <= 0) {
model.shield = 0;
model.Attrs[Attrs.SHIELD_MAX] = 0;
}
return 0;
} else {
const remainingDamage = damage - model.shield;
model.shield = 0;
model.Attrs[Attrs.SHIELD_MAX] = 0;
return remainingDamage;
}
}
/**
* 被攻击时触发的事件
*/
private onAttacked(entity: ecs.Entity): void {
const model = entity.get(HeroModelComp);
if (!model || model.is_dead) return;
// 这里可以添加被攻击时的特殊处理逻辑
if (model.fac === FacSet.MON) return;
// 例如:触发某些天赋效果、反击逻辑等
}
/**
* 死亡时触发的事件
*/
private onDeath(entity: ecs.Entity): void {
const model = entity.get(HeroModelComp);
if (!model) return;
if (model.fac === FacSet.MON) {
// 怪物死亡处理
this.scheduleDrop(entity);
} else if (model.fac === FacSet.HERO) {
// 英雄死亡处理
this.scheduleHeroDeath(entity);
}
}
/**
* 延迟执行掉落逻辑
*/
private scheduleDrop(entity: ecs.Entity): void {
// 这里可以添加掉落逻辑
// 例如:延迟一段时间后生成掉落物品
}
/**
* 延迟执行英雄死亡逻辑
*/
private scheduleHeroDeath(entity: ecs.Entity): void {
// 这里可以添加英雄死亡的特殊处理
// 例如:触发游戏结束、复活机制等
}
/**
* 启用调试模式
*/
enableDebug() {
this.debugMode = true;
}
/**
* 禁用调试模式
*/
disableDebug() {
this.debugMode = false;
}
/**
* 系统更新(每帧调用)
* 可以在这里添加需要每帧处理的战斗逻辑
*/
update(e: ecs.Entity): void {
// 这里可以添加需要每帧处理的战斗逻辑
// 例如:持续伤害、战斗状态检查等
}
}