refactor(hero): 移除天赋系统和相关属性,简化英雄架构

- 删除 SCDSystem、HeroAttrSystem 等独立系统,将功能整合到现有组件
- 移除 TalComp 天赋组件及相关配置(TalSet、AttrSet、CardSet)
- 清理 HeroAttrs 中未使用的属性枚举,保留核心战斗属性
- 简化 Hero 实体创建逻辑,不再为主角挂载天赋组件
- 移除 SingletonModuleComp 中与天赋、经验、收集相关的数据管理
This commit is contained in:
panw
2026-03-11 17:32:29 +08:00
parent b354c7ed9a
commit 350bbafcfb
12 changed files with 44 additions and 2520 deletions

View File

@@ -5,12 +5,7 @@ import { GameMap } from "../map/GameMap";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
import { GameEvent } from "./config/GameEvent";
import * as exp from "constants";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { Attrs, GameScoreStats } from "./config/HeroAttrs";
import { count, time } from "console";
import { getLevelExp } from "../map/RogueConfig";
import { FightSet } from "./config/GameSet";
import { GameScoreStats } from "./config/HeroAttrs";
import { mLogger } from "./Logger";
/**
* 用远程数据覆盖本地数据(统一方法)
@@ -107,126 +102,13 @@ export class SingletonModuleComp extends ecs.Comp {
elite_kill_count: 0, // 精英怪击杀数量
boss_kill_count: 0, // Boss击杀数
} as GameScoreStats,
hero:{
name:'',
path:'',
as:0,
type:0,
lv:1,
exp:0,
exp_max:100,
exp_pre:0,
hp:50,
hp_max:100,
mp:50,
mp_max:100,
def:0,
ap:0,
crt:0,
dodge:0,
skills:[],
buff:[],
tal:[],
info:'',
},
// 收集记录
collection: {
talents: {} as Record<number, number>,
skill: {uuid:0,count:0},
friend:{uuid:0,count:0},
},
gold: 0, // 金币数据MVVM绑定字段
};
// 全局属性加成 {attrIndex: [value, count]}
global_attrs: Record<number, [number, number]> = {
[Attrs.AP]: [1, 0], // 攻击力
[Attrs.HP_MAX]: [10, 10], // 生命上限
[Attrs.DEF]: [1, 0], // 防御
[Attrs.DODGE]: [1, 0], // 闪避率
[Attrs.CRITICAL]: [1, 0], // 暴击率
[Attrs.STUN_CHANCE]: [1, 0], // 眩晕率
[Attrs.WFUNY]: [1, 0], // 风怒率
};
/** 主角实体引用 */
// role: ecs.Entity | null = null;
/**
* 记录天赋获取
* @param id 天赋ID
*/
addTalentRecord(id: number) {
if (!this.vmdata.collection.talents[id]) {
this.vmdata.collection.talents[id] = 0;
}
this.vmdata.collection.talents[id]++;
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录天赋获取: ID=${id}, 次数=${this.vmdata.collection.talents[id]}`);
oops.message.dispatchEvent(GameEvent.UpdateCollection);
}
/**
* 记录技能获取
* @param id 技能ID
*/
addSkillRecord(id: number) {
if (!this.vmdata.collection.skill.uuid) {
this.vmdata.collection.skill.uuid = id;
}
this.vmdata.collection.skill.count++;
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录技能获取: ID=${id}, 次数=${this.vmdata.collection.skill.count}`);
oops.message.dispatchEvent(GameEvent.UpdateCollection);
}
/**
* 记录好友获取
* @param id 好友ID
*/
addFriendHero(id: number) {
if (!this.vmdata.collection.friend.uuid) {
this.vmdata.collection.friend.uuid = id;
}
this.vmdata.collection.friend.count++;
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录好友获取: ID=${id}, 次数=${this.vmdata.collection.friend.count}`);
oops.message.dispatchEvent(GameEvent.UpdateCollection);
}
/**
* 增加经验并处理升级逻辑
* @param exp 获得的经验值
*/
addExp(exp: number) {
if (exp <= 0) return;
const h = this.vmdata.hero;
// 确保等级至少为1
if (h.lv < 1) h.lv = 1;
// 确保经验上限正确
if (h.exp_max <= 0) h.exp_max = getLevelExp(h.lv);
h.exp += exp;
// 检查升级
let isLevelUp = false;
while (h.exp >= h.exp_max) {
h.exp -= h.exp_max;
h.lv++;
isLevelUp = true;
// 更新下一级所需经验
h.exp_max = getLevelExp(h.lv);
mLogger.log(this.debugMode, 'SMC', `[SMC] 升级! Lv.${h.lv - 1} -> Lv.${h.lv}, 下级所需: ${h.exp_max}`);
}
h.exp_pre=Math.round(h.exp/h.exp_max*100)
if (isLevelUp) {
mLogger.log(this.debugMode, 'SMC', `[SMC] 触发升级事件: Lv.${h.lv}`);
// 发送升级事件
oops.message.dispatchEvent(GameEvent.CanUpdateLv, { lv: h.lv });
}
}
vmAdd() {
VM.add(this.vmdata, "data");
// mLogger.log(this.debugMode, 'SMC', "[MissionComp]局内数据初始化",smc.vmdata.mission_data)
@@ -332,19 +214,7 @@ export class SingletonModuleComp extends ecs.Comp {
return true
}
// 设置单个出战英雄
updateFihgtHero(heroId: number) {
this.fight_hero = heroId;
if(this.isWxClient()){
let res = this.updateCloudData()
if (res){
return true
}else{
return false
}
}
return true
}
updateGold(gold:number, is_sync: boolean = true){
this.vmdata.gold += gold;
if(this.isWxClient() && is_sync){
@@ -361,56 +231,6 @@ export class SingletonModuleComp extends ecs.Comp {
return true
}
/**
* 更新主角英雄数据到 VM
* @param heroAttrs 英雄属性组件
*/
updateHeroInfo(heroAttrs: HeroAttrsComp) {
if (!heroAttrs || !heroAttrs.is_master) return;
const h = this.vmdata.hero;
// 基础信息
h.name = heroAttrs.hero_name;
h.type = heroAttrs.type;
// 防止 ECS 旧数据覆盖 VM 新数据 (如果 VM 里的等级更高,说明刚升级还没同步到 ECS)
if (heroAttrs.lv > h.lv) {
h.lv = heroAttrs.lv;
} else if (h.lv > heroAttrs.lv) {
// 此时应该反向同步?或者等待 CanUpdateLv 事件处理
// 这里暂时保持 VM 的高等级,不被 ECS 覆盖
} else {
h.lv = heroAttrs.lv;
}
// 动态属性
h.hp = Math.floor(heroAttrs.hp);
h.mp = Math.floor(heroAttrs.mp);
// 计算属性
h.hp_max = Math.floor(heroAttrs.Attrs[Attrs.HP_MAX] || 0);
h.mp_max = Math.floor(heroAttrs.Attrs[Attrs.MP_MAX] || 0);
h.def = Math.floor(heroAttrs.Attrs[Attrs.DEF] || 0);
h.ap = Math.floor(heroAttrs.Attrs[Attrs.AP] || 0);
h.dodge = Math.floor(heroAttrs.Attrs[Attrs.DODGE] || 0);
h.crt = Math.floor(heroAttrs.Attrs[Attrs.CRITICAL] || 0);
h.as = Math.floor(heroAttrs.Attrs[Attrs.AS] || 0);
// 强制触发 VM 更新
// 如果 VM 监听的是 smc.vmdata.hero 的属性变化,上面的赋值应该有效。
// 但如果 UI 绑定的是 hero 整体对象,或者因为深层监听问题,可能需要手动通知。
// 为了保险,我们可以重新赋值一次(如果是对象引用可能不会触发),或者使用 VM 提供的 set 方法
// 这里尝试直接重新赋值整个对象属性来触发更新,或者假设 VM 已经处理好了深层监听。
// 如果 UI 没变,可能是 VM 没有检测到深层属性变化。
// 尝试手动通知或重新赋值关键路径
// 注意Oops Framework 的 VM 通常支持对象属性修改的监听,前提是初始化时已经建立了监听。
// 这里我们尝试显式调用 VM.modify 来通知更新(如果有这个 API或者重新赋值给 vmdata
// 方案:重新设置 vmdata.hero 来触发根节点的更新通知
this.vmdata.hero = h;
}
error(){
oops.gui.toast("数据处理异常,请重试或重新登录")
}

View File

@@ -1,56 +0,0 @@
import { Attrs } from "./HeroAttrs";
export interface AttrInfo {
uuid: number;
icon:string; // 属性图标
attr: Attrs; // 属性类型
value: number;// 属性值
desc: string;// 属性描述
isSpecial: boolean;// 是否为特殊属性
name?: string;// 属性备注
}
export const AttrCards: Record<number, AttrInfo> = {
//*一阶 */
2001:{uuid:2001, icon:"1020", attr: Attrs.AP, value: 20, desc: "攻击力 +20%", isSpecial: false,name: "攻击" },
2002:{uuid:2002, icon:"1020", attr: Attrs.HP_MAX, value: 20, desc: "生命上限 +20%", isSpecial: false,name: "生命" },
2003:{uuid:2003, icon:"1020", attr: Attrs.DEF, value: 20, desc: "防御力 +20%", isSpecial: false,name: "防御" },
2004:{uuid:2004, icon:"1020", attr: Attrs.AS, value: 5, desc: "攻击速度 +5%", isSpecial: false,name: "攻速" },
2005:{uuid:2007, icon:"1020", attr: Attrs.CRITICAL_DMG, value: 10, desc: "暴击伤害 +10%", isSpecial: true,name: "爆伤" },
2006:{uuid:2013, icon:"1020", attr: Attrs.DODGE, value: 5, desc: "闪避率 +5%", isSpecial: true,name: "闪避" },
}
export interface PotionInfo extends AttrInfo {
duration: number; // 持续时间
}
export const PotionCards: Record<number, PotionInfo> = {
// 持续时间20秒的强力药水
3001: { uuid: 3001, icon: "1020", attr: Attrs.AP, value: 100, desc: "15秒内攻击力 +100%", isSpecial: false,name: "狂暴药水", duration: 15 },
3002: { uuid: 3002, icon: "1020", attr: Attrs.AS, value: 100, desc: "15秒内攻速 +100%", isSpecial: false,name: "急速药水", duration: 15 },
3003: { uuid: 3003, icon: "1020", attr: Attrs.DEF, value: 100, desc: "15秒内防御 +100%", isSpecial: false,name: "防御药水", duration: 15 },
3004: { uuid: 3004, icon: "1020", attr: Attrs.SPEED, value: 100, desc: "15秒内移速 +100%", isSpecial: false,name: "神行药水", duration: 15 },
// 持续时间60秒的普通药水
3005: { uuid: 3005, icon: "1020", attr: Attrs.AP, value: 25, desc: "60秒内攻击力 +25%", isSpecial: false,name: "力量药剂", duration: 60 },
3006: { uuid: 3006, icon: "1020", attr: Attrs.AS, value: 25, desc: "60秒内攻速 +25%", isSpecial: false,name: "敏捷药剂", duration: 60 },
3007: { uuid: 3007, icon: "1020", attr: Attrs.DEF, value: 25, desc: "60秒内防御 +25%", isSpecial: false,name: "护甲药剂", duration: 60 },
3008: { uuid: 3008, icon: "1020", attr: Attrs.SPEED, value: 25, desc: "60秒内移速 +25%", isSpecial: false,name: "轻灵药剂", duration: 60 },
// 闪避药水
3009: { uuid: 3009, icon: "1020", attr: Attrs.DODGE, value: 100, desc: "20秒内闪避率 +100%", isSpecial: false,name: "残影药水", duration: 15 },
3010: { uuid: 3010, icon: "1020", attr: Attrs.DODGE, value: 25, desc: "60秒内闪避率 +25%", isSpecial: false,name: "闪避药剂", duration: 60 },
// 回血药水
3011: { uuid: 3011, icon: "1020", attr: Attrs.HP_REGEN, value: 30, desc: "每5秒回血生命最大值的30%", isSpecial: false,name: "生命药水", duration: 15 },
3012: { uuid: 3012, icon: "1020", attr: Attrs.HP_REGEN, value: 10, desc: "每5秒回血生命最大值的10%", isSpecial: false,name: "回春药剂", duration: 60 },
};
export const CanSelectAttrs: Record<number, number[]> = {
// 1阶属性
1: [2001, 2002, 2003, 2004, 2005, 2006],
};
export const CanSelectPotions: Record<number, number[]> = {
// 全药水
1: [3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012]
};

View File

@@ -1,372 +0,0 @@
import { AttrCards, AttrInfo, CanSelectAttrs, PotionCards, CanSelectPotions } from "./AttrSet";
import { talConf, ItalConf, CanSelectTalents } from "./TalSet";
import { SkillSet, SkillConfig, CanSelectSkills } from "./SkillSet";
import { HeroInfo, heroInfo, CanSelectHeros } from "./heroSet";
import { CardType, CardKind } from "./GameSet";
/**
* 获取等级对应的奖励类型
* @param level 当前等级
* @returns 奖励类型 CardType
*/
export function getLevelRewardType(level: number): CardType {
switch (level) {
case 2: return CardType.Talent;
case 3: return CardType.Attr;
case 4: return CardType.Attr;
case 5: return CardType.Talent;
case 6: return CardType.Attr;
case 7: return CardType.Attr;
case 8: return CardType.Attr;
case 9: return CardType.Attr;
case 10: return CardType.Talent;
case 11: return CardType.Attr;
case 12: return CardType.Attr;
case 13: return CardType.Attr;
case 14: return CardType.Attr;
case 15: return CardType.Talent;
case 16: return CardType.Attr;
case 17: return CardType.Attr;
case 18: return CardType.Attr;
case 19: return CardType.Attr;
case 20: return CardType.Attr;
default:
return null
}
}
/**
* 统一卡牌信息接口 (用于UI显示和逻辑处理)
*/
export interface ICardInfo {
uuid: number;
type: CardType;
kind: CardKind;
name: string;
desc: string;
icon: string;
weight: number; // 抽取权重
tag?: string; // 标签 (如 "special" 表示特殊属性)
payload: any; // 原始配置数据
}
/**
* 具体卡牌配置项 (内部使用)
*/
export interface IPoolItem {
id: number; // 卡牌UUID
weight: number; // 该卡牌在池中的权重
}
/**
* 等级池配置项
* 仅定义类型和权重,具体卡牌内容由各模块的 CanSelectXXX 配置决定
*/
export interface IPoolConfig {
type: CardType; // 卡牌类型
poolWeight: number; // 该类型池被选中的概率权重
tag?: string; // 辅助筛选从全池中筛选带tag的如 "special"
}
// 默认单卡权重
const DEFAULT_CARD_WEIGHT = 100;
/**
* 1-20 级卡牌池配置表
* 定义每个等级可能出现的卡牌类型及其权重
*/
export const LevelPoolConfigs: Record<number, IPoolConfig[]> = {
1: [{ type: CardType.Skill, poolWeight: 100 }],
2: [{ type: CardType.Attr, poolWeight: 100 }], // 常规属性
3: [{ type: CardType.Talent, poolWeight: 50 }, { type: CardType.Attr, poolWeight: 50, tag: "special" }], // 天赋或特殊属性
4: [{ type: CardType.Attr, poolWeight: 100 }],
5: [{ type: CardType.Talent, poolWeight: 100 }],
6: [{ type: CardType.Partner, poolWeight: 100 }], // 伙伴节点
7: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
8: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
9: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
10: [{ type: CardType.Talent, poolWeight: 100 }],
11: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
12: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
13: [{ type: CardType.Attr, poolWeight: 100 }],
14: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
15: [{ type: CardType.Talent, poolWeight: 100 }],
16: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
17: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
18: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
19: [{ type: CardType.Attr, poolWeight: 100 }],
20: [{ type: CardType.Talent, poolWeight: 100 }],
};
// ========== 卡牌池构建逻辑 ==========
/**
* 获取指定类型的卡牌信息(不含权重,仅基础信息)
*/
function getCardBaseInfo(type: CardType, uuid: number): ICardInfo | null {
let baseInfo: any = null;
let name = "";
let desc = "";
let icon = "";
let kind = CardKind.Attr;
let tag = undefined;
switch (type) {
case CardType.Attr:
baseInfo = AttrCards[uuid];
if (!baseInfo) return null;
name = baseInfo.name || "属性";
desc = baseInfo.desc;
icon = baseInfo.icon;
kind = CardKind.Attr;
tag = baseInfo.isSpecial ? "special" : undefined;
break;
case CardType.Talent:
baseInfo = talConf[uuid];
if (!baseInfo) return null;
name = baseInfo.name;
desc = baseInfo.desc;
icon = baseInfo.icon;
kind = baseInfo.kind;
break;
case CardType.Skill:
baseInfo = SkillSet[uuid];
if (!baseInfo) return null;
name = baseInfo.name;
desc = baseInfo.info;
icon = baseInfo.icon;
kind = CardKind.Skill;
break;
case CardType.Partner:
baseInfo = HeroInfo[uuid];
if (!baseInfo) return null;
name = baseInfo.name;
desc = baseInfo.info;
icon = baseInfo.icon;
kind = CardKind.Partner;
break;
case CardType.Potion:
baseInfo = PotionCards[uuid];
if (!baseInfo) return null;
name = baseInfo.name || "药水";
desc = baseInfo.desc;
icon = baseInfo.icon;
kind = CardKind.Buff; // 药水归类为 Buff 类型的卡片显示
break;
}
return {
uuid,
type,
kind,
name,
desc,
icon,
weight: 0, // 基础信息不包含权重,权重由配置决定
tag,
payload: baseInfo
};
}
/**
* 获取指定类型的全量池
* 自动累加所有 <= level 的配置项
* @param type 卡牌类型
* @param level 当前等级
*/
function getDefaultPool(type: CardType, level: number = 1): IPoolItem[] {
const items: IPoolItem[] = [];
let configMap: Record<number, number[]> | null = null;
let defaultWeight = 100;
switch (type) {
case CardType.Attr:
configMap = CanSelectAttrs;
defaultWeight = 100;
break;
case CardType.Talent:
configMap = CanSelectTalents;
defaultWeight = 50;
break;
case CardType.Skill:
configMap = CanSelectSkills;
defaultWeight = 80;
break;
case CardType.Partner:
configMap = CanSelectHeros;
defaultWeight = 100;
break;
case CardType.Potion:
configMap = CanSelectPotions;
defaultWeight = 100;
break;
}
if (configMap) {
// 1. 找到符合条件的最大等级 Key (<= level 且 != 99)
let targetKey = -1;
let maxLv = -1;
Object.keys(configMap).forEach(lvlStr => {
const lv = parseInt(lvlStr);
if (lv !== 99 && lv <= level) {
if (lv > maxLv) {
maxLv = lv;
targetKey = lv;
}
}
});
// 2. 如果找到了目标等级配置,则使用该配置
if (targetKey !== -1 && configMap[targetKey]) {
const ids = configMap[targetKey];
ids.forEach(id => {
items.push({ id, weight: defaultWeight });
});
}
// 3. 如果没找到(等级过低),且存在 99 号默认配置,则使用 99 号配置
else if (configMap[99]) {
configMap[99].forEach(id => {
items.push({ id, weight: defaultWeight });
});
}
} else {
// 兜底逻辑:如果该类型没有配置表 (理论上不应发生,除非新增类型未配置)
// 这里保留原有的全量兜底逻辑作为最后的防线
switch (type) {
case CardType.Attr:
Object.keys(AttrCards).forEach(key => items.push({ id: Number(key), weight: 100 }));
break;
case CardType.Talent:
Object.keys(talConf).forEach(key => items.push({ id: Number(key), weight: 50 }));
break;
case CardType.Skill:
Object.keys(SkillSet).forEach(key => items.push({ id: Number(key), weight: 80 }));
break;
case CardType.Partner:
Object.keys(HeroInfo).forEach(key => {
const id = Number(key);
if (id < 5200) items.push({ id, weight: 100 });
});
break;
case CardType.Potion:
Object.keys(PotionCards).forEach(key => items.push({ id: Number(key), weight: 100 }));
break;
}
}
return items;
}
/**
* 根据等级获取随机卡牌选项
* @param level 当前等级
* @param count 选项数量 (默认3个)
* @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有)
* @param forcedType 强制指定卡牌类型 (用于特殊获取,如商店、技能书等)
* @param preferredAttrs 偏好的属性类型列表 (用于增加权重)
*/
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType, preferredAttrs: number[] = []): ICardInfo[] {
// 1. 确定类型:强制指定 > 默认为属性
const type = forcedType !== undefined ? forcedType : CardType.Attr;
const result: ICardInfo[] = [];
const excludeSet = new Set(excludeUuids);
// 2. 获取该类型的候选池
const rawItems = getDefaultPool(type, level);
// 3. 构建候选列表(包含完整信息)
const candidates: ICardInfo[] = [];
for (const item of rawItems) {
if (excludeSet.has(item.id)) continue;
const info = getCardBaseInfo(type, item.id);
if (info) {
info.weight = item.weight;
// 如果是属性卡,且该属性在偏好列表中,增加权重
if (type === CardType.Attr && preferredAttrs.length > 0) {
// AttrCards 的 payload 就是 AttrInfo包含 attr 字段
if (info.payload && preferredAttrs.includes(info.payload.attr)) {
// 增加权重,这里设置为 2 倍
info.weight *= 2;
}
}
candidates.push(info);
}
}
// 4. 抽取
for (let i = 0; i < count; i++) {
if (candidates.length === 0) break;
const card = weightedRandomCard(candidates);
if (card) {
result.push(card);
// 移除已选,防止重复
const idx = candidates.indexOf(card);
if (idx > -1) candidates.splice(idx, 1);
}
}
// 5. 兜底逻辑:如果数量不足,且当前类型不是属性,则用属性卡补齐
if (result.length < count && type !== CardType.Attr) {
const attrItems = getDefaultPool(CardType.Attr); // 兜底可以用全量属性池
for (const item of attrItems) {
if (result.length >= count) break;
// 检查是否已存在ID去重
if (excludeSet.has(item.id) || result.some(r => r.uuid === item.id)) continue;
const info = getCardBaseInfo(CardType.Attr, item.id);
if (info) {
info.weight = 100;
result.push(info);
}
}
}
shuffleArray(result);
return result;
}
// ========== 工具函数 ==========
function weightedRandomPool(configs: IPoolConfig[]): IPoolConfig | null {
if (!configs || configs.length === 0) return null;
const totalWeight = configs.reduce((sum, item) => sum + item.poolWeight, 0);
let randomVal = Math.random() * totalWeight;
for (const config of configs) {
randomVal -= config.poolWeight;
if (randomVal <= 0) {
return config;
}
}
return configs[configs.length - 1];
}
function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
if (!cards || cards.length === 0) return null;
const totalWeight = cards.reduce((sum, item) => sum + item.weight, 0);
let randomVal = Math.random() * totalWeight;
for (const card of cards) {
randomVal -= card.weight;
if (randomVal <= 0) {
return card;
}
}
return cards[cards.length - 1];
}
function shuffleArray(array: any[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

View File

@@ -13,8 +13,6 @@ export enum BType {
export enum NeAttrs {
IN_FROST = 0, // 冰冻状态
IN_STUN = 1, // 眩晕状态
IN_BURN = 2, // 灼烧状态
IN_POISON = 3, // 中毒状态
}
// ========== 属性枚举 ==========
@@ -26,53 +24,27 @@ export enum NeAttrs {
export enum Attrs {
// ========== 基础生存属性 (0-9) ==========
HP_MAX = 0, // 最大生命值
MP_MAX = 1, // 最大魔法值
SHIELD_MAX = 2, // 最大护盾值
HP_REGEN = 3, // 生命回复
MP_REGEN = 4, // 魔法回复
LUCK = 5, // 幸运值
SPEED = 3, // 移动速度
// ========== 攻击属性 (10-19) ==========
AP = 10, // 攻击力
DIS = 12, // 攻击距离
AS = 13, // 攻击速度减少技能skills[0]CD
SS = 14, // 技能速度 (减少skills[0] 以外的cd)
SKILL_DURATION = 15, // 技能持续时间
AREA_OF_EFFECT = 16, // 作用范围
// ========== 防御属性 (20-29) ==========
DEF = 20, // 防御 伤害减免
DODGE = 22, // 闪避率
THORNS = 25, // 反伤
CRITICAL_RES = 26, // 暴击抗性
CON_RES = 27, // 控制抗性
// ========== 暴击与命中属性 (30-39) ==========
CRITICAL = 30, // 暴击率
CRITICAL_DMG = 31, // 暴击伤害
HIT = 32, // 命中率
// ========== 特殊效果属性 (50-59) ==========
LIFESTEAL = 50, // 吸血比率
MANASTEAL = 51, // 吸蓝比率
FREEZE_CHANCE = 52, // 冰冻概率
BURN_CHANCE = 53, // 燃烧概率
STUN_CHANCE = 54, // 眩晕概率
BACK_CHANCE = 55, // 击退概率
SLOW_CHANCE = 56, // 减速概率
POISON_CHANCE = 57, // 中毒概率
SILENCE_CHANCE = 58, // 沉默概率
EXPLOSION_CHANCE = 59, // 爆炸概率
// ========== 增益效果属性 (60-69) ==========
BUFF_UP = 60, // Buff效果提升
DBUFF_UP = 61, // Debuff效果提升
SHIELD_UP = 62, // 护盾效果提升
SPEED = 63, // 移动速度加成
EXP_GAIN = 64, // 经验获取
GOLD_GAIN = 65, // 金币获取
DROP_CHANCE = 66, // 掉落率
REVIVE_COUNT = 67, // 复活次数
REVIVE_TIME = 68, // 复活时间
INVINCIBLE_TIME = 69, // 无敌时间
@@ -80,23 +52,14 @@ export enum Attrs {
// ========== 武器进化相关 (70-79) ==========
PUNCTURE = 70, // 穿刺次数
PUNCTURE_DMG = 71, // 穿刺伤害
MOVE_SPEED = 74, // 移动速度
BURN = 75, // 易伤效果
WFUNY = 77, // 风怒
// ========== 负面状态相关 (80-89) ==========
DMG_INVUL = 80, //易伤
// ========== 怪物特殊属性 (90-99)==========
ATK_TRI_RESET = 90, // 英雄攻击触发计数重置
DMG_TRI_RESET = 91, // 英雄伤害触发计数重置
BOOM = 92, // 自爆怪
}
export const defaultAttrs = {
[Attrs.BACK_CHANCE]:0,
[Attrs.DODGE]:0,
[Attrs.CON_RES]:0,
}
/**
* 初始化英雄属性对象
@@ -136,55 +99,27 @@ export const getNeAttrs = () => {
export const AttrsType: Record<Attrs, BType> = {
// ========== 基础生存属性(数值型) ==========
[Attrs.HP_MAX]: BType.VALUE, // 最大生命值 - 数值型
[Attrs.MP_MAX]: BType.VALUE, // 最大魔法值 - 数值型
[Attrs.SHIELD_MAX]: BType.VALUE, // 最大护盾值 - 数值型
[Attrs.HP_REGEN]: BType.RATIO, // 生命回复 - 百分比型
[Attrs.MP_REGEN]: BType.VALUE, // 魔法回复 - 数值型
[Attrs.LUCK]: BType.VALUE, // 幸运 - 数值型
// ========== 攻击属性(数值型) ==========
[Attrs.AP]: BType.VALUE, // 攻击力 - 数值型
[Attrs.DIS]: BType.VALUE, // 攻击距离 - 数值型
[Attrs.AS]: BType.RATIO, // 攻击速度 - 百分比型
[Attrs.SS]: BType.RATIO, // 技能速度 - 百分比型
[Attrs.SKILL_DURATION]: BType.RATIO, // 技能持续时间 - 百分比型
[Attrs.AREA_OF_EFFECT]: BType.VALUE, // 作用范围 - 数值型
// ========== 防御属性(混合类型) ==========
[Attrs.DEF]: BType.VALUE, // 物理防御 - 数值型
[Attrs.DODGE]: BType.RATIO, // 闪避率 - 百分比型
[Attrs.THORNS]: BType.RATIO, // 反伤 - 百分比型
// ========== 暴击与命中属性(百分比型) ==========
[Attrs.CRITICAL]: BType.RATIO, // 暴击率 - 百分比型
[Attrs.CRITICAL_DMG]: BType.RATIO, // 暴击伤害 - 百分比型
[Attrs.HIT]: BType.RATIO, // 命中率 - 百分比型
[Attrs.CRITICAL_RES]: BType.RATIO, // 暴击抗性 - 百分比型
[Attrs.CON_RES]: BType.RATIO, // 控制抗性 - 百分比型
// ========== 特殊效果属性(百分比型) ==========
[Attrs.LIFESTEAL]: BType.RATIO, // 吸血比率 - 百分比型
[Attrs.MANASTEAL]: BType.RATIO, // 吸蓝 - 百分比型
[Attrs.FREEZE_CHANCE]: BType.RATIO, // 冰冻概率 - 百分比型
[Attrs.BURN_CHANCE]: BType.RATIO, // 燃烧概率 - 百分比型
[Attrs.STUN_CHANCE]: BType.RATIO, // 眩晕概率 - 百分比型
[Attrs.BACK_CHANCE]: BType.RATIO, // 击退概率 - 百分比型
[Attrs.SLOW_CHANCE]: BType.RATIO, // 减速概率 - 百分比型
[Attrs.POISON_CHANCE]: BType.RATIO, // 中毒概率 - 百分比型
[Attrs.SILENCE_CHANCE]: BType.RATIO, // 沉默概率 - 百分比型
[Attrs.EXPLOSION_CHANCE]: BType.RATIO, // 爆炸概率 - 百分比型
// ========== 增益效果属性(百分比型) ==========
[Attrs.BUFF_UP]: BType.RATIO, // Buff效果提升 - 百分比型
[Attrs.DBUFF_UP]: BType.RATIO, // Debuff效果提升 - 百分比型
[Attrs.SHIELD_UP]: BType.RATIO, // 护盾效果提升 - 百分比型
[Attrs.SPEED]: BType.RATIO, // 移动速度加成 - 百分比型
[Attrs.EXP_GAIN]: BType.RATIO, // 经验获取 - 百分比型
[Attrs.GOLD_GAIN]: BType.RATIO, // 金币获取 - 百分比型
[Attrs.DROP_CHANCE]: BType.RATIO, // 掉落率 - 百分比型
[Attrs.REVIVE_COUNT]: BType.VALUE, // 复活次数 - 数值型
[Attrs.REVIVE_TIME]: BType.RATIO, // 复活时间 - 百分比型
[Attrs.INVINCIBLE_TIME]: BType.RATIO, // 无敌时间 - 百分比型
@@ -192,17 +127,7 @@ export const AttrsType: Record<Attrs, BType> = {
// ========== 武器进化相关(混合类型) ==========
[Attrs.PUNCTURE]: BType.VALUE, // 穿刺次数 - 数值型
[Attrs.PUNCTURE_DMG]: BType.RATIO, // 穿刺伤害 - 百分比型
[Attrs.MOVE_SPEED]: BType.VALUE, // 移动速度 - 数值型
[Attrs.BURN]: BType.RATIO, // 易伤效果 - 百分比型
[Attrs.WFUNY]: BType.RATIO, // 未知特殊属性 - 百分比型
// ========== 负面状态相关(混合类型) ==========
[Attrs.DMG_INVUL]: BType.RATIO, //易伤
// ========== 怪物独有特殊属性 (90-99)==========
[Attrs.ATK_TRI_RESET]: BType.BOOLEAN, // 英雄攻击触发计数重置
[Attrs.DMG_TRI_RESET]: BType.BOOLEAN, // 英雄伤害触发计数重置
[Attrs.BOOM]: BType.BOOLEAN, // 自爆怪
};

View File

@@ -1,153 +0,0 @@
import { count } from "console";
import { Attrs, BType } from "./HeroAttrs";
import { CardKind } from "./GameSet";
/**
* 天赋类型枚举,也是触发条件
*/
export enum TriType {
ATK = 1, // 等级升触发
DMG = 2, //普通攻击触发
ATKED=3,
SKILL = 4, // 技能触发
HPL = 6, // 失去生命值触发
HPA = 7, // 获得生命值触发
INIT = 8, // 初始触发,如:多1个技能
DEAD = 9, // 基于死亡触发
}
export enum TalEffet {
ATK_DMG=1, // 伤害 次数+伤害加成如额外5次 伤害+20%
SKILL_DMG=2, // 技能伤害 次数+伤害加成如额外5次 伤害+20%
DEF=10, // 减伤 次数+减伤加成如额外5次 伤害-20%
THORNS=14, //反伤 百分比 次数+反伤加成如额外5次 反伤-20%
///////////////////////////////////////////////////////////////////////
HP=3, // 回血 百分比 直接触发回血20%
SHIELD=9, // 护盾 直接触发获得20%的生命值护盾
////////////////////////////////////////////////////////////////////////
BUFF = 5, // 数值叠加 触发后清零: 暴击率,闪避率等,触发后效果取消
////////////////////////////////////////////////////////////////////////////////
ATTR=6, // 属性 永久添加
WFUNY=7, // 风怒 次数 叠加 如额外5次 风怒
D_SKILL=8, //两次技能 次数 叠加如额外5次 两次技能
C_MSKILL=11, // 必杀技能必暴 次数 叠加 如额外5次 必杀技能必暴
C_ATK=12, // 普工必爆 次数 叠加 如额外5次 普工必爆
C_SKILL=13, // 一般技能必暴 次数 叠加 如额外5次 一般技能必暴
}
export enum TalTarget {
SELF = 1, // 自己触发
ENEMY = 2, // 敌人触发
}
export enum TalAttrs {
NON=0,
FREEZE_CHANCE=Attrs.FREEZE_CHANCE, // 冰冻概率
STUN_CHANCE=Attrs.STUN_CHANCE, // 冰冻概率
BACK_CHANCE=Attrs.BACK_CHANCE, // 击退概率
SILENCE_CHANCE=Attrs.SILENCE_CHANCE, // 沉默概率
CRITICAL=Attrs.CRITICAL, // 暴击率
AP=Attrs.AP, // 攻击力
HP_MAX=Attrs.HP_MAX, // 最大生命值
THORNS=Attrs.THORNS, //反伤 百分比
REVIVE_COUNT=Attrs.REVIVE_COUNT, // 复活次数
WFUNY=Attrs.WFUNY, // 风怒
}
/**
* 天赋配置接口
* 定义一个完整的天赋效果
*/
export interface ItalConf {
uuid: number; // 天赋ID
name: string; // 天赋名称
icon:string; // 天赋图标
kind:CardKind; // 天赋类型
triType: TriType; // 天赋触发类型
target: TalTarget; // 天赋触发目标
effet: TalEffet; // 天赋触发效果
vType:BType; //数值型还是百分比型
value: number; // 触发的效果值如增加10%攻击力, 触发的技能uuid,增加1个技能uuid
attrs?:TalAttrs //触发的attrs效果的对应attrs
Trigger:number //触发值
Pts:number //添加的天赋点数
CPts:number //消耗的天赋点数
desc: string; // 天赋描述(说明触发条件和效果)
count:number //执行次数,及可以触发的次数
}
// ========== 天赋配置表 ==========
/**
* 天赋配置表 - 2行紧凑格式
* 每个天赋配置压缩为2行注释行 + 配置对象行
*
* 格式说明:
* 第1行// 天赋名称 - 英雄专属/通用 | 触发条件 | 效果描述
* 第2行{uuid, name, desc, type, triggerType, chance, t_value, e_value, e_name, e_type, e_scaling, e_count, stackable, maxStack}
*
* 使用说明:
* 1. 等级类天赋:当英雄升级到指定等级时,每次都会触发效果
* 2. 行为计数类:当特定行为累计达到阈值时触发,支持是否重置计数
* 3. 受伤计数类:当受伤累计达到阈值时触发,支持是否重置计数
* 4. 技能触发类:当特定条件满足时自动触发指定技能
*/
export const talConf: Record<number, ItalConf> = {
/*** 普通攻击触发 ***/
7001:{uuid:7001,name:"风怒",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.WFUNY,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"普通攻击3次后, 立即给与目标150%伤害的额外打击"},
7003:{uuid:7003,name:"回血",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.HP,vType:BType.RATIO, value:1,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"普通攻击3次后, 会回复1%的生命值"},
7004:{uuid:7004,name:"回血(大)",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.HP,vType:BType.RATIO, value:2,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"普通攻击5次后, 会回复2%的生命值"},
7005:{uuid:7005,name:"冰冻",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.FREEZE_CHANCE,
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定冻结目标"},
7006:{uuid:7006,name:"沉默",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.SILENCE_CHANCE,
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定沉默目标"},
7007:{uuid:7007,name:"击退",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.BACK_CHANCE,
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定击退目标"},
7008:{uuid:7008,name:"会心",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.CRITICAL,
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定获得100%的暴击率"},
7009:{uuid:7009,name:"眩晕",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.STUN_CHANCE,
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定获得100%的眩晕率"},
7010:{uuid:7010,name:"熟练",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:10,count:1,target:TalTarget.SELF,effet:TalEffet.D_SKILL,vType:BType.RATIO, value:0,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"普通攻击10次后, 下次一般技能额外释放1次,伤害100%"},
/*** 受伤触发 ***/
7101:{uuid:7101,name:"反击",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.ATK_DMG,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"被攻击3次后, 给于目标50%的伤害"},
7102:{uuid:7102,name:"护盾",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:30,count:1,target:TalTarget.SELF,effet:TalEffet.SHIELD,vType:BType.RATIO, value:20,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"被攻击30次后, 获得20%的生命值护盾"},
7103:{uuid:7103,name:"减伤",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.DEF,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"被攻击3次后, 下1次伤害减50%"},
7104:{uuid:7104,name:"复苏",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:100,count:1,target:TalTarget.SELF,effet:TalEffet.BUFF,vType:BType.VALUE, value:1,attrs:TalAttrs.REVIVE_COUNT,
Pts:2,CPts:0,desc:"被攻击100次后, 获得1次复活"},
/*** 失去血量触发 ***/
7201:{uuid:7201,name:"背水",icon:"7001",kind:CardKind.Hp,triType:TriType.HPL,Trigger:50,count:10,target:TalTarget.SELF,effet:TalEffet.C_ATK,vType:BType.VALUE, value:0,attrs:TalAttrs.NON,
Pts:2,CPts:0,desc:"每失去50%生命值,获得下10次普通攻击暴击"},
/*** 7400 回血量触发 ***/
/** 7500 一击必杀触发 */
};
export const CanSelectTalents: Record<number, number[]> = {
// 3级开放攻击类天赋
3: [7001, 7003, 7005, 7008],
// 5级必出防御类
5: [7101, 7102, 7103],
// 9级混合
9: [7001, 7003, 7005, 7008, 7101, 7102, 7103],
// 20级终极天赋
20: [7301, 7302],
// 默认全开
99: [7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7101, 7102, 7103, 7104, 7201, 7301, 7302]
};
// ========== 工具函数 ==========

View File

@@ -3,7 +3,6 @@ import { BoxSet, FacSet } from "./GameSet"
import { smc } from "../SingletonModuleComp"
import { BuffConf, SkillRange } from "./SkillSet"
import { Interface } from "readline"
import { TriType } from "./TalSet"
export enum AttrSet {
ATTR_MAX = 85,
@@ -94,11 +93,6 @@ export enum HRegen {
HP=0.5
}
export interface ITalPts {
TriType: TriType; //触发添加天赋点数类型
TriVal: number; //触发添加天赋点需求数值
TalPts: number; //添加天赋点数值
}
/**
* 不同职业升级属性加成配置
* 战士:高血量成长,低攻击成长
@@ -137,8 +131,6 @@ export interface heroInfo {
speed: number; // 移动速度(像素/秒)
skills: number[]; // 携带技能ID列表
buff: BuffConf[]; // 自带buff配置通常为空由技能动态添加
tal: number[]; // 天赋ID列表
talPts?: ITalPts[]; // 天赋触发配置
info: string; // 描述文案
}
@@ -159,37 +151,37 @@ export const HeroInfo: Record<number, heroInfo> = {
5001:{uuid:5001,name:"盾战士",icon:"1001",path:"hk1", fac:FacSet.HERO, kind:1,as:1,
type:HType.warrior,lv:1,hp:300,mp:200,def:5,ap:25,speed:120,skills:[6001,6004],
rangeType: SkillRange.Melee,
buff:[],tal:[],talPts:[{TriType:TriType.ATK,TriVal:1,TalPts:1}],info:"盾战士"},
buff:[],info:"盾战士"},
5002:{uuid:5002,name:"奥术法师",icon:"1001",path:"hm2", fac:FacSet.HERO, kind:2,as:1,
type:HType.mage,lv:1,hp:150,mp:135,def:0,ap:40,speed:95,skills:[6003,6101],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"奥术法师"},
buff:[],info:"奥术法师"},
5003:{uuid:5003,name:"射手",icon:"1001",path:"ha1", fac:FacSet.HERO, kind:2,as:1,
type:HType.remote,lv:1,hp:180,mp:80,def:0,ap:30,speed:140,skills:[6002,6100],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"射手"},
buff:[],info:"射手"},
5005:{uuid:5005,name:"牧师",icon:"1001",path:"hh1", fac:FacSet.HERO, kind:2,as:1,
type:HType.mage,lv:1,hp:160,mp:135,def:0,ap:25,speed:100,skills:[6003,6100],
rangeType: SkillRange.Mid,
buff:[],tal:[],info:"牧师"},
buff:[],info:"牧师"},
5004:{uuid:5004,name:"火焰法师",icon:"1001",path:"hm1", fac:FacSet.HERO, kind:2,as:1,
type:HType.mage,lv:1,hp:150,mp:140,def:0,ap:45,speed:90,skills:[6003,6101],
rangeType: SkillRange.Mid,
buff:[],tal:[],info:"火焰法师"},
buff:[],info:"火焰法师"},
5006:{uuid:5006,name:"召唤法师",icon:"1001",path:"hz1", fac:FacSet.HERO, kind:2,as:1,
type:HType.support,lv:1,hp:200,mp:145,def:0,ap:20,speed:105,skills:[6003,6101],
rangeType: SkillRange.Mid,
buff:[],tal:[],info:"召唤法师"},
buff:[],info:"召唤法师"},
5007:{uuid:5007,name:"刺客",icon:"1001",path:"hc1", fac:FacSet.HERO, kind:1,as:1,
type:HType.assassin,lv:1,hp:140,mp:60,def:0,ap:50,speed:180,skills:[6001,6004],
rangeType: SkillRange.Melee,
buff:[],tal:[],info:"刺客"},
buff:[],info:"刺客"},
@@ -198,48 +190,48 @@ export const HeroInfo: Record<number, heroInfo> = {
5201:{uuid:5201,name:"兽人战士",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.warrior,lv:1,hp:60,mp:100,def:0,ap:8,speed:180,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"标准炮灰确保英雄能完成3次普攻积累天赋计数"},
buff:[],info:"标准炮灰确保英雄能完成3次普攻积累天赋计数"},
// 2. 快速突击型
5301:{uuid:5301,name:"兽人斥候",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:1.2,
type:HType.assassin,lv:1,hp:40,mp:100,def:0,ap:12,speed:400,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"},
buff:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"},
// 3. 重型坦克型
5401:{uuid:5401,name:"兽人卫士",icon:"1001",path:"mo3", fac:FacSet.MON, kind:1,as:5.0,
type:HType.warrior,lv:1,hp:200,mp:100,def:5,ap:15,speed:60,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"},
buff:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"},
// 4. 远程骚扰型
5501:{uuid:5501,name:"兽人射手",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.remote,lv:1,hp:50,mp:100,def:0,ap:10,speed:90,skills:[6203],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"},
buff:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"},
// 5. 特殊机制型
5601:{uuid:5601,name:"兽人自爆兵",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.assassin,lv:1,hp:80,mp:100,def:0,ap:200,speed:220,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"},
buff:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"},
// 召唤师:持续召唤小怪(后续可在技能系统中实现 SType.zhaohuan
5602:{uuid:5602,name:"兽人召唤师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.mage,lv:1,hp:150,mp:300,def:5,ap:10,speed:100,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"},
buff:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"},
// 治疗者:为周围怪物回血(此处以提升治疗效果和生命回复为基础被动)
5603:{uuid:5603,name:"兽人祭司",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.support,lv:1,hp:150,mp:300,def:5,ap:10,speed:105,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"},
buff:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"},
// 光环怪为周围怪物提供增益此处以Buff效果提升与移动速度提升为基础被动
// Attrs.BUFF_UP=60 (RATIO=1)Attrs.SPEED=63 (RATIO=1)
5604:{uuid:5604,name:"兽人图腾师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
type:HType.support,lv:1,hp:150,mp:250,def:5,ap:10,speed:110,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"战术目标:提供加速光环,改变怪群推进节奏"},
buff:[],info:"战术目标:提供加速光环,改变怪群推进节奏"},
// 6. 精英/BOSS型
5701:{uuid:5701,name:"兽人首领(BOSS)",icon:"1001",path:"mo4", fac:FacSet.MON, kind:1,as:2.5,
type:HType.warrior,lv:3,hp:2000,mp:500,def:10,ap:60,speed:120,skills:[6003],
rangeType: SkillRange.Long,
buff:[],tal:[],info:"终极考验极高HP检测大招重置与辐射协同输出"},
buff:[],info:"终极考验极高HP检测大招重置与辐射协同输出"},
};

View File

@@ -10,7 +10,6 @@ import { GameEvent } from "../common/config/GameEvent";
import { getNeAttrs, getAttrs ,Attrs, defaultAttrs} from "../common/config/HeroAttrs";
import { HeroSkillsComp } from "./HeroSkills";
import { HeroMoveComp } from "./HeroMove";
import { TalComp } from "./TalComp";
import { mLogger } from "../common/Logger";
import { HeroMasterComp } from "./HeroMasterComp";
/** 角色实体 */
@@ -21,7 +20,6 @@ export class Hero extends ecs.Entity {
HeroSkills!: HeroSkillsComp;
View!: HeroViewComp;
HeroMove!: HeroMoveComp;
TalComp!: TalComp;
debugMode: boolean = false; // 是否启用调试模式
protected init() {
this.addComponents<ecs.Comp>(
@@ -41,7 +39,6 @@ export class Hero extends ecs.Entity {
this.remove(HeroViewComp);
this.remove(HeroAttrsComp);
this.remove(HeroSkillsComp);
this.remove(TalComp);
this.remove(HeroMasterComp)
super.destroy();
}
@@ -85,16 +82,6 @@ export class Hero extends ecs.Entity {
model.is_friend = is_friend
model.rangeType = hero.rangeType;
// 只有主角才挂载天赋组件
if (is_master) {
mLogger.log(this.debugMode,`[Hero] 主角创建, uuid=${uuid},is_master=${is_master}`);
this.add(TalComp);
this.add(HeroMasterComp)
const talComp = this.get(TalComp);
if (talComp) {
talComp.init(uuid);
}
model.initEvent();
}
// ✅ 初始化技能数据(迁移到 HeroSkillsComp
skillsComp.initSkills(hero.skills, uuid);

View File

@@ -6,7 +6,6 @@ import { BuffConf, SkillRange } from "../common/config/SkillSet";
import { HeroInfo, AttrSet, HType, JobUpConf } from "../common/config/heroSet";
import { HeroSkillsComp } from "./HeroSkills";
import { smc } from "../common/SingletonModuleComp";
import { AttrCards, PotionCards } from "../common/config/AttrSet";
import { mLogger } from "../common/Logger";
import { _decorator } from "cc";
@@ -29,23 +28,14 @@ export class HeroAttrsComp extends ecs.Comp {
fac: number = 0; // 0:hero 1:monster
rangeType:SkillRange = SkillRange.Melee;
// ==================== 基础属性(有初始值) ====================
base_ap: 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; // 当前魔法值
ap: number = 0; // 基础攻击
hp: number = 100; // 基础血量
hp_max: number = 100; // 最大血量
speed: number = 100; // 基础移动速度
dis: number = 100; // 基础距离
shield: number = 0; // 当前护盾
Attrs: any = []; // 最终属性数组经过Buff计算后
NeAttrs: any = []; // 负面状态数组
//计数型天赋buff
Talents: Record<number, talTrigger> = {};
//数值型天赋buff
BUFFS_TAL: Record<number, {count:number,BType:BType,attrIndex:number,value: number}> = {};
shield_max: number = 0; // 最大护盾值
// ==================== 脏标签标记 ====================
dirty_hp: boolean = false; // 血量变更标记
@@ -58,11 +48,8 @@ export class HeroAttrsComp extends ecs.Comp {
// ==================== Buff/Debuff 系统 ====================
/** 持久型buff数组 - 不会自动过期 */
BUFFS: Record<number, Array<{value: number, BType: BType}>> = {};
/** 临时型buff数组 - 按时间自动过期 */
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {};
BUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
DEBUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
// ==================== 标记状态 ====================
is_dead: boolean = false;
@@ -81,103 +68,7 @@ export class HeroAttrsComp extends ecs.Comp {
killed_count:number=0;
// 注意:技能数据已迁移到 HeroSkillsComp不再存储在这里
initEvent() {
// 监听升级事件
mLogger.log(this.debugMode, 'HeroAttrs', ` 注册升级事件监听`);
oops.message.on(GameEvent.CanUpdateLv, this.onLevelUp, this);
// 移除卡牌事件监听,改为由 MissionCardComp 直接调用,避免非主角响应
// oops.message.on(GameEvent.UseItemCard, this.onUseItemCard, this);
// oops.message.on(GameEvent.UseAttrCard, this.onUseAttrCard, this);
}
removeEvent() {
mLogger.log(this.debugMode, 'HeroAttrs', ` 移除升级事件监听`);
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
// oops.message.off(GameEvent.UseItemCard, this.onUseItemCard, this);
// oops.message.off(GameEvent.UseAttrCard, this.onUseAttrCard, this);
}
onUseAttrCard(event: string, args: any) {
if (!this.is_master) return;
const uuid = args;
const attrCard = AttrCards[uuid];
if (attrCard) {
mLogger.log(this.debugMode, 'HeroAttrs', ` 使用属性卡: ${attrCard.desc}`);
// 构造 BuffConf默认使用 BType.VALUE永久生效 (time: 0)
const buffConf: BuffConf = {
buff: attrCard.attr,
value: attrCard.value,
BType: BType.VALUE,
time: 0,
chance: 1, // 必中
};
this.addBuff(buffConf);
smc.updateHeroInfo(this); // 确保同步到全局数据
oops.gui.toast(attrCard.desc);
}
}
onUseItemCard(event: string, args: any) {
if (!this.is_master) return;
const itemId = args;
// 1. 尝试从 PotionCards 获取 (新版药水)
const potion = PotionCards[itemId];
if (potion) {
mLogger.log(this.debugMode, 'HeroAttrs', ` 使用药水: ${potion.desc}`);
const buffConf: BuffConf = {
buff: potion.attr,
value: potion.value,
BType: BType.RATIO, // 药水默认是百分比加成
time: potion.duration,
chance: 1,
};
this.addBuff(buffConf);
oops.gui.toast(potion.desc);
return;
}
}
/**
* 处理英雄升级逻辑
*/
onLevelUp(event: string, args: any) {
mLogger.log(this.debugMode, 'HeroAttrs', ` 收到升级事件: is_master=${this.is_master}, args=${JSON.stringify(args)}`);
// 只有主角才响应升级事件
if (!this.is_master) return;
const newLv = args.lv;
mLogger.log(this.debugMode, 'HeroAttrs', ` 英雄升级处理: Lv.${this.lv} -> Lv.${newLv}`);
if (newLv > this.lv) {
this.lv = newLv;
// === 属性成长逻辑 ===
// 根据职业获取成长属性
const jobConf = JobUpConf[this.type as HType] || { hp: 30, ap: 5, def: 1 };
const hpGrow = jobConf.hp;
const apGrow = jobConf.ap;
const defGrow = jobConf.def;
this.base_hp += hpGrow;
this.base_ap += apGrow;
this.base_def += defGrow;
// 重新计算受影响的属性
this.recalculateSingleAttr(Attrs.HP_MAX);
this.recalculateSingleAttr(Attrs.AP);
this.recalculateSingleAttr(Attrs.DEF);
// 升级福利:回复 20% 最大生命值
const healRatio = 0.2;
const healAmount = Math.floor(this.Attrs[Attrs.HP_MAX] * healRatio);
this.add_hp(healAmount, true);
// 同步数据到全局
smc.updateHeroInfo(this);
// 简单的UI提示
// oops.gui.toast(`升级HP+${hpGrow} AP+${apGrow}`);
}
}
start(){
}
@@ -189,55 +80,12 @@ export class HeroAttrsComp extends ecs.Comp {
initAttrs() {
// 清空现有 buff/debuff
this.BUFFS = {};
this.BUFFS_TEMP = {};
this.BUFFS_TAL = {};
this.Talents = {};
this.DEBUFFS = {};
// 获取英雄配置
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.SPEED] = this.base_speed;
// 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.SPEED
) {
this.Attrs[attrIndex] = 0;
}
}
// 加载初始 buff
if (heroInfo.buff && heroInfo.buff.length > 0) {
for (const buffConf of heroInfo.buff) {
this.addBuff(buffConf);
}
}
// 3. 应用全局属性加成 (强制重算受影响的属性)
if (this.fac === 0 && smc.global_attrs) {
for (const key in smc.global_attrs) {
const attrIndex = Number(key);
this.recalculateSingleAttr(attrIndex);
}
}
// 4. 初始化状态值 (确保满状态)
this.hp = this.Attrs[Attrs.HP_MAX];
this.mp = this.Attrs[Attrs.MP_MAX];
smc.updateHeroInfo(this);
}
/*******************基础属性管理********************/
@@ -245,7 +93,7 @@ export class HeroAttrsComp extends ecs.Comp {
const oldHp = this.hp;
let addValue = value;
if(!isValue){
addValue = value * this.Attrs[Attrs.HP_MAX] / 100;
addValue = value * this.hp_max / 100;
}
// ✅ 数据层只负责数据修改,不调用视图层
@@ -255,34 +103,16 @@ export class HeroAttrsComp extends ecs.Comp {
// }
this.hp += addValue;
this.hp = Math.max(0, Math.min(this.hp, this.Attrs[Attrs.HP_MAX]));
this.hp = Math.max(0, Math.min(this.hp, this.hp_max));
this.dirty_hp = true; // ✅ 仅标记需要更新
smc.updateHeroInfo(this);
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
}
add_mp(value:number,isValue:boolean){
const oldMp = this.mp;
let addValue = value;
if(!isValue){
addValue = value * this.Attrs[Attrs.MP_MAX] / 100;
}
// ✅ 数据层只负责数据修改,不调用视图层
// let heroView = this.ent.get(HeroViewComp);
// if(heroView){
// heroView.mp_add(addValue);
// }
this.mp += addValue;
this.mp = Math.max(0, Math.min(this.mp, this.Attrs[Attrs.MP_MAX]));
this.dirty_mp = true; // ✅ 仅标记需要更新
mLogger.log(this.debugMode, 'HeroAttrs', ` MP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldMp.toFixed(1)} -> ${this.mp.toFixed(1)}`);
}
add_shield(value:number,isValue:boolean){
const oldShield = this.shield;
let addValue = value;
if(!isValue){
addValue = value * this.Attrs[Attrs.HP_MAX] / 100;
addValue = value * this.shield_max / 100;
}
this.shield += addValue;
this.dirty_shield = true; // 标记护盾需要更新
@@ -294,268 +124,21 @@ export class HeroAttrsComp extends ecs.Comp {
* @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);
smc.updateHeroInfo(this); // 确保同步到全局数据
}
// ==================== 属性计算系统 ====================
private getBaseValue(attrIndex: number): number {
switch (attrIndex) {
case Attrs.HP_MAX: return this.base_hp;
case Attrs.MP_MAX: return this.base_mp;
case Attrs.DEF: return this.base_def;
case Attrs.AP: return this.base_ap;
case Attrs.SPEED: return this.base_speed;
case Attrs.SHIELD_MAX: return 0;
default: return 0;
}
}
/**
* 重新计算单个属性
* @param attrIndex 属性索引
*/
recalculateSingleAttr(attrIndex: number) {
const oldVal = this.Attrs[attrIndex] || 0;
const baseVal = this.getBaseValue(attrIndex);
// 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;
}
}
}
for (const key in this.BUFFS_TAL) {
const buff = this.BUFFS_TAL[Number(key)];
if (buff.attrIndex === attrIndex && 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;
}
}
}
for (const key in this.BUFFS_TAL) {
const buff = this.BUFFS_TAL[Number(key)];
if (buff.attrIndex === attrIndex && buff.BType === BType.RATIO) {
totalRatio += buff.value;
}
}
// 全局属性加成 (只对英雄生效,怪物不生效)
// this.fac === 0 代表英雄 (FacSet.HERO)
if (this.fac === 0 && smc.global_attrs && smc.global_attrs[attrIndex]) {
const [val, count] = smc.global_attrs[attrIndex];
const globalAdd = val * count;
totalRatio += globalAdd;
// mLogger.log(this.debugMode, 'HeroAttrs', ` 全局加成: ${this.hero_name} Attr=${attrIndex} Val=${val} Count=${count} Add=${globalAdd}%`);
}
// 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);
if (oldVal !== this.Attrs[attrIndex]) {
mLogger.log(this.debugMode, 'HeroAttrs', ` 属性重算: ${this.hero_name}, 属性ID=${attrIndex}, ${oldVal} -> ${this.Attrs[attrIndex]} (Base=${baseVal}, Add=${totalValue-baseVal}, Ratio=${totalRatio}%)`);
}
}
/**
* 确保单个属性值合理
*/
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:
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>();
updateBuffsDebuffs(dt: number) {
// 更新临时型buff
for (const attrIndex in this.BUFFS_TEMP) {
const buffs = this.BUFFS_TEMP[attrIndex];
for (let i = buffs.length - 1; i >= 0; i--) {
const buff = buffs[i];
buff.remainTime -= dt;
if (buff.remainTime <= 0) {
buffs.splice(i, 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;
}
// ==================== 技能距离缓存管理 ====================
/**
@@ -591,117 +174,23 @@ export class HeroAttrsComp extends ecs.Comp {
public getCachedMinSkillDistance(): number {
return this.minSkillDistance;
}
/**
* 添加数值型天赋buff 数值会进行累加count 时叠加层数
* @param t_uuid 天赋唯一标识
* @param attrIndex 属性索引
* @param bType buff类型(VALUE:数值型, RATIO:百分比型)
* @param value buff值
*/
addValueTal(t_uuid: number, attrIndex?: number, bType?: BType, value: number = 0) {
if (attrIndex === undefined || bType === undefined) return;
const buff = this.BUFFS_TAL[t_uuid];
if (!buff) {
this.BUFFS_TAL[t_uuid] = { count: 1, BType: bType, attrIndex, value };
} else {
buff.count += 1;
buff.value += value;
}
this.recalculateSingleAttr(attrIndex);
}
/**
* 添加计数型天赋每次添加数值已最新额覆盖久的count为可用次数
* @param eff 天赋效果ID
* @param value 天赋值
*/
addCountTal(eff: number, value: number) {
const t = this.Talents[eff] || { value: 0, count: 0 };
t.value = value;
t.count += 1;
this.Talents[eff] = t;
}
/**
* 使用计数型天赋,如风怒 ,必爆等天赋,只需要返回是否暴击的,使用这个方法
* @param eff 天赋效果ID
* @returns 是否使用成功
*/
useCountTal(eff: number): boolean {
const t = this.Talents[eff];
if (!t || t.count <= 0) return false;
t.count -= 1;
return true;
}
/**
* 使用计数型天赋并返回天赋值如额外5次 伤害+20% 5次受伤免伤20% 使用这个方法
* @param eff 天赋效果ID
* @returns 天赋值
*/
useCountValTal(eff: number): number {
const t = this.Talents[eff];
if (!t || t.value <= 0) return 0;
t.count -= 1;
return t.value;
}
/**
* 根据UUID移除数值型天赋buff
* @param t_uuid 天赋唯一标识
*/
useValueTalByUuid(t_uuid: number) {
const buff = this.BUFFS_TAL[t_uuid];
if (!buff) return;
buff.count--;
if (buff.count <= 0) {
delete this.BUFFS_TAL[t_uuid];
}
this.recalculateSingleAttr(buff.attrIndex);
}
/**
* 根据属性索引移除数值型天赋buff
* @param attrIndex 属性索引
*/
useValueTalByAttr(attrIndex: number) {
let changed = false;
for (const key in this.BUFFS_TAL) {
const b = this.BUFFS_TAL[Number(key)];
if (b && b.attrIndex === attrIndex) {
delete this.BUFFS_TAL[Number(key)];
changed = true;
}
}
if (changed) this.recalculateSingleAttr(attrIndex);
}
reset() {
this.removeEvent();
// 重置为初始状态
this.hero_uuid = 1001;
this.hero_name = "hero";
this.lv = 1;
this.type = 0;
this.fac = 0;
this.base_ap = 0;
this.base_def = 5;
this.base_hp = 100;
this.base_mp = 100;
this.base_speed = 100;
this.base_dis = 0;
this.ap = 0;
this.hp = 100;
this.speed = 100;
this.dis = 0;
this.hp = 100;
this.mp = 100;
this.shield = 0;
this.Attrs = [];
this.NeAttrs = [];
this.shield_max = 0;
this.BUFFS = {};
this.BUFFS_TEMP = {};
this.BUFFS_TAL = {};
this.Talents = {};
this.DEBUFFS = {};
// 重置技能距离缓存
this.maxSkillDistance = 0;
this.minSkillDistance = 0;

View File

@@ -1,140 +0,0 @@
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { smc } from "../common/SingletonModuleComp";
import { Attrs } from "../common/config/HeroAttrs";
import { HRegen } from "../common/config/heroSet";
import { HeroSkillsComp } from "./HeroSkills";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { mLogger } from "../common/Logger";
/**
* ==================== 英雄属性更新系统 ====================
*
* 按照 ECS 设计理念:
* - ComponentHeroAttrsComp存储数据
* - SystemHeroAttrSystem处理业务逻辑
*
* 系统职责:
* 1. 每帧更新临时 Buff时间递减过期移除
* 2. 每帧更新 HP/MP 自然回复
* 3. 限制属性值在合理范围内
*
/**
* 使用方式:
* 在 RootSystem 中注册此系统,它会自动每帧更新所有拥有 HeroAttrsComp 的实体
*/
@ecs.register('HeroAttrSystem')
export class HeroAttrSystem extends ecs.ComblockSystem
implements ecs.ISystemUpdate, ecs.IEntityEnterSystem, ecs.ISystemFirstUpdate {
// ==================== 调试统计(可选)====================
private entityCount: number = 0; // 本帧处理的实体数
private frameCount: number = 0; // 总帧数
public debugMode: boolean = false; // 是否启用调试模式
private RTimer:Timer=new Timer(5)
/**
* 过滤器:只处理拥有 HeroAttrsComp 的实体
*/
filter(): ecs.IMatcher {
return ecs.allOf(HeroAttrsComp);
}
/**
* 实体首次进入系统时调用(每个实体只调用一次)
*/
entityEnter(e: ecs.Entity): void {
const model = e.get(HeroAttrsComp);
if (!model) return;
mLogger.log(this.debugMode, 'HeroAttrSystem', `[HeroAttrSystem] 英雄进入系统: ${model.hero_name} (uuid: ${model.hero_uuid})`);
}
/**
* 系统首次更新前调用(整个系统只调用一次)
*/
firstUpdate(): void {
mLogger.log(this.debugMode, 'HeroAttrSystem', "[HeroAttrSystem] 系统首次更新");
}
/**
* 每帧更新(为每个英雄调用一次)
*
* ⭐ 关键理解:
* - 如果有 3 个英雄,这个方法每帧会被调用 3 次
* - 每次调用处理不同的实体 e
* - 这是正确的设计,不是 bug
*/
update(e: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
const model = e.get(HeroAttrsComp);
if (!model || model.is_dead || model.is_reviving) return;
// 统计:记录本帧处理的实体数
this.entityCount++;
// 调试日志(可选,调试时启用)
if (this.debugMode) {
mLogger.log(this.debugMode, 'HeroAttrSystem', ` [${this.entityCount}] 更新英雄: ${model.hero_name}, HP: ${model.hp.toFixed(2)}`);
}
// 1. 更新临时 Buff/Debuff时间递减过期自动移除
model.updateTemporaryBuffsDebuffs(this.dt);
// 记录MP变化前的值
const oldMp = model.mp;
if(this.RTimer.update(this.dt)){
// 每5秒回血1次
const hpRegen = model.Attrs[Attrs.HP_REGEN] || 0;
// 回血逻辑 + 视图表现
const totalHpRegen = HRegen.HP + hpRegen;
if (totalHpRegen > 0) {
model.add_hp(totalHpRegen, false);
// 触发视图层回血特效
const view = e.get(HeroViewComp);
if (view) {
view.health(totalHpRegen);
}
}
}
// 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];
}
// 4. 如果MP发生变化更新最大技能距离缓存最小距离不受MP影响
if (model.mp !== oldMp) {
const skillsComp = e.get(HeroSkillsComp);
if (skillsComp) {
model.updateSkillDistanceCache(skillsComp);
}
}
// 每 60 帧输出一次统计
this.frameCount++;
if (this.frameCount % 60 === 0 && this.entityCount === 1) {
mLogger.log(this.debugMode, 'HeroAttrSystem', `[HeroAttrSystem] 第 ${this.frameCount} 帧,处理 ${this.entityCount} 个英雄`);
}
// 注意:显示更新由 HeroViewComp 负责,这里只处理数据
}
/**
* 启用调试模式(调试时使用)
*/
enableDebug() {
this.debugMode = true;
}
/**
* 禁用调试模式(正式运行)
*/
disableDebug() {
this.debugMode = false;
}
}

View File

@@ -1,621 +0,0 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, v3 } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { HSSet, SkillSet, SType, TGroup, SkillConfig } from "../common/config/SkillSet";
import { HeroSkillsComp, SkillSlot } from "./HeroSkills";
import { Skill } from "../skill/Skill";
import { smc } from "../common/SingletonModuleComp";
import { TalComp } from "./TalComp";
import { TalEffet, TriType } from "../common/config/TalSet";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { GameConst } from "../common/config/GameConst";
import { Attrs } from "../common/config/HeroAttrs";
import { mLogger } from "../common/Logger";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CSRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
* - 支持多种AI策略
*/
@ecs.register('SACastSystem')
export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
debugMode: boolean = false; // 是否启用调试模式
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
const skills = e.get(HeroSkillsComp);
if (!skills) return;
// AI 降频每0.2秒执行一次
skills.ai_timer += this.dt;
if (skills.ai_timer < GameConst.Battle.AI_CHECK_INTERVAL) return;
skills.ai_timer = 0;
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!heroAttrs || !heroView) return;
// 检查基本条件
if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return;
// 移除 is_atking 检查实现只要距离和CD满足即施法
// if (!heroAttrs.is_atking) return;
const readySkills = skills.getReadySkills();
if (readySkills.length === 0) return;
// 选择第一个可施放的技能(支持伤害/治疗/护盾)
for (const s_uuid of readySkills) {
const skill = skills.getSkill(s_uuid);
if (!skill) continue;
if (skill.hset === HSSet.max && !skills.max_auto) continue;
const config = SkillSet[skill.s_uuid];
if (!config) continue;
// 根据技能类型检查目标
if (config.SType === SType.damage) {
if (!this.hasEnemyInSkillRange(heroView, heroAttrs, skill.dis)) continue;
} else if (config.SType === SType.heal || config.SType === SType.shield) {
if (!this.hasTeamInSkillRange(heroView, heroAttrs, skill.dis)) continue;
} else if (config.SType === SType.buff) {
if (!this.hasBuffTarget(heroView, heroAttrs, skill.dis, config.TGroup)) continue;
}
// ✅ 开始执行施法
this.startCast(e, skill, skill.hset);
// 一次只施放一个技能
break;
}
}
private startCast(e: ecs.Entity,skill:SkillSlot,hset:HSSet): boolean {
if (!skill||!e) return false
const skills = e.get(HeroSkillsComp);
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
// 3. 检查施法条件
if (!this.checkCastConditions(skills, heroAttrs, skill.s_uuid)) return false
// 4. 执行施法
const castSucess = this.executeCast(e, skill.s_uuid, heroView,hset);
// 5. 扣除资源和重置CD
if (castSucess) {
// 🔥 怪物不消耗蓝
if (heroAttrs.fac !== FacSet.MON) {
// 手动更新技能距离缓存
heroAttrs.updateSkillDistanceCache(skills);
}
skills.resetCD(skill.s_uuid);
}
return castSucess;
}
public manualCast(e: ecs.Entity, s_uuid: number): boolean {
if (!e) return false
const skills = e.get(HeroSkillsComp)
const heroAttrs = e.get(HeroAttrsComp)
const heroView = e.get(HeroViewComp)
if (!skills || !heroAttrs || !heroView) return false
const slot = skills.getSkill(s_uuid)
if (!slot) return false
return this.startCast(e, slot, slot.hset)
}
public manualCastMax(e: ecs.Entity): boolean {
const skills = e.get(HeroSkillsComp)
if (!skills) return false
for (const key in skills.skills) {
const s_uuid = Number(key)
const slot = skills.getSkill(s_uuid)
if (slot && slot.hset === HSSet.max) {
return this.manualCast(e, s_uuid)
}
}
return false
}
/**
* 检查施法条件
*/
private checkCastConditions(skills: HeroSkillsComp, heroAttrs: HeroAttrsComp, s_uuid: number): boolean {
// 检查角色状态
if (heroAttrs.is_dead) {
return false;
}
// 检查控制状态(眩晕、冰冻)
if (heroAttrs.isStun() || heroAttrs.isFrost()) {
return false;
}
// 检查CD
if (!skills.canCast(s_uuid)) {
return false;
}
return true;
}
/**
* 执行施法
*/
private executeCast(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp,hset:HSSet): boolean {
const heroAttrs=casterEntity.get(HeroAttrsComp)
const config = SkillSet[s_uuid];
if (!config) {
mLogger.error(this.debugMode, 'SACastSystem', "[SACastSystem] 技能配置不存在:", s_uuid);
return false;
}
// 1. 播放施法动画
heroView.playSkillEffect(s_uuid);
/**********************天赋处理*************************************************************************/
// 2. 更新攻击类型的天赋触发值,技能和必杀级
if(casterEntity.has(TalComp)){
const talComp = casterEntity.get(TalComp);
if (hset === HSSet.atk) talComp.updateCur(TriType.ATK);
if (hset === HSSet.skill) talComp.updateCur(TriType.SKILL);
}
/**********************天赋处理*************************************************************************/
// 根据技能类型执行不同逻辑
if (config.SType === SType.heal) {
return this.executeHealSkill(casterEntity, s_uuid, heroView, hset);
} else if (config.SType === SType.shield) {
return this.executeShieldSkill(casterEntity, s_uuid, heroView, hset);
} else if (config.SType === SType.buff) {
return this.executeBuffSkill(casterEntity, s_uuid, heroView, hset);
}
// 获取目标位置(伤害技能)
let targets = this.sTargets(heroView, s_uuid);
if (targets.length === 0) {
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 没有找到有效目标");
return false;
}
// 2.1 普通攻击逻辑
if (hset === HSSet.atk){
let delay = GameConst.Battle.SKILL_CAST_DELAY
let ext_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG);
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext_dmg);
}, delay);
//风怒wfuny 只针对 普通攻击起效
if (heroAttrs.useCountTal(TalEffet.WFUNY)){
let ext2_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG);
let delay = GameConst.Battle.SKILL_CAST_DELAY
heroView.playSkillEffect(s_uuid);
//需要再添加 风怒动画
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext2_dmg);
},delay);
}
}
// 2.2 技能攻击逻辑
if(hset === HSSet.skill){
let delay = GameConst.Battle.SKILL_CAST_DELAY
let ext_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG);
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext_dmg);
}, delay);
// 双技能 只针对 技能起效
if(heroAttrs.useCountTal(TalEffet.D_SKILL)){
let ext2_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG);
let delay = GameConst.Battle.SKILL_CAST_DELAY
heroView.playSkillEffect(s_uuid);
//需要再添加 双技能动画
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext2_dmg);
},delay);
}
}
// 2.3 必杀技能逻辑
if(hset === HSSet.max){
let delay = GameConst.Battle.SKILL_CAST_DELAY
heroView.playSkillEffect(s_uuid);
//需要再添加 最大伤害动画
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets);
},delay);
}
return true;
}
/**
* 创建技能实体
*/
private createSkill(s_uuid: number, caster: HeroViewComp,targets:Vec3[]=[],ext_dmg:number=0) {
// 检查节点有效性
if (!caster.node || !caster.node.isValid) {
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 施法者节点无效");
return;
}
// 获取场景节点
const parent = caster.node.parent;
if (!parent) {
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 场景节点无效");
return;
}
// 创建技能实体
const skill = ecs.getEntity<Skill>(Skill);
// 获取施法者位置作为起始位置
const startPos = caster.node.position.clone();
const targetPos = targets[0]; // 使用第一个目标位置
// mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem]: ${s_uuid}, 起始位置: ${startPos}, 目标位置: ${targetPos}`);
// 加载技能实体(包括预制体、组件初始化等)
skill.load(startPos, parent, s_uuid, targetPos, caster,ext_dmg);
}
/**
* 选择目标位置
*/
private sTargets(caster: HeroViewComp, s_uuid: number): Vec3[] {
const heroAttrs = caster.ent.get(HeroAttrsComp);
if (!heroAttrs) return [];
const config = SkillSet[s_uuid];
if (!config) return this.sDefaultTargets(caster, heroAttrs.fac);
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, config.t_num ?? 1);
const targets = this.sDamageTargets(caster, config, maxTargets);
if (targets.length === 0) {
targets.push(...this.sDefaultTargets(caster, heroAttrs.fac));
}
return targets;
}
/**
* 选择伤害技能目标
*/
private sDamageTargets(caster: HeroViewComp, config: SkillConfig, maxTargets: number): Vec3[] {
const targets: Vec3[] = [];
const heroAttrs = caster.ent.get(HeroAttrsComp);
if (!heroAttrs) return targets;
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
const enemyPositions = this.findNearbyEnemies(caster, heroAttrs.fac, range);
// 选择最多maxTargets个目标
for (let i = 0; i < Math.min(maxTargets, enemyPositions.length); i++) {
targets.push(enemyPositions[i]);
}
// 如果没有找到敌人,使用默认位置
if (targets.length === 0) {
targets.push(...this.sDefaultTargets(caster, heroAttrs.fac));
}
return targets;
}
/**
* 选择默认目标
*/
private sDefaultTargets(caster: HeroViewComp, fac: number): Vec3[] {
const targets: Vec3[] = [];
const defaultX = fac === 0 ? GameConst.Battle.DEFAULT_TARGET_X_RIGHT : GameConst.Battle.DEFAULT_TARGET_X_LEFT;
targets.push(v3(defaultX, BoxSet.GAME_LINE, GameConst.Battle.DEFAULT_TARGET_Z));
return targets;
}
/**
* 查找附近的敌人
*/
private findNearbyEnemies(caster: HeroViewComp, fac: number, range: number): Vec3[] {
const enemies: Vec3[] = [];
if (!caster || !caster.node) return enemies;
const currentPos = caster.node.position;
const results: { pos: Vec3; dist: number; laneBias: number }[] = [];
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return false;
if (model.is_dead) return false;
if (model.fac === fac) return false;
const pos = view.node.position.clone();
pos.y += GameConst.Battle.SEARCH_Y_OFFSET;
const dist = Math.abs(currentPos.x - pos.x);
if (dist <= range) {
const laneBias = Math.abs(currentPos.y - pos.y);
results.push({ pos: pos, dist, laneBias });
}
return false;
});
results.sort((a, b) => {
if (a.laneBias !== b.laneBias) return a.laneBias - b.laneBias;
return a.dist - b.dist;
});
for (const r of results) enemies.push(r.pos);
return enemies;
}
/**
* 检查技能攻击范围内是否有敌人
*/
private hasEnemyInSkillRange(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number): boolean {
if (!heroView || !heroView.node) return false;
const currentPos = heroView.node.position;
const team = heroAttrs.fac;
let found = false;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac !== team && !model.is_dead) {
if (distance <= skillDistance) {
found = true;
return true;
}
}
});
return found;
}
/**
* 检查技能范围内是否有友军
*/
private hasTeamInSkillRange(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number): boolean {
if (!heroView || !heroView.node) return false;
const currentPos = heroView.node.position;
const team = heroAttrs.fac;
let found = false;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Math.abs(currentPos.x - view.node.position.x);
if (model.fac === team && !model.is_dead) {
if (distance <= skillDistance) {
found = true;
return true;
}
}
});
return found;
}
/**
* 检查Buff技能是否有目标
*/
private hasBuffTarget(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number, tGroup: TGroup): boolean {
if (tGroup === TGroup.Self) return true; // 自身Buff总是可以释放
// 如果是团队Buff检查范围内是否有队友
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
return this.hasTeamInSkillRange(heroView, heroAttrs, skillDistance);
}
return false;
}
/**
* 执行Buff技能
*/
private executeBuffSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config || !config.buffs || config.buffs.length === 0) return false;
const targets = this.sBuffTargets(casterEntity, heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const delay = GameConst.Battle.SKILL_CAST_DELAY;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
if (!targetAttrs) continue;
// 应用所有配置的Buff
for (const buffConf of config.buffs) {
// 检查概率
if (buffConf.chance >= 1 || Math.random() < buffConf.chance) {
targetAttrs.addBuff(buffConf);
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] Buff生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, Buff类型=${buffConf.buff}, 值=${buffConf.value}`);
}
}
}
}, delay);
return true;
}
/**
* 选择Buff目标
*/
private sBuffTargets(casterEntity: ecs.Entity, casterView: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const tGroup = config.TGroup;
// 1. 自身
if (tGroup === TGroup.Self) {
targets.push(casterEntity);
return targets;
}
// 2. 团队/友军
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return; // 必须是同阵营
if (model.is_dead) return;
const distance = Math.abs(casterView.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
return targets.slice(0, maxTargets);
}
return targets;
}
/**
* 执行治疗技能
*/
private executeHealSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config) return false;
const targets = this.sHealTargets(heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const healAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
const delay = GameConst.Battle.SKILL_CAST_DELAY;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
const targetView = targetEntity.get(HeroViewComp);
if (!targetAttrs || !targetView) continue;
targetAttrs.add_hp(healAmount, true);
targetView.health(healAmount);
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] 治疗生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 治疗量=${healAmount}`);
}
}, delay);
return true;
}
/**
* 执行护盾技能
*/
private executeShieldSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config) return false;
const targets = this.sShieldTargets(heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const shieldAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
const delay = GameConst.Battle.SKILL_CAST_DELAY;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
const targetView = targetEntity.get(HeroViewComp);
if (!targetAttrs || !targetView) continue;
targetAttrs.add_shield(shieldAmount, true);
targetView.add_shield(shieldAmount);
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] 护盾生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 护盾量=${shieldAmount}`);
}
}, delay);
return true;
}
/**
* 选择治疗目标
*/
private sHealTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return;
if (model.is_dead) return;
const distance = Math.abs(caster.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
targets.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
if (!attrsA || !attrsB) return 0;
return attrsA.hp - attrsB.hp;
});
return targets.slice(0, maxTargets);
}
/**
* 选择护盾目标
*/
private sShieldTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return;
if (model.is_dead) return;
const distance = Math.abs(caster.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
return targets.slice(0, maxTargets);
}
/**
* 根据位置查找实体
*/
private findEntityAtPosition(pos: Vec3): ecs.Entity | null {
let foundEntity: ecs.Entity | null = null;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Vec3.distance(pos, view.node.position);
if (distance < 50) {
foundEntity = e;
return true;
}
return false;
});
return foundEntity;
}
}

View File

@@ -1,51 +0,0 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { smc } from "../common/SingletonModuleComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroSkillsComp } from "./HeroSkills";
/**
* ==================== 技能CD更新系统 ====================
*
* 职责:
* 1. 每帧更新所有角色的技能CD
* 2. 自动递减CD时间
* 3. 管理技能冷却状态
* 4. 优化CD计算性能
*
* 设计理念:
* - 独立的CD管理系统
* - 只负责时间递减,不处理施法逻辑
* - 支持CD加速和减免效果
*/
@ecs.register('SkillCDSystem')
export class SkillCDSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
filter(): ecs.IMatcher {
return ecs.allOf(HeroSkillsComp);
}
update(e: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
const skills = e.get(HeroSkillsComp);
if (!skills) return;
const attrsCom = e.get(HeroAttrsComp);
if (!attrsCom) return;
if(smc.mission.stop_mon_action) return;
if (attrsCom.isStun() || attrsCom.isFrost()) { // 眩晕和冰冻状态不更新CD
return;
}
// 更新所有技能CD
skills.updateCDs(this.dt);
}
/**
* 计算CD减免效果
*/
/**
* 更新技能就绪状态
*/
}

View File

@@ -1,296 +0,0 @@
import { _decorator } from "cc";
import { mLogger } from "../common/Logger";
import { basename } from "path/win32";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { Attrs, BType } from "../common/config/HeroAttrs";
import { BuffConf } from "../common/config/SkillSet";
import { TalAttrs, talConf, TalEffet, TalTarget, TriType} from "../common/config/TalSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { ITalPts } from "../common/config/heroSet";
const { property } = _decorator;
/**
* 天赋槽位接口定义
* 描述单个天赋的数据结构
*/
export interface TalSlot {
uuid: number; // 天赋唯一标识符
name: string; // 天赋名称
triType: TriType; // 天赋触发类型
target: TalTarget;
effet: TalEffet;
attrs?:TalAttrs //触发的attrs效果的对应attrs value: number; // 触发的效果数值
vType:BType; // 数值型还是百分比型
value: number; // 触发的效果数值
value_add: number; // 触发的效果数值增量
count: number; // 执行次数,及可以触发的次数
count_add: number; // 执行次数增量
Trigger: number; // 天赋触发阈值
Trigger_add: number; // 天赋触发阈值减值
desc: string; // 天赋描述(说明触发条件和效果)
cur: number; // 当前累积值
}
/**
* 天赋系统组件类
* 作为ECS架构中的组件负责管理英雄的天赋系统
*
* 核心功能:
* - 初始化英雄天赋系统
* - 添加新天赋到英雄
* - 累积天赋触发进度
* - 检查并触发满足条件的天赋
* - 管理天赋效果数值
*/
@ecs.register('TalComp', true)
export class TalComp extends ecs.Comp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false;
/** 天赋集合以天赋ID为键存储所有已获得的天赋 */
Tals: Record<number, TalSlot> = {};
TalPts:Record<number,ITalPts> = {};
init(heroUuid: number) {
// 初始化天赋集合
this.Tals = {};
this.TalPts = {};
// 监听升级事件
oops.message.on(GameEvent.CanUpdateLv, this.onLevelUp, this);
// 监听天赋选择事件
oops.message.on(GameEvent.UseTalentCard, this.onUseTalentCard, this);
}
onDestroy() {
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
oops.message.off(GameEvent.UseTalentCard, this.onUseTalentCard, this);
}
/**
* 处理天赋选择事件
* @param event 事件名
* @param args 天赋UUID
*/
private onUseTalentCard(event: string, args: any) {
const uuid = args as number;
mLogger.log(this.debugMode, 'TalComp', `[TalComp] 收到天赋选择事件,添加天赋 ID: ${uuid}`);
this.addTal(uuid);
}
/**
* 处理英雄升级事件,触发升级类型的天赋
*/
private onLevelUp(event: string, args: any) {
// 只有当前实体是主角时才处理虽然TalComp只挂载在主角上但为了安全起见可以再确认或者直接处理
// GameEvent.CanUpdateLv 事件参数 { lv: number }
mLogger.log(this.debugMode, 'TalComp', `[TalComp] 监听到升级事件,当前等级: ${args.lv}`);
}
/**
* 为英雄添加一个新天赋
* @param uuid 要添加的天赋ID
*
* 添加流程:
* 1. 检查天赋是否已存在
* 2. 检查天赋配置是否存在
* 3. 创建并初始化天赋数据
*/
addTal(uuid: number,v_add:number = 0,c_add:number = 0,t_add:number = 0) {
// 检查天赋是否已存在
if (this.Tals[uuid]) {
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋已存在,执行叠加逻辑 ID:${uuid}`);
const tConf = talConf[uuid];
if (tConf) {
// 叠加效果数值
this.Tals[uuid].value_add += tConf.value;
}
return;
}
// 获取天赋配置
const tConf = talConf[uuid];
if (!tConf) {
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋配置不存在,天赋ID:${uuid}`);
return;
}
// 创建并初始化天赋数据
this.Tals[uuid] = {
uuid: uuid,
name: tConf.name,
triType: tConf.triType,
target: tConf.target,
effet: tConf.effet,
attrs: tConf.attrs,
vType: tConf.vType,
value: tConf.value, // 效果数值初始为配置值
value_add: v_add, // 效果数值增量初始为0
count: tConf.count, // 执行次数,及可以触发的次数
count_add: c_add, // 执行次数增量初始为0
Trigger: tConf.Trigger, // 触发阈值(后续可从配置中读取)
Trigger_add: t_add, // 触发阈值增量初始为0
desc: tConf.desc,
cur: 0, // 当前累积值初始为0
};
mLogger.log(this.debugMode, 'TalComp', `[TalComp]添加天赋成功,天赋ID:${uuid}`);
}
checkTal() {
return Object.keys(this.Tals).length > 0;
}
getTriggers() {
// 存储所有触发的天赋
let Triggers: Record<string, TalSlot> = {};
// 遍历所有天赋
for (let uuid in this.Tals) {
const talent = this.Tals[uuid];
if (talent.cur >= (talent.Trigger - talent.Trigger_add)) { // 修复触发条件,累积值达到或超过触发阈值时触发
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋触发,天赋ID:${uuid}`);
// 重置累积值
talent.cur = 0;
// 添加到触发列表
Triggers[uuid] = talent;
}
}
// 判断是否有天赋被触发
return Triggers;
}
/**
* 更新天赋的效果数值
* @param uuid 天赋ID
* @param val 要增减的数值
*
* 功能:
* - 用于调整天赋的实际效果数值
* - 可通过正负数来增加或减少效果
*/
updateVal(uuid: number, val: number) {
// 检查天赋是否存在
if (!this.Tals[uuid]) {
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
return;
}
// 更新天赋效果数值
this.Tals[uuid].value_add += val;
}
updateTrigger(uuid: number, val: number) {
// 检查天赋是否存在
if (!this.Tals[uuid]) {
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
return;
}
// 更新天赋触发阈值
this.Tals[uuid].Trigger_add += val;
if (this.Tals[uuid].Trigger-this.Tals[uuid].Trigger_add <= 1) {
this.Tals[uuid].Trigger_add = this.Tals[uuid].Trigger-1;
}
}
/**
* 更新指定类型天赋的累积值
* @param triType 天赋触发类型
* @param val 要增加的累积值默认值为1
*
* 设计注意:
* - 当前实现只会更新第一个匹配类型的天赋
* - 累积值用于后续判断是否触发天赋效果
*/
updateCur(triType: TriType, val: number = 1) {
// 遍历所有天赋
for (let uuid in this.Tals) {
const talent = this.Tals[uuid];
// 找到所有匹配类型的天赋并更新
if (talent.triType == triType) {
talent.cur += val;
this.checkTrigger(talent.uuid);
}
}
}
checkTrigger(uuid:number){
const talent = this.Tals[uuid];
if (talent.cur >= (talent.Trigger - talent.Trigger_add)) { // 修复触发条件,累积值达到或超过触发阈值时触发
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋触发,天赋ID:${uuid}`);
for(let i=0;i<(talent.count+talent.count_add);i++){
this.doTriggerTal(talent.uuid);
}
// 重置累积值
talent.cur = 0;
}
}
//执行天赋触发效果
// 功能:
// - 根据天赋类型执行相应的效果
// - 支持计数型和数值型天赋
// --heroAttrs.addTalent 是计数型天赋buff heroAttrs.addTalBuff 是数值型天赋buff
doTriggerTal(uuid: number) {
// 检查天赋是否存在
if (!this.Tals[uuid]) {
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
return;
}
const talent = this.Tals[uuid];
const heroAttrs=this.ent.get(HeroAttrsComp);
switch(talent.effet){
case TalEffet.ATK_DMG:
heroAttrs.addCountTal(TalEffet.ATK_DMG, talent.value + talent.value_add);
break;
case TalEffet.SKILL_DMG:
heroAttrs.addCountTal(TalEffet.SKILL_DMG, talent.value + talent.value_add);
break;
case TalEffet.DEF:
heroAttrs.addCountTal(TalEffet.DEF, talent.value + talent.value_add);
break;
case TalEffet.HP:
heroAttrs.add_hp(talent.value + talent.value_add,talent.vType == BType.VALUE);
break;
case TalEffet.WFUNY:
heroAttrs.addCountTal(TalEffet.WFUNY, talent.value + talent.value_add);
break;
case TalEffet.D_SKILL:
heroAttrs.addCountTal(TalEffet.D_SKILL, talent.value + talent.value_add);
break;
case TalEffet.C_ATK:
heroAttrs.addCountTal(TalEffet.C_ATK, talent.value + talent.value_add);
break;
case TalEffet.C_SKILL:
heroAttrs.addCountTal(TalEffet.C_SKILL, talent.value + talent.value_add);
break;
case TalEffet.C_MSKILL:
heroAttrs.addCountTal(TalEffet.C_MSKILL, talent.value + talent.value_add);
break;
case TalEffet.BUFF: //临时buff
heroAttrs.addValueTal(talent.uuid, talent.attrs, talent.vType, talent.value + talent.value_add);
break;
case TalEffet.ATTR: //永久属性
// 修正:强制类型转换为 number (Attrs 是数字枚举)
let attrIndex = talent.attrs as unknown as number;
// 如果 TalAttrs 存在且需要映射,请取消注释下一行
// attrIndex = Attrs[TalAttrs[talent.attrs]];
let b:BuffConf={buff:attrIndex, BType:talent.vType, value:talent.value + talent.value_add, time:0, chance:100 }
heroAttrs.addBuff(b);
break;
}
}
/**
* 重置组件状态
*/
reset() {
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
oops.message.off(GameEvent.UseTalentCard, this.onUseTalentCard, this);
this.Tals = {};
}
}